From ccd4b42ecb9b01252bde040fb1cb30e931db9e9a Mon Sep 17 00:00:00 2001 From: Saty <5752427+SatyPardus@users.noreply.github.com> Date: Mon, 29 May 2023 22:23:11 +0200 Subject: [PATCH] Public version 1.0 --- .gitattributes | 2 + .gitignore | 331 +++ SRMP.sln | 37 + SRMP/ChatUI.cs | 188 ++ SRMP/Compression.cs | 242 ++ SRMP/Console/ConsoleInput.cs | 103 + SRMP/Console/ConsoleWindow.cs | 69 + SRMP/Extensions.cs | 249 ++ SRMP/Globals.cs | 78 + .../Encryption/NetAESEncryption.cs | 38 + .../Encryption/NetBlockEncryptionBase.cs | 89 + .../Encryption/NetCryptoProviderBase.cs | 77 + .../Encryption/NetCryptoProviderEncryption.cs | 59 + .../Encryption/NetDESEncryption.cs | 26 + .../Encryption/NetEncryption.cs | 45 + .../Encryption/NetRC2Encryption.cs | 26 + .../Encryption/NetTripleDESEncryption.cs | 26 + .../Encryption/NetXorEncryption.cs | 66 + .../Encryption/NetXteaEncryption.cs | 154 ++ SRMP/Lidgren.Network/NamespaceDoc.cs | 14 + SRMP/Lidgren.Network/NetBigInteger.cs | 2352 +++++++++++++++++ SRMP/Lidgren.Network/NetBitVector.cs | 172 ++ SRMP/Lidgren.Network/NetBitWriter.cs | 513 ++++ SRMP/Lidgren.Network/NetBuffer.Peek.cs | 312 +++ .../NetBuffer.Read.Reflection.cs | 103 + SRMP/Lidgren.Network/NetBuffer.Read.cs | 713 +++++ .../NetBuffer.Write.Reflection.cs | 94 + SRMP/Lidgren.Network/NetBuffer.Write.cs | 703 +++++ SRMP/Lidgren.Network/NetBuffer.cs | 98 + SRMP/Lidgren.Network/NetClient.cs | 176 ++ .../NetConnection.Handshake.cs | 499 ++++ SRMP/Lidgren.Network/NetConnection.Latency.cs | 141 + SRMP/Lidgren.Network/NetConnection.MTU.cs | 182 ++ SRMP/Lidgren.Network/NetConnection.cs | 584 ++++ .../NetConnectionStatistics.cs | 213 ++ SRMP/Lidgren.Network/NetConnectionStatus.cs | 68 + SRMP/Lidgren.Network/NetConstants.cs | 57 + SRMP/Lidgren.Network/NetDeliveryMethod.cs | 46 + SRMP/Lidgren.Network/NetException.cs | 74 + .../Lidgren.Network/NetFragmentationHelper.cs | 175 ++ SRMP/Lidgren.Network/NetFragmentationInfo.cs | 12 + SRMP/Lidgren.Network/NetIncomingMessage.cs | 119 + .../Lidgren.Network/NetIncomingMessageType.cs | 105 + SRMP/Lidgren.Network/NetMessageType.cs | 177 ++ SRMP/Lidgren.Network/NetNatIntroduction.cs | 162 ++ SRMP/Lidgren.Network/NetOutgoingMessage.cs | 142 + SRMP/Lidgren.Network/NetPeer.Discovery.cs | 69 + SRMP/Lidgren.Network/NetPeer.Fragmentation.cs | 169 ++ SRMP/Lidgren.Network/NetPeer.Internal.cs | 761 ++++++ .../NetPeer.LatencySimulation.cs | 311 +++ SRMP/Lidgren.Network/NetPeer.Logging.cs | 63 + SRMP/Lidgren.Network/NetPeer.MessagePools.cs | 243 ++ SRMP/Lidgren.Network/NetPeer.Send.cs | 255 ++ SRMP/Lidgren.Network/NetPeer.cs | 389 +++ SRMP/Lidgren.Network/NetPeerConfiguration.cs | 541 ++++ SRMP/Lidgren.Network/NetPeerStatistics.cs | 158 ++ SRMP/Lidgren.Network/NetPeerStatus.cs | 49 + SRMP/Lidgren.Network/NetQueue.cs | 356 +++ .../NetRandom.Implementations.cs | 281 ++ SRMP/Lidgren.Network/NetRandom.cs | 177 ++ SRMP/Lidgren.Network/NetRandomSeed.cs | 45 + .../Lidgren.Network/NetReceiverChannelBase.cs | 18 + .../NetReliableOrderedReceiver.cs | 89 + .../NetReliableSenderChannel.cs | 290 ++ .../NetReliableSequencedReceiver.cs | 65 + .../NetReliableUnorderedReceiver.cs | 97 + SRMP/Lidgren.Network/NetSRP.cs | 179 ++ SRMP/Lidgren.Network/NetSendResult.cs | 30 + SRMP/Lidgren.Network/NetSenderChannelBase.cs | 28 + SRMP/Lidgren.Network/NetServer.cs | 97 + .../NetStoredReliableMessage.cs | 19 + SRMP/Lidgren.Network/NetTime.cs | 42 + SRMP/Lidgren.Network/NetTuple.cs | 19 + SRMP/Lidgren.Network/NetUPnP.cs | 283 ++ .../NetUnreliableSenderChannel.cs | 142 + .../NetUnreliableSequencedReceiver.cs | 33 + .../NetUnreliableUnorderedReceiver.cs | 23 + SRMP/Lidgren.Network/NetUtility.cs | 469 ++++ .../Platform/PlatformAndroid.cs | 91 + .../Platform/PlatformConstrained.cs | 98 + .../Platform/PlatformUnityExtras.cs | 183 ++ .../Lidgren.Network/Platform/PlatformWin32.cs | 156 ++ .../Lidgren.Network/Platform/PlatformWinRT.cs | 102 + SRMP/MainSRML.cs | 93 + SRMP/MainStandalone.cs | 71 + SRMP/MultiplayerUI.cs | 393 +++ SRMP/Networking/NetworkAccessDoor.cs | 15 + SRMP/Networking/NetworkActor.cs | 258 ++ SRMP/Networking/NetworkAmmo.cs | 48 + SRMP/Networking/NetworkClient.cs | 264 ++ SRMP/Networking/NetworkClientUI.cs | 84 + .../Networking/NetworkDirectedActorSpawner.cs | 18 + SRMP/Networking/NetworkDrone.cs | 131 + SRMP/Networking/NetworkDroneAmmo.cs | 18 + SRMP/Networking/NetworkExchangeAcceptor.cs | 16 + SRMP/Networking/NetworkFireColumn.cs | 16 + SRMP/Networking/NetworkGadgetSite.cs | 95 + SRMP/Networking/NetworkGordo.cs | 71 + SRMP/Networking/NetworkHandlerClient.cs | 2048 ++++++++++++++ SRMP/Networking/NetworkHandlerServer.cs | 1773 +++++++++++++ SRMP/Networking/NetworkHostUI.cs | 27 + SRMP/Networking/NetworkKookadobaPatchNode.cs | 16 + SRMP/Networking/NetworkLandplot.cs | 18 + SRMP/Networking/NetworkMasterServer.cs | 209 ++ SRMP/Networking/NetworkNutcracker.cs | 38 + SRMP/Networking/NetworkPlayer.Animation.cs | 305 +++ SRMP/Networking/NetworkPlayer.cs | 465 ++++ SRMP/Networking/NetworkPlayerAmmo.cs | 18 + SRMP/Networking/NetworkPlayerDisplayOnMap.cs | 117 + SRMP/Networking/NetworkPlayerSave.cs | 20 + SRMP/Networking/NetworkPuzzleSlot.cs | 15 + SRMP/Networking/NetworkRaceTrigger.cs | 39 + SRMP/Networking/NetworkRegion.cs | 110 + SRMP/Networking/NetworkServer.cs | 432 +++ SRMP/Networking/NetworkSpawnResource.cs | 32 + SRMP/Networking/NetworkTreasurePod.cs | 74 + .../NetworkWorldStateMasterSwitch.cs | 15 + .../AccessDoors/PacketAccessDoorOpen.cs | 18 + SRMP/Packets/AccessDoors/PacketAccessDoors.cs | 55 + SRMP/Packets/Actors/PacketActorAttach.cs | 21 + SRMP/Packets/Actors/PacketActorDestroy.cs | 18 + SRMP/Packets/Actors/PacketActorEmotions.cs | 21 + SRMP/Packets/Actors/PacketActorFX.cs | 27 + SRMP/Packets/Actors/PacketActorFeral.cs | 20 + SRMP/Packets/Actors/PacketActorOwner.cs | 19 + SRMP/Packets/Actors/PacketActorPosition.cs | 21 + .../Actors/PacketActorReproduceTime.cs | 19 + .../Actors/PacketActorResourceState.cs | 21 + SRMP/Packets/Actors/PacketActorSpawn.cs | 24 + SRMP/Packets/Actors/PacketActors.cs | 151 ++ SRMP/Packets/Drones/PacketDroneActive.cs | 19 + SRMP/Packets/Drones/PacketDroneAmmoAdd.cs | 19 + SRMP/Packets/Drones/PacketDroneAmmoClear.cs | 18 + SRMP/Packets/Drones/PacketDroneAmmoRemove.cs | 18 + SRMP/Packets/Drones/PacketDroneAnimation.cs | 19 + SRMP/Packets/Drones/PacketDroneLiquid.cs | 18 + SRMP/Packets/Drones/PacketDronePosition.cs | 21 + SRMP/Packets/Drones/PacketDronePrograms.cs | 49 + .../Drones/PacketDroneStationEnabled.cs | 19 + SRMP/Packets/Exchanges/PacketExchangeClear.cs | 18 + SRMP/Packets/Exchanges/PacketExchangeOffer.cs | 64 + .../Packets/Exchanges/PacketExchangeOffers.cs | 95 + .../Exchanges/PacketExchangePrepareDaily.cs | 42 + .../Exchanges/PacketExchangeTryAccept.cs | 19 + SRMP/Packets/FX/PacketGlobalFX.cs | 20 + SRMP/Packets/FX/PacketIncinerateFX.cs | 23 + SRMP/Packets/FX/PacketPlayAudio.cs | 21 + SRMP/Packets/Fashions/PacketFashionAttach.cs | 21 + .../Fashions/PacketFashionDetachAll.cs | 23 + .../FireColumn/PacketFireColumnActivate.cs | 18 + .../Packets/FireColumn/PacketFireStormMode.cs | 18 + SRMP/Packets/Gadgets/PacketGadgetAdd.cs | 18 + .../Gadgets/PacketGadgetAddBlueprint.cs | 18 + .../Gadgets/PacketGadgetEchoNetTime.cs | 18 + .../Gadgets/PacketGadgetExtractorUpdate.cs | 22 + .../Gadgets/PacketGadgetRefinerySpend.cs | 42 + SRMP/Packets/Gadgets/PacketGadgetRemove.cs | 18 + SRMP/Packets/Gadgets/PacketGadgetRotation.cs | 19 + .../Gadgets/PacketGadgetSnareAttach.cs | 19 + .../Packets/Gadgets/PacketGadgetSnareGordo.cs | 19 + SRMP/Packets/Gadgets/PacketGadgetSpawn.cs | 20 + SRMP/Packets/Gadgets/PacketGadgetSpend.cs | 18 + SRMP/Packets/Gadgets/PacketGadgetTurrets.cs | 53 + SRMP/Packets/Gadgets/PacketGadgets.cs | 195 ++ SRMP/Packets/Gordos/PacketGordoEat.cs | 23 + SRMP/Packets/Gordos/PacketGordos.cs | 66 + SRMP/Packets/LandPlots/PacketLandPlotAsh.cs | 19 + .../LandPlots/PacketLandPlotCollect.cs | 21 + .../LandPlots/PacketLandPlotFeederSpeed.cs | 19 + .../LandPlots/PacketLandPlotPlantGarden.cs | 20 + .../LandPlots/PacketLandPlotReplace.cs | 19 + .../LandPlots/PacketLandPlotSiloAmmoAdd.cs | 55 + .../LandPlots/PacketLandPlotSiloAmmoClear.cs | 20 + .../LandPlots/PacketLandPlotSiloAmmoRemove.cs | 21 + .../LandPlots/PacketLandPlotSiloInsert.cs | 23 + .../LandPlots/PacketLandPlotSiloRemove.cs | 22 + .../LandPlots/PacketLandPlotSiloSlot.cs | 20 + .../PacketLandPlotStartCollection.cs | 18 + .../LandPlots/PacketLandPlotUpgrade.cs | 18 + SRMP/Packets/LandPlots/PacketLandplots.cs | 151 ++ SRMP/Packets/Oasis/PacketOasis.cs | 56 + SRMP/Packets/Oasis/PacketOasisLive.cs | 18 + SRMP/Packets/Others/PacketGingerAction.cs | 20 + SRMP/Packets/Others/PacketGingerAttach.cs | 19 + SRMP/Packets/Others/PacketKookadobaAction.cs | 20 + SRMP/Packets/Others/PacketKookadobaAttach.cs | 19 + SRMP/Packets/Pedia/PacketPediaShowPopup.cs | 18 + SRMP/Packets/Pedia/PacketPediaUnlock.cs | 41 + SRMP/Packets/Players/PacketPlayerChat.cs | 18 + SRMP/Packets/Players/PacketPlayerCurrency.cs | 20 + .../Players/PacketPlayerCurrencyDisplay.cs | 19 + SRMP/Packets/Players/PacketPlayerFX.cs | 32 + SRMP/Packets/Players/PacketPlayerJoined.cs | 18 + SRMP/Packets/Players/PacketPlayerLeft.cs | 17 + SRMP/Packets/Players/PacketPlayerLoaded.cs | 18 + SRMP/Packets/Players/PacketPlayerPosition.cs | 22 + SRMP/Packets/Players/PacketPlayerUpgrade.cs | 18 + .../Players/PacketPlayerUpgradeUnlock.cs | 18 + .../PuzzleSlots/PacketPuzzleGateActivate.cs | 16 + .../PuzzleSlots/PacketPuzzleSlotFilled.cs | 18 + SRMP/Packets/PuzzleSlots/PacketPuzzleSlots.cs | 55 + SRMP/Packets/Race/PacketRaceActivate.cs | 18 + SRMP/Packets/Race/PacketRaceEnd.cs | 18 + SRMP/Packets/Race/PacketRaceTime.cs | 19 + SRMP/Packets/Race/PacketRaceTrigger.cs | 18 + SRMP/Packets/Regions/PacketRegionChange.cs | 19 + SRMP/Packets/Regions/PacketRegionOwner.cs | 19 + .../TreasurePods/PacketTreasurePodOpen.cs | 18 + .../TreasurePods/PacketTreasurePods.cs | 55 + SRMP/Packets/World/PacketWorldCredits.cs | 16 + SRMP/Packets/World/PacketWorldData.cs | 285 ++ SRMP/Packets/World/PacketWorldDecorizer.cs | 57 + .../World/PacketWorldDecorizerSetting.cs | 19 + SRMP/Packets/World/PacketWorldFastForward.cs | 18 + SRMP/Packets/World/PacketWorldKey.cs | 18 + SRMP/Packets/World/PacketWorldMailRead.cs | 19 + SRMP/Packets/World/PacketWorldMailSend.cs | 19 + SRMP/Packets/World/PacketWorldMapUnlock.cs | 18 + SRMP/Packets/World/PacketWorldMarketPrices.cs | 57 + SRMP/Packets/World/PacketWorldMarketSold.cs | 19 + SRMP/Packets/World/PacketWorldProgress.cs | 18 + .../Packets/World/PacketWorldSelectPalette.cs | 19 + .../World/PacketWorldSwitchActivate.cs | 19 + SRMP/Packets/World/PacketWorldSwitches.cs | 55 + SRMP/Packets/World/PacketWorldTime.cs | 18 + SRMP/Packets/_Classes/IPacket.cs | 15 + SRMP/Packets/_Classes/Packet.cs | 37 + SRMP/Packets/_Classes/PacketAttribute.cs | 17 + SRMP/Packets/_Classes/PacketType.cs | 119 + SRMP/Patches/Patch_AccessDoor.cs | 30 + SRMP/Patches/Patch_AchievementsDirector.cs | 32 + SRMP/Patches/Patch_Ammo.cs | 306 +++ SRMP/Patches/Patch_AttachFashions.cs | 94 + SRMP/Patches/Patch_AutoSaveDirector.cs | 79 + SRMP/Patches/Patch_BaseUI.cs | 26 + SRMP/Patches/Patch_DecorizerStorage.cs | 41 + SRMP/Patches/Patch_DestroyOnTouching.cs | 25 + SRMP/Patches/Patch_DirectedActorSpawner.cs | 45 + SRMP/Patches/Patch_Drone.cs | 24 + SRMP/Patches/Patch_DroneAnimator.cs | 33 + SRMP/Patches/Patch_DroneGadget.cs | 28 + SRMP/Patches/Patch_DroneProgram.cs | 24 + SRMP/Patches/Patch_DroneStationAnimator.cs | 33 + SRMP/Patches/Patch_DroneStationBattery.cs | 27 + SRMP/Patches/Patch_DroneSubbehaviourRest.cs | 47 + SRMP/Patches/Patch_EchoNet.cs | 32 + SRMP/Patches/Patch_EconomyDirector.cs | 56 + SRMP/Patches/Patch_EnergyJetpack.cs | 44 + SRMP/Patches/Patch_ExchangeAcceptor.cs | 78 + SRMP/Patches/Patch_ExchangeBreakOnImpact.cs | 22 + SRMP/Patches/Patch_ExchangeDirector.cs | 149 ++ SRMP/Patches/Patch_Extractor.cs | 80 + SRMP/Patches/Patch_FashionPod.cs | 23 + SRMP/Patches/Patch_FillableAshSource.cs | 48 + SRMP/Patches/Patch_FireColumn.cs | 45 + SRMP/Patches/Patch_FireStormActivator.cs | 81 + SRMP/Patches/Patch_GadgetChickenCloner.cs | 24 + SRMP/Patches/Patch_GadgetDirector.cs | 85 + SRMP/Patches/Patch_GadgetSite.cs | 67 + SRMP/Patches/Patch_GameModel.cs | 98 + SRMP/Patches/Patch_GardenCatcher.cs | 26 + SRMP/Patches/Patch_GingerPatchNode.cs | 59 + SRMP/Patches/Patch_GordoEat.cs | 57 + SRMP/Patches/Patch_GordoSnare.cs | 70 + SRMP/Patches/Patch_HydroTurret.cs | 23 + SRMP/Patches/Patch_Identifiable.cs | 34 + SRMP/Patches/Patch_Incinerate.cs | 32 + SRMP/Patches/Patch_IntorUI.cs | 22 + SRMP/Patches/Patch_KookadobaPatchNode.cs | 45 + SRMP/Patches/Patch_LandPlot.cs | 47 + SRMP/Patches/Patch_LandPlotLocation.cs | 28 + SRMP/Patches/Patch_MailDirector.cs | 45 + SRMP/Patches/Patch_NutCracker.cs | 44 + SRMP/Patches/Patch_Oasis.cs | 25 + SRMP/Patches/Patch_PauseFix.cs | 190 ++ SRMP/Patches/Patch_PauseMenu.cs | 53 + SRMP/Patches/Patch_PediaDirector.cs | 27 + SRMP/Patches/Patch_PediaModel.cs | 27 + SRMP/Patches/Patch_PhaseSiteDirector.cs | 19 + SRMP/Patches/Patch_PlaceGadgetUI.cs | 27 + SRMP/Patches/Patch_PlayerModel.cs | 27 + SRMP/Patches/Patch_PlayerState.cs | 180 ++ SRMP/Patches/Patch_PlortCollector.cs | 59 + SRMP/Patches/Patch_ProgressDirector.cs | 27 + SRMP/Patches/Patch_PuzzleGateActivator.cs | 27 + SRMP/Patches/Patch_PuzzleSlot.cs | 27 + SRMP/Patches/Patch_QuicksilverAmmoReplacer.cs | 41 + .../Patch_QuicksilverEnergyCheckpoint.cs | 41 + .../Patch_QuicksilverEnergyGenerator.cs | 69 + .../Patch_QuicksilverEnergyReplacer.cs | 41 + .../Patch_QuicksilverPlortCollector.cs | 26 + SRMP/Patches/Patch_RanchCellFastForwarder.cs | 61 + SRMP/Patches/Patch_RanchModel.cs | 27 + SRMP/Patches/Patch_Region.cs | 70 + SRMP/Patches/Patch_Reproduce.cs | 44 + SRMP/Patches/Patch_ResourceCycle.cs | 177 ++ SRMP/Patches/Patch_SECTR_CharacterAudio.cs | 62 + SRMP/Patches/Patch_SRBehaviour.cs | 20 + SRMP/Patches/Patch_ScorePlort.cs | 24 + SRMP/Patches/Patch_SiloCatcher.cs | 95 + SRMP/Patches/Patch_SiloStorage.cs | 29 + SRMP/Patches/Patch_SiloStorageActivator.cs | 30 + SRMP/Patches/Patch_SlimeEat.cs | 81 + SRMP/Patches/Patch_SlimeEatAsh.cs | 25 + SRMP/Patches/Patch_SlimeEatWater.cs | 25 + SRMP/Patches/Patch_SlimeFeeder.cs | 61 + SRMP/Patches/Patch_SlimeFeral.cs | 55 + SRMP/Patches/Patch_SlimeRandomMove.cs | 22 + SRMP/Patches/Patch_SlimeSubbehaviour.cs | 22 + SRMP/Patches/Patch_SpawnResource.cs | 44 + SRMP/Patches/Patch_SplashOnTrigger.cs | 41 + SRMP/Patches/Patch_TimeDirector.cs | 26 + SRMP/Patches/Patch_TreasurePod.cs | 32 + SRMP/Patches/Patch_TutorialDirector.cs | 24 + SRMP/Patches/Patch_UITemplates.cs | 23 + SRMP/Patches/Patch_Vacuumable.cs | 40 + SRMP/Patches/Patch_WeaponVacuum.cs | 160 ++ SRMP/Patches/Patch_WorldStateMasterSwitch.cs | 27 + SRMP/Patches/Patch_ZoneDirector.cs | 41 + SRMP/PauseState.cs | 14 + SRMP/Properties/AssemblyInfo.cs | 21 + SRMP/Properties/AssemblyInfo.tt | 45 + SRMP/SRMP.cs | 384 +++ SRMP/SRMP.csproj | 508 ++++ SRMP/SRMPConsole.cs | 238 ++ SRMP/TestUI.cs | 650 +++++ SRMP/UserData.cs | 16 + SRMP/Utils.cs | 127 + SRMP/modinfo.json | 11 + SRMP/modinfo.tt | 33 + SRMP/srmultiplayer.dat | Bin 0 -> 59771 bytes 331 files changed, 33996 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 SRMP.sln create mode 100644 SRMP/ChatUI.cs create mode 100644 SRMP/Compression.cs create mode 100644 SRMP/Console/ConsoleInput.cs create mode 100644 SRMP/Console/ConsoleWindow.cs create mode 100644 SRMP/Extensions.cs create mode 100644 SRMP/Globals.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetAESEncryption.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetCryptoProviderBase.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetDESEncryption.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetEncryption.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetRC2Encryption.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetTripleDESEncryption.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetXorEncryption.cs create mode 100644 SRMP/Lidgren.Network/Encryption/NetXteaEncryption.cs create mode 100644 SRMP/Lidgren.Network/NamespaceDoc.cs create mode 100644 SRMP/Lidgren.Network/NetBigInteger.cs create mode 100644 SRMP/Lidgren.Network/NetBitVector.cs create mode 100644 SRMP/Lidgren.Network/NetBitWriter.cs create mode 100644 SRMP/Lidgren.Network/NetBuffer.Peek.cs create mode 100644 SRMP/Lidgren.Network/NetBuffer.Read.Reflection.cs create mode 100644 SRMP/Lidgren.Network/NetBuffer.Read.cs create mode 100644 SRMP/Lidgren.Network/NetBuffer.Write.Reflection.cs create mode 100644 SRMP/Lidgren.Network/NetBuffer.Write.cs create mode 100644 SRMP/Lidgren.Network/NetBuffer.cs create mode 100644 SRMP/Lidgren.Network/NetClient.cs create mode 100644 SRMP/Lidgren.Network/NetConnection.Handshake.cs create mode 100644 SRMP/Lidgren.Network/NetConnection.Latency.cs create mode 100644 SRMP/Lidgren.Network/NetConnection.MTU.cs create mode 100644 SRMP/Lidgren.Network/NetConnection.cs create mode 100644 SRMP/Lidgren.Network/NetConnectionStatistics.cs create mode 100644 SRMP/Lidgren.Network/NetConnectionStatus.cs create mode 100644 SRMP/Lidgren.Network/NetConstants.cs create mode 100644 SRMP/Lidgren.Network/NetDeliveryMethod.cs create mode 100644 SRMP/Lidgren.Network/NetException.cs create mode 100644 SRMP/Lidgren.Network/NetFragmentationHelper.cs create mode 100644 SRMP/Lidgren.Network/NetFragmentationInfo.cs create mode 100644 SRMP/Lidgren.Network/NetIncomingMessage.cs create mode 100644 SRMP/Lidgren.Network/NetIncomingMessageType.cs create mode 100644 SRMP/Lidgren.Network/NetMessageType.cs create mode 100644 SRMP/Lidgren.Network/NetNatIntroduction.cs create mode 100644 SRMP/Lidgren.Network/NetOutgoingMessage.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.Discovery.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.Fragmentation.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.Internal.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.LatencySimulation.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.Logging.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.MessagePools.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.Send.cs create mode 100644 SRMP/Lidgren.Network/NetPeer.cs create mode 100644 SRMP/Lidgren.Network/NetPeerConfiguration.cs create mode 100644 SRMP/Lidgren.Network/NetPeerStatistics.cs create mode 100644 SRMP/Lidgren.Network/NetPeerStatus.cs create mode 100644 SRMP/Lidgren.Network/NetQueue.cs create mode 100644 SRMP/Lidgren.Network/NetRandom.Implementations.cs create mode 100644 SRMP/Lidgren.Network/NetRandom.cs create mode 100644 SRMP/Lidgren.Network/NetRandomSeed.cs create mode 100644 SRMP/Lidgren.Network/NetReceiverChannelBase.cs create mode 100644 SRMP/Lidgren.Network/NetReliableOrderedReceiver.cs create mode 100644 SRMP/Lidgren.Network/NetReliableSenderChannel.cs create mode 100644 SRMP/Lidgren.Network/NetReliableSequencedReceiver.cs create mode 100644 SRMP/Lidgren.Network/NetReliableUnorderedReceiver.cs create mode 100644 SRMP/Lidgren.Network/NetSRP.cs create mode 100644 SRMP/Lidgren.Network/NetSendResult.cs create mode 100644 SRMP/Lidgren.Network/NetSenderChannelBase.cs create mode 100644 SRMP/Lidgren.Network/NetServer.cs create mode 100644 SRMP/Lidgren.Network/NetStoredReliableMessage.cs create mode 100644 SRMP/Lidgren.Network/NetTime.cs create mode 100644 SRMP/Lidgren.Network/NetTuple.cs create mode 100644 SRMP/Lidgren.Network/NetUPnP.cs create mode 100644 SRMP/Lidgren.Network/NetUnreliableSenderChannel.cs create mode 100644 SRMP/Lidgren.Network/NetUnreliableSequencedReceiver.cs create mode 100644 SRMP/Lidgren.Network/NetUnreliableUnorderedReceiver.cs create mode 100644 SRMP/Lidgren.Network/NetUtility.cs create mode 100644 SRMP/Lidgren.Network/Platform/PlatformAndroid.cs create mode 100644 SRMP/Lidgren.Network/Platform/PlatformConstrained.cs create mode 100644 SRMP/Lidgren.Network/Platform/PlatformUnityExtras.cs create mode 100644 SRMP/Lidgren.Network/Platform/PlatformWin32.cs create mode 100644 SRMP/Lidgren.Network/Platform/PlatformWinRT.cs create mode 100644 SRMP/MainSRML.cs create mode 100644 SRMP/MainStandalone.cs create mode 100644 SRMP/MultiplayerUI.cs create mode 100644 SRMP/Networking/NetworkAccessDoor.cs create mode 100644 SRMP/Networking/NetworkActor.cs create mode 100644 SRMP/Networking/NetworkAmmo.cs create mode 100644 SRMP/Networking/NetworkClient.cs create mode 100644 SRMP/Networking/NetworkClientUI.cs create mode 100644 SRMP/Networking/NetworkDirectedActorSpawner.cs create mode 100644 SRMP/Networking/NetworkDrone.cs create mode 100644 SRMP/Networking/NetworkDroneAmmo.cs create mode 100644 SRMP/Networking/NetworkExchangeAcceptor.cs create mode 100644 SRMP/Networking/NetworkFireColumn.cs create mode 100644 SRMP/Networking/NetworkGadgetSite.cs create mode 100644 SRMP/Networking/NetworkGordo.cs create mode 100644 SRMP/Networking/NetworkHandlerClient.cs create mode 100644 SRMP/Networking/NetworkHandlerServer.cs create mode 100644 SRMP/Networking/NetworkHostUI.cs create mode 100644 SRMP/Networking/NetworkKookadobaPatchNode.cs create mode 100644 SRMP/Networking/NetworkLandplot.cs create mode 100644 SRMP/Networking/NetworkMasterServer.cs create mode 100644 SRMP/Networking/NetworkNutcracker.cs create mode 100644 SRMP/Networking/NetworkPlayer.Animation.cs create mode 100644 SRMP/Networking/NetworkPlayer.cs create mode 100644 SRMP/Networking/NetworkPlayerAmmo.cs create mode 100644 SRMP/Networking/NetworkPlayerDisplayOnMap.cs create mode 100644 SRMP/Networking/NetworkPlayerSave.cs create mode 100644 SRMP/Networking/NetworkPuzzleSlot.cs create mode 100644 SRMP/Networking/NetworkRaceTrigger.cs create mode 100644 SRMP/Networking/NetworkRegion.cs create mode 100644 SRMP/Networking/NetworkServer.cs create mode 100644 SRMP/Networking/NetworkSpawnResource.cs create mode 100644 SRMP/Networking/NetworkTreasurePod.cs create mode 100644 SRMP/Networking/NetworkWorldStateMasterSwitch.cs create mode 100644 SRMP/Packets/AccessDoors/PacketAccessDoorOpen.cs create mode 100644 SRMP/Packets/AccessDoors/PacketAccessDoors.cs create mode 100644 SRMP/Packets/Actors/PacketActorAttach.cs create mode 100644 SRMP/Packets/Actors/PacketActorDestroy.cs create mode 100644 SRMP/Packets/Actors/PacketActorEmotions.cs create mode 100644 SRMP/Packets/Actors/PacketActorFX.cs create mode 100644 SRMP/Packets/Actors/PacketActorFeral.cs create mode 100644 SRMP/Packets/Actors/PacketActorOwner.cs create mode 100644 SRMP/Packets/Actors/PacketActorPosition.cs create mode 100644 SRMP/Packets/Actors/PacketActorReproduceTime.cs create mode 100644 SRMP/Packets/Actors/PacketActorResourceState.cs create mode 100644 SRMP/Packets/Actors/PacketActorSpawn.cs create mode 100644 SRMP/Packets/Actors/PacketActors.cs create mode 100644 SRMP/Packets/Drones/PacketDroneActive.cs create mode 100644 SRMP/Packets/Drones/PacketDroneAmmoAdd.cs create mode 100644 SRMP/Packets/Drones/PacketDroneAmmoClear.cs create mode 100644 SRMP/Packets/Drones/PacketDroneAmmoRemove.cs create mode 100644 SRMP/Packets/Drones/PacketDroneAnimation.cs create mode 100644 SRMP/Packets/Drones/PacketDroneLiquid.cs create mode 100644 SRMP/Packets/Drones/PacketDronePosition.cs create mode 100644 SRMP/Packets/Drones/PacketDronePrograms.cs create mode 100644 SRMP/Packets/Drones/PacketDroneStationEnabled.cs create mode 100644 SRMP/Packets/Exchanges/PacketExchangeClear.cs create mode 100644 SRMP/Packets/Exchanges/PacketExchangeOffer.cs create mode 100644 SRMP/Packets/Exchanges/PacketExchangeOffers.cs create mode 100644 SRMP/Packets/Exchanges/PacketExchangePrepareDaily.cs create mode 100644 SRMP/Packets/Exchanges/PacketExchangeTryAccept.cs create mode 100644 SRMP/Packets/FX/PacketGlobalFX.cs create mode 100644 SRMP/Packets/FX/PacketIncinerateFX.cs create mode 100644 SRMP/Packets/FX/PacketPlayAudio.cs create mode 100644 SRMP/Packets/Fashions/PacketFashionAttach.cs create mode 100644 SRMP/Packets/Fashions/PacketFashionDetachAll.cs create mode 100644 SRMP/Packets/FireColumn/PacketFireColumnActivate.cs create mode 100644 SRMP/Packets/FireColumn/PacketFireStormMode.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetAdd.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetAddBlueprint.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetEchoNetTime.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetExtractorUpdate.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetRefinerySpend.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetRemove.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetRotation.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetSnareAttach.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetSnareGordo.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetSpawn.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetSpend.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgetTurrets.cs create mode 100644 SRMP/Packets/Gadgets/PacketGadgets.cs create mode 100644 SRMP/Packets/Gordos/PacketGordoEat.cs create mode 100644 SRMP/Packets/Gordos/PacketGordos.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotAsh.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotCollect.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotFeederSpeed.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotPlantGarden.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotReplace.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotSiloAmmoAdd.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotSiloAmmoClear.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotSiloAmmoRemove.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotSiloInsert.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotSiloRemove.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotSiloSlot.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotStartCollection.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandPlotUpgrade.cs create mode 100644 SRMP/Packets/LandPlots/PacketLandplots.cs create mode 100644 SRMP/Packets/Oasis/PacketOasis.cs create mode 100644 SRMP/Packets/Oasis/PacketOasisLive.cs create mode 100644 SRMP/Packets/Others/PacketGingerAction.cs create mode 100644 SRMP/Packets/Others/PacketGingerAttach.cs create mode 100644 SRMP/Packets/Others/PacketKookadobaAction.cs create mode 100644 SRMP/Packets/Others/PacketKookadobaAttach.cs create mode 100644 SRMP/Packets/Pedia/PacketPediaShowPopup.cs create mode 100644 SRMP/Packets/Pedia/PacketPediaUnlock.cs create mode 100644 SRMP/Packets/Players/PacketPlayerChat.cs create mode 100644 SRMP/Packets/Players/PacketPlayerCurrency.cs create mode 100644 SRMP/Packets/Players/PacketPlayerCurrencyDisplay.cs create mode 100644 SRMP/Packets/Players/PacketPlayerFX.cs create mode 100644 SRMP/Packets/Players/PacketPlayerJoined.cs create mode 100644 SRMP/Packets/Players/PacketPlayerLeft.cs create mode 100644 SRMP/Packets/Players/PacketPlayerLoaded.cs create mode 100644 SRMP/Packets/Players/PacketPlayerPosition.cs create mode 100644 SRMP/Packets/Players/PacketPlayerUpgrade.cs create mode 100644 SRMP/Packets/Players/PacketPlayerUpgradeUnlock.cs create mode 100644 SRMP/Packets/PuzzleSlots/PacketPuzzleGateActivate.cs create mode 100644 SRMP/Packets/PuzzleSlots/PacketPuzzleSlotFilled.cs create mode 100644 SRMP/Packets/PuzzleSlots/PacketPuzzleSlots.cs create mode 100644 SRMP/Packets/Race/PacketRaceActivate.cs create mode 100644 SRMP/Packets/Race/PacketRaceEnd.cs create mode 100644 SRMP/Packets/Race/PacketRaceTime.cs create mode 100644 SRMP/Packets/Race/PacketRaceTrigger.cs create mode 100644 SRMP/Packets/Regions/PacketRegionChange.cs create mode 100644 SRMP/Packets/Regions/PacketRegionOwner.cs create mode 100644 SRMP/Packets/TreasurePods/PacketTreasurePodOpen.cs create mode 100644 SRMP/Packets/TreasurePods/PacketTreasurePods.cs create mode 100644 SRMP/Packets/World/PacketWorldCredits.cs create mode 100644 SRMP/Packets/World/PacketWorldData.cs create mode 100644 SRMP/Packets/World/PacketWorldDecorizer.cs create mode 100644 SRMP/Packets/World/PacketWorldDecorizerSetting.cs create mode 100644 SRMP/Packets/World/PacketWorldFastForward.cs create mode 100644 SRMP/Packets/World/PacketWorldKey.cs create mode 100644 SRMP/Packets/World/PacketWorldMailRead.cs create mode 100644 SRMP/Packets/World/PacketWorldMailSend.cs create mode 100644 SRMP/Packets/World/PacketWorldMapUnlock.cs create mode 100644 SRMP/Packets/World/PacketWorldMarketPrices.cs create mode 100644 SRMP/Packets/World/PacketWorldMarketSold.cs create mode 100644 SRMP/Packets/World/PacketWorldProgress.cs create mode 100644 SRMP/Packets/World/PacketWorldSelectPalette.cs create mode 100644 SRMP/Packets/World/PacketWorldSwitchActivate.cs create mode 100644 SRMP/Packets/World/PacketWorldSwitches.cs create mode 100644 SRMP/Packets/World/PacketWorldTime.cs create mode 100644 SRMP/Packets/_Classes/IPacket.cs create mode 100644 SRMP/Packets/_Classes/Packet.cs create mode 100644 SRMP/Packets/_Classes/PacketAttribute.cs create mode 100644 SRMP/Packets/_Classes/PacketType.cs create mode 100644 SRMP/Patches/Patch_AccessDoor.cs create mode 100644 SRMP/Patches/Patch_AchievementsDirector.cs create mode 100644 SRMP/Patches/Patch_Ammo.cs create mode 100644 SRMP/Patches/Patch_AttachFashions.cs create mode 100644 SRMP/Patches/Patch_AutoSaveDirector.cs create mode 100644 SRMP/Patches/Patch_BaseUI.cs create mode 100644 SRMP/Patches/Patch_DecorizerStorage.cs create mode 100644 SRMP/Patches/Patch_DestroyOnTouching.cs create mode 100644 SRMP/Patches/Patch_DirectedActorSpawner.cs create mode 100644 SRMP/Patches/Patch_Drone.cs create mode 100644 SRMP/Patches/Patch_DroneAnimator.cs create mode 100644 SRMP/Patches/Patch_DroneGadget.cs create mode 100644 SRMP/Patches/Patch_DroneProgram.cs create mode 100644 SRMP/Patches/Patch_DroneStationAnimator.cs create mode 100644 SRMP/Patches/Patch_DroneStationBattery.cs create mode 100644 SRMP/Patches/Patch_DroneSubbehaviourRest.cs create mode 100644 SRMP/Patches/Patch_EchoNet.cs create mode 100644 SRMP/Patches/Patch_EconomyDirector.cs create mode 100644 SRMP/Patches/Patch_EnergyJetpack.cs create mode 100644 SRMP/Patches/Patch_ExchangeAcceptor.cs create mode 100644 SRMP/Patches/Patch_ExchangeBreakOnImpact.cs create mode 100644 SRMP/Patches/Patch_ExchangeDirector.cs create mode 100644 SRMP/Patches/Patch_Extractor.cs create mode 100644 SRMP/Patches/Patch_FashionPod.cs create mode 100644 SRMP/Patches/Patch_FillableAshSource.cs create mode 100644 SRMP/Patches/Patch_FireColumn.cs create mode 100644 SRMP/Patches/Patch_FireStormActivator.cs create mode 100644 SRMP/Patches/Patch_GadgetChickenCloner.cs create mode 100644 SRMP/Patches/Patch_GadgetDirector.cs create mode 100644 SRMP/Patches/Patch_GadgetSite.cs create mode 100644 SRMP/Patches/Patch_GameModel.cs create mode 100644 SRMP/Patches/Patch_GardenCatcher.cs create mode 100644 SRMP/Patches/Patch_GingerPatchNode.cs create mode 100644 SRMP/Patches/Patch_GordoEat.cs create mode 100644 SRMP/Patches/Patch_GordoSnare.cs create mode 100644 SRMP/Patches/Patch_HydroTurret.cs create mode 100644 SRMP/Patches/Patch_Identifiable.cs create mode 100644 SRMP/Patches/Patch_Incinerate.cs create mode 100644 SRMP/Patches/Patch_IntorUI.cs create mode 100644 SRMP/Patches/Patch_KookadobaPatchNode.cs create mode 100644 SRMP/Patches/Patch_LandPlot.cs create mode 100644 SRMP/Patches/Patch_LandPlotLocation.cs create mode 100644 SRMP/Patches/Patch_MailDirector.cs create mode 100644 SRMP/Patches/Patch_NutCracker.cs create mode 100644 SRMP/Patches/Patch_Oasis.cs create mode 100644 SRMP/Patches/Patch_PauseFix.cs create mode 100644 SRMP/Patches/Patch_PauseMenu.cs create mode 100644 SRMP/Patches/Patch_PediaDirector.cs create mode 100644 SRMP/Patches/Patch_PediaModel.cs create mode 100644 SRMP/Patches/Patch_PhaseSiteDirector.cs create mode 100644 SRMP/Patches/Patch_PlaceGadgetUI.cs create mode 100644 SRMP/Patches/Patch_PlayerModel.cs create mode 100644 SRMP/Patches/Patch_PlayerState.cs create mode 100644 SRMP/Patches/Patch_PlortCollector.cs create mode 100644 SRMP/Patches/Patch_ProgressDirector.cs create mode 100644 SRMP/Patches/Patch_PuzzleGateActivator.cs create mode 100644 SRMP/Patches/Patch_PuzzleSlot.cs create mode 100644 SRMP/Patches/Patch_QuicksilverAmmoReplacer.cs create mode 100644 SRMP/Patches/Patch_QuicksilverEnergyCheckpoint.cs create mode 100644 SRMP/Patches/Patch_QuicksilverEnergyGenerator.cs create mode 100644 SRMP/Patches/Patch_QuicksilverEnergyReplacer.cs create mode 100644 SRMP/Patches/Patch_QuicksilverPlortCollector.cs create mode 100644 SRMP/Patches/Patch_RanchCellFastForwarder.cs create mode 100644 SRMP/Patches/Patch_RanchModel.cs create mode 100644 SRMP/Patches/Patch_Region.cs create mode 100644 SRMP/Patches/Patch_Reproduce.cs create mode 100644 SRMP/Patches/Patch_ResourceCycle.cs create mode 100644 SRMP/Patches/Patch_SECTR_CharacterAudio.cs create mode 100644 SRMP/Patches/Patch_SRBehaviour.cs create mode 100644 SRMP/Patches/Patch_ScorePlort.cs create mode 100644 SRMP/Patches/Patch_SiloCatcher.cs create mode 100644 SRMP/Patches/Patch_SiloStorage.cs create mode 100644 SRMP/Patches/Patch_SiloStorageActivator.cs create mode 100644 SRMP/Patches/Patch_SlimeEat.cs create mode 100644 SRMP/Patches/Patch_SlimeEatAsh.cs create mode 100644 SRMP/Patches/Patch_SlimeEatWater.cs create mode 100644 SRMP/Patches/Patch_SlimeFeeder.cs create mode 100644 SRMP/Patches/Patch_SlimeFeral.cs create mode 100644 SRMP/Patches/Patch_SlimeRandomMove.cs create mode 100644 SRMP/Patches/Patch_SlimeSubbehaviour.cs create mode 100644 SRMP/Patches/Patch_SpawnResource.cs create mode 100644 SRMP/Patches/Patch_SplashOnTrigger.cs create mode 100644 SRMP/Patches/Patch_TimeDirector.cs create mode 100644 SRMP/Patches/Patch_TreasurePod.cs create mode 100644 SRMP/Patches/Patch_TutorialDirector.cs create mode 100644 SRMP/Patches/Patch_UITemplates.cs create mode 100644 SRMP/Patches/Patch_Vacuumable.cs create mode 100644 SRMP/Patches/Patch_WeaponVacuum.cs create mode 100644 SRMP/Patches/Patch_WorldStateMasterSwitch.cs create mode 100644 SRMP/Patches/Patch_ZoneDirector.cs create mode 100644 SRMP/PauseState.cs create mode 100644 SRMP/Properties/AssemblyInfo.cs create mode 100644 SRMP/Properties/AssemblyInfo.tt create mode 100644 SRMP/SRMP.cs create mode 100644 SRMP/SRMP.csproj create mode 100644 SRMP/SRMPConsole.cs create mode 100644 SRMP/TestUI.cs create mode 100644 SRMP/UserData.cs create mode 100644 SRMP/Utils.cs create mode 100644 SRMP/modinfo.json create mode 100644 SRMP/modinfo.tt create mode 100644 SRMP/srmultiplayer.dat diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50f9ec9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,331 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Bb]uilds/ +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ diff --git a/SRMP.sln b/SRMP.sln new file mode 100644 index 0000000..285d74f --- /dev/null +++ b/SRMP.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1267 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SRMP", "SRMP\SRMP.csproj", "{E1BF7CDA-F2AE-4042-A992-DCBF62E79238}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + SRML|Any CPU = SRML|Any CPU + SRML NoVer|Any CPU = SRML NoVer|Any CPU + Standalone|Any CPU = Standalone|Any CPU + Standalone NoVer|Any CPU = Standalone NoVer|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Release|Any CPU.Build.0 = Release|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML|Any CPU.ActiveCfg = SRML|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML|Any CPU.Build.0 = SRML|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML NoVer|Any CPU.ActiveCfg = SRML NoVer|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.SRML NoVer|Any CPU.Build.0 = SRML NoVer|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone|Any CPU.ActiveCfg = Standalone|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone|Any CPU.Build.0 = Standalone|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone NoVer|Any CPU.ActiveCfg = Standalone NoVer|Any CPU + {E1BF7CDA-F2AE-4042-A992-DCBF62E79238}.Standalone NoVer|Any CPU.Build.0 = Standalone NoVer|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6E1CE44C-14A2-4691-A970-B971E4861A66} + EndGlobalSection +EndGlobal diff --git a/SRMP/ChatUI.cs b/SRMP/ChatUI.cs new file mode 100644 index 0000000..9518920 --- /dev/null +++ b/SRMP/ChatUI.cs @@ -0,0 +1,188 @@ +using UnityEngine; +using System.Collections; +using System; +using System.Collections.Generic; +using SRMultiplayer; +using SRMultiplayer.Networking; +using SRMultiplayer.Packets; + +public class ChatUI : SRSingleton +{ + private bool openChat; + private string message; + private float fadeTime; + private List messages = new List(); + private Vector2 chatScroll; + + public class ChatMessage + { + public string Text; + public float FadeTime; + public DateTime Time; + + public ChatMessage(string msg) + { + Text = msg; + FadeTime = 10f; + Time = DateTime.Now; + } + } + + private void Update() + { + if (!Globals.IsMultiplayer) + { + openChat = false; + message = ""; + return; + } + + if (Input.GetKeyUp(KeyCode.Return)) + { + StartCoroutine(FocusChat()); + } + } + + private void OnGUI() + { + if (!Globals.IsMultiplayer) return; + + if (openChat) + { + GUILayout.BeginArea(new Rect(20, Screen.height / 2, 500, 300), GUI.skin.box); + chatScroll = GUILayout.BeginScrollView(chatScroll); + var skin = GUI.skin.box; + skin.wordWrap = true; + skin.alignment = TextAnchor.MiddleLeft; + foreach (var msg in messages) + { + GUILayout.Label(wrapString(msg.Text, 490), skin, GUILayout.MaxWidth(490)); + } + GUILayout.EndScrollView(); + GUI.SetNextControlName("ChatInput"); + message = GUILayout.TextField(message); + GUILayout.EndArea(); + + GUI.FocusControl("ChatInput"); + + Event e = Event.current; + if (e.rawType == EventType.KeyUp && e.keyCode == KeyCode.Return) + { + openChat = !openChat; + if (!string.IsNullOrWhiteSpace(message)) + { + if (Globals.IsServer) + { + AddChatMessage(Globals.Username + ": " + message); + new PacketPlayerChat() + { + message = Globals.Username + ": " + message + }.Send(); + } + else + { + new PacketPlayerChat() + { + message = message + }.Send(); + } + message = ""; + } + } + } + else + { + GUILayout.BeginArea(new Rect(20, Screen.height / 2, 500, 300)); + chatScroll = GUILayout.BeginScrollView(chatScroll); + var skin = GUI.skin.box; + skin.wordWrap = true; + skin.alignment = TextAnchor.MiddleLeft; + foreach (var msg in messages) + { + if (msg.FadeTime > 0f) + { + msg.FadeTime -= Time.deltaTime; + var c = GUI.color; + c.a = (msg.FadeTime / 5f); + GUI.color = c; + GUILayout.Label(wrapString(msg.Text, 490), skin, GUILayout.MaxWidth(490)); + } + } + GUILayout.EndScrollView(); + GUILayout.EndArea(); + } + } + + public void AddChatMessage(string message) + { + fadeTime = 10f; + messages.Add(new ChatMessage(message)); + chatScroll = new Vector2(0, 100000); + } + + public void Clear() + { + messages.Clear(); + } + + private IEnumerator FocusChat() + { + yield return new WaitForEndOfFrame(); + yield return new WaitForEndOfFrame(); + openChat = !openChat; + GUI.FocusControl("ChatInput"); + } + + string wrapString(string msg, int width) + { + string[] words = msg.Split(" "[0]); + string retVal = ""; //returning string + string NLstr = ""; //leftover string on new line + for (int index = 0; index < words.Length; index++) + { + string word = words[index].Trim(); + //if word exceeds width + if (words[index].Length >= width + 2) + { + string[] temp = new string[5]; + int i = 0; + while (words[index].Length > width) + { //word exceeds width, cut it at widrh + temp[i] = words[index].Substring(0, width) + "\n"; //cut the word at width + words[index] = words[index].Substring(width); //keep remaining word + i++; + if (words[index].Length <= width) + { //the balance is smaller than width + temp[i] = words[index]; + NLstr = temp[i]; + } + } + retVal += "\n"; + for (int x = 0; x < i + 1; x++) + { //loops through temp array + retVal = retVal + temp[x]; + } + } + else if (index == 0) + { + retVal = words[0]; + NLstr = retVal; + } + else if (index > 0) + { + if (NLstr.Length + words[index].Length <= width) + { + retVal = retVal + " " + words[index]; + NLstr = NLstr + " " + words[index]; //add the current line length + } + else if (NLstr.Length + words[index].Length > width) + { + retVal = retVal + "\n" + words[index]; + NLstr = words[index]; //reset the line length + print("newline! at word " + words[index]); + } + } + } + return retVal; + } +} diff --git a/SRMP/Compression.cs b/SRMP/Compression.cs new file mode 100644 index 0000000..6c93bb1 --- /dev/null +++ b/SRMP/Compression.cs @@ -0,0 +1,242 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace SRMultiplayer +{ + /// + /// Functions to Compress Quaternions and Floats + /// + /// + /// Uncompressed Quaternion = 32 * 4 = 128 bits => send 16 bytes + /// + /// + /// Quaternion is always normalized so we drop largest value and re-calculate it. + /// We can encode which one is the largest using 2 bits + /// + /// x^2 + y^2 + z^2 + w^2 = 1 + /// + /// + /// + /// + /// 2nd largest value has max size of 1/sqrt(2) + /// We can encode the smallest three components in [-1/sqrt(2),+1/sqrt(2)] instead of [-1,+1] + /// + /// c^2 + c^2 + 0 + 0 = 1 + /// + /// + /// + /// + /// Sign of largest value doesn't matter + /// + /// Q * vec3 == (-Q) * vec3 + /// + /// + /// + /// + /// + /// RotationPrecision
+ /// + /// 2/sqrt(2) / (2^bitCount - 1) + /// + ///
+ /// + /// + /// rotation precision +-0.00138 in range [-1,+1] + /// + /// 10 bits per value + /// 2 + 10 * 3 = 32 bits => send 4 bytes + /// + /// + ///
+ /// + /// + /// Links for more info: + ///
GDC Talk + ///
Post on Snapshot Compression + ///
+ ///
+ public static class Compression + { + const float QuaternionMinValue = -1f / 1.414214f; // 1/ sqrt(2) + const float QuaternionMaxValue = 1f / 1.414214f; + + const int QuaternionBitLength = 10; + // same as Mathf.Pow(2, targetBitLength) - 1 + const uint QuaternionUintRange = (1 << QuaternionBitLength) - 1; + + /// + /// Used to Compress Quaternion into 4 bytes + /// + public static uint CompressQuaternion(Quaternion value) + { + // make sure value is normalized (don't trust user given value, and math here assumes normalized) + value = value.normalized; + + int largestIndex = FindLargestIndex(value); + Vector3 small = GetSmallerDimensions(largestIndex, value); + // largest needs to be positive to be calculated by reader + // if largest is negative flip sign of others because Q = -Q + if (value[largestIndex] < 0) + { + small *= -1; + } + + uint a = ScaleToUInt(small.x, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange); + uint b = ScaleToUInt(small.y, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange); + uint c = ScaleToUInt(small.z, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange); + + // pack each 10 bits and extra 2 bits into uint32 + uint packed = a | b << 10 | c << 20 | (uint)largestIndex << 30; + + return packed; + } + + internal static int FindLargestIndex(Quaternion q) + { + int index = 0; + float current = 0; + + // check each value to see which one is largest (ignoring +-) + for (int i = 0; i < 4; i++) + { + float next = Mathf.Abs(q[i]); + if (next > current) + { + index = i; + current = next; + } + } + + return index; + } + + static Vector3 GetSmallerDimensions(int largestIndex, Quaternion value) + { + float x = value.x; + float y = value.y; + float z = value.z; + float w = value.w; + + switch (largestIndex) + { + case 0: + return new Vector3(y, z, w); + case 1: + return new Vector3(x, z, w); + case 2: + return new Vector3(x, y, w); + case 3: + return new Vector3(x, y, z); + default: + throw new IndexOutOfRangeException("Invalid Quaternion index!"); + } + } + + + /// + /// Used to read a Compressed Quaternion from 4 bytes + /// Quaternion is normalized + /// + public static Quaternion DecompressQuaternion(uint packed) + { + // 10 bits + const uint mask = 0b11_1111_1111; + Quaternion result; + + + uint a = packed & mask; + uint b = (packed >> 10) & mask; + uint c = (packed >> 20) & mask; + uint largestIndex = (packed >> 30) & mask; + + float x = ScaleFromUInt(a, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange); + float y = ScaleFromUInt(b, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange); + float z = ScaleFromUInt(c, QuaternionMinValue, QuaternionMaxValue, 0, QuaternionUintRange); + + Vector3 small = new Vector3(x, y, z); + result = FromSmallerDimensions(largestIndex, small); + return result; + } + + static Quaternion FromSmallerDimensions(uint largestIndex, Vector3 smallest) + { + float a = smallest.x; + float b = smallest.y; + float c = smallest.z; + + float largest = Mathf.Sqrt(1 - a * a - b * b - c * c); + switch (largestIndex) + { + case 0: + return new Quaternion(largest, a, b, c).normalized; + case 1: + return new Quaternion(a, largest, b, c).normalized; + case 2: + return new Quaternion(a, b, largest, c).normalized; + case 3: + return new Quaternion(a, b, c, largest).normalized; + default: + throw new IndexOutOfRangeException("Invalid Quaternion index!"); + + } + } + + + /// + /// Scales float from minFloat->maxFloat to minUint->maxUint + /// values out side of minFloat/maxFloat will return either 0 or maxUint + /// + /// + /// + /// + /// should be a power of 2, can be 0 + /// should be a power of 2, for example 1 << 8 for value to take up 8 bytes + /// + public static uint ScaleToUInt(float value, float minFloat, float maxFloat, uint minUint, uint maxUint) + { + // if out of range return min/max + if (value > maxFloat) { return maxUint; } + if (value < minFloat) { return minUint; } + + float rangeFloat = maxFloat - minFloat; + uint rangeUint = maxUint - minUint; + + // scale value to 0->1 (as float) + float valueRelative = (value - minFloat) / rangeFloat; + // scale value to uMin->uMax + float outValue = valueRelative * rangeUint + minUint; + + return (uint)outValue; + } + + /// + /// Scales uint from minUint->maxUint to minFloat->maxFloat + /// + /// + /// + /// + /// should be a power of 2, can be 0 + /// should be a power of 2, for example 1 << 8 for value to take up 8 bytes + /// + public static float ScaleFromUInt(uint value, float minFloat, float maxFloat, uint minUint, uint maxUint) + { + // if out of range return min/max + if (value > maxUint) { return maxFloat; } + if (value < minUint) { return minFloat; } + + float rangeFloat = maxFloat - minFloat; + uint rangeUint = maxUint - minUint; + + // scale value to 0->1 (as float) + // make sure divide is float + float valueRelative = (value - minUint) / (float)rangeUint; + // scale value to fMin->fMax + float outValue = valueRelative * rangeFloat + minFloat; + return outValue; + } + } +} diff --git a/SRMP/Console/ConsoleInput.cs b/SRMP/Console/ConsoleInput.cs new file mode 100644 index 0000000..cf52165 --- /dev/null +++ b/SRMP/Console/ConsoleInput.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace SRMultiplayer +{ + public class ConsoleInput + { + public string inputString = ""; + public event Action OnInputText; + + public void ClearLine() + { + Console.CursorLeft = 0; + Console.Write(new string(' ', Console.BufferWidth)); + Console.CursorTop--; + Console.CursorLeft = 0; + } + + public void RedrawInputLine() + { + bool flag = Console.CursorLeft > 0; + if (flag) + { + this.ClearLine(); + } + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("> "); + Console.Write(this.inputString); + } + + internal void OnBackspace() + { + bool flag = this.inputString.Length <= 0; + if (!flag) + { + this.inputString = this.inputString.Substring(0, this.inputString.Length - 1); + this.RedrawInputLine(); + } + } + + internal void OnEscape() + { + this.ClearLine(); + this.inputString = ""; + this.RedrawInputLine(); + } + + internal void OnEnter() + { + this.ClearLine(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("> " + this.inputString); + string obj = this.inputString; + this.inputString = ""; + bool flag = this.OnInputText != null; + if (flag) + { + this.OnInputText(obj); + } + } + + public void Update() + { + bool flag = !Console.KeyAvailable; + if (!flag) + { + ConsoleKeyInfo consoleKeyInfo = Console.ReadKey(); + bool flag2 = consoleKeyInfo.Key == ConsoleKey.Enter; + if (flag2) + { + this.OnEnter(); + } + else + { + bool flag3 = consoleKeyInfo.Key == ConsoleKey.Backspace; + if (flag3) + { + this.OnBackspace(); + } + else + { + bool flag4 = consoleKeyInfo.Key == ConsoleKey.Escape; + if (flag4) + { + this.OnEscape(); + } + else + { + bool flag5 = consoleKeyInfo.KeyChar > '\0'; + if (flag5) + { + this.inputString += consoleKeyInfo.KeyChar.ToString(); + this.RedrawInputLine(); + } + } + } + } + } + } + } +} diff --git a/SRMP/Console/ConsoleWindow.cs b/SRMP/Console/ConsoleWindow.cs new file mode 100644 index 0000000..0f4ff33 --- /dev/null +++ b/SRMP/Console/ConsoleWindow.cs @@ -0,0 +1,69 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using UnityEngine; + +namespace SRMultiplayer +{ + public class ConsoleWindow + { + private TextWriter oldOutput; + + private const int STD_OUTPUT_HANDLE = -11; + + public void Initialize() + { + bool flag = !ConsoleWindow.AttachConsole(uint.MaxValue); + if (flag) + { + ConsoleWindow.AllocConsole(); + } + this.oldOutput = Console.Out; + try + { + IntPtr stdHandle = ConsoleWindow.GetStdHandle(-11); + SafeFileHandle handle = new SafeFileHandle(stdHandle, true); + FileStream stream = new FileStream(handle, FileAccess.Write); + Encoding ascii = Encoding.ASCII; + Console.SetOut(new StreamWriter(stream, ascii) + { + AutoFlush = true + }); + } + catch (Exception ex) + { + SRMP.Log("Couldn't redirect output: " + ex.Message); + } + } + + public void Shutdown() + { + Console.SetOut(this.oldOutput); + ConsoleWindow.FreeConsole(); + } + + public void SetTitle(string strName) + { + ConsoleWindow.SetConsoleTitle(strName); + } + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AttachConsole(uint dwProcessId); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool AllocConsole(); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool FreeConsole(); + + [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Auto, SetLastError = true)] + private static extern IntPtr GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll")] + private static extern bool SetConsoleTitle(string lpConsoleTitle); + } +} diff --git a/SRMP/Extensions.cs b/SRMP/Extensions.cs new file mode 100644 index 0000000..0dce3e1 --- /dev/null +++ b/SRMP/Extensions.cs @@ -0,0 +1,249 @@ +using Lidgren.Network; +using SRMultiplayer; +using SRMultiplayer.Networking; +using SRMultiplayer.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace SRMultiplayer +{ + public static class Extensions + { + public static void Rebuild(this RefineryUI ui) + { + foreach(Transform child in ui.inventoryGridPanel.transform) + { + GameObject.Destroy(child.gameObject); + } + + GadgetDirector gadgetDirector = SRSingleton.Instance.GadgetDirector; + PediaDirector pediaDirector = SRSingleton.Instance.PediaDirector; + int num = 0; + foreach (Identifiable.Id id in ui.listedItems) + { + int refineryCount = gadgetDirector.GetRefineryCount(id); + Identifiable.Id id2 = id; + PediaDirector.Id? pediaId = pediaDirector.GetPediaId(Identifiable.IsPlort(id) ? ui.PlortToSlime(id) : id); + if (refineryCount == 0 && pediaId != null && !pediaDirector.IsUnlocked(pediaId.Value)) + { + id2 = Identifiable.Id.NONE; + } + ui.AddInventory(id2, refineryCount); + num++; + } + for (int j = num; j < 15; j++) + { + ui.AddEmptyInventory(); + } + } + + public static void WriteAmmoSlot(this NetOutgoingMessage om, Ammo.Slot slot) + { + om.Write(slot != null); + if (slot != null) + { + om.Write((ushort)slot.id); + om.Write(slot.count); + om.Write(slot.emotions != null); + if (slot.emotions != null) + { + om.Write(slot.emotions.Count); + foreach (var emotion in slot.emotions) + { + om.Write((byte)emotion.Key); + om.Write(emotion.Value); + } + } + } + } + + public static Ammo.Slot ReadAmmoSlot(this NetIncomingMessage im) + { + if (im.ReadBoolean()) + { + var slot = new Ammo.Slot((Identifiable.Id)im.ReadUInt16(), im.ReadInt32()); + if (im.ReadBoolean()) + { + int emotionCount = im.ReadInt32(); + slot.emotions = new SlimeEmotionData(); + for (int l = 0; l < emotionCount; l++) + { + slot.emotions.Add((SlimeEmotions.Emotion)im.ReadByte(), im.ReadFloat()); + } + } + return slot; + } + return null; + } + + public static void Send(this Packet packet, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0) + { + if(!Globals.IsClient) + { + NetworkServer.Instance.SendToAll(packet, method, sequence); + return; + } + NetworkClient.Instance.Send(packet, method, sequence); + } + + public static void Send(this Packet packet, NetworkPlayer player, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0) + { + if (!Globals.IsServer) + { + SRMP.Log("Trying to send packet as server while not server"); + return; + } + + NetworkServer.Instance.Send(player.Connection, packet, method, sequence); + } + + public static void SendToAll(this Packet packet, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0) + { + if (!Globals.IsServer) + { + SRMP.Log("Trying to send packet as server while not server"); + return; + } + + List cons = new List(); + foreach (var p in Globals.Players.Values) + { + if (p.Connection != null) + { + cons.Add(p.Connection); + } + } + NetworkServer.Instance.SendTo(packet, cons, method, sequence); + } + + public static void SendToAllInRegions(this Packet packet, NetworkPlayer player, bool includeSelf, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0) + { + if (!Globals.IsServer) + { + SRMP.Log("Trying to send packet as server while not server"); + return; + } + + List cons = new List(); + foreach(var netRegion in player.Regions) + { + foreach(var p in netRegion.Players) + { + if(p.ID != player.ID && p.Connection != null && !cons.Contains(p.Connection)) + { + cons.Add(p.Connection); + } + } + } + NetworkServer.Instance.SendTo(packet, cons, method, sequence); + } + + public static void SendToAllExcept(this Packet packet, NetworkPlayer player, NetDeliveryMethod method = NetDeliveryMethod.ReliableOrdered, int sequence = 0) + { + if (!Globals.IsServer) + { + SRMP.Log("Trying to send packet as server while not server"); + return; + } + + List cons = new List(); + foreach (var p in Globals.Players.Values) + { + if (p.ID != player.ID && p.Connection != null) + { + cons.Add(p.Connection); + } + } + NetworkServer.Instance.SendTo(packet, cons, method, sequence); + } + + public static T CopyComponent(this T original, GameObject destination) where T : Component + { + System.Type type = original.GetType(); + Component copy = destination.AddComponent(type); + System.Reflection.FieldInfo[] fields = type.GetFields(); + foreach (System.Reflection.FieldInfo field in fields) + { + field.SetValue(copy, field.GetValue(original)); + } + return copy as T; + } + + public static T GetOrAddComponent(this GameObject obj) where T : Component + { + var comp = obj.GetComponent(); + if(comp == null) + { + return obj.AddComponent(); + } + return comp; + } + + public static T GetInParent(this GameObject obj) where T : Component + { + var cmp = obj.GetComponent(); + if (cmp != null) + { + return cmp; + } + if (obj.transform.parent != null) + { + return GetInParent(obj.transform.parent.gameObject); + } + return default(T); + } + + public static string GetGameObjectPath(this Transform transform, bool withID = true) + { + string path = transform.name + (withID ? transform.GetSiblingIndex().ToString() : ""); + while (transform.parent != null) + { + transform = transform.parent; + path = transform.name + (withID ? transform.GetSiblingIndex().ToString() : "") + "/" + path; + } + return path; + } + + public static string GetGameObjectPath(this GameObject transform, bool withID = true) + { + return GetGameObjectPath(transform.transform, withID); + } + + public static bool GetBit(this byte b, int bitNumber) + { + return (b & (1 << bitNumber)) != 0; + } + + public static byte SetBit(this byte b, int position, bool value) + { + if(value) + { + return (byte)(b | (1 << position)); + } + else + { + return (byte)(b & ~(1 << position)); + } + } + + public static Transform FindDisabled(this Transform transform, string name) + { + if(transform.name.Equals(name, StringComparison.CurrentCulture)) + { + return transform; + } + foreach(Transform child in transform) + { + var found = FindDisabled(child, name); + if(found != null) + { + return found; + } + } + return null; + } + } +} diff --git a/SRMP/Globals.cs b/SRMP/Globals.cs new file mode 100644 index 0000000..dac99cf --- /dev/null +++ b/SRMP/Globals.cs @@ -0,0 +1,78 @@ +using SRMultiplayer.Networking; +using SRMultiplayer.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace SRMultiplayer +{ + public static class Globals + { + public static int Version; + public static UserData UserData; + public static GameObject BeatrixModel; + public static RuntimeAnimatorController BeatrixController; + public static GameObject IngameMultiplayerMenuPrefab; + public static GameObject MainMultiplayerMenuPrefab; + public static Dictionary Players = new Dictionary(); + public static string Username; + public static string ServerCode; + public static byte LocalID; + public static NetworkPlayer LocalPlayer; + public static bool HandlePacket; + public static Guid PartyID; + public static bool IsClient { get { return NetworkClient.Instance.Status == NetworkClient.ConnectionStatus.Connected; } } + public static bool IsServer { get { return NetworkServer.Instance.Status == NetworkServer.ServerStatus.Running; } } + public static bool IsMultiplayer { get { return IsClient || IsServer; } } + public static bool GameLoaded; + public static bool ClientLoaded; + public static bool DisableAchievements; + public static string CurrentGameName; + public static PauseState PauseState; + public static Dictionary Audios = new Dictionary(); + public static Dictionary Actors = new Dictionary(); + public static Dictionary Regions = new Dictionary(); + public static Dictionary LandPlots = new Dictionary(); + public static Dictionary FXPrefabs = new Dictionary(); + public static Dictionary AccessDoors = new Dictionary(); + public static Dictionary Gordos = new Dictionary(); + public static Dictionary SpawnResources = new Dictionary(); + public static Dictionary PuzzleSlots = new Dictionary(); + public static Dictionary Switches = new Dictionary(); + public static Dictionary GadgetSites = new Dictionary(); + public static Dictionary Spawners = new Dictionary(); + public static Dictionary TreasurePods = new Dictionary(); + public static Dictionary ExchangeAcceptors = new Dictionary(); + public static Dictionary FireColumns = new Dictionary(); + public static Dictionary Kookadobas = new Dictionary(); + public static Dictionary Nutcrackers = new Dictionary(); + public static Dictionary RaceTriggers = new Dictionary(); + public static List LemonTrees = new List(); + public static Dictionary PacketSize = new Dictionary(); + + public static List Mods + { + get + { + List mods = new List(); + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var assembly in assemblies) + { + if (!assembly.GetName().Name.Contains("Unity") && !assembly.GetName().Name.Contains("InControl") && !assembly.GetName().Name.Contains("DOTween") && + !assembly.GetName().Name.Contains("mscorlib") && !assembly.GetName().Name.Contains("System") && !assembly.GetName().Name.Contains("Assembly-CSharp") && + !assembly.GetName().Name.Contains("Logger") && !assembly.GetName().Name.Contains("Mono.") && !assembly.GetName().Name.Contains("Harmony") && + !assembly.GetName().Name.Equals("SRML") && !assembly.GetName().Name.Equals("SRML.Editor") && !assembly.GetName().Name.Equals("Newtonsoft.Json") && + !assembly.GetName().Name.Equals("INIFileParser") && !assembly.GetName().Name.Equals("SRMultiplayer") && !assembly.GetName().Name.Contains("Microsoft.") && + !assembly.GetName().Name.Equals("SRMP") && !assembly.GetName().Name.Equals("XGamingRuntime") && !Globals.UserData.IgnoredMods.Contains(assembly.GetName().Name)) + { + mods.Add(assembly.GetName().Name); + } + } + return mods; + } + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetAESEncryption.cs b/SRMP/Lidgren.Network/Encryption/NetAESEncryption.cs new file mode 100644 index 0000000..04bbe8b --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetAESEncryption.cs @@ -0,0 +1,38 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetAESEncryption : NetCryptoProviderBase + { + public NetAESEncryption(NetPeer peer) +#if UNITY + : base(peer, new RijndaelManaged()) +#else + : base(peer, new AesCryptoServiceProvider()) +#endif + { + } + + public NetAESEncryption(NetPeer peer, string key) +#if UNITY + : base(peer, new RijndaelManaged()) +#else + : base(peer, new AesCryptoServiceProvider()) +#endif + { + SetKey(key); + } + + public NetAESEncryption(NetPeer peer, byte[] data, int offset, int count) +#if UNITY + : base(peer, new RijndaelManaged()) +#else + : base(peer, new AesCryptoServiceProvider()) +#endif + { + SetKey(data, offset, count); + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs b/SRMP/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs new file mode 100644 index 0000000..f16aeba --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetBlockEncryptionBase.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + /// + /// Base for a non-threadsafe encryption class + /// + public abstract class NetBlockEncryptionBase : NetEncryption + { + // temporary space for one block to avoid reallocating every time + private byte[] m_tmp; + + /// + /// Block size in bytes for this cipher + /// + public abstract int BlockSize { get; } + + /// + /// NetBlockEncryptionBase constructor + /// + public NetBlockEncryptionBase(NetPeer peer) + : base(peer) + { + m_tmp = new byte[BlockSize]; + } + + /// + /// Encrypt am outgoing message with this algorithm; no writing can be done to the message after encryption, or message will be corrupted + /// + public override bool Encrypt(NetOutgoingMessage msg) + { + int payloadBitLength = msg.LengthBits; + int numBytes = msg.LengthBytes; + int blockSize = BlockSize; + int numBlocks = (int)Math.Ceiling((double)numBytes / (double)blockSize); + int dstSize = numBlocks * blockSize; + + msg.EnsureBufferSize(dstSize * 8 + (4 * 8)); // add 4 bytes for payload length at end + msg.LengthBits = dstSize * 8; // length will automatically adjust +4 bytes when payload length is written + + for(int i=0;i + /// Decrypt an incoming message encrypted with corresponding Encrypt + /// + /// message to decrypt + /// true if successful; false if failed + public override bool Decrypt(NetIncomingMessage msg) + { + int numEncryptedBytes = msg.LengthBytes - 4; // last 4 bytes is true bit length + int blockSize = BlockSize; + int numBlocks = numEncryptedBytes / blockSize; + if (numBlocks * blockSize != numEncryptedBytes) + return false; + + for (int i = 0; i < numBlocks; i++) + { + DecryptBlock(msg.m_data, (i * blockSize), m_tmp); + Buffer.BlockCopy(m_tmp, 0, msg.m_data, (i * blockSize), m_tmp.Length); + } + + // read 32 bits of true payload length + uint realSize = NetBitWriter.ReadUInt32(msg.m_data, 32, (numEncryptedBytes * 8)); + msg.m_bitLength = (int)realSize; + return true; + } + + /// + /// Encrypt a block of bytes + /// + protected abstract void EncryptBlock(byte[] source, int sourceOffset, byte[] destination); + + /// + /// Decrypt a block of bytes + /// + protected abstract void DecryptBlock(byte[] source, int sourceOffset, byte[] destination); + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetCryptoProviderBase.cs b/SRMP/Lidgren.Network/Encryption/NetCryptoProviderBase.cs new file mode 100644 index 0000000..e3ff3f4 --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetCryptoProviderBase.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public abstract class NetCryptoProviderBase : NetEncryption + { + protected SymmetricAlgorithm m_algorithm; + + public NetCryptoProviderBase(NetPeer peer, SymmetricAlgorithm algo) + : base(peer) + { + m_algorithm = algo; + m_algorithm.GenerateKey(); + m_algorithm.GenerateIV(); + } + + public override void SetKey(byte[] data, int offset, int count) + { + int len = m_algorithm.Key.Length; + var key = new byte[len]; + for (int i = 0; i < len; i++) + key[i] = data[offset + (i % count)]; + m_algorithm.Key = key; + + len = m_algorithm.IV.Length; + key = new byte[len]; + for (int i = 0; i < len; i++) + key[len - 1 - i] = data[offset + (i % count)]; + m_algorithm.IV = key; + } + + public override bool Encrypt(NetOutgoingMessage msg) + { + int unEncLenBits = msg.LengthBits; + + var ms = new MemoryStream(); + var cs = new CryptoStream(ms, m_algorithm.CreateEncryptor(), CryptoStreamMode.Write); + cs.Write(msg.m_data, 0, msg.LengthBytes); + cs.Close(); + + // get results + var arr = ms.ToArray(); + ms.Close(); + + msg.EnsureBufferSize((arr.Length + 4) * 8); + msg.LengthBits = 0; // reset write pointer + msg.Write((uint)unEncLenBits); + msg.Write(arr); + msg.LengthBits = (arr.Length + 4) * 8; + + return true; + } + + public override bool Decrypt(NetIncomingMessage msg) + { + int unEncLenBits = (int)msg.ReadUInt32(); + + var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4); + var cs = new CryptoStream(ms, m_algorithm.CreateDecryptor(), CryptoStreamMode.Read); + + var byteLen = NetUtility.BytesToHoldBits(unEncLenBits); + var result = m_peer.GetStorage(byteLen); + cs.Read(result, 0, byteLen); + cs.Close(); + + // TODO: recycle existing msg + + msg.m_data = result; + msg.m_bitLength = unEncLenBits; + msg.m_readPosition = 0; + + return true; + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs b/SRMP/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs new file mode 100644 index 0000000..d112046 --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetCryptoProviderEncryption.cs @@ -0,0 +1,59 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public abstract class NetCryptoProviderEncryption : NetEncryption + { + public NetCryptoProviderEncryption(NetPeer peer) + : base(peer) + { + } + + protected abstract CryptoStream GetEncryptStream(MemoryStream ms); + + protected abstract CryptoStream GetDecryptStream(MemoryStream ms); + + public override bool Encrypt(NetOutgoingMessage msg) + { + int unEncLenBits = msg.LengthBits; + + var ms = new MemoryStream(); + var cs = GetEncryptStream(ms); + cs.Write(msg.m_data, 0, msg.LengthBytes); + cs.Close(); + + // get results + var arr = ms.ToArray(); + ms.Close(); + + msg.EnsureBufferSize((arr.Length + 4) * 8); + msg.LengthBits = 0; // reset write pointer + msg.Write((uint)unEncLenBits); + msg.Write(arr); + msg.LengthBits = (arr.Length + 4) * 8; + + return true; + } + + public override bool Decrypt(NetIncomingMessage msg) + { + int unEncLenBits = (int)msg.ReadUInt32(); + + var ms = new MemoryStream(msg.m_data, 4, msg.LengthBytes - 4); + var cs = GetDecryptStream(ms); + + var result = m_peer.GetStorage(unEncLenBits); + cs.Read(result, 0, NetUtility.BytesToHoldBits(unEncLenBits)); + cs.Close(); + + // TODO: recycle existing msg + + msg.m_data = result; + msg.m_bitLength = unEncLenBits; + + return true; + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetDESEncryption.cs b/SRMP/Lidgren.Network/Encryption/NetDESEncryption.cs new file mode 100644 index 0000000..b6ab813 --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetDESEncryption.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetDESEncryption : NetCryptoProviderBase + { + public NetDESEncryption(NetPeer peer) + : base(peer, new DESCryptoServiceProvider()) + { + } + + public NetDESEncryption(NetPeer peer, string key) + : base(peer, new DESCryptoServiceProvider()) + { + SetKey(key); + } + + public NetDESEncryption(NetPeer peer, byte[] data, int offset, int count) + : base(peer, new DESCryptoServiceProvider()) + { + SetKey(data, offset, count); + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetEncryption.cs b/SRMP/Lidgren.Network/Encryption/NetEncryption.cs new file mode 100644 index 0000000..fea1e7c --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetEncryption.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + /// + /// Interface for an encryption algorithm + /// + public abstract class NetEncryption + { + /// + /// NetPeer + /// + protected NetPeer m_peer; + + /// + /// Constructor + /// + public NetEncryption(NetPeer peer) + { + if (peer == null) + throw new NetException("Peer must not be null"); + m_peer = peer; + } + + public void SetKey(string str) + { + var bytes = System.Text.Encoding.ASCII.GetBytes(str); + SetKey(bytes, 0, bytes.Length); + } + + public abstract void SetKey(byte[] data, int offset, int count); + + /// + /// Encrypt an outgoing message in place + /// + public abstract bool Encrypt(NetOutgoingMessage msg); + + /// + /// Decrypt an incoming message in place + /// + public abstract bool Decrypt(NetIncomingMessage msg); + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetRC2Encryption.cs b/SRMP/Lidgren.Network/Encryption/NetRC2Encryption.cs new file mode 100644 index 0000000..a2d0c23 --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetRC2Encryption.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetRC2Encryption : NetCryptoProviderBase + { + public NetRC2Encryption(NetPeer peer) + : base(peer, new RC2CryptoServiceProvider()) + { + } + + public NetRC2Encryption(NetPeer peer, string key) + : base(peer, new RC2CryptoServiceProvider()) + { + SetKey(key); + } + + public NetRC2Encryption(NetPeer peer, byte[] data, int offset, int count) + : base(peer, new RC2CryptoServiceProvider()) + { + SetKey(data, offset, count); + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetTripleDESEncryption.cs b/SRMP/Lidgren.Network/Encryption/NetTripleDESEncryption.cs new file mode 100644 index 0000000..58044d5 --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetTripleDESEncryption.cs @@ -0,0 +1,26 @@ +using System; +using System.IO; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public class NetTripleDESEncryption : NetCryptoProviderBase + { + public NetTripleDESEncryption(NetPeer peer) + : base(peer, new TripleDESCryptoServiceProvider()) + { + } + + public NetTripleDESEncryption(NetPeer peer, string key) + : base(peer, new TripleDESCryptoServiceProvider()) + { + SetKey(key); + } + + public NetTripleDESEncryption(NetPeer peer, byte[] data, int offset, int count) + : base(peer, new TripleDESCryptoServiceProvider()) + { + SetKey(data, offset, count); + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetXorEncryption.cs b/SRMP/Lidgren.Network/Encryption/NetXorEncryption.cs new file mode 100644 index 0000000..04d8e50 --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetXorEncryption.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Example class; not very good encryption + /// + public class NetXorEncryption : NetEncryption + { + private byte[] m_key; + + /// + /// NetXorEncryption constructor + /// + public NetXorEncryption(NetPeer peer, byte[] key) + : base(peer) + { + m_key = key; + } + + public override void SetKey(byte[] data, int offset, int count) + { + m_key = new byte[count]; + Array.Copy(data, offset, m_key, 0, count); + } + + /// + /// NetXorEncryption constructor + /// + public NetXorEncryption(NetPeer peer, string key) + : base(peer) + { + m_key = Encoding.UTF8.GetBytes(key); + } + + /// + /// Encrypt an outgoing message + /// + public override bool Encrypt(NetOutgoingMessage msg) + { + int numBytes = msg.LengthBytes; + for (int i = 0; i < numBytes; i++) + { + int offset = i % m_key.Length; + msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + } + return true; + } + + /// + /// Decrypt an incoming message + /// + public override bool Decrypt(NetIncomingMessage msg) + { + int numBytes = msg.LengthBytes; + for (int i = 0; i < numBytes; i++) + { + int offset = i % m_key.Length; + msg.m_data[i] = (byte)(msg.m_data[i] ^ m_key[offset]); + } + return true; + } + } +} diff --git a/SRMP/Lidgren.Network/Encryption/NetXteaEncryption.cs b/SRMP/Lidgren.Network/Encryption/NetXteaEncryption.cs new file mode 100644 index 0000000..600e408 --- /dev/null +++ b/SRMP/Lidgren.Network/Encryption/NetXteaEncryption.cs @@ -0,0 +1,154 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Security.Cryptography; +using System.Text; +using System.Security; + +namespace Lidgren.Network +{ + /// + /// Methods to encrypt and decrypt data using the XTEA algorithm + /// + public sealed class NetXtea : NetBlockEncryptionBase + { + private const int c_blockSize = 8; + private const int c_keySize = 16; + private const int c_delta = unchecked((int)0x9E3779B9); + + private readonly int m_numRounds; + private readonly uint[] m_sum0; + private readonly uint[] m_sum1; + + /// + /// Gets the block size for this cipher + /// + public override int BlockSize { get { return c_blockSize; } } + + /// + /// 16 byte key + /// + public NetXtea(NetPeer peer, byte[] key, int rounds) + : base(peer) + { + if (key.Length < c_keySize) + throw new NetException("Key too short!"); + + m_numRounds = rounds; + m_sum0 = new uint[m_numRounds]; + m_sum1 = new uint[m_numRounds]; + uint[] tmp = new uint[8]; + + int num2; + int index = num2 = 0; + while (index < 4) + { + tmp[index] = BitConverter.ToUInt32(key, num2); + index++; + num2 += 4; + } + for (index = num2 = 0; index < 32; index++) + { + m_sum0[index] = ((uint)num2) + tmp[num2 & 3]; + num2 += -1640531527; + m_sum1[index] = ((uint)num2) + tmp[(num2 >> 11) & 3]; + } + } + + /// + /// 16 byte key + /// + public NetXtea(NetPeer peer, byte[] key) + : this(peer, key, 32) + { + } + + /// + /// String to hash for key + /// + public NetXtea(NetPeer peer, string key) + : this(peer, NetUtility.ComputeSHAHash(Encoding.UTF8.GetBytes(key)), 32) + { + } + + public override void SetKey(byte[] data, int offset, int length) + { + var key = NetUtility.ComputeSHAHash(data, offset, length); + NetException.Assert(key.Length >= 16); + SetKey(key, 0, 16); + } + + /// + /// Encrypts a block of bytes + /// + protected override void EncryptBlock(byte[] source, int sourceOffset, byte[] destination) + { + uint v0 = BytesToUInt(source, sourceOffset); + uint v1 = BytesToUInt(source, sourceOffset + 4); + + for (int i = 0; i != m_numRounds; i++) + { + v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i]; + v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i]; + } + + UIntToBytes(v0, destination, 0); + UIntToBytes(v1, destination, 0 + 4); + + return; + } + + /// + /// Decrypts a block of bytes + /// + protected override void DecryptBlock(byte[] source, int sourceOffset, byte[] destination) + { + // Pack bytes into integers + uint v0 = BytesToUInt(source, sourceOffset); + uint v1 = BytesToUInt(source, sourceOffset + 4); + + for (int i = m_numRounds - 1; i >= 0; i--) + { + v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ m_sum1[i]; + v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ m_sum0[i]; + } + + UIntToBytes(v0, destination, 0); + UIntToBytes(v1, destination, 0 + 4); + + return; + } + + private static uint BytesToUInt(byte[] bytes, int offset) + { + uint retval = (uint)(bytes[offset] << 24); + retval |= (uint)(bytes[++offset] << 16); + retval |= (uint)(bytes[++offset] << 8); + return (retval | bytes[++offset]); + } + + private static void UIntToBytes(uint value, byte[] destination, int destinationOffset) + { + destination[destinationOffset++] = (byte)(value >> 24); + destination[destinationOffset++] = (byte)(value >> 16); + destination[destinationOffset++] = (byte)(value >> 8); + destination[destinationOffset++] = (byte)value; + } + } +} diff --git a/SRMP/Lidgren.Network/NamespaceDoc.cs b/SRMP/Lidgren.Network/NamespaceDoc.cs new file mode 100644 index 0000000..b7d6db0 --- /dev/null +++ b/SRMP/Lidgren.Network/NamespaceDoc.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Lidgren Network Library + /// + internal class NamespaceDoc + { + // + } +} diff --git a/SRMP/Lidgren.Network/NetBigInteger.cs b/SRMP/Lidgren.Network/NetBigInteger.cs new file mode 100644 index 0000000..f9a607d --- /dev/null +++ b/SRMP/Lidgren.Network/NetBigInteger.cs @@ -0,0 +1,2352 @@ +using System; +using System.Text; +using System.Collections; +using System.Diagnostics; +using System.Globalization; + +namespace Lidgren.Network +{ + /// + /// Big integer class based on BouncyCastle (http://www.bouncycastle.org) big integer code + /// + internal class NetBigInteger + { + private const long IMASK = 0xffffffffL; + private const ulong UIMASK = (ulong)IMASK; + + private static readonly int[] ZeroMagnitude = new int[0]; + private static readonly byte[] ZeroEncoding = new byte[0]; + + public static readonly NetBigInteger Zero = new NetBigInteger(0, ZeroMagnitude, false); + public static readonly NetBigInteger One = createUValueOf(1); + public static readonly NetBigInteger Two = createUValueOf(2); + public static readonly NetBigInteger Three = createUValueOf(3); + public static readonly NetBigInteger Ten = createUValueOf(10); + + private const int chunk2 = 1; + private static readonly NetBigInteger radix2 = ValueOf(2); + private static readonly NetBigInteger radix2E = radix2.Pow(chunk2); + + private const int chunk10 = 19; + private static readonly NetBigInteger radix10 = ValueOf(10); + private static readonly NetBigInteger radix10E = radix10.Pow(chunk10); + + private const int chunk16 = 16; + private static readonly NetBigInteger radix16 = ValueOf(16); + private static readonly NetBigInteger radix16E = radix16.Pow(chunk16); + + private const int BitsPerByte = 8; + private const int BitsPerInt = 32; + private const int BytesPerInt = 4; + + private int m_sign; // -1 means -ve; +1 means +ve; 0 means 0; + private int[] m_magnitude; // array of ints with [0] being the most significant + private int m_numBits = -1; // cache BitCount() value + private int m_numBitLength = -1; // cache calcBitLength() value + private long m_quote = -1L; // -m^(-1) mod b, b = 2^32 (see Montgomery mult.) + + private static int GetByteLength( + int nBits) + { + return (nBits + BitsPerByte - 1) / BitsPerByte; + } + + private NetBigInteger() + { + } + + private NetBigInteger( + int signum, + int[] mag, + bool checkMag) + { + if (checkMag) + { + int i = 0; + while (i < mag.Length && mag[i] == 0) + { + ++i; + } + + if (i == mag.Length) + { + // sign = 0; + m_magnitude = ZeroMagnitude; + } + else + { + m_sign = signum; + + if (i == 0) + { + m_magnitude = mag; + } + else + { + // strip leading 0 words + m_magnitude = new int[mag.Length - i]; + Array.Copy(mag, i, m_magnitude, 0, m_magnitude.Length); + } + } + } + else + { + m_sign = signum; + m_magnitude = mag; + } + } + + public NetBigInteger( + string value) + : this(value, 10) + { + } + + public NetBigInteger( + string str, + int radix) + { + if (str.Length == 0) + throw new FormatException("Zero length BigInteger"); + + NumberStyles style; + int chunk; + NetBigInteger r; + NetBigInteger rE; + + switch (radix) + { + case 2: + // Is there anyway to restrict to binary digits? + style = NumberStyles.Integer; + chunk = chunk2; + r = radix2; + rE = radix2E; + break; + case 10: + // This style seems to handle spaces and minus sign already (our processing redundant?) + style = NumberStyles.Integer; + chunk = chunk10; + r = radix10; + rE = radix10E; + break; + case 16: + // TODO Should this be HexNumber? + style = NumberStyles.AllowHexSpecifier; + chunk = chunk16; + r = radix16; + rE = radix16E; + break; + default: + throw new FormatException("Only bases 2, 10, or 16 allowed"); + } + + + int index = 0; + m_sign = 1; + + if (str[0] == '-') + { + if (str.Length == 1) + throw new FormatException("Zero length BigInteger"); + + m_sign = -1; + index = 1; + } + + // strip leading zeros from the string str + while (index < str.Length && Int32.Parse(str[index].ToString(), style) == 0) + { + index++; + } + + if (index >= str.Length) + { + // zero value - we're done + m_sign = 0; + m_magnitude = ZeroMagnitude; + return; + } + + ////// + // could we work out the max number of ints required to store + // str.Length digits in the given base, then allocate that + // storage in one hit?, then Generate the magnitude in one hit too? + ////// + + NetBigInteger b = Zero; + + + int next = index + chunk; + + if (next <= str.Length) + { + do + { + string s = str.Substring(index, chunk); + ulong i = ulong.Parse(s, style); + NetBigInteger bi = createUValueOf(i); + + switch (radix) + { + case 2: + if (i > 1) + throw new FormatException("Bad character in radix 2 string: " + s); + + b = b.ShiftLeft(1); + break; + case 16: + b = b.ShiftLeft(64); + break; + default: + b = b.Multiply(rE); + break; + } + + b = b.Add(bi); + + index = next; + next += chunk; + } + while (next <= str.Length); + } + + if (index < str.Length) + { + string s = str.Substring(index); + ulong i = ulong.Parse(s, style); + NetBigInteger bi = createUValueOf(i); + + if (b.m_sign > 0) + { + if (radix == 2) + { + // NB: Can't reach here since we are parsing one char at a time + Debug.Assert(false); + } + else if (radix == 16) + { + b = b.ShiftLeft(s.Length << 2); + } + else + { + b = b.Multiply(r.Pow(s.Length)); + } + + b = b.Add(bi); + } + else + { + b = bi; + } + } + + // Note: This is the previous (slower) algorithm + // while (index < value.Length) + // { + // char c = value[index]; + // string s = c.ToString(); + // int i = Int32.Parse(s, style); + // + // b = b.Multiply(r).Add(ValueOf(i)); + // index++; + // } + + m_magnitude = b.m_magnitude; + } + + public NetBigInteger( + byte[] bytes) + : this(bytes, 0, bytes.Length) + { + } + + public NetBigInteger( + byte[] bytes, + int offset, + int length) + { + if (length == 0) + throw new FormatException("Zero length BigInteger"); + if ((sbyte)bytes[offset] < 0) + { + m_sign = -1; + + int end = offset + length; + + int iBval; + // strip leading sign bytes + for (iBval = offset; iBval < end && ((sbyte)bytes[iBval] == -1); iBval++) + { + } + + if (iBval >= end) + { + m_magnitude = One.m_magnitude; + } + else + { + int numBytes = end - iBval; + byte[] inverse = new byte[numBytes]; + + int index = 0; + while (index < numBytes) + { + inverse[index++] = (byte)~bytes[iBval++]; + } + + Debug.Assert(iBval == end); + + while (inverse[--index] == byte.MaxValue) + { + inverse[index] = byte.MinValue; + } + + inverse[index]++; + + m_magnitude = MakeMagnitude(inverse, 0, inverse.Length); + } + } + else + { + // strip leading zero bytes and return magnitude bytes + m_magnitude = MakeMagnitude(bytes, offset, length); + m_sign = m_magnitude.Length > 0 ? 1 : 0; + } + } + + private static int[] MakeMagnitude( + byte[] bytes, + int offset, + int length) + { + int end = offset + length; + + // strip leading zeros + int firstSignificant; + for (firstSignificant = offset; firstSignificant < end + && bytes[firstSignificant] == 0; firstSignificant++) + { + } + + if (firstSignificant >= end) + { + return ZeroMagnitude; + } + + int nInts = (end - firstSignificant + 3) / BytesPerInt; + int bCount = (end - firstSignificant) % BytesPerInt; + if (bCount == 0) + { + bCount = BytesPerInt; + } + + if (nInts < 1) + { + return ZeroMagnitude; + } + + int[] mag = new int[nInts]; + + int v = 0; + int magnitudeIndex = 0; + for (int i = firstSignificant; i < end; ++i) + { + v <<= 8; + v |= bytes[i] & 0xff; + bCount--; + if (bCount <= 0) + { + mag[magnitudeIndex] = v; + magnitudeIndex++; + bCount = BytesPerInt; + v = 0; + } + } + + if (magnitudeIndex < mag.Length) + { + mag[magnitudeIndex] = v; + } + + return mag; + } + + public NetBigInteger( + int sign, + byte[] bytes) + : this(sign, bytes, 0, bytes.Length) + { + } + + public NetBigInteger( + int sign, + byte[] bytes, + int offset, + int length) + { + if (sign < -1 || sign > 1) + throw new FormatException("Invalid sign value"); + + if (sign == 0) + { + //sign = 0; + m_magnitude = ZeroMagnitude; + } + else + { + // copy bytes + m_magnitude = MakeMagnitude(bytes, offset, length); + m_sign = m_magnitude.Length < 1 ? 0 : sign; + } + } + + public NetBigInteger Abs() + { + return m_sign >= 0 ? this : Negate(); + } + + // return a = a + b - b preserved. + private static int[] AddMagnitudes( + int[] a, + int[] b) + { + int tI = a.Length - 1; + int vI = b.Length - 1; + long m = 0; + + while (vI >= 0) + { + m += ((long)(uint)a[tI] + (long)(uint)b[vI--]); + a[tI--] = (int)m; + m = (long)((ulong)m >> 32); + } + + if (m != 0) + { + while (tI >= 0 && ++a[tI--] == 0) + { + } + } + + return a; + } + + public NetBigInteger Add( + NetBigInteger value) + { + if (m_sign == 0) + return value; + + if (m_sign != value.m_sign) + { + if (value.m_sign == 0) + return this; + + if (value.m_sign < 0) + return Subtract(value.Negate()); + + return value.Subtract(Negate()); + } + + return AddToMagnitude(value.m_magnitude); + } + + private NetBigInteger AddToMagnitude( + int[] magToAdd) + { + int[] big, small; + if (m_magnitude.Length < magToAdd.Length) + { + big = magToAdd; + small = m_magnitude; + } + else + { + big = m_magnitude; + small = magToAdd; + } + + // Conservatively avoid over-allocation when no overflow possible + uint limit = uint.MaxValue; + if (big.Length == small.Length) + limit -= (uint)small[0]; + + bool possibleOverflow = (uint)big[0] >= limit; + + int[] bigCopy; + if (possibleOverflow) + { + bigCopy = new int[big.Length + 1]; + big.CopyTo(bigCopy, 1); + } + else + { + bigCopy = (int[])big.Clone(); + } + + bigCopy = AddMagnitudes(bigCopy, small); + + return new NetBigInteger(m_sign, bigCopy, possibleOverflow); + } + + public NetBigInteger And( + NetBigInteger value) + { + if (m_sign == 0 || value.m_sign == 0) + { + return Zero; + } + + int[] aMag = m_sign > 0 + ? m_magnitude + : Add(One).m_magnitude; + + int[] bMag = value.m_sign > 0 + ? value.m_magnitude + : value.Add(One).m_magnitude; + + bool resultNeg = m_sign < 0 && value.m_sign < 0; + int resultLength = System.Math.Max(aMag.Length, bMag.Length); + int[] resultMag = new int[resultLength]; + + int aStart = resultMag.Length - aMag.Length; + int bStart = resultMag.Length - bMag.Length; + + for (int i = 0; i < resultMag.Length; ++i) + { + int aWord = i >= aStart ? aMag[i - aStart] : 0; + int bWord = i >= bStart ? bMag[i - bStart] : 0; + + if (m_sign < 0) + { + aWord = ~aWord; + } + + if (value.m_sign < 0) + { + bWord = ~bWord; + } + + resultMag[i] = aWord & bWord; + + if (resultNeg) + { + resultMag[i] = ~resultMag[i]; + } + } + + NetBigInteger result = new NetBigInteger(1, resultMag, true); + + if (resultNeg) + { + result = result.Not(); + } + + return result; + } + + private int calcBitLength( + int indx, + int[] mag) + { + for (; ; ) + { + if (indx >= mag.Length) + return 0; + + if (mag[indx] != 0) + break; + + ++indx; + } + + // bit length for everything after the first int + int bitLength = 32 * ((mag.Length - indx) - 1); + + // and determine bitlength of first int + int firstMag = mag[indx]; + bitLength += BitLen(firstMag); + + // Check for negative powers of two + if (m_sign < 0 && ((firstMag & -firstMag) == firstMag)) + { + do + { + if (++indx >= mag.Length) + { + --bitLength; + break; + } + } + while (mag[indx] == 0); + } + + return bitLength; + } + + public int BitLength + { + get + { + if (m_numBitLength == -1) + { + m_numBitLength = m_sign == 0 + ? 0 + : calcBitLength(0, m_magnitude); + } + + return m_numBitLength; + } + } + + // + // BitLen(value) is the number of bits in value. + // + private static int BitLen( + int w) + { + // Binary search - decision tree (5 tests, rarely 6) + return (w < 1 << 15 ? (w < 1 << 7 + ? (w < 1 << 3 ? (w < 1 << 1 + ? (w < 1 << 0 ? (w < 0 ? 32 : 0) : 1) + : (w < 1 << 2 ? 2 : 3)) : (w < 1 << 5 + ? (w < 1 << 4 ? 4 : 5) + : (w < 1 << 6 ? 6 : 7))) + : (w < 1 << 11 + ? (w < 1 << 9 ? (w < 1 << 8 ? 8 : 9) : (w < 1 << 10 ? 10 : 11)) + : (w < 1 << 13 ? (w < 1 << 12 ? 12 : 13) : (w < 1 << 14 ? 14 : 15)))) : (w < 1 << 23 ? (w < 1 << 19 + ? (w < 1 << 17 ? (w < 1 << 16 ? 16 : 17) : (w < 1 << 18 ? 18 : 19)) + : (w < 1 << 21 ? (w < 1 << 20 ? 20 : 21) : (w < 1 << 22 ? 22 : 23))) : (w < 1 << 27 + ? (w < 1 << 25 ? (w < 1 << 24 ? 24 : 25) : (w < 1 << 26 ? 26 : 27)) + : (w < 1 << 29 ? (w < 1 << 28 ? 28 : 29) : (w < 1 << 30 ? 30 : 31))))); + } + + private bool QuickPow2Check() + { + return m_sign > 0 && m_numBits == 1; + } + + public int CompareTo( + object obj) + { + return CompareTo((NetBigInteger)obj); + } + + + // unsigned comparison on two arrays - note the arrays may + // start with leading zeros. + private static int CompareTo( + int xIndx, + int[] x, + int yIndx, + int[] y) + { + while (xIndx != x.Length && x[xIndx] == 0) + { + xIndx++; + } + + while (yIndx != y.Length && y[yIndx] == 0) + { + yIndx++; + } + + return CompareNoLeadingZeroes(xIndx, x, yIndx, y); + } + + private static int CompareNoLeadingZeroes( + int xIndx, + int[] x, + int yIndx, + int[] y) + { + int diff = (x.Length - y.Length) - (xIndx - yIndx); + + if (diff != 0) + { + return diff < 0 ? -1 : 1; + } + + // lengths of magnitudes the same, test the magnitude values + + while (xIndx < x.Length) + { + uint v1 = (uint)x[xIndx++]; + uint v2 = (uint)y[yIndx++]; + + if (v1 != v2) + return v1 < v2 ? -1 : 1; + } + + return 0; + } + + public int CompareTo( + NetBigInteger value) + { + return m_sign < value.m_sign ? -1 + : m_sign > value.m_sign ? 1 + : m_sign == 0 ? 0 + : m_sign * CompareNoLeadingZeroes(0, m_magnitude, 0, value.m_magnitude); + } + + // return z = x / y - done in place (z value preserved, x contains the remainder) + private int[] Divide( + int[] x, + int[] y) + { + int xStart = 0; + while (xStart < x.Length && x[xStart] == 0) + { + ++xStart; + } + + int yStart = 0; + while (yStart < y.Length && y[yStart] == 0) + { + ++yStart; + } + + Debug.Assert(yStart < y.Length); + + int xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + int[] count; + + if (xyCmp > 0) + { + int yBitLength = calcBitLength(yStart, y); + int xBitLength = calcBitLength(xStart, x); + int shift = xBitLength - yBitLength; + + int[] iCount; + int iCountStart = 0; + + int[] c; + int cStart = 0; + int cBitLength = yBitLength; + if (shift > 0) + { + // iCount = ShiftLeft(One.magnitude, shift); + iCount = new int[(shift >> 5) + 1]; + iCount[0] = 1 << (shift % 32); + + c = ShiftLeft(y, shift); + cBitLength += shift; + } + else + { + iCount = new int[] { 1 }; + + int len = y.Length - yStart; + c = new int[len]; + Array.Copy(y, yStart, c, 0, len); + } + + count = new int[iCount.Length]; + + for (; ; ) + { + if (cBitLength < xBitLength + || CompareNoLeadingZeroes(xStart, x, cStart, c) >= 0) + { + Subtract(xStart, x, cStart, c); + AddMagnitudes(count, iCount); + + while (x[xStart] == 0) + { + if (++xStart == x.Length) + return count; + } + + //xBitLength = calcBitLength(xStart, x); + xBitLength = 32 * (x.Length - xStart - 1) + BitLen(x[xStart]); + + if (xBitLength <= yBitLength) + { + if (xBitLength < yBitLength) + return count; + + xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp <= 0) + break; + } + } + + shift = cBitLength - xBitLength; + + // NB: The case where c[cStart] is 1-bit is harmless + if (shift == 1) + { + uint firstC = (uint)c[cStart] >> 1; + uint firstX = (uint)x[xStart]; + if (firstC > firstX) + ++shift; + } + + if (shift < 2) + { + c = ShiftRightOneInPlace(cStart, c); + --cBitLength; + iCount = ShiftRightOneInPlace(iCountStart, iCount); + } + else + { + c = ShiftRightInPlace(cStart, c, shift); + cBitLength -= shift; + iCount = ShiftRightInPlace(iCountStart, iCount, shift); + } + + //cStart = c.Length - ((cBitLength + 31) / 32); + while (c[cStart] == 0) + { + ++cStart; + } + + while (iCount[iCountStart] == 0) + { + ++iCountStart; + } + } + } + else + { + count = new int[1]; + } + + if (xyCmp == 0) + { + AddMagnitudes(count, One.m_magnitude); + Array.Clear(x, xStart, x.Length - xStart); + } + + return count; + } + + public NetBigInteger Divide( + NetBigInteger val) + { + if (val.m_sign == 0) + throw new ArithmeticException("Division by zero error"); + + if (m_sign == 0) + return Zero; + + if (val.QuickPow2Check()) // val is power of two + { + NetBigInteger result = Abs().ShiftRight(val.Abs().BitLength - 1); + return val.m_sign == m_sign ? result : result.Negate(); + } + + int[] mag = (int[])m_magnitude.Clone(); + + return new NetBigInteger(m_sign * val.m_sign, Divide(mag, val.m_magnitude), true); + } + + public NetBigInteger[] DivideAndRemainder( + NetBigInteger val) + { + if (val.m_sign == 0) + throw new ArithmeticException("Division by zero error"); + + NetBigInteger[] biggies = new NetBigInteger[2]; + + if (m_sign == 0) + { + biggies[0] = Zero; + biggies[1] = Zero; + } + else if (val.QuickPow2Check()) // val is power of two + { + int e = val.Abs().BitLength - 1; + NetBigInteger quotient = Abs().ShiftRight(e); + int[] remainder = LastNBits(e); + + biggies[0] = val.m_sign == m_sign ? quotient : quotient.Negate(); + biggies[1] = new NetBigInteger(m_sign, remainder, true); + } + else + { + int[] remainder = (int[])m_magnitude.Clone(); + int[] quotient = Divide(remainder, val.m_magnitude); + + biggies[0] = new NetBigInteger(m_sign * val.m_sign, quotient, true); + biggies[1] = new NetBigInteger(m_sign, remainder, true); + } + + return biggies; + } + + public override bool Equals( + object obj) + { + if (obj == this) + return true; + + NetBigInteger biggie = obj as NetBigInteger; + if (biggie == null) + return false; + + if (biggie.m_sign != m_sign || biggie.m_magnitude.Length != m_magnitude.Length) + return false; + + for (int i = 0; i < m_magnitude.Length; i++) + { + if (biggie.m_magnitude[i] != m_magnitude[i]) + { + return false; + } + } + + return true; + } + + public NetBigInteger Gcd( + NetBigInteger value) + { + if (value.m_sign == 0) + return Abs(); + + if (m_sign == 0) + return value.Abs(); + + NetBigInteger r; + NetBigInteger u = this; + NetBigInteger v = value; + + while (v.m_sign != 0) + { + r = u.Mod(v); + u = v; + v = r; + } + + return u; + } + + public override int GetHashCode() + { + int hc = m_magnitude.Length; + if (m_magnitude.Length > 0) + { + hc ^= m_magnitude[0]; + + if (m_magnitude.Length > 1) + { + hc ^= m_magnitude[m_magnitude.Length - 1]; + } + } + + return m_sign < 0 ? ~hc : hc; + } + + private NetBigInteger Inc() + { + if (m_sign == 0) + return One; + + if (m_sign < 0) + return new NetBigInteger(-1, doSubBigLil(m_magnitude, One.m_magnitude), true); + + return AddToMagnitude(One.m_magnitude); + } + + public int IntValue + { + get + { + return m_sign == 0 ? 0 + : m_sign > 0 ? m_magnitude[m_magnitude.Length - 1] + : -m_magnitude[m_magnitude.Length - 1]; + } + } + + public NetBigInteger Max( + NetBigInteger value) + { + return CompareTo(value) > 0 ? this : value; + } + + public NetBigInteger Min( + NetBigInteger value) + { + return CompareTo(value) < 0 ? this : value; + } + + public NetBigInteger Mod( + NetBigInteger m) + { + if (m.m_sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + NetBigInteger biggie = Remainder(m); + + return (biggie.m_sign >= 0 ? biggie : biggie.Add(m)); + } + + public NetBigInteger ModInverse( + NetBigInteger m) + { + if (m.m_sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + NetBigInteger x = new NetBigInteger(); + NetBigInteger gcd = ExtEuclid(this, m, x, null); + + if (!gcd.Equals(One)) + throw new ArithmeticException("Numbers not relatively prime."); + + if (x.m_sign < 0) + { + x.m_sign = 1; + //x = m.Subtract(x); + x.m_magnitude = doSubBigLil(m.m_magnitude, x.m_magnitude); + } + + return x; + } + + private static NetBigInteger ExtEuclid( + NetBigInteger a, + NetBigInteger b, + NetBigInteger u1Out, + NetBigInteger u2Out) + { + NetBigInteger u1 = One; + NetBigInteger u3 = a; + NetBigInteger v1 = Zero; + NetBigInteger v3 = b; + + while (v3.m_sign > 0) + { + NetBigInteger[] q = u3.DivideAndRemainder(v3); + + NetBigInteger tmp = v1.Multiply(q[0]); + NetBigInteger tn = u1.Subtract(tmp); + u1 = v1; + v1 = tn; + + u3 = v3; + v3 = q[1]; + } + + if (u1Out != null) + { + u1Out.m_sign = u1.m_sign; + u1Out.m_magnitude = u1.m_magnitude; + } + + if (u2Out != null) + { + NetBigInteger tmp = u1.Multiply(a); + tmp = u3.Subtract(tmp); + NetBigInteger res = tmp.Divide(b); + u2Out.m_sign = res.m_sign; + u2Out.m_magnitude = res.m_magnitude; + } + + return u3; + } + + private static void ZeroOut( + int[] x) + { + Array.Clear(x, 0, x.Length); + } + + public NetBigInteger ModPow( + NetBigInteger exponent, + NetBigInteger m) + { + if (m.m_sign < 1) + throw new ArithmeticException("Modulus must be positive"); + + if (m.Equals(One)) + return Zero; + + if (exponent.m_sign == 0) + return One; + + if (m_sign == 0) + return Zero; + + int[] zVal = null; + int[] yAccum = null; + int[] yVal; + + // Montgomery exponentiation is only possible if the modulus is odd, + // but AFAIK, this is always the case for crypto algo's + bool useMonty = ((m.m_magnitude[m.m_magnitude.Length - 1] & 1) == 1); + long mQ = 0; + if (useMonty) + { + mQ = m.GetMQuote(); + + // tmp = this * R mod m + NetBigInteger tmp = ShiftLeft(32 * m.m_magnitude.Length).Mod(m); + zVal = tmp.m_magnitude; + + useMonty = (zVal.Length <= m.m_magnitude.Length); + + if (useMonty) + { + yAccum = new int[m.m_magnitude.Length + 1]; + if (zVal.Length < m.m_magnitude.Length) + { + int[] longZ = new int[m.m_magnitude.Length]; + zVal.CopyTo(longZ, longZ.Length - zVal.Length); + zVal = longZ; + } + } + } + + if (!useMonty) + { + if (m_magnitude.Length <= m.m_magnitude.Length) + { + //zAccum = new int[m.magnitude.Length * 2]; + zVal = new int[m.m_magnitude.Length]; + m_magnitude.CopyTo(zVal, zVal.Length - m_magnitude.Length); + } + else + { + // + // in normal practice we'll never see .. + // + NetBigInteger tmp = Remainder(m); + + //zAccum = new int[m.magnitude.Length * 2]; + zVal = new int[m.m_magnitude.Length]; + tmp.m_magnitude.CopyTo(zVal, zVal.Length - tmp.m_magnitude.Length); + } + + yAccum = new int[m.m_magnitude.Length * 2]; + } + + yVal = new int[m.m_magnitude.Length]; + + // + // from LSW to MSW + // + for (int i = 0; i < exponent.m_magnitude.Length; i++) + { + int v = exponent.m_magnitude[i]; + int bits = 0; + + if (i == 0) + { + while (v > 0) + { + v <<= 1; + bits++; + } + + // + // first time in initialise y + // + zVal.CopyTo(yVal, 0); + + v <<= 1; + bits++; + } + + while (v != 0) + { + if (useMonty) + { + // Montgomery square algo doesn't exist, and a normal + // square followed by a Montgomery reduction proved to + // be almost as heavy as a Montgomery mulitply. + MultiplyMonty(yAccum, yVal, yVal, m.m_magnitude, mQ); + } + else + { + Square(yAccum, yVal); + Remainder(yAccum, m.m_magnitude); + Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, yVal.Length); + ZeroOut(yAccum); + } + bits++; + + if (v < 0) + { + if (useMonty) + { + MultiplyMonty(yAccum, yVal, zVal, m.m_magnitude, mQ); + } + else + { + Multiply(yAccum, yVal, zVal); + Remainder(yAccum, m.m_magnitude); + Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, + yVal.Length); + ZeroOut(yAccum); + } + } + + v <<= 1; + } + + while (bits < 32) + { + if (useMonty) + { + MultiplyMonty(yAccum, yVal, yVal, m.m_magnitude, mQ); + } + else + { + Square(yAccum, yVal); + Remainder(yAccum, m.m_magnitude); + Array.Copy(yAccum, yAccum.Length - yVal.Length, yVal, 0, yVal.Length); + ZeroOut(yAccum); + } + bits++; + } + } + + if (useMonty) + { + // Return y * R^(-1) mod m by doing y * 1 * R^(-1) mod m + ZeroOut(zVal); + zVal[zVal.Length - 1] = 1; + MultiplyMonty(yAccum, yVal, zVal, m.m_magnitude, mQ); + } + + NetBigInteger result = new NetBigInteger(1, yVal, true); + + return exponent.m_sign > 0 + ? result + : result.ModInverse(m); + } + + // return w with w = x * x - w is assumed to have enough space. + private static int[] Square( + int[] w, + int[] x) + { + // Note: this method allows w to be only (2 * x.Length - 1) words if result will fit + // if (w.Length != 2 * x.Length) + // throw new ArgumentException("no I don't think so..."); + + ulong u1, u2, c; + + int wBase = w.Length - 1; + + for (int i = x.Length - 1; i != 0; i--) + { + ulong v = (ulong)(uint)x[i]; + + u1 = v * v; + u2 = u1 >> 32; + u1 = (uint)u1; + + u1 += (ulong)(uint)w[wBase]; + + w[wBase] = (int)(uint)u1; + c = u2 + (u1 >> 32); + + for (int j = i - 1; j >= 0; j--) + { + --wBase; + u1 = v * (ulong)(uint)x[j]; + u2 = u1 >> 31; // multiply by 2! + u1 = (uint)(u1 << 1); // multiply by 2! + u1 += c + (ulong)(uint)w[wBase]; + + w[wBase] = (int)(uint)u1; + c = u2 + (u1 >> 32); + } + + c += (ulong)(uint)w[--wBase]; + w[wBase] = (int)(uint)c; + + if (--wBase >= 0) + { + w[wBase] = (int)(uint)(c >> 32); + } + else + { + Debug.Assert((uint)(c >> 32) == 0); + } + wBase += i; + } + + u1 = (ulong)(uint)x[0]; + u1 = u1 * u1; + u2 = u1 >> 32; + u1 = u1 & IMASK; + + u1 += (ulong)(uint)w[wBase]; + + w[wBase] = (int)(uint)u1; + if (--wBase >= 0) + { + w[wBase] = (int)(uint)(u2 + (u1 >> 32) + (ulong)(uint)w[wBase]); + } + else + { + Debug.Assert((uint)(u2 + (u1 >> 32)) == 0); + } + + return w; + } + + // return x with x = y * z - x is assumed to have enough space. + private static int[] Multiply( + int[] x, + int[] y, + int[] z) + { + int i = z.Length; + + if (i < 1) + return x; + + int xBase = x.Length - y.Length; + + for (; ; ) + { + long a = z[--i] & IMASK; + long val = 0; + + for (int j = y.Length - 1; j >= 0; j--) + { + val += a * (y[j] & IMASK) + (x[xBase + j] & IMASK); + + x[xBase + j] = (int)val; + + val = (long)((ulong)val >> 32); + } + + --xBase; + + if (i < 1) + { + if (xBase >= 0) + { + x[xBase] = (int)val; + } + else + { + Debug.Assert(val == 0); + } + break; + } + + x[xBase] = (int)val; + } + + return x; + } + + private static long FastExtEuclid( + long a, + long b, + long[] uOut) + { + long u1 = 1; + long u3 = a; + long v1 = 0; + long v3 = b; + + while (v3 > 0) + { + long q, tn; + + q = u3 / v3; + + tn = u1 - (v1 * q); + u1 = v1; + v1 = tn; + + tn = u3 - (v3 * q); + u3 = v3; + v3 = tn; + } + + uOut[0] = u1; + uOut[1] = (u3 - (u1 * a)) / b; + + return u3; + } + + private static long FastModInverse( + long v, + long m) + { + if (m < 1) + throw new ArithmeticException("Modulus must be positive"); + + long[] x = new long[2]; + long gcd = FastExtEuclid(v, m, x); + + if (gcd != 1) + throw new ArithmeticException("Numbers not relatively prime."); + + if (x[0] < 0) + { + x[0] += m; + } + + return x[0]; + } + + private long GetMQuote() + { + Debug.Assert(m_sign > 0); + + if (m_quote != -1) + { + return m_quote; // already calculated + } + + if (m_magnitude.Length == 0 || (m_magnitude[m_magnitude.Length - 1] & 1) == 0) + { + return -1; // not for even numbers + } + + long v = (((~m_magnitude[m_magnitude.Length - 1]) | 1) & 0xffffffffL); + m_quote = FastModInverse(v, 0x100000000L); + + return m_quote; + } + + private static void MultiplyMonty( + int[] a, + int[] x, + int[] y, + int[] m, + long mQuote) + // mQuote = -m^(-1) mod b + { + if (m.Length == 1) + { + x[0] = (int)MultiplyMontyNIsOne((uint)x[0], (uint)y[0], (uint)m[0], (ulong)mQuote); + return; + } + + int n = m.Length; + int nMinus1 = n - 1; + long y_0 = y[nMinus1] & IMASK; + + // 1. a = 0 (Notation: a = (a_{n} a_{n-1} ... a_{0})_{b} ) + Array.Clear(a, 0, n + 1); + + // 2. for i from 0 to (n - 1) do the following: + for (int i = n; i > 0; i--) + { + long x_i = x[i - 1] & IMASK; + + // 2.1 u = ((a[0] + (x[i] * y[0]) * mQuote) mod b + long u = ((((a[n] & IMASK) + ((x_i * y_0) & IMASK)) & IMASK) * mQuote) & IMASK; + + // 2.2 a = (a + x_i * y + u * m) / b + long prod1 = x_i * y_0; + long prod2 = u * (m[nMinus1] & IMASK); + long tmp = (a[n] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK); + long carry = (long)((ulong)prod1 >> 32) + (long)((ulong)prod2 >> 32) + (long)((ulong)tmp >> 32); + for (int j = nMinus1; j > 0; j--) + { + prod1 = x_i * (y[j - 1] & IMASK); + prod2 = u * (m[j - 1] & IMASK); + tmp = (a[j] & IMASK) + (prod1 & IMASK) + (prod2 & IMASK) + (carry & IMASK); + carry = (long)((ulong)carry >> 32) + (long)((ulong)prod1 >> 32) + + (long)((ulong)prod2 >> 32) + (long)((ulong)tmp >> 32); + a[j + 1] = (int)tmp; // division by b + } + carry += (a[0] & IMASK); + a[1] = (int)carry; + a[0] = (int)((ulong)carry >> 32); // OJO!!!!! + } + + // 3. if x >= m the x = x - m + if (CompareTo(0, a, 0, m) >= 0) + { + Subtract(0, a, 0, m); + } + + // put the result in x + Array.Copy(a, 1, x, 0, n); + } + + private static uint MultiplyMontyNIsOne( + uint x, + uint y, + uint m, + ulong mQuote) + { + ulong um = m; + ulong prod1 = (ulong)x * (ulong)y; + ulong u = (prod1 * mQuote) & UIMASK; + ulong prod2 = u * um; + ulong tmp = (prod1 & UIMASK) + (prod2 & UIMASK); + ulong carry = (prod1 >> 32) + (prod2 >> 32) + (tmp >> 32); + + if (carry > um) + { + carry -= um; + } + + return (uint)(carry & UIMASK); + } + + public NetBigInteger Modulus( + NetBigInteger val) + { + return Mod(val); + } + + public NetBigInteger Multiply( + NetBigInteger val) + { + if (m_sign == 0 || val.m_sign == 0) + return Zero; + + if (val.QuickPow2Check()) // val is power of two + { + NetBigInteger result = ShiftLeft(val.Abs().BitLength - 1); + return val.m_sign > 0 ? result : result.Negate(); + } + + if (QuickPow2Check()) // this is power of two + { + NetBigInteger result = val.ShiftLeft(Abs().BitLength - 1); + return m_sign > 0 ? result : result.Negate(); + } + + int maxBitLength = BitLength + val.BitLength; + int resLength = (maxBitLength + BitsPerInt - 1) / BitsPerInt; + + int[] res = new int[resLength]; + + if (val == this) + { + Square(res, m_magnitude); + } + else + { + Multiply(res, m_magnitude, val.m_magnitude); + } + + return new NetBigInteger(m_sign * val.m_sign, res, true); + } + + public NetBigInteger Negate() + { + if (m_sign == 0) + return this; + + return new NetBigInteger(-m_sign, m_magnitude, false); + } + + public NetBigInteger Not() + { + return Inc().Negate(); + } + + public NetBigInteger Pow(int exp) + { + if (exp < 0) + { + throw new ArithmeticException("Negative exponent"); + } + + if (exp == 0) + { + return One; + } + + if (m_sign == 0 || Equals(One)) + { + return this; + } + + NetBigInteger y = One; + NetBigInteger z = this; + + for (; ; ) + { + if ((exp & 0x1) == 1) + { + y = y.Multiply(z); + } + exp >>= 1; + if (exp == 0) break; + z = z.Multiply(z); + } + + return y; + } + + private int Remainder( + int m) + { + Debug.Assert(m > 0); + + long acc = 0; + for (int pos = 0; pos < m_magnitude.Length; ++pos) + { + long posVal = (uint)m_magnitude[pos]; + acc = (acc << 32 | posVal) % m; + } + + return (int)acc; + } + + // return x = x % y - done in place (y value preserved) + private int[] Remainder( + int[] x, + int[] y) + { + int xStart = 0; + while (xStart < x.Length && x[xStart] == 0) + { + ++xStart; + } + + int yStart = 0; + while (yStart < y.Length && y[yStart] == 0) + { + ++yStart; + } + + Debug.Assert(yStart < y.Length); + + int xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp > 0) + { + int yBitLength = calcBitLength(yStart, y); + int xBitLength = calcBitLength(xStart, x); + int shift = xBitLength - yBitLength; + + int[] c; + int cStart = 0; + int cBitLength = yBitLength; + if (shift > 0) + { + c = ShiftLeft(y, shift); + cBitLength += shift; + Debug.Assert(c[0] != 0); + } + else + { + int len = y.Length - yStart; + c = new int[len]; + Array.Copy(y, yStart, c, 0, len); + } + + for (; ; ) + { + if (cBitLength < xBitLength + || CompareNoLeadingZeroes(xStart, x, cStart, c) >= 0) + { + Subtract(xStart, x, cStart, c); + + while (x[xStart] == 0) + { + if (++xStart == x.Length) + return x; + } + + //xBitLength = calcBitLength(xStart, x); + xBitLength = 32 * (x.Length - xStart - 1) + BitLen(x[xStart]); + + if (xBitLength <= yBitLength) + { + if (xBitLength < yBitLength) + return x; + + xyCmp = CompareNoLeadingZeroes(xStart, x, yStart, y); + + if (xyCmp <= 0) + break; + } + } + + shift = cBitLength - xBitLength; + + // NB: The case where c[cStart] is 1-bit is harmless + if (shift == 1) + { + uint firstC = (uint)c[cStart] >> 1; + uint firstX = (uint)x[xStart]; + if (firstC > firstX) + ++shift; + } + + if (shift < 2) + { + c = ShiftRightOneInPlace(cStart, c); + --cBitLength; + } + else + { + c = ShiftRightInPlace(cStart, c, shift); + cBitLength -= shift; + } + + //cStart = c.Length - ((cBitLength + 31) / 32); + while (c[cStart] == 0) + { + ++cStart; + } + } + } + + if (xyCmp == 0) + { + Array.Clear(x, xStart, x.Length - xStart); + } + + return x; + } + + public NetBigInteger Remainder( + NetBigInteger n) + { + if (n.m_sign == 0) + throw new ArithmeticException("Division by zero error"); + + if (m_sign == 0) + return Zero; + + // For small values, use fast remainder method + if (n.m_magnitude.Length == 1) + { + int val = n.m_magnitude[0]; + + if (val > 0) + { + if (val == 1) + return Zero; + + int rem = Remainder(val); + + return rem == 0 + ? Zero + : new NetBigInteger(m_sign, new int[] { rem }, false); + } + } + + if (CompareNoLeadingZeroes(0, m_magnitude, 0, n.m_magnitude) < 0) + return this; + + int[] result; + if (n.QuickPow2Check()) // n is power of two + { + result = LastNBits(n.Abs().BitLength - 1); + } + else + { + result = (int[])m_magnitude.Clone(); + result = Remainder(result, n.m_magnitude); + } + + return new NetBigInteger(m_sign, result, true); + } + + private int[] LastNBits( + int n) + { + if (n < 1) + return ZeroMagnitude; + + int numWords = (n + BitsPerInt - 1) / BitsPerInt; + numWords = System.Math.Min(numWords, m_magnitude.Length); + int[] result = new int[numWords]; + + Array.Copy(m_magnitude, m_magnitude.Length - numWords, result, 0, numWords); + + int hiBits = n % 32; + if (hiBits != 0) + { + result[0] &= ~(-1 << hiBits); + } + + return result; + } + + + // do a left shift - this returns a new array. + private static int[] ShiftLeft( + int[] mag, + int n) + { + int nInts = (int)((uint)n >> 5); + int nBits = n & 0x1f; + int magLen = mag.Length; + int[] newMag; + + if (nBits == 0) + { + newMag = new int[magLen + nInts]; + mag.CopyTo(newMag, 0); + } + else + { + int i = 0; + int nBits2 = 32 - nBits; + int highBits = (int)((uint)mag[0] >> nBits2); + + if (highBits != 0) + { + newMag = new int[magLen + nInts + 1]; + newMag[i++] = highBits; + } + else + { + newMag = new int[magLen + nInts]; + } + + int m = mag[0]; + for (int j = 0; j < magLen - 1; j++) + { + int next = mag[j + 1]; + + newMag[i++] = (m << nBits) | (int)((uint)next >> nBits2); + m = next; + } + + newMag[i] = mag[magLen - 1] << nBits; + } + + return newMag; + } + + public NetBigInteger ShiftLeft( + int n) + { + if (m_sign == 0 || m_magnitude.Length == 0) + return Zero; + + if (n == 0) + return this; + + if (n < 0) + return ShiftRight(-n); + + NetBigInteger result = new NetBigInteger(m_sign, ShiftLeft(m_magnitude, n), true); + + if (m_numBits != -1) + { + result.m_numBits = m_sign > 0 + ? m_numBits + : m_numBits + n; + } + + if (m_numBitLength != -1) + { + result.m_numBitLength = m_numBitLength + n; + } + + return result; + } + + // do a right shift - this does it in place. + private static int[] ShiftRightInPlace( + int start, + int[] mag, + int n) + { + int nInts = (int)((uint)n >> 5) + start; + int nBits = n & 0x1f; + int magEnd = mag.Length - 1; + + if (nInts != start) + { + int delta = (nInts - start); + + for (int i = magEnd; i >= nInts; i--) + { + mag[i] = mag[i - delta]; + } + for (int i = nInts - 1; i >= start; i--) + { + mag[i] = 0; + } + } + + if (nBits != 0) + { + int nBits2 = 32 - nBits; + int m = mag[magEnd]; + + for (int i = magEnd; i > nInts; --i) + { + int next = mag[i - 1]; + + mag[i] = (int)((uint)m >> nBits) | (next << nBits2); + m = next; + } + + mag[nInts] = (int)((uint)mag[nInts] >> nBits); + } + + return mag; + } + + // do a right shift by one - this does it in place. + private static int[] ShiftRightOneInPlace( + int start, + int[] mag) + { + int i = mag.Length; + int m = mag[i - 1]; + + while (--i > start) + { + int next = mag[i - 1]; + mag[i] = ((int)((uint)m >> 1)) | (next << 31); + m = next; + } + + mag[start] = (int)((uint)mag[start] >> 1); + + return mag; + } + + public NetBigInteger ShiftRight( + int n) + { + if (n == 0) + return this; + + if (n < 0) + return ShiftLeft(-n); + + if (n >= BitLength) + return (m_sign < 0 ? One.Negate() : Zero); + + // int[] res = (int[]) magnitude.Clone(); + // + // res = ShiftRightInPlace(0, res, n); + // + // return new BigInteger(sign, res, true); + + int resultLength = (BitLength - n + 31) >> 5; + int[] res = new int[resultLength]; + + int numInts = n >> 5; + int numBits = n & 31; + + if (numBits == 0) + { + Array.Copy(m_magnitude, 0, res, 0, res.Length); + } + else + { + int numBits2 = 32 - numBits; + + int magPos = m_magnitude.Length - 1 - numInts; + for (int i = resultLength - 1; i >= 0; --i) + { + res[i] = (int)((uint)m_magnitude[magPos--] >> numBits); + + if (magPos >= 0) + { + res[i] |= m_magnitude[magPos] << numBits2; + } + } + } + + Debug.Assert(res[0] != 0); + + return new NetBigInteger(m_sign, res, false); + } + + public int SignValue + { + get { return m_sign; } + } + + // returns x = x - y - we assume x is >= y + private static int[] Subtract( + int xStart, + int[] x, + int yStart, + int[] y) + { + Debug.Assert(yStart < y.Length); + Debug.Assert(x.Length - xStart >= y.Length - yStart); + + int iT = x.Length; + int iV = y.Length; + long m; + int borrow = 0; + + do + { + m = (x[--iT] & IMASK) - (y[--iV] & IMASK) + borrow; + x[iT] = (int)m; + + // borrow = (m < 0) ? -1 : 0; + borrow = (int)(m >> 63); + } + while (iV > yStart); + + if (borrow != 0) + { + while (--x[--iT] == -1) + { + } + } + + return x; + } + + public NetBigInteger Subtract( + NetBigInteger n) + { + if (n.m_sign == 0) + return this; + + if (m_sign == 0) + return n.Negate(); + + if (m_sign != n.m_sign) + return Add(n.Negate()); + + int compare = CompareNoLeadingZeroes(0, m_magnitude, 0, n.m_magnitude); + if (compare == 0) + return Zero; + + NetBigInteger bigun, lilun; + if (compare < 0) + { + bigun = n; + lilun = this; + } + else + { + bigun = this; + lilun = n; + } + + return new NetBigInteger(m_sign * compare, doSubBigLil(bigun.m_magnitude, lilun.m_magnitude), true); + } + + private static int[] doSubBigLil( + int[] bigMag, + int[] lilMag) + { + int[] res = (int[])bigMag.Clone(); + + return Subtract(0, res, 0, lilMag); + } + + public byte[] ToByteArray() + { + return ToByteArray(false); + } + + public byte[] ToByteArrayUnsigned() + { + return ToByteArray(true); + } + + private byte[] ToByteArray( + bool unsigned) + { + if (m_sign == 0) + return unsigned ? ZeroEncoding : new byte[1]; + + int nBits = (unsigned && m_sign > 0) + ? BitLength + : BitLength + 1; + + int nBytes = GetByteLength(nBits); + byte[] bytes = new byte[nBytes]; + + int magIndex = m_magnitude.Length; + int bytesIndex = bytes.Length; + + if (m_sign > 0) + { + while (magIndex > 1) + { + uint mag = (uint)m_magnitude[--magIndex]; + bytes[--bytesIndex] = (byte)mag; + bytes[--bytesIndex] = (byte)(mag >> 8); + bytes[--bytesIndex] = (byte)(mag >> 16); + bytes[--bytesIndex] = (byte)(mag >> 24); + } + + uint lastMag = (uint)m_magnitude[0]; + while (lastMag > byte.MaxValue) + { + bytes[--bytesIndex] = (byte)lastMag; + lastMag >>= 8; + } + + bytes[--bytesIndex] = (byte)lastMag; + } + else // sign < 0 + { + bool carry = true; + + while (magIndex > 1) + { + uint mag = ~((uint)m_magnitude[--magIndex]); + + if (carry) + { + carry = (++mag == uint.MinValue); + } + + bytes[--bytesIndex] = (byte)mag; + bytes[--bytesIndex] = (byte)(mag >> 8); + bytes[--bytesIndex] = (byte)(mag >> 16); + bytes[--bytesIndex] = (byte)(mag >> 24); + } + + uint lastMag = (uint)m_magnitude[0]; + + if (carry) + { + // Never wraps because magnitude[0] != 0 + --lastMag; + } + + while (lastMag > byte.MaxValue) + { + bytes[--bytesIndex] = (byte)~lastMag; + lastMag >>= 8; + } + + bytes[--bytesIndex] = (byte)~lastMag; + + if (bytesIndex > 0) + { + bytes[--bytesIndex] = byte.MaxValue; + } + } + + return bytes; + } + + public override string ToString() + { + return ToString(10); + } + + public string ToString( + int radix) + { + switch (radix) + { + case 2: + case 10: + case 16: + break; + default: + throw new FormatException("Only bases 2, 10, 16 are allowed"); + } + + // NB: Can only happen to internally managed instances + if (m_magnitude == null) + return "null"; + + if (m_sign == 0) + return "0"; + + Debug.Assert(m_magnitude.Length > 0); + + StringBuilder sb = new StringBuilder(); + + if (radix == 16) + { + sb.Append(m_magnitude[0].ToString("x")); + + for (int i = 1; i < m_magnitude.Length; i++) + { + sb.Append(m_magnitude[i].ToString("x8")); + } + } + else if (radix == 2) + { + sb.Append('1'); + + for (int i = BitLength - 2; i >= 0; --i) + { + sb.Append(TestBit(i) ? '1' : '0'); + } + } + else + { + // This is algorithm 1a from chapter 4.4 in Seminumerical Algorithms, slow but it works + Stack S = new Stack(); + NetBigInteger bs = ValueOf(radix); + + NetBigInteger u = Abs(); + NetBigInteger b; + + while (u.m_sign != 0) + { + b = u.Mod(bs); + if (b.m_sign == 0) + { + S.Push("0"); + } + else + { + // see how to interact with different bases + S.Push(b.m_magnitude[0].ToString("d")); + } + u = u.Divide(bs); + } + + // Then pop the stack + while (S.Count != 0) + { + sb.Append((string)S.Pop()); + } + } + + string s = sb.ToString(); + + Debug.Assert(s.Length > 0); + + // Strip leading zeros. (We know this number is not all zeroes though) + if (s[0] == '0') + { + int nonZeroPos = 0; + while (s[++nonZeroPos] == '0') { } + + s = s.Substring(nonZeroPos); + } + + if (m_sign == -1) + { + s = "-" + s; + } + + return s; + } + + private static NetBigInteger createUValueOf( + ulong value) + { + int msw = (int)(value >> 32); + int lsw = (int)value; + + if (msw != 0) + return new NetBigInteger(1, new int[] { msw, lsw }, false); + + if (lsw != 0) + { + NetBigInteger n = new NetBigInteger(1, new int[] { lsw }, false); + // Check for a power of two + if ((lsw & -lsw) == lsw) + { + n.m_numBits = 1; + } + return n; + } + + return Zero; + } + + private static NetBigInteger createValueOf( + long value) + { + if (value < 0) + { + if (value == long.MinValue) + return createValueOf(~value).Not(); + + return createValueOf(-value).Negate(); + } + + return createUValueOf((ulong)value); + } + + public static NetBigInteger ValueOf( + long value) + { + switch (value) + { + case 0: + return Zero; + case 1: + return One; + case 2: + return Two; + case 3: + return Three; + case 10: + return Ten; + } + + return createValueOf(value); + } + + public int GetLowestSetBit() + { + if (m_sign == 0) + return -1; + + int w = m_magnitude.Length; + + while (--w > 0) + { + if (m_magnitude[w] != 0) + break; + } + + int word = (int)m_magnitude[w]; + Debug.Assert(word != 0); + + int b = (word & 0x0000FFFF) == 0 + ? (word & 0x00FF0000) == 0 + ? 7 + : 15 + : (word & 0x000000FF) == 0 + ? 23 + : 31; + + while (b > 0) + { + if ((word << b) == int.MinValue) + break; + + b--; + } + + return ((m_magnitude.Length - w) * 32 - (b + 1)); + } + + public bool TestBit( + int n) + { + if (n < 0) + throw new ArithmeticException("Bit position must not be negative"); + + if (m_sign < 0) + return !Not().TestBit(n); + + int wordNum = n / 32; + if (wordNum >= m_magnitude.Length) + return false; + + int word = m_magnitude[m_magnitude.Length - 1 - wordNum]; + return ((word >> (n % 32)) & 1) > 0; + } + } + +#if WINDOWS_RUNTIME + internal sealed class Stack + { + private System.Collections.Generic.List m_list = new System.Collections.Generic.List(); + public int Count { get { return m_list.Count; } } + public void Push(object item) { m_list.Add(item); } + public object Pop() + { + var item = m_list[m_list.Count - 1]; + m_list.RemoveAt(m_list.Count - 1); + return item; + } + } +#endif +} \ No newline at end of file diff --git a/SRMP/Lidgren.Network/NetBitVector.cs b/SRMP/Lidgren.Network/NetBitVector.cs new file mode 100644 index 0000000..af4b4d7 --- /dev/null +++ b/SRMP/Lidgren.Network/NetBitVector.cs @@ -0,0 +1,172 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Fixed size vector of booleans + /// + public sealed class NetBitVector + { + private readonly int m_capacity; + private readonly int[] m_data; + private int m_numBitsSet; + + /// + /// Gets the number of bits/booleans stored in this vector + /// + public int Capacity { get { return m_capacity; } } + + /// + /// NetBitVector constructor + /// + public NetBitVector(int bitsCapacity) + { + m_capacity = bitsCapacity; + m_data = new int[(bitsCapacity + 31) / 32]; + } + + /// + /// Returns true if all bits/booleans are set to zero/false + /// + public bool IsEmpty() + { + return (m_numBitsSet == 0); + } + + /// + /// Returns the number of bits/booleans set to one/true + /// + /// + public int Count() + { + return m_numBitsSet; + } + + /// + /// Shift all bits one step down, cycling the first bit to the top + /// + public void RotateDown() + { + int lenMinusOne = m_data.Length - 1; + + int firstBit = m_data[0] & 1; + for (int i = 0; i < lenMinusOne; i++) + m_data[i] = ((m_data[i] >> 1) & ~(1 << 31)) | m_data[i + 1] << 31; + + int lastIndex = m_capacity - 1 - (32 * lenMinusOne); + + // special handling of last int + int cur = m_data[lenMinusOne]; + cur = cur >> 1; + cur |= firstBit << lastIndex; + + m_data[lenMinusOne] = cur; + } + + /// + /// Gets the first (lowest) index set to true + /// + public int GetFirstSetIndex() + { + int idx = 0; + + int data = m_data[0]; + while (data == 0) + { + idx++; + data = m_data[idx]; + } + + int a = 0; + while (((data >> a) & 1) == 0) + a++; + + return (idx * 32) + a; + } + + /// + /// Gets the bit/bool at the specified index + /// + public bool Get(int bitIndex) + { + NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity); + + return (m_data[bitIndex / 32] & (1 << (bitIndex % 32))) != 0; + } + + /// + /// Sets or clears the bit/bool at the specified index + /// + public void Set(int bitIndex, bool value) + { + NetException.Assert(bitIndex >= 0 && bitIndex < m_capacity); + + int idx = bitIndex / 32; + if (value) + { + if ((m_data[idx] & (1 << (bitIndex % 32))) == 0) + m_numBitsSet++; + m_data[idx] |= (1 << (bitIndex % 32)); + } + else + { + if ((m_data[idx] & (1 << (bitIndex % 32))) != 0) + m_numBitsSet--; + m_data[idx] &= (~(1 << (bitIndex % 32))); + } + } + + /// + /// Gets the bit/bool at the specified index + /// + [System.Runtime.CompilerServices.IndexerName("Bit")] + public bool this[int index] + { + get { return Get(index); } + set { Set(index, value); } + } + + /// + /// Sets all bits/booleans to zero/false + /// + public void Clear() + { + Array.Clear(m_data, 0, m_data.Length); + m_numBitsSet = 0; + NetException.Assert(this.IsEmpty()); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + StringBuilder bdr = new StringBuilder(m_capacity + 2); + bdr.Append('['); + for (int i = 0; i < m_capacity; i++) + bdr.Append(Get(m_capacity - i - 1) ? '1' : '0'); + bdr.Append(']'); + return bdr.ToString(); + } + } +} diff --git a/SRMP/Lidgren.Network/NetBitWriter.cs b/SRMP/Lidgren.Network/NetBitWriter.cs new file mode 100644 index 0000000..c641c49 --- /dev/null +++ b/SRMP/Lidgren.Network/NetBitWriter.cs @@ -0,0 +1,513 @@ +//#define UNSAFE +//#define BIGENDIAN +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; + +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Helper class for NetBuffer to write/read bits + /// + public static class NetBitWriter + { + /// + /// Read 1-8 bits from a buffer into a byte + /// + public static byte ReadByte(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits < 9)), "Read() can only read between 1 and 8 bits"); + + int bytePtr = readBitOffset >> 3; + int startReadAtIndex = readBitOffset - (bytePtr * 8); // (readBitOffset % 8); + + if (startReadAtIndex == 0 && numberOfBits == 8) + return fromBuffer[bytePtr]; + + // mask away unused bits lower than (right of) relevant bits in first byte + byte returnValue = (byte)(fromBuffer[bytePtr] >> startReadAtIndex); + + int numberOfBitsInSecondByte = numberOfBits - (8 - startReadAtIndex); + + if (numberOfBitsInSecondByte < 1) + { + // we don't need to read from the second byte, but we DO need + // to mask away unused bits higher than (left of) relevant bits + return (byte)(returnValue & (255 >> (8 - numberOfBits))); + } + + byte second = fromBuffer[bytePtr + 1]; + + // mask away unused bits higher than (left of) relevant bits in second byte + second &= (byte)(255 >> (8 - numberOfBitsInSecondByte)); + + return (byte)(returnValue | (byte)(second << (numberOfBits - numberOfBitsInSecondByte))); + } + + /// + /// Read several bytes from a buffer + /// + public static void ReadBytes(byte[] fromBuffer, int numberOfBytes, int readBitOffset, byte[] destination, int destinationByteOffset) + { + int readPtr = readBitOffset >> 3; + int startReadAtIndex = readBitOffset - (readPtr * 8); // (readBitOffset % 8); + + if (startReadAtIndex == 0) + { + Buffer.BlockCopy(fromBuffer, readPtr, destination, destinationByteOffset, numberOfBytes); + return; + } + + int secondPartLen = 8 - startReadAtIndex; + int secondMask = 255 >> secondPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + // mask away unused bits lower than (right of) relevant bits in byte + int b = fromBuffer[readPtr] >> startReadAtIndex; + + readPtr++; + + // mask away unused bits higher than (left of) relevant bits in second byte + int second = fromBuffer[readPtr] & secondMask; + + destination[destinationByteOffset++] = (byte)(b | (second << secondPartLen)); + } + + return; + } + + /// + /// Write 0-8 bits of data to buffer + /// + public static void WriteByte(byte source, int numberOfBits, byte[] destination, int destBitOffset) + { + if (numberOfBits == 0) + return; + + NetException.Assert(((numberOfBits >= 0) && (numberOfBits <= 8)), "Must write between 0 and 8 bits!"); + + // Mask out all the bits we dont want + source = (byte)(source & (0xFF >> (8 - numberOfBits))); + + int p = destBitOffset >> 3; + int bitsUsed = destBitOffset & 0x7; // mod 8 + int bitsFree = 8 - bitsUsed; + int bitsLeft = bitsFree - numberOfBits; + + // Fast path, everything fits in the first byte + if (bitsLeft >= 0) + { + int mask = (0xFF >> bitsFree) | (0xFF << (8 - bitsLeft)); + + destination[p] = (byte)( + // Mask out lower and upper bits + (destination[p] & mask) | + + // Insert new bits + (source << bitsUsed) + ); + + return; + } + + destination[p] = (byte)( + // Mask out upper bits + (destination[p] & (0xFF >> bitsFree)) | + + // Write the lower bits to the upper bits in the first byte + (source << bitsUsed) + ); + + p += 1; + + destination[p] = (byte)( + // Mask out lower bits + (destination[p] & (0xFF << (numberOfBits - bitsFree))) | + + // Write the upper bits to the lower bits of the second byte + (source >> bitsFree) + ); + } + + /// + /// Write several whole bytes + /// + public static void WriteBytes(byte[] source, int sourceByteOffset, int numberOfBytes, byte[] destination, int destBitOffset) + { + int dstBytePtr = destBitOffset >> 3; + int firstPartLen = (destBitOffset % 8); + + if (firstPartLen == 0) + { + Buffer.BlockCopy(source, sourceByteOffset, destination, dstBytePtr, numberOfBytes); + return; + } + + int lastPartLen = 8 - firstPartLen; + + for (int i = 0; i < numberOfBytes; i++) + { + byte src = source[sourceByteOffset + i]; + + // write last part of this byte + destination[dstBytePtr] &= (byte)(255 >> lastPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src << firstPartLen); // write first half + + dstBytePtr++; + + // write first part of next byte + destination[dstBytePtr] &= (byte)(255 << firstPartLen); // clear before writing + destination[dstBytePtr] |= (byte)(src >> lastPartLen); // write second half + } + + return; + } + + /// + /// Reads an unsigned 16 bit integer + /// + [CLSCompliant(false)] +#if UNSAFE + public static unsafe ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits"); + + if (numberOfBits == 16 && ((readBitOffset % 8) == 0)) + { + fixed (byte* ptr = &(fromBuffer[readBitOffset / 8])) + { + return *(((ushort*)ptr)); + } + } +#else + public static ushort ReadUInt16(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + Debug.Assert(((numberOfBits > 0) && (numberOfBits <= 16)), "ReadUInt16() can only read between 1 and 16 bits"); +#endif + ushort returnValue; + if (numberOfBits <= 8) + { + returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset); + return returnValue; + } + returnValue = ReadByte(fromBuffer, 8, readBitOffset); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + returnValue |= (ushort)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8); + } + +#if BIGENDIAN + // reorder bytes + uint retVal = returnValue; + retVal = ((retVal & 0x0000ff00) >> 8) | ((retVal & 0x000000ff) << 8); + return (ushort)retVal; +#else + return returnValue; +#endif + } + + /// + /// Reads the specified number of bits into an UInt32 + /// + [CLSCompliant(false)] +#if UNSAFE + public static unsafe uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); + + if (numberOfBits == 32 && ((readBitOffset % 8) == 0)) + { + fixed (byte* ptr = &(fromBuffer[readBitOffset / 8])) + { + return *(((uint*)ptr)); + } + } +#else + + public static uint ReadUInt32(byte[] fromBuffer, int numberOfBits, int readBitOffset) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 32)), "ReadUInt32() can only read between 1 and 32 bits"); +#endif + uint returnValue; + if (numberOfBits <= 8) + { + returnValue = ReadByte(fromBuffer, numberOfBits, readBitOffset); + return returnValue; + } + returnValue = ReadByte(fromBuffer, 8, readBitOffset); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 8); + return returnValue; + } + returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 8); + numberOfBits -= 8; + readBitOffset += 8; + + if (numberOfBits <= 8) + { + uint r = ReadByte(fromBuffer, numberOfBits, readBitOffset); + r <<= 16; + returnValue |= r; + return returnValue; + } + returnValue |= (uint)(ReadByte(fromBuffer, 8, readBitOffset) << 16); + numberOfBits -= 8; + readBitOffset += 8; + + returnValue |= (uint)(ReadByte(fromBuffer, numberOfBits, readBitOffset) << 24); + +#if BIGENDIAN + // reorder bytes + return + ((returnValue & 0xff000000) >> 24) | + ((returnValue & 0x00ff0000) >> 8) | + ((returnValue & 0x0000ff00) << 8) | + ((returnValue & 0x000000ff) << 24); +#else + return returnValue; +#endif + } + + //[CLSCompliant(false)] + //public static ulong ReadUInt64(byte[] fromBuffer, int numberOfBits, int readBitOffset) + + /// + /// Writes an unsigned 16 bit integer + /// + [CLSCompliant(false)] + public static void WriteUInt16(ushort source, int numberOfBits, byte[] destination, int destinationBitOffset) + { + if (numberOfBits == 0) + return; + + NetException.Assert((numberOfBits >= 0 && numberOfBits <= 16), "numberOfBits must be between 0 and 16"); +#if BIGENDIAN + // reorder bytes + uint intSource = source; + intSource = ((intSource & 0x0000ff00) >> 8) | ((intSource & 0x000000ff) << 8); + source = (ushort)intSource; +#endif + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return; + } + + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + + numberOfBits -= 8; + if (numberOfBits > 0) + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset + 8); + } + + /// + /// Writes the specified number of bits into a byte array + /// + [CLSCompliant(false)] + public static int WriteUInt32(uint source, int numberOfBits, byte[] destination, int destinationBitOffset) + { +#if BIGENDIAN + // reorder bytes + source = ((source & 0xff000000) >> 24) | + ((source & 0x00ff0000) >> 8) | + ((source & 0x0000ff00) << 8) | + ((source & 0x000000ff) << 24); +#endif + + int returnValue = destinationBitOffset + numberOfBits; + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + + /// + /// Writes the specified number of bits into a byte array + /// + [CLSCompliant(false)] + public static int WriteUInt64(ulong source, int numberOfBits, byte[] destination, int destinationBitOffset) + { +#if BIGENDIAN + source = ((source & 0xff00000000000000L) >> 56) | + ((source & 0x00ff000000000000L) >> 40) | + ((source & 0x0000ff0000000000L) >> 24) | + ((source & 0x000000ff00000000L) >> 8) | + ((source & 0x00000000ff000000L) << 8) | + ((source & 0x0000000000ff0000L) << 24) | + ((source & 0x000000000000ff00L) << 40) | + ((source & 0x00000000000000ffL) << 56); +#endif + + int returnValue = destinationBitOffset + numberOfBits; + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)source, numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)source, 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 8), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 8), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 16), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 16), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 24), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 24), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 32), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 32), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 40), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 40), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 48), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 48), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + if (numberOfBits <= 8) + { + NetBitWriter.WriteByte((byte)(source >> 56), numberOfBits, destination, destinationBitOffset); + return returnValue; + } + NetBitWriter.WriteByte((byte)(source >> 56), 8, destination, destinationBitOffset); + destinationBitOffset += 8; + numberOfBits -= 8; + + return returnValue; + } + + // + // Variable size + // + + /// + /// Write Base128 encoded variable sized unsigned integer + /// + /// number of bytes written + [CLSCompliant(false)] + public static int WriteVariableUInt32(byte[] intoBuffer, int offset, uint value) + { + int retval = 0; + uint num1 = (uint)value; + while (num1 >= 0x80) + { + intoBuffer[offset + retval] = (byte)(num1 | 0x80); + num1 = num1 >> 7; + retval++; + } + intoBuffer[offset + retval] = (byte)num1; + return retval + 1; + } + + /// + /// Reads a UInt32 written using WriteUnsignedVarInt(); will increment offset! + /// + [CLSCompliant(false)] + public static uint ReadVariableUInt32(byte[] buffer, ref int offset) + { + int num1 = 0; + int num2 = 0; + while (true) + { + NetException.Assert(num2 != 0x23, "Bad 7-bit encoded integer"); + + byte num3 = buffer[offset++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + return (uint)num1; + } + } + } +} diff --git a/SRMP/Lidgren.Network/NetBuffer.Peek.cs b/SRMP/Lidgren.Network/NetBuffer.Peek.cs new file mode 100644 index 0000000..818f94f --- /dev/null +++ b/SRMP/Lidgren.Network/NetBuffer.Peek.cs @@ -0,0 +1,312 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; +using System.Net; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Gets the internal data buffer + /// + public byte[] PeekDataBuffer() { return m_data; } + + // + // 1 bit + // + /// + /// Reads a 1-bit Boolean without advancing the read pointer + /// + public bool PeekBoolean() + { + NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition); + return (retval > 0 ? true : false); + } + + // + // 8 bit + // + /// + /// Reads a Byte without advancing the read pointer + /// + public byte PeekByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + return retval; + } + + /// + /// Reads an SByte without advancing the read pointer + /// + [CLSCompliant(false)] + public sbyte PeekSByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + return (sbyte)retval; + } + + /// + /// Reads the specified number of bits into a Byte without advancing the read pointer + /// + public byte PeekByte(int numberOfBits) + { + byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition); + return retval; + } + + /// + /// Reads the specified number of bytes without advancing the read pointer + /// + public byte[] PeekBytes(int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + + byte[] retval = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); + return retval; + } + + /// + /// Reads the specified number of bytes without advancing the read pointer + /// + public void PeekBytes(byte[] into, int offset, int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition >= (numberOfBytes * 8), c_readOverflowError); + NetException.Assert(offset + numberOfBytes <= into.Length); + + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); + return; + } + + // + // 16 bit + // + /// + /// Reads an Int16 without advancing the read pointer + /// + public Int16 PeekInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + return (short)retval; + } + + /// + /// Reads a UInt16 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt16 PeekUInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + return (ushort)retval; + } + + // + // 32 bit + // + /// + /// Reads an Int32 without advancing the read pointer + /// + public Int32 PeekInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + return (Int32)retval; + } + + /// + /// Reads the specified number of bits into an Int32 without advancing the read pointer + /// + public Int32 PeekInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadInt() can only read between 1 and 32 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + + if (numberOfBits == 32) + return (int)retval; + + int signBit = 1 << (numberOfBits - 1); + if ((retval & signBit) == 0) + return (int)retval; // positive + + // negative + unchecked + { + uint mask = ((uint)-1) >> (33 - numberOfBits); + uint tmp = (retval & mask) + 1; + return -((int)tmp); + } + } + + /// + /// Reads a UInt32 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt32 PeekUInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + return retval; + } + + /// + /// Reads the specified number of bits into a UInt32 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt32 PeekUInt32(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "ReadUInt() can only read between 1 and 32 bits"); + //NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size"); + + UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + return retval; + } + + // + // 64 bit + // + /// + /// Reads a UInt64 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt64 PeekUInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition + 32); + + ulong retval = low + (high << 32); + + return retval; + } + + /// + /// Reads an Int64 without advancing the read pointer + /// + public Int64 PeekInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + unchecked + { + ulong retval = PeekUInt64(); + long longRetval = (long)retval; + return longRetval; + } + } + + /// + /// Reads the specified number of bits into an UInt64 without advancing the read pointer + /// + [CLSCompliant(false)] + public UInt64 PeekUInt64(int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 64), "ReadUInt() can only read between 1 and 64 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + ulong retval; + if (numberOfBits <= 32) + { + retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + } + else + { + retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + retval |= (UInt64)NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition + 32) << 32; + } + return retval; + } + + /// + /// Reads the specified number of bits into an Int64 without advancing the read pointer + /// + public Int64 PeekInt64(int numberOfBits) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits < 65)), "ReadInt64(bits) can only read between 1 and 64 bits"); + return (long)PeekUInt64(numberOfBits); + } + + // + // Floating point + // + /// + /// Reads a 32-bit Single without advancing the read pointer + /// + public float PeekFloat() + { + return PeekSingle(); + } + + /// + /// Reads a 32-bit Single without advancing the read pointer + /// + public float PeekSingle() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); + return retval; + } + + byte[] bytes = PeekBytes(4); + return BitConverter.ToSingle(bytes, 0); + } + + /// + /// Reads a 64-bit Double without advancing the read pointer + /// + public double PeekDouble() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // read directly + double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); + return retval; + } + + byte[] bytes = PeekBytes(8); + return BitConverter.ToDouble(bytes, 0); + } + + /// + /// Reads a string without advancing the read pointer + /// + public string PeekString() + { + int wasReadPosition = m_readPosition; + string retval = ReadString(); + m_readPosition = wasReadPosition; + return retval; + } + } +} + diff --git a/SRMP/Lidgren.Network/NetBuffer.Read.Reflection.cs b/SRMP/Lidgren.Network/NetBuffer.Read.Reflection.cs new file mode 100644 index 0000000..92d591d --- /dev/null +++ b/SRMP/Lidgren.Network/NetBuffer.Read.Reflection.cs @@ -0,0 +1,103 @@ + /* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Reflection; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Reads all public and private declared instance fields of the object in alphabetical order using reflection + /// + public void ReadAllFields(object target) + { + ReadAllFields(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Reads all fields with the specified binding of the object in alphabetical order using reflection + /// + public void ReadAllFields(object target, BindingFlags flags) + { + if (target == null) + throw new ArgumentNullException("target"); + + Type tp = target.GetType(); + + FieldInfo[] fields = tp.GetFields(flags); + NetUtility.SortMembersList(fields); + + foreach (FieldInfo fi in fields) + { + object value; + + // find read method + MethodInfo readMethod; + if (s_readMethods.TryGetValue(fi.FieldType, out readMethod)) + { + // read value + value = readMethod.Invoke(this, null); + + // set the value + fi.SetValue(target, value); + } + } + } + + /// + /// Reads all public and private declared instance fields of the object in alphabetical order using reflection + /// + public void ReadAllProperties(object target) + { + ReadAllProperties(target, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Reads all fields with the specified binding of the object in alphabetical order using reflection + /// + public void ReadAllProperties(object target, BindingFlags flags) + { + if (target == null) + throw new ArgumentNullException("target"); + + Type tp = target.GetType(); + + PropertyInfo[] fields = tp.GetProperties(flags); + NetUtility.SortMembersList(fields); + foreach (PropertyInfo fi in fields) + { + object value; + + // find read method + MethodInfo readMethod; + if (s_readMethods.TryGetValue(fi.PropertyType, out readMethod)) + { + // read value + value = readMethod.Invoke(this, null); + + // set the value + var setMethod = fi.GetSetMethod(); + if (setMethod != null) + setMethod.Invoke(target, new object[] { value }); + } + } + } + } +} \ No newline at end of file diff --git a/SRMP/Lidgren.Network/NetBuffer.Read.cs b/SRMP/Lidgren.Network/NetBuffer.Read.cs new file mode 100644 index 0000000..1b9f3e0 --- /dev/null +++ b/SRMP/Lidgren.Network/NetBuffer.Read.cs @@ -0,0 +1,713 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; +using System.Net; +using System.Threading; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + /// + /// Base class for NetIncomingMessage and NetOutgoingMessage + /// + public partial class NetBuffer + { + private const string c_readOverflowError = "Trying to read past the buffer size - likely caused by mismatching Write/Reads, different size or order."; + private const int c_bufferSize = 64; // Min 8 to hold anything but strings. Increase it if readed strings usally don't fit inside the buffer + private static object s_buffer; + + /// + /// Reads a boolean value (stored as a single bit) written using Write(bool) + /// + public bool ReadBoolean() + { + NetException.Assert(m_bitLength - m_readPosition >= 1, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 1, m_readPosition); + m_readPosition += 1; + return (retval > 0 ? true : false); + } + + /// + /// Reads a byte + /// + public byte ReadByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return retval; + } + + /// + /// Reads a byte and returns true or false for success + /// + public bool ReadByte(out byte result) + { + if (m_bitLength - m_readPosition < 8) + { + result = 0; + return false; + } + result = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return true; + } + + /// + /// Reads a signed byte + /// + [CLSCompliant(false)] + public sbyte ReadSByte() + { + NetException.Assert(m_bitLength - m_readPosition >= 8, c_readOverflowError); + byte retval = NetBitWriter.ReadByte(m_data, 8, m_readPosition); + m_readPosition += 8; + return (sbyte)retval; + } + + /// + /// Reads 1 to 8 bits into a byte + /// + public byte ReadByte(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 8, "ReadByte(bits) can only read between 1 and 8 bits"); + byte retval = NetBitWriter.ReadByte(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + return retval; + } + + /// + /// Reads the specified number of bytes + /// + public byte[] ReadBytes(int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError); + + byte[] retval = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, retval, 0); + m_readPosition += (8 * numberOfBytes); + return retval; + } + + /// + /// Reads the specified number of bytes and returns true for success + /// + public bool ReadBytes(int numberOfBytes, out byte[] result) + { + if (m_bitLength - m_readPosition + 7 < (numberOfBytes * 8)) + { + result = null; + return false; + } + + result = new byte[numberOfBytes]; + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, result, 0); + m_readPosition += (8 * numberOfBytes); + return true; + } + + /// + /// Reads the specified number of bytes into a preallocated array + /// + /// The destination array + /// The offset where to start writing in the destination array + /// The number of bytes to read + public void ReadBytes(byte[] into, int offset, int numberOfBytes) + { + NetException.Assert(m_bitLength - m_readPosition + 7 >= (numberOfBytes * 8), c_readOverflowError); + NetException.Assert(offset + numberOfBytes <= into.Length); + + NetBitWriter.ReadBytes(m_data, numberOfBytes, m_readPosition, into, offset); + m_readPosition += (8 * numberOfBytes); + return; + } + + /// + /// Reads the specified number of bits into a preallocated array + /// + /// The destination array + /// The offset where to start writing in the destination array + /// The number of bits to read + public void ReadBits(byte[] into, int offset, int numberOfBits) + { + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + NetException.Assert(offset + NetUtility.BytesToHoldBits(numberOfBits) <= into.Length); + + int numberOfWholeBytes = numberOfBits / 8; + int extraBits = numberOfBits - (numberOfWholeBytes * 8); + + NetBitWriter.ReadBytes(m_data, numberOfWholeBytes, m_readPosition, into, offset); + m_readPosition += (8 * numberOfWholeBytes); + + if (extraBits > 0) + into[offset + numberOfWholeBytes] = ReadByte(extraBits); + + return; + } + + /// + /// Reads a 16 bit signed integer written using Write(Int16) + /// + public Int16 ReadInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + m_readPosition += 16; + return (short)retval; + } + + /// + /// Reads a 16 bit unsigned integer written using Write(UInt16) + /// + [CLSCompliant(false)] + public UInt16 ReadUInt16() + { + NetException.Assert(m_bitLength - m_readPosition >= 16, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt16(m_data, 16, m_readPosition); + m_readPosition += 16; + return (ushort)retval; + } + + /// + /// Reads a 32 bit signed integer written using Write(Int32) + /// + public Int32 ReadInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return (Int32)retval; + } + + /// + /// Reads a 32 bit signed integer written using Write(Int32) + /// + [CLSCompliant(false)] + public bool ReadInt32(out Int32 result) + { + if (m_bitLength - m_readPosition < 32) + { + result = 0; + return false; + } + + result = (Int32)NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return true; + } + + /// + /// Reads a signed integer stored in 1 to 32 bits, written using Write(Int32, Int32) + /// + public Int32 ReadInt32(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadInt32(bits) can only read between 1 and 32 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + uint retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + + if (numberOfBits == 32) + return (int)retval; + + int signBit = 1 << (numberOfBits - 1); + if ((retval & signBit) == 0) + return (int)retval; // positive + + // negative + unchecked + { + uint mask = ((uint)-1) >> (33 - numberOfBits); + uint tmp = (retval & mask) + 1; + return -((int)tmp); + } + } + + /// + /// Reads an 32 bit unsigned integer written using Write(UInt32) + /// + [CLSCompliant(false)] + public UInt32 ReadUInt32() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + uint retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return retval; + } + + /// + /// Reads an 32 bit unsigned integer written using Write(UInt32) and returns true for success + /// + [CLSCompliant(false)] + public bool ReadUInt32(out UInt32 result) + { + if (m_bitLength - m_readPosition < 32) + { + result = 0; + return false; + } + result = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + return true; + } + + /// + /// Reads an unsigned integer stored in 1 to 32 bits, written using Write(UInt32, Int32) + /// + [CLSCompliant(false)] + public UInt32 ReadUInt32(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 32, "ReadUInt32(bits) can only read between 1 and 32 bits"); + //NetException.Assert(m_bitLength - m_readBitPtr >= numberOfBits, "tried to read past buffer size"); + + UInt32 retval = NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + m_readPosition += numberOfBits; + return retval; + } + + /// + /// Reads a 64 bit unsigned integer written using Write(UInt64) + /// + [CLSCompliant(false)] + public UInt64 ReadUInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + ulong low = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + m_readPosition += 32; + ulong high = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + + ulong retval = low + (high << 32); + + m_readPosition += 32; + return retval; + } + + /// + /// Reads a 64 bit signed integer written using Write(Int64) + /// + public Int64 ReadInt64() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + unchecked + { + ulong retval = ReadUInt64(); + long longRetval = (long)retval; + return longRetval; + } + } + + /// + /// Reads an unsigned integer stored in 1 to 64 bits, written using Write(UInt64, Int32) + /// + [CLSCompliant(false)] + public UInt64 ReadUInt64(int numberOfBits) + { + NetException.Assert(numberOfBits > 0 && numberOfBits <= 64, "ReadUInt64(bits) can only read between 1 and 64 bits"); + NetException.Assert(m_bitLength - m_readPosition >= numberOfBits, c_readOverflowError); + + ulong retval; + if (numberOfBits <= 32) + { + retval = (ulong)NetBitWriter.ReadUInt32(m_data, numberOfBits, m_readPosition); + } + else + { + retval = NetBitWriter.ReadUInt32(m_data, 32, m_readPosition); + retval |= (UInt64)NetBitWriter.ReadUInt32(m_data, numberOfBits - 32, m_readPosition + 32) << 32; + } + m_readPosition += numberOfBits; + return retval; + } + + /// + /// Reads a signed integer stored in 1 to 64 bits, written using Write(Int64, Int32) + /// + public Int64 ReadInt64(int numberOfBits) + { + NetException.Assert(((numberOfBits > 0) && (numberOfBits <= 64)), "ReadInt64(bits) can only read between 1 and 64 bits"); + return (long)ReadUInt64(numberOfBits); + } + + /// + /// Reads a 32 bit floating point value written using Write(Single) + /// + public float ReadFloat() + { + return ReadSingle(); + } + + /// + /// Reads a 32 bit floating point value written using Write(Single) + /// + public float ReadSingle() + { + NetException.Assert(m_bitLength - m_readPosition >= 32, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + float retval = BitConverter.ToSingle(m_data, m_readPosition >> 3); + m_readPosition += 32; + return retval; + } + + byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; + ReadBytes(bytes, 0, 4); + float res = BitConverter.ToSingle(bytes, 0); + s_buffer = bytes; + return res; + } + + /// + /// Reads a 32 bit floating point value written using Write(Single) + /// + public bool ReadSingle(out float result) + { + if (m_bitLength - m_readPosition < 32) + { + result = 0.0f; + return false; + } + + if ((m_readPosition & 7) == 0) // read directly + { + result = BitConverter.ToSingle(m_data, m_readPosition >> 3); + m_readPosition += 32; + return true; + } + + byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; + ReadBytes(bytes, 0, 4); + result = BitConverter.ToSingle(bytes, 0); + s_buffer = bytes; + return true; + } + + /// + /// Reads a 64 bit floating point value written using Write(Double) + /// + public double ReadDouble() + { + NetException.Assert(m_bitLength - m_readPosition >= 64, c_readOverflowError); + + if ((m_readPosition & 7) == 0) // read directly + { + // read directly + double retval = BitConverter.ToDouble(m_data, m_readPosition >> 3); + m_readPosition += 64; + return retval; + } + + byte[] bytes = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; + ReadBytes(bytes, 0, 8); + double res = BitConverter.ToDouble(bytes, 0); + s_buffer = bytes; + return res; + } + + // + // Variable bit count + // + + /// + /// Reads a variable sized UInt32 written using WriteVariableUInt32() + /// + [CLSCompliant(false)] + public uint ReadVariableUInt32() + { + int num1 = 0; + int num2 = 0; + while (m_bitLength - m_readPosition >= 8) + { + byte num3 = this.ReadByte(); + num1 |= (num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + return (uint)num1; + } + + // ouch; failed to find enough bytes; malformed variable length number? + return (uint)num1; + } + + /// + /// Reads a variable sized UInt32 written using WriteVariableUInt32() and returns true for success + /// + [CLSCompliant(false)] + public bool ReadVariableUInt32(out uint result) + { + int num1 = 0; + int num2 = 0; + while (m_bitLength - m_readPosition >= 8) + { + byte num3; + if (ReadByte(out num3) == false) + { + result = 0; + return false; + } + num1 |= (num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + { + result = (uint)num1; + return true; + } + } + result = (uint)num1; + return false; + } + + /// + /// Reads a variable sized Int32 written using WriteVariableInt32() + /// + public int ReadVariableInt32() + { + uint n = ReadVariableUInt32(); + return (int)(n >> 1) ^ -(int)(n & 1); // decode zigzag + } + + /// + /// Reads a variable sized Int64 written using WriteVariableInt64() + /// + public Int64 ReadVariableInt64() + { + UInt64 n = ReadVariableUInt64(); + return (Int64)(n >> 1) ^ -(long)(n & 1); // decode zigzag + } + + /// + /// Reads a variable sized UInt32 written using WriteVariableInt64() + /// + [CLSCompliant(false)] + public UInt64 ReadVariableUInt64() + { + UInt64 num1 = 0; + int num2 = 0; + while (m_bitLength - m_readPosition >= 8) + { + //if (num2 == 0x23) + // throw new FormatException("Bad 7-bit encoded integer"); + + byte num3 = this.ReadByte(); + num1 |= ((UInt64)num3 & 0x7f) << num2; + num2 += 7; + if ((num3 & 0x80) == 0) + return num1; + } + + // ouch; failed to find enough bytes; malformed variable length number? + return num1; + } + + /// + /// Reads a 32 bit floating point value written using WriteSignedSingle() + /// + /// The number of bits used when writing the value + /// A floating point value larger or equal to -1 and smaller or equal to 1 + public float ReadSignedSingle(int numberOfBits) + { + uint encodedVal = ReadUInt32(numberOfBits); + int maxVal = (1 << numberOfBits) - 1; + return ((float)(encodedVal + 1) / (float)(maxVal + 1) - 0.5f) * 2.0f; + } + + /// + /// Reads a 32 bit floating point value written using WriteUnitSingle() + /// + /// The number of bits used when writing the value + /// A floating point value larger or equal to 0 and smaller or equal to 1 + public float ReadUnitSingle(int numberOfBits) + { + uint encodedVal = ReadUInt32(numberOfBits); + int maxVal = (1 << numberOfBits) - 1; + return (float)(encodedVal + 1) / (float)(maxVal + 1); + } + + /// + /// Reads a 32 bit floating point value written using WriteRangedSingle() + /// + /// The minimum value used when writing the value + /// The maximum value used when writing the value + /// The number of bits used when writing the value + /// A floating point value larger or equal to MIN and smaller or equal to MAX + public float ReadRangedSingle(float min, float max, int numberOfBits) + { + float range = max - min; + int maxVal = (1 << numberOfBits) - 1; + float encodedVal = (float)ReadUInt32(numberOfBits); + float unit = encodedVal / (float)maxVal; + return min + (unit * range); + } + + /// + /// Reads a 32 bit integer value written using WriteRangedInteger() + /// + /// The minimum value used when writing the value + /// The maximum value used when writing the value + /// A signed integer value larger or equal to MIN and smaller or equal to MAX + public int ReadRangedInteger(int min, int max) + { + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); + + uint rvalue = ReadUInt32(numBits); + return (int)(min + rvalue); + } + + /// + /// Reads a 64 bit integer value written using WriteRangedInteger() (64 version) + /// + /// The minimum value used when writing the value + /// The maximum value used when writing the value + /// A signed integer value larger or equal to MIN and smaller or equal to MAX + public long ReadRangedInteger(long min, long max) + { + ulong range = (ulong)(max - min); + int numBits = NetUtility.BitsToHoldUInt64(range); + + ulong rvalue = ReadUInt64(numBits); + return min + (long)rvalue; + } + + /// + /// Reads a string written using Write(string) + /// + public string ReadString() + { + int byteLen = (int)ReadVariableUInt32(); + + if (byteLen <= 0) + return String.Empty; + + if ((ulong)(m_bitLength - m_readPosition) < ((ulong)byteLen * 8)) + { + // not enough data +#if DEBUG + + throw new NetException(c_readOverflowError); +#else + m_readPosition = m_bitLength; + return null; // unfortunate; but we need to protect against DDOS +#endif + } + + if ((m_readPosition & 7) == 0) + { + // read directly + string retval = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, byteLen); + m_readPosition += (8 * byteLen); + return retval; + } + + if (byteLen <= c_bufferSize) { + byte[] buffer = (byte[]) Interlocked.Exchange(ref s_buffer, null) ?? new byte[c_bufferSize]; + ReadBytes(buffer, 0, byteLen); + string retval = Encoding.UTF8.GetString(buffer, 0, byteLen); + s_buffer = buffer; + return retval; + } else { + byte[] bytes = ReadBytes(byteLen); + return Encoding.UTF8.GetString(bytes, 0, bytes.Length); + } + } + + /// + /// Reads a string written using Write(string) and returns true for success + /// + public bool ReadString(out string result) + { + uint byteLen; + if (ReadVariableUInt32(out byteLen) == false) + { + result = String.Empty; + return false; + } + + if (byteLen <= 0) + { + result = String.Empty; + return true; + } + + if (m_bitLength - m_readPosition < (byteLen * 8)) + { + result = String.Empty; + return false; + } + + if ((m_readPosition & 7) == 0) + { + // read directly + result = System.Text.Encoding.UTF8.GetString(m_data, m_readPosition >> 3, (int)byteLen); + m_readPosition += (8 * (int)byteLen); + return true; + } + + byte[] bytes; + if (ReadBytes((int)byteLen, out bytes) == false) + { + result = String.Empty; + return false; + } + + result = System.Text.Encoding.UTF8.GetString(bytes, 0, bytes.Length); + return true; + } + + /// + /// Reads a value, in local time comparable to NetTime.Now, written using WriteTime() for the connection supplied + /// + public double ReadTime(NetConnection connection, bool highPrecision) + { + double remoteTime = (highPrecision ? ReadDouble() : (double)ReadSingle()); + + if (connection == null) + throw new NetException("Cannot call ReadTime() on message without a connected sender (ie. unconnected messages)"); + + // lets bypass NetConnection.GetLocalTime for speed + return remoteTime - connection.m_remoteTimeOffset; + } + + /// + /// Reads a stored IPv4 endpoint description + /// + public NetEndPoint ReadIPEndPoint() + { + byte len = ReadByte(); + byte[] addressBytes = ReadBytes(len); + int port = (int)ReadUInt16(); + + var address = NetUtility.CreateAddressFromBytes(addressBytes); + return new NetEndPoint(address, port); + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void SkipPadBits() + { + m_readPosition = ((m_readPosition + 7) >> 3) * 8; + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void ReadPadBits() + { + m_readPosition = ((m_readPosition + 7) >> 3) * 8; + } + + /// + /// Pads data with the specified number of bits. + /// + public void SkipPadBits(int numberOfBits) + { + m_readPosition += numberOfBits; + } + } +} diff --git a/SRMP/Lidgren.Network/NetBuffer.Write.Reflection.cs b/SRMP/Lidgren.Network/NetBuffer.Write.Reflection.cs new file mode 100644 index 0000000..bd62cb5 --- /dev/null +++ b/SRMP/Lidgren.Network/NetBuffer.Write.Reflection.cs @@ -0,0 +1,94 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Reflection; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Writes all public and private declared instance fields of the object in alphabetical order using reflection + /// + public void WriteAllFields(object ob) + { + WriteAllFields(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Writes all fields with specified binding in alphabetical order using reflection + /// + public void WriteAllFields(object ob, BindingFlags flags) + { + if (ob == null) + return; + Type tp = ob.GetType(); + + FieldInfo[] fields = tp.GetFields(flags); + NetUtility.SortMembersList(fields); + + foreach (FieldInfo fi in fields) + { + object value = fi.GetValue(ob); + + // find the appropriate Write method + MethodInfo writeMethod; + if (s_writeMethods.TryGetValue(fi.FieldType, out writeMethod)) + writeMethod.Invoke(this, new object[] { value }); + else + throw new NetException("Failed to find write method for type " + fi.FieldType); + } + } + + /// + /// Writes all public and private declared instance properties of the object in alphabetical order using reflection + /// + public void WriteAllProperties(object ob) + { + WriteAllProperties(ob, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public); + } + + /// + /// Writes all properties with specified binding in alphabetical order using reflection + /// + public void WriteAllProperties(object ob, BindingFlags flags) + { + if (ob == null) + return; + Type tp = ob.GetType(); + + PropertyInfo[] fields = tp.GetProperties(flags); + NetUtility.SortMembersList(fields); + + foreach (PropertyInfo fi in fields) + { + MethodInfo getMethod = fi.GetGetMethod(); + if (getMethod != null) + { + object value = getMethod.Invoke(ob, null); + + // find the appropriate Write method + MethodInfo writeMethod; + if (s_writeMethods.TryGetValue(fi.PropertyType, out writeMethod)) + writeMethod.Invoke(this, new object[] { value }); + } + } + } + } +} \ No newline at end of file diff --git a/SRMP/Lidgren.Network/NetBuffer.Write.cs b/SRMP/Lidgren.Network/NetBuffer.Write.cs new file mode 100644 index 0000000..9673594 --- /dev/null +++ b/SRMP/Lidgren.Network/NetBuffer.Write.cs @@ -0,0 +1,703 @@ +//#define UNSAFE +//#define BIGENDIAN +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using System.Text; +using System.Runtime.InteropServices; + +namespace Lidgren.Network +{ + /// + /// Utility struct for writing Singles + /// + [StructLayout(LayoutKind.Explicit)] + public struct SingleUIntUnion + { + /// + /// Value as a 32 bit float + /// + [FieldOffset(0)] + public float SingleValue; + + /// + /// Value as an unsigned 32 bit integer + /// + [FieldOffset(0)] + [CLSCompliant(false)] + public uint UIntValue; + } + + public partial class NetBuffer + { + /// + /// Ensures the buffer can hold this number of bits + /// + public void EnsureBufferSize(int numberOfBits) + { + int byteLen = ((numberOfBits + 7) >> 3); + if (m_data == null) + { + m_data = new byte[byteLen + c_overAllocateAmount]; + return; + } + if (m_data.Length < byteLen) + Array.Resize(ref m_data, byteLen + c_overAllocateAmount); + return; + } + + /// + /// Ensures the buffer can hold this number of bits + /// + internal void InternalEnsureBufferSize(int numberOfBits) + { + int byteLen = ((numberOfBits + 7) >> 3); + if (m_data == null) + { + m_data = new byte[byteLen]; + return; + } + if (m_data.Length < byteLen) + Array.Resize(ref m_data, byteLen); + return; + } + + /// + /// Writes a boolean value using 1 bit + /// + public void Write(bool value) + { + EnsureBufferSize(m_bitLength + 1); + NetBitWriter.WriteByte((value ? (byte)1 : (byte)0), 1, m_data, m_bitLength); + m_bitLength += 1; + } + + /// + /// Write a byte + /// + public void Write(byte source) + { + EnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte(source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + /// + /// Writes a byte at a given offset in the buffer + /// + public void WriteAt(Int32 offset, byte source) { + int newBitLength = Math.Max(m_bitLength, offset + 8); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteByte((byte) source, 8, m_data, offset); + m_bitLength = newBitLength; + } + + /// + /// Writes a signed byte + /// + [CLSCompliant(false)] + public void Write(sbyte source) + { + EnsureBufferSize(m_bitLength + 8); + NetBitWriter.WriteByte((byte)source, 8, m_data, m_bitLength); + m_bitLength += 8; + } + + /// + /// Writes 1 to 8 bits of a byte + /// + public void Write(byte source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 8), "Write(byte, numberOfBits) can only write between 1 and 8 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteByte(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes all bytes in an array + /// + public void Write(byte[] source) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = source.Length * 8; + EnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, 0, source.Length, m_data, m_bitLength); + m_bitLength += bits; + } + + /// + /// Writes the specified number of bytes from an array + /// + public void Write(byte[] source, int offsetInBytes, int numberOfBytes) + { + if (source == null) + throw new ArgumentNullException("source"); + int bits = numberOfBytes * 8; + EnsureBufferSize(m_bitLength + bits); + NetBitWriter.WriteBytes(source, offsetInBytes, numberOfBytes, m_data, m_bitLength); + m_bitLength += bits; + } + + /// + /// Writes an unsigned 16 bit integer + /// + /// + [CLSCompliant(false)] + public void Write(UInt16 source) + { + EnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt16(source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + /// + /// Writes a 16 bit unsigned integer at a given offset in the buffer + /// + [CLSCompliant(false)] + public void WriteAt(Int32 offset, UInt16 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 16); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt16(source, 16, m_data, offset); + m_bitLength = newBitLength; + } + + /// + /// Writes an unsigned integer using 1 to 16 bits + /// + [CLSCompliant(false)] + public void Write(UInt16 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 16), "Write(ushort, numberOfBits) can only write between 1 and 16 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt16(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes a signed 16 bit integer + /// + public void Write(Int16 source) + { + EnsureBufferSize(m_bitLength + 16); + NetBitWriter.WriteUInt16((ushort)source, 16, m_data, m_bitLength); + m_bitLength += 16; + } + + /// + /// Writes a 16 bit signed integer at a given offset in the buffer + /// + public void WriteAt(Int32 offset, Int16 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 16); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt16((ushort)source, 16, m_data, offset); + m_bitLength = newBitLength; + } + +#if UNSAFE + /// + /// Writes a 32 bit signed integer + /// + public unsafe void Write(Int32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((int*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32((UInt32)source, 32, Data, m_bitLength); + } + m_bitLength += 32; + } +#else + /// + /// Writes a 32 bit signed integer + /// + public void Write(Int32 source) + { + EnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + + /// + /// Writes a 32 bit signed integer at a given offset in the buffer + /// + public void WriteAt(Int32 offset, Int32 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 32); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt32((UInt32)source, 32, m_data, offset); + m_bitLength = newBitLength; + } + +#if UNSAFE + /// + /// Writes a 32 bit unsigned integer + /// + public unsafe void Write(UInt32 source) + { + EnsureBufferSize(m_bitLength + 32); + + // can write fast? + if (m_bitLength % 8 == 0) + { + fixed (byte* numRef = &Data[m_bitLength / 8]) + { + *((uint*)numRef) = source; + } + } + else + { + NetBitWriter.WriteUInt32(source, 32, Data, m_bitLength); + } + + m_bitLength += 32; + } +#else + /// + /// Writes a 32 bit unsigned integer + /// + [CLSCompliant(false)] + public void Write(UInt32 source) + { + EnsureBufferSize(m_bitLength + 32); + NetBitWriter.WriteUInt32(source, 32, m_data, m_bitLength); + m_bitLength += 32; + } +#endif + + /// + /// Writes a 32 bit unsigned integer at a given offset in the buffer + /// + [CLSCompliant(false)] + public void WriteAt(Int32 offset, UInt32 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 32); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt32(source, 32, m_data, offset); + m_bitLength = newBitLength; + } + + /// + /// Writes a 32 bit signed integer + /// + [CLSCompliant(false)] + public void Write(UInt32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(uint, numberOfBits) can only write between 1 and 32 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt32(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes a signed integer using 1 to 32 bits + /// + public void Write(Int32 source, int numberOfBits) + { + NetException.Assert((numberOfBits > 0 && numberOfBits <= 32), "Write(int, numberOfBits) can only write between 1 and 32 bits"); + EnsureBufferSize(m_bitLength + numberOfBits); + + if (numberOfBits != 32) + { + // make first bit sign + int signBit = 1 << (numberOfBits - 1); + if (source < 0) + source = (-source - 1) | signBit; + else + source &= (~signBit); + } + + NetBitWriter.WriteUInt32((uint)source, numberOfBits, m_data, m_bitLength); + + m_bitLength += numberOfBits; + } + + /// + /// Writes a 64 bit unsigned integer + /// + [CLSCompliant(false)] + public void Write(UInt64 source) + { + EnsureBufferSize(m_bitLength + 64); + NetBitWriter.WriteUInt64(source, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + /// + /// Writes a 64 bit unsigned integer at a given offset in the buffer + /// + [CLSCompliant(false)] + public void WriteAt(Int32 offset, UInt64 source) + { + int newBitLength = Math.Max(m_bitLength, offset + 64); + EnsureBufferSize(newBitLength); + NetBitWriter.WriteUInt64(source, 64, m_data, offset); + m_bitLength = newBitLength; + } + + /// + /// Writes an unsigned integer using 1 to 64 bits + /// + [CLSCompliant(false)] + public void Write(UInt64 source, int numberOfBits) + { + EnsureBufferSize(m_bitLength + numberOfBits); + NetBitWriter.WriteUInt64(source, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + /// + /// Writes a 64 bit signed integer + /// + public void Write(Int64 source) + { + EnsureBufferSize(m_bitLength + 64); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, 64, m_data, m_bitLength); + m_bitLength += 64; + } + + /// + /// Writes a signed integer using 1 to 64 bits + /// + public void Write(Int64 source, int numberOfBits) + { + EnsureBufferSize(m_bitLength + numberOfBits); + ulong usource = (ulong)source; + NetBitWriter.WriteUInt64(usource, numberOfBits, m_data, m_bitLength); + m_bitLength += numberOfBits; + } + + // + // Floating point + // +#if UNSAFE + /// + /// Writes a 32 bit floating point value + /// + public unsafe void Write(float source) + { + uint val = *((uint*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + /// + /// Writes a 32 bit floating point value + /// + public void Write(float source) + { + // Use union to avoid BitConverter.GetBytes() which allocates memory on the heap + SingleUIntUnion su; + su.UIntValue = 0; // must initialize every member of the union to avoid warning + su.SingleValue = source; + +#if BIGENDIAN + // swap byte order + su.UIntValue = NetUtility.SwapByteOrder(su.UIntValue); +#endif + Write(su.UIntValue); + } +#endif + +#if UNSAFE + /// + /// Writes a 64 bit floating point value + /// + public unsafe void Write(double source) + { + ulong val = *((ulong*)&source); +#if BIGENDIAN + val = NetUtility.SwapByteOrder(val); +#endif + Write(val); + } +#else + /// + /// Writes a 64 bit floating point value + /// + public void Write(double source) + { + byte[] val = BitConverter.GetBytes(source); +#if BIGENDIAN + // 0 1 2 3 4 5 6 7 + + // swap byte order + byte tmp = val[7]; + val[7] = val[0]; + val[0] = tmp; + + tmp = val[6]; + val[6] = val[1]; + val[1] = tmp; + + tmp = val[5]; + val[5] = val[2]; + val[2] = tmp; + + tmp = val[4]; + val[4] = val[3]; + val[3] = tmp; +#endif + Write(val); + } +#endif + + // + // Variable bits + // + + /// + /// Write Base128 encoded variable sized unsigned integer of up to 32 bits + /// + /// number of bytes written + [CLSCompliant(false)] + public int WriteVariableUInt32(uint value) + { + int retval = 1; + uint num1 = (uint)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Write Base128 encoded variable sized signed integer of up to 32 bits + /// + /// number of bytes written + public int WriteVariableInt32(int value) + { + uint zigzag = (uint)(value << 1) ^ (uint)(value >> 31); + return WriteVariableUInt32(zigzag); + } + + /// + /// Write Base128 encoded variable sized signed integer of up to 64 bits + /// + /// number of bytes written + public int WriteVariableInt64(Int64 value) + { + ulong zigzag = (ulong)(value << 1) ^ (ulong)(value >> 63); + return WriteVariableUInt64(zigzag); + } + + /// + /// Write Base128 encoded variable sized unsigned integer of up to 64 bits + /// + /// number of bytes written + [CLSCompliant(false)] + public int WriteVariableUInt64(UInt64 value) + { + int retval = 1; + UInt64 num1 = (UInt64)value; + while (num1 >= 0x80) + { + this.Write((byte)(num1 | 0x80)); + num1 = num1 >> 7; + retval++; + } + this.Write((byte)num1); + return retval; + } + + /// + /// Compress (lossy) a float in the range -1..1 using numberOfBits bits + /// + public void WriteSignedSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= -1.0) && (value <= 1.0)), " WriteSignedSingle() must be passed a float in the range -1 to 1; val is " + value); + + float unit = (value + 1.0f) * 0.5f; + int maxVal = (1 << numberOfBits) - 1; + uint writeVal = (uint)(unit * (float)maxVal); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress (lossy) a float in the range 0..1 using numberOfBits bits + /// + public void WriteUnitSingle(float value, int numberOfBits) + { + NetException.Assert(((value >= 0.0) && (value <= 1.0)), " WriteUnitSingle() must be passed a float in the range 0 to 1; val is " + value); + + int maxValue = (1 << numberOfBits) - 1; + uint writeVal = (uint)(value * (float)maxValue); + + Write(writeVal, numberOfBits); + } + + /// + /// Compress a float within a specified range using a certain number of bits + /// + public void WriteRangedSingle(float value, float min, float max, int numberOfBits) + { + NetException.Assert(((value >= min) && (value <= max)), " WriteRangedSingle() must be passed a float in the range MIN to MAX; val is " + value); + + float range = max - min; + float unit = ((value - min) / range); + int maxVal = (1 << numberOfBits) - 1; + Write((UInt32)((float)maxVal * unit), numberOfBits); + } + + /// + /// Writes an integer with the least amount of bits need for the specified range + /// Returns number of bits written + /// + public int WriteRangedInteger(int min, int max, int value) + { + NetException.Assert(value >= min && value <= max, "Value not within min/max range!"); + + uint range = (uint)(max - min); + int numBits = NetUtility.BitsToHoldUInt(range); + + uint rvalue = (uint)(value - min); + Write(rvalue, numBits); + + return numBits; + } + + /// + /// Writes an integer with the least amount of bits need for the specified range + /// Returns number of bits written + /// + public int WriteRangedInteger(long min, long max, long value) + { + NetException.Assert(value >= min && value <= max, "Value not within min/max range!"); + + ulong range = (ulong)(max - min); + int numBits = NetUtility.BitsToHoldUInt64(range); + + ulong rvalue = (ulong)(value - min); + Write(rvalue, numBits); + + return numBits; + } + + /// + /// Write a string + /// + public void Write(string source) + { + if (string.IsNullOrEmpty(source)) + { + WriteVariableUInt32(0); + return; + } + + byte[] bytes = Encoding.UTF8.GetBytes(source); + EnsureBufferSize(m_bitLength + 8 + (bytes.Length * 8)); + WriteVariableUInt32((uint)bytes.Length); + Write(bytes); + } + + /// + /// Writes an endpoint description + /// + public void Write(IPEndPoint endPoint) + { + byte[] bytes = endPoint.Address.GetAddressBytes(); + Write((byte)bytes.Length); + Write(bytes); + Write((ushort)endPoint.Port); + } + + /// + /// Writes the current local time to a message; readable (and convertable to local time) by the remote host using ReadTime() + /// + public void WriteTime(bool highPrecision) + { + double localTime = NetTime.Now; + if (highPrecision) + Write(localTime); + else + Write((float)localTime); + } + + /// + /// Writes a local timestamp to a message; readable (and convertable to local time) by the remote host using ReadTime() + /// + public void WriteTime(double localTime, bool highPrecision) + { + if (highPrecision) + Write(localTime); + else + Write((float)localTime); + } + + /// + /// Pads data with enough bits to reach a full byte. Decreases cpu usage for subsequent byte writes. + /// + public void WritePadBits() + { + m_bitLength = ((m_bitLength + 7) >> 3) * 8; + EnsureBufferSize(m_bitLength); + } + + /// + /// Pads data with the specified number of bits. + /// + public void WritePadBits(int numberOfBits) + { + m_bitLength += numberOfBits; + EnsureBufferSize(m_bitLength); + } + + /// + /// Append all the bits of message to this message + /// + public void Write(NetBuffer buffer) + { + EnsureBufferSize(m_bitLength + (buffer.LengthBytes * 8)); + + Write(buffer.m_data, 0, buffer.LengthBytes); + + // did we write excessive bits? + int bitsInLastByte = (buffer.m_bitLength % 8); + if (bitsInLastByte != 0) + { + int excessBits = 8 - bitsInLastByte; + m_bitLength -= excessBits; + } + } + } +} diff --git a/SRMP/Lidgren.Network/NetBuffer.cs b/SRMP/Lidgren.Network/NetBuffer.cs new file mode 100644 index 0000000..34c783f --- /dev/null +++ b/SRMP/Lidgren.Network/NetBuffer.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + /// + /// Number of bytes to overallocate for each message to avoid resizing + /// + protected const int c_overAllocateAmount = 4; + + private static readonly Dictionary s_readMethods; + private static readonly Dictionary s_writeMethods; + + internal byte[] m_data; + internal int m_bitLength; + internal int m_readPosition; + + /// + /// Gets or sets the internal data buffer + /// + public byte[] Data + { + get { return m_data; } + set { m_data = value; } + } + + /// + /// Gets or sets the length of the used portion of the buffer in bytes + /// + public int LengthBytes + { + get { return ((m_bitLength + 7) >> 3); } + set + { + m_bitLength = value * 8; + InternalEnsureBufferSize(m_bitLength); + } + } + + /// + /// Gets or sets the length of the used portion of the buffer in bits + /// + public int LengthBits + { + get { return m_bitLength; } + set + { + m_bitLength = value; + InternalEnsureBufferSize(m_bitLength); + } + } + + /// + /// Gets or sets the read position in the buffer, in bits (not bytes) + /// + public long Position + { + get { return (long)m_readPosition; } + set { m_readPosition = (int)value; } + } + + /// + /// Gets the position in the buffer in bytes; note that the bits of the first returned byte may already have been read - check the Position property to make sure. + /// + public int PositionInBytes + { + get { return (int)(m_readPosition / 8); } + } + + static NetBuffer() + { + s_readMethods = new Dictionary(); + MethodInfo[] methods = typeof(NetIncomingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public); + foreach (MethodInfo mi in methods) + { + if (mi.GetParameters().Length == 0 && mi.Name.StartsWith("Read", StringComparison.InvariantCulture) && mi.Name.Substring(4) == mi.ReturnType.Name) + { + s_readMethods[mi.ReturnType] = mi; + } + } + + s_writeMethods = new Dictionary(); + methods = typeof(NetOutgoingMessage).GetMethods(BindingFlags.Instance | BindingFlags.Public); + foreach (MethodInfo mi in methods) + { + if (mi.Name.Equals("Write", StringComparison.InvariantCulture)) + { + ParameterInfo[] pis = mi.GetParameters(); + if (pis.Length == 1) + s_writeMethods[pis[0].ParameterType] = mi; + } + } + } + } +} diff --git a/SRMP/Lidgren.Network/NetClient.cs b/SRMP/Lidgren.Network/NetClient.cs new file mode 100644 index 0000000..2af6c10 --- /dev/null +++ b/SRMP/Lidgren.Network/NetClient.cs @@ -0,0 +1,176 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Net; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + /// + /// Specialized version of NetPeer used for a "client" connection. It does not accept any incoming connections and maintains a ServerConnection property + /// + public class NetClient : NetPeer + { + /// + /// Gets the connection to the server, if any + /// + public NetConnection ServerConnection + { + get + { + NetConnection retval = null; + if (m_connections.Count > 0) + { + try + { + retval = m_connections[0]; + } + catch + { + // preempted! + return null; + } + } + return retval; + } + } + + /// + /// Gets the connection status of the server connection (or NetConnectionStatus.Disconnected if no connection) + /// + public NetConnectionStatus ConnectionStatus + { + get + { + var conn = ServerConnection; + if (conn == null) + return NetConnectionStatus.Disconnected; + return conn.Status; + } + } + + /// + /// NetClient constructor + /// + /// + public NetClient(NetPeerConfiguration config) + : base(config) + { + config.AcceptIncomingConnections = false; + } + + /// + /// Connect to a remote server + /// + /// The remote endpoint to connect to + /// The hail message to pass + /// server connection, or null if already connected + public override NetConnection Connect(NetEndPoint remoteEndPoint, NetOutgoingMessage hailMessage) + { + lock (m_connections) + { + if (m_connections.Count > 0) + { + LogWarning("Connect attempt failed; Already connected"); + return null; + } + } + + lock (m_handshakes) + { + if (m_handshakes.Count > 0) + { + LogWarning("Connect attempt failed; Handshake already in progress"); + return null; + } + } + + return base.Connect(remoteEndPoint, hailMessage); + } + + /// + /// Disconnect from server + /// + /// reason for disconnect + public void Disconnect(string byeMessage) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + lock (m_handshakes) + { + if (m_handshakes.Count > 0) + { + LogVerbose("Aborting connection attempt"); + foreach(var hs in m_handshakes) + hs.Value.Disconnect(byeMessage); + return; + } + } + + LogWarning("Disconnect requested when not connected!"); + return; + } + serverConnection.Disconnect(byeMessage); + } + + /// + /// Sends message to server + /// + public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + LogWarning("Cannot send message, no server connection!"); + return NetSendResult.FailedNotConnected; + } + + return serverConnection.SendMessage(msg, method, 0); + } + + /// + /// Sends message to server + /// + public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + NetConnection serverConnection = ServerConnection; + if (serverConnection == null) + { + LogWarning("Cannot send message, no server connection!"); + Recycle(msg); + return NetSendResult.FailedNotConnected; + } + + return serverConnection.SendMessage(msg, method, sequenceChannel); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetClient " + ServerConnection + "]"; + } + + } +} diff --git a/SRMP/Lidgren.Network/NetConnection.Handshake.cs b/SRMP/Lidgren.Network/NetConnection.Handshake.cs new file mode 100644 index 0000000..c846057 --- /dev/null +++ b/SRMP/Lidgren.Network/NetConnection.Handshake.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + public partial class NetConnection + { + internal bool m_connectRequested; + internal bool m_disconnectRequested; + internal bool m_disconnectReqSendBye; + internal string m_disconnectMessage; + internal bool m_connectionInitiator; + internal NetIncomingMessage m_remoteHailMessage; + internal double m_lastHandshakeSendTime; + internal int m_handshakeAttempts; + + /// + /// The message that the remote part specified via Connect() or Approve() - can be null. + /// + public NetIncomingMessage RemoteHailMessage { get { return m_remoteHailMessage; } } + + // heartbeat called when connection still is in m_handshakes of NetPeer + internal void UnconnectedHeartbeat(double now) + { + m_peer.VerifyNetworkThread(); + + if (m_disconnectRequested) + ExecuteDisconnect(m_disconnectMessage, true); + + if (m_connectRequested) + { + switch (m_status) + { + case NetConnectionStatus.Connected: + case NetConnectionStatus.RespondedConnect: + // reconnect + ExecuteDisconnect("Reconnecting", true); + break; + + case NetConnectionStatus.InitiatedConnect: + // send another connect attempt + SendConnect(now); + break; + + case NetConnectionStatus.Disconnected: + m_peer.ThrowOrLog("This connection is Disconnected; spent. A new one should have been created"); + break; + + case NetConnectionStatus.Disconnecting: + // let disconnect finish first + break; + + case NetConnectionStatus.None: + default: + SendConnect(now); + break; + } + return; + } + + if (now - m_lastHandshakeSendTime > m_peerConfiguration.m_resendHandshakeInterval) + { + if (m_handshakeAttempts >= m_peerConfiguration.m_maximumHandshakeAttempts) + { + // failed to connect + ExecuteDisconnect("Failed to establish connection - no response from remote host", true); + return; + } + + // resend handshake + switch (m_status) + { + case NetConnectionStatus.InitiatedConnect: + SendConnect(now); + break; + case NetConnectionStatus.RespondedConnect: + SendConnectResponse(now, true); + break; + case NetConnectionStatus.RespondedAwaitingApproval: + // awaiting approval + m_lastHandshakeSendTime = now; // postpone handshake resend + break; + case NetConnectionStatus.None: + case NetConnectionStatus.ReceivedInitiation: + default: + m_peer.LogWarning("Time to resend handshake, but status is " + m_status); + break; + } + } + } + + internal void ExecuteDisconnect(string reason, bool sendByeMessage) + { + m_peer.VerifyNetworkThread(); + + // clear send queues + for (int i = 0; i < m_sendChannels.Length; i++) + { + NetSenderChannelBase channel = m_sendChannels[i]; + if (channel != null) + channel.Reset(); + } + + if (sendByeMessage) + SendDisconnect(reason, true); + + if (m_status == NetConnectionStatus.ReceivedInitiation) + { + // nothing much has happened yet; no need to send disconnected status message + m_status = NetConnectionStatus.Disconnected; + } + else + { + SetStatus(NetConnectionStatus.Disconnected, reason); + } + + // in case we're still in handshake + lock (m_peer.m_handshakes) + m_peer.m_handshakes.Remove(m_remoteEndPoint); + + m_disconnectRequested = false; + m_connectRequested = false; + m_handshakeAttempts = 0; + } + + internal void SendConnect(double now) + { + m_peer.VerifyNetworkThread(); + + int preAllocate = 13 + m_peerConfiguration.AppIdentifier.Length; + preAllocate += (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes); + + NetOutgoingMessage om = m_peer.CreateMessage(preAllocate); + om.m_messageType = NetMessageType.Connect; + om.Write(m_peerConfiguration.AppIdentifier); + om.Write(m_peer.m_uniqueIdentifier); + om.Write((float)now); + + WriteLocalHail(om); + + m_peer.SendLibrary(om, m_remoteEndPoint); + + m_connectRequested = false; + m_lastHandshakeSendTime = now; + m_handshakeAttempts++; + + if (m_handshakeAttempts > 1) + m_peer.LogDebug("Resending Connect..."); + SetStatus(NetConnectionStatus.InitiatedConnect, "Locally requested connect"); + } + + internal void SendConnectResponse(double now, bool onLibraryThread) + { + if (onLibraryThread) + m_peer.VerifyNetworkThread(); + + NetOutgoingMessage om = m_peer.CreateMessage(m_peerConfiguration.AppIdentifier.Length + 13 + (m_localHailMessage == null ? 0 : m_localHailMessage.LengthBytes)); + om.m_messageType = NetMessageType.ConnectResponse; + om.Write(m_peerConfiguration.AppIdentifier); + om.Write(m_peer.m_uniqueIdentifier); + om.Write((float)now); + Interlocked.Increment(ref om.m_recyclingCount); + WriteLocalHail(om); + + if (onLibraryThread) + m_peer.SendLibrary(om, m_remoteEndPoint); + else + m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple(m_remoteEndPoint, om)); + + m_lastHandshakeSendTime = now; + m_handshakeAttempts++; + + if (m_handshakeAttempts > 1) + m_peer.LogDebug("Resending ConnectResponse..."); + + SetStatus(NetConnectionStatus.RespondedConnect, "Remotely requested connect"); + } + + internal void SendDisconnect(string reason, bool onLibraryThread) + { + if (onLibraryThread) + m_peer.VerifyNetworkThread(); + + NetOutgoingMessage om = m_peer.CreateMessage(reason); + om.m_messageType = NetMessageType.Disconnect; + Interlocked.Increment(ref om.m_recyclingCount); + if (onLibraryThread) + m_peer.SendLibrary(om, m_remoteEndPoint); + else + m_peer.m_unsentUnconnectedMessages.Enqueue(new NetTuple(m_remoteEndPoint, om)); + } + + private void WriteLocalHail(NetOutgoingMessage om) + { + if (m_localHailMessage != null) + { + byte[] hi = m_localHailMessage.Data; + if (hi != null && hi.Length >= m_localHailMessage.LengthBytes) + { + if (om.LengthBytes + m_localHailMessage.LengthBytes > m_peerConfiguration.m_maximumTransmissionUnit - 10) + m_peer.ThrowOrLog("Hail message too large; can maximally be " + (m_peerConfiguration.m_maximumTransmissionUnit - 10 - om.LengthBytes)); + om.Write(m_localHailMessage.Data, 0, m_localHailMessage.LengthBytes); + } + } + } + + internal void SendConnectionEstablished() + { + NetOutgoingMessage om = m_peer.CreateMessage(4); + om.m_messageType = NetMessageType.ConnectionEstablished; + om.Write((float)NetTime.Now); + m_peer.SendLibrary(om, m_remoteEndPoint); + + m_handshakeAttempts = 0; + + InitializePing(); + if (m_status != NetConnectionStatus.Connected) + SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier)); + } + + /// + /// Approves this connection; sending a connection response to the remote host + /// + public void Approve() + { + if (m_status != NetConnectionStatus.RespondedAwaitingApproval) + { + m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status); + return; + } + + m_localHailMessage = null; + m_handshakeAttempts = 0; + SendConnectResponse(NetTime.Now, false); + } + + /// + /// Approves this connection; sending a connection response to the remote host + /// + /// The local hail message that will be set as RemoteHailMessage on the remote host + public void Approve(NetOutgoingMessage localHail) + { + if (m_status != NetConnectionStatus.RespondedAwaitingApproval) + { + m_peer.LogWarning("Approve() called in wrong status; expected RespondedAwaitingApproval; got " + m_status); + return; + } + + m_localHailMessage = localHail; + m_handshakeAttempts = 0; + SendConnectResponse(NetTime.Now, false); + } + + /// + /// Denies this connection; disconnecting it + /// + public void Deny() + { + Deny(string.Empty); + } + + /// + /// Denies this connection; disconnecting it + /// + /// The stated reason for the disconnect, readable as a string in the StatusChanged message on the remote host + public void Deny(string reason) + { + // send disconnect; remove from handshakes + SendDisconnect(reason, false); + + // remove from handshakes + lock (m_peer.m_handshakes) + m_peer.m_handshakes.Remove(m_remoteEndPoint); + } + + internal void ReceivedHandshake(double now, NetMessageType tp, int ptr, int payloadLength) + { + m_peer.VerifyNetworkThread(); + + byte[] hail; + switch (tp) + { + case NetMessageType.Connect: + if (m_status == NetConnectionStatus.ReceivedInitiation) + { + // Whee! Server full has already been checked + bool ok = ValidateHandshakeData(ptr, payloadLength, out hail); + if (ok) + { + if (hail != null) + { + m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail); + m_remoteHailMessage.LengthBits = (hail.Length * 8); + } + else + { + m_remoteHailMessage = null; + } + + if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval)) + { + // ok, let's not add connection just yet + NetIncomingMessage appMsg = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionApproval, (m_remoteHailMessage == null ? 0 : m_remoteHailMessage.LengthBytes)); + appMsg.m_receiveTime = now; + appMsg.m_senderConnection = this; + appMsg.m_senderEndPoint = this.m_remoteEndPoint; + if (m_remoteHailMessage != null) + appMsg.Write(m_remoteHailMessage.m_data, 0, m_remoteHailMessage.LengthBytes); + SetStatus(NetConnectionStatus.RespondedAwaitingApproval, "Awaiting approval"); + m_peer.ReleaseMessage(appMsg); + return; + } + + SendConnectResponse((float)now, true); + } + return; + } + if (m_status == NetConnectionStatus.RespondedAwaitingApproval) + { + m_peer.LogWarning("Ignoring multiple Connect() most likely due to a delayed Approval"); + return; + } + if (m_status == NetConnectionStatus.RespondedConnect) + { + // our ConnectResponse must have been lost + SendConnectResponse((float)now, true); + return; + } + m_peer.LogDebug("Unhandled Connect: " + tp + ", status is " + m_status + " length: " + payloadLength); + break; + case NetMessageType.ConnectResponse: + HandleConnectResponse(now, tp, ptr, payloadLength); + break; + + case NetMessageType.ConnectionEstablished: + switch (m_status) + { + case NetConnectionStatus.Connected: + // ok... + break; + case NetConnectionStatus.Disconnected: + case NetConnectionStatus.Disconnecting: + case NetConnectionStatus.None: + // too bad, almost made it + break; + case NetConnectionStatus.ReceivedInitiation: + // uh, a little premature... ignore + break; + case NetConnectionStatus.InitiatedConnect: + // weird, should have been RespondedConnect... + break; + case NetConnectionStatus.RespondedConnect: + // awesome + + NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + InitializeRemoteTimeOffset(msg.ReadSingle()); + + m_peer.AcceptConnection(this); + InitializePing(); + SetStatus(NetConnectionStatus.Connected, "Connected to " + NetUtility.ToHexString(m_remoteUniqueIdentifier)); + return; + } + break; + + case NetMessageType.Disconnect: + // ouch + string reason = "Ouch"; + try + { + NetIncomingMessage inc = m_peer.SetupReadHelperMessage(ptr, payloadLength); + reason = inc.ReadString(); + } + catch + { + } + ExecuteDisconnect(reason, false); + break; + + case NetMessageType.Discovery: + m_peer.HandleIncomingDiscoveryRequest(now, m_remoteEndPoint, ptr, payloadLength); + return; + + case NetMessageType.DiscoveryResponse: + m_peer.HandleIncomingDiscoveryResponse(now, m_remoteEndPoint, ptr, payloadLength); + return; + + case NetMessageType.Ping: + // silently ignore + return; + + default: + m_peer.LogDebug("Unhandled type during handshake: " + tp + " length: " + payloadLength); + break; + } + } + + private void HandleConnectResponse(double now, NetMessageType tp, int ptr, int payloadLength) + { + byte[] hail; + switch (m_status) + { + case NetConnectionStatus.InitiatedConnect: + // awesome + bool ok = ValidateHandshakeData(ptr, payloadLength, out hail); + if (ok) + { + if (hail != null) + { + m_remoteHailMessage = m_peer.CreateIncomingMessage(NetIncomingMessageType.Data, hail); + m_remoteHailMessage.LengthBits = (hail.Length * 8); + } + else + { + m_remoteHailMessage = null; + } + + m_peer.AcceptConnection(this); + SendConnectionEstablished(); + return; + } + break; + case NetConnectionStatus.RespondedConnect: + // hello, wtf? + break; + case NetConnectionStatus.Disconnecting: + case NetConnectionStatus.Disconnected: + case NetConnectionStatus.ReceivedInitiation: + case NetConnectionStatus.None: + // wtf? anyway, bye! + break; + case NetConnectionStatus.Connected: + // my ConnectionEstablished must have been lost, send another one + SendConnectionEstablished(); + return; + } + } + + private bool ValidateHandshakeData(int ptr, int payloadLength, out byte[] hail) + { + hail = null; + + // create temporary incoming message + NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + try + { + string remoteAppIdentifier = msg.ReadString(); + long remoteUniqueIdentifier = msg.ReadInt64(); + InitializeRemoteTimeOffset(msg.ReadSingle()); + + int remainingBytes = payloadLength - (msg.PositionInBytes - ptr); + if (remainingBytes > 0) + hail = msg.ReadBytes(remainingBytes); + + if (remoteAppIdentifier != m_peer.m_configuration.AppIdentifier) + { + ExecuteDisconnect("Wrong application identifier!", true); + return false; + } + + m_remoteUniqueIdentifier = remoteUniqueIdentifier; + } + catch(Exception ex) + { + // whatever; we failed + ExecuteDisconnect("Handshake data validation failed", true); + m_peer.LogWarning("ReadRemoteHandshakeData failed: " + ex.Message); + return false; + } + return true; + } + + /// + /// Disconnect from the remote peer + /// + /// the message to send with the disconnect message + public void Disconnect(string byeMessage) + { + // user or library thread + if (m_status == NetConnectionStatus.None || m_status == NetConnectionStatus.Disconnected) + return; + + m_peer.LogVerbose("Disconnect requested for " + this); + m_disconnectMessage = byeMessage; + + if (m_status != NetConnectionStatus.Disconnected && m_status != NetConnectionStatus.None) + SetStatus(NetConnectionStatus.Disconnecting, byeMessage); + + m_handshakeAttempts = 0; + m_disconnectRequested = true; + m_disconnectReqSendBye = true; + } + } +} diff --git a/SRMP/Lidgren.Network/NetConnection.Latency.cs b/SRMP/Lidgren.Network/NetConnection.Latency.cs new file mode 100644 index 0000000..14a4b12 --- /dev/null +++ b/SRMP/Lidgren.Network/NetConnection.Latency.cs @@ -0,0 +1,141 @@ +using System; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + private double m_sentPingTime; + private int m_sentPingNumber; + private double m_averageRoundtripTime; + private double m_timeoutDeadline = double.MaxValue; + + // local time value + m_remoteTimeOffset = remote time value + internal double m_remoteTimeOffset; + + /// + /// Gets the current average roundtrip time in seconds + /// + public float AverageRoundtripTime { get { return (float)m_averageRoundtripTime; } } + + /// + /// Time offset between this peer and the remote peer + /// + public float RemoteTimeOffset { get { return (float)m_remoteTimeOffset; } } + + // this might happen more than once + internal void InitializeRemoteTimeOffset(float remoteSendTime) + { + m_remoteTimeOffset = (remoteSendTime + (m_averageRoundtripTime / 2.0)) - NetTime.Now; + } + + /// + /// Gets local time value comparable to NetTime.Now from a remote value + /// + public double GetLocalTime(double remoteTimestamp) + { + return remoteTimestamp - m_remoteTimeOffset; + } + + /// + /// Gets the remote time value for a local time value produced by NetTime.Now + /// + public double GetRemoteTime(double localTimestamp) + { + return localTimestamp + m_remoteTimeOffset; + } + + internal void InitializePing() + { + m_timeoutDeadline = NetTime.Now + (m_peerConfiguration.m_connectionTimeout * 2.0); // initially allow a little more time + SendPing(); + } + + internal void SendPing() + { + m_peer.VerifyNetworkThread(); + + m_sentPingNumber++; + + m_sentPingTime = NetTime.Now; + NetOutgoingMessage om = m_peer.CreateMessage(1); + om.Write((byte)m_sentPingNumber); // truncating to 0-255 + om.m_messageType = NetMessageType.Ping; + + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + bool connectionReset; + m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset); + + m_statistics.PacketSent(len, 1); + m_peer.Recycle(om); + } + + internal void SendPong(int pingNumber) + { + m_peer.VerifyNetworkThread(); + + NetOutgoingMessage om = m_peer.CreateMessage(5); + om.Write((byte)pingNumber); + om.Write((float)NetTime.Now); // we should update this value to reflect the exact point in time the packet is SENT + om.m_messageType = NetMessageType.Pong; + + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + bool connectionReset; + + m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset); + + m_statistics.PacketSent(len, 1); + m_peer.Recycle(om); + } + + internal void ReceivedPong(double now, int pongNumber, float remoteSendTime) + { + if ((byte)pongNumber != (byte)m_sentPingNumber) + { + m_peer.LogVerbose("Ping/Pong mismatch; dropped message?"); + return; + } + + m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout; + + double rtt = now - m_sentPingTime; + NetException.Assert(rtt >= 0); + + double diff = (remoteSendTime + (rtt / 2.0)) - now; + + if (m_averageRoundtripTime < 0) + { + m_remoteTimeOffset = diff; + m_averageRoundtripTime = rtt; + m_peer.LogDebug("Initiated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + " Remote time is: " + (now + diff)); + } + else + { + m_averageRoundtripTime = (m_averageRoundtripTime * 0.7) + (rtt * 0.3); + + m_remoteTimeOffset = ((m_remoteTimeOffset * (double)(m_sentPingNumber - 1)) + diff) / (double)m_sentPingNumber; + m_peer.LogVerbose("Updated average roundtrip time to " + NetTime.ToReadable(m_averageRoundtripTime) + ", remote time to " + (now + m_remoteTimeOffset) + " (ie. diff " + m_remoteTimeOffset + ")"); + } + + // update resend delay for all channels + double resendDelay = GetResendDelay(); + foreach (var chan in m_sendChannels) + { + var rchan = chan as NetReliableSenderChannel; + if (rchan != null) + rchan.m_resendDelay = resendDelay; + } + + // m_peer.LogVerbose("Timeout deadline pushed to " + m_timeoutDeadline); + + // notify the application that average rtt changed + if (m_peer.m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ConnectionLatencyUpdated)) + { + NetIncomingMessage update = m_peer.CreateIncomingMessage(NetIncomingMessageType.ConnectionLatencyUpdated, 4); + update.m_senderConnection = this; + update.m_senderEndPoint = this.m_remoteEndPoint; + update.Write((float)rtt); + m_peer.ReleaseMessage(update); + } + } + } +} diff --git a/SRMP/Lidgren.Network/NetConnection.MTU.cs b/SRMP/Lidgren.Network/NetConnection.MTU.cs new file mode 100644 index 0000000..d5f64da --- /dev/null +++ b/SRMP/Lidgren.Network/NetConnection.MTU.cs @@ -0,0 +1,182 @@ +using System; + +namespace Lidgren.Network +{ + public partial class NetConnection + { + private enum ExpandMTUStatus + { + None, + InProgress, + Finished + } + + private const int c_protocolMaxMTU = (int)((((float)ushort.MaxValue / 8.0f) - 1.0f)); + + private ExpandMTUStatus m_expandMTUStatus; + + private int m_largestSuccessfulMTU; + private int m_smallestFailedMTU; + + private int m_lastSentMTUAttemptSize; + private double m_lastSentMTUAttemptTime; + private int m_mtuAttemptFails; + + internal int m_currentMTU; + + /// + /// Gets the current MTU in bytes. If PeerConfiguration.AutoExpandMTU is false, this will be PeerConfiguration.MaximumTransmissionUnit. + /// + public int CurrentMTU { get { return m_currentMTU; } } + + internal void InitExpandMTU(double now) + { + m_lastSentMTUAttemptTime = now + m_peerConfiguration.m_expandMTUFrequency + 1.5f + m_averageRoundtripTime; // wait a tiny bit before starting to expand mtu + m_largestSuccessfulMTU = 512; + m_smallestFailedMTU = -1; + m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit; + } + + private void MTUExpansionHeartbeat(double now) + { + if (m_expandMTUStatus == ExpandMTUStatus.Finished) + return; + + if (m_expandMTUStatus == ExpandMTUStatus.None) + { + if (m_peerConfiguration.m_autoExpandMTU == false) + { + FinalizeMTU(m_currentMTU); + return; + } + + // begin expansion + ExpandMTU(now); + return; + } + + if (now > m_lastSentMTUAttemptTime + m_peerConfiguration.ExpandMTUFrequency) + { + m_mtuAttemptFails++; + if (m_mtuAttemptFails == 3) + { + FinalizeMTU(m_currentMTU); + return; + } + + // timed out; ie. failed + m_smallestFailedMTU = m_lastSentMTUAttemptSize; + ExpandMTU(now); + } + } + + private void ExpandMTU(double now) + { + int tryMTU; + + // we've nevered encountered failure + if (m_smallestFailedMTU == -1) + { + // we've never encountered failure; expand by 25% each time + tryMTU = (int)((float)m_currentMTU * 1.25f); + //m_peer.LogDebug("Trying MTU " + tryMTU); + } + else + { + // we HAVE encountered failure; so try in between + tryMTU = (int)(((float)m_smallestFailedMTU + (float)m_largestSuccessfulMTU) / 2.0f); + //m_peer.LogDebug("Trying MTU " + m_smallestFailedMTU + " <-> " + m_largestSuccessfulMTU + " = " + tryMTU); + } + + if (tryMTU > c_protocolMaxMTU) + tryMTU = c_protocolMaxMTU; + + if (tryMTU == m_largestSuccessfulMTU) + { + //m_peer.LogDebug("Found optimal MTU - exiting"); + FinalizeMTU(m_largestSuccessfulMTU); + return; + } + + SendExpandMTU(now, tryMTU); + } + + private void SendExpandMTU(double now, int size) + { + NetOutgoingMessage om = m_peer.CreateMessage(size); + byte[] tmp = new byte[size]; + om.Write(tmp); + om.m_messageType = NetMessageType.ExpandMTURequest; + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + + bool ok = m_peer.SendMTUPacket(len, m_remoteEndPoint); + if (ok == false) + { + //m_peer.LogDebug("Send MTU failed for size " + size); + + // failure + if (m_smallestFailedMTU == -1 || size < m_smallestFailedMTU) + { + m_smallestFailedMTU = size; + m_mtuAttemptFails++; + if (m_mtuAttemptFails >= m_peerConfiguration.ExpandMTUFailAttempts) + { + FinalizeMTU(m_largestSuccessfulMTU); + return; + } + } + ExpandMTU(now); + return; + } + + m_lastSentMTUAttemptSize = size; + m_lastSentMTUAttemptTime = now; + + m_statistics.PacketSent(len, 1); + m_peer.Recycle(om); + } + + private void FinalizeMTU(int size) + { + if (m_expandMTUStatus == ExpandMTUStatus.Finished) + return; + m_expandMTUStatus = ExpandMTUStatus.Finished; + m_currentMTU = size; + if (m_currentMTU != m_peerConfiguration.m_maximumTransmissionUnit) + m_peer.LogDebug("Expanded Maximum Transmission Unit to: " + m_currentMTU + " bytes"); + return; + } + + private void SendMTUSuccess(int size) + { + NetOutgoingMessage om = m_peer.CreateMessage(4); + om.Write(size); + om.m_messageType = NetMessageType.ExpandMTUSuccess; + int len = om.Encode(m_peer.m_sendBuffer, 0, 0); + bool connectionReset; + m_peer.SendPacket(len, m_remoteEndPoint, 1, out connectionReset); + m_peer.Recycle(om); + + //m_peer.LogDebug("Received MTU expand request for " + size + " bytes"); + + m_statistics.PacketSent(len, 1); + } + + private void HandleExpandMTUSuccess(double now, int size) + { + if (size > m_largestSuccessfulMTU) + m_largestSuccessfulMTU = size; + + if (size < m_currentMTU) + { + //m_peer.LogDebug("Received low MTU expand success (size " + size + "); current mtu is " + m_currentMTU); + return; + } + + //m_peer.LogDebug("Expanding MTU to " + size); + m_currentMTU = size; + + ExpandMTU(now); + } + } +} diff --git a/SRMP/Lidgren.Network/NetConnection.cs b/SRMP/Lidgren.Network/NetConnection.cs new file mode 100644 index 0000000..2a4d6fe --- /dev/null +++ b/SRMP/Lidgren.Network/NetConnection.cs @@ -0,0 +1,584 @@ +using System; +using System.Net; +using System.Threading; +using System.Diagnostics; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + /// + /// Represents a connection to a remote peer + /// + [DebuggerDisplay("RemoteUniqueIdentifier={RemoteUniqueIdentifier} RemoteEndPoint={m_remoteEndPoint}")] + public partial class NetConnection + { + private const int m_infrequentEventsSkipFrames = 8; // number of heartbeats to skip checking for infrequent events (ping, timeout etc) + private const int m_messageCoalesceFrames = 3; // number of heartbeats to wait for more incoming messages before sending packet + + internal NetPeer m_peer; + internal NetPeerConfiguration m_peerConfiguration; + internal NetConnectionStatus m_status; // actual status + internal NetConnectionStatus m_outputtedStatus; // status that has been sent as StatusChanged message + internal NetConnectionStatus m_visibleStatus; // status visible by querying the Status property + internal NetEndPoint m_remoteEndPoint; + internal NetSenderChannelBase[] m_sendChannels; + internal NetReceiverChannelBase[] m_receiveChannels; + internal NetOutgoingMessage m_localHailMessage; + internal long m_remoteUniqueIdentifier; + internal NetQueue> m_queuedOutgoingAcks; + internal NetQueue> m_queuedIncomingAcks; + private int m_sendBufferWritePtr; + private int m_sendBufferNumMessages; + private object m_tag; + internal NetConnectionStatistics m_statistics; + + /// + /// Gets or sets the application defined object containing data about the connection + /// + public object Tag + { + get { return m_tag; } + set { m_tag = value; } + } + + /// + /// Gets the peer which holds this connection + /// + public NetPeer Peer { get { return m_peer; } } + + /// + /// Gets the current status of the connection (synced to the last status message read) + /// + public NetConnectionStatus Status { get { return m_visibleStatus; } } + + /// + /// Gets various statistics for this connection + /// + public NetConnectionStatistics Statistics { get { return m_statistics; } } + + /// + /// Gets the remote endpoint for the connection + /// + public NetEndPoint RemoteEndPoint { get { return m_remoteEndPoint; } } + + /// + /// Gets the unique identifier of the remote NetPeer for this connection + /// + public long RemoteUniqueIdentifier { get { return m_remoteUniqueIdentifier; } } + + /// + /// Gets the local hail message that was sent as part of the handshake + /// + public NetOutgoingMessage LocalHailMessage { get { return m_localHailMessage; } } + + // gets the time before automatically resending an unacked message + internal double GetResendDelay() + { + double avgRtt = m_averageRoundtripTime; + if (avgRtt <= 0) + avgRtt = 0.1; // "default" resend is based on 100 ms roundtrip time + return 0.025 + (avgRtt * 2.1); // 25 ms + double rtt + } + + internal NetConnection(NetPeer peer, NetEndPoint remoteEndPoint) + { + m_peer = peer; + m_peerConfiguration = m_peer.Configuration; + m_status = NetConnectionStatus.None; + m_outputtedStatus = NetConnectionStatus.None; + m_visibleStatus = NetConnectionStatus.None; + m_remoteEndPoint = remoteEndPoint; + m_sendChannels = new NetSenderChannelBase[NetConstants.NumTotalChannels]; + m_receiveChannels = new NetReceiverChannelBase[NetConstants.NumTotalChannels]; + m_queuedOutgoingAcks = new NetQueue>(4); + m_queuedIncomingAcks = new NetQueue>(4); + m_statistics = new NetConnectionStatistics(this); + m_averageRoundtripTime = -1.0f; + m_currentMTU = m_peerConfiguration.MaximumTransmissionUnit; + } + + /// + /// Change the internal endpoint to this new one. Used when, during handshake, a switch in port is detected (due to NAT) + /// + internal void MutateEndPoint(NetEndPoint endPoint) + { + m_remoteEndPoint = endPoint; + } + + internal void ResetTimeout(double now) + { + m_timeoutDeadline = now + m_peerConfiguration.m_connectionTimeout; + } + + internal void SetStatus(NetConnectionStatus status, string reason) + { + // user or library thread + + m_status = status; + if (reason == null) + reason = string.Empty; + + if (m_status == NetConnectionStatus.Connected) + { + m_timeoutDeadline = NetTime.Now + m_peerConfiguration.m_connectionTimeout; + m_peer.LogVerbose("Timeout deadline initialized to " + m_timeoutDeadline); + } + + if (m_peerConfiguration.IsMessageTypeEnabled(NetIncomingMessageType.StatusChanged)) + { + if (m_outputtedStatus != status) + { + NetIncomingMessage info = m_peer.CreateIncomingMessage(NetIncomingMessageType.StatusChanged, 4 + reason.Length + (reason.Length > 126 ? 2 : 1)); + info.m_senderConnection = this; + info.m_senderEndPoint = m_remoteEndPoint; + info.Write((byte)m_status); + info.Write(reason); + m_peer.ReleaseMessage(info); + m_outputtedStatus = status; + } + } + else + { + // app dont want those messages, update visible status immediately + m_outputtedStatus = m_status; + m_visibleStatus = m_status; + } + } + + internal void Heartbeat(double now, uint frameCounter) + { + m_peer.VerifyNetworkThread(); + + NetException.Assert(m_status != NetConnectionStatus.InitiatedConnect && m_status != NetConnectionStatus.RespondedConnect); + + if ((frameCounter % m_infrequentEventsSkipFrames) == 0) + { + if (now > m_timeoutDeadline) + { + // + // connection timed out + // + m_peer.LogVerbose("Connection timed out at " + now + " deadline was " + m_timeoutDeadline); + ExecuteDisconnect("Connection timed out", true); + return; + } + + // send ping? + if (m_status == NetConnectionStatus.Connected) + { + if (now > m_sentPingTime + m_peer.m_configuration.m_pingInterval) + SendPing(); + + // handle expand mtu + MTUExpansionHeartbeat(now); + } + + if (m_disconnectRequested) + { + ExecuteDisconnect(m_disconnectMessage, m_disconnectReqSendBye); + return; + } + } + + bool connectionReset; // TODO: handle connection reset + + // + // Note: at this point m_sendBufferWritePtr and m_sendBufferNumMessages may be non-null; resends may already be queued up + // + + byte[] sendBuffer = m_peer.m_sendBuffer; + int mtu = m_currentMTU; + + if ((frameCounter % m_messageCoalesceFrames) == 0) // coalesce a few frames + { + // + // send ack messages + // + while (m_queuedOutgoingAcks.Count > 0) + { + int acks = (mtu - (m_sendBufferWritePtr + 5)) / 3; // 3 bytes per actual ack + if (acks > m_queuedOutgoingAcks.Count) + acks = m_queuedOutgoingAcks.Count; + + NetException.Assert(acks > 0); + + m_sendBufferNumMessages++; + + // write acks header + sendBuffer[m_sendBufferWritePtr++] = (byte)NetMessageType.Acknowledge; + sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number + sendBuffer[m_sendBufferWritePtr++] = 0; // no sequence number + int len = (acks * 3) * 8; // bits + sendBuffer[m_sendBufferWritePtr++] = (byte)len; + sendBuffer[m_sendBufferWritePtr++] = (byte)(len >> 8); + + // write acks + for (int i = 0; i < acks; i++) + { + NetTuple tuple; + m_queuedOutgoingAcks.TryDequeue(out tuple); + + //m_peer.LogVerbose("Sending ack for " + tuple.Item1 + "#" + tuple.Item2); + + sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item1; + sendBuffer[m_sendBufferWritePtr++] = (byte)tuple.Item2; + sendBuffer[m_sendBufferWritePtr++] = (byte)(tuple.Item2 >> 8); + } + + if (m_queuedOutgoingAcks.Count > 0) + { + // send packet and go for another round of acks + NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0); + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset); + m_statistics.PacketSent(m_sendBufferWritePtr, 1); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + } + + // + // Parse incoming acks (may trigger resends) + // + NetTuple incAck; + while (m_queuedIncomingAcks.TryDequeue(out incAck)) + { + //m_peer.LogVerbose("Received ack for " + acktp + "#" + seqNr); + NetSenderChannelBase chan = m_sendChannels[(int)incAck.Item1 - 1]; + + // If we haven't sent a message on this channel there is no reason to ack it + if (chan == null) + continue; + + chan.ReceiveAcknowledge(now, incAck.Item2); + } + } + + // + // send queued messages + // + if (m_peer.m_executeFlushSendQueue) + { + for (int i = m_sendChannels.Length - 1; i >= 0; i--) // Reverse order so reliable messages are sent first + { + var channel = m_sendChannels[i]; + NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0); + if (channel != null) + { + channel.SendQueuedMessages(now); + if (channel.NeedToSendMessages()) + m_peer.m_needFlushSendQueue = true; // failed to send all queued sends; likely a full window - need to try again + } + NetException.Assert(m_sendBufferWritePtr < 1 || m_sendBufferNumMessages > 0); + } + } + + // + // Put on wire data has been written to send buffer but not yet sent + // + if (m_sendBufferWritePtr > 0) + { + m_peer.VerifyNetworkThread(); + NetException.Assert(m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0); + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connectionReset); + m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + } + + // Queue an item for immediate sending on the wire + // This method is called from the ISenderChannels + internal void QueueSendMessage(NetOutgoingMessage om, int seqNr) + { + m_peer.VerifyNetworkThread(); + + int sz = om.GetEncodedSize(); + //if (sz > m_currentMTU) + // m_peer.LogWarning("Message larger than MTU! Fragmentation must have failed!"); + + bool connReset; // TODO: handle connection reset + + // can fit this message together with previously written to buffer? + if (m_sendBufferWritePtr + sz > m_currentMTU) + { + if (m_sendBufferWritePtr > 0 && m_sendBufferNumMessages > 0) + { + // previous message in buffer; send these first + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset); + m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + } + + // encode it into buffer regardless if it (now) fits within MTU or not + m_sendBufferWritePtr = om.Encode(m_peer.m_sendBuffer, m_sendBufferWritePtr, seqNr); + m_sendBufferNumMessages++; + + if (m_sendBufferWritePtr > m_currentMTU) + { + // send immediately; we're already over MTU + m_peer.SendPacket(m_sendBufferWritePtr, m_remoteEndPoint, m_sendBufferNumMessages, out connReset); + m_statistics.PacketSent(m_sendBufferWritePtr, m_sendBufferNumMessages); + m_sendBufferWritePtr = 0; + m_sendBufferNumMessages = 0; + } + + if (m_sendBufferWritePtr > 0) + m_peer.m_needFlushSendQueue = true; // flush in heartbeat + + Interlocked.Decrement(ref om.m_recyclingCount); + } + + /// + /// Send a message to this remote connection + /// + /// The message to send + /// How to deliver the message + /// Sequence channel within the delivery method + public NetSendResult SendMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + return m_peer.SendMessage(msg, this, method, sequenceChannel); + } + + // called by SendMessage() and NetPeer.SendMessage; ie. may be user thread + internal NetSendResult EnqueueMessage(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + if (m_status != NetConnectionStatus.Connected) + return NetSendResult.FailedNotConnected; + + NetMessageType tp = (NetMessageType)((int)method + sequenceChannel); + msg.m_messageType = tp; + + // TODO: do we need to make this more thread safe? + int channelSlot = (int)method - 1 + sequenceChannel; + NetSenderChannelBase chan = m_sendChannels[channelSlot]; + if (chan == null) + chan = CreateSenderChannel(tp); + + if ((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.UnreliableSequenced) && msg.GetEncodedSize() > m_currentMTU) + m_peer.ThrowOrLog("Reliable message too large! Fragmentation failure?"); + + var retval = chan.Enqueue(msg); + //if (retval == NetSendResult.Sent && m_peerConfiguration.m_autoFlushSendQueue == false) + // retval = NetSendResult.Queued; // queued since we're not autoflushing + return retval; + } + + // may be on user thread + private NetSenderChannelBase CreateSenderChannel(NetMessageType tp) + { + NetSenderChannelBase chan; + lock (m_sendChannels) + { + NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp); + int sequenceChannel = (int)tp - (int)method; + + int channelSlot = (int)method - 1 + sequenceChannel; + if (m_sendChannels[channelSlot] != null) + { + // we were pre-empted by another call to this method + chan = m_sendChannels[channelSlot]; + } + else + { + switch (method) + { + case NetDeliveryMethod.Unreliable: + case NetDeliveryMethod.UnreliableSequenced: + chan = new NetUnreliableSenderChannel(this, NetUtility.GetWindowSize(method), method); + break; + case NetDeliveryMethod.ReliableOrdered: + chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method)); + break; + case NetDeliveryMethod.ReliableSequenced: + case NetDeliveryMethod.ReliableUnordered: + default: + chan = new NetReliableSenderChannel(this, NetUtility.GetWindowSize(method)); + break; + } + m_sendChannels[channelSlot] = chan; + } + } + + return chan; + } + + // received a library message while Connected + internal void ReceivedLibraryMessage(NetMessageType tp, int ptr, int payloadLength) + { + m_peer.VerifyNetworkThread(); + + double now = NetTime.Now; + + switch (tp) + { + case NetMessageType.Connect: + m_peer.LogDebug("Received handshake message (" + tp + ") despite connection being in place"); + break; + + case NetMessageType.ConnectResponse: + // handshake message must have been lost + HandleConnectResponse(now, tp, ptr, payloadLength); + break; + + case NetMessageType.ConnectionEstablished: + // do nothing, all's well + break; + + case NetMessageType.LibraryError: + m_peer.ThrowOrLog("LibraryError received by ReceivedLibraryMessage; this usually indicates a malformed message"); + break; + + case NetMessageType.Disconnect: + NetIncomingMessage msg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + + m_disconnectRequested = true; + m_disconnectMessage = msg.ReadString(); + m_disconnectReqSendBye = false; + //ExecuteDisconnect(msg.ReadString(), false); + break; + case NetMessageType.Acknowledge: + for (int i = 0; i < payloadLength; i+=3) + { + NetMessageType acktp = (NetMessageType)m_peer.m_receiveBuffer[ptr++]; // netmessagetype + int seqNr = m_peer.m_receiveBuffer[ptr++]; + seqNr |= (m_peer.m_receiveBuffer[ptr++] << 8); + + // need to enqueue this and handle it in the netconnection heartbeat; so be able to send resends together with normal sends + m_queuedIncomingAcks.Enqueue(new NetTuple(acktp, seqNr)); + } + break; + case NetMessageType.Ping: + int pingNr = m_peer.m_receiveBuffer[ptr++]; + SendPong(pingNr); + break; + case NetMessageType.Pong: + NetIncomingMessage pmsg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + int pongNr = pmsg.ReadByte(); + float remoteSendTime = pmsg.ReadSingle(); + ReceivedPong(now, pongNr, remoteSendTime); + break; + case NetMessageType.ExpandMTURequest: + SendMTUSuccess(payloadLength); + break; + case NetMessageType.ExpandMTUSuccess: + if (m_peer.Configuration.AutoExpandMTU == false) + { + m_peer.LogDebug("Received ExpandMTURequest altho AutoExpandMTU is turned off!"); + break; + } + NetIncomingMessage emsg = m_peer.SetupReadHelperMessage(ptr, payloadLength); + int size = emsg.ReadInt32(); + HandleExpandMTUSuccess(now, size); + break; + case NetMessageType.NatIntroduction: + // Unusual situation where server is actually already known, but got a nat introduction - oh well, lets handle it as usual + m_peer.HandleNatIntroduction(ptr); + break; + default: + m_peer.LogWarning("Connection received unhandled library message: " + tp); + break; + } + } + + internal void ReceivedMessage(NetIncomingMessage msg) + { + m_peer.VerifyNetworkThread(); + + NetMessageType tp = msg.m_receivedMessageType; + + int channelSlot = (int)tp - 1; + NetReceiverChannelBase chan = m_receiveChannels[channelSlot]; + if (chan == null) + chan = CreateReceiverChannel(tp); + + chan.ReceiveMessage(msg); + } + + private NetReceiverChannelBase CreateReceiverChannel(NetMessageType tp) + { + m_peer.VerifyNetworkThread(); + + // create receiver channel + NetReceiverChannelBase chan; + NetDeliveryMethod method = NetUtility.GetDeliveryMethod(tp); + switch (method) + { + case NetDeliveryMethod.Unreliable: + chan = new NetUnreliableUnorderedReceiver(this); + break; + case NetDeliveryMethod.ReliableOrdered: + chan = new NetReliableOrderedReceiver(this, NetConstants.ReliableOrderedWindowSize); + break; + case NetDeliveryMethod.UnreliableSequenced: + chan = new NetUnreliableSequencedReceiver(this); + break; + case NetDeliveryMethod.ReliableUnordered: + chan = new NetReliableUnorderedReceiver(this, NetConstants.ReliableOrderedWindowSize); + break; + case NetDeliveryMethod.ReliableSequenced: + chan = new NetReliableSequencedReceiver(this, NetConstants.ReliableSequencedWindowSize); + break; + default: + throw new NetException("Unhandled NetDeliveryMethod!"); + } + + int channelSlot = (int)tp - 1; + NetException.Assert(m_receiveChannels[channelSlot] == null); + m_receiveChannels[channelSlot] = chan; + + return chan; + } + + internal void QueueAck(NetMessageType tp, int sequenceNumber) + { + m_queuedOutgoingAcks.Enqueue(new NetTuple(tp, sequenceNumber)); + } + + /// + /// Zero windowSize indicates that the channel is not yet instantiated (used) + /// Negative freeWindowSlots means this amount of messages are currently queued but delayed due to closed window + /// + public void GetSendQueueInfo(NetDeliveryMethod method, int sequenceChannel, out int windowSize, out int freeWindowSlots) + { + int channelSlot = (int)method - 1 + sequenceChannel; + var chan = m_sendChannels[channelSlot]; + if (chan == null) + { + windowSize = NetUtility.GetWindowSize(method); + freeWindowSlots = windowSize; + return; + } + + windowSize = chan.WindowSize; + freeWindowSlots = chan.GetFreeWindowSlots(); + return; + } + + public bool CanSendImmediately(NetDeliveryMethod method, int sequenceChannel) + { + int channelSlot = (int)method - 1 + sequenceChannel; + var chan = m_sendChannels[channelSlot]; + if (chan == null) + return true; + return chan.GetFreeWindowSlots() > 0; + } + + internal void Shutdown(string reason) + { + ExecuteDisconnect(reason, true); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetConnection to " + m_remoteEndPoint + "]"; + } + } +} diff --git a/SRMP/Lidgren.Network/NetConnectionStatistics.cs b/SRMP/Lidgren.Network/NetConnectionStatistics.cs new file mode 100644 index 0000000..7f31454 --- /dev/null +++ b/SRMP/Lidgren.Network/NetConnectionStatistics.cs @@ -0,0 +1,213 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +// Uncomment the line below to get statistics in RELEASE builds +//#define USE_RELEASE_STATISTICS + +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + internal enum MessageResendReason + { + Delay, + HoleInSequence + } + + /// + /// Statistics for a NetConnection instance + /// + public sealed class NetConnectionStatistics + { + private readonly NetConnection m_connection; + + internal long m_sentPackets; + internal long m_receivedPackets; + + internal long m_sentMessages; + internal long m_receivedMessages; + internal long m_droppedMessages; + internal long m_receivedFragments; + + internal long m_sentBytes; + internal long m_receivedBytes; + + internal long m_resentMessagesDueToDelay; + internal long m_resentMessagesDueToHole; + + internal NetConnectionStatistics(NetConnection conn) + { + m_connection = conn; + Reset(); + } + + internal void Reset() + { + m_sentPackets = 0; + m_receivedPackets = 0; + m_sentMessages = 0; + m_receivedMessages = 0; + m_receivedFragments = 0; + m_sentBytes = 0; + m_receivedBytes = 0; + m_resentMessagesDueToDelay = 0; + m_resentMessagesDueToHole = 0; + } + + /// + /// Gets the number of sent packets for this connection + /// + public long SentPackets { get { return m_sentPackets; } } + + /// + /// Gets the number of received packets for this connection + /// + public long ReceivedPackets { get { return m_receivedPackets; } } + + /// + /// Gets the number of sent bytes for this connection + /// + public long SentBytes { get { return m_sentBytes; } } + + /// + /// Gets the number of received bytes for this connection + /// + public long ReceivedBytes { get { return m_receivedBytes; } } + + /// + /// Gets the number of sent messages for this connection + /// + public long SentMessages { get { return m_sentMessages; } } + + /// + /// Gets the number of received messages for this connection + /// + public long ReceivedMessages { get { return m_receivedMessages; } } + + /// + /// Gets the number of resent reliable messages for this connection + /// + public long ResentMessages { get { return m_resentMessagesDueToHole + m_resentMessagesDueToDelay; } } + + /// + /// Gets the number of dropped messages for this connection + /// + public long DroppedMessages { get { return m_droppedMessages; } } + + // public double LastSendRespondedTo { get { return m_connection.m_lastSendRespondedTo; } } + +#if !USE_RELEASE_STATISTICS + [Conditional("DEBUG")] +#endif + internal void PacketSent(int numBytes, int numMessages) + { + NetException.Assert(numBytes > 0 && numMessages > 0); + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } + +#if !USE_RELEASE_STATISTICS + [Conditional("DEBUG")] +#endif + internal void PacketReceived(int numBytes, int numMessages, int numFragments) + { + NetException.Assert(numBytes > 0 && numMessages > 0); + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + m_receivedFragments += numFragments; + } + +#if !USE_RELEASE_STATISTICS + [Conditional("DEBUG")] +#endif + internal void MessageResent(MessageResendReason reason) + { + if (reason == MessageResendReason.Delay) + m_resentMessagesDueToDelay++; + else + m_resentMessagesDueToHole++; + } + +#if !USE_RELEASE_STATISTICS + [Conditional("DEBUG")] +#endif + internal void MessageDropped() + { + m_droppedMessages++; + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + StringBuilder bdr = new StringBuilder(); + //bdr.AppendLine("Average roundtrip time: " + NetTime.ToReadable(m_connection.m_averageRoundtripTime)); + bdr.AppendLine("Current MTU: " + m_connection.m_currentMTU); + bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); + bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets"); + bdr.AppendLine("Dropped " + m_droppedMessages + " messages (dupes/late/early)"); + + if (m_resentMessagesDueToDelay > 0) + bdr.AppendLine("Resent messages (delay): " + m_resentMessagesDueToDelay); + if (m_resentMessagesDueToHole > 0) + bdr.AppendLine("Resent messages (holes): " + m_resentMessagesDueToHole); + + int numUnsent = 0; + int numStored = 0; + foreach (NetSenderChannelBase sendChan in m_connection.m_sendChannels) + { + if (sendChan == null) + continue; + numUnsent += sendChan.QueuedSendsCount; + + var relSendChan = sendChan as NetReliableSenderChannel; + if (relSendChan != null) + { + for (int i = 0; i < relSendChan.m_storedMessages.Length; i++) + if (relSendChan.m_storedMessages[i].Message != null) + numStored++; + } + } + + int numWithheld = 0; + foreach (NetReceiverChannelBase recChan in m_connection.m_receiveChannels) + { + var relRecChan = recChan as NetReliableOrderedReceiver; + if (relRecChan != null) + { + for (int i = 0; i < relRecChan.m_withheldMessages.Length; i++) + if (relRecChan.m_withheldMessages[i] != null) + numWithheld++; + } + } + + bdr.AppendLine("Unsent messages: " + numUnsent); + bdr.AppendLine("Stored messages: " + numStored); + bdr.AppendLine("Withheld messages: " + numWithheld); + + return bdr.ToString(); + } + } +} \ No newline at end of file diff --git a/SRMP/Lidgren.Network/NetConnectionStatus.cs b/SRMP/Lidgren.Network/NetConnectionStatus.cs new file mode 100644 index 0000000..15c1937 --- /dev/null +++ b/SRMP/Lidgren.Network/NetConnectionStatus.cs @@ -0,0 +1,68 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + /// + /// Status for a NetConnection instance + /// + public enum NetConnectionStatus + { + /// + /// No connection, or attempt, in place + /// + None, + + /// + /// Connect has been sent; waiting for ConnectResponse + /// + InitiatedConnect, + + /// + /// Connect was received, but ConnectResponse hasn't been sent yet + /// + ReceivedInitiation, + + /// + /// Connect was received and ApprovalMessage released to the application; awaiting Approve() or Deny() + /// + RespondedAwaitingApproval, // We got Connect, released ApprovalMessage + + /// + /// Connect was received and ConnectResponse has been sent; waiting for ConnectionEstablished + /// + RespondedConnect, // we got Connect, sent ConnectResponse + + /// + /// Connected + /// + Connected, // we received ConnectResponse (if initiator) or ConnectionEstablished (if passive) + + /// + /// In the process of disconnecting + /// + Disconnecting, + + /// + /// Disconnected + /// + Disconnected + } +} diff --git a/SRMP/Lidgren.Network/NetConstants.cs b/SRMP/Lidgren.Network/NetConstants.cs new file mode 100644 index 0000000..a5a0c37 --- /dev/null +++ b/SRMP/Lidgren.Network/NetConstants.cs @@ -0,0 +1,57 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + /// + /// All the constants used when compiling the library + /// + internal static class NetConstants + { + internal const int NumTotalChannels = 99; + + internal const int NetChannelsPerDeliveryMethod = 32; + + internal const int NumSequenceNumbers = 1024; + + internal const int HeaderByteSize = 5; + + internal const int UnreliableWindowSize = 128; + internal const int ReliableOrderedWindowSize = 64; + internal const int ReliableSequencedWindowSize = 64; + internal const int DefaultWindowSize = 64; + + internal const int MaxFragmentationGroups = ushort.MaxValue - 1; + + internal const int UnfragmentedMessageHeaderSize = 5; + + /// + /// Number of channels which needs a sequence number to work + /// + internal const int NumSequencedChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserSequenced1; + + /// + /// Number of reliable channels + /// + internal const int NumReliableChannels = ((int)NetMessageType.UserReliableOrdered1 + NetConstants.NetChannelsPerDeliveryMethod) - (int)NetMessageType.UserReliableUnordered; + + internal const string ConnResetMessage = "Connection was reset by remote host"; + } +} diff --git a/SRMP/Lidgren.Network/NetDeliveryMethod.cs b/SRMP/Lidgren.Network/NetDeliveryMethod.cs new file mode 100644 index 0000000..d04266d --- /dev/null +++ b/SRMP/Lidgren.Network/NetDeliveryMethod.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// How the library deals with resends and handling of late messages + /// + public enum NetDeliveryMethod : byte + { + // + // Actually a publicly visible subset of NetMessageType + // + + /// + /// Indicates an error + /// + Unknown = 0, + + /// + /// Unreliable, unordered delivery + /// + Unreliable = 1, + + /// + /// Unreliable delivery, but automatically dropping late messages + /// + UnreliableSequenced = 2, + + /// + /// Reliable delivery, but unordered + /// + ReliableUnordered = 34, + + /// + /// Reliable delivery, except for late messages which are dropped + /// + ReliableSequenced = 35, + + /// + /// Reliable, ordered delivery + /// + ReliableOrdered = 67, + } +} diff --git a/SRMP/Lidgren.Network/NetException.cs b/SRMP/Lidgren.Network/NetException.cs new file mode 100644 index 0000000..41a50c7 --- /dev/null +++ b/SRMP/Lidgren.Network/NetException.cs @@ -0,0 +1,74 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Diagnostics; +using System.Runtime.Serialization; + +namespace Lidgren.Network +{ + /// + /// Exception thrown in the Lidgren Network Library + /// + public sealed class NetException : Exception + { + /// + /// NetException constructor + /// + public NetException() + : base() + { + } + + /// + /// NetException constructor + /// + public NetException(string message) + : base(message) + { + } + + /// + /// NetException constructor + /// + public NetException(string message, Exception inner) + : base(message, inner) + { + } + + /// + /// Throws an exception, in DEBUG only, if first parameter is false + /// + [Conditional("DEBUG")] + public static void Assert(bool isOk, string message) + { + if (!isOk) + throw new NetException(message); + } + + /// + /// Throws an exception, in DEBUG only, if first parameter is false + /// + [Conditional("DEBUG")] + public static void Assert(bool isOk) + { + if (!isOk) + throw new NetException(); + } + } +} diff --git a/SRMP/Lidgren.Network/NetFragmentationHelper.cs b/SRMP/Lidgren.Network/NetFragmentationHelper.cs new file mode 100644 index 0000000..85efe4d --- /dev/null +++ b/SRMP/Lidgren.Network/NetFragmentationHelper.cs @@ -0,0 +1,175 @@ +using System; + +namespace Lidgren.Network +{ + internal static class NetFragmentationHelper + { + internal static int WriteHeader( + byte[] destination, + int ptr, + int group, + int totalBits, + int chunkByteSize, + int chunkNumber) + { + uint num1 = (uint)group; + while (num1 >= 0x80) + { + destination[ptr++] = (byte)(num1 | 0x80); + num1 = num1 >> 7; + } + destination[ptr++] = (byte)num1; + + // write variable length fragment total bits + uint num2 = (uint)totalBits; + while (num2 >= 0x80) + { + destination[ptr++] = (byte)(num2 | 0x80); + num2 = num2 >> 7; + } + destination[ptr++] = (byte)num2; + + // write variable length fragment chunk size + uint num3 = (uint)chunkByteSize; + while (num3 >= 0x80) + { + destination[ptr++] = (byte)(num3 | 0x80); + num3 = num3 >> 7; + } + destination[ptr++] = (byte)num3; + + // write variable length fragment chunk number + uint num4 = (uint)chunkNumber; + while (num4 >= 0x80) + { + destination[ptr++] = (byte)(num4 | 0x80); + num4 = num4 >> 7; + } + destination[ptr++] = (byte)num4; + + return ptr; + } + + internal static int ReadHeader(byte[] buffer, int ptr, out int group, out int totalBits, out int chunkByteSize, out int chunkNumber) + { + int num1 = 0; + int num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + group = num1; + break; + } + } + + num1 = 0; + num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + totalBits = num1; + break; + } + } + + num1 = 0; + num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + chunkByteSize = num1; + break; + } + } + + num1 = 0; + num2 = 0; + while (true) + { + byte num3 = buffer[ptr++]; + num1 |= (num3 & 0x7f) << (num2 & 0x1f); + num2 += 7; + if ((num3 & 0x80) == 0) + { + chunkNumber = num1; + break; + } + } + + return ptr; + } + + internal static int GetFragmentationHeaderSize(int groupId, int totalBytes, int chunkByteSize, int numChunks) + { + int len = 4; + + // write variable length fragment group id + uint num1 = (uint)groupId; + while (num1 >= 0x80) + { + len++; + num1 = num1 >> 7; + } + + // write variable length fragment total bits + uint num2 = (uint)(totalBytes * 8); + while (num2 >= 0x80) + { + len++; + num2 = num2 >> 7; + } + + // write variable length fragment chunk byte size + uint num3 = (uint)chunkByteSize; + while (num3 >= 0x80) + { + len++; + num3 = num3 >> 7; + } + + // write variable length fragment chunk number + uint num4 = (uint)numChunks; + while (num4 >= 0x80) + { + len++; + num4 = num4 >> 7; + } + + return len; + } + + internal static int GetBestChunkSize(int group, int totalBytes, int mtu) + { + int tryChunkSize = mtu - NetConstants.HeaderByteSize - 4; // naive approximation + int est = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, totalBytes / tryChunkSize); + tryChunkSize = mtu - NetConstants.HeaderByteSize - est; // slightly less naive approximation + + int headerSize = 0; + do + { + tryChunkSize--; // keep reducing chunk size until it fits within MTU including header + + int numChunks = totalBytes / tryChunkSize; + if (numChunks * tryChunkSize < totalBytes) + numChunks++; + + headerSize = GetFragmentationHeaderSize(group, totalBytes, tryChunkSize, numChunks); // 4+ bytes + + } while (tryChunkSize + headerSize + NetConstants.HeaderByteSize + 1 >= mtu); + + return tryChunkSize; + } + } +} diff --git a/SRMP/Lidgren.Network/NetFragmentationInfo.cs b/SRMP/Lidgren.Network/NetFragmentationInfo.cs new file mode 100644 index 0000000..2c8b70e --- /dev/null +++ b/SRMP/Lidgren.Network/NetFragmentationInfo.cs @@ -0,0 +1,12 @@ +using System; + +namespace Lidgren.Network +{ + public sealed class NetFragmentationInfo + { + public int TotalFragmentCount; + public bool[] Received; + public int TotalReceived; + public int FragmentSize; + } +} diff --git a/SRMP/Lidgren.Network/NetIncomingMessage.cs b/SRMP/Lidgren.Network/NetIncomingMessage.cs new file mode 100644 index 0000000..0485c35 --- /dev/null +++ b/SRMP/Lidgren.Network/NetIncomingMessage.cs @@ -0,0 +1,119 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; +using System.Net; +using System.Diagnostics; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + /// + /// Incoming message either sent from a remote peer or generated within the library + /// + [DebuggerDisplay("Type={MessageType} LengthBits={LengthBits}")] + public sealed class NetIncomingMessage : NetBuffer + { + internal NetIncomingMessageType m_incomingMessageType; + internal NetEndPoint m_senderEndPoint; + internal NetConnection m_senderConnection; + internal int m_sequenceNumber; + internal NetMessageType m_receivedMessageType; + internal bool m_isFragment; + internal double m_receiveTime; + + /// + /// Gets the type of this incoming message + /// + public NetIncomingMessageType MessageType { get { return m_incomingMessageType; } } + + /// + /// Gets the delivery method this message was sent with (if user data) + /// + public NetDeliveryMethod DeliveryMethod { get { return NetUtility.GetDeliveryMethod(m_receivedMessageType); } } + + /// + /// Gets the sequence channel this message was sent with (if user data) + /// + public int SequenceChannel { get { return (int)m_receivedMessageType - (int)NetUtility.GetDeliveryMethod(m_receivedMessageType); } } + + /// + /// endpoint of sender, if any + /// + public NetEndPoint SenderEndPoint { get { return m_senderEndPoint; } } + + /// + /// NetConnection of sender, if any + /// + public NetConnection SenderConnection { get { return m_senderConnection; } } + + /// + /// What local time the message was received from the network + /// + public double ReceiveTime { get { return m_receiveTime; } } + + internal NetIncomingMessage() + { + } + + internal NetIncomingMessage(NetIncomingMessageType tp) + { + m_incomingMessageType = tp; + } + + internal void Reset() + { + m_incomingMessageType = NetIncomingMessageType.Error; + m_readPosition = 0; + m_receivedMessageType = NetMessageType.LibraryError; + m_senderConnection = null; + m_bitLength = 0; + m_isFragment = false; + } + + /// + /// Decrypt a message + /// + /// The encryption algorithm used to encrypt the message + /// true on success + public bool Decrypt(NetEncryption encryption) + { + return encryption.Decrypt(this); + } + + /// + /// Reads a value, in local time comparable to NetTime.Now, written using WriteTime() + /// Must have a connected sender + /// + public double ReadTime(bool highPrecision) + { + return ReadTime(m_senderConnection, highPrecision); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetIncomingMessage #" + m_sequenceNumber + " " + this.LengthBytes + " bytes]"; + } + } +} diff --git a/SRMP/Lidgren.Network/NetIncomingMessageType.cs b/SRMP/Lidgren.Network/NetIncomingMessageType.cs new file mode 100644 index 0000000..73f4ae7 --- /dev/null +++ b/SRMP/Lidgren.Network/NetIncomingMessageType.cs @@ -0,0 +1,105 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Lidgren.Network +{ + /// + /// The type of a NetIncomingMessage + /// + [SuppressMessage("Microsoft.Design", "CA1027:MarkEnumsWithFlags")] + public enum NetIncomingMessageType + { + // + // library note: values are power-of-two, but they are not flags - it's a convenience for NetPeerConfiguration.DisabledMessageTypes + // + + /// + /// Error; this value should never appear + /// + Error = 0, + + /// + /// Status for a connection changed + /// + StatusChanged = 1 << 0, // Data (string) + + /// + /// Data sent using SendUnconnectedMessage + /// + UnconnectedData = 1 << 1, // Data Based on data received + + /// + /// Connection approval is needed + /// + ConnectionApproval = 1 << 2, // Data + + /// + /// Application data + /// + Data = 1 << 3, // Data Based on data received + + /// + /// Receipt of delivery + /// + Receipt = 1 << 4, // Data + + /// + /// Discovery request for a response + /// + DiscoveryRequest = 1 << 5, // (no data) + + /// + /// Discovery response to a request + /// + DiscoveryResponse = 1 << 6, // Data + + /// + /// Verbose debug message + /// + VerboseDebugMessage = 1 << 7, // Data (string) + + /// + /// Debug message + /// + DebugMessage = 1 << 8, // Data (string) + + /// + /// Warning message + /// + WarningMessage = 1 << 9, // Data (string) + + /// + /// Error message + /// + ErrorMessage = 1 << 10, // Data (string) + + /// + /// NAT introduction was successful + /// + NatIntroductionSuccess = 1 << 11, // Data (as passed to master server) + + /// + /// A roundtrip was measured and NetConnection.AverageRoundtripTime was updated + /// + ConnectionLatencyUpdated = 1 << 12, // Seconds as a Single + } +} diff --git a/SRMP/Lidgren.Network/NetMessageType.cs b/SRMP/Lidgren.Network/NetMessageType.cs new file mode 100644 index 0000000..90b1ea7 --- /dev/null +++ b/SRMP/Lidgren.Network/NetMessageType.cs @@ -0,0 +1,177 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System; + +namespace Lidgren.Network +{ + internal enum NetMessageType : byte + { + Unconnected = 0, + + UserUnreliable = 1, + + UserSequenced1 = 2, + UserSequenced2 = 3, + UserSequenced3 = 4, + UserSequenced4 = 5, + UserSequenced5 = 6, + UserSequenced6 = 7, + UserSequenced7 = 8, + UserSequenced8 = 9, + UserSequenced9 = 10, + UserSequenced10 = 11, + UserSequenced11 = 12, + UserSequenced12 = 13, + UserSequenced13 = 14, + UserSequenced14 = 15, + UserSequenced15 = 16, + UserSequenced16 = 17, + UserSequenced17 = 18, + UserSequenced18 = 19, + UserSequenced19 = 20, + UserSequenced20 = 21, + UserSequenced21 = 22, + UserSequenced22 = 23, + UserSequenced23 = 24, + UserSequenced24 = 25, + UserSequenced25 = 26, + UserSequenced26 = 27, + UserSequenced27 = 28, + UserSequenced28 = 29, + UserSequenced29 = 30, + UserSequenced30 = 31, + UserSequenced31 = 32, + UserSequenced32 = 33, + + UserReliableUnordered = 34, + + UserReliableSequenced1 = 35, + UserReliableSequenced2 = 36, + UserReliableSequenced3 = 37, + UserReliableSequenced4 = 38, + UserReliableSequenced5 = 39, + UserReliableSequenced6 = 40, + UserReliableSequenced7 = 41, + UserReliableSequenced8 = 42, + UserReliableSequenced9 = 43, + UserReliableSequenced10 = 44, + UserReliableSequenced11 = 45, + UserReliableSequenced12 = 46, + UserReliableSequenced13 = 47, + UserReliableSequenced14 = 48, + UserReliableSequenced15 = 49, + UserReliableSequenced16 = 50, + UserReliableSequenced17 = 51, + UserReliableSequenced18 = 52, + UserReliableSequenced19 = 53, + UserReliableSequenced20 = 54, + UserReliableSequenced21 = 55, + UserReliableSequenced22 = 56, + UserReliableSequenced23 = 57, + UserReliableSequenced24 = 58, + UserReliableSequenced25 = 59, + UserReliableSequenced26 = 60, + UserReliableSequenced27 = 61, + UserReliableSequenced28 = 62, + UserReliableSequenced29 = 63, + UserReliableSequenced30 = 64, + UserReliableSequenced31 = 65, + UserReliableSequenced32 = 66, + + UserReliableOrdered1 = 67, + UserReliableOrdered2 = 68, + UserReliableOrdered3 = 69, + UserReliableOrdered4 = 70, + UserReliableOrdered5 = 71, + UserReliableOrdered6 = 72, + UserReliableOrdered7 = 73, + UserReliableOrdered8 = 74, + UserReliableOrdered9 = 75, + UserReliableOrdered10 = 76, + UserReliableOrdered11 = 77, + UserReliableOrdered12 = 78, + UserReliableOrdered13 = 79, + UserReliableOrdered14 = 80, + UserReliableOrdered15 = 81, + UserReliableOrdered16 = 82, + UserReliableOrdered17 = 83, + UserReliableOrdered18 = 84, + UserReliableOrdered19 = 85, + UserReliableOrdered20 = 86, + UserReliableOrdered21 = 87, + UserReliableOrdered22 = 88, + UserReliableOrdered23 = 89, + UserReliableOrdered24 = 90, + UserReliableOrdered25 = 91, + UserReliableOrdered26 = 92, + UserReliableOrdered27 = 93, + UserReliableOrdered28 = 94, + UserReliableOrdered29 = 95, + UserReliableOrdered30 = 96, + UserReliableOrdered31 = 97, + UserReliableOrdered32 = 98, + + Unused1 = 99, + Unused2 = 100, + Unused3 = 101, + Unused4 = 102, + Unused5 = 103, + Unused6 = 104, + Unused7 = 105, + Unused8 = 106, + Unused9 = 107, + Unused10 = 108, + Unused11 = 109, + Unused12 = 110, + Unused13 = 111, + Unused14 = 112, + Unused15 = 113, + Unused16 = 114, + Unused17 = 115, + Unused18 = 116, + Unused19 = 117, + Unused20 = 118, + Unused21 = 119, + Unused22 = 120, + Unused23 = 121, + Unused24 = 122, + Unused25 = 123, + Unused26 = 124, + Unused27 = 125, + Unused28 = 126, + Unused29 = 127, + + LibraryError = 128, + Ping = 129, // used for RTT calculation + Pong = 130, // used for RTT calculation + Connect = 131, + ConnectResponse = 132, + ConnectionEstablished = 133, + Acknowledge = 134, + Disconnect = 135, + Discovery = 136, + DiscoveryResponse = 137, + NatPunchMessage = 138, // send between peers + NatIntroduction = 139, // send to master server + NatIntroductionConfirmRequest = 142, + NatIntroductionConfirmed = 143, + ExpandMTURequest = 140, + ExpandMTUSuccess = 141, + } +} diff --git a/SRMP/Lidgren.Network/NetNatIntroduction.cs b/SRMP/Lidgren.Network/NetNatIntroduction.cs new file mode 100644 index 0000000..3990c60 --- /dev/null +++ b/SRMP/Lidgren.Network/NetNatIntroduction.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + public partial class NetPeer { + private const byte HostByte = 1; + private const byte ClientByte = 0; + + /// + /// Send NetIntroduction to hostExternal and clientExternal; introducing client to host + /// + public void Introduce( + NetEndPoint hostInternal, + NetEndPoint hostExternal, + NetEndPoint clientInternal, + NetEndPoint clientExternal, + string token) + { + // send message to client + NetOutgoingMessage um = CreateMessage(10 + token.Length + 1); + um.m_messageType = NetMessageType.NatIntroduction; + um.Write((byte)0); + um.Write(hostInternal); + um.Write(hostExternal); + um.Write(token); + Interlocked.Increment(ref um.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(clientExternal, um)); + + // send message to host + um = CreateMessage(10 + token.Length + 1); + um.m_messageType = NetMessageType.NatIntroduction; + um.Write((byte)1); + um.Write(clientInternal); + um.Write(clientExternal); + um.Write(token); + Interlocked.Increment(ref um.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(hostExternal, um)); + } + + /// + /// Called when host/client receives a NatIntroduction message from a master server + /// + internal void HandleNatIntroduction(int ptr) + { + VerifyNetworkThread(); + + // read intro + NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length + + byte hostByte = tmp.ReadByte(); + NetEndPoint remoteInternal = tmp.ReadIPEndPoint(); + NetEndPoint remoteExternal = tmp.ReadIPEndPoint(); + string token = tmp.ReadString(); + bool isHost = (hostByte != 0); + + LogDebug("NAT introduction received; we are designated " + (isHost ? "host" : "client")); + + NetOutgoingMessage punch; + + if (!isHost && m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess) == false) + return; // no need to punch - we're not listening for nat intros! + + // send internal punch + punch = CreateMessage(1); + punch.m_messageType = NetMessageType.NatPunchMessage; + punch.Write(hostByte); + punch.Write(token); + Interlocked.Increment(ref punch.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(remoteInternal, punch)); + LogDebug("NAT punch sent to " + remoteInternal); + + // send external punch + punch = CreateMessage(1); + punch.m_messageType = NetMessageType.NatPunchMessage; + punch.Write(hostByte); + punch.Write(token); + Interlocked.Increment(ref punch.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(remoteExternal, punch)); + LogDebug("NAT punch sent to " + remoteExternal); + + } + + /// + /// Called when receiving a NatPunchMessage from a remote endpoint + /// + private void HandleNatPunch(int ptr, NetEndPoint senderEndPoint) + { + NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length + + var isFromClient = tmp.ReadByte() == ClientByte; + string token = tmp.ReadString(); + if (isFromClient) + { + LogDebug("NAT punch received from " + senderEndPoint + " we're host, so we send a NatIntroductionConfirmed message - token is " + token); + + var confirmResponse = CreateMessage(1); + confirmResponse.m_messageType = NetMessageType.NatIntroductionConfirmed; + confirmResponse.Write(HostByte); + confirmResponse.Write(token); + Interlocked.Increment(ref confirmResponse.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(senderEndPoint, confirmResponse)); + } + else + { + LogDebug("NAT punch received from " + senderEndPoint + " we're client, so we send a NatIntroductionConfirmRequest - token is " + token); + + var confirmRequest = CreateMessage(1); + confirmRequest.m_messageType = NetMessageType.NatIntroductionConfirmRequest; + confirmRequest.Write(ClientByte); + confirmRequest.Write(token); + Interlocked.Increment(ref confirmRequest.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(senderEndPoint, confirmRequest)); + } + } + + private void HandleNatPunchConfirmRequest(int ptr, NetEndPoint senderEndPoint) + { + NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length + var isFromClient = tmp.ReadByte() == ClientByte; + string token = tmp.ReadString(); + + LogDebug("Received NAT punch confirmation from " + senderEndPoint + " sending NatIntroductionConfirmed - token is " + token); + + var confirmResponse = CreateMessage(1); + confirmResponse.m_messageType = NetMessageType.NatIntroductionConfirmed; + confirmResponse.Write(isFromClient ? HostByte : ClientByte); + confirmResponse.Write(token); + Interlocked.Increment(ref confirmResponse.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(senderEndPoint, confirmResponse)); + } + + private void HandleNatPunchConfirmed(int ptr, NetEndPoint senderEndPoint) + { + NetIncomingMessage tmp = SetupReadHelperMessage(ptr, 1000); // never mind length + var isFromClient = tmp.ReadByte() == ClientByte; + if (isFromClient) + { + LogDebug("NAT punch confirmation received from " + senderEndPoint + " we're host, so we ignore this"); + return; + } + + string token = tmp.ReadString(); + + LogDebug("NAT punch confirmation received from " + senderEndPoint + " we're client so we go ahead and succeed the introduction"); + + // + // Release punch success to client; enabling him to Connect() to msg.Sender if token is ok + // + NetIncomingMessage punchSuccess = CreateIncomingMessage(NetIncomingMessageType.NatIntroductionSuccess, 10); + punchSuccess.m_senderEndPoint = senderEndPoint; + punchSuccess.Write(token); + ReleaseMessage(punchSuccess); + } + } +} diff --git a/SRMP/Lidgren.Network/NetOutgoingMessage.cs b/SRMP/Lidgren.Network/NetOutgoingMessage.cs new file mode 100644 index 0000000..d982cfd --- /dev/null +++ b/SRMP/Lidgren.Network/NetOutgoingMessage.cs @@ -0,0 +1,142 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Outgoing message used to send data to remote peer(s) + /// + [DebuggerDisplay("LengthBits={LengthBits}")] + public sealed class NetOutgoingMessage : NetBuffer + { + internal NetMessageType m_messageType; + internal bool m_isSent; + + // Recycling count is: + // * incremented for each recipient on send + // * incremented, when reliable, in SenderChannel.ExecuteSend() + // * decremented (both reliable and unreliable) in NetConnection.QueueSendMessage() + // * decremented, when reliable, in SenderChannel.DestoreMessage() + // ... when it reaches zero it can be recycled + internal int m_recyclingCount; + + internal int m_fragmentGroup; // which group of fragments ths belongs to + internal int m_fragmentGroupTotalBits; // total number of bits in this group + internal int m_fragmentChunkByteSize; // size, in bytes, of every chunk but the last one + internal int m_fragmentChunkNumber; // which number chunk this is, starting with 0 + + internal NetOutgoingMessage() + { + } + + internal void Reset() + { + m_messageType = NetMessageType.LibraryError; + m_bitLength = 0; + m_isSent = false; + NetException.Assert(m_recyclingCount == 0); + m_fragmentGroup = 0; + } + + internal int Encode(byte[] intoBuffer, int ptr, int sequenceNumber) + { + // 8 bits - NetMessageType + // 1 bit - Fragment? + // 15 bits - Sequence number + // 16 bits - Payload length in bits + + intoBuffer[ptr++] = (byte)m_messageType; + + byte low = (byte)((sequenceNumber << 1) | (m_fragmentGroup == 0 ? 0 : 1)); + intoBuffer[ptr++] = low; + intoBuffer[ptr++] = (byte)(sequenceNumber >> 7); + + if (m_fragmentGroup == 0) + { + intoBuffer[ptr++] = (byte)m_bitLength; + intoBuffer[ptr++] = (byte)(m_bitLength >> 8); + + int byteLen = NetUtility.BytesToHoldBits(m_bitLength); + if (byteLen > 0) + { + Buffer.BlockCopy(m_data, 0, intoBuffer, ptr, byteLen); + ptr += byteLen; + } + } + else + { + int wasPtr = ptr; + intoBuffer[ptr++] = (byte)m_bitLength; + intoBuffer[ptr++] = (byte)(m_bitLength >> 8); + + // + // write fragmentation header + // + ptr = NetFragmentationHelper.WriteHeader(intoBuffer, ptr, m_fragmentGroup, m_fragmentGroupTotalBits, m_fragmentChunkByteSize, m_fragmentChunkNumber); + int hdrLen = ptr - wasPtr - 2; + + // update length + int realBitLength = m_bitLength + (hdrLen * 8); + intoBuffer[wasPtr] = (byte)realBitLength; + intoBuffer[wasPtr + 1] = (byte)(realBitLength >> 8); + + int byteLen = NetUtility.BytesToHoldBits(m_bitLength); + if (byteLen > 0) + { + Buffer.BlockCopy(m_data, (int)(m_fragmentChunkNumber * m_fragmentChunkByteSize), intoBuffer, ptr, byteLen); + ptr += byteLen; + } + } + + NetException.Assert(ptr > 0); + return ptr; + } + + internal int GetEncodedSize() + { + int retval = NetConstants.UnfragmentedMessageHeaderSize; // regular headers + if (m_fragmentGroup != 0) + retval += NetFragmentationHelper.GetFragmentationHeaderSize(m_fragmentGroup, m_fragmentGroupTotalBits / 8, m_fragmentChunkByteSize, m_fragmentChunkNumber); + retval += this.LengthBytes; + return retval; + } + + /// + /// Encrypt this message using the provided algorithm; no more writing can be done before sending it or the message will be corrupt! + /// + public bool Encrypt(NetEncryption encryption) + { + return encryption.Encrypt(this); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + if (m_isSent) + return "[NetOutgoingMessage " + m_messageType + " " + this.LengthBytes + " bytes]"; + + return "[NetOutgoingMessage " + this.LengthBytes + " bytes]"; + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.Discovery.cs b/SRMP/Lidgren.Network/NetPeer.Discovery.cs new file mode 100644 index 0000000..de71736 --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.Discovery.cs @@ -0,0 +1,69 @@ +using System; +using System.Net; +using System.Threading; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + public partial class NetPeer + { + /// + /// Emit a discovery signal to all hosts on your subnet + /// + public void DiscoverLocalPeers(int serverPort) + { + NetOutgoingMessage um = CreateMessage(0); + um.m_messageType = NetMessageType.Discovery; + Interlocked.Increment(ref um.m_recyclingCount); + + m_unsentUnconnectedMessages.Enqueue(new NetTuple(new NetEndPoint(NetUtility.GetBroadcastAddress(), serverPort), um)); + } + + /// + /// Emit a discovery signal to a single known host + /// + public bool DiscoverKnownPeer(string host, int serverPort) + { + var address = NetUtility.Resolve(host); + if (address == null) + return false; + DiscoverKnownPeer(new NetEndPoint(address, serverPort)); + return true; + } + + /// + /// Emit a discovery signal to a single known host + /// + public void DiscoverKnownPeer(NetEndPoint endPoint) + { + NetOutgoingMessage om = CreateMessage(0); + om.m_messageType = NetMessageType.Discovery; + om.m_recyclingCount = 1; + m_unsentUnconnectedMessages.Enqueue(new NetTuple(endPoint, om)); + } + + /// + /// Send a discovery response message + /// + public void SendDiscoveryResponse(NetOutgoingMessage msg, NetEndPoint recipient) + { + if (recipient == null) + throw new ArgumentNullException("recipient"); + + if (msg == null) + msg = CreateMessage(0); + else if (msg.m_isSent) + throw new NetException("Message has already been sent!"); + + if (msg.LengthBytes >= m_configuration.MaximumTransmissionUnit) + throw new NetException("Cannot send discovery message larger than MTU (currently " + m_configuration.MaximumTransmissionUnit + " bytes)"); + + msg.m_messageType = NetMessageType.DiscoveryResponse; + Interlocked.Increment(ref msg.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(recipient, msg)); + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.Fragmentation.cs b/SRMP/Lidgren.Network/NetPeer.Fragmentation.cs new file mode 100644 index 0000000..2628b8e --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.Fragmentation.cs @@ -0,0 +1,169 @@ +using System; +using System.Threading; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + internal class ReceivedFragmentGroup + { + //public float LastReceived; + public byte[] Data; + public NetBitVector ReceivedChunks; + } + + public partial class NetPeer + { + private int m_lastUsedFragmentGroup; + + private Dictionary> m_receivedFragmentGroups; + + // on user thread + private NetSendResult SendFragmentedMessage(NetOutgoingMessage msg, IList recipients, NetDeliveryMethod method, int sequenceChannel) + { + // Note: this group id is PER SENDING/NetPeer; ie. same id is sent to all recipients; + // this should be ok however; as long as recipients differentiate between same id but different sender + int group = Interlocked.Increment(ref m_lastUsedFragmentGroup); + if (group >= NetConstants.MaxFragmentationGroups) + { + // @TODO: not thread safe; but in practice probably not an issue + m_lastUsedFragmentGroup = 1; + group = 1; + } + msg.m_fragmentGroup = group; + + // do not send msg; but set fragmentgroup in case user tries to recycle it immediately + + // create fragmentation specifics + int totalBytes = msg.LengthBytes; + + // determine minimum mtu for all recipients + int mtu = GetMTU(recipients); + int bytesPerChunk = NetFragmentationHelper.GetBestChunkSize(group, totalBytes, mtu); + + int numChunks = totalBytes / bytesPerChunk; + if (numChunks * bytesPerChunk < totalBytes) + numChunks++; + + NetSendResult retval = NetSendResult.Sent; + + int bitsPerChunk = bytesPerChunk * 8; + int bitsLeft = msg.LengthBits; + for (int i = 0; i < numChunks; i++) + { + NetOutgoingMessage chunk = CreateMessage(0); + + chunk.m_bitLength = (bitsLeft > bitsPerChunk ? bitsPerChunk : bitsLeft); + chunk.m_data = msg.m_data; + chunk.m_fragmentGroup = group; + chunk.m_fragmentGroupTotalBits = totalBytes * 8; + chunk.m_fragmentChunkByteSize = bytesPerChunk; + chunk.m_fragmentChunkNumber = i; + + NetException.Assert(chunk.m_bitLength != 0); + NetException.Assert(chunk.GetEncodedSize() < mtu); + + Interlocked.Add(ref chunk.m_recyclingCount, recipients.Count); + + foreach (NetConnection recipient in recipients) + { + var res = recipient.EnqueueMessage(chunk, method, sequenceChannel); + if (res == NetSendResult.Dropped) + Interlocked.Decrement(ref chunk.m_recyclingCount); + if ((int)res > (int)retval) + retval = res; // return "worst" result + } + + bitsLeft -= bitsPerChunk; + } + + return retval; + } + + private void HandleReleasedFragment(NetIncomingMessage im) + { + VerifyNetworkThread(); + + // + // read fragmentation header and combine fragments + // + int group; + int totalBits; + int chunkByteSize; + int chunkNumber; + int ptr = NetFragmentationHelper.ReadHeader( + im.m_data, 0, + out group, + out totalBits, + out chunkByteSize, + out chunkNumber + ); + + NetException.Assert(im.LengthBytes > ptr); + + NetException.Assert(group > 0); + NetException.Assert(totalBits > 0); + NetException.Assert(chunkByteSize > 0); + + int totalBytes = NetUtility.BytesToHoldBits((int)totalBits); + int totalNumChunks = totalBytes / chunkByteSize; + if (totalNumChunks * chunkByteSize < totalBytes) + totalNumChunks++; + + NetException.Assert(chunkNumber < totalNumChunks); + + if (chunkNumber >= totalNumChunks) + { + LogWarning("Index out of bounds for chunk " + chunkNumber + " (total chunks " + totalNumChunks + ")"); + return; + } + + Dictionary groups; + if (!m_receivedFragmentGroups.TryGetValue(im.SenderConnection, out groups)) + { + groups = new Dictionary(); + m_receivedFragmentGroups[im.SenderConnection] = groups; + } + + ReceivedFragmentGroup info; + if (!groups.TryGetValue(group, out info)) + { + info = new ReceivedFragmentGroup(); + info.Data = new byte[totalBytes]; + info.ReceivedChunks = new NetBitVector(totalNumChunks); + groups[group] = info; + } + + info.ReceivedChunks[chunkNumber] = true; + //info.LastReceived = (float)NetTime.Now; + + // copy to data + int offset = (chunkNumber * chunkByteSize); + Buffer.BlockCopy(im.m_data, ptr, info.Data, offset, im.LengthBytes - ptr); + + int cnt = info.ReceivedChunks.Count(); + //LogVerbose("Found fragment #" + chunkNumber + " in group " + group + " offset " + offset + " of total bits " + totalBits + " (total chunks done " + cnt + ")"); + + LogVerbose("Received fragment " + chunkNumber + " of " + totalNumChunks + " (" + cnt + " chunks received)"); + + if (info.ReceivedChunks.Count() == totalNumChunks) + { + // Done! Transform this incoming message + im.m_data = info.Data; + im.m_bitLength = (int)totalBits; + im.m_isFragment = false; + + LogVerbose("Fragment group #" + group + " fully received in " + totalNumChunks + " chunks (" + totalBits + " bits)"); + groups.Remove(group); + + ReleaseMessage(im); + } + else + { + // data has been copied; recycle this incoming message + Recycle(im); + } + + return; + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.Internal.cs b/SRMP/Lidgren.Network/NetPeer.Internal.cs new file mode 100644 index 0000000..6d4996b --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.Internal.cs @@ -0,0 +1,761 @@ +using System; +using System.Net; +using System.Threading; +using System.Diagnostics; +using System.Security.Cryptography; +using System.Net.Sockets; +using System.Collections.Generic; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + public partial class NetPeer + { + private NetPeerStatus m_status; + private Thread m_networkThread; + private Socket m_socket; + internal byte[] m_sendBuffer; + internal byte[] m_receiveBuffer; + internal NetIncomingMessage m_readHelperMessage; + private EndPoint m_senderRemote; + private object m_initializeLock = new object(); + private uint m_frameCounter; + private double m_lastHeartbeat; + private double m_lastSocketBind = float.MinValue; + private NetUPnP m_upnp; + internal bool m_needFlushSendQueue; + + internal readonly NetPeerConfiguration m_configuration; + private readonly NetQueue m_releasedIncomingMessages; + internal readonly NetQueue> m_unsentUnconnectedMessages; + + internal Dictionary m_handshakes; + + internal readonly NetPeerStatistics m_statistics; + internal long m_uniqueIdentifier; + internal bool m_executeFlushSendQueue; + + private AutoResetEvent m_messageReceivedEvent; + private List> m_receiveCallbacks; + + /// + /// Gets the socket, if Start() has been called + /// + public Socket Socket { get { return m_socket; } } + + /// + /// Call this to register a callback for when a new message arrives + /// + public void RegisterReceivedCallback(SendOrPostCallback callback, SynchronizationContext syncContext = null) + { + if (syncContext == null) + syncContext = SynchronizationContext.Current; + if (syncContext == null) + throw new NetException("Need a SynchronizationContext to register callback on correct thread!"); + if (m_receiveCallbacks == null) + m_receiveCallbacks = new List>(); + m_receiveCallbacks.Add(new NetTuple(syncContext, callback)); + } + + /// + /// Call this to unregister a callback, but remember to do it in the same synchronization context! + /// + public void UnregisterReceivedCallback(SendOrPostCallback callback) + { + if (m_receiveCallbacks == null) + return; + + // remove all callbacks regardless of sync context + m_receiveCallbacks.RemoveAll(tuple => tuple.Item2.Equals(callback)); + + if (m_receiveCallbacks.Count < 1) + m_receiveCallbacks = null; + } + + internal void ReleaseMessage(NetIncomingMessage msg) + { + NetException.Assert(msg.m_incomingMessageType != NetIncomingMessageType.Error); + + if (msg.m_isFragment) + { + HandleReleasedFragment(msg); + return; + } + + m_releasedIncomingMessages.Enqueue(msg); + + if (m_messageReceivedEvent != null) + m_messageReceivedEvent.Set(); + + if (m_receiveCallbacks != null) + { + foreach (var tuple in m_receiveCallbacks) + { + try + { + tuple.Item1.Post(tuple.Item2, this); + } + catch (Exception ex) + { + LogWarning("Receive callback exception:" + ex); + } + } + } + } + + private void BindSocket(bool reBind) + { + double now = NetTime.Now; + if (now - m_lastSocketBind < 1.0) + { + LogDebug("Suppressed socket rebind; last bound " + (now - m_lastSocketBind) + " seconds ago"); + return; // only allow rebind once every second + } + m_lastSocketBind = now; + + using (var mutex = new Mutex(false, "Global\\lidgrenSocketBind")) + { + try + { + mutex.WaitOne(); + + if (m_socket == null) + m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + + if (reBind) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, (int)1); + + m_socket.ReceiveBufferSize = m_configuration.ReceiveBufferSize; + m_socket.SendBufferSize = m_configuration.SendBufferSize; + m_socket.Blocking = false; + + var ep = (EndPoint)new NetEndPoint(m_configuration.LocalAddress, reBind ? m_listenPort : m_configuration.Port); + m_socket.Bind(ep); + + try + { + const uint IOC_IN = 0x80000000; + const uint IOC_VENDOR = 0x18000000; + uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12; + m_socket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null); + } + catch + { + // ignore; SIO_UDP_CONNRESET not supported on this platform + } + } + finally + { + mutex.ReleaseMutex(); + } + } + + var boundEp = m_socket.LocalEndPoint as NetEndPoint; + LogDebug("Socket bound to " + boundEp + ": " + m_socket.IsBound); + m_listenPort = boundEp.Port; + } + + private void InitializeNetwork() + { + lock (m_initializeLock) + { + m_configuration.Lock(); + + if (m_status == NetPeerStatus.Running) + return; + + if (m_configuration.m_enableUPnP) + m_upnp = new NetUPnP(this); + + InitializePools(); + + m_releasedIncomingMessages.Clear(); + m_unsentUnconnectedMessages.Clear(); + m_handshakes.Clear(); + + // bind to socket + BindSocket(false); + + m_receiveBuffer = new byte[m_configuration.ReceiveBufferSize]; + m_sendBuffer = new byte[m_configuration.SendBufferSize]; + m_readHelperMessage = new NetIncomingMessage(NetIncomingMessageType.Error); + m_readHelperMessage.m_data = m_receiveBuffer; + + byte[] macBytes = NetUtility.GetMacAddressBytes(); + + var boundEp = m_socket.LocalEndPoint as NetEndPoint; + byte[] epBytes = BitConverter.GetBytes(boundEp.GetHashCode()); + byte[] combined = new byte[epBytes.Length + macBytes.Length]; + Array.Copy(epBytes, 0, combined, 0, epBytes.Length); + Array.Copy(macBytes, 0, combined, epBytes.Length, macBytes.Length); + m_uniqueIdentifier = BitConverter.ToInt64(NetUtility.ComputeSHAHash(combined), 0); + + m_status = NetPeerStatus.Running; + } + } + + private void NetworkLoop() + { + VerifyNetworkThread(); + + LogDebug("Network thread started"); + + // + // Network loop + // + do + { + try + { + Heartbeat(); + } + catch (Exception ex) + { + LogWarning(ex.ToString()); + } + } while (m_status == NetPeerStatus.Running); + + // + // perform shutdown + // + ExecutePeerShutdown(); + } + + private void ExecutePeerShutdown() + { + VerifyNetworkThread(); + + LogDebug("Shutting down..."); + + // disconnect and make one final heartbeat + var list = new List(m_handshakes.Count + m_connections.Count); + lock (m_connections) + { + foreach (var conn in m_connections) + if (conn != null) + list.Add(conn); + } + + lock (m_handshakes) + { + foreach (var hs in m_handshakes.Values) + if (hs != null && list.Contains(hs) == false) + list.Add(hs); + } + + // shut down connections + foreach (NetConnection conn in list) + conn.Shutdown(m_shutdownReason); + + FlushDelayedPackets(); + + // one final heartbeat, will send stuff and do disconnect + Heartbeat(); + + NetUtility.Sleep(10); + + lock (m_initializeLock) + { + try + { + if (m_socket != null) + { + try + { + m_socket.Shutdown(SocketShutdown.Receive); + } + catch(Exception ex) + { + LogDebug("Socket.Shutdown exception: " + ex.ToString()); + } + + try + { + m_socket.Close(2); // 2 seconds timeout + } + catch (Exception ex) + { + LogDebug("Socket.Close exception: " + ex.ToString()); + } + } + } + finally + { + m_socket = null; + m_status = NetPeerStatus.NotRunning; + LogDebug("Shutdown complete"); + + // wake up any threads waiting for server shutdown + if (m_messageReceivedEvent != null) + m_messageReceivedEvent.Set(); + } + + m_lastSocketBind = float.MinValue; + m_receiveBuffer = null; + m_sendBuffer = null; + m_unsentUnconnectedMessages.Clear(); + m_connections.Clear(); + m_connectionLookup.Clear(); + m_handshakes.Clear(); + } + + return; + } + + private void Heartbeat() + { + VerifyNetworkThread(); + + double now = NetTime.Now; + double delta = now - m_lastHeartbeat; + + int maxCHBpS = 1250 - m_connections.Count; + if (maxCHBpS < 250) + maxCHBpS = 250; + if (delta > (1.0 / (double)maxCHBpS) || delta < 0.0) // max connection heartbeats/second max + { + m_frameCounter++; + m_lastHeartbeat = now; + + // do handshake heartbeats + if ((m_frameCounter % 3) == 0) + { + foreach (var kvp in m_handshakes) + { + NetConnection conn = kvp.Value as NetConnection; +#if DEBUG + // sanity check + if (kvp.Key != kvp.Key) + LogWarning("Sanity fail! Connection in handshake list under wrong key!"); +#endif + conn.UnconnectedHeartbeat(now); + if (conn.m_status == NetConnectionStatus.Connected || conn.m_status == NetConnectionStatus.Disconnected) + { +#if DEBUG + // sanity check + if (conn.m_status == NetConnectionStatus.Disconnected && m_handshakes.ContainsKey(conn.RemoteEndPoint)) + { + LogWarning("Sanity fail! Handshakes list contained disconnected connection!"); + m_handshakes.Remove(conn.RemoteEndPoint); + } +#endif + break; // collection has been modified + } + } + } + +#if DEBUG + SendDelayedPackets(); +#endif + + // update m_executeFlushSendQueue + if (m_configuration.m_autoFlushSendQueue && m_needFlushSendQueue == true) + { + m_executeFlushSendQueue = true; + m_needFlushSendQueue = false; // a race condition to this variable will simply result in a single superfluous call to FlushSendQueue() + } + + // do connection heartbeats + lock (m_connections) + { + for (int i = m_connections.Count - 1; i >= 0; i--) + { + var conn = m_connections[i]; + conn.Heartbeat(now, m_frameCounter); + if (conn.m_status == NetConnectionStatus.Disconnected) + { + // + // remove connection + // + m_connections.RemoveAt(i); + m_connectionLookup.Remove(conn.RemoteEndPoint); + } + } + } + m_executeFlushSendQueue = false; + + // send unsent unconnected messages + NetTuple unsent; + while (m_unsentUnconnectedMessages.TryDequeue(out unsent)) + { + NetOutgoingMessage om = unsent.Item2; + + int len = om.Encode(m_sendBuffer, 0, 0); + + Interlocked.Decrement(ref om.m_recyclingCount); + if (om.m_recyclingCount <= 0) + Recycle(om); + + bool connReset; + SendPacket(len, unsent.Item1, 1, out connReset); + } + } + + if (m_upnp != null) + m_upnp.CheckForDiscoveryTimeout(); + + // + // read from socket + // + if (m_socket == null) + return; + + if (!m_socket.Poll(1000, SelectMode.SelectRead)) // wait up to 1 ms for data to arrive + return; + + //if (m_socket == null || m_socket.Available < 1) + // return; + + // update now + now = NetTime.Now; + + do + { + int bytesReceived = 0; + try + { + bytesReceived = m_socket.ReceiveFrom(m_receiveBuffer, 0, m_receiveBuffer.Length, SocketFlags.None, ref m_senderRemote); + } + catch (SocketException sx) + { + switch (sx.SocketErrorCode) + { + case SocketError.ConnectionReset: + // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable" + // we should shut down the connection; but m_senderRemote seemingly cannot be trusted, so which connection should we shut down?! + // So, what to do? + LogWarning("ConnectionReset"); + return; + + case SocketError.NotConnected: + // socket is unbound; try to rebind it (happens on mobile when process goes to sleep) + BindSocket(true); + return; + + default: + LogWarning("Socket exception: " + sx.ToString()); + return; + } + } + + if (bytesReceived < NetConstants.HeaderByteSize) + return; + + //LogVerbose("Received " + bytesReceived + " bytes"); + + var ipsender = (NetEndPoint)m_senderRemote; + + if (m_upnp != null && now < m_upnp.m_discoveryResponseDeadline && bytesReceived > 32) + { + // is this an UPnP response? + string resp = System.Text.Encoding.UTF8.GetString(m_receiveBuffer, 0, bytesReceived); + if (resp.Contains("upnp:rootdevice") || resp.Contains("UPnP/1.0")) + { + try + { + resp = resp.Substring(resp.ToLower().IndexOf("location:") + 9); + resp = resp.Substring(0, resp.IndexOf("\r")).Trim(); + m_upnp.ExtractServiceUrl(resp); + return; + } + catch (Exception ex) + { + LogDebug("Failed to parse UPnP response: " + ex.ToString()); + + // don't try to parse this packet further + return; + } + } + } + + NetConnection sender = null; + m_connectionLookup.TryGetValue(ipsender, out sender); + + // + // parse packet into messages + // + int numMessages = 0; + int numFragments = 0; + int ptr = 0; + while ((bytesReceived - ptr) >= NetConstants.HeaderByteSize) + { + // decode header + // 8 bits - NetMessageType + // 1 bit - Fragment? + // 15 bits - Sequence number + // 16 bits - Payload length in bits + + numMessages++; + + NetMessageType tp = (NetMessageType)m_receiveBuffer[ptr++]; + + byte low = m_receiveBuffer[ptr++]; + byte high = m_receiveBuffer[ptr++]; + + bool isFragment = ((low & 1) == 1); + ushort sequenceNumber = (ushort)((low >> 1) | (((int)high) << 7)); + + if (isFragment) + numFragments++; + + ushort payloadBitLength = (ushort)(m_receiveBuffer[ptr++] | (m_receiveBuffer[ptr++] << 8)); + int payloadByteLength = NetUtility.BytesToHoldBits(payloadBitLength); + + if (bytesReceived - ptr < payloadByteLength) + { + LogWarning("Malformed packet; stated payload length " + payloadByteLength + ", remaining bytes " + (bytesReceived - ptr)); + return; + } + + if (tp >= NetMessageType.Unused1 && tp <= NetMessageType.Unused29) + { + ThrowOrLog("Unexpected NetMessageType: " + tp); + return; + } + + try + { + if (tp >= NetMessageType.LibraryError) + { + if (sender != null) + sender.ReceivedLibraryMessage(tp, ptr, payloadByteLength); + else + ReceivedUnconnectedLibraryMessage(now, ipsender, tp, ptr, payloadByteLength); + } + else + { + if (sender == null && !m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData)) + return; // dropping unconnected message since it's not enabled + + NetIncomingMessage msg = CreateIncomingMessage(NetIncomingMessageType.Data, payloadByteLength); + msg.m_isFragment = isFragment; + msg.m_receiveTime = now; + msg.m_sequenceNumber = sequenceNumber; + msg.m_receivedMessageType = tp; + msg.m_senderConnection = sender; + msg.m_senderEndPoint = ipsender; + msg.m_bitLength = payloadBitLength; + + Buffer.BlockCopy(m_receiveBuffer, ptr, msg.m_data, 0, payloadByteLength); + if (sender != null) + { + if (tp == NetMessageType.Unconnected) + { + // We're connected; but we can still send unconnected messages to this peer + msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData; + ReleaseMessage(msg); + } + else + { + // connected application (non-library) message + sender.ReceivedMessage(msg); + } + } + else + { + // at this point we know the message type is enabled + // unconnected application (non-library) message + msg.m_incomingMessageType = NetIncomingMessageType.UnconnectedData; + ReleaseMessage(msg); + } + } + } + catch (Exception ex) + { + LogError("Packet parsing error: " + ex.Message + " from " + ipsender); + } + ptr += payloadByteLength; + } + + m_statistics.PacketReceived(bytesReceived, numMessages, numFragments); + if (sender != null) + sender.m_statistics.PacketReceived(bytesReceived, numMessages, numFragments); + + } while (m_socket.Available > 0); + } + + /// + /// If NetPeerConfiguration.AutoFlushSendQueue() is false; you need to call this to send all messages queued using SendMessage() + /// + public void FlushSendQueue() + { + m_executeFlushSendQueue = true; + } + + internal void HandleIncomingDiscoveryRequest(double now, NetEndPoint senderEndPoint, int ptr, int payloadByteLength) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryRequest)) + { + NetIncomingMessage dm = CreateIncomingMessage(NetIncomingMessageType.DiscoveryRequest, payloadByteLength); + if (payloadByteLength > 0) + Buffer.BlockCopy(m_receiveBuffer, ptr, dm.m_data, 0, payloadByteLength); + dm.m_receiveTime = now; + dm.m_bitLength = payloadByteLength * 8; + dm.m_senderEndPoint = senderEndPoint; + ReleaseMessage(dm); + } + } + + internal void HandleIncomingDiscoveryResponse(double now, NetEndPoint senderEndPoint, int ptr, int payloadByteLength) + { + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DiscoveryResponse)) + { + NetIncomingMessage dr = CreateIncomingMessage(NetIncomingMessageType.DiscoveryResponse, payloadByteLength); + if (payloadByteLength > 0) + Buffer.BlockCopy(m_receiveBuffer, ptr, dr.m_data, 0, payloadByteLength); + dr.m_receiveTime = now; + dr.m_bitLength = payloadByteLength * 8; + dr.m_senderEndPoint = senderEndPoint; + ReleaseMessage(dr); + } + } + + private void ReceivedUnconnectedLibraryMessage(double now, NetEndPoint senderEndPoint, NetMessageType tp, int ptr, int payloadByteLength) + { + NetConnection shake; + if (m_handshakes.TryGetValue(senderEndPoint, out shake)) + { + shake.ReceivedHandshake(now, tp, ptr, payloadByteLength); + return; + } + + // + // Library message from a completely unknown sender; lets just accept Connect + // + switch (tp) + { + case NetMessageType.Discovery: + HandleIncomingDiscoveryRequest(now, senderEndPoint, ptr, payloadByteLength); + return; + case NetMessageType.DiscoveryResponse: + HandleIncomingDiscoveryResponse(now, senderEndPoint, ptr, payloadByteLength); + return; + case NetMessageType.NatIntroduction: + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess)) + HandleNatIntroduction(ptr); + return; + case NetMessageType.NatPunchMessage: + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess)) + HandleNatPunch(ptr, senderEndPoint); + return; + case NetMessageType.NatIntroductionConfirmRequest: + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess)) + HandleNatPunchConfirmRequest(ptr, senderEndPoint); + return; + case NetMessageType.NatIntroductionConfirmed: + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.NatIntroductionSuccess)) + HandleNatPunchConfirmed(ptr, senderEndPoint); + return; + case NetMessageType.ConnectResponse: + + lock (m_handshakes) + { + foreach (var hs in m_handshakes) + { + if (hs.Key.Address.Equals(senderEndPoint.Address)) + { + if (hs.Value.m_connectionInitiator) + { + // + // We are currently trying to connection to XX.XX.XX.XX:Y + // ... but we just received a ConnectResponse from XX.XX.XX.XX:Z + // Lets just assume the router decided to use this port instead + // + var hsconn = hs.Value; + m_connectionLookup.Remove(hs.Key); + m_handshakes.Remove(hs.Key); + + LogDebug("Detected host port change; rerouting connection to " + senderEndPoint); + hsconn.MutateEndPoint(senderEndPoint); + + m_connectionLookup.Add(senderEndPoint, hsconn); + m_handshakes.Add(senderEndPoint, hsconn); + + hsconn.ReceivedHandshake(now, tp, ptr, payloadByteLength); + return; + } + } + } + } + + LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint); + return; + case NetMessageType.Connect: + if (m_configuration.AcceptIncomingConnections == false) + { + LogWarning(m_configuration.AppIdentifier + " Received Connect, but we're not accepting incoming connections!"); + return; + } + // handle connect + // It's someone wanting to shake hands with us! + + int reservedSlots = m_handshakes.Count + m_connections.Count; + if (reservedSlots >= m_configuration.m_maximumConnections) + { + // server full + NetOutgoingMessage full = CreateMessage("Server full"); + full.m_messageType = NetMessageType.Disconnect; + SendLibrary(full, senderEndPoint); + return; + } + + // Ok, start handshake! + NetConnection conn = new NetConnection(this, senderEndPoint); + conn.m_status = NetConnectionStatus.ReceivedInitiation; + m_handshakes.Add(senderEndPoint, conn); + conn.ReceivedHandshake(now, tp, ptr, payloadByteLength); + return; + + case NetMessageType.Disconnect: + // this is probably ok + LogVerbose("Received Disconnect from unconnected source: " + senderEndPoint); + return; + default: + LogWarning("Received unhandled library message " + tp + " from " + senderEndPoint); + return; + } + } + + internal void AcceptConnection(NetConnection conn) + { + // LogDebug("Accepted connection " + conn); + conn.InitExpandMTU(NetTime.Now); + + if (m_handshakes.Remove(conn.m_remoteEndPoint) == false) + LogWarning("AcceptConnection called but m_handshakes did not contain it!"); + + lock (m_connections) + { + if (m_connections.Contains(conn)) + { + LogWarning("AcceptConnection called but m_connection already contains it!"); + } + else + { + m_connections.Add(conn); + m_connectionLookup.Add(conn.m_remoteEndPoint, conn); + } + } + } + + [Conditional("DEBUG")] + internal void VerifyNetworkThread() + { + Thread ct = Thread.CurrentThread; + if (Thread.CurrentThread != m_networkThread) + throw new NetException("Executing on wrong thread! Should be library system thread (is " + ct.Name + " mId " + ct.ManagedThreadId + ")"); + } + + internal NetIncomingMessage SetupReadHelperMessage(int ptr, int payloadLength) + { + VerifyNetworkThread(); + + m_readHelperMessage.m_bitLength = (ptr + payloadLength) * 8; + m_readHelperMessage.m_readPosition = (ptr * 8); + return m_readHelperMessage; + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.LatencySimulation.cs b/SRMP/Lidgren.Network/NetPeer.LatencySimulation.cs new file mode 100644 index 0000000..ee029c3 --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.LatencySimulation.cs @@ -0,0 +1,311 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +//#define USE_RELEASE_STATISTICS + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Diagnostics; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + public partial class NetPeer + { + +#if DEBUG + private readonly List m_delayedPackets = new List(); + + private class DelayedPacket + { + public byte[] Data; + public double DelayedUntil; + public NetEndPoint Target; + } + + internal void SendPacket(int numBytes, NetEndPoint target, int numMessages, out bool connectionReset) + { + connectionReset = false; + + // simulate loss + float loss = m_configuration.m_loss; + if (loss > 0.0f) + { + if ((float)MWCRandom.Instance.NextDouble() < loss) + { + LogVerbose("Sending packet " + numBytes + " bytes - SIMULATED LOST!"); + return; // packet "lost" + } + } + + m_statistics.PacketSent(numBytes, numMessages); + + // simulate latency + float m = m_configuration.m_minimumOneWayLatency; + float r = m_configuration.m_randomOneWayLatency; + if (m == 0.0f && r == 0.0f) + { + // no latency simulation + // LogVerbose("Sending packet " + numBytes + " bytes"); + bool wasSent = ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset); + // TODO: handle wasSent == false? + + if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextDouble() < m_configuration.m_duplicates) + ActuallySendPacket(m_sendBuffer, numBytes, target, out connectionReset); // send it again! + + return; + } + + int num = 1; + if (m_configuration.m_duplicates > 0.0f && MWCRandom.Instance.NextSingle() < m_configuration.m_duplicates) + num++; + + float delay = 0; + for (int i = 0; i < num; i++) + { + delay = m_configuration.m_minimumOneWayLatency + (MWCRandom.Instance.NextSingle() * m_configuration.m_randomOneWayLatency); + + // Enqueue delayed packet + DelayedPacket p = new DelayedPacket(); + p.Target = target; + p.Data = new byte[numBytes]; + Buffer.BlockCopy(m_sendBuffer, 0, p.Data, 0, numBytes); + p.DelayedUntil = NetTime.Now + delay; + + m_delayedPackets.Add(p); + } + + // LogVerbose("Sending packet " + numBytes + " bytes - delayed " + NetTime.ToReadable(delay)); + } + + private void SendDelayedPackets() + { + if (m_delayedPackets.Count <= 0) + return; + + double now = NetTime.Now; + + bool connectionReset; + + RestartDelaySending: + foreach (DelayedPacket p in m_delayedPackets) + { + if (now > p.DelayedUntil) + { + ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset); + m_delayedPackets.Remove(p); + goto RestartDelaySending; + } + } + } + + private void FlushDelayedPackets() + { + try + { + bool connectionReset; + foreach (DelayedPacket p in m_delayedPackets) + ActuallySendPacket(p.Data, p.Data.Length, p.Target, out connectionReset); + m_delayedPackets.Clear(); + } + catch { } + } + + internal bool ActuallySendPacket(byte[] data, int numBytes, NetEndPoint target, out bool connectionReset) + { + connectionReset = false; + IPAddress ba = default(IPAddress); + try + { + ba = NetUtility.GetCachedBroadcastAddress(); + + // TODO: refactor this check outta here + if (target.Address.Equals(ba)) + { + // Some networks do not allow + // a global broadcast so we use the BroadcastAddress from the configuration + // this can be resolved to a local broadcast addresss e.g 192.168.x.255 + target.Address = m_configuration.BroadcastAddress; + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + } + + int bytesSent = m_socket.SendTo(data, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + + // LogDebug("Sent " + numBytes + " bytes"); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return false; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + { + // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable" + connectionReset = true; + return false; + } + LogError("Failed to send packet: " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + if (target.Address == ba) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); + } + return true; + } + + internal bool SendMTUPacket(int numBytes, NetEndPoint target) + { + try + { + m_socket.DontFragment = true; + int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + + m_statistics.PacketSent(numBytes, 1); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.MessageSize) + return false; + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return true; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + return true; + LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + m_socket.DontFragment = false; + } + return true; + } +#else + internal bool SendMTUPacket(int numBytes, NetEndPoint target) + { + try + { + m_socket.DontFragment = true; + int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.MessageSize) + return false; + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return true; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + return true; + LogError("Failed to send packet: (" + sx.SocketErrorCode + ") " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + m_socket.DontFragment = false; + } + return true; + } + + // + // Release - just send the packet straight away + // + internal void SendPacket(int numBytes, NetEndPoint target, int numMessages, out bool connectionReset) + { +#if USE_RELEASE_STATISTICS + m_statistics.PacketSent(numBytes, numMessages); +#endif + connectionReset = false; + IPAddress ba = default(IPAddress); + try + { + // TODO: refactor this check outta here + ba = NetUtility.GetCachedBroadcastAddress(); + if (target.Address == ba) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + + int bytesSent = m_socket.SendTo(m_sendBuffer, 0, numBytes, SocketFlags.None, target); + if (numBytes != bytesSent) + LogWarning("Failed to send the full " + numBytes + "; only " + bytesSent + " bytes sent in packet!"); + } + catch (SocketException sx) + { + if (sx.SocketErrorCode == SocketError.WouldBlock) + { + // send buffer full? + LogWarning("Socket threw exception; would block - send buffer full? Increase in NetPeerConfiguration"); + return; + } + if (sx.SocketErrorCode == SocketError.ConnectionReset) + { + // connection reset by peer, aka connection forcibly closed aka "ICMP port unreachable" + connectionReset = true; + return; + } + LogError("Failed to send packet: " + sx); + } + catch (Exception ex) + { + LogError("Failed to send packet: " + ex); + } + finally + { + if (target.Address == ba) + m_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); + } + return; + } + + private void FlushDelayedPackets() + { + } +#endif + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.Logging.cs b/SRMP/Lidgren.Network/NetPeer.Logging.cs new file mode 100644 index 0000000..006cdf5 --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.Logging.cs @@ -0,0 +1,63 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ +using System.Diagnostics; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + [Conditional("DEBUG")] + internal void LogVerbose(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Verbose, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.VerboseDebugMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.VerboseDebugMessage, message)); + } + + [Conditional("DEBUG")] + internal void LogDebug(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Debug, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.DebugMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.DebugMessage, message)); + } + + internal void LogWarning(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Warn, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.WarningMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.WarningMessage, message)); + } + + internal void LogError(string message) + { +#if __ANDROID__ + Android.Util.Log.WriteLine(Android.Util.LogPriority.Error, "", message); +#endif + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.ErrorMessage)) + ReleaseMessage(CreateIncomingMessage(NetIncomingMessageType.ErrorMessage, message)); + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.MessagePools.cs b/SRMP/Lidgren.Network/NetPeer.MessagePools.cs new file mode 100644 index 0000000..ea70208 --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.MessagePools.cs @@ -0,0 +1,243 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + public partial class NetPeer + { + internal List m_storagePool; + private NetQueue m_outgoingMessagesPool; + private NetQueue m_incomingMessagesPool; + + internal int m_storagePoolBytes; + internal int m_storageSlotsUsedCount; + private int m_maxCacheCount; + + private void InitializePools() + { + m_storageSlotsUsedCount = 0; + + if (m_configuration.UseMessageRecycling) + { + m_storagePool = new List(16); + m_outgoingMessagesPool = new NetQueue(4); + m_incomingMessagesPool = new NetQueue(4); + } + else + { + m_storagePool = null; + m_outgoingMessagesPool = null; + m_incomingMessagesPool = null; + } + + m_maxCacheCount = m_configuration.RecycledCacheMaxCount; + } + + internal byte[] GetStorage(int minimumCapacityInBytes) + { + if (m_storagePool == null) + return new byte[minimumCapacityInBytes]; + + lock (m_storagePool) + { + for (int i = 0; i < m_storagePool.Count; i++) + { + byte[] retval = m_storagePool[i]; + if (retval != null && retval.Length >= minimumCapacityInBytes) + { + m_storagePool[i] = null; + m_storageSlotsUsedCount--; + m_storagePoolBytes -= retval.Length; + return retval; + } + } + } + m_statistics.m_bytesAllocated += minimumCapacityInBytes; + return new byte[minimumCapacityInBytes]; + } + + internal void Recycle(byte[] storage) + { + if (m_storagePool == null || storage == null) + return; + + lock (m_storagePool) + { + int cnt = m_storagePool.Count; + for (int i = 0; i < cnt; i++) + { + if (m_storagePool[i] == null) + { + m_storageSlotsUsedCount++; + m_storagePoolBytes += storage.Length; + m_storagePool[i] = storage; + return; + } + } + + if (m_storagePool.Count >= m_maxCacheCount) + { + // pool is full; replace randomly chosen entry to keep size distribution + var idx = NetRandom.Instance.Next(m_storagePool.Count); + + m_storagePoolBytes -= m_storagePool[idx].Length; + m_storagePoolBytes += storage.Length; + + m_storagePool[idx] = storage; // replace + } + else + { + m_storageSlotsUsedCount++; + m_storagePoolBytes += storage.Length; + m_storagePool.Add(storage); + } + } + } + + /// + /// Creates a new message for sending + /// + public NetOutgoingMessage CreateMessage() + { + return CreateMessage(m_configuration.m_defaultOutgoingMessageCapacity); + } + + /// + /// Creates a new message for sending and writes the provided string to it + /// + public NetOutgoingMessage CreateMessage(string content) + { + NetOutgoingMessage om; + + // Since this could be null. + if (string.IsNullOrEmpty(content)) + { + om = CreateMessage(1); // One byte for the internal variable-length zero byte. + } + else + { + om = CreateMessage(2 + content.Length); // Fair guess. + } + + om.Write(content); + return om; + } + + /// + /// Creates a new message for sending + /// + /// initial capacity in bytes + public NetOutgoingMessage CreateMessage(int initialCapacity) + { + NetOutgoingMessage retval; + if (m_outgoingMessagesPool == null || !m_outgoingMessagesPool.TryDequeue(out retval)) + retval = new NetOutgoingMessage(); + + NetException.Assert(retval.m_recyclingCount == 0, "Wrong recycling count! Should be zero" + retval.m_recyclingCount); + + if (initialCapacity > 0) + retval.m_data = GetStorage(initialCapacity); + + return retval; + } + + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, byte[] useStorageData) + { + NetIncomingMessage retval; + if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval)) + retval = new NetIncomingMessage(tp); + else + retval.m_incomingMessageType = tp; + retval.m_data = useStorageData; + return retval; + } + + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, int minimumByteSize) + { + NetIncomingMessage retval; + if (m_incomingMessagesPool == null || !m_incomingMessagesPool.TryDequeue(out retval)) + retval = new NetIncomingMessage(tp); + else + retval.m_incomingMessageType = tp; + retval.m_data = GetStorage(minimumByteSize); + return retval; + } + + /// + /// Recycles a NetIncomingMessage instance for reuse; taking pressure off the garbage collector + /// + public void Recycle(NetIncomingMessage msg) + { + if (m_incomingMessagesPool == null || msg == null) + return; + + NetException.Assert(m_incomingMessagesPool.Contains(msg) == false, "Recyling already recycled incoming message! Thread race?"); + + byte[] storage = msg.m_data; + msg.m_data = null; + Recycle(storage); + msg.Reset(); + + if (m_incomingMessagesPool.Count < m_maxCacheCount) + m_incomingMessagesPool.Enqueue(msg); + } + + /// + /// Recycles a list of NetIncomingMessage instances for reuse; taking pressure off the garbage collector + /// + public void Recycle(IEnumerable toRecycle) + { + if (m_incomingMessagesPool == null) + return; + foreach (var im in toRecycle) + Recycle(im); + } + + internal void Recycle(NetOutgoingMessage msg) + { + if (m_outgoingMessagesPool == null) + return; +#if DEBUG + NetException.Assert(m_outgoingMessagesPool.Contains(msg) == false, "Recyling already recycled outgoing message! Thread race?"); + if (msg.m_recyclingCount != 0) + LogWarning("Wrong recycling count! should be zero; found " + msg.m_recyclingCount); +#endif + // setting m_recyclingCount to zero SHOULD be an unnecessary maneuver, if it's not zero something is wrong + // however, in RELEASE, we'll just have to accept this and move on with life + msg.m_recyclingCount = 0; + + byte[] storage = msg.m_data; + msg.m_data = null; + + // message fragments cannot be recycled + // TODO: find a way to recycle large message after all fragments has been acknowledged; or? possibly better just to garbage collect them + if (msg.m_fragmentGroup == 0) + Recycle(storage); + + msg.Reset(); + if (m_outgoingMessagesPool.Count < m_maxCacheCount) + m_outgoingMessagesPool.Enqueue(msg); + } + + /// + /// Creates an incoming message with the required capacity for releasing to the application + /// + internal NetIncomingMessage CreateIncomingMessage(NetIncomingMessageType tp, string text) + { + NetIncomingMessage retval; + if (string.IsNullOrEmpty(text)) + { + retval = CreateIncomingMessage(tp, 1); + retval.Write(string.Empty); + return retval; + } + + int numBytes = System.Text.Encoding.UTF8.GetByteCount(text); + retval = CreateIncomingMessage(tp, numBytes + (numBytes > 127 ? 2 : 1)); + retval.Write(text); + + return retval; + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.Send.cs b/SRMP/Lidgren.Network/NetPeer.Send.cs new file mode 100644 index 0000000..5e71f85 --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.Send.cs @@ -0,0 +1,255 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Net; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + public partial class NetPeer + { + /// + /// Send a message to a specific connection + /// + /// The message to send + /// The recipient connection + /// How to deliver the message + public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method) + { + return SendMessage(msg, recipient, method, 0); + } + + /// + /// Send a message to a specific connection + /// + /// The message to send + /// The recipient connection + /// How to deliver the message + /// Sequence channel within the delivery method + public NetSendResult SendMessage(NetOutgoingMessage msg, NetConnection recipient, NetDeliveryMethod method, int sequenceChannel) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipient == null) + throw new ArgumentNullException("recipient"); + if (sequenceChannel >= NetConstants.NetChannelsPerDeliveryMethod) + throw new ArgumentOutOfRangeException("sequenceChannel"); + + NetException.Assert( + ((method != NetDeliveryMethod.Unreliable && method != NetDeliveryMethod.ReliableUnordered) || + ((method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered) && sequenceChannel == 0)), + "Delivery method " + method + " cannot use sequence channels other than 0!" + ); + + NetException.Assert(method != NetDeliveryMethod.Unknown, "Bad delivery method!"); + + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + msg.m_isSent = true; + + bool suppressFragmentation = (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.UnreliableSequenced) && m_configuration.UnreliableSizeBehaviour != NetUnreliableSizeBehaviour.NormalFragmentation; + + int len = NetConstants.UnfragmentedMessageHeaderSize + msg.LengthBytes; // headers + length, faster than calling msg.GetEncodedSize + if (len <= recipient.m_currentMTU || suppressFragmentation) + { + Interlocked.Increment(ref msg.m_recyclingCount); + return recipient.EnqueueMessage(msg, method, sequenceChannel); + } + else + { + // message must be fragmented! + if (recipient.m_status != NetConnectionStatus.Connected) + return NetSendResult.FailedNotConnected; + return SendFragmentedMessage(msg, new NetConnection[] { recipient }, method, sequenceChannel); + } + } + + internal static int GetMTU(IList recipients) + { + int count = recipients.Count; + + int mtu = int.MaxValue; + if (count < 1) + { +#if DEBUG + throw new NetException("GetMTU called with no recipients"); +#else + // we don't have access to the particular peer, so just use default MTU + return NetPeerConfiguration.kDefaultMTU; +#endif + } + + for(int i=0;i + /// Send a message to a list of connections + /// + /// The message to send + /// The list of recipients to send to + /// How to deliver the message + /// Sequence channel within the delivery method + public void SendMessage(NetOutgoingMessage msg, IList recipients, NetDeliveryMethod method, int sequenceChannel) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipients == null) + { + if (msg.m_isSent == false) + Recycle(msg); + throw new ArgumentNullException("recipients"); + } + if (recipients.Count < 1) + { + if (msg.m_isSent == false) + Recycle(msg); + throw new NetException("recipients must contain at least one item"); + } + if (method == NetDeliveryMethod.Unreliable || method == NetDeliveryMethod.ReliableUnordered) + NetException.Assert(sequenceChannel == 0, "Delivery method " + method + " cannot use sequence channels other than 0!"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + msg.m_isSent = true; + + int mtu = GetMTU(recipients); + + int len = msg.GetEncodedSize(); + if (len <= mtu) + { + Interlocked.Add(ref msg.m_recyclingCount, recipients.Count); + foreach (NetConnection conn in recipients) + { + if (conn == null) + { + Interlocked.Decrement(ref msg.m_recyclingCount); + continue; + } + NetSendResult res = conn.EnqueueMessage(msg, method, sequenceChannel); + if (res == NetSendResult.Dropped) + Interlocked.Decrement(ref msg.m_recyclingCount); + } + } + else + { + // message must be fragmented! + SendFragmentedMessage(msg, recipients, method, sequenceChannel); + } + + return; + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, string host, int port) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (host == null) + throw new ArgumentNullException("host"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit) + throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")"); + + msg.m_isSent = true; + msg.m_messageType = NetMessageType.Unconnected; + + var adr = NetUtility.Resolve(host); + if (adr == null) + throw new NetException("Failed to resolve " + host); + + Interlocked.Increment(ref msg.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(new NetEndPoint(adr, port), msg)); + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, NetEndPoint recipient) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipient == null) + throw new ArgumentNullException("recipient"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit) + throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")"); + + msg.m_messageType = NetMessageType.Unconnected; + msg.m_isSent = true; + + Interlocked.Increment(ref msg.m_recyclingCount); + m_unsentUnconnectedMessages.Enqueue(new NetTuple(recipient, msg)); + } + + /// + /// Send a message to an unconnected host + /// + public void SendUnconnectedMessage(NetOutgoingMessage msg, IList recipients) + { + if (msg == null) + throw new ArgumentNullException("msg"); + if (recipients == null) + throw new ArgumentNullException("recipients"); + if (recipients.Count < 1) + throw new NetException("recipients must contain at least one item"); + if (msg.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + if (msg.LengthBytes > m_configuration.MaximumTransmissionUnit) + throw new NetException("Unconnected messages too long! Must be shorter than NetConfiguration.MaximumTransmissionUnit (currently " + m_configuration.MaximumTransmissionUnit + ")"); + + msg.m_messageType = NetMessageType.Unconnected; + msg.m_isSent = true; + + Interlocked.Add(ref msg.m_recyclingCount, recipients.Count); + foreach (NetEndPoint ep in recipients) + m_unsentUnconnectedMessages.Enqueue(new NetTuple(ep, msg)); + } + + /// + /// Send a message to this exact same netpeer (loopback) + /// + public void SendUnconnectedToSelf(NetOutgoingMessage om) + { + if (om == null) + throw new ArgumentNullException("msg"); + if (om.m_isSent) + throw new NetException("This message has already been sent! Use NetPeer.SendMessage() to send to multiple recipients efficiently"); + + om.m_messageType = NetMessageType.Unconnected; + om.m_isSent = true; + + if (m_configuration.IsMessageTypeEnabled(NetIncomingMessageType.UnconnectedData) == false) + { + Interlocked.Decrement(ref om.m_recyclingCount); + return; // dropping unconnected message since it's not enabled for receiving + } + + // convert outgoing to incoming + NetIncomingMessage im = CreateIncomingMessage(NetIncomingMessageType.UnconnectedData, om.LengthBytes); + im.Write(om); + im.m_isFragment = false; + im.m_receiveTime = NetTime.Now; + im.m_senderConnection = null; + im.m_senderEndPoint = m_socket.LocalEndPoint as NetEndPoint; + NetException.Assert(im.m_bitLength == om.LengthBits); + + // recycle outgoing message + Recycle(om); + + ReleaseMessage(im); + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeer.cs b/SRMP/Lidgren.Network/NetPeer.cs new file mode 100644 index 0000000..e9886f4 --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeer.cs @@ -0,0 +1,389 @@ +using System; +using System.Threading; +using System.Collections.Generic; +using System.Net; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + /// + /// Represents a local peer capable of holding zero, one or more connections to remote peers + /// + public partial class NetPeer + { + private static int s_initializedPeersCount; + + private int m_listenPort; + private object m_tag; + private object m_messageReceivedEventCreationLock = new object(); + + internal readonly List m_connections; + private readonly Dictionary m_connectionLookup; + + private string m_shutdownReason; + + /// + /// Gets the NetPeerStatus of the NetPeer + /// + public NetPeerStatus Status { get { return m_status; } } + + /// + /// Signalling event which can be waited on to determine when a message is queued for reading. + /// Note that there is no guarantee that after the event is signaled the blocked thread will + /// find the message in the queue. Other user created threads could be preempted and dequeue + /// the message before the waiting thread wakes up. + /// + public AutoResetEvent MessageReceivedEvent + { + get + { + if (m_messageReceivedEvent == null) + { + lock (m_messageReceivedEventCreationLock) // make sure we don't create more than one event object + { + if (m_messageReceivedEvent == null) + m_messageReceivedEvent = new AutoResetEvent(false); + } + } + return m_messageReceivedEvent; + } + } + + /// + /// Gets a unique identifier for this NetPeer based on Mac address and ip/port. Note! Not available until Start() has been called! + /// + public long UniqueIdentifier { get { return m_uniqueIdentifier; } } + + /// + /// Gets the port number this NetPeer is listening and sending on, if Start() has been called + /// + public int Port { get { return m_listenPort; } } + + /// + /// Returns an UPnP object if enabled in the NetPeerConfiguration + /// + public NetUPnP UPnP { get { return m_upnp; } } + + /// + /// Gets or sets the application defined object containing data about the peer + /// + public object Tag + { + get { return m_tag; } + set { m_tag = value; } + } + + /// + /// Gets a copy of the list of connections + /// + public List Connections + { + get + { + lock (m_connections) + return new List(m_connections); + } + } + + /// + /// Gets the number of active connections + /// + public int ConnectionsCount + { + get { return m_connections.Count; } + } + + /// + /// Statistics on this NetPeer since it was initialized + /// + public NetPeerStatistics Statistics + { + get { return m_statistics; } + } + + /// + /// Gets the configuration used to instanciate this NetPeer + /// + public NetPeerConfiguration Configuration { get { return m_configuration; } } + + /// + /// NetPeer constructor + /// + public NetPeer(NetPeerConfiguration config) + { + m_configuration = config; + m_statistics = new NetPeerStatistics(this); + m_releasedIncomingMessages = new NetQueue(4); + m_unsentUnconnectedMessages = new NetQueue>(2); + m_connections = new List(); + m_connectionLookup = new Dictionary(); + m_handshakes = new Dictionary(); + m_senderRemote = (EndPoint)new NetEndPoint(IPAddress.Any, 0); + m_status = NetPeerStatus.NotRunning; + m_receivedFragmentGroups = new Dictionary>(); + } + + /// + /// Binds to socket and spawns the networking thread + /// + public void Start() + { + if (m_status != NetPeerStatus.NotRunning) + { + // already running! Just ignore... + LogWarning("Start() called on already running NetPeer - ignoring."); + return; + } + + m_status = NetPeerStatus.Starting; + + // fix network thread name + if (m_configuration.NetworkThreadName == "Lidgren network thread") + { + int pc = Interlocked.Increment(ref s_initializedPeersCount); + m_configuration.NetworkThreadName = "Lidgren network thread " + pc.ToString(); + } + + InitializeNetwork(); + + // start network thread + m_networkThread = new Thread(new ThreadStart(NetworkLoop)); + m_networkThread.Name = m_configuration.NetworkThreadName; + m_networkThread.IsBackground = true; + m_networkThread.Start(); + + // send upnp discovery + if (m_upnp != null) + m_upnp.Discover(this); + + // allow some time for network thread to start up in case they call Connect() or UPnP calls immediately + NetUtility.Sleep(50); + } + + /// + /// Get the connection, if any, for a certain remote endpoint + /// + public NetConnection GetConnection(NetEndPoint ep) + { + NetConnection retval; + + // this should not pose a threading problem, m_connectionLookup is never added to concurrently + // and TryGetValue will not throw an exception on fail, only yield null, which is acceptable + m_connectionLookup.TryGetValue(ep, out retval); + + return retval; + } + + /// + /// Read a pending message from any connection, blocking up to maxMillis if needed + /// + public NetIncomingMessage WaitMessage(int maxMillis) + { + NetIncomingMessage msg = ReadMessage(); + + while (msg == null) + { + // This could return true... + if (!MessageReceivedEvent.WaitOne(maxMillis)) + { + return null; + } + + // ... while this will still returns null. That's why we need to cycle. + msg = ReadMessage(); + } + + return msg; + } + + /// + /// Read a pending message from any connection, if any + /// + public NetIncomingMessage ReadMessage() + { + NetIncomingMessage retval; + if (m_releasedIncomingMessages.TryDequeue(out retval)) + { + if (retval.MessageType == NetIncomingMessageType.StatusChanged) + { + NetConnectionStatus status = (NetConnectionStatus)retval.PeekByte(); + retval.SenderConnection.m_visibleStatus = status; + } + } + return retval; + } + + /// + /// Reads a pending message from any connection, if any. + /// Returns true if message was read, otherwise false. + /// + /// True, if message was read. + public bool ReadMessage(out NetIncomingMessage message) + { + message = ReadMessage(); + return message != null; + } + + /// + /// Read a pending message from any connection, if any + /// + public int ReadMessages(IList addTo) + { + int added = m_releasedIncomingMessages.TryDrain(addTo); + if (added > 0) + { + for (int i = 0; i < added; i++) + { + var index = addTo.Count - added + i; + var nim = addTo[index]; + if (nim.MessageType == NetIncomingMessageType.StatusChanged) + { + NetConnectionStatus status = (NetConnectionStatus)nim.PeekByte(); + nim.SenderConnection.m_visibleStatus = status; + } + } + } + return added; + } + + // send message immediately and recycle it + internal void SendLibrary(NetOutgoingMessage msg, NetEndPoint recipient) + { + VerifyNetworkThread(); + NetException.Assert(msg.m_isSent == false); + + bool connReset; + int len = msg.Encode(m_sendBuffer, 0, 0); + SendPacket(len, recipient, 1, out connReset); + + // no reliability, no multiple recipients - we can just recycle this message immediately + msg.m_recyclingCount = 0; + Recycle(msg); + } + + static NetEndPoint GetNetEndPoint(string host, int port) + { + IPAddress address = NetUtility.Resolve(host); + if (address == null) + throw new NetException("Could not resolve host"); + return new NetEndPoint(address, port); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(string host, int port) + { + return Connect(GetNetEndPoint(host, port), null); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(string host, int port, NetOutgoingMessage hailMessage) + { + return Connect(GetNetEndPoint(host, port), hailMessage); + } + + /// + /// Create a connection to a remote endpoint + /// + public NetConnection Connect(NetEndPoint remoteEndPoint) + { + return Connect(remoteEndPoint, null); + } + + /// + /// Create a connection to a remote endpoint + /// + public virtual NetConnection Connect(NetEndPoint remoteEndPoint, NetOutgoingMessage hailMessage) + { + if (remoteEndPoint == null) + throw new ArgumentNullException("remoteEndPoint"); + + lock (m_connections) + { + if (m_status == NetPeerStatus.NotRunning) + throw new NetException("Must call Start() first"); + + if (m_connectionLookup.ContainsKey(remoteEndPoint)) + throw new NetException("Already connected to that endpoint!"); + + NetConnection hs; + if (m_handshakes.TryGetValue(remoteEndPoint, out hs)) + { + // already trying to connect to that endpoint; make another try + switch (hs.m_status) + { + case NetConnectionStatus.InitiatedConnect: + // send another connect + hs.m_connectRequested = true; + break; + case NetConnectionStatus.RespondedConnect: + // send another response + hs.SendConnectResponse(NetTime.Now, false); + break; + default: + // weird + LogWarning("Weird situation; Connect() already in progress to remote endpoint; but hs status is " + hs.m_status); + break; + } + return hs; + } + + NetConnection conn = new NetConnection(this, remoteEndPoint); + conn.SetStatus(NetConnectionStatus.InitiatedConnect, "user called connect"); + conn.m_localHailMessage = hailMessage; + + // handle on network thread + conn.m_connectRequested = true; + conn.m_connectionInitiator = true; + + m_handshakes.Add(remoteEndPoint, conn); + + return conn; + } + } + + /// + /// Send raw bytes; only used for debugging + /// + public void RawSend(byte[] arr, int offset, int length, NetEndPoint destination) + { + // wrong thread - this miiiight crash with network thread... but what's a boy to do. + Array.Copy(arr, offset, m_sendBuffer, 0, length); + bool unused; + SendPacket(length, destination, 1, out unused); + } + + /// + /// In DEBUG, throws an exception, in RELEASE logs an error message + /// + /// + internal void ThrowOrLog(string message) + { +#if DEBUG + throw new NetException(message); +#else + LogError(message); +#endif + } + + /// + /// Disconnects all active connections and closes the socket + /// + public void Shutdown(string bye) + { + // called on user thread + if (m_socket == null) + return; // already shut down + + LogDebug("Shutdown requested"); + m_shutdownReason = bye; + m_status = NetPeerStatus.ShutdownRequested; + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeerConfiguration.cs b/SRMP/Lidgren.Network/NetPeerConfiguration.cs new file mode 100644 index 0000000..e041aaa --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeerConfiguration.cs @@ -0,0 +1,541 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Net; + +namespace Lidgren.Network +{ + /// + /// Partly immutable after NetPeer has been initialized + /// + public sealed class NetPeerConfiguration + { + // Maximum transmission unit + // Ethernet can take 1500 bytes of payload, so lets stay below that. + // The aim is for a max full packet to be 1440 bytes (30 x 48 bytes, lower than 1468) + // -20 bytes IP header + // -8 bytes UDP header + // -4 bytes to be on the safe side and align to 8-byte boundary + // Total 1408 bytes + // Note that lidgren headers (5 bytes) are not included here; since it's part of the "mtu payload" + + /// + /// Default MTU value in bytes + /// + public const int kDefaultMTU = 1408; + + private const string c_isLockedMessage = "You may not modify the NetPeerConfiguration after it has been used to initialize a NetPeer"; + + private bool m_isLocked; + private readonly string m_appIdentifier; + private string m_networkThreadName; + private IPAddress m_localAddress; + private IPAddress m_broadcastAddress; + internal bool m_acceptIncomingConnections; + internal int m_maximumConnections; + internal int m_defaultOutgoingMessageCapacity; + internal float m_pingInterval; + internal bool m_useMessageRecycling; + internal int m_recycledCacheMaxCount; + internal float m_connectionTimeout; + internal bool m_enableUPnP; + internal bool m_autoFlushSendQueue; + private NetUnreliableSizeBehaviour m_unreliableSizeBehaviour; + internal bool m_suppressUnreliableUnorderedAcks; + + internal NetIncomingMessageType m_disabledTypes; + internal int m_port; + internal int m_receiveBufferSize; + internal int m_sendBufferSize; + internal float m_resendHandshakeInterval; + internal int m_maximumHandshakeAttempts; + + // bad network simulation + internal float m_loss; + internal float m_duplicates; + internal float m_minimumOneWayLatency; + internal float m_randomOneWayLatency; + + // MTU + internal int m_maximumTransmissionUnit; + internal bool m_autoExpandMTU; + internal float m_expandMTUFrequency; + internal int m_expandMTUFailAttempts; + + /// + /// NetPeerConfiguration constructor + /// + public NetPeerConfiguration(string appIdentifier) + { + if (string.IsNullOrEmpty(appIdentifier)) + throw new NetException("App identifier must be at least one character long"); + m_appIdentifier = appIdentifier; + + // + // default values + // + m_disabledTypes = NetIncomingMessageType.ConnectionApproval | NetIncomingMessageType.UnconnectedData | NetIncomingMessageType.VerboseDebugMessage | NetIncomingMessageType.ConnectionLatencyUpdated | NetIncomingMessageType.NatIntroductionSuccess; + m_networkThreadName = "Lidgren network thread"; + m_localAddress = IPAddress.Any; + m_broadcastAddress = IPAddress.Broadcast; + var ip = NetUtility.GetBroadcastAddress(); + if (ip != null) + { + m_broadcastAddress = ip; + } + m_port = 0; + m_receiveBufferSize = 131071; + m_sendBufferSize = 131071; + m_acceptIncomingConnections = false; + m_maximumConnections = 32; + m_defaultOutgoingMessageCapacity = 16; + m_pingInterval = 4.0f; + m_connectionTimeout = 25.0f; + m_useMessageRecycling = true; + m_recycledCacheMaxCount = 64; + m_resendHandshakeInterval = 3.0f; + m_maximumHandshakeAttempts = 5; + m_autoFlushSendQueue = true; + m_suppressUnreliableUnorderedAcks = false; + + m_maximumTransmissionUnit = kDefaultMTU; + m_autoExpandMTU = false; + m_expandMTUFrequency = 2.0f; + m_expandMTUFailAttempts = 5; + m_unreliableSizeBehaviour = NetUnreliableSizeBehaviour.IgnoreMTU; + + m_loss = 0.0f; + m_minimumOneWayLatency = 0.0f; + m_randomOneWayLatency = 0.0f; + m_duplicates = 0.0f; + + m_isLocked = false; + } + + internal void Lock() + { + m_isLocked = true; + } + + /// + /// Gets the identifier of this application; the library can only connect to matching app identifier peers + /// + public string AppIdentifier + { + get { return m_appIdentifier; } + } + + /// + /// Enables receiving of the specified type of message + /// + public void EnableMessageType(NetIncomingMessageType type) + { + m_disabledTypes &= (~type); + } + + /// + /// Disables receiving of the specified type of message + /// + public void DisableMessageType(NetIncomingMessageType type) + { + m_disabledTypes |= type; + } + + /// + /// Enables or disables receiving of the specified type of message + /// + public void SetMessageTypeEnabled(NetIncomingMessageType type, bool enabled) + { + if (enabled) + m_disabledTypes &= (~type); + else + m_disabledTypes |= type; + } + + /// + /// Gets if receiving of the specified type of message is enabled + /// + public bool IsMessageTypeEnabled(NetIncomingMessageType type) + { + return !((m_disabledTypes & type) == type); + } + + /// + /// Gets or sets the behaviour of unreliable sends above MTU + /// + public NetUnreliableSizeBehaviour UnreliableSizeBehaviour + { + get { return m_unreliableSizeBehaviour; } + set { m_unreliableSizeBehaviour = value; } + } + + /// + /// Gets or sets the name of the library network thread. Cannot be changed once NetPeer is initialized. + /// + public string NetworkThreadName + { + get { return m_networkThreadName; } + set + { + if (m_isLocked) + throw new NetException("NetworkThreadName may not be set after the NetPeer which uses the configuration has been started"); + m_networkThreadName = value; + } + } + + /// + /// Gets or sets the maximum amount of connections this peer can hold. Cannot be changed once NetPeer is initialized. + /// + public int MaximumConnections + { + get { return m_maximumConnections; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_maximumConnections = value; + } + } + + /// + /// Gets or sets the maximum amount of bytes to send in a single packet, excluding ip, udp and lidgren headers. Cannot be changed once NetPeer is initialized. + /// + public int MaximumTransmissionUnit + { + get { return m_maximumTransmissionUnit; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + if (value < 1 || value >= ((ushort.MaxValue + 1) / 8)) + throw new NetException("MaximumTransmissionUnit must be between 1 and " + (((ushort.MaxValue + 1) / 8) - 1) + " bytes"); + m_maximumTransmissionUnit = value; + } + } + + /// + /// Gets or sets the default capacity in bytes when NetPeer.CreateMessage() is called without argument + /// + public int DefaultOutgoingMessageCapacity + { + get { return m_defaultOutgoingMessageCapacity; } + set { m_defaultOutgoingMessageCapacity = value; } + } + + /// + /// Gets or sets the time between latency calculating pings + /// + public float PingInterval + { + get { return m_pingInterval; } + set { m_pingInterval = value; } + } + + /// + /// Gets or sets if the library should recycling messages to avoid excessive garbage collection. Cannot be changed once NetPeer is initialized. + /// + public bool UseMessageRecycling + { + get { return m_useMessageRecycling; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_useMessageRecycling = value; + } + } + + /// + /// Gets or sets the maximum number of incoming/outgoing messages to keep in the recycle cache. + /// + public int RecycledCacheMaxCount + { + get { return m_recycledCacheMaxCount; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_recycledCacheMaxCount = value; + } + } + + /// + /// Gets or sets the number of seconds timeout will be postponed on a successful ping/pong + /// + public float ConnectionTimeout + { + get { return m_connectionTimeout; } + set + { + if (value < m_pingInterval) + throw new NetException("Connection timeout cannot be lower than ping interval!"); + m_connectionTimeout = value; + } + } + + /// + /// Enables UPnP support; enabling port forwarding and getting external ip + /// + public bool EnableUPnP + { + get { return m_enableUPnP; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_enableUPnP = value; + } + } + + /// + /// Enables or disables automatic flushing of the send queue. If disabled, you must manully call NetPeer.FlushSendQueue() to flush sent messages to network. + /// + public bool AutoFlushSendQueue + { + get { return m_autoFlushSendQueue; } + set { m_autoFlushSendQueue = value; } + } + + /// + /// If true, will not send acks for unreliable unordered messages. This will save bandwidth, but disable flow control and duplicate detection for this type of messages. + /// + public bool SuppressUnreliableUnorderedAcks + { + get { return m_suppressUnreliableUnorderedAcks; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_suppressUnreliableUnorderedAcks = value; + } + } + + /// + /// Gets or sets the local ip address to bind to. Defaults to IPAddress.Any. Cannot be changed once NetPeer is initialized. + /// + public IPAddress LocalAddress + { + get { return m_localAddress; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_localAddress = value; + } + } + + /// + /// Gets or sets the local broadcast address to use when broadcasting + /// + public IPAddress BroadcastAddress + { + get { return m_broadcastAddress; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_broadcastAddress = value; + } + } + + /// + /// Gets or sets the local port to bind to. Defaults to 0. Cannot be changed once NetPeer is initialized. + /// + public int Port + { + get { return m_port; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_port = value; + } + } + + /// + /// Gets or sets the size in bytes of the receiving buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. + /// + public int ReceiveBufferSize + { + get { return m_receiveBufferSize; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_receiveBufferSize = value; + } + } + + /// + /// Gets or sets the size in bytes of the sending buffer. Defaults to 131071 bytes. Cannot be changed once NetPeer is initialized. + /// + public int SendBufferSize + { + get { return m_sendBufferSize; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_sendBufferSize = value; + } + } + + /// + /// Gets or sets if the NetPeer should accept incoming connections. This is automatically set to true in NetServer and false in NetClient. + /// + public bool AcceptIncomingConnections + { + get { return m_acceptIncomingConnections; } + set { m_acceptIncomingConnections = value; } + } + + /// + /// Gets or sets the number of seconds between handshake attempts + /// + public float ResendHandshakeInterval + { + get { return m_resendHandshakeInterval; } + set { m_resendHandshakeInterval = value; } + } + + /// + /// Gets or sets the maximum number of handshake attempts before failing to connect + /// + public int MaximumHandshakeAttempts + { + get { return m_maximumHandshakeAttempts; } + set + { + if (value < 1) + throw new NetException("MaximumHandshakeAttempts must be at least 1"); + m_maximumHandshakeAttempts = value; + } + } + + /// + /// Gets or sets if the NetPeer should send large messages to try to expand the maximum transmission unit size + /// + public bool AutoExpandMTU + { + get { return m_autoExpandMTU; } + set + { + if (m_isLocked) + throw new NetException(c_isLockedMessage); + m_autoExpandMTU = value; + } + } + + /// + /// Gets or sets how often to send large messages to expand MTU if AutoExpandMTU is enabled + /// + public float ExpandMTUFrequency + { + get { return m_expandMTUFrequency; } + set { m_expandMTUFrequency = value; } + } + + /// + /// Gets or sets the number of failed expand mtu attempts to perform before setting final MTU + /// + public int ExpandMTUFailAttempts + { + get { return m_expandMTUFailAttempts; } + set { m_expandMTUFailAttempts = value; } + } + +#if DEBUG + /// + /// Gets or sets the simulated amount of sent packets lost from 0.0f to 1.0f + /// + public float SimulatedLoss + { + get { return m_loss; } + set { m_loss = value; } + } + + /// + /// Gets or sets the minimum simulated amount of one way latency for sent packets in seconds + /// + public float SimulatedMinimumLatency + { + get { return m_minimumOneWayLatency; } + set { m_minimumOneWayLatency = value; } + } + + /// + /// Gets or sets the simulated added random amount of one way latency for sent packets in seconds + /// + public float SimulatedRandomLatency + { + get { return m_randomOneWayLatency; } + set { m_randomOneWayLatency = value; } + } + + /// + /// Gets the average simulated one way latency in seconds + /// + public float SimulatedAverageLatency + { + get { return m_minimumOneWayLatency + (m_randomOneWayLatency * 0.5f); } + } + + /// + /// Gets or sets the simulated amount of duplicated packets from 0.0f to 1.0f + /// + public float SimulatedDuplicatesChance + { + get { return m_duplicates; } + set { m_duplicates = value; } + } +#endif + + /// + /// Creates a memberwise shallow clone of this configuration + /// + public NetPeerConfiguration Clone() + { + NetPeerConfiguration retval = this.MemberwiseClone() as NetPeerConfiguration; + retval.m_isLocked = false; + return retval; + } + } + + /// + /// Behaviour of unreliable sends above MTU + /// + public enum NetUnreliableSizeBehaviour + { + /// + /// Sending an unreliable message will ignore MTU and send everything in a single packet; this is the new default + /// + IgnoreMTU = 0, + + /// + /// Old behaviour; use normal fragmentation for unreliable messages - if a fragment is dropped, memory for received fragments are never reclaimed! + /// + NormalFragmentation = 1, + + /// + /// Alternate behaviour; just drops unreliable messages above MTU + /// + DropAboveMTU = 2, + } +} diff --git a/SRMP/Lidgren.Network/NetPeerStatistics.cs b/SRMP/Lidgren.Network/NetPeerStatistics.cs new file mode 100644 index 0000000..c55dafc --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeerStatistics.cs @@ -0,0 +1,158 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ + +// Uncomment the line below to get statistics in RELEASE builds +//#define USE_RELEASE_STATISTICS + +using System; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Statistics for a NetPeer instance + /// + public sealed class NetPeerStatistics + { + private readonly NetPeer m_peer; + + internal int m_sentPackets; + internal int m_receivedPackets; + + internal int m_sentMessages; + internal int m_receivedMessages; + internal int m_receivedFragments; + + internal int m_sentBytes; + internal int m_receivedBytes; + + internal long m_bytesAllocated; + + internal NetPeerStatistics(NetPeer peer) + { + m_peer = peer; + Reset(); + } + + internal void Reset() + { + m_sentPackets = 0; + m_receivedPackets = 0; + + m_sentMessages = 0; + m_receivedMessages = 0; + m_receivedFragments = 0; + + m_sentBytes = 0; + m_receivedBytes = 0; + + m_bytesAllocated = 0; + } + + /// + /// Gets the number of sent packets since the NetPeer was initialized + /// + public int SentPackets { get { return m_sentPackets; } } + + /// + /// Gets the number of received packets since the NetPeer was initialized + /// + public int ReceivedPackets { get { return m_receivedPackets; } } + + /// + /// Gets the number of sent messages since the NetPeer was initialized + /// + public int SentMessages { get { return m_sentMessages; } } + + /// + /// Gets the number of received messages since the NetPeer was initialized + /// + public int ReceivedMessages { get { return m_receivedMessages; } } + + /// + /// Gets the number of sent bytes since the NetPeer was initialized + /// + public int SentBytes { get { return m_sentBytes; } } + + /// + /// Gets the number of received bytes since the NetPeer was initialized + /// + public int ReceivedBytes { get { return m_receivedBytes; } } + + /// + /// Gets the number of bytes allocated (and possibly garbage collected) for message storage + /// + public long StorageBytesAllocated { get { return m_bytesAllocated; } } + + /// + /// Gets the number of bytes in the recycled pool + /// + public int BytesInRecyclePool + { + get + { + lock (m_peer.m_storagePool) + return m_peer.m_storagePoolBytes; + } + } + +#if !USE_RELEASE_STATISTICS + [Conditional("DEBUG")] +#endif + internal void PacketSent(int numBytes, int numMessages) + { + m_sentPackets++; + m_sentBytes += numBytes; + m_sentMessages += numMessages; + } + +#if !USE_RELEASE_STATISTICS + [Conditional("DEBUG")] +#endif + internal void PacketReceived(int numBytes, int numMessages, int numFragments) + { + m_receivedPackets++; + m_receivedBytes += numBytes; + m_receivedMessages += numMessages; + m_receivedFragments += numFragments; + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + StringBuilder bdr = new StringBuilder(); + bdr.AppendLine(m_peer.ConnectionsCount.ToString() + " connections"); +#if DEBUG || USE_RELEASE_STATISTICS + bdr.AppendLine("Sent " + m_sentBytes + " bytes in " + m_sentMessages + " messages in " + m_sentPackets + " packets"); + bdr.AppendLine("Received " + m_receivedBytes + " bytes in " + m_receivedMessages + " messages (of which " + m_receivedFragments + " fragments) in " + m_receivedPackets + " packets"); +#else + bdr.AppendLine("Sent (n/a) bytes in (n/a) messages in (n/a) packets"); + bdr.AppendLine("Received (n/a) bytes in (n/a) messages in (n/a) packets"); +#endif + bdr.AppendLine("Storage allocated " + m_bytesAllocated + " bytes"); + if (m_peer.m_storagePool != null) + bdr.AppendLine("Recycled pool " + m_peer.m_storagePoolBytes + " bytes (" + m_peer.m_storageSlotsUsedCount + " entries)"); + return bdr.ToString(); + } + } +} diff --git a/SRMP/Lidgren.Network/NetPeerStatus.cs b/SRMP/Lidgren.Network/NetPeerStatus.cs new file mode 100644 index 0000000..3be8a1d --- /dev/null +++ b/SRMP/Lidgren.Network/NetPeerStatus.cs @@ -0,0 +1,49 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; + +namespace Lidgren.Network +{ + /// + /// Status for a NetPeer instance + /// + public enum NetPeerStatus + { + /// + /// NetPeer is not running; socket is not bound + /// + NotRunning = 0, + + /// + /// NetPeer is in the process of starting up + /// + Starting = 1, + + /// + /// NetPeer is bound to socket and listening for packets + /// + Running = 2, + + /// + /// Shutdown has been requested and will be executed shortly + /// + ShutdownRequested = 3, + } +} diff --git a/SRMP/Lidgren.Network/NetQueue.cs b/SRMP/Lidgren.Network/NetQueue.cs new file mode 100644 index 0000000..a86a7bd --- /dev/null +++ b/SRMP/Lidgren.Network/NetQueue.cs @@ -0,0 +1,356 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.Threading; + +// +// Comment for Linux Mono users: reports of library thread hangs on EnterReadLock() suggests switching to plain lock() works better +// + +namespace Lidgren.Network +{ + /// + /// Thread safe (blocking) expanding queue with TryDequeue() and EnqueueFirst() + /// + [DebuggerDisplay("Count={Count} Capacity={Capacity}")] + public sealed class NetQueue + { + // Example: + // m_capacity = 8 + // m_size = 6 + // m_head = 4 + // + // [0] item + // [1] item (tail = ((head + size - 1) % capacity) + // [2] + // [3] + // [4] item (head) + // [5] item + // [6] item + // [7] item + // + private T[] m_items; + private readonly ReaderWriterLockSlim m_lock = new ReaderWriterLockSlim(); + private int m_size; + private int m_head; + + /// + /// Gets the number of items in the queue + /// + public int Count { + get + { + m_lock.EnterReadLock(); + int count = m_size; + m_lock.ExitReadLock(); + return count; + } + } + + /// + /// Gets the current capacity for the queue + /// + public int Capacity + { + get + { + m_lock.EnterReadLock(); + int capacity = m_items.Length; + m_lock.ExitReadLock(); + return capacity; + } + } + + /// + /// NetQueue constructor + /// + public NetQueue(int initialCapacity) + { + m_items = new T[initialCapacity]; + } + + /// + /// Adds an item last/tail of the queue + /// + public void Enqueue(T item) + { + m_lock.EnterWriteLock(); + try + { + if (m_size == m_items.Length) + SetCapacity(m_items.Length + 8); + + int slot = (m_head + m_size) % m_items.Length; + m_items[slot] = item; + m_size++; + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Adds an item last/tail of the queue + /// + public void Enqueue(IEnumerable items) + { + m_lock.EnterWriteLock(); + try + { + foreach (var item in items) + { + if (m_size == m_items.Length) + SetCapacity(m_items.Length + 8); // @TODO move this out of loop + + int slot = (m_head + m_size) % m_items.Length; + m_items[slot] = item; + m_size++; + } + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Places an item first, at the head of the queue + /// + public void EnqueueFirst(T item) + { + m_lock.EnterWriteLock(); + try + { + if (m_size >= m_items.Length) + SetCapacity(m_items.Length + 8); + + m_head--; + if (m_head < 0) + m_head = m_items.Length - 1; + m_items[m_head] = item; + m_size++; + } + finally + { + m_lock.ExitWriteLock(); + } + } + + // must be called from within a write locked m_lock! + private void SetCapacity(int newCapacity) + { + if (m_size == 0) + { + if (m_size == 0) + { + m_items = new T[newCapacity]; + m_head = 0; + return; + } + } + + T[] newItems = new T[newCapacity]; + + if (m_head + m_size - 1 < m_items.Length) + { + Array.Copy(m_items, m_head, newItems, 0, m_size); + } + else + { + Array.Copy(m_items, m_head, newItems, 0, m_items.Length - m_head); + Array.Copy(m_items, 0, newItems, m_items.Length - m_head, (m_size - (m_items.Length - m_head))); + } + + m_items = newItems; + m_head = 0; + + } + + /// + /// Gets an item from the head of the queue, or returns default(T) if empty + /// + public bool TryDequeue(out T item) + { + if (m_size == 0) + { + item = default(T); + return false; + } + + m_lock.EnterWriteLock(); + try + { + if (m_size == 0) + { + item = default(T); + return false; + } + + item = m_items[m_head]; + m_items[m_head] = default(T); + + m_head = (m_head + 1) % m_items.Length; + m_size--; + + return true; + } + catch + { +#if DEBUG + throw; +#else + item = default(T); + return false; +#endif + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Gets all items from the head of the queue, or returns number of items popped + /// + public int TryDrain(IList addTo) + { + if (m_size == 0) + return 0; + + m_lock.EnterWriteLock(); + try + { + int added = m_size; + while (m_size > 0) + { + var item = m_items[m_head]; + addTo.Add(item); + + m_items[m_head] = default(T); + m_head = (m_head + 1) % m_items.Length; + m_size--; + } + return added; + } + finally + { + m_lock.ExitWriteLock(); + } + } + + /// + /// Returns default(T) if queue is empty + /// + public T TryPeek(int offset) + { + if (m_size == 0) + return default(T); + + m_lock.EnterReadLock(); + try + { + if (m_size == 0) + return default(T); + return m_items[(m_head + offset) % m_items.Length]; + } + finally + { + m_lock.ExitReadLock(); + } + } + + /// + /// Determines whether an item is in the queue + /// + public bool Contains(T item) + { + m_lock.EnterReadLock(); + try + { + int ptr = m_head; + for (int i = 0; i < m_size; i++) + { + if (m_items[ptr] == null) + { + if (item == null) + return true; + } + else + { + if (m_items[ptr].Equals(item)) + return true; + } + ptr = (ptr + 1) % m_items.Length; + } + return false; + } + finally + { + m_lock.ExitReadLock(); + } + } + + /// + /// Copies the queue items to a new array + /// + public T[] ToArray() + { + m_lock.EnterReadLock(); + try + { + T[] retval = new T[m_size]; + int ptr = m_head; + for (int i = 0; i < m_size; i++) + { + retval[i] = m_items[ptr++]; + if (ptr >= m_items.Length) + ptr = 0; + } + return retval; + } + finally + { + m_lock.ExitReadLock(); + } + } + + /// + /// Removes all objects from the queue + /// + public void Clear() + { + m_lock.EnterWriteLock(); + try + { + for (int i = 0; i < m_items.Length; i++) + m_items[i] = default(T); + m_head = 0; + m_size = 0; + } + finally + { + m_lock.ExitWriteLock(); + } + } + } +} diff --git a/SRMP/Lidgren.Network/NetRandom.Implementations.cs b/SRMP/Lidgren.Network/NetRandom.Implementations.cs new file mode 100644 index 0000000..1a7bff3 --- /dev/null +++ b/SRMP/Lidgren.Network/NetRandom.Implementations.cs @@ -0,0 +1,281 @@ +using System; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + /// + /// Multiply With Carry random + /// + public class MWCRandom : NetRandom + { + /// + /// Get global instance of MWCRandom + /// + public static new readonly MWCRandom Instance = new MWCRandom(); + + private uint m_w, m_z; + + /// + /// Constructor with randomized seed + /// + public MWCRandom() + { + Initialize(NetRandomSeed.GetUInt64()); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + m_w = seed; + m_z = seed * 16777619; + } + + /// + /// (Re)initialize this instance with provided 64 bit seed + /// + [CLSCompliant(false)] + public void Initialize(ulong seed) + { + m_w = (uint)seed; + m_z = (uint)(seed >> 32); + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + m_z = 36969 * (m_z & 65535) + (m_z >> 16); + m_w = 18000 * (m_w & 65535) + (m_w >> 16); + return ((m_z << 16) + m_w); + } + } + + /// + /// Xor Shift based random + /// + public sealed class XorShiftRandom : NetRandom + { + /// + /// Get global instance of XorShiftRandom + /// + public static new readonly XorShiftRandom Instance = new XorShiftRandom(); + + private const uint c_x = 123456789; + private const uint c_y = 362436069; + private const uint c_z = 521288629; + private const uint c_w = 88675123; + + private uint m_x, m_y, m_z, m_w; + + /// + /// Constructor with randomized seed + /// + public XorShiftRandom() + { + Initialize(NetRandomSeed.GetUInt64()); + } + + /// + /// Constructor with provided 64 bit seed + /// + [CLSCompliant(false)] + public XorShiftRandom(ulong seed) + { + Initialize(seed); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + m_x = (uint)seed; + m_y = c_y; + m_z = c_z; + m_w = c_w; + } + + /// + /// (Re)initialize this instance with provided 64 bit seed + /// + [CLSCompliant(false)] + public void Initialize(ulong seed) + { + m_x = (uint)seed; + m_y = c_y; + m_z = (uint)(seed << 32); + m_w = c_w; + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + uint t = (m_x ^ (m_x << 11)); + m_x = m_y; m_y = m_z; m_z = m_w; + return (m_w = (m_w ^ (m_w >> 19)) ^ (t ^ (t >> 8))); + } + } + + /// + /// Mersenne Twister based random + /// + public sealed class MersenneTwisterRandom : NetRandom + { + /// + /// Get global instance of MersenneTwisterRandom + /// + public static new readonly MersenneTwisterRandom Instance = new MersenneTwisterRandom(); + + private const int N = 624; + private const int M = 397; + private const uint MATRIX_A = 0x9908b0dfU; + private const uint UPPER_MASK = 0x80000000U; + private const uint LOWER_MASK = 0x7fffffffU; + private const uint TEMPER1 = 0x9d2c5680U; + private const uint TEMPER2 = 0xefc60000U; + private const int TEMPER3 = 11; + private const int TEMPER4 = 7; + private const int TEMPER5 = 15; + private const int TEMPER6 = 18; + + private UInt32[] mt; + private int mti; + private UInt32[] mag01; + + private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0); + + /// + /// Constructor with randomized seed + /// + public MersenneTwisterRandom() + { + Initialize(NetRandomSeed.GetUInt32()); + } + + /// + /// Constructor with provided 32 bit seed + /// + [CLSCompliant(false)] + public MersenneTwisterRandom(uint seed) + { + Initialize(seed); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + mt = new UInt32[N]; + mti = N + 1; + mag01 = new UInt32[] { 0x0U, MATRIX_A }; + mt[0] = seed; + for (int i = 1; i < N; i++) + mt[i] = (UInt32)(1812433253 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i); + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + UInt32 y; + if (mti >= N) + { + GenRandAll(); + mti = 0; + } + y = mt[mti++]; + y ^= (y >> TEMPER3); + y ^= (y << TEMPER4) & TEMPER1; + y ^= (y << TEMPER5) & TEMPER2; + y ^= (y >> TEMPER6); + return y; + } + + private void GenRandAll() + { + int kk = 1; + UInt32 y; + UInt32 p; + y = mt[0] & UPPER_MASK; + do + { + p = mt[kk]; + mt[kk - 1] = mt[kk + (M - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1]; + y = p & UPPER_MASK; + } while (++kk < N - M + 1); + do + { + p = mt[kk]; + mt[kk - 1] = mt[kk + (M - N - 1)] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1]; + y = p & UPPER_MASK; + } while (++kk < N); + p = mt[0]; + mt[N - 1] = mt[M - 1] ^ ((y | (p & LOWER_MASK)) >> 1) ^ mag01[p & 1]; + } + } + + /// + /// RNGCryptoServiceProvider based random; very slow but cryptographically safe + /// + public class CryptoRandom : NetRandom + { + /// + /// Global instance of CryptoRandom + /// + public static new readonly CryptoRandom Instance = new CryptoRandom(); + + private RandomNumberGenerator m_rnd = new RNGCryptoServiceProvider(); + + /// + /// Seed in CryptoRandom does not create deterministic sequences + /// + [CLSCompliant(false)] + public override void Initialize(uint seed) + { + byte[] tmp = new byte[seed % 16]; + m_rnd.GetBytes(tmp); // just prime it + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public override uint NextUInt32() + { + var bytes = new byte[4]; + m_rnd.GetBytes(bytes); + return (uint)bytes[0] | (((uint)bytes[1]) << 8) | (((uint)bytes[2]) << 16) | (((uint)bytes[3]) << 24); + } + + /// + /// Fill the specified buffer with random values + /// + public override void NextBytes(byte[] buffer) + { + m_rnd.GetBytes(buffer); + } + + /// + /// Fills all bytes from offset to offset + length in buffer with random values + /// + public override void NextBytes(byte[] buffer, int offset, int length) + { + var bytes = new byte[length]; + m_rnd.GetBytes(bytes); + Array.Copy(bytes, 0, buffer, offset, length); + } + } +} diff --git a/SRMP/Lidgren.Network/NetRandom.cs b/SRMP/Lidgren.Network/NetRandom.cs new file mode 100644 index 0000000..155b21c --- /dev/null +++ b/SRMP/Lidgren.Network/NetRandom.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// NetRandom base class + /// + public abstract class NetRandom : Random + { + /// + /// Get global instance of NetRandom (uses MWCRandom) + /// + public static NetRandom Instance = new MWCRandom(); + + private const double c_realUnitInt = 1.0 / ((double)int.MaxValue + 1.0); + + /// + /// Constructor with randomized seed + /// + public NetRandom() + { + Initialize(NetRandomSeed.GetUInt32()); + } + + /// + /// Constructor with provided 32 bit seed + /// + public NetRandom(int seed) + { + Initialize((uint)seed); + } + + /// + /// (Re)initialize this instance with provided 32 bit seed + /// + [CLSCompliant(false)] + public virtual void Initialize(uint seed) + { + // should be abstract, but non-CLS compliant methods can't be abstract! + throw new NotImplementedException("Implement this in inherited classes"); + } + + /// + /// Generates a random value from UInt32.MinValue to UInt32.MaxValue, inclusively + /// + [CLSCompliant(false)] + public virtual uint NextUInt32() + { + // should be abstract, but non-CLS compliant methods can't be abstract! + throw new NotImplementedException("Implement this in inherited classes"); + } + + /// + /// Generates a random value that is greater or equal than 0 and less than Int32.MaxValue + /// + public override int Next() + { + var retval = (int)(0x7FFFFFFF & NextUInt32()); + if (retval == 0x7FFFFFFF) + return NextInt32(); + return retval; + } + + /// + /// Generates a random value greater or equal than 0 and less or equal than Int32.MaxValue (inclusively) + /// + public int NextInt32() + { + return (int)(0x7FFFFFFF & NextUInt32()); + } + + /// + /// Returns random value larger or equal to 0.0 and less than 1.0 + /// + public override double NextDouble() + { + return c_realUnitInt * NextInt32(); + } + + /// + /// Returns random value is greater or equal than 0.0 and less than 1.0 + /// + protected override double Sample() + { + return c_realUnitInt * NextInt32(); + } + + /// + /// Returns random value is greater or equal than 0.0f and less than 1.0f + /// + public float NextSingle() + { + var retval = (float)(c_realUnitInt * NextInt32()); + if (retval == 1.0f) + return NextSingle(); + return retval; + } + + /// + /// Returns a random value is greater or equal than 0 and less than maxValue + /// + public override int Next(int maxValue) + { + return (int)(NextDouble() * maxValue); + } + + /// + /// Returns a random value is greater or equal than minValue and less than maxValue + /// + public override int Next(int minValue, int maxValue) + { + return minValue + (int)(NextDouble() * (double)(maxValue - minValue)); + } + + /// + /// Generates a random value between UInt64.MinValue to UInt64.MaxValue + /// + [CLSCompliant(false)] + public ulong NextUInt64() + { + ulong retval = NextUInt32(); + retval |= NextUInt32() << 32; + return retval; + } + + private uint m_boolValues; + private int m_nextBoolIndex; + + /// + /// Returns true or false, randomly + /// + public bool NextBool() + { + if (m_nextBoolIndex >= 32) + { + m_boolValues = NextUInt32(); + m_nextBoolIndex = 1; + } + + var retval = ((m_boolValues >> m_nextBoolIndex) & 1) == 1; + m_nextBoolIndex++; + return retval; + } + + + /// + /// Fills all bytes from offset to offset + length in buffer with random values + /// + public virtual void NextBytes(byte[] buffer, int offset, int length) + { + int full = length / 4; + int ptr = offset; + for (int i = 0; i < full; i++) + { + uint r = NextUInt32(); + buffer[ptr++] = (byte)r; + buffer[ptr++] = (byte)(r >> 8); + buffer[ptr++] = (byte)(r >> 16); + buffer[ptr++] = (byte)(r >> 24); + } + + int rest = length - (full * 4); + for (int i = 0; i < rest; i++) + buffer[ptr++] = (byte)NextUInt32(); + } + + /// + /// Fill the specified buffer with random values + /// + public override void NextBytes(byte[] buffer) + { + NextBytes(buffer, 0, buffer.Length); + } + } +} diff --git a/SRMP/Lidgren.Network/NetRandomSeed.cs b/SRMP/Lidgren.Network/NetRandomSeed.cs new file mode 100644 index 0000000..3835b39 --- /dev/null +++ b/SRMP/Lidgren.Network/NetRandomSeed.cs @@ -0,0 +1,45 @@ +using System; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Class for generating random seeds + /// + public static class NetRandomSeed + { + private static int m_seedIncrement = -1640531527; + + /// + /// Generates a 32 bit random seed + /// + [CLSCompliant(false)] + public static uint GetUInt32() + { + ulong seed = GetUInt64(); + uint low = (uint)seed; + uint high = (uint)(seed >> 32); + return low ^ high; + } + + /// + /// Generates a 64 bit random seed + /// + [CLSCompliant(false)] + public static ulong GetUInt64() + { + var guidBytes = Guid.NewGuid().ToByteArray(); + ulong seed = + ((ulong)guidBytes[0] << (8 * 0)) | + ((ulong)guidBytes[1] << (8 * 1)) | + ((ulong)guidBytes[2] << (8 * 2)) | + ((ulong)guidBytes[3] << (8 * 3)) | + ((ulong)guidBytes[4] << (8 * 4)) | + ((ulong)guidBytes[5] << (8 * 5)) | + ((ulong)guidBytes[6] << (8 * 6)) | + ((ulong)guidBytes[7] << (8 * 7)); + + return seed ^ NetUtility.GetPlatformSeed(m_seedIncrement); + } + } +} diff --git a/SRMP/Lidgren.Network/NetReceiverChannelBase.cs b/SRMP/Lidgren.Network/NetReceiverChannelBase.cs new file mode 100644 index 0000000..e3f634e --- /dev/null +++ b/SRMP/Lidgren.Network/NetReceiverChannelBase.cs @@ -0,0 +1,18 @@ +using System; + +namespace Lidgren.Network +{ + internal abstract class NetReceiverChannelBase + { + internal NetPeer m_peer; + internal NetConnection m_connection; + + public NetReceiverChannelBase(NetConnection connection) + { + m_connection = connection; + m_peer = connection.m_peer; + } + + internal abstract void ReceiveMessage(NetIncomingMessage msg); + } +} diff --git a/SRMP/Lidgren.Network/NetReliableOrderedReceiver.cs b/SRMP/Lidgren.Network/NetReliableOrderedReceiver.cs new file mode 100644 index 0000000..51dfaaf --- /dev/null +++ b/SRMP/Lidgren.Network/NetReliableOrderedReceiver.cs @@ -0,0 +1,89 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetReliableOrderedReceiver : NetReceiverChannelBase + { + private int m_windowStart; + private int m_windowSize; + private NetBitVector m_earlyReceived; + internal NetIncomingMessage[] m_withheldMessages; + + public NetReliableOrderedReceiver(NetConnection connection, int windowSize) + : base(connection) + { + m_windowSize = windowSize; + m_withheldMessages = new NetIncomingMessage[windowSize]; + m_earlyReceived = new NetBitVector(windowSize); + } + + private void AdvanceWindow() + { + m_earlyReceived.Set(m_windowStart % m_windowSize, false); + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + + internal override void ReceiveMessage(NetIncomingMessage message) + { + int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart); + + // ack no matter what + m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber); + + if (relate == 0) + { + // Log("Received message #" + message.SequenceNumber + " right on time"); + + // + // excellent, right on time + // + //m_peer.LogVerbose("Received RIGHT-ON-TIME " + message); + + AdvanceWindow(); + m_peer.ReleaseMessage(message); + + // release withheld messages + int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers; + + while (m_earlyReceived[nextSeqNr % m_windowSize]) + { + message = m_withheldMessages[nextSeqNr % m_windowSize]; + NetException.Assert(message != null); + + // remove it from withheld messages + m_withheldMessages[nextSeqNr % m_windowSize] = null; + + m_peer.LogVerbose("Releasing withheld message #" + message); + + m_peer.ReleaseMessage(message); + + AdvanceWindow(); + nextSeqNr++; + } + + return; + } + + if (relate < 0) + { + // duplicate + m_connection.m_statistics.MessageDropped(); + m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE"); + return; + } + + // relate > 0 = early message + if (relate > m_windowSize) + { + // too early message! + m_connection.m_statistics.MessageDropped(); + m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart); + return; + } + + m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true); + m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart); + m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message; + } + } +} diff --git a/SRMP/Lidgren.Network/NetReliableSenderChannel.cs b/SRMP/Lidgren.Network/NetReliableSenderChannel.cs new file mode 100644 index 0000000..c24e634 --- /dev/null +++ b/SRMP/Lidgren.Network/NetReliableSenderChannel.cs @@ -0,0 +1,290 @@ +using System; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Sender part of Selective repeat ARQ for a particular NetChannel + /// + internal sealed class NetReliableSenderChannel : NetSenderChannelBase + { + private NetConnection m_connection; + private int m_windowStart; + private int m_windowSize; + private int m_sendStart; + + private bool m_anyStoredResends; + + private NetBitVector m_receivedAcks; + internal NetStoredReliableMessage[] m_storedMessages; + + internal double m_resendDelay; + + internal override int WindowSize { get { return m_windowSize; } } + + internal override bool NeedToSendMessages() + { + return base.NeedToSendMessages() || m_anyStoredResends; + } + + internal NetReliableSenderChannel(NetConnection connection, int windowSize) + { + m_connection = connection; + m_windowSize = windowSize; + m_windowStart = 0; + m_sendStart = 0; + m_anyStoredResends = false; + m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers); + m_storedMessages = new NetStoredReliableMessage[m_windowSize]; + m_queuedSends = new NetQueue(8); + m_resendDelay = m_connection.GetResendDelay(); + } + + internal override int GetAllowedSends() + { + int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % NetConstants.NumSequenceNumbers; + NetException.Assert(retval >= 0 && retval <= m_windowSize); + return retval; + } + + internal override void Reset() + { + m_receivedAcks.Clear(); + for (int i = 0; i < m_storedMessages.Length; i++) + m_storedMessages[i].Reset(); + m_anyStoredResends = false; + m_queuedSends.Clear(); + m_windowStart = 0; + m_sendStart = 0; + } + + internal override NetSendResult Enqueue(NetOutgoingMessage message) + { + m_queuedSends.Enqueue(message); + m_connection.m_peer.m_needFlushSendQueue = true; // a race condition to this variable will simply result in a single superflous call to FlushSendQueue() + if (m_queuedSends.Count <= GetAllowedSends()) + return NetSendResult.Sent; + return NetSendResult.Queued; + } + + // call this regularely + internal override void SendQueuedMessages(double now) + { + // + // resends + // + m_anyStoredResends = false; + for (int i = 0; i < m_storedMessages.Length; i++) + { + var storedMsg = m_storedMessages[i]; + NetOutgoingMessage om = storedMsg.Message; + if (om == null) + continue; + + m_anyStoredResends = true; + + double t = storedMsg.LastSent; + if (t > 0 && (now - t) > m_resendDelay) + { + // deduce sequence number + /* + int startSlot = m_windowStart % m_windowSize; + int seqNr = m_windowStart; + while (startSlot != i) + { + startSlot--; + if (startSlot < 0) + startSlot = m_windowSize - 1; + seqNr--; + } + */ + + //m_connection.m_peer.LogVerbose("Resending due to delay #" + m_storedMessages[i].SequenceNumber + " " + om.ToString()); + m_connection.m_statistics.MessageResent(MessageResendReason.Delay); + + Interlocked.Increment(ref om.m_recyclingCount); // increment this since it's being decremented in QueueSendMessage + m_connection.QueueSendMessage(om, storedMsg.SequenceNumber); + + m_storedMessages[i].LastSent = now; + m_storedMessages[i].NumSent++; + } + } + + int num = GetAllowedSends(); + if (num < 1) + return; + + // queued sends + while (num > 0 && m_queuedSends.Count > 0) + { + NetOutgoingMessage om; + if (m_queuedSends.TryDequeue(out om)) + ExecuteSend(now, om); + num--; + NetException.Assert(num == GetAllowedSends()); + } + } + + private void ExecuteSend(double now, NetOutgoingMessage message) + { + int seqNr = m_sendStart; + m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers; + + // must increment recycle count here, since it's decremented in QueueSendMessage and we want to keep it for the future in case or resends + // we will decrement once more in DestoreMessage for final recycling + Interlocked.Increment(ref message.m_recyclingCount); + + m_connection.QueueSendMessage(message, seqNr); + + int storeIndex = seqNr % m_windowSize; + NetException.Assert(m_storedMessages[storeIndex].Message == null); + + m_storedMessages[storeIndex].NumSent++; + m_storedMessages[storeIndex].Message = message; + m_storedMessages[storeIndex].LastSent = now; + m_storedMessages[storeIndex].SequenceNumber = seqNr; + m_anyStoredResends = true; + + return; + } + + private void DestoreMessage(double now, int storeIndex, out bool resetTimeout) + { + // reset timeout if we receive ack within kThreshold of sending it + const double kThreshold = 2.0; + var srm = m_storedMessages[storeIndex]; + resetTimeout = (srm.NumSent == 1) && (now - srm.LastSent < kThreshold); + + var storedMessage = srm.Message; + + // on each destore; reduce recyclingcount so that when all instances are destored, the outgoing message can be recycled + Interlocked.Decrement(ref storedMessage.m_recyclingCount); +#if DEBUG + if (storedMessage == null) + throw new NetException("m_storedMessages[" + storeIndex + "].Message is null; sent " + m_storedMessages[storeIndex].NumSent + " times, last time " + (NetTime.Now - m_storedMessages[storeIndex].LastSent) + " seconds ago"); +#else + if (storedMessage != null) + { +#endif + if (storedMessage.m_recyclingCount <= 0) + m_connection.m_peer.Recycle(storedMessage); + +#if !DEBUG + } +#endif + m_storedMessages[storeIndex] = new NetStoredReliableMessage(); + } + + // remoteWindowStart is remote expected sequence number; everything below this has arrived properly + // seqNr is the actual nr received + internal override void ReceiveAcknowledge(double now, int seqNr) + { + // late (dupe), on time or early ack? + int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart); + + if (relate < 0) + { + //m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr); + return; // late/duplicate ack + } + + if (relate == 0) + { + //m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr); + + // ack arrived right on time + NetException.Assert(seqNr == m_windowStart); + + bool resetTimeout; + m_receivedAcks[m_windowStart] = false; + DestoreMessage(now, m_windowStart % m_windowSize, out resetTimeout); + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + + // advance window if we already have early acks + while (m_receivedAcks.Get(m_windowStart)) + { + //m_connection.m_peer.LogDebug("Using early ack for #" + m_windowStart + "..."); + m_receivedAcks[m_windowStart] = false; + bool rt; + DestoreMessage(now, m_windowStart % m_windowSize, out rt); + resetTimeout |= rt; + + NetException.Assert(m_storedMessages[m_windowStart % m_windowSize].Message == null); // should already be destored + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + //m_connection.m_peer.LogDebug("Advancing window to #" + m_windowStart); + } + if (resetTimeout) + m_connection.ResetTimeout(now); + return; + } + + // + // early ack... (if it has been sent!) + // + // If it has been sent either the m_windowStart message was lost + // ... or the ack for that message was lost + // + + //m_connection.m_peer.LogDebug("Received early ack for #" + seqNr); + + int sendRelate = NetUtility.RelativeSequenceNumber(seqNr, m_sendStart); + if (sendRelate <= 0) + { + // yes, we've sent this message - it's an early (but valid) ack + if (m_receivedAcks[seqNr]) + { + // we've already destored/been acked for this message + } + else + { + m_receivedAcks[seqNr] = true; + } + } + else if (sendRelate > 0) + { + // uh... we haven't sent this message yet? Weird, dupe or error... + NetException.Assert(false, "Got ack for message not yet sent?"); + return; + } + + // Ok, lets resend all missing acks + int rnr = seqNr; + do + { + rnr--; + if (rnr < 0) + rnr = NetConstants.NumSequenceNumbers - 1; + + if (m_receivedAcks[rnr]) + { + // m_connection.m_peer.LogDebug("Not resending #" + rnr + " (since we got ack)"); + } + else + { + int slot = rnr % m_windowSize; + NetException.Assert(m_storedMessages[slot].Message != null); + if (m_storedMessages[slot].NumSent == 1) + { + // just sent once; resend immediately since we found gap in ack sequence + NetOutgoingMessage rmsg = m_storedMessages[slot].Message; + //m_connection.m_peer.LogVerbose("Resending #" + rnr + " (" + rmsg + ")"); + + if (now - m_storedMessages[slot].LastSent < (m_resendDelay * 0.35)) + { + // already resent recently + } + else + { + m_storedMessages[slot].LastSent = now; + m_storedMessages[slot].NumSent++; + m_connection.m_statistics.MessageResent(MessageResendReason.HoleInSequence); + Interlocked.Increment(ref rmsg.m_recyclingCount); // increment this since it's being decremented in QueueSendMessage + m_connection.QueueSendMessage(rmsg, rnr); + } + } + } + + } while (rnr != m_windowStart); + } + } +} diff --git a/SRMP/Lidgren.Network/NetReliableSequencedReceiver.cs b/SRMP/Lidgren.Network/NetReliableSequencedReceiver.cs new file mode 100644 index 0000000..0bc4fb1 --- /dev/null +++ b/SRMP/Lidgren.Network/NetReliableSequencedReceiver.cs @@ -0,0 +1,65 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetReliableSequencedReceiver : NetReceiverChannelBase + { + private int m_windowStart; + private int m_windowSize; + + public NetReliableSequencedReceiver(NetConnection connection, int windowSize) + : base(connection) + { + m_windowSize = windowSize; + } + + private void AdvanceWindow() + { + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + + internal override void ReceiveMessage(NetIncomingMessage message) + { + int nr = message.m_sequenceNumber; + + int relate = NetUtility.RelativeSequenceNumber(nr, m_windowStart); + + // ack no matter what + m_connection.QueueAck(message.m_receivedMessageType, nr); + + if (relate == 0) + { + // Log("Received message #" + message.SequenceNumber + " right on time"); + + // + // excellent, right on time + // + + AdvanceWindow(); + m_peer.ReleaseMessage(message); + return; + } + + if (relate < 0) + { + m_connection.m_statistics.MessageDropped(); + m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING LATE or DUPE"); + return; + } + + // relate > 0 = early message + if (relate > m_windowSize) + { + // too early message! + m_connection.m_statistics.MessageDropped(); + m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart); + return; + } + + // ok + m_windowStart = (m_windowStart + relate) % NetConstants.NumSequenceNumbers; + m_peer.ReleaseMessage(message); + return; + } + } +} diff --git a/SRMP/Lidgren.Network/NetReliableUnorderedReceiver.cs b/SRMP/Lidgren.Network/NetReliableUnorderedReceiver.cs new file mode 100644 index 0000000..002d5ea --- /dev/null +++ b/SRMP/Lidgren.Network/NetReliableUnorderedReceiver.cs @@ -0,0 +1,97 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetReliableUnorderedReceiver : NetReceiverChannelBase + { + private int m_windowStart; + private int m_windowSize; + private NetBitVector m_earlyReceived; + + public NetReliableUnorderedReceiver(NetConnection connection, int windowSize) + : base(connection) + { + m_windowSize = windowSize; + m_earlyReceived = new NetBitVector(windowSize); + } + + private void AdvanceWindow() + { + m_earlyReceived.Set(m_windowStart % m_windowSize, false); + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + + internal override void ReceiveMessage(NetIncomingMessage message) + { + int relate = NetUtility.RelativeSequenceNumber(message.m_sequenceNumber, m_windowStart); + + // ack no matter what + m_connection.QueueAck(message.m_receivedMessageType, message.m_sequenceNumber); + + if (relate == 0) + { + // Log("Received message #" + message.SequenceNumber + " right on time"); + + // + // excellent, right on time + // + //m_peer.LogVerbose("Received RIGHT-ON-TIME " + message); + + AdvanceWindow(); + m_peer.ReleaseMessage(message); + + // release withheld messages + int nextSeqNr = (message.m_sequenceNumber + 1) % NetConstants.NumSequenceNumbers; + + while (m_earlyReceived[nextSeqNr % m_windowSize]) + { + //message = m_withheldMessages[nextSeqNr % m_windowSize]; + //NetException.Assert(message != null); + + // remove it from withheld messages + //m_withheldMessages[nextSeqNr % m_windowSize] = null; + + //m_peer.LogVerbose("Releasing withheld message #" + message); + + //m_peer.ReleaseMessage(message); + + AdvanceWindow(); + nextSeqNr++; + } + + return; + } + + if (relate < 0) + { + // duplicate + m_connection.m_statistics.MessageDropped(); + m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE"); + return; + } + + // relate > 0 = early message + if (relate > m_windowSize) + { + // too early message! + m_connection.m_statistics.MessageDropped(); + m_peer.LogDebug("Received " + message + " TOO EARLY! Expected " + m_windowStart); + return; + } + + if (m_earlyReceived.Get(message.m_sequenceNumber % m_windowSize)) + { + // duplicate + m_connection.m_statistics.MessageDropped(); + m_peer.LogVerbose("Received message #" + message.m_sequenceNumber + " DROPPING DUPLICATE"); + return; + } + + m_earlyReceived.Set(message.m_sequenceNumber % m_windowSize, true); + //m_peer.LogVerbose("Received " + message + " WITHHOLDING, waiting for " + m_windowStart); + //m_withheldMessages[message.m_sequenceNumber % m_windowSize] = message; + + m_peer.ReleaseMessage(message); + } + } +} diff --git a/SRMP/Lidgren.Network/NetSRP.cs b/SRMP/Lidgren.Network/NetSRP.cs new file mode 100644 index 0000000..ffbfc65 --- /dev/null +++ b/SRMP/Lidgren.Network/NetSRP.cs @@ -0,0 +1,179 @@ +#define USE_SHA256 + +using System; +using System.Security.Cryptography; +using System.Text; + +namespace Lidgren.Network +{ + /// + /// Helper methods for implementing SRP authentication + /// + public static class NetSRP + { + private static readonly NetBigInteger N = new NetBigInteger("0115b8b692e0e045692cf280b436735c77a5a9e8a9e7ed56c965f87db5b2a2ece3", 16); + private static readonly NetBigInteger g = NetBigInteger.Two; + private static readonly NetBigInteger k = ComputeMultiplier(); + + /// + /// Compute multiplier (k) + /// + private static NetBigInteger ComputeMultiplier() + { + string one = NetUtility.ToHexString(N.ToByteArrayUnsigned()); + string two = NetUtility.ToHexString(g.ToByteArrayUnsigned()); + + string ccstr = one + two.PadLeft(one.Length, '0'); + byte[] cc = NetUtility.ToByteArray(ccstr); + + var ccHashed = NetUtility.ComputeSHAHash(cc); + return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16); + } + + /// + /// Create 16 bytes of random salt + /// + public static byte[] CreateRandomSalt() + { + byte[] retval = new byte[16]; + CryptoRandom.Instance.NextBytes(retval); + return retval; + } + + /// + /// Create 32 bytes of random ephemeral value + /// + public static byte[] CreateRandomEphemeral() + { + byte[] retval = new byte[32]; + CryptoRandom.Instance.NextBytes(retval); + return retval; + } + + /// + /// Computer private key (x) + /// + public static byte[] ComputePrivateKey(string username, string password, byte[] salt) + { + byte[] tmp = Encoding.UTF8.GetBytes(username + ":" + password); + byte[] innerHash = NetUtility.ComputeSHAHash(tmp); + + byte[] total = new byte[innerHash.Length + salt.Length]; + Buffer.BlockCopy(salt, 0, total, 0, salt.Length); + Buffer.BlockCopy(innerHash, 0, total, salt.Length, innerHash.Length); + + // x ie. H(salt || H(username || ":" || password)) + return new NetBigInteger(NetUtility.ToHexString(NetUtility.ComputeSHAHash(total)), 16).ToByteArrayUnsigned(); + } + + /// + /// Creates a verifier that the server can later use to authenticate users later on (v) + /// + public static byte[] ComputeServerVerifier(byte[] privateKey) + { + NetBigInteger x = new NetBigInteger(NetUtility.ToHexString(privateKey), 16); + + // Verifier (v) = g^x (mod N) + var serverVerifier = g.ModPow(x, N); + + return serverVerifier.ToByteArrayUnsigned(); + } + + /// + /// Compute client public ephemeral value (A) + /// + public static byte[] ComputeClientEphemeral(byte[] clientPrivateEphemeral) // a + { + // A= g^a (mod N) + NetBigInteger a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16); + NetBigInteger retval = g.ModPow(a, N); + + return retval.ToByteArrayUnsigned(); + } + + /// + /// Compute server ephemeral value (B) + /// + public static byte[] ComputeServerEphemeral(byte[] serverPrivateEphemeral, byte[] verifier) // b + { + var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16); + var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16); + + // B = kv + g^b (mod N) + var bb = g.ModPow(b, N); + var kv = v.Multiply(k); + var B = (kv.Add(bb)).Mod(N); + + return B.ToByteArrayUnsigned(); + } + + /// + /// Compute intermediate value (u) + /// + public static byte[] ComputeU(byte[] clientPublicEphemeral, byte[] serverPublicEphemeral) + { + // u = SHA-1(A || B) + string one = NetUtility.ToHexString(clientPublicEphemeral); + string two = NetUtility.ToHexString(serverPublicEphemeral); + + int len = 66; // Math.Max(one.Length, two.Length); + string ccstr = one.PadLeft(len, '0') + two.PadLeft(len, '0'); + + byte[] cc = NetUtility.ToByteArray(ccstr); + + var ccHashed = NetUtility.ComputeSHAHash(cc); + + return new NetBigInteger(NetUtility.ToHexString(ccHashed), 16).ToByteArrayUnsigned(); + } + + /// + /// Computes the server session value + /// + public static byte[] ComputeServerSessionValue(byte[] clientPublicEphemeral, byte[] verifier, byte[] udata, byte[] serverPrivateEphemeral) + { + // S = (Av^u) ^ b (mod N) + var A = new NetBigInteger(NetUtility.ToHexString(clientPublicEphemeral), 16); + var v = new NetBigInteger(NetUtility.ToHexString(verifier), 16); + var u = new NetBigInteger(NetUtility.ToHexString(udata), 16); + var b = new NetBigInteger(NetUtility.ToHexString(serverPrivateEphemeral), 16); + + NetBigInteger retval = v.ModPow(u, N).Multiply(A).Mod(N).ModPow(b, N).Mod(N); + + return retval.ToByteArrayUnsigned(); + } + + /// + /// Computes the client session value + /// + public static byte[] ComputeClientSessionValue(byte[] serverPublicEphemeral, byte[] xdata, byte[] udata, byte[] clientPrivateEphemeral) + { + // (B - kg^x) ^ (a + ux) (mod N) + var B = new NetBigInteger(NetUtility.ToHexString(serverPublicEphemeral), 16); + var x = new NetBigInteger(NetUtility.ToHexString(xdata), 16); + var u = new NetBigInteger(NetUtility.ToHexString(udata), 16); + var a = new NetBigInteger(NetUtility.ToHexString(clientPrivateEphemeral), 16); + + var bx = g.ModPow(x, N); + var btmp = B.Add(N.Multiply(k)).Subtract(bx.Multiply(k)).Mod(N); + return btmp.ModPow(x.Multiply(u).Add(a), N).ToByteArrayUnsigned(); + } + + /// + /// Create XTEA symmetrical encryption object from sessionValue + /// + public static NetXtea CreateEncryption(NetPeer peer, byte[] sessionValue) + { + var hash = NetUtility.ComputeSHAHash(sessionValue); + + var key = new byte[16]; + for(int i=0;i<16;i++) + { + key[i] = hash[i]; + for (int j = 1; j < hash.Length / 16; j++) + key[i] ^= hash[i + (j * 16)]; + } + + return new NetXtea(peer, key); + } + } +} diff --git a/SRMP/Lidgren.Network/NetSendResult.cs b/SRMP/Lidgren.Network/NetSendResult.cs new file mode 100644 index 0000000..9ceb5ba --- /dev/null +++ b/SRMP/Lidgren.Network/NetSendResult.cs @@ -0,0 +1,30 @@ +using System; + +namespace Lidgren.Network +{ + /// + /// Result of a SendMessage call + /// + public enum NetSendResult + { + /// + /// Message failed to enqueue because there is no connection + /// + FailedNotConnected = 0, + + /// + /// Message was immediately sent + /// + Sent = 1, + + /// + /// Message was queued for delivery + /// + Queued = 2, + + /// + /// Message was dropped immediately since too many message were queued + /// + Dropped = 3 + } +} diff --git a/SRMP/Lidgren.Network/NetSenderChannelBase.cs b/SRMP/Lidgren.Network/NetSenderChannelBase.cs new file mode 100644 index 0000000..04ac279 --- /dev/null +++ b/SRMP/Lidgren.Network/NetSenderChannelBase.cs @@ -0,0 +1,28 @@ +using System; + +namespace Lidgren.Network +{ + internal abstract class NetSenderChannelBase + { + // access this directly to queue things in this channel + protected NetQueue m_queuedSends; + + internal abstract int WindowSize { get; } + + internal abstract int GetAllowedSends(); + + internal int QueuedSendsCount { get { return m_queuedSends.Count; } } + + internal virtual bool NeedToSendMessages() { return m_queuedSends.Count > 0; } + + public int GetFreeWindowSlots() + { + return GetAllowedSends() - m_queuedSends.Count; + } + + internal abstract NetSendResult Enqueue(NetOutgoingMessage message); + internal abstract void SendQueuedMessages(double now); + internal abstract void Reset(); + internal abstract void ReceiveAcknowledge(double now, int sequenceNumber); + } +} diff --git a/SRMP/Lidgren.Network/NetServer.cs b/SRMP/Lidgren.Network/NetServer.cs new file mode 100644 index 0000000..70b7341 --- /dev/null +++ b/SRMP/Lidgren.Network/NetServer.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; + +namespace Lidgren.Network +{ + /// + /// Specialized version of NetPeer used for "server" peers + /// + public class NetServer : NetPeer + { + /// + /// NetServer constructor + /// + public NetServer(NetPeerConfiguration config) + : base(config) + { + config.AcceptIncomingConnections = true; + } + + /// + /// Send a message to all connections + /// + /// The message to send + /// How to deliver the message + public void SendToAll(NetOutgoingMessage msg, NetDeliveryMethod method) + { + // Modifying m_connections will modify the list of the connections of the NetPeer. Do only reads here + var all = m_connections; + if (all.Count <= 0) { + if (msg.m_isSent == false) + Recycle(msg); + return; + } + + SendMessage(msg, all, method, 0); + } + + /// + /// Send a message to all connections + /// + /// The message to send + /// How to deliver the message + /// Which sequence channel to use for the message + public void SendToAll(NetOutgoingMessage msg, NetDeliveryMethod method, int sequenceChannel) + { + // Modifying m_connections will modify the list of the connections of the NetPeer. Do only reads here + var all = m_connections; + if (all.Count <= 0) { + if (msg.m_isSent == false) + Recycle(msg); + return; + } + + SendMessage(msg, all, method, sequenceChannel); + } + + /// + /// Send a message to all connections except one + /// + /// The message to send + /// How to deliver the message + /// Don't send to this particular connection + /// Which sequence channel to use for the message + public void SendToAll(NetOutgoingMessage msg, NetConnection except, NetDeliveryMethod method, int sequenceChannel) + { + // Modifying m_connections will modify the list of the connections of the NetPeer. Do only reads here + var all = m_connections; + if (all.Count <= 0) { + if (msg.m_isSent == false) + Recycle(msg); + return; + } + + if (except == null) + { + SendMessage(msg, all, method, sequenceChannel); + return; + } + + List recipients = new List(all.Count - 1); + foreach (var conn in all) + if (conn != except) + recipients.Add(conn); + + if (recipients.Count > 0) + SendMessage(msg, recipients, method, sequenceChannel); + } + + /// + /// Returns a string that represents this object + /// + public override string ToString() + { + return "[NetServer " + ConnectionsCount + " connections]"; + } + } +} diff --git a/SRMP/Lidgren.Network/NetStoredReliableMessage.cs b/SRMP/Lidgren.Network/NetStoredReliableMessage.cs new file mode 100644 index 0000000..6da63c9 --- /dev/null +++ b/SRMP/Lidgren.Network/NetStoredReliableMessage.cs @@ -0,0 +1,19 @@ +using System; + +namespace Lidgren.Network +{ + internal struct NetStoredReliableMessage + { + public int NumSent; + public double LastSent; + public NetOutgoingMessage Message; + public int SequenceNumber; + + public void Reset() + { + NumSent = 0; + LastSent = 0.0; + Message = null; + } + } +} diff --git a/SRMP/Lidgren.Network/NetTime.cs b/SRMP/Lidgren.Network/NetTime.cs new file mode 100644 index 0000000..29d1375 --- /dev/null +++ b/SRMP/Lidgren.Network/NetTime.cs @@ -0,0 +1,42 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +*/ +using System; +using System.Collections.Generic; +using System.Text; +using System.Diagnostics; + +namespace Lidgren.Network +{ + /// + /// Time service + /// + public static partial class NetTime + { + /// + /// Given seconds it will output a human friendly readable string (milliseconds if less than 60 seconds) + /// + public static string ToReadable(double seconds) + { + if (seconds > 60) + return TimeSpan.FromSeconds(seconds).ToString(); + return (seconds * 1000.0).ToString("N2") + " ms"; + } + } +} \ No newline at end of file diff --git a/SRMP/Lidgren.Network/NetTuple.cs b/SRMP/Lidgren.Network/NetTuple.cs new file mode 100644 index 0000000..fe3c276 --- /dev/null +++ b/SRMP/Lidgren.Network/NetTuple.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Lidgren.Network +{ + // replace with BCL 4.0 Tuple<> when appropriate + internal struct NetTuple + { + public A Item1; + public B Item2; + + public NetTuple(A item1, B item2) + { + Item1 = item1; + Item2 = item2; + } + } +} diff --git a/SRMP/Lidgren.Network/NetUPnP.cs b/SRMP/Lidgren.Network/NetUPnP.cs new file mode 100644 index 0000000..d44dd22 --- /dev/null +++ b/SRMP/Lidgren.Network/NetUPnP.cs @@ -0,0 +1,283 @@ +using System; +using System.IO; +using System.Xml; +using System.Net; +using System.Net.Sockets; +using System.Threading; + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +#endif + +namespace Lidgren.Network +{ + /// + /// Status of the UPnP capabilities + /// + public enum UPnPStatus + { + /// + /// Still discovering UPnP capabilities + /// + Discovering, + + /// + /// UPnP is not available + /// + NotAvailable, + + /// + /// UPnP is available and ready to use + /// + Available + } + + /// + /// UPnP support class + /// + public class NetUPnP + { + private const int c_discoveryTimeOutMillis = 1000; + + private string m_serviceUrl; + private string m_serviceName = ""; + private NetPeer m_peer; + private ManualResetEvent m_discoveryComplete = new ManualResetEvent(false); + + internal double m_discoveryResponseDeadline; + + private UPnPStatus m_status; + + /// + /// Status of the UPnP capabilities of this NetPeer + /// + public UPnPStatus Status { get { return m_status; } } + + /// + /// NetUPnP constructor + /// + public NetUPnP(NetPeer peer) + { + m_peer = peer; + m_discoveryResponseDeadline = double.MinValue; + } + + internal void Discover(NetPeer peer) + { + string str = +"M-SEARCH * HTTP/1.1\r\n" + +"HOST: 239.255.255.250:1900\r\n" + +"ST:upnp:rootdevice\r\n" + +"MAN:\"ssdp:discover\"\r\n" + +"MX:3\r\n\r\n"; + + m_discoveryResponseDeadline = NetTime.Now + 6.0; // arbitrarily chosen number, router gets 6 seconds to respond + m_status = UPnPStatus.Discovering; + + byte[] arr = System.Text.Encoding.UTF8.GetBytes(str); + + m_peer.LogDebug("Attempting UPnP discovery"); + peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); + peer.RawSend(arr, 0, arr.Length, new NetEndPoint(NetUtility.GetBroadcastAddress(), 1900)); + peer.Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, false); + } + + internal void CheckForDiscoveryTimeout() + { + if ((m_status != UPnPStatus.Discovering) || (NetTime.Now < m_discoveryResponseDeadline)) + return; + m_peer.LogDebug("UPnP discovery timed out"); + m_status = UPnPStatus.NotAvailable; + } + + internal void ExtractServiceUrl(string resp) + { +#if !DEBUG + try + { +#endif + XmlDocument desc = new XmlDocument(); + using (var response = WebRequest.Create(resp).GetResponse()) + desc.Load(response.GetResponseStream()); + + XmlNamespaceManager nsMgr = new XmlNamespaceManager(desc.NameTable); + nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); + XmlNode typen = desc.SelectSingleNode("//tns:device/tns:deviceType/text()", nsMgr); + if (!typen.Value.Contains("InternetGatewayDevice")) + return; + + m_serviceName = "WANIPConnection"; + XmlNode node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr); + if (node == null) + { + //try another service name + m_serviceName = "WANPPPConnection"; + node = desc.SelectSingleNode("//tns:service[tns:serviceType=\"urn:schemas-upnp-org:service:" + m_serviceName + ":1\"]/tns:controlURL/text()", nsMgr); + if (node == null) + return; + } + + m_serviceUrl = CombineUrls(resp, node.Value); + m_peer.LogDebug("UPnP service ready"); + m_status = UPnPStatus.Available; + m_discoveryComplete.Set(); +#if !DEBUG + } + catch + { + m_peer.LogVerbose("Exception ignored trying to parse UPnP XML response"); + return; + } +#endif + } + + private static string CombineUrls(string gatewayURL, string subURL) + { + // Is Control URL an absolute URL? + if ((subURL.Contains("http:")) || (subURL.Contains("."))) + return subURL; + + gatewayURL = gatewayURL.Replace("http://", ""); // strip any protocol + int n = gatewayURL.IndexOf("/"); + if (n != -1) + gatewayURL = gatewayURL.Substring(0, n); // Use first portion of URL + return "http://" + gatewayURL + subURL; + } + + private bool CheckAvailability() + { + switch (m_status) + { + case UPnPStatus.NotAvailable: + return false; + case UPnPStatus.Available: + return true; + case UPnPStatus.Discovering: + if (m_discoveryComplete.WaitOne(c_discoveryTimeOutMillis)) + return true; + if (NetTime.Now > m_discoveryResponseDeadline) + m_status = UPnPStatus.NotAvailable; + return false; + } + return false; + } + + /// + /// Add a forwarding rule to the router using UPnP + /// + /// The external, WAN facing, port + /// A description for the port forwarding rule + /// The port on the client machine to send traffic to + public bool ForwardPort(int externalPort, string description, int internalPort = 0) + { + if (!CheckAvailability()) + return false; + + IPAddress mask; + var client = NetUtility.GetMyAddress(out mask); + if (client == null) + return false; + + if (internalPort == 0) + internalPort = externalPort; + + try + { + SOAPRequest(m_serviceUrl, + "" + + "" + + "" + externalPort.ToString() + "" + + "" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "" + + "" + internalPort.ToString() + "" + + "" + client.ToString() + "" + + "1" + + "" + description + "" + + "0" + + "", + "AddPortMapping"); + + m_peer.LogDebug("Sent UPnP port forward request"); + NetUtility.Sleep(50); + } + catch (Exception ex) + { + m_peer.LogWarning("UPnP port forward failed: " + ex.Message); + return false; + } + return true; + } + + /// + /// Delete a forwarding rule from the router using UPnP + /// + /// The external, 'internet facing', port + public bool DeleteForwardingRule(int externalPort) + { + if (!CheckAvailability()) + return false; + + try + { + SOAPRequest(m_serviceUrl, + "" + + "" + + "" + + "" + externalPort + "" + + "" + ProtocolType.Udp.ToString().ToUpper(System.Globalization.CultureInfo.InvariantCulture) + "" + + "", "DeletePortMapping"); + return true; + } + catch (Exception ex) + { + m_peer.LogWarning("UPnP delete forwarding rule failed: " + ex.Message); + return false; + } + } + + /// + /// Retrieve the extern ip using UPnP + /// + public IPAddress GetExternalIP() + { + if (!CheckAvailability()) + return null; + try + { + XmlDocument xdoc = SOAPRequest(m_serviceUrl, "" + + "", "GetExternalIPAddress"); + XmlNamespaceManager nsMgr = new XmlNamespaceManager(xdoc.NameTable); + nsMgr.AddNamespace("tns", "urn:schemas-upnp-org:device-1-0"); + string IP = xdoc.SelectSingleNode("//NewExternalIPAddress/text()", nsMgr).Value; + return IPAddress.Parse(IP); + } + catch (Exception ex) + { + m_peer.LogWarning("Failed to get external IP: " + ex.Message); + return null; + } + } + + private XmlDocument SOAPRequest(string url, string soap, string function) + { + string req = "" + + "" + + "" + + soap + + "" + + ""; + WebRequest r = HttpWebRequest.Create(url); + r.Method = "POST"; + byte[] b = System.Text.Encoding.UTF8.GetBytes(req); + r.Headers.Add("SOAPACTION", "\"urn:schemas-upnp-org:service:" + m_serviceName + ":1#" + function + "\""); + r.ContentType = "text/xml; charset=\"utf-8\""; + r.ContentLength = b.Length; + r.GetRequestStream().Write(b, 0, b.Length); + using (WebResponse wres = r.GetResponse()) { + XmlDocument resp = new XmlDocument(); + Stream ress = wres.GetResponseStream(); + resp.Load(ress); + return resp; + } + } + } +} \ No newline at end of file diff --git a/SRMP/Lidgren.Network/NetUnreliableSenderChannel.cs b/SRMP/Lidgren.Network/NetUnreliableSenderChannel.cs new file mode 100644 index 0000000..319ffd1 --- /dev/null +++ b/SRMP/Lidgren.Network/NetUnreliableSenderChannel.cs @@ -0,0 +1,142 @@ +using System; +using System.Threading; + +namespace Lidgren.Network +{ + /// + /// Sender part of Selective repeat ARQ for a particular NetChannel + /// + internal sealed class NetUnreliableSenderChannel : NetSenderChannelBase + { + private NetConnection m_connection; + private int m_windowStart; + private int m_windowSize; + private int m_sendStart; + private bool m_doFlowControl; + + private NetBitVector m_receivedAcks; + + internal override int WindowSize { get { return m_windowSize; } } + + internal NetUnreliableSenderChannel(NetConnection connection, int windowSize, NetDeliveryMethod method) + { + m_connection = connection; + m_windowSize = windowSize; + m_windowStart = 0; + m_sendStart = 0; + m_receivedAcks = new NetBitVector(NetConstants.NumSequenceNumbers); + m_queuedSends = new NetQueue(8); + + m_doFlowControl = true; + if (method == NetDeliveryMethod.Unreliable && connection.Peer.Configuration.SuppressUnreliableUnorderedAcks == true) + m_doFlowControl = false; + } + + internal override int GetAllowedSends() + { + if (!m_doFlowControl) + return int.MaxValue; // always allowed to send without flow control! + int retval = m_windowSize - ((m_sendStart + NetConstants.NumSequenceNumbers) - m_windowStart) % m_windowSize; + NetException.Assert(retval >= 0 && retval <= m_windowSize); + return retval; + } + + internal override void Reset() + { + m_receivedAcks.Clear(); + m_queuedSends.Clear(); + m_windowStart = 0; + m_sendStart = 0; + } + + internal override NetSendResult Enqueue(NetOutgoingMessage message) + { + int queueLen = m_queuedSends.Count + 1; + int left = GetAllowedSends(); + if (queueLen > left || (message.LengthBytes > m_connection.m_currentMTU && m_connection.m_peerConfiguration.UnreliableSizeBehaviour == NetUnreliableSizeBehaviour.DropAboveMTU)) + { + // drop message + return NetSendResult.Dropped; + } + + m_queuedSends.Enqueue(message); + m_connection.m_peer.m_needFlushSendQueue = true; // a race condition to this variable will simply result in a single superflous call to FlushSendQueue() + return NetSendResult.Sent; + } + + // call this regularely + internal override void SendQueuedMessages(double now) + { + int num = GetAllowedSends(); + if (num < 1) + return; + + // queued sends + while (num > 0 && m_queuedSends.Count > 0) + { + NetOutgoingMessage om; + if (m_queuedSends.TryDequeue(out om)) + ExecuteSend(om); + num--; + } + } + + private void ExecuteSend(NetOutgoingMessage message) + { + m_connection.m_peer.VerifyNetworkThread(); + + int seqNr = m_sendStart; + m_sendStart = (m_sendStart + 1) % NetConstants.NumSequenceNumbers; + + m_connection.QueueSendMessage(message, seqNr); + + if (message.m_recyclingCount <= 0) + m_connection.m_peer.Recycle(message); + + return; + } + + // remoteWindowStart is remote expected sequence number; everything below this has arrived properly + // seqNr is the actual nr received + internal override void ReceiveAcknowledge(double now, int seqNr) + { + if (m_doFlowControl == false) + { + // we have no use for acks on this channel since we don't respect the window anyway + m_connection.m_peer.LogWarning("SuppressUnreliableUnorderedAcks sender/receiver mismatch!"); + return; + } + + // late (dupe), on time or early ack? + int relate = NetUtility.RelativeSequenceNumber(seqNr, m_windowStart); + + if (relate < 0) + { + //m_connection.m_peer.LogDebug("Received late/dupe ack for #" + seqNr); + return; // late/duplicate ack + } + + if (relate == 0) + { + //m_connection.m_peer.LogDebug("Received right-on-time ack for #" + seqNr); + + // ack arrived right on time + NetException.Assert(seqNr == m_windowStart); + + m_receivedAcks[m_windowStart] = false; + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + + return; + } + + // Advance window to this position + m_receivedAcks[seqNr] = true; + + while (m_windowStart != seqNr) + { + m_receivedAcks[m_windowStart] = false; + m_windowStart = (m_windowStart + 1) % NetConstants.NumSequenceNumbers; + } + } + } +} diff --git a/SRMP/Lidgren.Network/NetUnreliableSequencedReceiver.cs b/SRMP/Lidgren.Network/NetUnreliableSequencedReceiver.cs new file mode 100644 index 0000000..60d44b3 --- /dev/null +++ b/SRMP/Lidgren.Network/NetUnreliableSequencedReceiver.cs @@ -0,0 +1,33 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetUnreliableSequencedReceiver : NetReceiverChannelBase + { + private int m_lastReceivedSequenceNumber = -1; + + public NetUnreliableSequencedReceiver(NetConnection connection) + : base(connection) + { + } + + internal override void ReceiveMessage(NetIncomingMessage msg) + { + int nr = msg.m_sequenceNumber; + + // ack no matter what + m_connection.QueueAck(msg.m_receivedMessageType, nr); + + int relate = NetUtility.RelativeSequenceNumber(nr, m_lastReceivedSequenceNumber + 1); + if (relate < 0) + { + m_connection.m_statistics.MessageDropped(); + m_peer.LogVerbose("Received message #" + nr + " DROPPING DUPLICATE"); + return; // drop if late + } + + m_lastReceivedSequenceNumber = nr; + m_peer.ReleaseMessage(msg); + } + } +} diff --git a/SRMP/Lidgren.Network/NetUnreliableUnorderedReceiver.cs b/SRMP/Lidgren.Network/NetUnreliableUnorderedReceiver.cs new file mode 100644 index 0000000..cfa81c8 --- /dev/null +++ b/SRMP/Lidgren.Network/NetUnreliableUnorderedReceiver.cs @@ -0,0 +1,23 @@ +using System; + +namespace Lidgren.Network +{ + internal sealed class NetUnreliableUnorderedReceiver : NetReceiverChannelBase + { + private bool m_doFlowControl; + + public NetUnreliableUnorderedReceiver(NetConnection connection) + : base(connection) + { + m_doFlowControl = connection.Peer.Configuration.SuppressUnreliableUnorderedAcks == false; + } + + internal override void ReceiveMessage(NetIncomingMessage msg) + { + if (m_doFlowControl) + m_connection.QueueAck(msg.m_receivedMessageType, msg.m_sequenceNumber); + + m_peer.ReleaseMessage(msg); + } + } +} diff --git a/SRMP/Lidgren.Network/NetUtility.cs b/SRMP/Lidgren.Network/NetUtility.cs new file mode 100644 index 0000000..f99d3b3 --- /dev/null +++ b/SRMP/Lidgren.Network/NetUtility.cs @@ -0,0 +1,469 @@ +/* Copyright (c) 2010 Michael Lidgren + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software +and associated documentation files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom +the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or +substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#if !__NOIPENDPOINT__ +using NetEndPoint = System.Net.IPEndPoint; +using NetAddress = System.Net.IPAddress; +#endif + +using System; +using System.Net; + +using System.Net.Sockets; +using System.Text; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + /// + /// Utility methods + /// + public static partial class NetUtility + { + private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; + + /// + /// Resolve endpoint callback + /// + public delegate void ResolveEndPointCallback(NetEndPoint endPoint); + + /// + /// Resolve address callback + /// + public delegate void ResolveAddressCallback(NetAddress adr); + + /// + /// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number (asynchronous version) + /// + public static void ResolveAsync(string ipOrHost, int port, ResolveEndPointCallback callback) + { + ResolveAsync(ipOrHost, delegate(NetAddress adr) + { + if (adr == null) + { + callback(null); + } + else + { + callback(new NetEndPoint(adr, port)); + } + }); + } + + /// + /// Get IPv4 endpoint from notation (xxx.xxx.xxx.xxx) or hostname and port number + /// + public static NetEndPoint Resolve(string ipOrHost, int port) + { + var adr = Resolve(ipOrHost); + return adr == null ? null : new NetEndPoint(adr, port); + } + + private static IPAddress s_broadcastAddress; + public static IPAddress GetCachedBroadcastAddress() + { + if (s_broadcastAddress == null) + s_broadcastAddress = GetBroadcastAddress(); + return s_broadcastAddress; + } + + /// + /// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname (asynchronous version) + /// + public static void ResolveAsync(string ipOrHost, ResolveAddressCallback callback) + { + if (string.IsNullOrEmpty(ipOrHost)) + throw new ArgumentException("Supplied string must not be empty", "ipOrHost"); + + ipOrHost = ipOrHost.Trim(); + + NetAddress ipAddress = null; + if (NetAddress.TryParse(ipOrHost, out ipAddress)) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + { + callback(ipAddress); + return; + } + throw new ArgumentException("This method will not currently resolve other than ipv4 addresses"); + } + + // ok must be a host name + IPHostEntry entry; + try + { + Dns.BeginGetHostEntry(ipOrHost, delegate(IAsyncResult result) + { + try + { + entry = Dns.EndGetHostEntry(result); + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostNotFound) + { + //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost)); + callback(null); + return; + } + else + { + throw; + } + } + + if (entry == null) + { + callback(null); + return; + } + + // check each entry for a valid IP address + foreach (var ipCurrent in entry.AddressList) + { + if (ipCurrent.AddressFamily == AddressFamily.InterNetwork) + { + callback(ipCurrent); + return; + } + } + + callback(null); + }, null); + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostNotFound) + { + //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost)); + callback(null); + } + else + { + throw; + } + } + } + + /// + /// Get IPv4 address from notation (xxx.xxx.xxx.xxx) or hostname + /// + public static NetAddress Resolve(string ipOrHost) + { + if (string.IsNullOrEmpty(ipOrHost)) + throw new ArgumentException("Supplied string must not be empty", "ipOrHost"); + + ipOrHost = ipOrHost.Trim(); + + NetAddress ipAddress = null; + if (NetAddress.TryParse(ipOrHost, out ipAddress)) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetwork) + return ipAddress; + throw new ArgumentException("This method will not currently resolve other than ipv4 addresses"); + } + + // ok must be a host name + try + { + var addresses = Dns.GetHostAddresses(ipOrHost); + if (addresses == null) + return null; + foreach (var address in addresses) + { + if (address.AddressFamily == AddressFamily.InterNetwork) + return address; + } + return null; + } + catch (SocketException ex) + { + if (ex.SocketErrorCode == SocketError.HostNotFound) + { + //LogWrite(string.Format(CultureInfo.InvariantCulture, "Failed to resolve host '{0}'.", ipOrHost)); + return null; + } + else + { + throw; + } + } + } + + /// + /// Create a hex string from an Int64 value + /// + public static string ToHexString(long data) + { + return ToHexString(BitConverter.GetBytes(data)); + } + + /// + /// Create a hex string from an array of bytes + /// + public static string ToHexString(byte[] data) + { + return ToHexString(data, 0, data.Length); + } + + /// + /// Create a hex string from an array of bytes + /// + public static string ToHexString(byte[] data, int offset, int length) + { + char[] c = new char[length * 2]; + byte b; + for (int i = 0; i < length; ++i) + { + b = ((byte)(data[offset + i] >> 4)); + c[i * 2] = (char)(b > 9 ? b + 0x37 : b + 0x30); + b = ((byte)(data[offset + i] & 0xF)); + c[i * 2 + 1] = (char)(b > 9 ? b + 0x37 : b + 0x30); + } + return new string(c); + } + + /// + /// Returns true if the endpoint supplied is on the same subnet as this host + /// + public static bool IsLocal(NetEndPoint endPoint) + { + if (endPoint == null) + return false; + return IsLocal(endPoint.Address); + } + + /// + /// Returns true if the IPAddress supplied is on the same subnet as this host + /// + public static bool IsLocal(NetAddress remote) + { + NetAddress mask; + var local = GetMyAddress(out mask); + + if (mask == null) + return false; + + uint maskBits = BitConverter.ToUInt32(mask.GetAddressBytes(), 0); + uint remoteBits = BitConverter.ToUInt32(remote.GetAddressBytes(), 0); + uint localBits = BitConverter.ToUInt32(local.GetAddressBytes(), 0); + + // compare network portions + return ((remoteBits & maskBits) == (localBits & maskBits)); + } + + /// + /// Returns how many bits are necessary to hold a certain number + /// + [CLSCompliant(false)] + public static int BitsToHoldUInt(uint value) + { + int bits = 1; + while ((value >>= 1) != 0) + bits++; + return bits; + } + + /// + /// Returns how many bits are necessary to hold a certain number + /// + [CLSCompliant(false)] + public static int BitsToHoldUInt64(ulong value) + { + int bits = 1; + while ((value >>= 1) != 0) + bits++; + return bits; + } + + /// + /// Returns how many bytes are required to hold a certain number of bits + /// + public static int BytesToHoldBits(int numBits) + { + return (numBits + 7) / 8; + } + + internal static UInt32 SwapByteOrder(UInt32 value) + { + return + ((value & 0xff000000) >> 24) | + ((value & 0x00ff0000) >> 8) | + ((value & 0x0000ff00) << 8) | + ((value & 0x000000ff) << 24); + } + + internal static UInt64 SwapByteOrder(UInt64 value) + { + return + ((value & 0xff00000000000000L) >> 56) | + ((value & 0x00ff000000000000L) >> 40) | + ((value & 0x0000ff0000000000L) >> 24) | + ((value & 0x000000ff00000000L) >> 8) | + ((value & 0x00000000ff000000L) << 8) | + ((value & 0x0000000000ff0000L) << 24) | + ((value & 0x000000000000ff00L) << 40) | + ((value & 0x00000000000000ffL) << 56); + } + + internal static bool CompareElements(byte[] one, byte[] two) + { + if (one.Length != two.Length) + return false; + for (int i = 0; i < one.Length; i++) + if (one[i] != two[i]) + return false; + return true; + } + + /// + /// Convert a hexadecimal string to a byte array + /// + public static byte[] ToByteArray(String hexString) + { + byte[] retval = new byte[hexString.Length / 2]; + for (int i = 0; i < hexString.Length; i += 2) + retval[i / 2] = Convert.ToByte(hexString.Substring(i, 2), 16); + return retval; + } + + /// + /// Converts a number of bytes to a shorter, more readable string representation + /// + public static string ToHumanReadable(long bytes) + { + if (bytes < 4000) // 1-4 kb is printed in bytes + return bytes + " bytes"; + if (bytes < 1000 * 1000) // 4-999 kb is printed in kb + return Math.Round(((double)bytes / 1000.0), 2) + " kilobytes"; + return Math.Round(((double)bytes / (1000.0 * 1000.0)), 2) + " megabytes"; // else megabytes + } + + internal static int RelativeSequenceNumber(int nr, int expected) + { + return (nr - expected + NetConstants.NumSequenceNumbers + (NetConstants.NumSequenceNumbers / 2)) % NetConstants.NumSequenceNumbers - (NetConstants.NumSequenceNumbers / 2); + + // old impl: + //int retval = ((nr + NetConstants.NumSequenceNumbers) - expected) % NetConstants.NumSequenceNumbers; + //if (retval > (NetConstants.NumSequenceNumbers / 2)) + // retval -= NetConstants.NumSequenceNumbers; + //return retval; + } + + /// + /// Gets the window size used internally in the library for a certain delivery method + /// + public static int GetWindowSize(NetDeliveryMethod method) + { + switch (method) + { + case NetDeliveryMethod.Unknown: + return 0; + + case NetDeliveryMethod.Unreliable: + case NetDeliveryMethod.UnreliableSequenced: + return NetConstants.UnreliableWindowSize; + + case NetDeliveryMethod.ReliableOrdered: + return NetConstants.ReliableOrderedWindowSize; + + case NetDeliveryMethod.ReliableSequenced: + case NetDeliveryMethod.ReliableUnordered: + default: + return NetConstants.DefaultWindowSize; + } + } + + // shell sort + internal static void SortMembersList(System.Reflection.MemberInfo[] list) + { + int h; + int j; + System.Reflection.MemberInfo tmp; + + h = 1; + while (h * 3 + 1 <= list.Length) + h = 3 * h + 1; + + while (h > 0) + { + for (int i = h - 1; i < list.Length; i++) + { + tmp = list[i]; + j = i; + while (true) + { + if (j >= h) + { + if (string.Compare(list[j - h].Name, tmp.Name, StringComparison.InvariantCulture) > 0) + { + list[j] = list[j - h]; + j -= h; + } + else + break; + } + else + break; + } + + list[j] = tmp; + } + h /= 3; + } + } + + internal static NetDeliveryMethod GetDeliveryMethod(NetMessageType mtp) + { + if (mtp >= NetMessageType.UserReliableOrdered1) + return NetDeliveryMethod.ReliableOrdered; + else if (mtp >= NetMessageType.UserReliableSequenced1) + return NetDeliveryMethod.ReliableSequenced; + else if (mtp >= NetMessageType.UserReliableUnordered) + return NetDeliveryMethod.ReliableUnordered; + else if (mtp >= NetMessageType.UserSequenced1) + return NetDeliveryMethod.UnreliableSequenced; + return NetDeliveryMethod.Unreliable; + } + + /// + /// Creates a comma delimited string from a lite of items + /// + public static string MakeCommaDelimitedList(IList list) + { + var cnt = list.Count; + StringBuilder bdr = new StringBuilder(cnt * 5); // educated guess + for(int i=0;i + /// Gets my local IPv4 address (not necessarily external) and subnet mask + /// + public static IPAddress GetMyAddress(out IPAddress mask) + { + mask = null; + try + { + Android.Net.Wifi.WifiManager wifi = (Android.Net.Wifi.WifiManager)Android.App.Application.Context.GetSystemService(Android.App.Activity.WifiService); + if (!wifi.IsWifiEnabled) + return null; + + var dhcp = wifi.DhcpInfo; + int addr = dhcp.IpAddress; + byte[] quads = new byte[4]; + for (int k = 0; k < 4; k++) + quads[k] = (byte)((addr >> k * 8) & 0xFF); + return new IPAddress(quads); + } + catch // Catch Access Denied errors + { + return null; + } + } + + public static byte[] GetMacAddressBytes() + { + if (s_randomMacBytes == null) + { + s_randomMacBytes = new byte[8]; + MWCRandom.Instance.NextBytes(s_randomMacBytes); + } + return s_randomMacBytes; + } + + public static void Sleep(int milliseconds) + { + System.Threading.Thread.Sleep(milliseconds); + } + + public static IPAddress GetBroadcastAddress() + { + return IPAddress.Broadcast; + } + + public static IPAddress CreateAddressFromBytes(byte[] bytes) + { + return new IPAddress(bytes); + } + + private static readonly SHA1 s_sha = SHA1.Create(); + public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count) + { + return s_sha.ComputeHash(bytes, offset, count); + } + } + + public static partial class NetTime + { + private static readonly long s_timeInitialized = Environment.TickCount; + + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } } + } +} +#endif \ No newline at end of file diff --git a/SRMP/Lidgren.Network/Platform/PlatformConstrained.cs b/SRMP/Lidgren.Network/Platform/PlatformConstrained.cs new file mode 100644 index 0000000..7017962 --- /dev/null +++ b/SRMP/Lidgren.Network/Platform/PlatformConstrained.cs @@ -0,0 +1,98 @@ +#if __CONSTRAINED__ || UNITY_STANDALONE_LINUX || UNITY +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public static partial class NetUtility + { + private static byte[] s_randomMacBytes; + + static NetUtility() + { + } + + [CLSCompliant(false)] + public static ulong GetPlatformSeed(int seedInc) + { + ulong seed = (ulong)Environment.TickCount + (ulong)seedInc; + return seed ^ ((ulong)(new object().GetHashCode()) << 32); + } + + /// + /// Gets my local IPv4 address (not necessarily external) and subnet mask + /// + public static IPAddress GetMyAddress(out IPAddress mask) + { + mask = null; + var host = Dns.GetHostEntry(Dns.GetHostName()); + foreach (var ip in host.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + { + return ip; + } + } +#if UNITY_ANDROID || UNITY_STANDALONE_OSX || UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_IOS || UNITY + try + { + if (!(UnityEngine.Application.internetReachability == UnityEngine.NetworkReachability.NotReachable)) + { + return null; + } + return IPAddress.Parse(UnityEngine.Network.player.externalIP); + } + catch // Catch Access Denied errors + { + return null; + } +#endif + return null; + } + + public static byte[] GetMacAddressBytes() + { + if (s_randomMacBytes == null) + { + s_randomMacBytes = new byte[8]; + MWCRandom.Instance.NextBytes(s_randomMacBytes); + } + return s_randomMacBytes; + } + + public static IPAddress GetBroadcastAddress() + { + return IPAddress.Broadcast; + } + + public static void Sleep(int milliseconds) + { + System.Threading.Thread.Sleep(milliseconds); + } + + public static IPAddress CreateAddressFromBytes(byte[] bytes) + { + return new IPAddress(bytes); + } + + private static readonly SHA1 s_sha = SHA1.Create(); + public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count) + { + return s_sha.ComputeHash(bytes, offset, count); + } + } + + public static partial class NetTime + { + private static readonly long s_timeInitialized = Environment.TickCount; + + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } } + } +} +#endif diff --git a/SRMP/Lidgren.Network/Platform/PlatformUnityExtras.cs b/SRMP/Lidgren.Network/Platform/PlatformUnityExtras.cs new file mode 100644 index 0000000..60be43b --- /dev/null +++ b/SRMP/Lidgren.Network/Platform/PlatformUnityExtras.cs @@ -0,0 +1,183 @@ +using SRMultiplayer; +using UnityEngine; + +namespace Lidgren.Network +{ + public partial class NetBuffer + { + public void Write(Vector2 vector) + { + Write(vector.x); + Write(vector.y); + } + + public Vector2 ReadVector2() + { + return new Vector2( + x: ReadSingle(), + y: ReadSingle()); + } + + public void Write(Vector3 vector) + { + Write(vector.x); + Write(vector.y); + Write(vector.z); + } + + public Vector3 ReadVector3() + { + return new Vector3( + x: ReadSingle(), + y: ReadSingle(), + z: ReadSingle()); + } + + public void Write(Vector4 vector) + { + Write(vector.x); + Write(vector.y); + Write(vector.z); + Write(vector.w); + } + + public Vector4 ReadVector4() + { + return new Vector4( + x: ReadSingle(), + y: ReadSingle(), + z: ReadSingle(), + w: ReadSingle()); + } + + public void Write(Quaternion quaternion) + { + uint comp = Compression.CompressQuaternion(quaternion); + Write(comp); + } + + public Quaternion ReadQuaternion() + { + return Compression.DecompressQuaternion(ReadUInt32()); + } + + public void WriteRgbColor(Color32 color) + { + Write(color.r); + Write(color.g); + Write(color.b); + } + + public Color32 ReadRgbColor() + { + return new Color32( + r: ReadByte(), + g: ReadByte(), + b: ReadByte(), + a: byte.MaxValue); + } + + public void WriteRgbaColor(Color32 color) + { + Write(color.r); + Write(color.g); + Write(color.b); + Write(color.a); + } + + public Color32 ReadRgbaColor() + { + return new Color32( + r: ReadByte(), + g: ReadByte(), + b: ReadByte(), + a: ReadByte()); + } + + public void Write(Ray ray) + { + Write(ray.direction); + Write(ray.origin); + } + + public Ray ReadRay() + { + return new Ray( + direction: ReadVector3(), + origin: ReadVector3()); + } + + public void Write(Plane plane) + { + Write(plane.normal); + Write(plane.distance); + } + + public Plane ReadPlane() + { + return new Plane( + inNormal: ReadVector3(), + d: ReadSingle()); + } + + public void Write(Matrix4x4 matrix) + { + Write(matrix.m00); + Write(matrix.m01); + Write(matrix.m02); + Write(matrix.m03); + Write(matrix.m10); + Write(matrix.m11); + Write(matrix.m12); + Write(matrix.m13); + Write(matrix.m20); + Write(matrix.m21); + Write(matrix.m22); + Write(matrix.m23); + Write(matrix.m30); + Write(matrix.m31); + Write(matrix.m32); + Write(matrix.m33); + } + + public Matrix4x4 ReadMatrix4X4() + { + return new Matrix4x4 + { + m00 = ReadSingle(), + m01 = ReadSingle(), + m02 = ReadSingle(), + m03 = ReadSingle(), + m10 = ReadSingle(), + m11 = ReadSingle(), + m12 = ReadSingle(), + m13 = ReadSingle(), + m20 = ReadSingle(), + m21 = ReadSingle(), + m22 = ReadSingle(), + m23 = ReadSingle(), + m30 = ReadSingle(), + m31 = ReadSingle(), + m32 = ReadSingle(), + m33 = ReadSingle() + }; + } + + public void Write(Rect rect) + { + Write(rect.xMin); + Write(rect.yMin); + Write(rect.width); + Write(rect.height); + } + + public Rect ReadRect() + { + return new Rect( + x: ReadSingle(), + y: ReadSingle(), + width: ReadSingle(), + height: ReadSingle()); + } + } +} diff --git a/SRMP/Lidgren.Network/Platform/PlatformWin32.cs b/SRMP/Lidgren.Network/Platform/PlatformWin32.cs new file mode 100644 index 0000000..79f9ced --- /dev/null +++ b/SRMP/Lidgren.Network/Platform/PlatformWin32.cs @@ -0,0 +1,156 @@ +#if !__ANDROID__ && !__CONSTRAINED__ && !WINDOWS_RUNTIME && !UNITY_STANDALONE_LINUX +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Security.Cryptography; + +namespace Lidgren.Network +{ + public static partial class NetUtility + { + private static readonly long s_timeInitialized = Stopwatch.GetTimestamp(); + private static readonly double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency; + + [CLSCompliant(false)] + public static ulong GetPlatformSeed(int seedInc) + { + ulong seed = (ulong)System.Diagnostics.Stopwatch.GetTimestamp(); + return seed ^ ((ulong)Environment.WorkingSet + (ulong)seedInc); + } + + public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } } + + private static NetworkInterface GetNetworkInterface() + { + var computerProperties = IPGlobalProperties.GetIPGlobalProperties(); + if (computerProperties == null) + return null; + + var nics = NetworkInterface.GetAllNetworkInterfaces(); + if (nics == null || nics.Length < 1) + return null; + + NetworkInterface best = null; + foreach (NetworkInterface adapter in nics) + { + if (adapter.NetworkInterfaceType == NetworkInterfaceType.Loopback || adapter.NetworkInterfaceType == NetworkInterfaceType.Unknown) + continue; + if (!adapter.Supports(NetworkInterfaceComponent.IPv4)) + continue; + if (best == null) + best = adapter; + if (adapter.OperationalStatus != OperationalStatus.Up) + continue; + + // make sure this adapter has any ipv4 addresses + IPInterfaceProperties properties = adapter.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses) + { + if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) + { + // Yes it does, return this network interface. + return adapter; + } + } + } + return best; + } + + /// + /// If available, returns the bytes of the physical (MAC) address for the first usable network interface + /// + public static byte[] GetMacAddressBytes() + { + var ni = GetNetworkInterface(); + if (ni == null) + return null; + return ni.GetPhysicalAddress().GetAddressBytes(); + } + + public static IPAddress GetBroadcastAddress() + { + var ni = GetNetworkInterface(); + if (ni == null) + return null; + + var properties = ni.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses) + { + if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) + { + var mask = unicastAddress.IPv4Mask; + byte[] ipAdressBytes = unicastAddress.Address.GetAddressBytes(); + byte[] subnetMaskBytes = mask.GetAddressBytes(); + + if (ipAdressBytes.Length != subnetMaskBytes.Length) + throw new ArgumentException("Lengths of IP address and subnet mask do not match."); + + byte[] broadcastAddress = new byte[ipAdressBytes.Length]; + for (int i = 0; i < broadcastAddress.Length; i++) + { + broadcastAddress[i] = (byte)(ipAdressBytes[i] | (subnetMaskBytes[i] ^ 255)); + } + return new IPAddress(broadcastAddress); + } + } + return IPAddress.Broadcast; + } + + /// + /// Gets my local IPv4 address (not necessarily external) and subnet mask + /// + public static IPAddress GetMyAddress(out IPAddress mask) + { + var ni = GetNetworkInterface(); + if (ni == null) + { + mask = null; + return null; + } + + IPInterfaceProperties properties = ni.GetIPProperties(); + foreach (UnicastIPAddressInformation unicastAddress in properties.UnicastAddresses) + { + if (unicastAddress != null && unicastAddress.Address != null && unicastAddress.Address.AddressFamily == AddressFamily.InterNetwork) + { + mask = unicastAddress.IPv4Mask; + return unicastAddress.Address; + } + } + + mask = null; + return null; + } + + public static void Sleep(int milliseconds) + { + System.Threading.Thread.Sleep(milliseconds); + } + + public static IPAddress CreateAddressFromBytes(byte[] bytes) + { + return new IPAddress(bytes); + } + + private static readonly SHA256 s_sha = SHA256.Create(); + public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count) + { + return s_sha.ComputeHash(bytes, offset, count); + } + } + + public static partial class NetTime + { + private static readonly long s_timeInitialized = Stopwatch.GetTimestamp(); + private static readonly double s_dInvFreq = 1.0 / (double)Stopwatch.Frequency; + + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)(Stopwatch.GetTimestamp() - s_timeInitialized) * s_dInvFreq; } } + } +} +#endif \ No newline at end of file diff --git a/SRMP/Lidgren.Network/Platform/PlatformWinRT.cs b/SRMP/Lidgren.Network/Platform/PlatformWinRT.cs new file mode 100644 index 0000000..d2502ca --- /dev/null +++ b/SRMP/Lidgren.Network/Platform/PlatformWinRT.cs @@ -0,0 +1,102 @@ +#if WINDOWS_RUNTIME +// +// +// +// Completely broken right now +// +// +// +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Threading.Tasks; + +namespace Lidgren.Network +{ + public class NetAddress + { + public static readonly HostName Any = null; + } + + public class NetEndPoint + { + public NetEndPoint(HostName hostname, int port) { HostName = hostname; Port = port; } + public NetEndPoint(HostName hostname, string port) { HostName = hostname; Port = int.Parse(port); } + public NetEndPoint(string hostname, int port) { HostName = (hostname == null) ? null : new HostName(hostname); Port = port; } + public HostName HostName; + public int Port; + public override string ToString() { return string.Format("{0}:{1}", HostName, Port); } + public override int GetHashCode() + { + return HostName.RawName.GetHashCode() + Port; + } + public override bool Equals(object obj) + { + var ep = obj as NetEndPoint; + if (ep == null) return false; + if (Port != ep.Port) return false; + return HostName.RawName.Equals(ep.HostName.RawName); + } + }; + + public static partial class NetUtility + { + [CLSCompliant(false)] + public static ulong GetPlatformSeed(int seedInc) + { + ulong seed = (ulong)Environment.TickCount + (ulong)seedInc; + return seed ^ ((ulong)(new object().GetHashCode()) << 32); + } + + /// + /// Returns the physical (MAC) address for the first usable network interface + /// + public static PhysicalAddress GetMacAddress() + { + throw new NotImplementedException(); + } + + public static IPAddress GetBroadcastAddress() + { + throw new NotImplementedException(); + } + + /// + /// Gets my local IPv4 address (not necessarily external) and subnet mask + /// + public static IPAddress GetMyAddress(out IPAddress mask) + { + throw new NotImplementedException(); + } + + public static void Sleep(int milliseconds) + { + Task.Delay(50).Wait(); + } + + public static NetAddress CreateAddressFromBytes(byte[] bytes) + { + throw new NotImplementedException(); + } + + private static readonly SHA1CryptoServiceProvider s_sha = new SHA1CryptoServiceProvider(); + public static byte[] ComputeSHAHash(byte[] bytes, int offset, int count) + { + return s_sha.ComputeHash(bytes, offset, count); + } + } + + public static partial class NetTime + { + private static readonly long s_timeInitialized = Environment.TickCount; + + /// + /// Get number of seconds since the application started + /// + public static double Now { get { return (double)((uint)Environment.TickCount - s_timeInitialized) / 1000.0; } } + } +} +#endif \ No newline at end of file diff --git a/SRMP/MainSRML.cs b/SRMP/MainSRML.cs new file mode 100644 index 0000000..d37551a --- /dev/null +++ b/SRMP/MainSRML.cs @@ -0,0 +1,93 @@ +#if SRML +using Newtonsoft.Json; +using SRML; +using SRML.SR; +using SRMultiplayer.Networking; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace SRMultiplayer +{ + public class MainSRML : ModEntryPoint + { + private static GameObject m_GameObject; + + // Called before GameContext.Awake + // this is where you want to register stuff (like custom enum values or identifiable id's) + // and patch anything you want to patch with harmony + public override void PreLoad() + { + base.PreLoad(); + } + + + // Called right before PostLoad + // Used to register stuff that needs lookupdirector access + public override void Load() + { + if (m_GameObject != null) return; + + SRMP.Log("Loading SRMP SRML Version"); + + if (!Directory.Exists(SRMP.ModDataPath)) + { + Directory.CreateDirectory(SRMP.ModDataPath); + } + if (!File.Exists(Path.Combine(SRMP.ModDataPath, "userdata.json"))) + { + Globals.UserData = new UserData() + { + UUID = System.Guid.NewGuid(), + CheckDLC = true, + IgnoredMods = new System.Collections.Generic.List() + }; + File.WriteAllText(Path.Combine(SRMP.ModDataPath, "userdata.json"), JsonConvert.SerializeObject(Globals.UserData)); + SRMP.Log("Created userdata with UUID " + Globals.UserData.UUID); + } + else + { + Globals.UserData = JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(SRMP.ModDataPath, "userdata.json"))); + if(Globals.UserData.IgnoredMods == null) + { + Globals.UserData.IgnoredMods = new System.Collections.Generic.List(); + } + SRMP.Log("Loaded userdata with UUID " + Globals.UserData.UUID); + } + + string[] args = System.Environment.GetCommandLineArgs(); + + m_GameObject = new GameObject("SRMP"); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + + GameObject.DontDestroyOnLoad(m_GameObject); + + Globals.Version = Assembly.GetExecutingAssembly().GetName().Version.Revision; + + Application.runInBackground = true; + + HarmonyPatcher.GetInstance().PatchAll(Assembly.GetExecutingAssembly()); + } + + + // Called after GameContext.Start + // stuff like gamecontext.lookupdirector are available in this step, generally for when you want to access + // ingame prefabs and the such + public override void PostLoad() + { + + } + } +} +#endif \ No newline at end of file diff --git a/SRMP/MainStandalone.cs b/SRMP/MainStandalone.cs new file mode 100644 index 0000000..f769e4f --- /dev/null +++ b/SRMP/MainStandalone.cs @@ -0,0 +1,71 @@ +#if Standalone +using HarmonyLib; +using Newtonsoft.Json; +using SRMultiplayer.Networking; +using System.IO; +using System.Reflection; +using UnityCoreMod; +using UnityEngine; + +namespace SRMultiplayer +{ + public class MainSaty : IUnityMod + { + private static GameObject m_GameObject; + + public void Load() + { + if (m_GameObject != null) return; + + SRMP.Log("Loading SRMP Standalone Version"); + + if (!Directory.Exists(SRMP.ModDataPath)) + { + Directory.CreateDirectory(SRMP.ModDataPath); + } + if (!File.Exists(Path.Combine(SRMP.ModDataPath, "userdata.json"))) + { + Globals.UserData = new UserData() + { + UUID = System.Guid.NewGuid(), + CheckDLC = true, + IgnoredMods = new System.Collections.Generic.List() + }; + File.WriteAllText(Path.Combine(SRMP.ModDataPath, "userdata.json"), JsonConvert.SerializeObject(Globals.UserData)); + SRMP.Log("Created userdata with UUID " + Globals.UserData.UUID); + } + else + { + Globals.UserData = JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(SRMP.ModDataPath, "userdata.json"))); + if(Globals.UserData.IgnoredMods == null) + { + Globals.UserData.IgnoredMods = new System.Collections.Generic.List(); + } + SRMP.Log("Loaded userdata with UUID " + Globals.UserData.UUID); + } + + string[] args = System.Environment.GetCommandLineArgs(); + + m_GameObject = new GameObject("SRMP"); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + m_GameObject.AddComponent(); + + GameObject.DontDestroyOnLoad(m_GameObject); + + Globals.Version = Assembly.GetExecutingAssembly().GetName().Version.Revision; + + var harmony = new Harmony("saty.mod.srmp"); + harmony.PatchAll(Assembly.GetExecutingAssembly()); + + Application.runInBackground = true; + } + + public void Unload() { } + } +} +#endif \ No newline at end of file diff --git a/SRMP/MultiplayerUI.cs b/SRMP/MultiplayerUI.cs new file mode 100644 index 0000000..b48b521 --- /dev/null +++ b/SRMP/MultiplayerUI.cs @@ -0,0 +1,393 @@ +using UnityEngine; +using System.Collections; +using SRMultiplayer; +using UnityEngine.SceneManagement; +using System; +using System.Text.RegularExpressions; +using SRMultiplayer.Networking; + +public class MultiplayerUI : SRSingleton +{ + private Rect windowRect = new Rect(Screen.width - 300, 20, 300, 500); + private Vector2 playersScroll = Vector2.zero; + private string ipaddress = "localhost"; + private string port = "16500"; + private string servercode = ""; + private bool menuOpen; + private string username; + private float lastCodeUse; + private ConnectError error; + private ConnectHelp help; + private string errorMessage; + + public enum ConnectError + { + None, + InvalidServerCode, + ServerCodeTimeout, + Kicked, + Message + } + + public enum ConnectHelp + { + None, + ServerCode, + Hosting, + } + + public override void Awake() + { + base.Awake(); + + Globals.Username = PlayerPrefs.GetString("SRMP_Username", ""); + ipaddress = PlayerPrefs.GetString("SRMP_IP", "localhost"); + port = PlayerPrefs.GetString("SRMP_Port", "16500"); + + menuOpen = true; + username = Globals.Username; + } + + private void Update() + { + if(lastCodeUse > 0f) + { + var prevTime = lastCodeUse; + lastCodeUse -= Time.deltaTime; + if(prevTime > 0f && lastCodeUse <= 0f) + { + error = ConnectError.ServerCodeTimeout; + } + } + + if(Input.GetKeyDown(KeyCode.F4)) + { + menuOpen = !menuOpen; + } + } + + private void OnGUI() + { + if (!menuOpen || (!Levels.isMainMenu() && !Globals.IsMultiplayer && Globals.PauseState != PauseState.Pause)) return; + if (Globals.IsMultiplayer && Globals.PauseState != PauseState.Pause) return; + + if (SceneManager.GetActiveScene().buildIndex >= 2) + { + windowRect = GUI.Window(1, windowRect, MultiplayerWindow, "SRMP v" + Globals.Version); + } + } + + private void MultiplayerWindow(int id) + { + if (id != 1) return; + + GUILayout.Label("You can close this menu with F4"); + GUILayout.Space(20); + + if(string.IsNullOrWhiteSpace(Globals.Username)) + { + UsernameGUI(); + } + else + { + if(Globals.IsServer) + { + ServerGUI(); + } + else if(Globals.IsClient) + { + ClientGUI(); + } + else + { + if (error != ConnectError.None) + { + ErrorGUI(); + } + else if(help != ConnectHelp.None) + { + HelpGUI(); + } + else + { + if(lastCodeUse > 0f) + { + GUILayout.Label("Trying to connect with server code..."); + } + else if (Levels.isMainMenu()) + { + ConnectGUI(); + } + else + { + HostGUI(); + } + } + } + } + + GUI.DragWindow(new Rect(0, 0, 10000, 10000)); + } + + private void ServerGUI() + { + GUILayout.Label("You are the server"); + GUILayout.Space(20); + + GUILayout.Label("Server Code: " + Globals.ServerCode); + GUILayout.Label("Players"); + playersScroll = GUILayout.BeginScrollView(playersScroll, GUI.skin.box); + foreach (var player in Globals.Players.Values) + { + if (player.IsLocal) continue; + + GUILayout.BeginHorizontal(); + GUILayout.Label(player.Username); + if(GUILayout.Button("Kick")) + { + player.Connection.Disconnect("kicked"); + } + GUILayout.EndHorizontal(); + } + GUILayout.EndScrollView(); + } + + private void ClientGUI() + { + GUILayout.Label("You are a client"); + GUILayout.Space(20); + + GUILayout.Label("Players"); + playersScroll = GUILayout.BeginScrollView(playersScroll, GUI.skin.box); + foreach (var player in Globals.Players.Values) + { + if (player.IsLocal) continue; + + GUILayout.BeginHorizontal(); + GUILayout.Label(player.Username); + GUILayout.EndHorizontal(); + } + GUILayout.EndScrollView(); + } + + private void ConnectGUI() + { + GUILayout.Label("Username: " + Globals.Username); + if (GUILayout.Button("Change Username")) + { + Globals.Username = ""; + return; + } + GUILayout.Space(20); + if(GUILayout.Button("How do I host a game?")) + { + help = ConnectHelp.Hosting; + } + GUILayout.Space(20); + + if (SRSingleton.Instance.Status == NetworkMasterServer.ConnectionStatus.Connected) + { + GUILayout.Label("Join with server code:"); + GUILayout.BeginHorizontal(); + servercode = GUILayout.TextField(servercode, 5, GUILayout.Width(80)); + servercode = servercode.Replace(" ", "").ToUpper(); + if (GUILayout.Button("Join")) + { + lastCodeUse = 5; + SRSingleton.Instance.JoinServer(servercode); + } + GUILayout.EndHorizontal(); + } + else + { + GUILayout.Label("Server codes currently not available"); + } + + GUILayout.Space(20); + + GUILayout.Label("Join with IP Address:"); + GUILayout.BeginHorizontal(); + GUILayout.Label("IP Address", GUILayout.Width(80)); + ipaddress = GUILayout.TextField(ipaddress); + GUILayout.EndHorizontal(); + GUILayout.BeginHorizontal(); + GUILayout.Label("Port", GUILayout.Width(80)); + port = GUILayout.TextField(port); + GUILayout.EndHorizontal(); + if (string.IsNullOrWhiteSpace(ipaddress)) + { + GUILayout.Label("IPAddress invalid"); + } + else if (!int.TryParse(port, out int numport) || numport < 1000 || numport > 65000) + { + GUILayout.Label("Invalid port, should be between 1000 and 65000"); + } + else + { + if (GUILayout.Button("Connect")) + { + SRSingleton.Instance.Connect(ipaddress, numport, Globals.Username); + SaveSettings(); + } + } + + GUILayout.Space(20); + GUILayout.Label("Games in the same Network:"); + if (GUILayout.Button("Refresh")) + { + SRSingleton.Instance.SendDiscoverMessage(); + } + if (SRSingleton.Instance.LocalGames.Count == 0) + { + GUILayout.Label("No games found"); + } + foreach (var game in SRSingleton.Instance.LocalGames) + { + if (GUILayout.Button(game.Name)) + { + SRSingleton.Instance.Connect(game.IP, game.Port, Globals.Username); + } + } + } + + private void HostGUI() + { + GUILayout.Label("Username: " + Globals.Username); + if (GUILayout.Button("Change Username")) + { + Globals.Username = ""; + return; + } + + GUILayout.BeginHorizontal(); + GUILayout.Label("Port", GUILayout.Width(80)); + port = GUILayout.TextField(port); + GUILayout.EndHorizontal(); + + if (!int.TryParse(port, out int numport) || numport < 1000 || numport > 65000) + { + GUILayout.Label("Invalid port"); + } + else + { + if (GUILayout.Button("Host")) + { + NetworkServer.Instance.StartServer(numport); + SaveSettings(); + } + } + } + + private void HelpGUI() + { + switch(help) + { + case ConnectHelp.ServerCode: + { + + } + break; + case ConnectHelp.Hosting: + { + GUILayout.Label("You can host a game by loading any Singleplayer save."); + GUILayout.Label("When the game is loaded, pause it and the HostUI should appear."); + GUILayout.Label("(Please make a backup of your Singleplayer save to avoid data loss on crashes or error)"); + + if (GUILayout.Button("Okay")) + { + help = ConnectHelp.None; + } + } + break; + } + } + + private void ErrorGUI() + { + switch(error) + { + case ConnectError.InvalidServerCode: + { + GUILayout.Label("There is no server with this server code."); + + if (GUILayout.Button("Okay")) + { + error = ConnectError.None; + } + } + break; + case ConnectError.Kicked: + { + GUILayout.Label("You got kicked from the game"); + + if (GUILayout.Button("Okay")) + { + error = ConnectError.None; + } + } + break; + case ConnectError.Message: + { + GUILayout.Label("Connection Error:"); + GUILayout.Label(errorMessage); + + if (GUILayout.Button("Okay")) + { + error = ConnectError.None; + } + } + break; + case ConnectError.ServerCodeTimeout: + { + GUILayout.Label("Connection via server code timed out."); + GUILayout.Label("Server codes do not have a 100% success chance."); + GUILayout.Label("You can try again, but if it keeps failing, you may have to port forward."); + GUILayout.Label("If you can't port forward, we recommend a service like 'Hamachi' or 'Radmin'"); + GUILayout.Label("Please note: Hamachi and Radmin degrade the experience by quite a lot. Port forwarding is always better."); + GUILayout.Label("(We can not offer support for port forwarding)"); + + if (GUILayout.Button("Okay")) + { + error = ConnectError.None; + } + } + break; + } + } + + private void UsernameGUI() + { + GUILayout.BeginHorizontal(); + GUILayout.Label("Username", GUILayout.Width(60)); + username = GUILayout.TextField(username, 30); + GUILayout.EndHorizontal(); + + if (string.IsNullOrWhiteSpace(username) || !new Regex(@"^[a-zA-Z_][\w]*$").IsMatch(username)) + { + GUILayout.Label("Invalid username"); + } + else + { + if (GUILayout.Button("Save Username")) + { + Globals.Username = username; + SaveSettings(); + SRSingleton.Instance.UpdateName(Globals.Username); + } + } + } + + private void SaveSettings() + { + PlayerPrefs.SetString("SRMP_Username", Globals.Username); + PlayerPrefs.SetString("SRMP_IP", ipaddress); + PlayerPrefs.GetString("SRMP_Port", port); + } + + public void ConnectResponse(ConnectError connectError, string message = "") + { + lastCodeUse = 0f; + error = connectError; + errorMessage = message; + } +} diff --git a/SRMP/Networking/NetworkAccessDoor.cs b/SRMP/Networking/NetworkAccessDoor.cs new file mode 100644 index 0000000..77ca919 --- /dev/null +++ b/SRMP/Networking/NetworkAccessDoor.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace SRMultiplayer.Networking +{ + public class NetworkAccessDoor : MonoBehaviour + { + public AccessDoor Door; + public NetworkRegion Region; + } +} diff --git a/SRMP/Networking/NetworkActor.cs b/SRMP/Networking/NetworkActor.cs new file mode 100644 index 0000000..f0a2feb --- /dev/null +++ b/SRMP/Networking/NetworkActor.cs @@ -0,0 +1,258 @@ +using MonomiPark.SlimeRancher.Regions; +using SRMultiplayer.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace SRMultiplayer.Networking +{ + public class NetworkActor : MonoBehaviour + { + public int ID { get; internal set; } + public byte Owner; + public ushort Ident; + public byte RegionSet; + public bool IsLocal { get { return Owner == Globals.LocalID; } } + public List KnownPlayers = new List(); + + private Rigidbody m_Rigidbody; + private RegionMember m_RegionMember; + private float m_MovementUpdateTimer; + private Vector3 m_ActualPosition; + private Vector3 m_LatestPosition; + private Vector3 m_PreviousPosition; + private Quaternion m_ActualRotation; + private Quaternion m_LatestRotation; + private Quaternion m_PreviousRotation; + private float m_LatestPositionTime; + private float m_InterpolationPeriod = 0.1f; + private Dictionary m_PreviousEmotions = new Dictionary(); + + public Reproduce Reproduce; + public SlimeEat SlimeEat; + public ResourceCycle ResourceCycle; + public DestroyPlortAfterTime DestroyPlortAfterTime; + + private void Awake() + { + m_Rigidbody = GetComponentInChildren(); + m_RegionMember = GetComponent(); + Reproduce = GetComponent(); + SlimeEat = GetComponent(); + ResourceCycle = GetComponent(); + DestroyPlortAfterTime = GetComponent(); + + m_PreviousEmotions.Add(SlimeEmotions.Emotion.AGITATION, 0); + m_PreviousEmotions.Add(SlimeEmotions.Emotion.FEAR, 0); + m_PreviousEmotions.Add(SlimeEmotions.Emotion.HUNGER, 0); + } + + private void OnEnable() + { + if(Owner == 0) + { + TakeOwnership(); + } + } + + private void OnDisable() + { + if (IsLocal) + { + DropOwnership(); + } + } + + private void Update() + { + if(IsLocal) + { + m_MovementUpdateTimer -= Time.deltaTime; + if (m_MovementUpdateTimer <= 0) + { + m_MovementUpdateTimer = 0.1f; + if (Vector3.Distance(m_ActualPosition, transform.position) > 0.1f || Vector3.Distance(m_ActualRotation.eulerAngles, transform.eulerAngles) > 0.1f) + { + m_LatestPosition = transform.position; + m_LatestPositionTime = Time.time + m_InterpolationPeriod; + m_PreviousPosition = m_LatestPosition; + m_ActualPosition = m_LatestPosition; + m_LatestRotation = transform.rotation; + m_PreviousRotation = m_LatestRotation; + m_ActualRotation = m_LatestRotation; + + new PacketActorPosition() + { + ID = ID, + Position = transform.position, + Rotation = transform.rotation + }.Send(Lidgren.Network.NetDeliveryMethod.Unreliable); + } + + if(SlimeEat != null && + (!Utils.CloseEnoughForMe(SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.AGITATION), m_PreviousEmotions[SlimeEmotions.Emotion.AGITATION], 0.1f) || + !Utils.CloseEnoughForMe(SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.FEAR), m_PreviousEmotions[SlimeEmotions.Emotion.FEAR], 0.1f) || + !Utils.CloseEnoughForMe(SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.HUNGER), m_PreviousEmotions[SlimeEmotions.Emotion.HUNGER], 0.1f))) + { + m_PreviousEmotions[SlimeEmotions.Emotion.AGITATION] = SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.AGITATION); + m_PreviousEmotions[SlimeEmotions.Emotion.FEAR] = SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.FEAR); + m_PreviousEmotions[SlimeEmotions.Emotion.HUNGER] = SlimeEat.emotions.GetCurr(SlimeEmotions.Emotion.HUNGER); + + new PacketActorEmotions() + { + ID = ID, + Fear = m_PreviousEmotions[SlimeEmotions.Emotion.FEAR], + Agitation = m_PreviousEmotions[SlimeEmotions.Emotion.AGITATION], + Hunger = m_PreviousEmotions[SlimeEmotions.Emotion.HUNGER] + }.Send(); + } + } + } + else + { + if(m_Rigidbody != null) + { + m_Rigidbody.velocity = Vector3.zero; + } + + float t = 1.0f - ((m_LatestPositionTime - Time.time) / m_InterpolationPeriod); + m_ActualPosition = Vector3.Lerp(m_PreviousPosition, m_LatestPosition, t); + transform.position = m_ActualPosition; + + m_ActualRotation = Quaternion.Slerp(m_PreviousRotation, m_LatestRotation, t); + transform.rotation = m_ActualRotation; + } + } + + public void PositionRotationUpdate(Vector3 pos, Quaternion rot, bool immediate) + { + if (Vector3.Distance(pos, m_ActualPosition) > 10f) // Teleport detection + { + immediate = true; + } + if (!immediate) + { + m_LatestPosition = pos; + m_LatestPositionTime = Time.time + m_InterpolationPeriod; + m_PreviousPosition = m_ActualPosition; + + m_LatestRotation = rot; + m_PreviousRotation = m_ActualRotation; + } + else + { + m_LatestPosition = pos; + m_LatestPositionTime = Time.time + m_InterpolationPeriod; + m_PreviousPosition = pos; + m_ActualPosition = pos; + transform.position = m_ActualPosition; + + m_PreviousRotation = rot; + m_LatestRotation = rot; + m_ActualRotation = rot; + transform.rotation = rot; + } + + if (m_RegionMember != null && m_RegionMember.hibernating) + { + m_RegionMember.UpdateRegionMembership(true); + } + } + + public void TakeOwnership() + { + if (Globals.IsServer) + { + SetOwnership(Globals.LocalID); + } + + new PacketActorOwner() + { + ID = ID, + Owner = Globals.LocalID + }.Send(); + } + + public void DropOwnership() + { + SetOwnership(0); + + new PacketActorOwner() + { + ID = ID, + Owner = 0 + }.Send(); + } + + public void SetOwnership(byte id) + { + Owner = id; + + if (Owner != Globals.LocalID) + { + if (SRSingleton.Instance.Player != null) + { + var weapon = SRSingleton.Instance.Player.GetComponentInChildren(); + if (weapon != null && weapon.held != null && weapon.held.GetComponent() != null && weapon.held.GetComponent().ID == ID) + { + weapon.DropAllVacced(); + } + } + } + else + { + if (m_Rigidbody != null) + { + m_Rigidbody.velocity = Vector3.zero; + } + } + + if (Owner == 0 && gameObject.activeInHierarchy) + { + TakeOwnership(); + } + } + + internal void OnDestroyEffect() + { + var slimeKey = GetComponentInChildren(); + if (slimeKey != null) + { + if (slimeKey.pickupFX != null) + { + SRBehaviour.SpawnAndPlayFX(slimeKey.pickupFX, slimeKey.transform.position, slimeKey.transform.rotation); + } + } + var destroyOnTouching = GetComponentInChildren(); + if(destroyOnTouching != null) + { + if (destroyOnTouching.destroyFX != null) + { + SRBehaviour.SpawnAndPlayFX(destroyOnTouching.destroyFX, destroyOnTouching.transform.position, destroyOnTouching.transform.rotation); + } + } + var exchangeBreakOnImpact = GetComponentInChildren(); + if (exchangeBreakOnImpact != null) + { + SRBehaviour.SpawnAndPlayFX(exchangeBreakOnImpact.breakFX, exchangeBreakOnImpact.gameObject.transform.position, exchangeBreakOnImpact.gameObject.transform.rotation); + } + var breakOnImpact = GetComponentInChildren(); + if (breakOnImpact != null) + { + SRBehaviour.SpawnAndPlayFX(breakOnImpact.breakFX, breakOnImpact.gameObject.transform.position, breakOnImpact.gameObject.transform.rotation); + } + var quicksilver = GetComponentInChildren(); + if(quicksilver != null) + { + if (quicksilver.destroyFX != null) + { + SRBehaviour.SpawnAndPlayFX(quicksilver.destroyFX, quicksilver.transform.position, Quaternion.identity); + } + SECTR_AudioSystem.Play(quicksilver.onCollectionCue, quicksilver.transform.position, false); + } + } + } +} diff --git a/SRMP/Networking/NetworkAmmo.cs b/SRMP/Networking/NetworkAmmo.cs new file mode 100644 index 0000000..2265b3b --- /dev/null +++ b/SRMP/Networking/NetworkAmmo.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; + +namespace SRMultiplayer.Networking +{ + public class NetworkAmmo : Ammo + { + public static Dictionary All = new Dictionary(); + + public string ID; + public SiloStorage Silo; + + public NetworkAmmo(SiloStorage silo, HashSet potentialAmmo, int numSlots, int usableSlots, Predicate[] slotPreds, Func slotMaxCountFunction) : base(potentialAmmo, numSlots, usableSlots, slotPreds, slotMaxCountFunction) + { + Silo = silo; + var landplotLocation = GetInParent(silo.gameObject); + if (landplotLocation != null) + { + ID = landplotLocation.id; + } + else + { + ID = GetInParent(silo.gameObject).id; + } + ID += "-" + silo.name; + + All[ID] = this; + } + + private T GetInParent(GameObject obj) + { + var cmp = obj.GetComponent(); + if(cmp != null) + { + return cmp; + } + if(obj.transform.parent != null) + { + return GetInParent(obj.transform.parent.gameObject); + } + return default(T); + } + } +} diff --git a/SRMP/Networking/NetworkClient.cs b/SRMP/Networking/NetworkClient.cs new file mode 100644 index 0000000..4e7ea68 --- /dev/null +++ b/SRMP/Networking/NetworkClient.cs @@ -0,0 +1,264 @@ +using Lidgren.Network; +using SRMultiplayer.Packets; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Text; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace SRMultiplayer.Networking +{ + public class NetworkClient : SRSingleton + { + private NetClient m_Client; + + public enum ConnectionStatus + { + Disconnected, + Connecting, + Connected + } + + public struct LocalGame + { + public string Name; + public string IP; + public int Port; + } + + public ConnectionStatus Status; + public NetPeerStatistics Statistics { get { return m_Client.Statistics; } } + public int Port { get { return m_Client.Port; } } + public List LocalGames = new List(); + + private void Start() + { + NetPeerConfiguration config = new NetPeerConfiguration("srmp"); + config.EnableMessageType(NetIncomingMessageType.NatIntroductionSuccess); + config.EnableMessageType(NetIncomingMessageType.DiscoveryResponse); + + m_Client = new NetClient(config); + m_Client.Start(); + } + + public void SendDiscoverMessage() + { + LocalGames.Clear(); + m_Client.DiscoverLocalPeers(6996); + } + + public void Connect(string ip, int port, string username) + { + Status = ConnectionStatus.Connecting; + + NetOutgoingMessage hail = CreateMessage(); + hail.Write(Globals.Version); + hail.Write(Globals.UserData.UUID.ToByteArray()); + hail.Write(username); + hail.Write(Globals.Mods.Count); + foreach (var mod in Globals.Mods) + { + hail.Write(mod); + } + var dlcs = SRSingleton.Instance.DLCDirector.Installed; + hail.Write(dlcs.Count()); + foreach (var dlc in dlcs) + { + hail.Write((byte)dlc); + } + m_Client.Connect(ip, port, hail); + } + + public void Connect(IPEndPoint ip, string username) + { + SRMP.Log("Connect " + ip.ToString()); + if (Status != ConnectionStatus.Disconnected) + { + SRMP.Log("Already connecting... abort"); + return; + } + + NetOutgoingMessage hail = CreateMessage(); + hail.Write(Globals.Version); + hail.Write(Globals.UserData.UUID.ToByteArray()); + hail.Write(username); + hail.Write(Globals.Mods.Count); + foreach (var mod in Globals.Mods) + { + hail.Write(mod); + } + var dlcs = SRSingleton.Instance.DLCDirector.Installed; + hail.Write(dlcs.Count()); + foreach(var dlc in dlcs) + { + hail.Write((byte)dlc); + } + m_Client.Connect(ip, hail); + } + + private void Update() + { + NetIncomingMessage im; + while ((im = m_Client?.ReadMessage()) != null) + { + switch (im.MessageType) + { + case NetIncomingMessageType.DebugMessage: + case NetIncomingMessageType.ErrorMessage: + case NetIncomingMessageType.WarningMessage: + case NetIncomingMessageType.VerboseDebugMessage: + { + string text = im.ReadString(); + SRMP.Log("[NetworkClient] " + text); + } + break; + case NetIncomingMessageType.NatIntroductionSuccess: + { + string token = im.ReadString(); + SRMP.Log("[NetworkClient] Nat introduction success to " + im.SenderEndPoint + " token is: " + token); + + MultiplayerUI.Instance.ConnectResponse(MultiplayerUI.ConnectError.None); + + Connect(im.SenderEndPoint, Globals.Username); + } + break; + case NetIncomingMessageType.DiscoveryResponse: + { + var game = new LocalGame() + { + Name = im.ReadString(), + Port = im.ReadInt32(), + IP = im.SenderEndPoint.Address.ToString() + }; + LocalGames.Add(game); + Console.WriteLine("Found server at " + im.SenderEndPoint + " name: " + game.Name); + } + break; + case NetIncomingMessageType.StatusChanged: + { + NetConnectionStatus status = (NetConnectionStatus)im.ReadByte(); + string reason = im.ReadString(); + SRMP.Log("[NetworkClient] " + NetUtility.ToHexString(im.SenderConnection.RemoteUniqueIdentifier) + " " + status + ": " + reason); + + if (status == NetConnectionStatus.Connected) + { + Status = ConnectionStatus.Connected; + NetIncomingMessage hail = im.SenderConnection.RemoteHailMessage; + Globals.LocalID = hail.ReadByte(); + + int playerCount = hail.ReadInt32(); + for (int i = 0; i < playerCount; i++) + { + byte id = hail.ReadByte(); + string username = hail.ReadString(); + bool hasloaded = hail.ReadBoolean(); + + var playerObject = new GameObject($"{username} ({id})"); + var player = playerObject.AddComponent(); + DontDestroyOnLoad(playerObject); + + player.ID = id; + player.Username = username; + player.HasLoaded = hasloaded; + Globals.Players.Add(id, player); + + if (id == Globals.LocalID) + { + Globals.LocalPlayer = player; + } + } + Globals.PartyID = new Guid(hail.ReadBytes(16)); + var gameMode = (PlayerState.GameMode)hail.ReadByte(); + Globals.CurrentGameName = hail.ReadString(); + + SRSingleton.Instance.AutoSaveDirector.LoadNewGame("SRMultiplayerGame", Identifiable.Id.GOLD_SLIME, gameMode, () => + { + Disconnect("Error loading save"); + + SceneManager.LoadScene(2); + }); + } + else if (status == NetConnectionStatus.Disconnected) + { + if (reason != "goodbye") + { + if(reason.Equals("kicked")) + { + MultiplayerUI.Instance.ConnectResponse(MultiplayerUI.ConnectError.Kicked); + } + else + { + MultiplayerUI.Instance.ConnectResponse(MultiplayerUI.ConnectError.Message, reason); + } + } + Status = ConnectionStatus.Disconnected; + if (SceneManager.GetActiveScene().buildIndex == 3) + { + SceneManager.LoadScene(2); + } + } + } + break; + case NetIncomingMessageType.Data: + { + PacketType type = (PacketType)im.ReadUInt16(); + + Globals.HandlePacket = true; + try + { + NetworkHandlerClient.HandlePacket(type, im); + } + catch (Exception ex) + { + SRMP.Log($"[NetworkClient] Could not handle packet {type}\n{ex}"); + } + Globals.HandlePacket = false; + } + break; + default: + SRMP.Log("[NetworkClient] Unhandled type: " + im.MessageType + " " + im.LengthBytes + " bytes " + im.DeliveryMethod + "|" + im.SequenceChannel); + break; + } + m_Client.Recycle(im); + } + } + + public void Disconnect(string message = "goodbye") + { + Status = ConnectionStatus.Disconnected; + m_Client?.Disconnect(message); + } + + public void Send(Packet packet, NetDeliveryMethod method, int sequence) + { + if (m_Client == null || m_Client.ConnectionStatus != NetConnectionStatus.Connected) return; + + var om = CreateMessage(); + packet.Serialize(om); + + m_Client?.SendMessage(om, method, sequence); + } + + public void Send(NetOutgoingMessage om, NetDeliveryMethod method, int sequence) + { + if (m_Client == null || m_Client.ConnectionStatus != NetConnectionStatus.Connected) return; + + m_Client?.SendMessage(om, method, sequence); + } + + public void SendUnconnected(NetOutgoingMessage om, IPEndPoint endPoint) + { + if (m_Client == null) return; + + m_Client?.SendUnconnectedMessage(om, endPoint); + } + + public NetOutgoingMessage CreateMessage() + { + return m_Client?.CreateMessage(); + } + } +} \ No newline at end of file diff --git a/SRMP/Networking/NetworkClientUI.cs b/SRMP/Networking/NetworkClientUI.cs new file mode 100644 index 0000000..bbfb843 --- /dev/null +++ b/SRMP/Networking/NetworkClientUI.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using UnityEngine; +using UnityEngine.UI; + +namespace SRMultiplayer.Networking +{ + public class NetworkClientUI : MonoBehaviour + { + public List Panels = new List(); + + public GameObject UsernamePanel; + public InputField UsernameInput; + + public GameObject ErrorPanel; + public Text ErrorText; + + private void Awake() + { + UsernamePanel = transform.Find("Background/UsernamePanel").gameObject; + UsernameInput = UsernamePanel.transform.Find("UsernameInput").GetComponent(); + UsernamePanel.transform.Find("ConfirmButton").GetComponent