├── .editorconfig
├── .gitignore
├── Directory.Build.props
├── Directory.Build.targets
├── LICENSE
├── OniMod.sln
├── README.md
├── docs
└── CONTRIBUTING.md
├── scripts
├── install.bat
└── install.sh
├── src
├── MultiplayerMod.Test
│ ├── Compatibility.cs
│ ├── Core
│ │ ├── Collections
│ │ │ ├── BidirectionalMapTests.cs
│ │ │ ├── IndexedObjectExtensionMapTests.cs
│ │ │ └── LinkedHashSetTests.cs
│ │ ├── Dependency
│ │ │ ├── DependencyContainerBuilderTests.cs
│ │ │ └── DependencyContainerTests.cs
│ │ ├── Events
│ │ │ ├── EventDispatcherTests.cs
│ │ │ └── TypedEventDispatcherTests.cs
│ │ ├── Extensions
│ │ │ ├── LinkedListExtensionTests.cs
│ │ │ ├── MethodSignatureExtensionTests.cs
│ │ │ └── TypeSignatureExtensionTests.cs
│ │ ├── Patch
│ │ │ ├── Compatibility
│ │ │ │ ├── PatchesCompatibility.cs
│ │ │ │ └── PatchesCompatibilityMetadata.cs
│ │ │ ├── ControlFlowCustomizerTests.cs
│ │ │ ├── Evaluators
│ │ │ │ └── MethodBoundedDetourTests.cs
│ │ │ └── Generics
│ │ │ │ └── HarmonyGenericsRouterTests.cs
│ │ └── Paths
│ │ │ └── SecurePathTests.cs
│ ├── Directory.Build.targets
│ ├── Environment
│ │ ├── EventRaiserExtension.cs
│ │ ├── Network
│ │ │ ├── CommandTools.cs
│ │ │ ├── TestMultiplayerClient.cs
│ │ │ ├── TestMultiplayerClientId.cs
│ │ │ ├── TestMultiplayerEndpoint.cs
│ │ │ └── TestMultiplayerServer.cs
│ │ ├── Patches
│ │ │ └── PatchesSetup.cs
│ │ ├── TestRuntime.cs
│ │ └── Unity
│ │ │ ├── Patches
│ │ │ ├── LoadingOverlayPatch.cs
│ │ │ ├── SpeedControlScreenPatch.cs
│ │ │ └── Unity
│ │ │ │ ├── ApplicationPatch.cs
│ │ │ │ ├── BehaviourPatch.cs
│ │ │ │ ├── ComponentPatch.cs
│ │ │ │ ├── DebugLogHandlerPatch.cs
│ │ │ │ ├── GameObjectPatch.cs
│ │ │ │ ├── LayerMaskPatch.cs
│ │ │ │ ├── MaterialPatch.cs
│ │ │ │ ├── MeshPatch.cs
│ │ │ │ ├── ObjectPatch.cs
│ │ │ │ ├── RandomPatch.cs
│ │ │ │ ├── RendererPatch.cs
│ │ │ │ ├── ResourcesAPIInternalPatch.cs
│ │ │ │ ├── ScriptableObjectPatch.cs
│ │ │ │ ├── ShaderPatch.cs
│ │ │ │ ├── SystemInfoPatch.cs
│ │ │ │ ├── TextAssetPatch.cs
│ │ │ │ ├── TimePatch.cs
│ │ │ │ └── TransformPatch.cs
│ │ │ ├── UnityEvent.cs
│ │ │ ├── UnityPlayerObjectManager.cs
│ │ │ ├── UnityTestRuntime.cs
│ │ │ └── UnityTestRuntimeTest.cs
│ ├── Game
│ │ └── Chores
│ │ │ ├── AbstractChoreTest.cs
│ │ │ ├── ChoreListTest.cs
│ │ │ └── States
│ │ │ └── ChoreStateEventsTest.cs
│ ├── GameRuntime
│ │ ├── Patches
│ │ │ ├── AssetsPatch.cs
│ │ │ ├── DbPatch.cs
│ │ │ └── ElementLoaderPatch.cs
│ │ └── PlayableGameTest.cs
│ ├── ModRuntime
│ │ ├── ExecutionContextManagerTests.cs
│ │ └── ExecutionLevelTests.cs
│ ├── Multiplayer
│ │ ├── Chores
│ │ │ ├── ChoreTest.cs
│ │ │ ├── ChoresInstantiationTests.cs
│ │ │ ├── ChoresMultiplayerConfigurationTest.cs
│ │ │ └── CreateChoreTest.cs
│ │ ├── Commands
│ │ │ └── Chores
│ │ │ │ └── States
│ │ │ │ ├── AllowStateTransitionTest.cs
│ │ │ │ ├── SetStateArgumentsTest.cs
│ │ │ │ ├── StartMoveTest.cs
│ │ │ │ ├── TransitToStateTest.cs
│ │ │ │ └── UpdateMoveTest.cs
│ │ ├── MultiplayerConnectionTests.cs
│ │ ├── MultiplayerStatusOverlayPatch.cs
│ │ ├── MultiplayerTools.cs
│ │ ├── Patches
│ │ │ └── Chores
│ │ │ │ └── States
│ │ │ │ └── AdjustStateMachineTransitionsTest.cs
│ │ ├── SpeedControlScreenContext.cs
│ │ ├── StateMachines
│ │ │ ├── RuntimeTools
│ │ │ │ ├── StateMachineContextRuntimeToolsTests.cs
│ │ │ │ └── StateMachineRuntimeToolsTests.cs
│ │ │ ├── StateMachineDslTests.cs
│ │ │ └── StateMachinesTest.cs
│ │ ├── States
│ │ │ ├── StatesManagerTest.cs
│ │ │ └── WaitHostStateTest.cs
│ │ ├── TestRuntimeExtensions.cs
│ │ └── WorldManagerPatch.cs
│ ├── MultiplayerMod.Test.csproj
│ └── Network
│ │ └── CommandTests.cs
└── MultiplayerMod
│ ├── Compatibility.cs
│ ├── Core
│ ├── Collections
│ │ ├── BidirectionalMap.cs
│ │ ├── BoxedValue.cs
│ │ ├── Deconstructable.cs
│ │ ├── IdentityEqualityComparer.cs
│ │ ├── IndexedObjectExtensionMap.cs
│ │ ├── LinkedHashSet.cs
│ │ └── SingletonCollection.cs
│ ├── Dependency
│ │ ├── AmbiguousDependencyException.cs
│ │ ├── DependencyAlreadyRegisteredException.cs
│ │ ├── DependencyAttribute.cs
│ │ ├── DependencyContainer.cs
│ │ ├── DependencyContainerBuilder.cs
│ │ ├── DependencyInfo.cs
│ │ ├── DependencyInstantiationException.cs
│ │ ├── DependencyIsInstantiatingException.cs
│ │ ├── IDependencyContainer.cs
│ │ ├── IDependencyInjector.cs
│ │ ├── InjectDependencyAttribute.cs
│ │ ├── InvalidDependencyException.cs
│ │ └── MissingDependencyException.cs
│ ├── Events
│ │ ├── EventDispatcher.cs
│ │ ├── EventSubscription.cs
│ │ ├── EventSubscriptions.cs
│ │ └── IDispatchableEvent.cs
│ ├── Extensions
│ │ ├── ArraysExtensions.cs
│ │ ├── DictionaryExtensions.cs
│ │ ├── EnumerableExtensions.cs
│ │ ├── HashAlgorithmExtensions.cs
│ │ ├── LinkedListExtensions.cs
│ │ ├── MethodSignatureExtension.cs
│ │ ├── NumberExtensions.cs
│ │ ├── SetExtensions.cs
│ │ ├── TypeExtensions.cs
│ │ └── TypeSignatureExtension.cs
│ ├── Logging
│ │ ├── LogLevel.cs
│ │ ├── Logger.cs
│ │ ├── LoggerFactory.cs
│ │ └── TcpLogger.cs
│ ├── Patch
│ │ ├── Capture
│ │ │ ├── CaptureUnavailableException.cs
│ │ │ ├── ILocalCapture.cs
│ │ │ ├── InvalidLocalVariableReferenceException.cs
│ │ │ ├── LocalCaptor.cs
│ │ │ ├── LocalCaptureContainer.cs
│ │ │ ├── LocalVariableAttribute.cs
│ │ │ ├── LocalVariableCaptureAttribute.cs
│ │ │ └── LocalVariableCaptureSupport.cs
│ │ ├── CodeInstructionExtensions.cs
│ │ ├── CodeInstructionsOffsetAccessor.cs
│ │ ├── ControlFlow
│ │ │ ├── ControlFlowContext.cs
│ │ │ ├── ControlFlowCustomizer.cs
│ │ │ ├── Evaluators
│ │ │ │ ├── CompositeDetour.cs
│ │ │ │ └── MethodBoundedDetour.cs
│ │ │ ├── ExecutionFlow.cs
│ │ │ └── IDetourEvaluator.cs
│ │ ├── Generics
│ │ │ ├── HarmonyGenericsRouter.cs
│ │ │ └── HarmonyGenericsRouterException.cs
│ │ ├── HarmonyManualAttribute.cs
│ │ ├── HarmonyOptionalAttribute.cs
│ │ ├── PatchTargetResolver.cs
│ │ └── StackTraceExtensions.cs
│ ├── Paths
│ │ ├── AccessDeniedException.cs
│ │ └── SecurePath.cs
│ ├── Reflection
│ │ └── ReflectionExtension.cs
│ ├── Scheduling
│ │ ├── UnityTaskExecutor.cs
│ │ ├── UnityTaskSchedulerConfigurer.cs
│ │ └── UntiyTaskScheduler.cs
│ └── Unity
│ │ ├── MultiplayerKMonoBehaviour.cs
│ │ ├── MultiplayerMonoBehaviour.cs
│ │ ├── UnityObject.cs
│ │ └── UnityPlayerObject.cs
│ ├── Directory.Build.targets
│ ├── Exceptions
│ └── MultiplayerException.cs
│ ├── Game
│ ├── Chores
│ │ ├── ChoreDriverEvents.cs
│ │ ├── ChoreList.cs
│ │ ├── States
│ │ │ ├── ChoreStateEvents.cs
│ │ │ ├── ChoreTransitStateArgs.cs
│ │ │ └── MoveToArgs.cs
│ │ └── Types
│ │ │ ├── ChoreSyncConfig.cs
│ │ │ ├── CreationStatusEnum.cs
│ │ │ ├── GlobalChoreProviderStatusEnum.cs
│ │ │ ├── StateTransitionConfig.cs
│ │ │ ├── StatesTransitionConfig.cs
│ │ │ ├── StatesTransitionStatus.cs
│ │ │ └── TransitionTypeEnum.cs
│ ├── Configuration
│ │ └── MethodPatchesDisabler.cs
│ ├── Context
│ │ ├── GameContext.cs
│ │ ├── GameContextComposite.cs
│ │ ├── GameStaticContext.cs
│ │ └── IGameContext.cs
│ ├── Debug
│ │ └── GameDebugEvents.cs
│ ├── Dev
│ │ └── DevToolSceneInspectorPatch.cs
│ ├── Effects
│ │ ├── DisablePopUpEffects.cs
│ │ └── DisablePriorityConfirmSound.cs
│ ├── Extension
│ │ ├── GameExtensionsConfigurator.cs
│ │ └── GameObjectExtension.cs
│ ├── GameEvents.cs
│ ├── GameState.cs
│ ├── KEventSystemExtensions.cs
│ ├── Location
│ │ └── GridObjectLayerIndexerPatch.cs
│ ├── MainMenuExtensions.cs
│ ├── Mechanics
│ │ ├── Minions
│ │ │ └── MinionIdentityExtensions.cs
│ │ ├── Objects
│ │ │ ├── ComponentEventsArgs.cs
│ │ │ ├── ObjectEvents.cs
│ │ │ └── StateMachineEventsArgs.cs
│ │ ├── Printing
│ │ │ ├── TelepadAcceptDeliveryCapture.cs
│ │ │ └── TelepadEvents.cs
│ │ └── Schedules
│ │ │ └── ScheduleConditionalUpdatePatch.cs
│ ├── NameOf
│ │ └── StateMachineMemberReference.cs
│ ├── UI
│ │ ├── GameSpeedControlEvents.cs
│ │ ├── MainMenuEvents.cs
│ │ ├── Overlay
│ │ │ └── DisinfectOverlayEvents.cs
│ │ ├── Screens
│ │ │ ├── Events
│ │ │ │ ├── ConsumableScreenEvents.cs
│ │ │ │ ├── ImmigrantScreenEvents.cs
│ │ │ │ ├── MeterScreenEvents.cs
│ │ │ │ ├── PauseScreenEvents.cs
│ │ │ │ ├── PrioritiesScreenEvents.cs
│ │ │ │ ├── ResearchScreenEvents.cs
│ │ │ │ ├── ScheduleScreenEvents.cs
│ │ │ │ ├── SkillScreenEvents.cs
│ │ │ │ ├── UserMenuButtonEvents.cs
│ │ │ │ └── UserMenuScreenEvents.cs
│ │ │ ├── ImmigrantScreenPatch.cs
│ │ │ └── ScreensUtils.cs
│ │ ├── SideScreens
│ │ │ ├── AlarmSideScreenEvents.cs
│ │ │ ├── CounterSideScreenEvents.cs
│ │ │ ├── CritterSensorSideScreenEvents.cs
│ │ │ ├── RailGunSideScreenEvents.cs
│ │ │ ├── TemperatureSwitchSideScreenEvents.cs
│ │ │ ├── TimeRangeSideScreenEvents.cs
│ │ │ └── TimerSideScreenEvents.cs
│ │ └── Tools
│ │ │ ├── Context
│ │ │ ├── DebugToolContext.cs
│ │ │ ├── DisableBuildingValidation.cs
│ │ │ ├── PrioritySettingsContext.cs
│ │ │ └── StampCompletionOverride.cs
│ │ │ └── Events
│ │ │ ├── BuildEventArgs.cs
│ │ │ ├── BuildEvents.cs
│ │ │ ├── CopySettingsEventArgs.cs
│ │ │ ├── CopySettingsEvents.cs
│ │ │ ├── DebugToolEvents.cs
│ │ │ ├── DragCompleteEventArgs.cs
│ │ │ ├── DragToolEvents.cs
│ │ │ ├── InterfaceToolEvents.cs
│ │ │ ├── ModifyEventArgs.cs
│ │ │ ├── MoveToLocationToolEvents.cs
│ │ │ ├── StampEventArgs.cs
│ │ │ ├── StampToolEvents.cs
│ │ │ ├── UtilityBuildEventArgs.cs
│ │ │ └── UtilityBuildEvents.cs
│ └── World
│ │ └── SaveLoaderEvents.cs
│ ├── ModRuntime
│ ├── Context
│ │ ├── ExecutionContext.cs
│ │ ├── ExecutionContextIntegrityFailureException.cs
│ │ ├── ExecutionContextManager.cs
│ │ ├── ExecutionContextStack.cs
│ │ ├── ExecutionLevel.cs
│ │ ├── ExecutionLevelManager.cs
│ │ ├── RequireDebugBuildAttribute.cs
│ │ └── RequireExecutionLevelAttribute.cs
│ ├── DependenciesStaticTargetAttribute.cs
│ ├── EventDispatcherMonitor.cs
│ ├── Loader
│ │ ├── DelayedModLoader.cs
│ │ ├── IModComponentConfigurer.cs
│ │ ├── LaunchInitializerPatch.cs
│ │ ├── ModComponentOrder.cs
│ │ └── ModLoader.cs
│ ├── Runtime.cs
│ ├── RuntimeReadyEvent.cs
│ └── StaticCompatibility
│ │ ├── Dependencies.cs
│ │ └── Execution.cs
│ ├── Multiplayer
│ ├── Chores
│ │ ├── ChoreConfiguration.cs
│ │ ├── ChoresMultiplayerConfiguration.cs
│ │ ├── ChoresPatcher.cs
│ │ ├── Commands
│ │ │ ├── CreateChore.cs
│ │ │ └── States
│ │ │ │ ├── AllowStateTransition.cs
│ │ │ │ ├── SetStateArguments.cs
│ │ │ │ ├── StartMove.cs
│ │ │ │ ├── TransitToState.cs
│ │ │ │ └── UpdateMove.cs
│ │ ├── Driver
│ │ │ ├── ChoreDriverSynchronization.cs
│ │ │ ├── Commands
│ │ │ │ ├── ReleaseChoreDriver.cs
│ │ │ │ └── SetDriverChore.cs
│ │ │ └── MultiplayerDriverChores.cs
│ │ ├── Dsl
│ │ │ └── ChoresConfigurationDsl.cs
│ │ ├── Events
│ │ │ ├── ChoreCleanupEvent.cs
│ │ │ └── ChoreCreatedEvent.cs
│ │ ├── Patches
│ │ │ └── States
│ │ │ │ └── AdjustStateMachineTransitions.cs
│ │ ├── RuntimeChoresStateManager.cs
│ │ ├── Serialization
│ │ │ └── ChoreArgumentsWrapper.cs
│ │ └── Synchronizers
│ │ │ ├── DeathMonitorSynchronizer.cs
│ │ │ ├── IdleChoreSynchronizer.cs
│ │ │ ├── IdleStatesSynchronizer.cs
│ │ │ └── MoveToSafetyChoreSynchronizer.cs
│ ├── Commands
│ │ ├── Alerts
│ │ │ └── ChangeRedAlertState.cs
│ │ ├── ArgumentUtils.cs
│ │ ├── Debug
│ │ │ ├── DebugGameFrameStep.cs
│ │ │ ├── DebugSimulationStep.cs
│ │ │ └── SyncWorldDebugSnapshot.cs
│ │ ├── Gameplay
│ │ │ ├── AcceptDelivery.cs
│ │ │ ├── CallMethod.cs
│ │ │ ├── ChangePriority.cs
│ │ │ └── RejectDelivery.cs
│ │ ├── IMultiplayerCommand.cs
│ │ ├── MultiplayerCommand.cs
│ │ ├── MultiplayerCommandAttribute.cs
│ │ ├── MultiplayerCommandContext.cs
│ │ ├── MultiplayerCommandRuntimeAccessor.cs
│ │ ├── MultiplayerCommandType.cs
│ │ ├── MultiplayerCommandsConfigurer.cs
│ │ ├── Objects
│ │ │ └── SynchronizeObjectPosition.cs
│ │ ├── Overlay
│ │ │ └── SetDisinfectSettings.cs
│ │ ├── Player
│ │ │ └── UpdatePlayerCursorPosition.cs
│ │ ├── Registry
│ │ │ ├── MultiplayerCommandAlreadyRegisteredException.cs
│ │ │ ├── MultiplayerCommandConfiguration.cs
│ │ │ ├── MultiplayerCommandInvalidInterfaceException.cs
│ │ │ ├── MultiplayerCommandNotFoundException.cs
│ │ │ └── MultiplayerCommandRegistry.cs
│ │ ├── Screens
│ │ │ ├── Consumable
│ │ │ │ ├── PermitConsumableByDefault.cs
│ │ │ │ └── PermitConsumableToMinion.cs
│ │ │ ├── Immigration
│ │ │ │ └── InitializeImmigration.cs
│ │ │ ├── Priorities
│ │ │ │ ├── SetDefaultPriority.cs
│ │ │ │ ├── SetPersonalPrioritiesAdvanced.cs
│ │ │ │ └── SetPersonalPriority.cs
│ │ │ ├── Research
│ │ │ │ ├── AbstractResearchEntryCommand.cs
│ │ │ │ ├── CancelResearch.cs
│ │ │ │ └── SelectResearch.cs
│ │ │ ├── Schedule
│ │ │ │ └── ChangeSchedulesList.cs
│ │ │ ├── SideScreen
│ │ │ │ ├── UpdateAlarm.cs
│ │ │ │ ├── UpdateCritterSensor.cs
│ │ │ │ ├── UpdateLogicCounter.cs
│ │ │ │ ├── UpdateLogicTimeOfDaySensor.cs
│ │ │ │ ├── UpdateLogicTimeSensor.cs
│ │ │ │ ├── UpdateRailGunCapacity.cs
│ │ │ │ └── UpdateTemperatureSwitch.cs
│ │ │ ├── Skill
│ │ │ │ ├── MasterSkill.cs
│ │ │ │ └── SetHat.cs
│ │ │ └── UserMenu
│ │ │ │ └── ClickUserMenuButton.cs
│ │ ├── Speed
│ │ │ ├── ChangeGameSpeed.cs
│ │ │ ├── PauseGame.cs
│ │ │ └── ResumeGame.cs
│ │ └── Tools
│ │ │ ├── AbstractBuildUtilityCommand.cs
│ │ │ ├── AbstractDragToolCommand.cs
│ │ │ ├── Attack.cs
│ │ │ ├── Build.cs
│ │ │ ├── BuildUtility.cs
│ │ │ ├── BuildWire.cs
│ │ │ ├── Cancel.cs
│ │ │ ├── CopySettings.cs
│ │ │ ├── Deconstruct.cs
│ │ │ ├── Dig.cs
│ │ │ ├── Disconnect.cs
│ │ │ ├── Disinfect.cs
│ │ │ ├── EmptyPipe.cs
│ │ │ ├── Harvest.cs
│ │ │ ├── Modify.cs
│ │ │ ├── Mop.cs
│ │ │ ├── MoveToLocation.cs
│ │ │ ├── Prioritize.cs
│ │ │ ├── Stamp.cs
│ │ │ ├── Sweep.cs
│ │ │ └── Wrangle.cs
│ ├── Components
│ │ ├── AssignedMultiplayerPlayer.cs
│ │ ├── CursorComponent.cs
│ │ ├── CursorManager.cs
│ │ ├── DestroyOnPlayerLeave.cs
│ │ └── MultiplayerPlayerNotifier.cs
│ ├── CoreOperations
│ │ ├── Binders
│ │ │ ├── GameEventsBinder.cs
│ │ │ └── HostEventsBinder.cs
│ │ ├── CommandExecution
│ │ │ ├── CommandConfigurationException.cs
│ │ │ ├── CommandExceptionHandler.cs
│ │ │ └── MultiplayerCommandExecutor.cs
│ │ ├── Events
│ │ │ ├── ConnectionLostEvent.cs
│ │ │ ├── GameQuitEvent.cs
│ │ │ ├── GameReadyEvent.cs
│ │ │ ├── GameStartedEvent.cs
│ │ │ ├── MultiplayerModeSelectedEvent.cs
│ │ │ ├── SinglePlayerModeSelectedEvent.cs
│ │ │ ├── StopMultiplayerEvent.cs
│ │ │ ├── WorldLoadingEvent.cs
│ │ │ ├── WorldSavedEvent.cs
│ │ │ └── WorldStateInitializingEvent.cs
│ │ ├── ExecutionLevelController.cs
│ │ ├── GameStateEventsRelay.cs
│ │ ├── MultiplayerCommandController.cs
│ │ ├── MultiplayerGameObjectsSpawner.cs
│ │ ├── MultiplayerJoinRequestController.cs
│ │ ├── MultiplayerServerController.cs
│ │ ├── PlayersManagement
│ │ │ ├── ClientInitializationRequestEvent.cs
│ │ │ ├── Commands
│ │ │ │ ├── AddPlayerCommand.cs
│ │ │ │ ├── ChangePlayerStateCommand.cs
│ │ │ │ ├── InitializeClientCommand.cs
│ │ │ │ ├── RemovePlayerCommand.cs
│ │ │ │ ├── RequestPlayerStateChangeCommand.cs
│ │ │ │ ├── RequestWorldSyncCommand.cs
│ │ │ │ └── SyncPlayersCommand.cs
│ │ │ ├── CurrentPlayerInitializedEvent.cs
│ │ │ ├── PlayersManagementController.cs
│ │ │ ├── PlayersManagementException.cs
│ │ │ └── WorldSyncRequestedEvent.cs
│ │ └── RequireMultiplayerModeAttribute.cs
│ ├── IMultiplayerOperations.cs
│ ├── MultiplayerGame.cs
│ ├── MultiplayerMode.cs
│ ├── Objects
│ │ ├── Extensions
│ │ │ ├── ChoreExtensions.cs
│ │ │ ├── ComponentReferenceExtensions.cs
│ │ │ ├── GameObjectExtensions.cs
│ │ │ └── StateMachineReferenceExtensions.cs
│ │ ├── InternalMultiplayerIdType.cs
│ │ ├── MultiplayerId.cs
│ │ ├── MultiplayerIdType.cs
│ │ ├── MultiplayerInstance.cs
│ │ ├── MultiplayerObject.cs
│ │ ├── MultiplayerObjects.cs
│ │ ├── MultiplayerObjectsDebugHelper.cs
│ │ ├── MultiplayerObjectsIndex.cs
│ │ ├── Reference
│ │ │ ├── ChoreReference.cs
│ │ │ ├── ComponentReference.cs
│ │ │ ├── GameObjectReference.cs
│ │ │ ├── GridReference.cs
│ │ │ ├── MultiplayerIdReference.cs
│ │ │ ├── ObjectNotFoundException.cs
│ │ │ ├── Reference.cs
│ │ │ └── StateMachineReference.cs
│ │ ├── SaveGameObjectsInitializer.cs
│ │ └── Support
│ │ │ └── MinionStorageRedirectionSupport.cs
│ ├── Patches
│ │ ├── KModalButtonMenuPatch.cs
│ │ └── MainMenuPatch.cs
│ ├── Players
│ │ ├── Events
│ │ │ ├── MultiplayerJoinRequestedEvent.cs
│ │ │ ├── PlayerCursorPositionUpdatedEvent.cs
│ │ │ ├── PlayerJoinedEvent.cs
│ │ │ ├── PlayerLeftEvent.cs
│ │ │ ├── PlayerStateChangedEvent.cs
│ │ │ ├── PlayersReadyEvent.cs
│ │ │ └── PlayersUpdatedEvent.cs
│ │ ├── IPlayerProfileProvider.cs
│ │ ├── MultiplayerPlayer.cs
│ │ ├── MultiplayerPlayers.cs
│ │ ├── PlayerIdentity.cs
│ │ ├── PlayerProfile.cs
│ │ ├── PlayerRole.cs
│ │ └── PlayerState.cs
│ ├── StateMachines
│ │ ├── Commands
│ │ │ ├── GoToState.cs
│ │ │ ├── MoveObjectToCell.cs
│ │ │ └── SetParameterValue.cs
│ │ ├── Configuration
│ │ │ ├── Configurers
│ │ │ │ ├── InvalidStateExpressionException.cs
│ │ │ │ ├── StateMachineBaseConfigurer.cs
│ │ │ │ ├── StateMachineConfigurerDelegate.cs
│ │ │ │ ├── StateMachinePostConfigurer.cs
│ │ │ │ ├── StateMachinePreConfigurer.cs
│ │ │ │ └── StateMachineRootConfigurer.cs
│ │ │ ├── InvalidConfigurationPhaseException.cs
│ │ │ ├── InvalidStateExpressionException.cs
│ │ │ ├── Parameters
│ │ │ │ ├── StateMachineMultiplayerParameter.cs
│ │ │ │ └── StateMachineMultiplayerParameterInfo.cs
│ │ │ ├── StateMachineBoundedConfigurer.cs
│ │ │ ├── StateMachineConfiguration.cs
│ │ │ ├── StateMachineConfigurationAction.cs
│ │ │ ├── StateMachineConfigurationContext.cs
│ │ │ ├── StateMachineConfigurationException.cs
│ │ │ ├── StateMachineConfigurationPhase.cs
│ │ │ ├── StateMachineConfigurer.cs
│ │ │ ├── StateMachineConfigurerDsl.cs
│ │ │ └── States
│ │ │ │ └── StateMachineMultiplayerStateInfo.cs
│ │ ├── RuntimeTools
│ │ │ ├── StateMachineContextRuntimeTools.cs
│ │ │ ├── StateMachineRuntimeParameter.cs
│ │ │ ├── StateMachineRuntimeTools.cs
│ │ │ └── StateMachineStateNotFoundException.cs
│ │ └── StateMachinesPatcher.cs
│ ├── States
│ │ ├── ContinuationState.cs
│ │ ├── IWaitHostState.cs
│ │ ├── StatesManager.cs
│ │ └── WaitHostState.cs
│ ├── Tools
│ │ └── CommandRateThrottle.cs
│ ├── UI
│ │ ├── Dev
│ │ │ ├── DevToolMultiplayerObjects.cs
│ │ │ └── DevToolsConfigurer.cs
│ │ ├── Diagnostics
│ │ │ ├── ColonyDiagnosticScreenPatch.cs
│ │ │ ├── ColonyDiagnosticUtilityPatch.cs
│ │ │ └── MultiplayerColonyDiagnostic.cs
│ │ ├── Notifications.cs
│ │ ├── Overlays
│ │ │ └── MultiplayerStatusOverlay.cs
│ │ └── WorldTrackers
│ │ │ ├── MultiplayerErrorsTracker.cs
│ │ │ └── TrackerToolPatch.cs
│ └── World
│ │ ├── Commands
│ │ ├── LoadWorld.cs
│ │ └── NotifyWorldSavePreparing.cs
│ │ ├── Data
│ │ ├── WorldSave.cs
│ │ └── WorldState.cs
│ │ ├── Debug
│ │ ├── DebugSnapshotAvailableEvent.cs
│ │ ├── WorldDebugSnapshot.cs
│ │ ├── WorldDebugSnapshotComparator.cs
│ │ └── WorldDebugSnapshotRunner.cs
│ │ ├── IWorldStateManager.cs
│ │ ├── WorldManager.cs
│ │ └── WorldSyncEvent.cs
│ ├── MultiplayerMod.csproj
│ ├── Network
│ ├── IMultiplayerClient.cs
│ ├── IMultiplayerClientId.cs
│ ├── IMultiplayerEndpoint.cs
│ ├── IMultiplayerServer.cs
│ ├── MultiplayerClientState.cs
│ ├── MultiplayerCommandOptions.cs
│ └── MultiplayerServerState.cs
│ ├── Platform
│ ├── PlatformException.cs
│ └── Steam
│ │ ├── Network
│ │ ├── Components
│ │ │ ├── LobbyJoinRequestComponent.cs
│ │ │ ├── SteamClientComponent.cs
│ │ │ └── SteamServerComponent.cs
│ │ ├── Configuration.cs
│ │ ├── Extensions.cs
│ │ ├── Messaging
│ │ │ ├── INetworkMessage.cs
│ │ │ ├── INetworkMessageHandle.cs
│ │ │ ├── NetworkMessage.cs
│ │ │ ├── NetworkMessageFactory.cs
│ │ │ ├── NetworkMessageFragment.cs
│ │ │ ├── NetworkMessageFragmentsHeader.cs
│ │ │ ├── NetworkMessageHandle.cs
│ │ │ ├── NetworkMessageProcessor.cs
│ │ │ ├── NetworkSerializer.cs
│ │ │ ├── SerializedNetworkMessage.cs
│ │ │ └── Surrogates
│ │ │ │ ├── AssignmentGroupSurrogate.cs
│ │ │ │ ├── CarePackageInstanceDataSurrogate.cs
│ │ │ │ ├── ChoreTypeSurrogate.cs
│ │ │ │ ├── ComplexRecipeSurrogate.cs
│ │ │ │ ├── DeathSurrogate.cs
│ │ │ │ ├── EmoteSurrogate.cs
│ │ │ │ ├── ISurrogateType.cs
│ │ │ │ ├── KAnimFileSurrogate.cs
│ │ │ │ ├── MinionStartingStatsSurrogate.cs
│ │ │ │ ├── PathNodeSurrogate.cs
│ │ │ │ ├── PrioritySettingSurrogate.cs
│ │ │ │ ├── RoomSurrogate.cs
│ │ │ │ ├── ScheduleBlockTypeSurrogate.cs
│ │ │ │ ├── SerializationSurrogates.cs
│ │ │ │ ├── SpaceDestinationSurrogate.cs
│ │ │ │ ├── SpiceGrinderSurrogate.cs
│ │ │ │ ├── TagSurrogate.cs
│ │ │ │ ├── Vector2SerializationSurrogate.cs
│ │ │ │ ├── Vector2fSerializationSurrogate.cs
│ │ │ │ └── Vector3SerializationSurrogate.cs
│ │ ├── NetworkPlatformException.cs
│ │ ├── SteamClient.cs
│ │ ├── SteamLobby.cs
│ │ ├── SteamMultiplayerClientId.cs
│ │ ├── SteamServer.cs
│ │ └── SteamServerEndpoint.cs
│ │ ├── SteamMultiplayerOperations.cs
│ │ ├── SteamPlatformConfigurer.cs
│ │ └── SteamPlayerProfileProvider.cs
│ └── mod.yaml
└── steamworkshop
├── STEAMWORSHOP_DESCRIPTION.md
├── loading.png
├── menu.png
└── preview.png
/.gitignore:
--------------------------------------------------------------------------------
1 | *.user
2 | /.idea
3 | /packages
4 | src/Assembly-CSharp-firstpass
5 | src/Assembly-CSharp
6 | src/*/bin/
7 | src/*/obj/
8 | /lib
9 |
--------------------------------------------------------------------------------
/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | net48
6 | 12
7 | true
8 |
9 |
10 |
11 |
12 | C:\Program Files (x86)\Steam
13 |
14 |
15 |
16 | $(MSBuildThisFileDirectory)\lib\exposed
17 | $(SteamLibraryPath)\steamapps\common\OxygenNotIncluded\OxygenNotIncluded_Data\Managed
18 | $(UserProfile)\Documents\Klei\OxygenNotIncluded\mods\dev\$(MSBuildProjectName)
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Zuev Vladimir, Denis Pakhorukov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/scripts/install.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | setlocal enabledelayedexpansion
3 |
4 | for /F "tokens=2* skip=2" %%a in ('reg query "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" /v "personal"') do set DocumentsFolder=%%b
5 |
6 | call set "DocumentsFolder=!DocumentsFolder!"
7 | set InstallationPath=%DocumentsFolder%\Klei\OxygenNotIncluded\mods\Local\MultiplayerMod\
8 |
9 | echo "Installing multiplayer mod in folder: %InstallationPath%"
10 | xcopy /s .\MultiplayerMod "%InstallationPath%"
11 | pause
12 |
--------------------------------------------------------------------------------
/scripts/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd "$(dirname "$0")"
3 |
4 | if [[ "$OSTYPE" == "darwin"* ]]; then
5 | # macOS
6 | MOD_PATH=~/Library/Application\ Support/unity.Klei.Oxygen\ Not\ Included/mods/Local/MultiplayerMod
7 | else
8 | # Linux
9 | MOD_PATH=~/.config/unity3d/Klei/Oxygen\ Not\ Included/mods/Local/MultiplayerMod
10 | fi
11 |
12 | mkdir -p "$MOD_PATH"
13 | cp -r ./MultiplayerMod/. "$MOD_PATH"
14 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Compatibility.cs:
--------------------------------------------------------------------------------
1 | // Record type support for .NET < 5
2 |
3 | // ReSharper disable once CheckNamespace
4 | // ReSharper disable once ArrangeNamespaceBody
5 |
6 | namespace System.Runtime.CompilerServices;
7 |
8 | // ReSharper disable once UnusedType.Global
9 | internal static class IsExternalInit { }
10 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Core/Collections/LinkedHashSetTests.cs:
--------------------------------------------------------------------------------
1 | using MultiplayerMod.Core.Collections;
2 | using NUnit.Framework;
3 |
4 | namespace MultiplayerMod.Test.Core.Collections;
5 |
6 | [TestFixture]
7 | [Parallelizable]
8 | public class LinkedHashSetTests {
9 |
10 | [Test]
11 | public void ItemRemovedCorrectly() {
12 | var set = new LinkedHashSet { "First", "Second" };
13 | Assert.IsTrue(set.Remove("Second"));
14 | Assert.IsFalse(set.Remove("Second"));
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Core/Paths/SecurePathTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using MultiplayerMod.Core.Paths;
3 | using NUnit.Framework;
4 |
5 | namespace MultiplayerMod.Test.Core.Paths;
6 |
7 | [TestFixture]
8 | public class SecurePathTests {
9 |
10 | [Test]
11 | public void RootIsAccessible() {
12 | Assert.DoesNotThrow(() => SecurePath.Combine("root"));
13 | }
14 |
15 | [Test]
16 | public void PathsUnderRootAreAccessible() {
17 | Assert.DoesNotThrow(() => SecurePath.Combine("root", "foo.txt"));
18 | Assert.DoesNotThrow(() => SecurePath.Combine("root", ".", "foo.txt"));
19 | Assert.DoesNotThrow(() => SecurePath.Combine("root", "dir", "foo.txt"));
20 | }
21 |
22 | [Test]
23 | public void AccessDeniedForPathsOutsideRoot() {
24 | Assert.Throws(() => SecurePath.Combine("root", "..", "foo.txt"));
25 | }
26 |
27 | [Test]
28 | public void PathsAreSeparated() {
29 | Assert.IsTrue(SecurePath.Combine("root", "a").EndsWith($"{Path.DirectorySeparatorChar}a"));
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Network/CommandTools.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Runtime.Serialization.Formatters.Binary;
3 | using MultiplayerMod.Multiplayer.Commands;
4 | using MultiplayerMod.Platform.Steam.Network.Messaging.Surrogates;
5 |
6 | namespace MultiplayerMod.Test.Environment.Network;
7 |
8 | public static class CommandTools {
9 |
10 | public static IMultiplayerCommand Copy(IMultiplayerCommand command) {
11 | using var memory = new MemoryStream();
12 | var formatter = new BinaryFormatter { SurrogateSelector = SerializationSurrogates.Selector };
13 | formatter.Serialize(memory, command);
14 | memory.Position = 0;
15 | return (IMultiplayerCommand) formatter.Deserialize(memory);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Network/TestMultiplayerClientId.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MultiplayerMod.Network;
3 |
4 | namespace MultiplayerMod.Test.Environment.Network;
5 |
6 | public class TestMultiplayerClientId : IMultiplayerClientId {
7 |
8 | public TestMultiplayerClient Client { get; set; } = null!;
9 |
10 | private readonly int id;
11 |
12 | public TestMultiplayerClientId(int id) {
13 | this.id = id;
14 | }
15 |
16 | bool IEquatable.Equals(IMultiplayerClientId other) {
17 | return other is TestMultiplayerClientId player && player.Equals(this);
18 | }
19 |
20 | protected bool Equals(TestMultiplayerClientId other) => id == other.id;
21 |
22 | public override bool Equals(object? obj) {
23 | if (ReferenceEquals(null, obj))
24 | return false;
25 | if (ReferenceEquals(this, obj))
26 | return true;
27 |
28 | return obj.GetType() == GetType() && Equals((TestMultiplayerClientId) obj);
29 | }
30 |
31 | public override int GetHashCode() => id;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Network/TestMultiplayerEndpoint.cs:
--------------------------------------------------------------------------------
1 | using MultiplayerMod.Network;
2 |
3 | namespace MultiplayerMod.Test.Environment.Network;
4 |
5 | public record TestMultiplayerEndpoint(TestMultiplayerServer Server) : IMultiplayerEndpoint;
6 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Patches/PatchesSetup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using HarmonyLib;
5 | using MultiplayerMod.Core.Extensions;
6 |
7 | namespace MultiplayerMod.Test.Environment.Patches;
8 |
9 | public static class PatchesSetup {
10 |
11 | public static void Install(Harmony harmony, IEnumerable patches) =>
12 | patches.ForEach(it => harmony.CreateClassProcessor(it).Patch());
13 |
14 | public static void Uninstall(Harmony harmony) {
15 | foreach (var methodBase in harmony.GetPatchedMethods()) {
16 | if (!methodBase.HasMethodBody())
17 | continue;
18 |
19 | var patches = Harmony.GetPatchInfo(methodBase);
20 | patches.Prefixes
21 | .Union(patches.Postfixes)
22 | .Union(patches.Transpilers)
23 | .Union(patches.Finalizers)
24 | .Where(it => it.owner == harmony.Id)
25 | .ForEach(it => harmony.Unpatch(methodBase, it.PatchMethod));
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/TestRuntime.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using MultiplayerMod.Core.Dependency;
4 | using MultiplayerMod.ModRuntime;
5 |
6 | namespace MultiplayerMod.Test.Environment;
7 |
8 | public class TestRuntime : Runtime {
9 |
10 | public event Action? Deactivated;
11 | public event Action? Activated;
12 |
13 | public TestRuntime(DependencyContainer container) : base(container) { }
14 |
15 | public void Activate() {
16 | var oldRuntime = Instance;
17 | var property = typeof(Runtime).GetProperty("Instance", BindingFlags.Static | BindingFlags.Public)!;
18 | property.SetValue(null, this);
19 | Deactivated?.Invoke(oldRuntime);
20 | Activated?.Invoke(this);
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/LoadingOverlayPatch.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using JetBrains.Annotations;
3 |
4 | namespace MultiplayerMod.Test.Environment.Unity.Patches;
5 |
6 | [UsedImplicitly]
7 | [HarmonyPatch(typeof(LoadingOverlay))]
8 | public static class LoadingOverlayPatch {
9 |
10 | [UsedImplicitly]
11 | [HarmonyPrefix]
12 | [HarmonyPatch(nameof(LoadingOverlay.Load))]
13 | private static bool Load(System.Action cb) {
14 | cb();
15 | return false;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/SpeedControlScreenPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection.Emit;
3 | using HarmonyLib;
4 | using JetBrains.Annotations;
5 | using MultiplayerMod.Core.Patch;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity.Patches;
8 |
9 | [UsedImplicitly]
10 | [HarmonyPatch(typeof(SpeedControlScreen))]
11 | public static class SpeedControlScreenPatch {
12 |
13 | [UsedImplicitly]
14 | [HarmonyTranspiler]
15 | [HarmonyPatch(nameof(SpeedControlScreen.Pause))]
16 | private static IEnumerable Pause(IEnumerable instructions) {
17 | using var source = instructions.GetEnumerator();
18 | var result = new List();
19 | result.AddConditional(
20 | source,
21 | it => it.StoresField(AccessTools.Field(typeof(SpeedControlScreen), "pauseCount"))
22 | );
23 | result.Add(new CodeInstruction(OpCodes.Ret));
24 | return result;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/BehaviourPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection.Emit;
3 | using HarmonyLib;
4 | using JetBrains.Annotations;
5 | using UnityEngine;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity;
8 |
9 | [UsedImplicitly]
10 | [HarmonyPatch(typeof(Behaviour))]
11 | public class BehaviourPatch {
12 |
13 | [UsedImplicitly]
14 | [HarmonyTranspiler]
15 | [HarmonyPatch("get_isActiveAndEnabled")]
16 | private static IEnumerable Behaviour_get_isActiveAndEnabled(IEnumerable instructions) {
17 | return new List {
18 | new(OpCodes.Ldc_I4_1), // true
19 | new(OpCodes.Ret)
20 | };
21 | }
22 |
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/LayerMaskPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection.Emit;
3 | using HarmonyLib;
4 | using JetBrains.Annotations;
5 | using UnityEngine;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity;
8 |
9 | [UsedImplicitly]
10 | [HarmonyPatch(typeof(LayerMask))]
11 | public class LayerMaskPatch {
12 |
13 | [UsedImplicitly]
14 | [HarmonyTranspiler]
15 | [HarmonyPatch("NameToLayer")]
16 | private static IEnumerable LayerMask_NameToLayer(IEnumerable instructions) {
17 | return new List {
18 | new(OpCodes.Ldc_I4_0),
19 | new(OpCodes.Ret)
20 | };
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/MeshPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection.Emit;
3 | using HarmonyLib;
4 | using JetBrains.Annotations;
5 | using UnityEngine;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity;
8 |
9 | [UsedImplicitly]
10 | [HarmonyPatch(typeof(Mesh))]
11 | public class MeshPatch {
12 |
13 | [UsedImplicitly]
14 | [HarmonyTranspiler]
15 | [HarmonyPatch("Internal_Create")]
16 | private static IEnumerable Mesh_Internal_Create(IEnumerable instructions) {
17 | return new List {
18 | new(OpCodes.Ret)
19 | };
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/RendererPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection.Emit;
3 | using HarmonyLib;
4 | using JetBrains.Annotations;
5 | using UnityEngine;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity;
8 |
9 | [UsedImplicitly]
10 | [HarmonyPatch(typeof(Renderer))]
11 | public class RendererPatch {
12 |
13 | [UsedImplicitly]
14 | [HarmonyTranspiler]
15 | [HarmonyPatch("get_material")]
16 | private static IEnumerable Renderer_get_material(IEnumerable instructions) {
17 | return new List {
18 | new(OpCodes.Ldarg_0), // this
19 | CodeInstruction.Call(typeof(RendererPatch), nameof(CreateMaterial)),
20 | new(OpCodes.Ret)
21 | };
22 | }
23 |
24 | public static Material CreateMaterial(Renderer _) {
25 | #pragma warning disable CS0618 // Type or member is obsolete
26 | return new Material("");
27 | #pragma warning restore CS0618 // Type or member is obsolete
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/ShaderPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection.Emit;
3 | using HarmonyLib;
4 | using JetBrains.Annotations;
5 | using UnityEngine;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity;
8 |
9 | [UsedImplicitly]
10 | [HarmonyPatch(typeof(Shader))]
11 | public class ShaderPatch {
12 |
13 | [UsedImplicitly]
14 | [HarmonyTranspiler]
15 | [HarmonyPatch(nameof(Shader.PropertyToID))]
16 | private static IEnumerable SystemInfo_get_processorCount(
17 | IEnumerable instructions
18 | ) {
19 | return new List {
20 | new(OpCodes.Ldc_I4_0), // 0
21 | new(OpCodes.Ret)
22 | };
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/Patches/Unity/SystemInfoPatch.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Reflection.Emit;
3 | using HarmonyLib;
4 | using JetBrains.Annotations;
5 | using UnityEngine;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity.Patches.Unity;
8 |
9 | [UsedImplicitly]
10 | [HarmonyPatch(typeof(SystemInfo))]
11 | public class SystemInfoPatch {
12 |
13 | [UsedImplicitly]
14 | [HarmonyTranspiler]
15 | [HarmonyPatch("get_processorCount")]
16 | private static IEnumerable SystemInfo_get_processorCount(
17 | IEnumerable instructions
18 | ) {
19 | return new List {
20 | new(OpCodes.Ldc_I4_1), // 1
21 | new(OpCodes.Ret)
22 | };
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/UnityEvent.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Test.Environment.Unity;
2 |
3 | public class UnityEvent {
4 |
5 | public static UnityEvent Awake = new("Awake");
6 | public static UnityEvent Update = new("Update");
7 | public static UnityEvent LateUpdate = new("LateUpdate");
8 | public static UnityEvent Enable = new("OnEnable");
9 | public static UnityEvent Start = new("Start");
10 |
11 | public string MethodName { get; }
12 |
13 | public UnityEvent(string methodName) {
14 | MethodName = methodName;
15 | }
16 |
17 | public override int GetHashCode() => MethodName.GetHashCode();
18 | public override bool Equals(object? other) => other is UnityEvent @event && @event.MethodName.Equals(MethodName);
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Environment/Unity/UnityPlayerObjectManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Threading;
3 | using MultiplayerMod.Core.Unity;
4 | using MultiplayerMod.Test.Environment.Unity.Patches.Unity;
5 | using Object = UnityEngine.Object;
6 |
7 | namespace MultiplayerMod.Test.Environment.Unity;
8 |
9 | public static class UnityPlayerObjectManager {
10 |
11 | private static int currentInstanceId;
12 |
13 | public static void Allocate(Object obj) {
14 | var pointer = UnityPlayerObject.CreateEmpty();
15 | obj.m_CachedPtr = pointer;
16 | // Since KObjectManager.GetOrCreateObject uses GameObject.GetInstanceID()
17 | // we have to provide different values for different objects.
18 | unsafe {
19 | var instanceId = (int*)IntPtr.Add(pointer, ObjectPatch.OffsetOfInstanceIDInCPlusPlusObject).ToPointer();
20 | *instanceId = currentInstanceId;
21 | }
22 | Interlocked.Increment(ref currentInstanceId);
23 | }
24 |
25 | public static void Release(Object obj) {
26 | UnityPlayerObject.Free(obj.m_CachedPtr);
27 | obj.m_CachedPtr = IntPtr.Zero;
28 | }
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/GameRuntime/Patches/ElementLoaderPatch.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using JetBrains.Annotations;
3 |
4 | namespace MultiplayerMod.Test.GameRuntime.Patches;
5 |
6 | [UsedImplicitly]
7 | [HarmonyPatch(typeof(ElementLoader))]
8 | public class ElementLoaderPatch {
9 |
10 | [UsedImplicitly]
11 | [HarmonyPrefix]
12 | [HarmonyPatch(nameof(ElementLoader.FindElementByHash))]
13 | private static bool ElementLoader_FindElementByHash(ref Element __result) {
14 | __result = new Element() { name = "Patched element", substance = new Substance() { anim = new KAnimFile() {IsBuildLoaded = true} } };
15 | return false;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Multiplayer/Chores/ChoresInstantiationTests.cs:
--------------------------------------------------------------------------------
1 | using MultiplayerMod.Multiplayer.Chores.Events;
2 | using NUnit.Framework;
3 |
4 | namespace MultiplayerMod.Test.Multiplayer.Chores;
5 |
6 | [TestFixture]
7 | public class ChoresInstantiationTests : ChoreTest {
8 |
9 | [Test, TestCaseSource(nameof(ChoresInstantiationTestCases))]
10 | public void ChoreCreatedEventMustBeFired(ChoreFactory factory) {
11 | ChoreCreatedEvent firedEvent = null!;
12 | Events.Subscribe((@event, subscription) => {
13 | firedEvent = @event;
14 | subscription.Cancel();
15 | });
16 | factory.Create();
17 | Assert.NotNull(firedEvent);
18 | Assert.That(firedEvent, Is.Not.Null);
19 | Assert.That(firedEvent.Type, Is.EqualTo(factory.Type));
20 | Assert.That(firedEvent.Arguments, Is.EqualTo(factory.GetArguments()));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Multiplayer/Chores/ChoresMultiplayerConfigurationTest.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 | using HarmonyLib;
4 | using MultiplayerMod.Multiplayer.Chores;
5 | using NUnit.Framework;
6 |
7 | namespace MultiplayerMod.Test.Multiplayer.Chores;
8 |
9 | [TestFixture]
10 | public class ChoresMultiplayerConfigurationTest {
11 |
12 | [Test]
13 | public void GeneralChoresMustBeSynchronized() {
14 | var choreTypes = AccessTools.GetTypesFromAssembly(Assembly.GetAssembly(typeof(Chore)))
15 | .Where(it => typeof(Chore).IsAssignableFrom(it))
16 | .Where(it => it != typeof(Chore))
17 | .Where(it => !it.IsGenericType);
18 |
19 | var configuredTypes = ChoresMultiplayerConfiguration.Configuration.Select(it => it.ChoreType);
20 |
21 | Assert.That(configuredTypes, Is.EquivalentTo(choreTypes));
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Multiplayer/MultiplayerStatusOverlayPatch.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using JetBrains.Annotations;
3 | using MultiplayerMod.Multiplayer.UI.Overlays;
4 |
5 | namespace MultiplayerMod.Test.Multiplayer;
6 |
7 | [UsedImplicitly]
8 | [HarmonyPatch(typeof(MultiplayerStatusOverlay))]
9 | public class MultiplayerStatusOverlayPatch {
10 |
11 | [UsedImplicitly]
12 | [HarmonyPrefix]
13 | [HarmonyPatch(nameof(MultiplayerStatusOverlay.Show))]
14 | private static bool Show() {
15 | return false;
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Multiplayer/SpeedControlScreenContext.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using JetBrains.Annotations;
3 | using MultiplayerMod.Game.Context;
4 |
5 | namespace MultiplayerMod.Test.Multiplayer;
6 |
7 | [UsedImplicitly]
8 | public class SpeedControlScreenContext : IGameContext {
9 |
10 | private SpeedControlScreen? instance;
11 |
12 | private readonly PropertyInfo property = typeof(SpeedControlScreen).GetProperty(
13 | nameof(SpeedControlScreen.Instance),
14 | BindingFlags.Static | BindingFlags.Public
15 | )!;
16 |
17 | // ReSharper disable once Unity.IncorrectMonoBehaviourInstantiation
18 | public void Apply() {
19 | instance = SpeedControlScreen.Instance;
20 | property.SetValue(null, new SpeedControlScreen());
21 | }
22 |
23 | public void Restore() {
24 | property.SetValue(null, instance);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Multiplayer/StateMachines/RuntimeTools/StateMachineRuntimeToolsTests.cs:
--------------------------------------------------------------------------------
1 | using MultiplayerMod.Multiplayer.StateMachines.RuntimeTools;
2 | using NUnit.Framework;
3 |
4 | namespace MultiplayerMod.Test.Multiplayer.StateMachines.RuntimeTools;
5 |
6 | [TestFixture]
7 | public class StateMachineRuntimeToolsTests : StateMachinesTest {
8 |
9 | [Test]
10 | public void ControllerIsResolved() {
11 | var instance = CreateStateMachineInstance();
12 | var runtime = StateMachineRuntimeTools.Get(instance);
13 | var controller = runtime.GetController();
14 | Assert.That(controller, Is.Not.Null);
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Multiplayer/StateMachines/StateMachinesTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MultiplayerMod.Test.GameRuntime;
3 | using NUnit.Framework;
4 | using UnityEngine;
5 |
6 | namespace MultiplayerMod.Test.Multiplayer.StateMachines;
7 |
8 | [TestFixture]
9 | public class StateMachinesTest : PlayableGameTest {
10 |
11 | [TearDown]
12 | private void StateMachinesTearDown() {
13 | StateMachineManager.Instance.stateMachines.Clear();
14 | }
15 |
16 | protected static T CreateStateMachineInstance() where T : StateMachine.Instance {
17 | var go = new GameObject();
18 | var target = go.AddComponent();
19 | target.Awake();
20 | return (T) Activator.CreateInstance(typeof(T), [target]);
21 | }
22 |
23 | public class TestTarget : KMonoBehaviour;
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/MultiplayerMod.Test/Multiplayer/WorldManagerPatch.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using JetBrains.Annotations;
3 | using MultiplayerMod.Multiplayer.World;
4 |
5 | namespace MultiplayerMod.Test.Multiplayer;
6 |
7 | [UsedImplicitly]
8 | [HarmonyPatch(typeof(WorldManager))]
9 | public class WorldManagerPatch {
10 |
11 | [UsedImplicitly]
12 | [HarmonyPrefix]
13 | [HarmonyPatch("GetWorldSave")]
14 | // ReSharper disable once RedundantAssignment
15 | private static bool GetWorldSave(ref byte[] __result) {
16 | __result = new byte[] { 0xFF };
17 | return false;
18 | }
19 |
20 | [UsedImplicitly]
21 | [HarmonyPrefix]
22 | [HarmonyPatch("LoadWorldSave")]
23 | // ReSharper disable once RedundantAssignment
24 | private static bool LoadWorldSave() {
25 | return false;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Compatibility.cs:
--------------------------------------------------------------------------------
1 | // Record type support for .NET < 5
2 |
3 | // ReSharper disable once CheckNamespace
4 | // ReSharper disable once ArrangeNamespaceBody
5 |
6 | namespace System.Runtime.CompilerServices;
7 |
8 | // ReSharper disable once UnusedType.Global
9 | internal static class IsExternalInit { }
10 |
11 | // Caller argument expression support for .NET < 5
12 | [AttributeUsage(AttributeTargets.Parameter)]
13 | internal sealed class CallerArgumentExpressionAttribute : Attribute {
14 |
15 | public CallerArgumentExpressionAttribute(string parameterName) {
16 | ParameterName = parameterName;
17 | }
18 |
19 | public string ParameterName { get; }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Collections/BoxedValue.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Collections;
2 |
3 | public class BoxedValue(T value) {
4 | public T Value { get; set; } = value;
5 | }
6 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Collections/Deconstructable.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Collections;
2 |
3 | public class Deconstructable(T1 v1, T2 v2) {
4 |
5 | public void Deconstruct(out T1 a1, out T2 a2) {
6 | a1 = v1;
7 | a2 = v2;
8 | }
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Collections/IdentityEqualityComparer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Runtime.CompilerServices;
3 |
4 | namespace MultiplayerMod.Core.Collections;
5 |
6 | public class IdentityEqualityComparer : IEqualityComparer {
7 | public bool Equals(T x, T y) => ReferenceEquals(x, y);
8 | public int GetHashCode(T obj) => RuntimeHelpers.GetHashCode(obj);
9 | }
10 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Collections/IndexedObjectExtensionMap.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Collections;
2 |
3 | public class IndexedObjectExtensionMap : BidirectionalMap {
4 | public IndexedObjectExtensionMap() : base(new IdentityEqualityComparer()) { }
5 | }
6 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Collections/SingletonCollection.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 |
4 | namespace MultiplayerMod.Core.Collections;
5 |
6 | public class SingletonCollection : IReadOnlyCollection {
7 | private readonly T value;
8 |
9 | public int Count => 1;
10 |
11 | public SingletonCollection(T value) {
12 | this.value = value;
13 | }
14 |
15 | public IEnumerator GetEnumerator() {
16 | yield return value;
17 | }
18 |
19 | IEnumerator IEnumerable.GetEnumerator() {
20 | yield return value;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/AmbiguousDependencyException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace MultiplayerMod.Core.Dependency;
5 |
6 | public class AmbiguousDependencyException : Exception {
7 |
8 | public AmbiguousDependencyException(Type type, ICollection infos) : base(
9 | $"Ambiguous dependencies of type \"{type}\": found {infos.Count} instances"
10 | ) { }
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/DependencyAlreadyRegisteredException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | public class DependencyAlreadyRegisteredException : Exception {
6 | public DependencyAlreadyRegisteredException(string name) : base($"Dependency \"{name}\" is already registered") { }
7 | }
8 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/DependencyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | [AttributeUsage(AttributeTargets.Class)]
6 | public class DependencyAttribute : Attribute {
7 | public string? Name { get; set; } = null;
8 | public bool Lazy { get; set; } = false;
9 | }
10 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/DependencyInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | public class DependencyInfo {
6 |
7 | public string Name { get; }
8 | public Type Type { get; }
9 | public bool Lazy { get; }
10 |
11 | public DependencyInfo(string name, Type type, bool lazy) {
12 | if (!type.IsClass)
13 | throw new InvalidDependencyException($"\"{type}\" must be a class.");
14 |
15 | Name = name;
16 | Type = type;
17 | Lazy = lazy;
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/DependencyInstantiationException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | public class DependencyInstantiationException : Exception {
6 |
7 | private readonly DependencyInfo info;
8 |
9 | public DependencyInstantiationException(DependencyInfo info, Exception cause) : base(
10 | message: $"Unable to instantiate a dependency \"{info.Name}\"",
11 | innerException: cause
12 | ) {
13 | this.info = info;
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/DependencyIsInstantiatingException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | public class DependencyIsInstantiatingException : Exception {
6 |
7 | private readonly DependencyInfo info;
8 |
9 | public DependencyIsInstantiatingException(DependencyInfo info) : base(
10 | $"A dependency \"{info.Name}\" is currently instantiating (circular dependency?)"
11 | ) {
12 | this.info = info;
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/IDependencyContainer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using MultiplayerMod.Core.Collections;
3 |
4 | namespace MultiplayerMod.Core.Dependency;
5 |
6 | public interface IDependencyContainer {
7 | T Get() where T : notnull;
8 | Deconstructable Get() where T1 : notnull where T2 : notnull;
9 | object Get(Type type);
10 | }
11 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/IDependencyInjector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | public interface IDependencyInjector {
6 | public T Inject(T instance) where T : notnull;
7 | public void Inject(Type type);
8 | }
9 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/InjectDependencyAttribute.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
6 | public class InjectDependencyAttribute : Attribute { }
7 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/InvalidDependencyException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | public class InvalidDependencyException : Exception {
6 | public InvalidDependencyException(string message) : base(message) { }
7 | }
8 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Dependency/MissingDependencyException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Dependency;
4 |
5 | public class MissingDependencyException : Exception {
6 | public MissingDependencyException(Type type) : base($"Dependency of type \"{type}\" not found") { }
7 | }
8 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Events/EventSubscription.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Events;
4 |
5 | public interface IEventSubscription {
6 | void Cancel();
7 | }
8 |
9 | public class EventSubscription : IEventSubscription {
10 |
11 | private readonly EventDispatcher dispatcher;
12 | private readonly Delegate action;
13 | private readonly Type type;
14 |
15 | public EventSubscription(EventDispatcher dispatcher, Delegate action, Type type) {
16 | this.dispatcher = dispatcher;
17 | this.action = action;
18 | this.type = type;
19 | }
20 |
21 | public void Cancel() => dispatcher.Unsubscribe(type, action);
22 |
23 | }
24 |
25 |
26 | public class EventSubscription : IEventSubscription where T : IDispatchableEvent {
27 |
28 | private readonly EventDispatcher dispatcher;
29 | private readonly Delegate action;
30 |
31 | public EventSubscription(EventDispatcher dispatcher, Delegate action) {
32 | this.dispatcher = dispatcher;
33 | this.action = action;
34 | }
35 |
36 | public void Cancel() => dispatcher.Unsubscribe(action);
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Events/EventSubscriptions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 |
4 | namespace MultiplayerMod.Core.Events;
5 |
6 | public class EventSubscriptions : IEnumerable {
7 |
8 | private readonly List subscriptions = new();
9 |
10 | public EventSubscriptions Add(IEventSubscription subscription) {
11 | subscriptions.Add(subscription);
12 | return this;
13 | }
14 |
15 | public void Cancel() => subscriptions.ForEach(it => it.Cancel());
16 |
17 | public IEnumerator GetEnumerator() => subscriptions.GetEnumerator();
18 |
19 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Events/IDispatchableEvent.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Events;
2 |
3 | public interface IDispatchableEvent;
4 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Extensions/ArraysExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Extensions;
2 |
3 | public static class ArraysExtensions {
4 |
5 | private static readonly uint[] hexLookup = CreateHexLookup();
6 |
7 | private static uint[] CreateHexLookup() {
8 | const int byteCardinality = byte.MaxValue + 1;
9 | var result = new uint[byteCardinality];
10 | for (var i = 0; i < byteCardinality; i++) {
11 | var value = $"{i:x2}";
12 | result[i] = value[0] + (uint) (value[1] << 16);
13 | }
14 | return result;
15 | }
16 |
17 | public static string ToHexString(this byte[] bytes) {
18 | var result = new char[bytes.Length * 2];
19 | for (var i = 0; i < bytes.Length; i++) {
20 | var value = hexLookup[bytes[i]];
21 | result[2 * i] = (char) value;
22 | result[2 * i + 1] = (char) (value >> 16);
23 | }
24 | return new string(result);
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Extensions/DictionaryExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace MultiplayerMod.Core.Extensions;
5 |
6 | public static class DictionaryExtensions {
7 |
8 | public static TValue GetOrAdd(this IDictionary dictionary, TKey key)
9 | where TValue : new()
10 | {
11 | if (dictionary.TryGetValue(key, out var val))
12 | return val;
13 |
14 | val = new TValue();
15 | dictionary.Add(key, val);
16 | return val;
17 | }
18 |
19 | public static TValue GetOrAdd(
20 | this IDictionary dictionary,
21 | TKey key,
22 | Func defaultValue
23 | ) {
24 | if (dictionary.TryGetValue(key, out var val))
25 | return val;
26 |
27 | val = defaultValue();
28 | dictionary.Add(key, val);
29 | return val;
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Extensions/HashAlgorithmExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Reflection;
3 | using System.Security.Cryptography;
4 |
5 | namespace MultiplayerMod.Core.Extensions;
6 |
7 | public static class HashAlgorithmExtensions {
8 |
9 | public static byte[] ComputeHash(this HashAlgorithm algorithm, MethodBase method) {
10 | var body = method.GetMethodBody() ?? throw new Exception(
11 | $"Unable to compute hash of " +
12 | $"{method.DeclaringType?.GetSignature(SignatureOptions.Namespace)}:{method.Name}, " +
13 | $"no method body available"
14 | );
15 | return algorithm.ComputeHash(body.GetILAsByteArray());
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Extensions/LinkedListExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace MultiplayerMod.Core.Extensions;
5 |
6 | public static class LinkedListExtensions {
7 |
8 | // ReSharper disable Unity.PerformanceAnalysis
9 | public static void ForEach(this LinkedList list, Action> action) {
10 | var node = list.First;
11 | while (node != null) {
12 | var next = node.Next;
13 | action(node.Value, node);
14 | node = next;
15 | }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Extensions/NumberExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Extensions;
4 |
5 | public static class NumberExtensions {
6 |
7 | public static readonly string Digits = "0123456789abcdefghijklmnopqrstuvwxyz";
8 |
9 | private static string LongToString(ulong value, int radix) {
10 | if (radix is < 2 or > 36)
11 | throw new ArgumentException("Radix must be from 2 to 36", nameof(radix));
12 | var result = "";
13 | do
14 | result = Digits[(int)(value % (byte)radix)] + result;
15 | while ((value /= (byte)radix) != 0);
16 | return result;
17 | }
18 |
19 | public static string ToString(this long value, int radix) => LongToString((ulong)value, radix);
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Extensions/SetExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace MultiplayerMod.Core.Extensions;
4 |
5 | public static class SetExtensions {
6 |
7 | public static void AddRange(this ISet set, IEnumerable items) => items.ForEach(it => set.Add(it));
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Extensions/TypeExtensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Reflection;
5 |
6 | namespace MultiplayerMod.Core.Extensions;
7 |
8 | public static class TypeExtensions {
9 |
10 | public static MethodInfo[] GetAllMethods(this Type type) => type.GetMethods(
11 | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic
12 | );
13 |
14 | public static IEnumerable GetInheritedTypes(this Type type) {
15 | var current = type;
16 | while (current != typeof(object) && current != null) {
17 | yield return current;
18 |
19 | current = current.BaseType;
20 | }
21 | }
22 |
23 | public static bool Inherits(this Type self, Type type) => self.GetInheritedTypes().Any(it => it == type);
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Logging/LogLevel.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Logging;
2 |
3 | public enum LogLevel {
4 | Trace,
5 | Debug,
6 | Info,
7 | Warning,
8 | Error
9 | }
10 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Patch/Capture/CaptureUnavailableException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Patch.Capture;
4 |
5 | public class CaptureUnavailableException : Exception {
6 | public CaptureUnavailableException(string message) : base(message) { }
7 | }
8 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Patch/Capture/ILocalCapture.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Patch.Capture;
2 |
3 | public interface ILocalCapture { }
4 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Patch/Capture/InvalidLocalVariableReferenceException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace MultiplayerMod.Core.Patch.Capture;
4 |
5 | public class InvalidLocalVariableReferenceException : Exception {
6 | public InvalidLocalVariableReferenceException(string message) : base(message) { }
7 | }
8 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Patch/Capture/LocalCaptor.cs:
--------------------------------------------------------------------------------
1 | namespace MultiplayerMod.Core.Patch.Capture;
2 |
3 | public class LocalCaptor {
4 |
5 | private readonly LocalCaptureContainer container = new();
6 |
7 | private LocalCaptor(System.Action action) {
8 | LocalVariableCaptureSupport.ContainerReference.Value = container;
9 | action();
10 | LocalVariableCaptureSupport.ContainerReference.Value = null;
11 | }
12 |
13 | public static T Capture(System.Action action) where T : ILocalCapture {
14 | var captures = new LocalCaptor(action).container.Captures;
15 | if (captures.Count == 0 || captures[0].GetType() != typeof(T))
16 | throw new CaptureUnavailableException($"Capture of type {typeof(T).FullName} is unavailable");
17 | return (T) captures[0];
18 | }
19 |
20 | }
21 |
--------------------------------------------------------------------------------
/src/MultiplayerMod/Core/Patch/Capture/LocalCaptureContainer.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace MultiplayerMod.Core.Patch.Capture;
4 |
5 | public class LocalCaptureContainer {
6 |
7 | // ReSharper disable once CollectionNeverUpdated.Global
8 | public List