Files
gregCore/mods/greg.Plugin.LangCompatBridge/GameAPI.cs
T
Marvin 84b6f56a50 chore: add DataCenter-RustBridge submodule
Initialize the DataCenter-RustBridge submodule to integrate the Rust bridge plugin for the DataCenter project. This enables using the external Rust-based components as a git submodule.
2026-04-20 14:48:52 +02:00

2969 lines
125 KiB
C#

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Il2Cpp;
using MelonLoader;
using UnityEngine;
namespace DataCenterModLoader;
// function pointer table for rust mods, append-only
[StructLayout(LayoutKind.Sequential)]
public struct GameAPITable
{
// v1
public uint ApiVersion;
public IntPtr LogInfo;
public IntPtr LogWarning;
public IntPtr LogError;
public IntPtr GetPlayerMoney;
public IntPtr SetPlayerMoney;
public IntPtr GetTimeScale;
public IntPtr SetTimeScale;
public IntPtr GetServerCount;
public IntPtr GetRackCount;
public IntPtr GetCurrentScene;
// v2
public IntPtr GetPlayerXP;
public IntPtr SetPlayerXP;
public IntPtr GetPlayerReputation;
public IntPtr SetPlayerReputation;
public IntPtr GetTimeOfDay;
public IntPtr GetDay;
public IntPtr GetSecondsInFullDay;
public IntPtr SetSecondsInFullDay;
public IntPtr GetSwitchCount;
public IntPtr GetSatisfiedCustomerCount;
// v3
public IntPtr SetNetWatchEnabled;
public IntPtr IsNetWatchEnabled;
public IntPtr GetNetWatchStats;
// v4
public IntPtr GetBrokenServerCount;
public IntPtr GetBrokenSwitchCount;
public IntPtr GetEolServerCount;
public IntPtr GetEolSwitchCount;
public IntPtr GetFreeTechnicianCount;
public IntPtr GetTotalTechnicianCount;
public IntPtr DispatchRepairServer;
public IntPtr DispatchRepairSwitch;
public IntPtr DispatchReplaceServer;
public IntPtr DispatchReplaceSwitch;
// v5
public IntPtr RegisterCustomEmployee;
public IntPtr IsCustomEmployeeHired;
public IntPtr FireCustomEmployee;
public IntPtr RegisterSalary;
// v6
public IntPtr ShowNotification;
public IntPtr GetMoneyPerSecond;
public IntPtr GetExpensesPerSecond;
public IntPtr GetXpPerSecond;
public IntPtr IsGamePaused;
public IntPtr SetGamePaused;
public IntPtr GetDifficulty;
public IntPtr TriggerSave;
// v7 - Steam / Multiplayer
public IntPtr SteamGetMyId;
public IntPtr SteamGetFriendName;
public IntPtr SteamCreateLobby;
public IntPtr SteamJoinLobby;
public IntPtr SteamLeaveLobby;
public IntPtr SteamGetLobbyId;
public IntPtr SteamGetLobbyOwner;
public IntPtr SteamGetLobbyMemberCount;
public IntPtr SteamGetLobbyMemberByIndex;
public IntPtr SteamSetLobbyData;
public IntPtr SteamGetLobbyData;
public IntPtr SteamSendP2P;
public IntPtr SteamIsP2PAvailable;
public IntPtr SteamReadP2P;
public IntPtr SteamAcceptP2P;
public IntPtr SteamPollEvent;
public IntPtr GetPlayerPosition;
// v8 - Mod Configuration
public IntPtr ConfigRegisterBool;
public IntPtr ConfigRegisterInt;
public IntPtr ConfigRegisterFloat;
public IntPtr ConfigGetBool;
public IntPtr ConfigGetInt;
public IntPtr ConfigGetFloat;
public IntPtr SpawnCharacter;
public IntPtr DestroyEntity;
public IntPtr SetEntityPosition;
public IntPtr IsEntityReady;
public IntPtr SetEntityAnimation;
public IntPtr GetPrefabCount;
public IntPtr SetEntityName;
public IntPtr GetPlayerCarryState;
public IntPtr GetPlayerCrouching;
public IntPtr GetPlayerSitting;
public IntPtr SetEntityCrouching;
public IntPtr SetEntitySitting;
public IntPtr SetEntityCarryAnim;
public IntPtr CreateEntityCarryVisual;
public IntPtr DestroyEntityCarryVisual;
public IntPtr GetDefaultSpawnPosition;
public IntPtr WarpLocalPlayer;
public IntPtr GetEntityPosition;
public IntPtr AddEntityCollider;
public IntPtr SetEntityCarryTransform;
// v13 - World Object Sync
public IntPtr WorldGetObjectCount;
public IntPtr WorldGetObjectHashes;
public IntPtr WorldGetObjectState;
public IntPtr WorldSpawnObject;
public IntPtr WorldDestroyObject;
public IntPtr WorldPlaceInRack;
public IntPtr WorldRemoveFromRack;
public IntPtr WorldSetPower;
public IntPtr WorldSetProperty;
public IntPtr WorldConnectCable;
public IntPtr WorldDisconnectCable;
public IntPtr WorldPickupObject;
public IntPtr WorldDropObject;
public IntPtr WorldEnsureRackUIDs;
public IntPtr ObjFindByType;
public IntPtr ObjGetStringField;
public IntPtr ObjIsActive;
public IntPtr ObjSetActive;
public IntPtr ObjGetPosition;
public IntPtr ObjSetPosition;
public IntPtr ObjSetRotation;
public IntPtr ObjSetParentToWorld;
public IntPtr RbSetKinematic;
public IntPtr RbSetGravity;
public IntPtr RbWakeUp;
public IntPtr ObjFindById;
public IntPtr GetHeldObject;
public IntPtr ObjGetRotation;
public IntPtr ObjSetParent;
public IntPtr ObjSetLocalPosition;
public IntPtr ObjSetLocalRotation;
public IntPtr RackFindPosition;
public IntPtr RackGameInstall;
public IntPtr RackGameUninstall;
public IntPtr ObjSetStringField;
}
public partial class GameAPIManager : IDisposable
{
public const uint API_VERSION = 19;
private IntPtr _tablePtr;
private GameAPITable _table;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void LogDelegate(IntPtr message);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate double GetDoubleDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetDoubleDelegate(double value);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate float GetFloatDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetFloatDelegate(float value);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint GetUIntDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetUIntDelegate(uint value);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr GetStringDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int GetIntDelegate();
// v7 delegate types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong GetULongDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr GetStringFromU64Delegate(ulong steamId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int CreateLobbyDelegate(uint lobbyType, uint maxPlayers);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int JoinLobbyDelegate(ulong lobbyId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void VoidDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate ulong GetLobbyMemberDelegate(uint index);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SetLobbyDataDelegate(IntPtr key, IntPtr value);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate IntPtr GetLobbyDataDelegate(IntPtr key);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int SendP2PDelegate(ulong target, IntPtr data, uint len, uint reliable);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint IsP2PAvailableDelegate(IntPtr outSize);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint ReadP2PDelegate(IntPtr buf, uint bufLen, IntPtr outSender);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void AcceptP2PDelegate(ulong remote);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint PollEventDelegate(IntPtr outType, IntPtr outData);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void GetPlayerPositionDelegate(IntPtr outX, IntPtr outY, IntPtr outZ, IntPtr outRy);
// v8 delegate types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint ConfigRegisterBoolDelegate(IntPtr modId, IntPtr key, IntPtr displayName, uint defaultValue, IntPtr description);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint ConfigRegisterIntDelegate(IntPtr modId, IntPtr key, IntPtr displayName, int defaultValue, int min, int max, IntPtr description);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint ConfigRegisterFloatDelegate(IntPtr modId, IntPtr key, IntPtr displayName, float defaultValue, float min, float max, IntPtr description);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint ConfigGetBoolDelegate(IntPtr modId, IntPtr key);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate int ConfigGetIntDelegate(IntPtr modId, IntPtr key);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate float ConfigGetFloatDelegate(IntPtr modId, IntPtr key);
// v9 delegate types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint SpawnCharacterDelegate(uint prefabIdx, float x, float y, float z, float rotY, IntPtr name);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void DestroyEntityDelegate(uint entityId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetEntityPositionDelegate(uint entityId, float x, float y, float z, float rotY);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint IsEntityReadyDelegate(uint entityId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetEntityAnimationDelegate(uint entityId, float speed, uint isWalking);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint GetPrefabCountDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetEntityNameDelegate(uint entityId, IntPtr name);
// v10 delegate types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void GetPlayerCarryStateDelegate(IntPtr outObjectInHand, IntPtr outNumObjects);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint GetPlayerCrouchingDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate uint GetPlayerSittingDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetEntityCrouchingDelegate(uint entityId, uint isCrouching);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetEntitySittingDelegate(uint entityId, uint isSitting);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void SetEntityCarryAnimDelegate(uint entityId, uint isCarrying);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void CreateEntityCarryVisualDelegate(uint entityId, uint objectInHandType);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void DestroyEntityCarryVisualDelegate(uint entityId);
// v12 delegate types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void GetDefaultSpawnPositionDelegate(IntPtr outX, IntPtr outY, IntPtr outZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate void WarpLocalPlayerDelegate(float x, float y, float z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate uint GetEntityPositionDelegate(uint entityId, IntPtr outX, IntPtr outY, IntPtr outZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void AddEntityColliderDelegate(uint entityId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SetEntityCarryTransformDelegate(uint entityId, float posX, float posY, float posZ, float rotX, float rotY, float rotZ);
// v13 - World sync delegate types
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate uint WorldGetObjectCountDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate uint WorldGetObjectHashesDelegate(IntPtr buf, uint maxCount);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate uint WorldGetObjectStateDelegate(IntPtr id, uint idLen, IntPtr buf, uint bufMax);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldSpawnObjectDelegate(byte objectType, int prefabId, float x, float y, float z, float rotX, float rotY, float rotZ, float rotW, IntPtr outId, uint outMax);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldDestroyObjectDelegate(IntPtr id, uint idLen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldPlaceInRackDelegate(IntPtr id, uint idLen, int rackUid);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldRemoveFromRackDelegate(IntPtr id, uint idLen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldSetPowerDelegate(IntPtr id, uint idLen, byte isOn);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldSetPropertyDelegate(IntPtr id, uint idLen, IntPtr key, uint keyLen, IntPtr val, uint valLen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldConnectCableDelegate(int cableId, byte startType, float sx, float sy, float sz, IntPtr startDevice, uint startDeviceLen, byte endType, float ex, float ey, float ez, IntPtr endDevice, uint endDeviceLen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldDisconnectCableDelegate(int cableId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldPickupObjectDelegate(IntPtr id, uint idLen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int WorldDropObjectDelegate(IntPtr id, uint idLen, float x, float y, float z, float rotX, float rotY, float rotZ, float rotW);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate uint ObjFindByTypeDelegate(byte typeId, IntPtr outHandles, uint max);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate uint ObjGetStringFieldDelegate(ulong handle, ushort fieldId, IntPtr outBuf, uint max);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetStringFieldDelegate(ulong handle, ushort fieldId, IntPtr value, uint valueLen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjIsActiveDelegate(ulong handle);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetActiveDelegate(ulong handle, int active);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjGetPositionDelegate(ulong handle, IntPtr outX, IntPtr outY, IntPtr outZ);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetPositionDelegate(ulong handle, float x, float y, float z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetRotationDelegate(ulong handle, float x, float y, float z, float w);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetParentToWorldDelegate(ulong handle);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int RbSetKinematicDelegate(ulong handle, int kinematic);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int RbSetGravityDelegate(ulong handle, int useGravity);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int RbWakeUpDelegate(ulong handle);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate ulong ObjFindByIdDelegate(byte typeId, ushort fieldId, IntPtr id, uint idLen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int GetHeldObjectDelegate(IntPtr outId, uint idMax, IntPtr outType);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjGetRotationDelegate(ulong handle, IntPtr outX, IntPtr outY, IntPtr outZ, IntPtr outW);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetParentDelegate(ulong child, ulong parent);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetLocalPositionDelegate(ulong handle, float x, float y, float z);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ObjSetLocalRotationDelegate(ulong handle, float x, float y, float z, float w);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate ulong RackFindPositionDelegate(int rackUid);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int RackGameInstallDelegate(ulong objHandle, ulong rackPosHandle, byte objectType);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int RackGameUninstallDelegate(ulong objHandle, byte objectType);
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr SteamAPI_SteamNetworking_v006();
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr SteamAPI_SteamUser_v023();
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr SteamAPI_SteamFriends_v018();
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
private static extern ulong SteamAPI_ISteamUser_GetSteamID(IntPtr self);
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr SteamAPI_ISteamFriends_GetFriendPersonaName(IntPtr self, ulong steamId);
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool SteamAPI_ISteamNetworking_SendP2PPacket(IntPtr self, ulong steamIDRemote, IntPtr pubData, uint cubData, int eP2PSendType, int nChannel);
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool SteamAPI_ISteamNetworking_IsP2PPacketAvailable(IntPtr self, out uint pcubMsgSize, int nChannel);
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool SteamAPI_ISteamNetworking_ReadP2PPacket(IntPtr self, IntPtr pubDest, uint cubDest, out uint pcubMsgSize, out ulong psteamIDRemote, int nChannel);
[DllImport("steam_api64", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(IntPtr instancePtr, ulong steamIDRemote);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int RegisterCustomEmployeeDelegate(IntPtr employeeId, IntPtr name, IntPtr description, float salary, float requiredReputation, uint confirmDialogs);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate uint IsCustomEmployeeHiredDelegate(IntPtr employeeId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FireCustomEmployeeDelegate(IntPtr employeeId);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int RegisterSalaryDelegate(int monthlySalary);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int ShowNotificationDelegate(IntPtr message);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void SetGamePausedDelegate(uint paused);
// prevent GC while rust holds pointers
private readonly LogDelegate _logInfo, _logWarning, _logError;
private readonly GetDoubleDelegate _getPlayerMoney, _getPlayerXP, _getPlayerReputation;
private readonly SetDoubleDelegate _setPlayerMoney, _setPlayerXP, _setPlayerReputation;
private readonly GetFloatDelegate _getTimeScale, _getTimeOfDay, _getSecondsInFullDay;
private readonly SetFloatDelegate _setTimeScale, _setSecondsInFullDay;
private readonly GetUIntDelegate _getServerCount, _getRackCount, _getDay, _getSwitchCount, _getSatisfiedCustomerCount;
private readonly GetUIntDelegate _isNetWatchEnabled, _getNetWatchStats;
private readonly SetUIntDelegate _setNetWatchEnabled;
private readonly GetStringDelegate _getCurrentScene;
// v4
private readonly GetUIntDelegate _getBrokenServerCount, _getBrokenSwitchCount, _getEolServerCount, _getEolSwitchCount;
private readonly GetUIntDelegate _getFreeTechnicianCount, _getTotalTechnicianCount;
private readonly GetIntDelegate _dispatchRepairServer, _dispatchRepairSwitch, _dispatchReplaceServer, _dispatchReplaceSwitch;
// v5
private readonly RegisterCustomEmployeeDelegate _registerCustomEmployee;
private readonly IsCustomEmployeeHiredDelegate _isCustomEmployeeHired;
private readonly FireCustomEmployeeDelegate _fireCustomEmployee;
private readonly RegisterSalaryDelegate _registerSalary;
// v6
private readonly ShowNotificationDelegate _showNotification;
private readonly GetFloatDelegate _getMoneyPerSecond, _getExpensesPerSecond, _getXpPerSecond;
private readonly GetUIntDelegate _isGamePaused2;
private readonly SetGamePausedDelegate _setGamePaused;
private readonly GetIntDelegate _getDifficulty, _triggerSave;
// v7
private readonly GetULongDelegate _steamGetMyId;
private readonly GetStringFromU64Delegate _steamGetFriendName;
private readonly CreateLobbyDelegate _steamCreateLobby;
private readonly JoinLobbyDelegate _steamJoinLobby;
private readonly VoidDelegate _steamLeaveLobby;
private readonly GetULongDelegate _steamGetLobbyId;
private readonly GetULongDelegate _steamGetLobbyOwner;
private readonly GetUIntDelegate _steamGetLobbyMemberCount;
private readonly GetLobbyMemberDelegate _steamGetLobbyMemberByIndex;
private readonly SetLobbyDataDelegate _steamSetLobbyData;
private readonly GetLobbyDataDelegate _steamGetLobbyData;
private readonly SendP2PDelegate _steamSendP2P;
private readonly IsP2PAvailableDelegate _steamIsP2PAvailable;
private readonly ReadP2PDelegate _steamReadP2P;
private readonly AcceptP2PDelegate _steamAcceptP2P;
private readonly PollEventDelegate _steamPollEvent;
private readonly GetPlayerPositionDelegate _getPlayerPosition;
// v8
private readonly ConfigRegisterBoolDelegate _configRegisterBool;
private readonly ConfigRegisterIntDelegate _configRegisterInt;
private readonly ConfigRegisterFloatDelegate _configRegisterFloat;
private readonly ConfigGetBoolDelegate _configGetBool;
private readonly ConfigGetIntDelegate _configGetInt;
private readonly ConfigGetFloatDelegate _configGetFloat;
private readonly SpawnCharacterDelegate _spawnCharacter;
private readonly DestroyEntityDelegate _destroyEntity;
private readonly SetEntityPositionDelegate _setEntityPosition;
private readonly IsEntityReadyDelegate _isEntityReady;
private readonly SetEntityAnimationDelegate _setEntityAnimation;
private readonly GetPrefabCountDelegate _getPrefabCount;
private readonly SetEntityNameDelegate _setEntityName;
private readonly GetPlayerCarryStateDelegate _getPlayerCarryState;
private readonly GetPlayerCrouchingDelegate _getPlayerCrouching;
private readonly GetPlayerSittingDelegate _getPlayerSitting;
private readonly SetEntityCrouchingDelegate _setEntityCrouching;
private readonly SetEntitySittingDelegate _setEntitySitting;
private readonly SetEntityCarryAnimDelegate _setEntityCarryAnim;
private readonly CreateEntityCarryVisualDelegate _createEntityCarryVisual;
private readonly DestroyEntityCarryVisualDelegate _destroyEntityCarryVisual;
private readonly GetDefaultSpawnPositionDelegate _getDefaultSpawnPosition;
private readonly WarpLocalPlayerDelegate _warpLocalPlayer;
GetEntityPositionDelegate _getEntityPosition;
AddEntityColliderDelegate _addEntityCollider;
SetEntityCarryTransformDelegate _setEntityCarryTransform;
// v13
WorldGetObjectCountDelegate _worldGetObjectCount;
WorldGetObjectHashesDelegate _worldGetObjectHashes;
WorldGetObjectStateDelegate _worldGetObjectState;
WorldSpawnObjectDelegate _worldSpawnObject;
WorldDestroyObjectDelegate _worldDestroyObject;
WorldPlaceInRackDelegate _worldPlaceInRack;
WorldRemoveFromRackDelegate _worldRemoveFromRack;
WorldSetPowerDelegate _worldSetPower;
WorldSetPropertyDelegate _worldSetProperty;
WorldConnectCableDelegate _worldConnectCable;
WorldDisconnectCableDelegate _worldDisconnectCable;
WorldPickupObjectDelegate _worldPickupObject;
WorldDropObjectDelegate _worldDropObject;
GetIntDelegate _worldEnsureRackUIDs;
private readonly ObjFindByTypeDelegate _objFindByType;
private readonly ObjGetStringFieldDelegate _objGetStringField;
private readonly ObjIsActiveDelegate _objIsActive;
private readonly ObjSetActiveDelegate _objSetActive;
private readonly ObjGetPositionDelegate _objGetPosition;
private readonly ObjSetPositionDelegate _objSetPosition;
private readonly ObjSetRotationDelegate _objSetRotation;
private readonly ObjSetParentToWorldDelegate _objSetParentToWorld;
private readonly RbSetKinematicDelegate _rbSetKinematic;
private readonly RbSetGravityDelegate _rbSetGravity;
private readonly RbWakeUpDelegate _rbWakeUp;
ObjFindByIdDelegate _objFindById;
private readonly GetHeldObjectDelegate _getHeldObject;
private readonly ObjGetRotationDelegate _objGetRotation;
private readonly ObjSetParentDelegate _objSetParent;
private readonly ObjSetLocalPositionDelegate _objSetLocalPosition;
private readonly ObjSetLocalRotationDelegate _objSetLocalRotation;
private readonly RackFindPositionDelegate _rackFindPosition;
private readonly RackGameInstallDelegate _rackGameInstall;
private readonly RackGameUninstallDelegate _rackGameUninstall;
private readonly ObjSetStringFieldDelegate _objSetStringField2;
private readonly MelonLogger.Instance _logger;
private IntPtr _currentScenePtr = IntPtr.Zero;
private IntPtr _friendNamePtr = IntPtr.Zero;
private IntPtr _lobbyDataPtr = IntPtr.Zero;
private IntPtr _steamNetworking = IntPtr.Zero;
private IntPtr _steamUser = IntPtr.Zero;
private IntPtr _steamFriends = IntPtr.Zero;
public GameAPIManager(MelonLogger.Instance logger)
{
_logger = logger;
_logInfo = LogInfoImpl;
_logWarning = LogWarningImpl;
_logError = LogErrorImpl;
_getPlayerMoney = GetPlayerMoneyImpl;
_setPlayerMoney = SetPlayerMoneyImpl;
_getTimeScale = GetTimeScaleImpl;
_setTimeScale = SetTimeScaleImpl;
_getServerCount = GetServerCountImpl;
_getRackCount = GetRackCountImpl;
_getCurrentScene = GetCurrentSceneImpl;
_getPlayerXP = GetPlayerXPImpl;
_setPlayerXP = SetPlayerXPImpl;
_getPlayerReputation = GetPlayerReputationImpl;
_setPlayerReputation = SetPlayerReputationImpl;
_getTimeOfDay = GetTimeOfDayImpl;
_getDay = GetDayImpl;
_getSecondsInFullDay = GetSecondsInFullDayImpl;
_setSecondsInFullDay = SetSecondsInFullDayImpl;
_getSwitchCount = GetSwitchCountImpl;
_getSatisfiedCustomerCount = GetSatisfiedCustomerCountImpl;
_setNetWatchEnabled = SetNetWatchEnabledImpl;
_isNetWatchEnabled = IsNetWatchEnabledImpl;
_getNetWatchStats = GetNetWatchStatsImpl;
_getBrokenServerCount = GetBrokenServerCountImpl;
_getBrokenSwitchCount = GetBrokenSwitchCountImpl;
_getEolServerCount = GetEolServerCountImpl;
_getEolSwitchCount = GetEolSwitchCountImpl;
_getFreeTechnicianCount = GetFreeTechnicianCountImpl;
_getTotalTechnicianCount = GetTotalTechnicianCountImpl;
_dispatchRepairServer = DispatchRepairServerImpl;
_dispatchRepairSwitch = DispatchRepairSwitchImpl;
_dispatchReplaceServer = DispatchReplaceServerImpl;
_dispatchReplaceSwitch = DispatchReplaceSwitchImpl;
_registerCustomEmployee = RegisterCustomEmployeeImpl;
_isCustomEmployeeHired = IsCustomEmployeeHiredImpl;
_fireCustomEmployee = FireCustomEmployeeImpl;
_registerSalary = RegisterSalaryImpl;
_showNotification = ShowNotificationImpl;
_getMoneyPerSecond = GetMoneyPerSecondImpl;
_getExpensesPerSecond = GetExpensesPerSecondImpl;
_getXpPerSecond = GetXpPerSecondImpl;
_isGamePaused2 = IsGamePausedImpl;
_setGamePaused = SetGamePausedImpl;
_getDifficulty = GetDifficultyImpl;
_triggerSave = TriggerSaveImpl;
_steamGetMyId = SteamGetMyIdImpl;
_steamGetFriendName = SteamGetFriendNameImpl;
_steamCreateLobby = SteamCreateLobbyImpl;
_steamJoinLobby = SteamJoinLobbyImpl;
_steamLeaveLobby = SteamLeaveLobbyImpl;
_steamGetLobbyId = SteamGetLobbyIdImpl;
_steamGetLobbyOwner = SteamGetLobbyOwnerImpl;
_steamGetLobbyMemberCount = SteamGetLobbyMemberCountImpl;
_steamGetLobbyMemberByIndex = SteamGetLobbyMemberByIndexImpl;
_steamSetLobbyData = SteamSetLobbyDataImpl;
_steamGetLobbyData = SteamGetLobbyDataImpl;
_steamSendP2P = SteamSendP2PImpl;
_steamIsP2PAvailable = SteamIsP2PAvailableImpl;
_steamReadP2P = SteamReadP2PImpl;
_steamAcceptP2P = SteamAcceptP2PImpl;
_steamPollEvent = SteamPollEventImpl;
_getPlayerPosition = GetPlayerPositionImpl;
_configRegisterBool = ConfigRegisterBoolImpl;
_configRegisterInt = ConfigRegisterIntImpl;
_configRegisterFloat = ConfigRegisterFloatImpl;
_configGetBool = ConfigGetBoolImpl;
_configGetInt = ConfigGetIntImpl;
_configGetFloat = ConfigGetFloatImpl;
_spawnCharacter = SpawnCharacterImpl;
_destroyEntity = DestroyEntityImpl;
_setEntityPosition = SetEntityPositionImpl;
_isEntityReady = IsEntityReadyImpl;
_setEntityAnimation = SetEntityAnimationImpl;
_getPrefabCount = GetPrefabCountImpl;
_setEntityName = SetEntityNameImpl;
_getPlayerCarryState = GetPlayerCarryStateImpl;
_getPlayerCrouching = GetPlayerCrouchingImpl;
_getPlayerSitting = GetPlayerSittingImpl;
_setEntityCrouching = SetEntityCrouchingImpl;
_setEntitySitting = SetEntitySittingImpl;
_setEntityCarryAnim = SetEntityCarryAnimImpl;
_createEntityCarryVisual = CreateEntityCarryVisualImpl;
_destroyEntityCarryVisual = DestroyEntityCarryVisualImpl;
_getDefaultSpawnPosition = GetDefaultSpawnPositionImpl;
_warpLocalPlayer = WarpLocalPlayerImpl;
_getEntityPosition = GetEntityPositionImpl;
_addEntityCollider = AddEntityColliderImpl;
_setEntityCarryTransform = SetEntityCarryTransformImpl;
// v13
_worldGetObjectCount = WorldGetObjectCountImpl;
_worldGetObjectHashes = WorldGetObjectHashesImpl;
_worldGetObjectState = WorldGetObjectStateImpl;
_worldSpawnObject = WorldSpawnObjectImpl;
_worldDestroyObject = WorldDestroyObjectImpl;
_worldPlaceInRack = WorldPlaceInRackImpl;
_worldRemoveFromRack = WorldRemoveFromRackImpl;
_worldSetPower = WorldSetPowerImpl;
_worldSetProperty = WorldSetPropertyImpl;
_worldConnectCable = WorldConnectCableImpl;
_worldDisconnectCable = WorldDisconnectCableImpl;
_worldPickupObject = WorldPickupObjectImpl;
_worldDropObject = WorldDropObjectImpl;
_worldEnsureRackUIDs = WorldEnsureRackUIDsImpl;
_objFindByType = ObjFindByTypeImpl;
_objGetStringField = ObjGetStringFieldImpl;
_objIsActive = ObjIsActiveImpl;
_objSetActive = ObjSetActiveImpl;
_objGetPosition = ObjGetPositionImpl;
_objSetPosition = ObjSetPositionImpl;
_objSetRotation = ObjSetRotationImpl;
_objSetParentToWorld = ObjSetParentToWorldImpl;
_rbSetKinematic = RbSetKinematicImpl;
_rbSetGravity = RbSetGravityImpl;
_rbWakeUp = RbWakeUpImpl;
_objFindById = ObjFindByIdImpl;
_getHeldObject = GetHeldObjectImpl;
_objGetRotation = ObjGetRotationImpl;
_objSetParent = ObjSetParentImpl;
_objSetLocalPosition = ObjSetLocalPositionImpl;
_objSetLocalRotation = ObjSetLocalRotationImpl;
_rackFindPosition = RackFindPositionImpl;
_rackGameInstall = RackGameInstallImpl;
_rackGameUninstall = RackGameUninstallImpl;
_objSetStringField2 = ObjSetStringFieldImpl;
_table = new GameAPITable
{
ApiVersion = API_VERSION,
LogInfo = Marshal.GetFunctionPointerForDelegate(_logInfo),
LogWarning = Marshal.GetFunctionPointerForDelegate(_logWarning),
LogError = Marshal.GetFunctionPointerForDelegate(_logError),
GetPlayerMoney = Marshal.GetFunctionPointerForDelegate(_getPlayerMoney),
SetPlayerMoney = Marshal.GetFunctionPointerForDelegate(_setPlayerMoney),
GetTimeScale = Marshal.GetFunctionPointerForDelegate(_getTimeScale),
SetTimeScale = Marshal.GetFunctionPointerForDelegate(_setTimeScale),
GetServerCount = Marshal.GetFunctionPointerForDelegate(_getServerCount),
GetRackCount = Marshal.GetFunctionPointerForDelegate(_getRackCount),
GetCurrentScene = Marshal.GetFunctionPointerForDelegate(_getCurrentScene),
GetPlayerXP = Marshal.GetFunctionPointerForDelegate(_getPlayerXP),
SetPlayerXP = Marshal.GetFunctionPointerForDelegate(_setPlayerXP),
GetPlayerReputation = Marshal.GetFunctionPointerForDelegate(_getPlayerReputation),
SetPlayerReputation = Marshal.GetFunctionPointerForDelegate(_setPlayerReputation),
GetTimeOfDay = Marshal.GetFunctionPointerForDelegate(_getTimeOfDay),
GetDay = Marshal.GetFunctionPointerForDelegate(_getDay),
GetSecondsInFullDay = Marshal.GetFunctionPointerForDelegate(_getSecondsInFullDay),
SetSecondsInFullDay = Marshal.GetFunctionPointerForDelegate(_setSecondsInFullDay),
GetSwitchCount = Marshal.GetFunctionPointerForDelegate(_getSwitchCount),
GetSatisfiedCustomerCount = Marshal.GetFunctionPointerForDelegate(_getSatisfiedCustomerCount),
SetNetWatchEnabled = Marshal.GetFunctionPointerForDelegate(_setNetWatchEnabled),
IsNetWatchEnabled = Marshal.GetFunctionPointerForDelegate(_isNetWatchEnabled),
GetNetWatchStats = Marshal.GetFunctionPointerForDelegate(_getNetWatchStats),
GetBrokenServerCount = Marshal.GetFunctionPointerForDelegate(_getBrokenServerCount),
GetBrokenSwitchCount = Marshal.GetFunctionPointerForDelegate(_getBrokenSwitchCount),
GetEolServerCount = Marshal.GetFunctionPointerForDelegate(_getEolServerCount),
GetEolSwitchCount = Marshal.GetFunctionPointerForDelegate(_getEolSwitchCount),
GetFreeTechnicianCount = Marshal.GetFunctionPointerForDelegate(_getFreeTechnicianCount),
GetTotalTechnicianCount = Marshal.GetFunctionPointerForDelegate(_getTotalTechnicianCount),
DispatchRepairServer = Marshal.GetFunctionPointerForDelegate(_dispatchRepairServer),
DispatchRepairSwitch = Marshal.GetFunctionPointerForDelegate(_dispatchRepairSwitch),
DispatchReplaceServer = Marshal.GetFunctionPointerForDelegate(_dispatchReplaceServer),
DispatchReplaceSwitch = Marshal.GetFunctionPointerForDelegate(_dispatchReplaceSwitch),
RegisterCustomEmployee = Marshal.GetFunctionPointerForDelegate(_registerCustomEmployee),
IsCustomEmployeeHired = Marshal.GetFunctionPointerForDelegate(_isCustomEmployeeHired),
FireCustomEmployee = Marshal.GetFunctionPointerForDelegate(_fireCustomEmployee),
RegisterSalary = Marshal.GetFunctionPointerForDelegate(_registerSalary),
ShowNotification = Marshal.GetFunctionPointerForDelegate(_showNotification),
GetMoneyPerSecond = Marshal.GetFunctionPointerForDelegate(_getMoneyPerSecond),
GetExpensesPerSecond = Marshal.GetFunctionPointerForDelegate(_getExpensesPerSecond),
GetXpPerSecond = Marshal.GetFunctionPointerForDelegate(_getXpPerSecond),
IsGamePaused = Marshal.GetFunctionPointerForDelegate(_isGamePaused2),
SetGamePaused = Marshal.GetFunctionPointerForDelegate(_setGamePaused),
GetDifficulty = Marshal.GetFunctionPointerForDelegate(_getDifficulty),
TriggerSave = Marshal.GetFunctionPointerForDelegate(_triggerSave),
SteamGetMyId = Marshal.GetFunctionPointerForDelegate(_steamGetMyId),
SteamGetFriendName = Marshal.GetFunctionPointerForDelegate(_steamGetFriendName),
SteamCreateLobby = Marshal.GetFunctionPointerForDelegate(_steamCreateLobby),
SteamJoinLobby = Marshal.GetFunctionPointerForDelegate(_steamJoinLobby),
SteamLeaveLobby = Marshal.GetFunctionPointerForDelegate(_steamLeaveLobby),
SteamGetLobbyId = Marshal.GetFunctionPointerForDelegate(_steamGetLobbyId),
SteamGetLobbyOwner = Marshal.GetFunctionPointerForDelegate(_steamGetLobbyOwner),
SteamGetLobbyMemberCount = Marshal.GetFunctionPointerForDelegate(_steamGetLobbyMemberCount),
SteamGetLobbyMemberByIndex = Marshal.GetFunctionPointerForDelegate(_steamGetLobbyMemberByIndex),
SteamSetLobbyData = Marshal.GetFunctionPointerForDelegate(_steamSetLobbyData),
SteamGetLobbyData = Marshal.GetFunctionPointerForDelegate(_steamGetLobbyData),
SteamSendP2P = Marshal.GetFunctionPointerForDelegate(_steamSendP2P),
SteamIsP2PAvailable = Marshal.GetFunctionPointerForDelegate(_steamIsP2PAvailable),
SteamReadP2P = Marshal.GetFunctionPointerForDelegate(_steamReadP2P),
SteamAcceptP2P = Marshal.GetFunctionPointerForDelegate(_steamAcceptP2P),
SteamPollEvent = Marshal.GetFunctionPointerForDelegate(_steamPollEvent),
GetPlayerPosition = Marshal.GetFunctionPointerForDelegate(_getPlayerPosition),
ConfigRegisterBool = Marshal.GetFunctionPointerForDelegate(_configRegisterBool),
ConfigRegisterInt = Marshal.GetFunctionPointerForDelegate(_configRegisterInt),
ConfigRegisterFloat = Marshal.GetFunctionPointerForDelegate(_configRegisterFloat),
ConfigGetBool = Marshal.GetFunctionPointerForDelegate(_configGetBool),
ConfigGetInt = Marshal.GetFunctionPointerForDelegate(_configGetInt),
ConfigGetFloat = Marshal.GetFunctionPointerForDelegate(_configGetFloat),
SpawnCharacter = Marshal.GetFunctionPointerForDelegate(_spawnCharacter),
DestroyEntity = Marshal.GetFunctionPointerForDelegate(_destroyEntity),
SetEntityPosition = Marshal.GetFunctionPointerForDelegate(_setEntityPosition),
IsEntityReady = Marshal.GetFunctionPointerForDelegate(_isEntityReady),
SetEntityAnimation = Marshal.GetFunctionPointerForDelegate(_setEntityAnimation),
GetPrefabCount = Marshal.GetFunctionPointerForDelegate(_getPrefabCount),
SetEntityName = Marshal.GetFunctionPointerForDelegate(_setEntityName),
GetPlayerCarryState = Marshal.GetFunctionPointerForDelegate(_getPlayerCarryState),
GetPlayerCrouching = Marshal.GetFunctionPointerForDelegate(_getPlayerCrouching),
GetPlayerSitting = Marshal.GetFunctionPointerForDelegate(_getPlayerSitting),
SetEntityCrouching = Marshal.GetFunctionPointerForDelegate(_setEntityCrouching),
SetEntitySitting = Marshal.GetFunctionPointerForDelegate(_setEntitySitting),
SetEntityCarryAnim = Marshal.GetFunctionPointerForDelegate(_setEntityCarryAnim),
CreateEntityCarryVisual = Marshal.GetFunctionPointerForDelegate(_createEntityCarryVisual),
DestroyEntityCarryVisual = Marshal.GetFunctionPointerForDelegate(_destroyEntityCarryVisual),
GetDefaultSpawnPosition = Marshal.GetFunctionPointerForDelegate(_getDefaultSpawnPosition),
WarpLocalPlayer = Marshal.GetFunctionPointerForDelegate(_warpLocalPlayer),
GetEntityPosition = Marshal.GetFunctionPointerForDelegate(_getEntityPosition),
AddEntityCollider = Marshal.GetFunctionPointerForDelegate(_addEntityCollider),
SetEntityCarryTransform = Marshal.GetFunctionPointerForDelegate(_setEntityCarryTransform),
WorldGetObjectCount = Marshal.GetFunctionPointerForDelegate(_worldGetObjectCount),
WorldGetObjectHashes = Marshal.GetFunctionPointerForDelegate(_worldGetObjectHashes),
WorldGetObjectState = Marshal.GetFunctionPointerForDelegate(_worldGetObjectState),
WorldSpawnObject = Marshal.GetFunctionPointerForDelegate(_worldSpawnObject),
WorldDestroyObject = Marshal.GetFunctionPointerForDelegate(_worldDestroyObject),
WorldPlaceInRack = Marshal.GetFunctionPointerForDelegate(_worldPlaceInRack),
WorldRemoveFromRack = Marshal.GetFunctionPointerForDelegate(_worldRemoveFromRack),
WorldSetPower = Marshal.GetFunctionPointerForDelegate(_worldSetPower),
WorldSetProperty = Marshal.GetFunctionPointerForDelegate(_worldSetProperty),
WorldConnectCable = Marshal.GetFunctionPointerForDelegate(_worldConnectCable),
WorldDisconnectCable = Marshal.GetFunctionPointerForDelegate(_worldDisconnectCable),
WorldPickupObject = Marshal.GetFunctionPointerForDelegate(_worldPickupObject),
WorldDropObject = Marshal.GetFunctionPointerForDelegate(_worldDropObject),
WorldEnsureRackUIDs = Marshal.GetFunctionPointerForDelegate(_worldEnsureRackUIDs),
ObjFindByType = Marshal.GetFunctionPointerForDelegate(_objFindByType),
ObjGetStringField = Marshal.GetFunctionPointerForDelegate(_objGetStringField),
ObjIsActive = Marshal.GetFunctionPointerForDelegate(_objIsActive),
ObjSetActive = Marshal.GetFunctionPointerForDelegate(_objSetActive),
ObjGetPosition = Marshal.GetFunctionPointerForDelegate(_objGetPosition),
ObjSetPosition = Marshal.GetFunctionPointerForDelegate(_objSetPosition),
ObjSetRotation = Marshal.GetFunctionPointerForDelegate(_objSetRotation),
ObjSetParentToWorld = Marshal.GetFunctionPointerForDelegate(_objSetParentToWorld),
RbSetKinematic = Marshal.GetFunctionPointerForDelegate(_rbSetKinematic),
RbSetGravity = Marshal.GetFunctionPointerForDelegate(_rbSetGravity),
RbWakeUp = Marshal.GetFunctionPointerForDelegate(_rbWakeUp),
ObjFindById = Marshal.GetFunctionPointerForDelegate(_objFindById),
GetHeldObject = Marshal.GetFunctionPointerForDelegate(_getHeldObject),
ObjGetRotation = Marshal.GetFunctionPointerForDelegate(_objGetRotation),
ObjSetParent = Marshal.GetFunctionPointerForDelegate(_objSetParent),
ObjSetLocalPosition = Marshal.GetFunctionPointerForDelegate(_objSetLocalPosition),
ObjSetLocalRotation = Marshal.GetFunctionPointerForDelegate(_objSetLocalRotation),
RackFindPosition = Marshal.GetFunctionPointerForDelegate(_rackFindPosition),
RackGameInstall = Marshal.GetFunctionPointerForDelegate(_rackGameInstall),
RackGameUninstall = Marshal.GetFunctionPointerForDelegate(_rackGameUninstall),
ObjSetStringField = Marshal.GetFunctionPointerForDelegate(_objSetStringField2),
};
_tablePtr = Marshal.AllocHGlobal(Marshal.SizeOf<GameAPITable>());
Marshal.StructureToPtr(_table, _tablePtr, false);
}
public IntPtr GetTablePointer() => _tablePtr;
private void LogInfoImpl(IntPtr msg) { _logger.Msg("[RustMod] " + (Marshal.PtrToStringAnsi(msg) ?? "")); }
private void LogWarningImpl(IntPtr msg) { _logger.Warning("[RustMod] " + (Marshal.PtrToStringAnsi(msg) ?? "")); }
private void LogErrorImpl(IntPtr msg) { _logger.Error("[RustMod] " + (Marshal.PtrToStringAnsi(msg) ?? "")); }
private double GetPlayerMoneyImpl()
{
try { return GameHooks.GetPlayerMoney(); }
catch (Exception ex) { _logger.Error("GetPlayerMoney: " + ex.Message); return 0.0; }
}
private void SetPlayerMoneyImpl(double value)
{
try { GameHooks.SetPlayerMoney((float)value); }
catch (Exception ex) { _logger.Error("SetPlayerMoney: " + ex.Message); }
}
private float GetTimeScaleImpl()
{
try { return Time.timeScale; } catch { return 1.0f; }
}
private void SetTimeScaleImpl(float value)
{
try { Time.timeScale = value; }
catch (Exception ex) { _logger.Error("SetTimeScale: " + ex.Message); }
}
private uint GetServerCountImpl() { try { return GameHooks.GetServerCount(); } catch { return 0; } }
private uint GetRackCountImpl() { try { return GameHooks.GetRackCount(); } catch { return 0; } }
private IntPtr GetCurrentSceneImpl()
{
try
{
var name = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name ?? "";
if (_currentScenePtr != IntPtr.Zero) Marshal.FreeHGlobal(_currentScenePtr);
_currentScenePtr = Marshal.StringToHGlobalAnsi(name);
return _currentScenePtr;
}
catch { return IntPtr.Zero; }
}
private double GetPlayerXPImpl()
{
try { return GameHooks.GetPlayerXP(); }
catch (Exception ex) { _logger.Error("GetPlayerXP: " + ex.Message); return 0.0; }
}
private void SetPlayerXPImpl(double value)
{
try { GameHooks.SetPlayerXP((float)value); }
catch (Exception ex) { _logger.Error("SetPlayerXP: " + ex.Message); }
}
private double GetPlayerReputationImpl()
{
try { return GameHooks.GetPlayerReputation(); }
catch (Exception ex) { _logger.Error("GetPlayerReputation: " + ex.Message); return 0.0; }
}
private void SetPlayerReputationImpl(double value)
{
try { GameHooks.SetPlayerReputation((float)value); }
catch (Exception ex) { _logger.Error("SetPlayerReputation: " + ex.Message); }
}
private float GetTimeOfDayImpl() { try { return GameHooks.GetTimeOfDay(); } catch { return 0f; } }
private uint GetDayImpl() { try { return (uint)Math.Max(0, GameHooks.GetDay()); } catch { return 0; } }
private float GetSecondsInFullDayImpl() { try { return GameHooks.GetSecondsInFullDay(); } catch { return 0f; } }
private void SetSecondsInFullDayImpl(float value)
{
try { GameHooks.SetSecondsInFullDay(value); }
catch (Exception ex) { _logger.Error("SetSecondsInFullDay: " + ex.Message); }
}
private uint GetSwitchCountImpl() { try { return GameHooks.GetSwitchCount(); } catch { return 0; } }
private uint GetSatisfiedCustomerCountImpl() { try { return (uint)Math.Max(0, GameHooks.GetSatisfiedCustomerCount()); } catch { return 0; } }
private static bool _netWatchEnabled;
private void SetNetWatchEnabledImpl(uint value)
{
_netWatchEnabled = value != 0;
}
private uint IsNetWatchEnabledImpl() { return _netWatchEnabled ? 1u : 0u; }
private uint GetNetWatchStatsImpl() { return 0; }
private uint GetBrokenServerCountImpl() { try { return GameHooks.GetBrokenServerCount(); } catch { return 0; } }
private uint GetBrokenSwitchCountImpl() { try { return GameHooks.GetBrokenSwitchCount(); } catch { return 0; } }
private uint GetEolServerCountImpl() { try { return GameHooks.GetEolServerCount(); } catch { return 0; } }
private uint GetEolSwitchCountImpl() { try { return GameHooks.GetEolSwitchCount(); } catch { return 0; } }
private uint GetFreeTechnicianCountImpl() { try { return GameHooks.GetFreeTechnicianCount(); } catch { return 0; } }
private uint GetTotalTechnicianCountImpl() { try { return GameHooks.GetTotalTechnicianCount(); } catch { return 0; } }
private int DispatchRepairServerImpl() { try { return GameHooks.DispatchRepairServer(); } catch { return 0; } }
private int DispatchRepairSwitchImpl() { try { return GameHooks.DispatchRepairSwitch(); } catch { return 0; } }
private int DispatchReplaceServerImpl() { try { return GameHooks.DispatchReplaceServer(); } catch { return 0; } }
private int DispatchReplaceSwitchImpl() { try { return GameHooks.DispatchReplaceSwitch(); } catch { return 0; } }
private int RegisterCustomEmployeeImpl(IntPtr employeeId, IntPtr name, IntPtr description, float salary, float requiredReputation, uint confirmDialogs)
{
try
{
string id = Marshal.PtrToStringAnsi(employeeId) ?? "";
string n = Marshal.PtrToStringAnsi(name) ?? "";
string desc = Marshal.PtrToStringAnsi(description) ?? "";
CrashLog.Log($"RegisterCustomEmployee: id={id}, name={n}, salary={salary}, rep={requiredReputation}, confirmDialogs={confirmDialogs}");
return CustomEmployeeManager.Register(id, n, desc, salary, requiredReputation, confirmDialogs != 0);
}
catch (Exception ex)
{
_logger.Error("RegisterCustomEmployee: " + ex.Message);
CrashLog.LogException("RegisterCustomEmployee", ex);
return 0;
}
}
private uint IsCustomEmployeeHiredImpl(IntPtr employeeId)
{
try
{
string id = Marshal.PtrToStringAnsi(employeeId) ?? "";
return CustomEmployeeManager.IsHired(id) ? 1u : 0u;
}
catch { return 0; }
}
private int FireCustomEmployeeImpl(IntPtr employeeId)
{
try
{
string id = Marshal.PtrToStringAnsi(employeeId) ?? "";
return CustomEmployeeManager.Fire(id);
}
catch { return 0; }
}
private int RegisterSalaryImpl(int monthlySalary)
{
try
{
var bs = BalanceSheet.instance;
if (bs == null) return 0;
bs.RegisterSalary(monthlySalary);
return 1;
}
catch (Exception ex)
{
CrashLog.LogException("RegisterSalary", ex);
return 0;
}
}
private int ShowNotificationImpl(IntPtr message)
{
try
{
string msg = Marshal.PtrToStringAnsi(message) ?? "";
var ui = StaticUIElements.instance;
if (ui == null) return 0;
ui.AddMeesageInField(msg);
return 1;
}
catch (Exception ex) { CrashLog.LogException("ShowNotification", ex); return 0; }
}
private float GetMoneyPerSecondImpl()
{
try
{
var ui = StaticUIElements.instance;
if (ui == null) return 0f;
ui.CalculateRates(out float money, out float _, out float _);
return money;
}
catch { return 0f; }
}
private float GetExpensesPerSecondImpl()
{
try
{
var ui = StaticUIElements.instance;
if (ui == null) return 0f;
ui.CalculateRates(out float _, out float _, out float expenses);
return expenses;
}
catch { return 0f; }
}
private float GetXpPerSecondImpl()
{
try
{
var ui = StaticUIElements.instance;
if (ui == null) return 0f;
ui.CalculateRates(out float _, out float xp, out float _);
return xp;
}
catch { return 0f; }
}
private uint IsGamePausedImpl()
{
try { return MainGameManager.instance?.isGamePaused == true ? 1u : 0u; }
catch { return 0; }
}
private void SetGamePausedImpl(uint paused)
{
try
{
var mgr = MainGameManager.instance;
if (mgr != null) mgr.isGamePaused = paused != 0;
}
catch (Exception ex) { CrashLog.LogException("SetGamePaused", ex); }
}
private int GetDifficultyImpl()
{
try { return MainGameManager.instance?.difficulty ?? -1; }
catch { return -1; }
}
private int TriggerSaveImpl()
{
try { SaveSystem.SaveGame(); return 1; }
catch (Exception ex) { CrashLog.LogException("TriggerSave", ex); return 0; }
}
private IntPtr GetSteamNetworking()
{
if (_steamNetworking == IntPtr.Zero)
_steamNetworking = SteamAPI_SteamNetworking_v006();
return _steamNetworking;
}
private IntPtr GetSteamUser()
{
if (_steamUser == IntPtr.Zero)
_steamUser = SteamAPI_SteamUser_v023();
return _steamUser;
}
private IntPtr GetSteamFriends()
{
if (_steamFriends == IntPtr.Zero)
_steamFriends = SteamAPI_SteamFriends_v018();
return _steamFriends;
}
private ulong SteamGetMyIdImpl()
{
try
{
var user = GetSteamUser();
if (user == IntPtr.Zero) return 0;
return SteamAPI_ISteamUser_GetSteamID(user);
}
catch (Exception ex) { CrashLog.LogException("SteamGetMyId", ex); return 0; }
}
private IntPtr SteamGetFriendNameImpl(ulong steamId)
{
try
{
var friends = GetSteamFriends();
if (friends == IntPtr.Zero) return IntPtr.Zero;
return SteamAPI_ISteamFriends_GetFriendPersonaName(friends, steamId);
}
catch (Exception ex) { CrashLog.LogException("SteamGetFriendName", ex); return IntPtr.Zero; }
}
private int SteamCreateLobbyImpl(uint lobbyType, uint maxPlayers) { return 0; }
private int SteamJoinLobbyImpl(ulong lobbyId) { return 0; }
private void SteamLeaveLobbyImpl() { }
private ulong SteamGetLobbyIdImpl() { return 0; }
private ulong SteamGetLobbyOwnerImpl() { return 0; }
private uint SteamGetLobbyMemberCountImpl() { return 0; }
private ulong SteamGetLobbyMemberByIndexImpl(uint index) { return 0; }
private int SteamSetLobbyDataImpl(IntPtr key, IntPtr value) { return 0; }
private IntPtr SteamGetLobbyDataImpl(IntPtr key) { return IntPtr.Zero; }
private int SteamSendP2PImpl(ulong target, IntPtr data, uint len, uint reliable)
{
try
{
var networking = GetSteamNetworking();
if (networking == IntPtr.Zero)
{
CrashLog.Log("[Steam] SendP2P: ISteamNetworking not available");
return 0;
}
// k_EP2PSendUnreliable=0, k_EP2PSendReliable=2
int sendType = reliable != 0 ? 2 : 0;
bool ok = SteamAPI_ISteamNetworking_SendP2PPacket(networking, target, data, len, sendType, 0);
if (!ok)
CrashLog.Log($"[Steam] SendP2PPacket failed: target={target}, len={len}, reliable={reliable}");
return ok ? 1 : 0;
}
catch (Exception ex) { CrashLog.LogException("SteamSendP2P", ex); return 0; }
}
private uint SteamIsP2PAvailableImpl(IntPtr outSize)
{
try
{
var networking = GetSteamNetworking();
if (networking == IntPtr.Zero) return 0;
bool available = SteamAPI_ISteamNetworking_IsP2PPacketAvailable(networking, out uint msgSize, 0);
if (available && msgSize > 0)
{
if (outSize != IntPtr.Zero)
Marshal.WriteInt32(outSize, (int)msgSize);
return 1;
}
return 0;
}
catch (Exception ex) { CrashLog.LogException("SteamIsP2PAvailable", ex); return 0; }
}
private uint SteamReadP2PImpl(IntPtr buf, uint bufLen, IntPtr outSender)
{
try
{
var networking = GetSteamNetworking();
if (networking == IntPtr.Zero) return 0;
bool ok = SteamAPI_ISteamNetworking_ReadP2PPacket(
networking, buf, bufLen, out uint bytesRead, out ulong sender, 0);
if (ok && bytesRead > 0)
{
if (outSender != IntPtr.Zero)
Marshal.WriteInt64(outSender, (long)sender);
return bytesRead;
}
return 0;
}
catch (Exception ex) { CrashLog.LogException("SteamReadP2P", ex); return 0; }
}
private void SteamAcceptP2PImpl(ulong remote)
{
try
{
var networking = GetSteamNetworking();
if (networking == IntPtr.Zero) return;
bool ok = SteamAPI_ISteamNetworking_AcceptP2PSessionWithUser(networking, remote);
CrashLog.Log($"[Steam] AcceptP2PSessionWithUser({remote}): {ok}");
}
catch (Exception ex) { CrashLog.LogException("SteamAcceptP2P", ex); }
}
private uint SteamPollEventImpl(IntPtr outType, IntPtr outData)
{
// TODO: implement event queue for lobby callbacks
return 0;
}
private void GetPlayerPositionImpl(IntPtr outX, IntPtr outY, IntPtr outZ, IntPtr outRy)
{
try
{
var pm = PlayerManager.instance;
if (pm == null || pm.playerGO == null) return;
var pos = pm.playerGO.transform.position;
var rot = pm.playerGO.transform.eulerAngles;
if (outX != IntPtr.Zero) Marshal.Copy(new float[] { pos.x }, 0, outX, 1);
if (outY != IntPtr.Zero) Marshal.Copy(new float[] { pos.y }, 0, outY, 1);
if (outZ != IntPtr.Zero) Marshal.Copy(new float[] { pos.z }, 0, outZ, 1);
if (outRy != IntPtr.Zero) Marshal.Copy(new float[] { rot.y }, 0, outRy, 1);
}
catch (Exception ex) { CrashLog.LogException("GetPlayerPosition", ex); }
}
private static uint ConfigRegisterBoolImpl(IntPtr modId, IntPtr key, IntPtr displayName, uint defaultValue, IntPtr description)
{
try
{
string mId = Marshal.PtrToStringAnsi(modId) ?? "";
string k = Marshal.PtrToStringAnsi(key) ?? "";
string dn = Marshal.PtrToStringAnsi(displayName) ?? k;
string desc = Marshal.PtrToStringAnsi(description) ?? "";
return ModConfigSystem.RegisterBool(mId, k, dn, defaultValue != 0, desc);
}
catch (Exception ex) { CrashLog.LogException("ConfigRegisterBoolImpl", ex); return 0; }
}
private static uint ConfigRegisterIntImpl(IntPtr modId, IntPtr key, IntPtr displayName, int defaultValue, int min, int max, IntPtr description)
{
try
{
string mId = Marshal.PtrToStringAnsi(modId) ?? "";
string k = Marshal.PtrToStringAnsi(key) ?? "";
string dn = Marshal.PtrToStringAnsi(displayName) ?? k;
string desc = Marshal.PtrToStringAnsi(description) ?? "";
return ModConfigSystem.RegisterInt(mId, k, dn, defaultValue, min, max, desc);
}
catch (Exception ex) { CrashLog.LogException("ConfigRegisterIntImpl", ex); return 0; }
}
private static uint ConfigRegisterFloatImpl(IntPtr modId, IntPtr key, IntPtr displayName, float defaultValue, float min, float max, IntPtr description)
{
try
{
string mId = Marshal.PtrToStringAnsi(modId) ?? "";
string k = Marshal.PtrToStringAnsi(key) ?? "";
string dn = Marshal.PtrToStringAnsi(displayName) ?? k;
string desc = Marshal.PtrToStringAnsi(description) ?? "";
return ModConfigSystem.RegisterFloat(mId, k, dn, defaultValue, min, max, desc);
}
catch (Exception ex) { CrashLog.LogException("ConfigRegisterFloatImpl", ex); return 0; }
}
private static uint ConfigGetBoolImpl(IntPtr modId, IntPtr key)
{
try
{
string mId = Marshal.PtrToStringAnsi(modId) ?? "";
string k = Marshal.PtrToStringAnsi(key) ?? "";
return ModConfigSystem.GetBool(mId, k);
}
catch (Exception ex) { CrashLog.LogException("ConfigGetBoolImpl", ex); return 0xFFFFFFFF; }
}
private static int ConfigGetIntImpl(IntPtr modId, IntPtr key)
{
try
{
string mId = Marshal.PtrToStringAnsi(modId) ?? "";
string k = Marshal.PtrToStringAnsi(key) ?? "";
return ModConfigSystem.GetInt(mId, k);
}
catch (Exception ex) { CrashLog.LogException("ConfigGetIntImpl", ex); return 0; }
}
private static float ConfigGetFloatImpl(IntPtr modId, IntPtr key)
{
try
{
string mId = Marshal.PtrToStringAnsi(modId) ?? "";
string k = Marshal.PtrToStringAnsi(key) ?? "";
return ModConfigSystem.GetFloat(mId, k);
}
catch (Exception ex) { CrashLog.LogException("ConfigGetFloatImpl", ex); return 0f; }
}
private static uint SpawnCharacterImpl(uint prefabIdx, float x, float y, float z, float rotY, IntPtr name)
{
try
{
string n = Marshal.PtrToStringAnsi(name) ?? "Entity";
return EntityManager.SpawnCharacter(prefabIdx, x, y, z, rotY, n);
}
catch (Exception ex) { CrashLog.LogException("SpawnCharacterImpl", ex); return 0; }
}
private static void DestroyEntityImpl(uint entityId)
{
try { EntityManager.DestroyEntity(entityId); }
catch (Exception ex) { CrashLog.LogException("DestroyEntityImpl", ex); }
}
private static void SetEntityPositionImpl(uint entityId, float x, float y, float z, float rotY)
{
try { EntityManager.SetPosition(entityId, x, y, z, rotY); }
catch (Exception ex) { CrashLog.LogException("SetEntityPositionImpl", ex); }
}
private static uint IsEntityReadyImpl(uint entityId)
{
try { return EntityManager.IsEntityReady(entityId) ? 1u : 0u; }
catch (Exception ex) { CrashLog.LogException("IsEntityReadyImpl", ex); return 0; }
}
private static void SetEntityAnimationImpl(uint entityId, float speed, uint isWalking)
{
try { EntityManager.SetAnimation(entityId, speed, isWalking != 0); }
catch (Exception ex) { CrashLog.LogException("SetEntityAnimationImpl", ex); }
}
private static uint GetPrefabCountImpl()
{
try { return EntityManager.GetPrefabCount(); }
catch (Exception ex) { CrashLog.LogException("GetPrefabCountImpl", ex); return 0; }
}
private static void SetEntityNameImpl(uint entityId, IntPtr name)
{
try
{
string n = Marshal.PtrToStringAnsi(name) ?? "";
EntityManager.SetEntityName(entityId, n);
}
catch (Exception ex) { CrashLog.LogException("SetEntityNameImpl", ex); }
}
private void GetPlayerCarryStateImpl(IntPtr outObjectInHand, IntPtr outNumObjects)
{
try
{
var pm = PlayerManager.instance;
if (pm == null) return;
uint objInHand = (uint)(int)pm.objectInHand;
uint numObj = (uint)pm.numberOfObjectsInHand;
if (outObjectInHand != IntPtr.Zero) Marshal.Copy(new int[] { (int)objInHand }, 0, outObjectInHand, 1);
if (outNumObjects != IntPtr.Zero) Marshal.Copy(new int[] { (int)numObj }, 0, outNumObjects, 1);
}
catch (Exception ex) { CrashLog.LogException("GetPlayerCarryStateImpl", ex); }
}
private static uint GetPlayerCrouchingImpl()
{
try
{
var pm = PlayerManager.instance;
if (pm == null || pm.fpc == null) return 0;
return pm.fpc.m_isCrouching ? 1u : 0u;
}
catch (Exception ex) { CrashLog.LogException("GetPlayerCrouchingImpl", ex); return 0; }
}
private static uint GetPlayerSittingImpl()
{
try
{
var pm = PlayerManager.instance;
if (pm == null || pm.fpc == null) return 0;
return pm.fpc.m_IsSitting ? 1u : 0u;
}
catch (Exception ex) { CrashLog.LogException("GetPlayerSittingImpl", ex); return 0; }
}
private static void SetEntityCrouchingImpl(uint entityId, uint isCrouching)
{
try { EntityManager.SetCrouching(entityId, isCrouching != 0); }
catch (Exception ex) { CrashLog.LogException("SetEntityCrouchingImpl", ex); }
}
private static void SetEntitySittingImpl(uint entityId, uint isSitting)
{
try { EntityManager.SetSitting(entityId, isSitting != 0); }
catch (Exception ex) { CrashLog.LogException("SetEntitySittingImpl", ex); }
}
private static void SetEntityCarryAnimImpl(uint entityId, uint isCarrying)
{
try { EntityManager.SetCarryAnim(entityId, isCarrying != 0); }
catch (Exception ex) { CrashLog.LogException("SetEntityCarryAnimImpl", ex); }
}
private static void CreateEntityCarryVisualImpl(uint entityId, uint objectInHandType)
{
try { EntityManager.CreateCarryVisual(entityId, objectInHandType); }
catch (Exception ex) { CrashLog.LogException("CreateEntityCarryVisualImpl", ex); }
}
private static void DestroyEntityCarryVisualImpl(uint entityId)
{
try { EntityManager.DestroyCarryVisual(entityId); }
catch (Exception ex) { CrashLog.LogException("DestroyEntityCarryVisualImpl", ex); }
}
private void GetDefaultSpawnPositionImpl(IntPtr outX, IntPtr outY, IntPtr outZ)
{
try
{
if (outX != IntPtr.Zero) Marshal.Copy(new float[] { 5f }, 0, outX, 1);
if (outY != IntPtr.Zero) Marshal.Copy(new float[] { 1f }, 0, outY, 1);
if (outZ != IntPtr.Zero) Marshal.Copy(new float[] { -24f }, 0, outZ, 1);
CrashLog.Log("[GameAPI] Default spawn: (5, 1, -24)");
}
catch (Exception ex) { CrashLog.LogException("GetDefaultSpawnPosition", ex); }
}
private void WarpLocalPlayerImpl(float x, float y, float z)
{
try
{
var pm = PlayerManager.instance;
if (pm == null || pm.playerClass == null || pm.playerGO == null) return;
var pos = new Vector3(x, y, z);
var rot = pm.playerGO.transform.rotation;
pm.playerClass.WarpPlayer(pos, rot);
CrashLog.Log($"[GameAPI] Warped local player to ({x:F1},{y:F1},{z:F1})");
}
catch (Exception ex) { CrashLog.LogException("WarpLocalPlayer", ex); }
}
private static uint GetEntityPositionImpl(uint entityId, IntPtr outX, IntPtr outY, IntPtr outZ)
{
try
{
var pos = EntityManager.GetEntityPosition(entityId);
if (pos == null) return 0;
var p = pos.Value;
if (outX != IntPtr.Zero) Marshal.Copy(new float[] { p.x }, 0, outX, 1);
if (outY != IntPtr.Zero) Marshal.Copy(new float[] { p.y }, 0, outY, 1);
if (outZ != IntPtr.Zero) Marshal.Copy(new float[] { p.z }, 0, outZ, 1);
return 1;
}
catch (Exception ex) { CrashLog.LogException("GetEntityPositionImpl", ex); return 0; }
}
private static void AddEntityColliderImpl(uint entityId)
{
try { EntityManager.AddEntityCollider(entityId); }
catch (Exception ex) { CrashLog.LogException("AddEntityColliderImpl", ex); }
}
private static void SetEntityCarryTransformImpl(uint entityId, float posX, float posY, float posZ, float rotX, float rotY, float rotZ)
{
try { EntityManager.SetEntityCarryTransform(entityId, posX, posY, posZ, rotX, rotY, rotZ); }
catch (Exception ex) { CrashLog.LogException("SetEntityCarryTransformImpl", ex); }
}
// ── v13: World Object Sync ────────────────────────────────────────
static string ReadUtf8(IntPtr ptr, uint len)
{
if (ptr == IntPtr.Zero || len == 0) return "";
byte[] buf = new byte[len];
Marshal.Copy(ptr, buf, 0, (int)len);
int end = Array.IndexOf(buf, (byte)0);
if (end < 0) end = (int)len;
return System.Text.Encoding.UTF8.GetString(buf, 0, end).Trim();
}
private ulong FindHandleByStableId(string targetId)
{
try
{
foreach (var srv in UnityEngine.Resources.FindObjectsOfTypeAll<Server>())
{
try
{
if (srv.gameObject.scene.name == null) continue;
if ((srv.ServerID ?? "") == targetId) return (ulong)srv.Pointer.ToInt64();
}
catch { }
}
foreach (var sw in UnityEngine.Resources.FindObjectsOfTypeAll<NetworkSwitch>())
{
try
{
if (sw.gameObject.scene.name == null) continue;
if ((sw.switchId ?? "") == targetId) return (ulong)sw.Pointer.ToInt64();
}
catch { }
}
foreach (var pp in UnityEngine.Resources.FindObjectsOfTypeAll<PatchPanel>())
{
try
{
if (pp.gameObject.scene.name == null) continue;
if ((pp.patchPanelId ?? "") == targetId) return (ulong)pp.Pointer.ToInt64();
}
catch { }
}
}
catch (Exception ex)
{
CrashLog.LogException("FindHandleByStableId", ex);
}
return 0;
}
uint WorldGetObjectCountImpl()
{
// Phase 4 stub
return 0;
}
uint WorldGetObjectHashesImpl(IntPtr buf, uint maxCount)
{
// Phase 4 stub
return 0;
}
uint WorldGetObjectStateImpl(IntPtr id, uint idLen, IntPtr buf, uint bufMax)
{
// Phase 4 stub
return 0;
}
int WorldSpawnObjectImpl(byte objectType, int prefabId, float x, float y, float z, float rotX, float rotY, float rotZ, float rotW, IntPtr outId, uint outMax)
{
try
{
string desiredId = null;
if (outId != IntPtr.Zero && outMax > 0)
{
byte firstByte = Marshal.ReadByte(outId);
if (firstByte != 0)
{
desiredId = ReadUtf8(outId, outMax);
}
}
CrashLog.Log($"[WorldSync] SpawnObject: type={objectType}, prefab={prefabId}, desiredId='{desiredId ?? "(none)"}', pos=({x:F1},{y:F1},{z:F1})");
var mgr = Il2Cpp.MainGameManager.instance;
if (mgr == null)
{
CrashLog.Log("[WorldSync] SpawnObject: MainGameManager is null");
return 0;
}
UnityEngine.GameObject prefab = null;
if (objectType == 4)
{
try
{
if (mgr.switchesPrefabs != null && prefabId >= 0 && prefabId < mgr.switchesPrefabs.Count)
{
prefab = mgr.switchesPrefabs[prefabId];
}
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] SpawnObject: switchesPrefabs lookup failed: {ex.Message}");
}
}
else if (objectType == 7)
{
try
{
prefab = mgr.GetPatchPanelPrefab(prefabId);
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] SpawnObject: GetPatchPanelPrefab failed: {ex.Message}");
}
}
else
{
try
{
if (mgr.serverPrefabs != null && prefabId >= 0 && prefabId < mgr.serverPrefabs.Count)
{
prefab = mgr.serverPrefabs[prefabId];
}
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] SpawnObject: serverPrefabs lookup failed: {ex.Message}");
}
}
if (prefab == null)
{
CrashLog.Log($"[WorldSync] SpawnObject: no prefab found for type={objectType} prefabId={prefabId}");
return 0;
}
var pos = new UnityEngine.Vector3(x, y, z);
var rot = new UnityEngine.Quaternion(rotX, rotY, rotZ, rotW);
var go = UnityEngine.Object.Instantiate(prefab, pos, rot);
if (go == null)
{
CrashLog.Log("[WorldSync] SpawnObject: Instantiate returned null");
return 0;
}
try
{
if (mgr.parentUsableObjects != null)
go.transform.SetParent(mgr.parentUsableObjects, true);
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] SpawnObject: parenting failed (non-fatal): {ex.Message}");
}
string resultId = go.name;
if (objectType == 4)
{
try
{
var switchComp = go.GetComponent<Il2Cpp.NetworkSwitch>();
if (switchComp != null)
{
if (!string.IsNullOrEmpty(desiredId))
{
switchComp.switchId = desiredId;
resultId = desiredId;
CrashLog.Log($"[WorldSync] SpawnObject: set switchId to '{desiredId}'");
}
else
{
resultId = switchComp.switchId ?? go.name;
}
}
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] SpawnObject: switch setup failed: {ex.Message}");
}
try
{
var rb = go.GetComponent<UnityEngine.Rigidbody>();
if (rb != null)
{
rb.isKinematic = false;
rb.useGravity = true;
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
rb.WakeUp();
}
}
catch { }
}
else if (objectType == 7)
{
try
{
var ppComp = go.GetComponent<Il2Cpp.PatchPanel>();
if (ppComp != null)
{
if (!string.IsNullOrEmpty(desiredId))
{
ppComp.patchPanelId = desiredId;
resultId = desiredId;
CrashLog.Log($"[WorldSync] SpawnObject: set patchPanelId to '{desiredId}'");
}
else
{
string ppId = ppComp.patchPanelId ?? "";
if (string.IsNullOrEmpty(ppId))
{
int instId = go.GetInstanceID();
string objName = go.name ?? "PatchPanel";
if (objName.EndsWith("(Clone)"))
objName = objName.Substring(0, objName.Length - 7);
ppId = $"{objName}_{instId}";
ppComp.patchPanelId = ppId;
}
resultId = ppId;
}
}
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] SpawnObject: patchpanel setup failed: {ex.Message}");
}
try
{
var rb = go.GetComponent<UnityEngine.Rigidbody>();
if (rb != null)
{
rb.isKinematic = false;
rb.useGravity = true;
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
rb.WakeUp();
}
}
catch { }
}
else
{
// Server setup
try
{
var serverComp = go.GetComponent<Il2Cpp.Server>();
if (serverComp != null)
{
if (!string.IsNullOrEmpty(desiredId))
{
serverComp.ServerID = desiredId;
resultId = desiredId;
CrashLog.Log($"[WorldSync] SpawnObject: set ServerID to '{desiredId}'");
}
else
{
resultId = serverComp.ServerID ?? go.name;
}
try
{
var rb = serverComp.rb;
if (rb != null)
{
rb.isKinematic = false;
rb.useGravity = true;
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
rb.WakeUp();
}
}
catch { }
}
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] SpawnObject: server setup failed: {ex.Message}");
}
}
if (outId != IntPtr.Zero && outMax > 0)
{
for (uint i = 0; i < outMax && i < 128; i++)
Marshal.WriteByte(outId, (int)i, 0);
var bytes = System.Text.Encoding.UTF8.GetBytes(resultId);
int copyLen = Math.Min(bytes.Length, (int)outMax - 1);
Marshal.Copy(bytes, 0, outId, copyLen);
Marshal.WriteByte(outId, copyLen, 0);
}
try { SpawnedObjectTracker.RegisterRemoteSpawn(go.GetInstanceID(), resultId); }
catch { }
CrashLog.Log($"[WorldSync] SpawnObject: created '{resultId}' (type={objectType}, prefab={prefabId}) OK");
return 1;
}
catch (Exception ex)
{
CrashLog.LogException("WorldSpawnObjectImpl", ex);
return 0;
}
}
int WorldDestroyObjectImpl(IntPtr id, uint idLen)
{
// Phase 3 stub
string objId = ReadUtf8(id, idLen);
CrashLog.Log($"[WorldSync] DestroyObject stub: id={objId}");
return 0;
}
private Il2Cpp.RackPosition FindRackPosition(int rackUid)
{
for (int attempt = 0; attempt < 2; attempt++)
{
var positions = UnityEngine.Object.FindObjectsOfType<Il2Cpp.RackPosition>();
foreach (var rp in positions)
{
try { if (rp.rackPosGlobalUID == rackUid) return rp; } catch { }
}
if (attempt == 0)
{
CrashLog.Log($"[WorldSync] FindRackPosition: uid={rackUid} not found, reassigning UIDs…");
GameHooks.EnsureAllRackPositionUIDs();
}
}
return null;
}
private int RackInstallBookkeeping(IntPtr ptr, Il2Cpp.RackPosition rackPos, byte objectType, string logPrefix)
{
int sizeInU = 1;
switch (objectType)
{
case 0: // Server1U
case 1: // (alias)
case 2: // Server7U
case 3: // Server3U
{
var server = new Il2Cpp.Server(ptr);
try
{
sizeInU = server.sizeInU > 0 ? server.sizeInU : 1;
int rackUid = rackPos.rackPosGlobalUID;
var sd = new Il2Cpp.ServerSaveData();
sd.serverID = server.ServerID;
sd.rackPositionUID = rackUid;
sd.serverType = server.serverType;
sd.position = rackPos.transform.position;
sd.rotation = rackPos.transform.rotation;
try { sd.isOn = server.isOn; } catch { sd.isOn = false; }
try { sd.isBroken = server.isBroken; } catch { sd.isBroken = false; }
server.ServerInsertedInRack(sd);
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: ServerInsertedInRack failed: {ex.Message}");
}
try
{
server.rackPositionUID = rackPos.rackPosGlobalUID;
server.currentRackPosition = rackPos;
server.objectInHands = false;
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: server field update failed: {ex.Message}");
}
break;
}
case 4: // NetworkSwitch
{
try
{
var sw = new Il2Cpp.NetworkSwitch(ptr);
int rackUid = rackPos.rackPosGlobalUID;
sizeInU = sw.sizeInU > 0 ? sw.sizeInU : 1;
var sd = new Il2Cpp.SwitchSaveData();
sd.switchID = sw.switchId;
sd.rackPositionUID = rackUid;
sd.switchType = sw.switchType;
sd.position = rackPos.transform.position;
sd.rotation = rackPos.transform.rotation;
try { sd.isOn = sw.isOn; } catch { sd.isOn = false; }
try { sd.isBroken = sw.isBroken; } catch { sd.isBroken = false; }
sw.SwitchInsertedInRack(sd);
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: SwitchInsertedInRack failed: {ex.Message}");
}
try
{
var sw = new Il2Cpp.NetworkSwitch(ptr);
sw.rackPositionUID = rackPos.rackPosGlobalUID;
sw.currentRackPosition = rackPos;
sw.objectInHands = false;
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: switch field update failed: {ex.Message}");
}
break;
}
case 7: // PatchPanel
{
try
{
var pp = new Il2Cpp.PatchPanel(ptr);
int rackUid = rackPos.rackPosGlobalUID;
sizeInU = pp.sizeInU > 0 ? pp.sizeInU : 1;
var sd = new Il2Cpp.PatchPanelSaveData();
sd.patchPanelID = pp.patchPanelId;
sd.rackPositionUID = rackUid;
sd.patchPanelType = pp.patchPanelType;
sd.position = rackPos.transform.position;
sd.rotation = rackPos.transform.rotation;
pp.InsertedInRack(sd);
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: PatchPanel.InsertedInRack failed: {ex.Message}");
}
try
{
var pp = new Il2Cpp.PatchPanel(ptr);
pp.rackPositionUID = rackPos.rackPosGlobalUID;
pp.currentRackPosition = rackPos;
pp.objectInHands = false;
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: patchpanel field update failed: {ex.Message}");
}
break;
}
default:
CrashLog.Log($"[WorldSync] {logPrefix}: unknown objectType={objectType}, no bookkeeping");
break;
}
return sizeInU;
}
private void RackUninstallBookkeeping(IntPtr ptr, byte objectType, string logPrefix)
{
switch (objectType)
{
case 0: // Server1U
case 1: // (alias)
case 2: // Server7U
case 3: // Server3U
{
try
{
var server = new Il2Cpp.Server(ptr);
server.rackPositionUID = 0;
server.currentRackPosition = null;
server.objectInHands = true;
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: server field clear failed: {ex.Message}");
}
break;
}
case 4: // NetworkSwitch
{
try
{
var sw = new Il2Cpp.NetworkSwitch(ptr);
sw.rackPositionUID = 0;
sw.currentRackPosition = null;
sw.objectInHands = true;
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: switch field clear failed: {ex.Message}");
}
break;
}
case 7: // PatchPanel
{
try
{
var pp = new Il2Cpp.PatchPanel(ptr);
pp.rackPositionUID = 0;
pp.currentRackPosition = null;
pp.objectInHands = true;
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] {logPrefix}: patchpanel field clear failed: {ex.Message}");
}
break;
}
default:
CrashLog.Log($"[WorldSync] {logPrefix}: unknown objectType={objectType}, no bookkeeping");
break;
}
}
int WorldPlaceInRackImpl(IntPtr id, uint idLen, int rackUid)
{
try
{
string objId = ReadUtf8(id, idLen);
CrashLog.Log($"[WorldSync] PlaceInRack: id={objId}, uid={rackUid}");
ulong handle = FindHandleByStableId(objId);
if (handle == 0)
{
CrashLog.Log($"[WorldSync] PlaceInRack: '{objId}' not found");
return 0;
}
var comp = ResolveComponent(handle);
if (comp == null)
{
CrashLog.Log($"[WorldSync] PlaceInRack: handle for '{objId}' resolved to null");
return 0;
}
var rackPos = FindRackPosition(rackUid);
if (rackPos == null)
{
CrashLog.Log($"[WorldSync] PlaceInRack: rack uid={rackUid} not found");
return 0;
}
var rack = rackPos.rack;
if (rack == null)
{
CrashLog.Log($"[WorldSync] PlaceInRack: rack uid={rackUid} has no parent Rack");
return 0;
}
if (!comp.gameObject.activeSelf)
comp.gameObject.SetActive(true);
var ptr = new IntPtr((long)handle);
byte guessedType = 0;
try { var s = new Il2Cpp.Server(ptr); if (!string.IsNullOrEmpty(s.ServerID)) guessedType = (byte)s.serverType; } catch { }
if (guessedType == 0)
{
try { var sw = new Il2Cpp.NetworkSwitch(ptr); if (!string.IsNullOrEmpty(sw.switchId)) guessedType = 4; } catch { }
}
if (guessedType == 0)
{
try { var pp = new Il2Cpp.PatchPanel(ptr); if (!string.IsNullOrEmpty(pp.patchPanelId)) guessedType = 7; } catch { }
}
// Preserve the object ID through InsertedInRack callbacks
string preserveId = "";
switch (guessedType)
{
case 0:
case 1:
case 2:
case 3:
try { var srv = new Il2Cpp.Server(ptr); preserveId = srv.ServerID ?? ""; } catch { }
break;
case 4:
try { var sw = new Il2Cpp.NetworkSwitch(ptr); preserveId = sw.switchId ?? ""; } catch { }
break;
case 7:
try { var pp = new Il2Cpp.PatchPanel(ptr); preserveId = pp.patchPanelId ?? ""; } catch { }
break;
}
if (!string.IsNullOrEmpty(preserveId))
{
Patch_Rack_MarkPositionAsUsed.PendingCloneRestore = (preserveId, guessedType, rackUid);
}
int sizeInU = RackInstallBookkeeping(ptr, rackPos, guessedType, "PlaceInRack");
comp.transform.SetParent(rackPos.transform, false);
comp.transform.localPosition = UnityEngine.Vector3.zero;
comp.transform.localRotation = UnityEngine.Quaternion.identity;
try
{
var rb = comp.GetComponent<UnityEngine.Rigidbody>();
if (rb != null)
{
rb.isKinematic = true;
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
}
}
catch { }
Patch_Rack_MarkPositionAsUsed.SuppressEvents = true;
try { rack.MarkPositionAsUsed(rackPos.positionIndex, sizeInU); }
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] PlaceInRack: MarkPositionAsUsed failed: {ex.Message}");
}
finally { Patch_Rack_MarkPositionAsUsed.SuppressEvents = false; }
CrashLog.Log($"[WorldSync] PlaceInRack: '{objId}' installed at uid={rackUid} OK");
return 1;
}
catch (Exception ex)
{
Patch_Rack_MarkPositionAsUsed.SuppressEvents = false;
CrashLog.LogException("WorldPlaceInRackImpl", ex);
return 0;
}
}
int WorldRemoveFromRackImpl(IntPtr id, uint idLen)
{
try
{
string objId = ReadUtf8(id, idLen);
CrashLog.Log($"[WorldSync] RemoveFromRack: id={objId}");
// ── 1. Find the object ──────────────────────────────────────────
ulong handle = FindHandleByStableId(objId);
if (handle == 0)
{
CrashLog.Log($"[WorldSync] RemoveFromRack: '{objId}' not found");
return 0;
}
var comp = ResolveComponent(handle);
if (comp == null) return 0;
var ptr = new IntPtr((long)handle);
byte guessedType = 0;
try { var s = new Il2Cpp.Server(ptr); if (!string.IsNullOrEmpty(s.ServerID)) guessedType = (byte)s.serverType; } catch { }
if (guessedType == 0)
{
try { var sw = new Il2Cpp.NetworkSwitch(ptr); if (!string.IsNullOrEmpty(sw.switchId)) guessedType = 4; } catch { }
}
if (guessedType == 0)
{
try { var pp = new Il2Cpp.PatchPanel(ptr); if (!string.IsNullOrEmpty(pp.patchPanelId)) guessedType = 7; } catch { }
}
// Clear installed objects tracking before uninstall bookkeeping
try
{
int removeUid = -1;
switch (guessedType)
{
case 0:
case 1:
case 2:
case 3:
try { var s2 = new Il2Cpp.Server(ptr); removeUid = s2.currentRackPosition != null ? s2.currentRackPosition.rackPosGlobalUID : s2.rackPositionUID; } catch { }
break;
case 4:
try { var sw2 = new Il2Cpp.NetworkSwitch(ptr); removeUid = sw2.currentRackPosition != null ? sw2.currentRackPosition.rackPosGlobalUID : sw2.rackPositionUID; } catch { }
break;
case 7:
try { var pp2 = new Il2Cpp.PatchPanel(ptr); removeUid = pp2.currentRackPosition != null ? pp2.currentRackPosition.rackPosGlobalUID : pp2.rackPositionUID; } catch { }
break;
}
if (removeUid > 0) Patch_Rack_MarkPositionAsUsed.RemoveInstalledObject(removeUid);
}
catch { }
RackUninstallBookkeeping(ptr, guessedType, "RemoveFromRack");
// ── 3. Reparent to world ────────────────────────────────────────
try
{
var mgr = Il2Cpp.MainGameManager.instance;
if (mgr != null && mgr.parentUsableObjects != null)
comp.transform.SetParent(mgr.parentUsableObjects, true);
}
catch { }
// ── 4. Re-enable physics ────────────────────────────────────────
try
{
var rb = comp.GetComponent<UnityEngine.Rigidbody>();
if (rb != null)
{
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
UnityEngine.Physics.SyncTransforms();
rb.isKinematic = false;
rb.useGravity = true;
rb.WakeUp();
}
}
catch { }
CrashLog.Log($"[WorldSync] RemoveFromRack: '{objId}' removed OK");
return 1;
}
catch (Exception ex)
{
CrashLog.LogException("WorldRemoveFromRackImpl", ex);
return 0;
}
}
int WorldSetPowerImpl(IntPtr id, uint idLen, byte isOn)
{
// Phase 3 stub
string objId = ReadUtf8(id, idLen);
CrashLog.Log($"[WorldSync] SetPower stub: id={objId}, on={isOn}");
return 0;
}
int WorldSetPropertyImpl(IntPtr id, uint idLen, IntPtr key, uint keyLen, IntPtr val, uint valLen)
{
// Phase 3 stub
return 0;
}
int WorldConnectCableImpl(int cableId, byte startType, float sx, float sy, float sz, IntPtr startDevice, uint startDeviceLen, byte endType, float ex, float ey, float ez, IntPtr endDevice, uint endDeviceLen)
{
// Phase 3 stub
CrashLog.Log($"[WorldSync] ConnectCable stub: cableId={cableId}");
return 0;
}
int WorldDisconnectCableImpl(int cableId)
{
// Phase 3 stub
CrashLog.Log($"[WorldSync] DisconnectCable stub: cableId={cableId}");
return 0;
}
int WorldPickupObjectImpl(IntPtr id, uint idLen)
{
try
{
string objId = ReadUtf8(id, idLen);
CrashLog.Log($"[WorldSync] PickupObject: id={objId}");
ulong handle = FindHandleByStableId(objId);
if (handle == 0)
{
CrashLog.Log($"[WorldSync] PickupObject: '{objId}' not found");
return 0;
}
int result = ObjSetActiveImpl(handle, 0);
CrashLog.Log($"[WorldSync] PickupObject: deactivated '{objId}' → {result}");
return result;
}
catch (Exception ex)
{
CrashLog.LogException("WorldPickupObjectImpl", ex);
return 0;
}
}
int WorldDropObjectImpl(IntPtr id, uint idLen, float x, float y, float z, float rotX, float rotY, float rotZ, float rotW)
{
try
{
string objId = ReadUtf8(id, idLen);
CrashLog.Log($"[WorldSync] DropObject: id={objId}, pos=({x:F1},{y:F1},{z:F1})");
ulong handle = FindHandleByStableId(objId);
if (handle == 0)
{
CrashLog.Log($"[WorldSync] DropObject: '{objId}' not found");
return 0;
}
var comp = ResolveComponent(handle);
if (comp == null)
{
CrashLog.Log($"[WorldSync] DropObject: component null for '{objId}'");
return 0;
}
// ── 1. Lock physics WHILE STILL INACTIVE ────────────────────────
// GetComponent works on inactive GameObjects in Unity/IL2CPP.
// Setting isKinematic=true before SetActive() means the Rigidbody
// enters the physics world as kinematic on the very first tick —
// no velocity, no forces, no depenetration.
var rb = comp.GetComponent<UnityEngine.Rigidbody>();
if (rb != null)
{
rb.isKinematic = true;
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
}
// ── 2. Clear objectInHands BEFORE activation ─────────────────────
// RackUninstallBookkeeping sets objectInHands = true whenever an
// object is removed from a rack. On the remote client the native
// drop logic never runs, so this flag is never cleared. If it is
// still true when SetActive(true) fires, the game's own scripts
// apply carry-follow forces on the very first Update tick — that
// is what flings the object across the room in the rack→carry→drop
// scenario while the plain floor→carry→drop case works fine.
TryResetObjectInHands(handle);
// ── 3. Teleport & reparent WHILE STILL INACTIVE ─────────────────
// Moving the transform before SetActive() is the critical fix:
// a) The game's own OnEnable callbacks fire at the DROP position,
// not at the stale rack-slot position.
// b) PhysX never sees the body at the rack slot — it goes straight
// from "not in simulation" to "at drop position, kinematic".
// Both of these prevent the object being flung across the room.
ObjSetParentToWorldImpl(handle);
comp.transform.position = new UnityEngine.Vector3(x, y, z);
comp.transform.rotation = new UnityEngine.Quaternion(rotX, rotY, rotZ, rotW);
// ── 4. Activate at the correct position ─────────────────────────
// OnEnable fires here; transform is already at the drop target.
ObjSetActiveImpl(handle, 1);
// ── 5. Flush new position into PhysX broadphase ─────────────────
// Ensures the engine's AABB / contact cache reflects the new
// world position before we release kinematic.
UnityEngine.Physics.SyncTransforms();
// ── 6. Release physics ───────────────────────────────────────────
if (rb != null)
{
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
rb.isKinematic = false;
rb.useGravity = true;
rb.WakeUp();
}
else
{
RbSetKinematicImpl(handle, 0);
RbSetGravityImpl(handle, 1);
RbWakeUpImpl(handle);
}
CrashLog.Log($"[WorldSync] DropObject: reactivated '{objId}' at ({x:F1},{y:F1},{z:F1})");
return 1;
}
catch (Exception ex)
{
CrashLog.LogException("WorldDropObjectImpl", ex);
return 0;
}
}
/// <summary>
/// Clears the <c>objectInHands</c> flag that
/// <see cref="RackUninstallBookkeeping"/> sets to <c>true</c> when an
/// object is removed from a rack. On the remote client this flag is
/// never cleared by the native drop logic, so we must clear it here
/// before calling SetActive(true). If left as <c>true</c>, the game's
/// own MonoBehaviour scripts apply carry-follow forces to the object on
/// the very first Update tick after activation, flinging it away.
/// </summary>
private static void TryResetObjectInHands(ulong handle)
{
var ptr = new IntPtr((long)handle);
// Try Server types first
try
{
var srv = new Il2Cpp.Server(ptr);
if (!string.IsNullOrEmpty(srv.ServerID))
{
srv.objectInHands = false;
CrashLog.Log($"[WorldSync] TryResetObjectInHands: cleared Server '{srv.ServerID}'");
return;
}
}
catch { }
// Try NetworkSwitch
try
{
var sw = new Il2Cpp.NetworkSwitch(ptr);
if (!string.IsNullOrEmpty(sw.switchId))
{
sw.objectInHands = false;
CrashLog.Log($"[WorldSync] TryResetObjectInHands: cleared NetworkSwitch '{sw.switchId}'");
return;
}
}
catch { }
// Try PatchPanel
try
{
var pp = new Il2Cpp.PatchPanel(ptr);
if (!string.IsNullOrEmpty(pp.patchPanelId))
{
pp.objectInHands = false;
CrashLog.Log($"[WorldSync] TryResetObjectInHands: cleared PatchPanel '{pp.patchPanelId}'");
}
}
catch { }
}
int WorldEnsureRackUIDsImpl()
{
try
{
return GameHooks.EnsureAllRackPositionUIDs();
}
catch (Exception ex)
{
CrashLog.LogException("WorldEnsureRackUIDsImpl", ex);
return 0;
}
}
private static UnityEngine.Component ResolveComponent(ulong handle)
{
try
{
if (handle == 0) return null;
var ptr = new IntPtr((long)handle);
var comp = new UnityEngine.Component(ptr);
if (comp == null || comp.gameObject == null) return null;
return comp;
}
catch { return null; }
}
uint ObjFindByTypeImpl(byte typeId, IntPtr outHandles, uint max)
{
try
{
uint count = 0;
switch (typeId)
{
case 0: // Server
{
var all = UnityEngine.Resources.FindObjectsOfTypeAll<Server>();
foreach (var srv in all)
{
try
{
if (srv.gameObject.scene.name == null) continue;
if (count >= max) break;
Marshal.WriteInt64(outHandles, (int)(count * 8), srv.Pointer.ToInt64());
count++;
}
catch { }
}
break;
}
case 4: // NetworkSwitch
{
var all = UnityEngine.Resources.FindObjectsOfTypeAll<NetworkSwitch>();
foreach (var sw in all)
{
try
{
if (sw.gameObject.scene.name == null) continue;
if (count >= max) break;
Marshal.WriteInt64(outHandles, (int)(count * 8), sw.Pointer.ToInt64());
count++;
}
catch { }
}
break;
}
}
return count;
}
catch (Exception ex)
{
CrashLog.LogException("ObjFindByTypeImpl", ex);
return 0;
}
}
uint ObjGetStringFieldImpl(ulong handle, ushort fieldId, IntPtr outBuf, uint max)
{
try
{
if (handle == 0 || outBuf == IntPtr.Zero || max == 0) return 0;
var ptr = new IntPtr((long)handle);
string value = "";
switch (fieldId)
{
case 0: // ServerId
try { var srv = new Server(ptr); value = srv?.ServerID ?? ""; } catch { }
break;
case 1: // SwitchId
try { var sw = new NetworkSwitch(ptr); value = sw?.switchId ?? ""; } catch { }
break;
case 2: // RACK_POSITION_UID
{
// Try each type, but filter negative values which indicate
// we're reading the wrong Il2Cpp field offset (type confusion).
int bestUid = 0;
try { var srv = new Server(ptr); int uid = srv.rackPositionUID; if (uid > 0) bestUid = uid; } catch { }
if (bestUid == 0)
{
try { var sw = new NetworkSwitch(ptr); int uid = sw.rackPositionUID; if (uid > 0) bestUid = uid; } catch { }
}
if (bestUid == 0)
{
try { var pp = new PatchPanel(ptr); int uid = pp.rackPositionUID; if (uid > 0) bestUid = uid; } catch { }
}
value = bestUid.ToString();
}
break;
case 3: // GameObjectName
try { var comp = new UnityEngine.Component(ptr); value = comp?.gameObject?.name ?? ""; } catch { }
break;
case 4: // PatchPanelId
try { var pp = new PatchPanel(ptr); value = pp?.patchPanelId ?? ""; } catch { }
break;
}
if (string.IsNullOrEmpty(value)) return 0;
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(value);
int len = System.Math.Min(bytes.Length, (int)max);
Marshal.Copy(bytes, 0, outBuf, len);
return (uint)len;
}
catch (Exception ex)
{
CrashLog.LogException("ObjGetStringFieldImpl", ex);
return 0;
}
}
int ObjSetStringFieldImpl(ulong handle, ushort fieldId, IntPtr value, uint valueLen)
{
try
{
if (handle == 0) return 0;
var ptr = new IntPtr((long)handle);
string newValue = "";
if (value != IntPtr.Zero && valueLen > 0)
{
byte[] buf = new byte[valueLen];
Marshal.Copy(value, buf, 0, (int)valueLen);
newValue = System.Text.Encoding.UTF8.GetString(buf);
}
switch (fieldId)
{
case 0: // ServerID
try { var srv = new Server(ptr); srv.ServerID = newValue; return 1; } catch { }
break;
case 1: // SwitchId
try { var sw = new NetworkSwitch(ptr); sw.switchId = newValue; return 1; } catch { }
break;
case 4: // PatchPanelId
try { var pp = new PatchPanel(ptr); pp.patchPanelId = newValue; return 1; } catch { }
break;
}
return 0;
}
catch (Exception ex)
{
CrashLog.LogException("ObjSetStringFieldImpl", ex);
return 0;
}
}
int ObjIsActiveImpl(ulong handle)
{
try
{
var comp = ResolveComponent(handle);
return (comp != null && comp.gameObject.activeSelf) ? 1 : 0;
}
catch { return 0; }
}
int ObjSetActiveImpl(ulong handle, int active)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
comp.gameObject.SetActive(active != 0);
return 1;
}
catch { return 0; }
}
int ObjGetPositionImpl(ulong handle, IntPtr outX, IntPtr outY, IntPtr outZ)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
var pos = comp.transform.position;
Marshal.Copy(new float[] { pos.x }, 0, outX, 1);
Marshal.Copy(new float[] { pos.y }, 0, outY, 1);
Marshal.Copy(new float[] { pos.z }, 0, outZ, 1);
return 1;
}
catch { return 0; }
}
int ObjSetPositionImpl(ulong handle, float x, float y, float z)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
comp.transform.position = new UnityEngine.Vector3(x, y, z);
return 1;
}
catch { return 0; }
}
int ObjSetRotationImpl(ulong handle, float x, float y, float z, float w)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
comp.transform.rotation = new UnityEngine.Quaternion(x, y, z, w);
return 1;
}
catch { return 0; }
}
int ObjSetParentToWorldImpl(ulong handle)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
var mgr = MainGameManager.instance;
if (mgr != null && mgr.parentUsableObjects != null)
{
comp.transform.SetParent(mgr.parentUsableObjects, true);
return 1;
}
return 0;
}
catch { return 0; }
}
int RbSetKinematicImpl(ulong handle, int kinematic)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
var rb = comp.GetComponent<UnityEngine.Rigidbody>();
if (rb == null) return 0;
rb.isKinematic = (kinematic != 0);
return 1;
}
catch { return 0; }
}
int RbSetGravityImpl(ulong handle, int useGravity)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
var rb = comp.GetComponent<UnityEngine.Rigidbody>();
if (rb == null) return 0;
rb.useGravity = (useGravity != 0);
return 1;
}
catch { return 0; }
}
int RbWakeUpImpl(ulong handle)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
var rb = comp.GetComponent<UnityEngine.Rigidbody>();
if (rb == null) return 0;
rb.velocity = UnityEngine.Vector3.zero;
rb.angularVelocity = UnityEngine.Vector3.zero;
rb.WakeUp();
return 1;
}
catch { return 0; }
}
ulong ObjFindByIdImpl(byte typeId, ushort fieldId, IntPtr id, uint idLen)
{
try
{
if (id == IntPtr.Zero || idLen == 0) return 0;
byte[] buf = new byte[idLen];
Marshal.Copy(id, buf, 0, (int)idLen);
int end = Array.IndexOf(buf, (byte)0);
if (end < 0) end = (int)idLen;
string targetId = System.Text.Encoding.UTF8.GetString(buf, 0, end).Trim();
if (string.IsNullOrEmpty(targetId)) return 0;
switch (typeId)
{
case 0: // Server
{
foreach (var srv in UnityEngine.Resources.FindObjectsOfTypeAll<Server>())
{
try
{
if (srv.gameObject.scene.name == null) continue;
string val = fieldId switch
{
0 => srv.ServerID ?? "",
2 => srv.rackPositionUID.ToString(),
3 => srv.gameObject.name ?? "",
_ => ""
};
if (val == targetId) return (ulong)srv.Pointer.ToInt64();
}
catch { }
}
// Lookup failed — dump all known servers so we can see if ID mismatch
try
{
var all = UnityEngine.Resources.FindObjectsOfTypeAll<Server>();
var sb = new System.Text.StringBuilder();
sb.Append($"[FindById] Server '{targetId}' not found. Scene servers ({all.Count}): ");
foreach (var srv in all)
{
try
{
bool inScene = srv.gameObject.scene.name != null;
string sid = srv.ServerID ?? "<null>";
bool active = srv.gameObject.activeInHierarchy;
sb.Append($"[id={sid} active={active} inScene={inScene}] ");
}
catch { sb.Append("[err] "); }
}
CrashLog.Log(sb.ToString());
}
catch { }
break;
}
case 4: // NetworkSwitch
{
foreach (var sw in UnityEngine.Resources.FindObjectsOfTypeAll<NetworkSwitch>())
{
try
{
if (sw.gameObject.scene.name == null) continue;
string val = fieldId switch
{
1 => sw.switchId ?? "",
3 => sw.gameObject.name ?? "",
_ => ""
};
if (val == targetId) return (ulong)sw.Pointer.ToInt64();
}
catch { }
}
try
{
var all = UnityEngine.Resources.FindObjectsOfTypeAll<NetworkSwitch>();
var sb = new System.Text.StringBuilder();
sb.Append($"[FindById] NetworkSwitch '{targetId}' not found. Scene switches ({all.Count}): ");
foreach (var sw in all)
{
try
{
bool inScene = sw.gameObject.scene.name != null;
string sid = sw.switchId ?? "<null>";
bool active = sw.gameObject.activeInHierarchy;
sb.Append($"[id={sid} active={active} inScene={inScene}] ");
}
catch { sb.Append("[err] "); }
}
CrashLog.Log(sb.ToString());
}
catch { }
break;
}
case 7: // PatchPanel
{
foreach (var pp in UnityEngine.Resources.FindObjectsOfTypeAll<PatchPanel>())
{
try
{
if (pp.gameObject.scene.name == null) continue;
string val = fieldId switch
{
4 => pp.patchPanelId ?? "",
3 => pp.gameObject.name ?? "",
_ => ""
};
if (val == targetId) return (ulong)pp.Pointer.ToInt64();
}
catch { }
}
// Lookup failed dump
try
{
var all = UnityEngine.Resources.FindObjectsOfTypeAll<PatchPanel>();
var sb = new System.Text.StringBuilder();
sb.Append($"[FindById] PatchPanel '{targetId}' not found. Scene panels ({all.Count}): ");
foreach (var pp in all)
{
try
{
bool inScene = pp.gameObject.scene.name != null;
string pid = pp.patchPanelId ?? "<null>";
bool active = pp.gameObject.activeInHierarchy;
sb.Append($"[id={pid} active={active} inScene={inScene}] ");
}
catch { sb.Append("[err] "); }
}
CrashLog.Log(sb.ToString());
}
catch { }
break;
}
}
return 0;
}
catch (Exception ex)
{
CrashLog.LogException("ObjFindByIdImpl", ex);
return 0;
}
}
int GetHeldObjectImpl(IntPtr outId, uint idMax, IntPtr outType)
{
try
{
string id = Patch_UsableObject_InteractOnClick.GetHeldObjectId();
byte objType = Patch_UsableObject_InteractOnClick.GetHeldObjectType();
if (string.IsNullOrEmpty(id) || outId == IntPtr.Zero || idMax == 0) return 0;
if (outType != IntPtr.Zero) Marshal.WriteByte(outType, objType);
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(id);
int len = System.Math.Min(bytes.Length, (int)idMax);
Marshal.Copy(bytes, 0, outId, len);
return len;
}
catch (Exception ex)
{
CrashLog.LogException("GetHeldObjectImpl", ex);
return 0;
}
}
int ObjGetRotationImpl(ulong handle, IntPtr outX, IntPtr outY, IntPtr outZ, IntPtr outW)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
var rot = comp.transform.rotation;
Marshal.Copy(new float[] { rot.x }, 0, outX, 1);
Marshal.Copy(new float[] { rot.y }, 0, outY, 1);
Marshal.Copy(new float[] { rot.z }, 0, outZ, 1);
Marshal.Copy(new float[] { rot.w }, 0, outW, 1);
return 1;
}
catch { return 0; }
}
// ── v17: handle-based rack operations + generic transform primitives ──
int ObjSetParentImpl(ulong child, ulong parent)
{
try
{
var childComp = ResolveComponent(child);
var parentComp = ResolveComponent(parent);
if (childComp == null || parentComp == null) return 0;
childComp.transform.SetParent(parentComp.transform, false);
return 1;
}
catch { return 0; }
}
int ObjSetLocalPositionImpl(ulong handle, float x, float y, float z)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
comp.transform.localPosition = new UnityEngine.Vector3(x, y, z);
return 1;
}
catch { return 0; }
}
int ObjSetLocalRotationImpl(ulong handle, float x, float y, float z, float w)
{
try
{
var comp = ResolveComponent(handle);
if (comp == null) return 0;
comp.transform.localRotation = new UnityEngine.Quaternion(x, y, z, w);
return 1;
}
catch { return 0; }
}
ulong RackFindPositionImpl(int rackUid)
{
try
{
var rackPos = FindRackPosition(rackUid);
if (rackPos == null) return 0;
return (ulong)rackPos.Pointer.ToInt64();
}
catch (Exception ex)
{
CrashLog.LogException("RackFindPositionImpl", ex);
return 0;
}
}
int RackGameInstallImpl(ulong objHandle, ulong rackPosHandle, byte objectType)
{
try
{
var rackPos = new Il2Cpp.RackPosition(new IntPtr((long)rackPosHandle));
if (rackPos == null) return 0;
var rack = rackPos.rack;
if (rack == null)
{
CrashLog.Log($"[WorldSync] RackGameInstall: rackPos has no parent Rack");
return 0;
}
var ptr = new IntPtr((long)objHandle);
Patch_Rack_MarkPositionAsUsed.SuppressEvents = true;
try
{
// Read the current object ID BEFORE calling InsertedInRack
// so the Harmony postfix can restore it if the game renames it
string objectId = "";
int rackPosUid = rackPos.rackPosGlobalUID;
switch (objectType)
{
case 0:
case 1:
case 2:
case 3:
try { var srv = new Il2Cpp.Server(ptr); objectId = srv.ServerID ?? ""; } catch { }
break;
case 4:
try { var sw = new Il2Cpp.NetworkSwitch(ptr); objectId = sw.switchId ?? ""; } catch { }
break;
case 7:
try { var pp = new Il2Cpp.PatchPanel(ptr); objectId = pp.patchPanelId ?? ""; } catch { }
break;
}
if (!string.IsNullOrEmpty(objectId))
{
Patch_Rack_MarkPositionAsUsed.PendingCloneRestore = (objectId, objectType, rackPosUid);
}
int sizeInU = RackInstallBookkeeping(ptr, rackPos, objectType, "RackGameInstall");
// Mark position as used
rack.MarkPositionAsUsed(rackPos.positionIndex, sizeInU);
CrashLog.Log($"[WorldSync] RackGameInstall: installed at uid={rackPos.rackPosGlobalUID} OK");
return 1;
}
catch (Exception ex)
{
CrashLog.Log($"[WorldSync] RackGameInstall: failed: {ex.Message}");
return 0;
}
finally
{
Patch_Rack_MarkPositionAsUsed.SuppressEvents = false;
}
}
catch (Exception ex)
{
Patch_Rack_MarkPositionAsUsed.SuppressEvents = false;
CrashLog.LogException("RackGameInstallImpl", ex);
return 0;
}
}
int RackGameUninstallImpl(ulong objHandle, byte objectType)
{
try
{
var ptr = new IntPtr((long)objHandle);
Il2Cpp.RackPosition savedRackPos = null;
int savedSizeInU = 1;
try
{
switch (objectType)
{
case 0:
case 1:
case 2:
case 3: // Server types
{
var srv = new Il2Cpp.Server(ptr);
savedRackPos = srv.currentRackPosition;
savedSizeInU = srv.sizeInU > 0 ? srv.sizeInU : 1;
}
break;
case 4: // NetworkSwitch
{
var sw = new Il2Cpp.NetworkSwitch(ptr);
savedRackPos = sw.currentRackPosition;
savedSizeInU = sw.sizeInU > 0 ? sw.sizeInU : 1;
}
break;
case 7: // PatchPanel
{
var pp = new Il2Cpp.PatchPanel(ptr);
savedRackPos = pp.currentRackPosition;
savedSizeInU = pp.sizeInU > 0 ? pp.sizeInU : 1;
}
break;
}
}
catch (Exception ex) { CrashLog.Log($"[WorldSync] RackGameUninstall: failed to save rack pos: {ex.Message}"); }
RackUninstallBookkeeping(ptr, objectType, "RackGameUninstall");
// Clear the installed objects tracking for this rack position
if (savedRackPos != null)
{
try
{
int posUid = savedRackPos.rackPosGlobalUID;
if (posUid > 0)
Patch_Rack_MarkPositionAsUsed.RemoveInstalledObject(posUid);
}
catch { }
}
if (savedRackPos != null)
{
try
{
var rack = savedRackPos.rack;
if (rack != null)
{
int startIndex = savedRackPos.positionIndex;
CrashLog.Log($"[WorldSync] RackGameUninstall: IsPositionAvailable({startIndex}, {savedSizeInU}) = {rack.IsPositionAvailable(startIndex, savedSizeInU)} before freeing");
rack.MarkPositionAsUnused(startIndex, savedSizeInU);
CrashLog.Log($"[WorldSync] RackGameUninstall: freed {savedSizeInU} position(s) starting at index {startIndex}");
}
}
catch (Exception ex) { CrashLog.Log($"[WorldSync] RackGameUninstall: rack position free failed: {ex.Message}"); }
}
CrashLog.Log($"[WorldSync] RackGameUninstall: cleared rack fields OK");
return 1;
}
catch (Exception ex)
{
CrashLog.LogException("RackGameUninstallImpl", ex);
return 0;
}
}
public void Dispose()
{
if (_tablePtr != IntPtr.Zero) { Marshal.FreeHGlobal(_tablePtr); _tablePtr = IntPtr.Zero; }
if (_currentScenePtr != IntPtr.Zero) { Marshal.FreeHGlobal(_currentScenePtr); _currentScenePtr = IntPtr.Zero; }
if (_friendNamePtr != IntPtr.Zero) { Marshal.FreeHGlobal(_friendNamePtr); _friendNamePtr = IntPtr.Zero; }
if (_lobbyDataPtr != IntPtr.Zero) { Marshal.FreeHGlobal(_lobbyDataPtr); _lobbyDataPtr = IntPtr.Zero; }
GC.SuppressFinalize(this);
}
}