mirror of
https://github.com/misternebula/quantum-space-buddies.git
synced 2025-03-14 22:20:51 +00:00
commit
576011da22
107
DEVELOPMENT.md
107
DEVELOPMENT.md
@ -1,107 +0,0 @@
|
||||
> [!WARNING]
|
||||
> Mod development needs a powerful PC!\
|
||||
> Unexpected errors and issues may occur when editing networking code.\
|
||||
> Running multiple instances of the game can be very taxing on your computer.\
|
||||
> We're not responsible if you push your PC too hard.
|
||||
|
||||
## Prerequisites
|
||||
- Visual Studio 2022.
|
||||
- Epic or Steam version of Outer Wilds.
|
||||
- Keyboard with numpad for in-game debug actions.
|
||||
|
||||
We recommend using the Outer Wilds Mod Manager, but you can use OWML on its own if you want.
|
||||
|
||||
## Cloning and configuration
|
||||
- Clone QSB's source code.
|
||||
- Copy the file `DevEnv.template.targets` and rename it to `DevEnv.targets`.
|
||||
- In `DevEnv.targets`, edit the entry for `<OwmlDir>` to point to your installation of OWML. This should be the folder named `OWML`. If using the manager, you can find this directory by :
|
||||
- Legacy Manager : Press the "Mods Directory" button and go up a folder.
|
||||
- New Manager : Press the "..." button at the top, and select "Show OWML Folder".
|
||||
- `QSB.sln` should now be ready to open. ***This solution needs to be opened with Visual Studio 2022 or higher!***
|
||||
|
||||
## Multiple instances on Steam
|
||||
If using the Steam version of Outer Wilds, you will need to create a file to allow you to run multiple instances of the game.
|
||||
- Navigate to your game install folder. You can find this by right-clicking on the game in Steam, and going `Manage > Browse local files`.
|
||||
- Create a file named `steam_appid.txt`.
|
||||
- In this file, write `753640` and save. This file will override some Steam DRM features and allow the game to be ran multiple times at once.
|
||||
- Either turn on "Force Exe" in the mod manager, or run OuterWilds.exe directly.
|
||||
|
||||
## Building
|
||||
Simply build the solution normally. (`Build > Build Solution` or CTRL-SHIFT-B)
|
||||
|
||||
The files will automatically be copied over to your OWML installation and be ready to play - no DLL copying needed.
|
||||
|
||||
For documentation reasons, here is the build flow :
|
||||
|
||||
- MirrorWeaver is built.
|
||||
- EpicOnlineTransport is built.
|
||||
- EpicRerouter is built.
|
||||
- QSB is built.
|
||||
- Any `.exe.config` files are removed from the build.
|
||||
- QSB.dll is processed ("weaved") by MirrorWeaver. This injects all the boilerplate code that Mirror needs to function.
|
||||
- If needed/possible, any `.dll` or `.exe` files are copied to the Unity project.
|
||||
|
||||
## API
|
||||
|
||||
Use the API by copying [the API definition](https://github.com/misternebula/quantum-space-buddies/blob/master/QSB/API/IQSBAPI.cs) into your mod.
|
||||
|
||||
## Debugging
|
||||
### Debug Actions :
|
||||
|
||||
> [!NOTE]
|
||||
> this list is slightly outdated. it will be updated when debug settings are updated
|
||||
|
||||
Press Q + Numpad Enter to toggle debug mode in game (corresponds with the debug setting "debugMode" in the section below).
|
||||
|
||||
Hold Q and press :
|
||||
|
||||
- Numpad 1 - Teleport to nearest player.
|
||||
- Numpad 2 - If holding LeftShift, warp to the dreamworld Vault fire. If not, warp to the Endless Canyon. If already in dreamworld, pick up lantern.
|
||||
- Numpad 3 - Unlock the Sealed Vault.
|
||||
- Numpad 4 - Damage the ship's electrical system.
|
||||
- Numpad 5 - Trigger the supernova.
|
||||
- Numpad 6 - Set the flags for having met Solanum and the Prisoner.
|
||||
- Numpad 7 - Warp to the Vessel and insert the warp core.
|
||||
- Numpad 8 - Spawn a fake player. For Ghostbuster testing.
|
||||
- Numpad 9 - If holding LeftShift, load the SolarSystem scene. If not, load the EyeOfTheUniverse scene.
|
||||
- Numpad 0 - Revive a random dead player.
|
||||
|
||||
### Debug Settings :
|
||||
|
||||
> [!NOTE]
|
||||
> this list is slightly outdated because it will be replaced by mod options at some point
|
||||
|
||||
Create a file called `debugsettings.json` in the QSB folder.
|
||||
The template for this file is this :
|
||||
|
||||
```json
|
||||
{
|
||||
"instanceIdInLogs": false,
|
||||
"hookDebugLogs": false,
|
||||
"avoidTimeSync": false,
|
||||
"autoStart": false,
|
||||
"kickEveryone": false,
|
||||
"disableLoopDeath": false,
|
||||
"timeout": 25,
|
||||
"debugMode": false,
|
||||
"drawGui": false,
|
||||
"drawLines": false,
|
||||
"drawLabels": false,
|
||||
"drawGhostAI": false,
|
||||
"greySkybox": false
|
||||
}
|
||||
```
|
||||
|
||||
- instanceIdInLogs - Appends the game instance id to every log message sent.
|
||||
- hookDebugLogs - Print Unity logs and warnings.
|
||||
- avoidTimeSync - Disables the syncing of time.
|
||||
- autoStart - Host/connect automatically for faster testing.
|
||||
- kickEveryone - Kick anyone who joins a game.
|
||||
- disableLoopDeath - Make it so the loop doesn't end when everyone is dead.
|
||||
- timeout - How many seconds for your connection to timeout, in seconds.
|
||||
- debugMode - Enables debug mode. If this is set to `false`, none of the following settings do anything.
|
||||
- drawGui - Draws a GUI at the top of the screen that gives information on many things.
|
||||
- drawLines - Draws gizmo-esque lines around things. Indicates reference sectors/transforms, triggers, etc. LAGGY.
|
||||
- drawLabels - Draws GUI labels attached to some objects. LAGGY.
|
||||
- drawGhostAI - Draws debug lines and labels just for the ghosts.
|
||||
- greySkybox - Turns the skybox grey. Useful in the Eye, where it's pretty dark.
|
@ -1,89 +0,0 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public class BidirectionalDictionary<T1, T2> : IEnumerable
|
||||
{
|
||||
private Dictionary<T1, T2> t1ToT2Dict = new Dictionary<T1, T2>();
|
||||
private Dictionary<T2, T1> t2ToT1Dict = new Dictionary<T2, T1>();
|
||||
|
||||
public IEnumerable<T1> FirstTypes => t1ToT2Dict.Keys;
|
||||
public IEnumerable<T2> SecondTypes => t2ToT1Dict.Keys;
|
||||
|
||||
public IEnumerator GetEnumerator() => t1ToT2Dict.GetEnumerator();
|
||||
|
||||
public int Count => t1ToT2Dict.Count;
|
||||
|
||||
public void Add(T1 key, T2 value)
|
||||
{
|
||||
if (t1ToT2Dict.ContainsKey(key))
|
||||
{
|
||||
Remove(key);
|
||||
}
|
||||
|
||||
t1ToT2Dict[key] = value;
|
||||
t2ToT1Dict[value] = key;
|
||||
}
|
||||
|
||||
public void Add(T2 key, T1 value)
|
||||
{
|
||||
if (t2ToT1Dict.ContainsKey(key))
|
||||
{
|
||||
Remove(key);
|
||||
}
|
||||
|
||||
t2ToT1Dict[key] = value;
|
||||
t1ToT2Dict[value] = key;
|
||||
}
|
||||
|
||||
public T2 Get(T1 key) => t1ToT2Dict[key];
|
||||
|
||||
public T1 Get(T2 key) => t2ToT1Dict[key];
|
||||
|
||||
public bool TryGetValue(T1 key, out T2 value) => t1ToT2Dict.TryGetValue(key, out value);
|
||||
|
||||
public bool TryGetValue(T2 key, out T1 value) => t2ToT1Dict.TryGetValue(key, out value);
|
||||
|
||||
public bool Contains(T1 key) => t1ToT2Dict.ContainsKey(key);
|
||||
|
||||
public bool Contains(T2 key) => t2ToT1Dict.ContainsKey(key);
|
||||
|
||||
public void Remove(T1 key)
|
||||
{
|
||||
if (Contains(key))
|
||||
{
|
||||
T2 val = t1ToT2Dict[key];
|
||||
t1ToT2Dict.Remove(key);
|
||||
t2ToT1Dict.Remove(val);
|
||||
}
|
||||
}
|
||||
public void Remove(T2 key)
|
||||
{
|
||||
if (Contains(key))
|
||||
{
|
||||
T1 val = t2ToT1Dict[key];
|
||||
t1ToT2Dict.Remove(val);
|
||||
t2ToT1Dict.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public T1 this[T2 key]
|
||||
{
|
||||
get => t2ToT1Dict[key];
|
||||
set
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
public T2 this[T1 key]
|
||||
{
|
||||
get => t1ToT2Dict[key];
|
||||
set
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,311 +0,0 @@
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
[HelpURL("https://github.com/Chykary/FizzySteamworks")]
|
||||
public class FizzySteamworks : Transport
|
||||
{
|
||||
private const string STEAM_SCHEME = "steam";
|
||||
|
||||
private static IClient client;
|
||||
private static IServer server;
|
||||
|
||||
[SerializeField]
|
||||
public EP2PSend[] Channels = new EP2PSend[2] { EP2PSend.k_EP2PSendReliable, EP2PSend.k_EP2PSendUnreliableNoDelay };
|
||||
|
||||
[Tooltip("Timeout for connecting in seconds.")]
|
||||
public int Timeout = 25;
|
||||
[Tooltip("Allow or disallow P2P connections to fall back to being relayed through the Steam servers if a direct connection or NAT-traversal cannot be established.")]
|
||||
public bool AllowSteamRelay = true;
|
||||
|
||||
[Tooltip("Use SteamSockets instead of the (deprecated) SteamNetworking. This will always use Relay.")]
|
||||
public bool UseNextGenSteamNetworking = true;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
Debug.Assert(Channels != null && Channels.Length > 0, "No channel configured for FizzySteamworks.");
|
||||
Invoke(nameof(InitRelayNetworkAccess), 1f);
|
||||
}
|
||||
|
||||
public override void ClientEarlyUpdate()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
client?.ReceiveData();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ServerEarlyUpdate()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
server?.ReceiveData();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ClientLateUpdate()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
client?.FlushData();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ServerLateUpdate()
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
server?.FlushData();
|
||||
}
|
||||
}
|
||||
|
||||
public override bool ClientConnected() => ClientActive() && client.Connected;
|
||||
public override void ClientConnect(string address)
|
||||
{
|
||||
try
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
|
||||
#else
|
||||
SteamNetworkingUtils.InitRelayNetworkAccess();
|
||||
#endif
|
||||
|
||||
InitRelayNetworkAccess();
|
||||
|
||||
if (ServerActive())
|
||||
{
|
||||
Debug.LogError("Transport already running as server!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ClientActive() || client.Error)
|
||||
{
|
||||
if (UseNextGenSteamNetworking)
|
||||
{
|
||||
Debug.Log($"Starting client [SteamSockets], target address {address}.");
|
||||
client = NextClient.CreateClient(this, address);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Starting client [DEPRECATED SteamNetworking], target address {address}. Relay enabled: {AllowSteamRelay}");
|
||||
SteamNetworking.AllowP2PPacketRelay(AllowSteamRelay);
|
||||
client = LegacyClient.CreateClient(this, address);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Client already running!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("Exception: " + ex.Message + ". Client could not be started.");
|
||||
OnClientDisconnected.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public override void ClientConnect(Uri uri)
|
||||
{
|
||||
if (uri.Scheme != STEAM_SCHEME)
|
||||
throw new ArgumentException($"Invalid url {uri}, use {STEAM_SCHEME}://SteamID instead", nameof(uri));
|
||||
|
||||
ClientConnect(uri.Host);
|
||||
}
|
||||
|
||||
public override void ClientSend(ArraySegment<byte> segment, int channelId)
|
||||
{
|
||||
byte[] data = new byte[segment.Count];
|
||||
Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count);
|
||||
client.Send(data, channelId);
|
||||
}
|
||||
|
||||
public override void ClientDisconnect()
|
||||
{
|
||||
if (ClientActive())
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
public bool ClientActive() => client != null;
|
||||
|
||||
|
||||
public override bool ServerActive() => server != null;
|
||||
public override void ServerStart()
|
||||
{
|
||||
try
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
|
||||
#else
|
||||
SteamNetworkingUtils.InitRelayNetworkAccess();
|
||||
#endif
|
||||
|
||||
|
||||
InitRelayNetworkAccess();
|
||||
|
||||
if (ClientActive())
|
||||
{
|
||||
Debug.LogError("Transport already running as client!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ServerActive())
|
||||
{
|
||||
if (UseNextGenSteamNetworking)
|
||||
{
|
||||
Debug.Log($"Starting server [SteamSockets].");
|
||||
server = NextServer.CreateServer(this, NetworkManager.singleton.maxConnections);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Starting server [DEPRECATED SteamNetworking]. Relay enabled: {AllowSteamRelay}");
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworking.AllowP2PPacketRelay(AllowSteamRelay);
|
||||
#else
|
||||
|
||||
SteamNetworking.AllowP2PPacketRelay(AllowSteamRelay);
|
||||
#endif
|
||||
server = LegacyServer.CreateServer(this, NetworkManager.singleton.maxConnections);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Server already started!");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public override Uri ServerUri()
|
||||
{
|
||||
var steamBuilder = new UriBuilder
|
||||
{
|
||||
Scheme = STEAM_SCHEME,
|
||||
#if UNITY_SERVER
|
||||
Host = SteamGameServer.GetSteamID().m_SteamID.ToString()
|
||||
#else
|
||||
Host = SteamUser.GetSteamID().m_SteamID.ToString()
|
||||
#endif
|
||||
};
|
||||
|
||||
return steamBuilder.Uri;
|
||||
}
|
||||
|
||||
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId)
|
||||
{
|
||||
if (ServerActive())
|
||||
{
|
||||
byte[] data = new byte[segment.Count];
|
||||
Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count);
|
||||
server.Send(connectionId, data, channelId);
|
||||
}
|
||||
}
|
||||
public override void ServerDisconnect(int connectionId)
|
||||
{
|
||||
if (ServerActive())
|
||||
{
|
||||
server.Disconnect(connectionId);
|
||||
}
|
||||
}
|
||||
public override string ServerGetClientAddress(int connectionId) => ServerActive() ? server.ServerGetClientAddress(connectionId) : string.Empty;
|
||||
public override void ServerStop()
|
||||
{
|
||||
if (ServerActive())
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
if (server != null)
|
||||
{
|
||||
server.Shutdown();
|
||||
server = null;
|
||||
Debug.Log("Transport shut down - was server.");
|
||||
}
|
||||
|
||||
if (client != null)
|
||||
{
|
||||
client.Disconnect();
|
||||
client = null;
|
||||
Debug.Log("Transport shut down - was client.");
|
||||
}
|
||||
}
|
||||
|
||||
public override int GetMaxPacketSize(int channelId)
|
||||
{
|
||||
if (UseNextGenSteamNetworking)
|
||||
{
|
||||
return Constants.k_cbMaxSteamNetworkingSocketsMessageSizeSend;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (channelId >= Channels.Length)
|
||||
{
|
||||
Debug.LogError("Channel Id exceeded configured channels! Please configure more channels.");
|
||||
return 1200;
|
||||
}
|
||||
|
||||
switch (Channels[channelId])
|
||||
{
|
||||
case EP2PSend.k_EP2PSendUnreliable:
|
||||
case EP2PSend.k_EP2PSendUnreliableNoDelay:
|
||||
return 1200;
|
||||
case EP2PSend.k_EP2PSendReliable:
|
||||
case EP2PSend.k_EP2PSendReliableWithBuffering:
|
||||
return 1048576;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool Available()
|
||||
{
|
||||
try
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
|
||||
#else
|
||||
SteamNetworkingUtils.InitRelayNetworkAccess();
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitRelayNetworkAccess()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (UseNextGenSteamNetworking)
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
|
||||
#else
|
||||
SteamNetworkingUtils.InitRelayNetworkAccess();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLESTEAMWORKS
|
@ -1,23 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RootNamespace>Mirror.FizzySteam</RootNamespace>
|
||||
<AssemblyTitle>Fizzy Steamworks</AssemblyTitle>
|
||||
<Product>Fizzy Steamworks</Product>
|
||||
<Title>Fizzy Steamworks</Title>
|
||||
<Description></Description>
|
||||
<Authors>Fizz Cube Ltd, Marco Hoffmann, William Corby</Authors>
|
||||
<Company>Fizz Cube Ltd, Marco Hoffmann, William Corby</Company>
|
||||
<Copyright></Copyright>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Include="LICENSE">
|
||||
<Pack>True</Pack>
|
||||
<PackagePath>\</PackagePath>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" IncludeAssets="compile" />
|
||||
<Reference Include="..\Lib\*.dll" />
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -1,14 +0,0 @@
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public interface IClient
|
||||
{
|
||||
bool Connected { get; }
|
||||
bool Error { get; }
|
||||
|
||||
|
||||
void ReceiveData();
|
||||
void Disconnect();
|
||||
void FlushData();
|
||||
void Send(byte[] data, int channelId);
|
||||
}
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public interface IServer
|
||||
{
|
||||
void ReceiveData();
|
||||
void Send(int connectionId, byte[] data, int channelId);
|
||||
void Disconnect(int connectionId);
|
||||
void FlushData();
|
||||
string ServerGetClientAddress(int connectionId);
|
||||
void Shutdown();
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright Fizz Cube Ltd (c) 2018
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
===
|
||||
|
||||
Copyright Marco Hoffmann (c) 2020
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
MIT License
|
@ -1,177 +0,0 @@
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public class LegacyClient : LegacyCommon, IClient
|
||||
{
|
||||
public bool Connected { get; private set; }
|
||||
public bool Error { get; private set; }
|
||||
|
||||
private event Action<byte[], int> OnReceivedData;
|
||||
private event Action OnConnected;
|
||||
private event Action OnDisconnected;
|
||||
|
||||
private TimeSpan ConnectionTimeout;
|
||||
|
||||
private CSteamID hostSteamID = CSteamID.Nil;
|
||||
private TaskCompletionSource<Task> connectedComplete;
|
||||
private CancellationTokenSource cancelToken;
|
||||
|
||||
private LegacyClient(FizzySteamworks transport) : base(transport)
|
||||
{
|
||||
ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.Timeout));
|
||||
}
|
||||
|
||||
public static LegacyClient CreateClient(FizzySteamworks transport, string host)
|
||||
{
|
||||
LegacyClient c = new LegacyClient(transport);
|
||||
|
||||
c.OnConnected += () => transport.OnClientConnected.Invoke();
|
||||
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
|
||||
c.OnReceivedData += (data, channel) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), channel);
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
InteropHelp.TestIfAvailableGameServer();
|
||||
#else
|
||||
InteropHelp.TestIfAvailableClient();
|
||||
#endif
|
||||
c.Connect(host);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.LogError("SteamWorks not initialized.");
|
||||
c.OnConnectionFailed(CSteamID.Nil);
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
private async void Connect(string host)
|
||||
{
|
||||
cancelToken = new CancellationTokenSource();
|
||||
|
||||
try
|
||||
{
|
||||
hostSteamID = new CSteamID(UInt64.Parse(host));
|
||||
connectedComplete = new TaskCompletionSource<Task>();
|
||||
|
||||
OnConnected += SetConnectedComplete;
|
||||
|
||||
SendInternal(hostSteamID, InternalMessages.CONNECT);
|
||||
|
||||
Task connectedCompleteTask = connectedComplete.Task;
|
||||
Task timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token);
|
||||
|
||||
if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask)
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
{
|
||||
Debug.LogError($"The connection attempt was cancelled.");
|
||||
}
|
||||
else if (timeOutTask.IsCompleted)
|
||||
{
|
||||
Debug.LogError($"Connection to {host} timed out.");
|
||||
}
|
||||
OnConnected -= SetConnectedComplete;
|
||||
OnConnectionFailed(hostSteamID);
|
||||
}
|
||||
|
||||
OnConnected -= SetConnectedComplete;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?");
|
||||
Error = true;
|
||||
OnConnectionFailed(hostSteamID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex.Message);
|
||||
Error = true;
|
||||
OnConnectionFailed(hostSteamID);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Error)
|
||||
{
|
||||
OnConnectionFailed(CSteamID.Nil);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
Debug.Log("Sending Disconnect message");
|
||||
SendInternal(hostSteamID, InternalMessages.DISCONNECT);
|
||||
Dispose();
|
||||
cancelToken?.Cancel();
|
||||
|
||||
WaitForClose(hostSteamID);
|
||||
}
|
||||
|
||||
private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task);
|
||||
|
||||
protected override void OnReceiveData(byte[] data, CSteamID clientSteamID, int channel)
|
||||
{
|
||||
if (clientSteamID != hostSteamID)
|
||||
{
|
||||
Debug.LogError("Received a message from an unknown");
|
||||
return;
|
||||
}
|
||||
|
||||
OnReceivedData.Invoke(data, channel);
|
||||
}
|
||||
|
||||
protected override void OnNewConnection(P2PSessionRequest_t result)
|
||||
{
|
||||
if (hostSteamID == result.m_steamIDRemote)
|
||||
{
|
||||
SteamNetworking.AcceptP2PSessionWithUser(result.m_steamIDRemote);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("P2P Acceptance Request from unknown host ID.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnReceiveInternalData(InternalMessages type, CSteamID clientSteamID)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case InternalMessages.ACCEPT_CONNECT:
|
||||
if (!Connected)
|
||||
{
|
||||
Connected = true;
|
||||
OnConnected.Invoke();
|
||||
Debug.Log("Connection established.");
|
||||
}
|
||||
break;
|
||||
case InternalMessages.DISCONNECT:
|
||||
if (Connected)
|
||||
{
|
||||
Connected = false;
|
||||
Debug.Log("Disconnected.");
|
||||
OnDisconnected.Invoke();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Debug.Log("Received unknown message type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(byte[] data, int channelId) => Send(hostSteamID, data, channelId);
|
||||
|
||||
protected override void OnConnectionFailed(CSteamID remoteId) => OnDisconnected.Invoke();
|
||||
public void FlushData() { }
|
||||
}
|
||||
}
|
||||
#endif // !DISABLESTEAMWORKS
|
@ -1,180 +0,0 @@
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public abstract class LegacyCommon
|
||||
{
|
||||
private EP2PSend[] channels;
|
||||
private int internal_ch => channels.Length;
|
||||
|
||||
protected enum InternalMessages : byte
|
||||
{
|
||||
CONNECT,
|
||||
ACCEPT_CONNECT,
|
||||
DISCONNECT
|
||||
}
|
||||
|
||||
private Steamworks.Callback<P2PSessionRequest_t> callback_OnNewConnection = null;
|
||||
private Steamworks.Callback<P2PSessionConnectFail_t> callback_OnConnectFail = null;
|
||||
|
||||
protected readonly FizzySteamworks transport;
|
||||
|
||||
protected LegacyCommon(FizzySteamworks transport)
|
||||
{
|
||||
channels = transport.Channels;
|
||||
|
||||
callback_OnNewConnection = Steamworks.Callback<P2PSessionRequest_t>.Create(OnNewConnection);
|
||||
callback_OnConnectFail = Steamworks.Callback<P2PSessionConnectFail_t>.Create(OnConnectFail);
|
||||
|
||||
this.transport = transport;
|
||||
}
|
||||
|
||||
protected void Dispose()
|
||||
{
|
||||
if (callback_OnNewConnection != null)
|
||||
{
|
||||
callback_OnNewConnection.Dispose();
|
||||
callback_OnNewConnection = null;
|
||||
}
|
||||
|
||||
if (callback_OnConnectFail != null)
|
||||
{
|
||||
callback_OnConnectFail.Dispose();
|
||||
callback_OnConnectFail = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnNewConnection(P2PSessionRequest_t result);
|
||||
|
||||
private void OnConnectFail(P2PSessionConnectFail_t result)
|
||||
{
|
||||
OnConnectionFailed(result.m_steamIDRemote);
|
||||
CloseP2PSessionWithUser(result.m_steamIDRemote);
|
||||
|
||||
switch (result.m_eP2PSessionError)
|
||||
{
|
||||
case 1:
|
||||
Debug.LogError("Connection failed: The target user is not running the same game.");
|
||||
break;
|
||||
case 2:
|
||||
Debug.LogError("Connection failed: The local user doesn't own the app that is running.");
|
||||
break;
|
||||
case 3:
|
||||
Debug.LogError("Connection failed: Target user isn't connected to Steam.");
|
||||
break;
|
||||
case 4:
|
||||
Debug.LogError("Connection failed: The connection timed out because the target user didn't respond.");
|
||||
break;
|
||||
default:
|
||||
Debug.LogError("Connection failed: Unknown error.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void SendInternal(CSteamID target, InternalMessages type)
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworking.SendP2PPacket(target, new byte[] { (byte)type }, 1, EP2PSend.k_EP2PSendReliable, internal_ch);
|
||||
#else
|
||||
SteamNetworking.SendP2PPacket(target, new byte[] { (byte)type }, 1, EP2PSend.k_EP2PSendReliable, internal_ch);
|
||||
#endif
|
||||
}
|
||||
protected void Send(CSteamID host, byte[] msgBuffer, int channel)
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworking.SendP2PPacket(host, msgBuffer, (uint)msgBuffer.Length, channels[Mathf.Min(channel, channels.Length - 1)], channel);
|
||||
#else
|
||||
SteamNetworking.SendP2PPacket(host, msgBuffer, (uint)msgBuffer.Length, channels[Mathf.Min(channel, channels.Length - 1)], channel);
|
||||
#endif
|
||||
}
|
||||
|
||||
private bool Receive(out CSteamID clientSteamID, out byte[] receiveBuffer, int channel)
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
if (SteamGameServerNetworking.IsP2PPacketAvailable(out uint packetSize, channel))
|
||||
{
|
||||
receiveBuffer = new byte[packetSize];
|
||||
return SteamGameServerNetworking.ReadP2PPacket(receiveBuffer, packetSize, out _, out clientSteamID, channel);
|
||||
}
|
||||
#else
|
||||
if (SteamNetworking.IsP2PPacketAvailable(out uint packetSize, channel))
|
||||
{
|
||||
receiveBuffer = new byte[packetSize];
|
||||
return SteamNetworking.ReadP2PPacket(receiveBuffer, packetSize, out _, out clientSteamID, channel);
|
||||
}
|
||||
#endif
|
||||
|
||||
receiveBuffer = null;
|
||||
clientSteamID = CSteamID.Nil;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected void CloseP2PSessionWithUser(CSteamID clientSteamID)
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworking.CloseP2PSessionWithUser(clientSteamID);
|
||||
#else
|
||||
SteamNetworking.CloseP2PSessionWithUser(clientSteamID);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected void WaitForClose(CSteamID cSteamID)
|
||||
{
|
||||
if (transport.enabled)
|
||||
{
|
||||
transport.StartCoroutine(DelayedClose(cSteamID));
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseP2PSessionWithUser(cSteamID);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerator DelayedClose(CSteamID cSteamID)
|
||||
{
|
||||
yield return null;
|
||||
CloseP2PSessionWithUser(cSteamID);
|
||||
}
|
||||
|
||||
public void ReceiveData()
|
||||
{
|
||||
try
|
||||
{
|
||||
while (transport.enabled && Receive(out CSteamID clientSteamID, out byte[] internalMessage, internal_ch))
|
||||
{
|
||||
if (internalMessage.Length == 1)
|
||||
{
|
||||
OnReceiveInternalData((InternalMessages)internalMessage[0], clientSteamID);
|
||||
return; // Wait one frame
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Incorrect package length on internal channel.");
|
||||
}
|
||||
}
|
||||
|
||||
for (int chNum = 0; chNum < channels.Length; chNum++)
|
||||
{
|
||||
while (transport.enabled && Receive(out CSteamID clientSteamID, out byte[] receiveBuffer, chNum))
|
||||
{
|
||||
OnReceiveData(receiveBuffer, clientSteamID, chNum);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void OnReceiveInternalData(InternalMessages type, CSteamID clientSteamID);
|
||||
protected abstract void OnReceiveData(byte[] data, CSteamID clientSteamID, int channel);
|
||||
protected abstract void OnConnectionFailed(CSteamID remoteId);
|
||||
}
|
||||
}
|
||||
#endif // !DISABLESTEAMWORKS
|
@ -1,170 +0,0 @@
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public class LegacyServer : LegacyCommon, IServer
|
||||
{
|
||||
private event Action<int> OnConnected;
|
||||
private event Action<int, byte[], int> OnReceivedData;
|
||||
private event Action<int> OnDisconnected;
|
||||
private event Action<int, TransportError, string> OnReceivedError;
|
||||
|
||||
private BidirectionalDictionary<CSteamID, int> steamToMirrorIds;
|
||||
private int maxConnections;
|
||||
private int nextConnectionID;
|
||||
|
||||
public static LegacyServer CreateServer(FizzySteamworks transport, int maxConnections)
|
||||
{
|
||||
LegacyServer s = new LegacyServer(transport, maxConnections);
|
||||
|
||||
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
|
||||
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
|
||||
s.OnReceivedData += (id, data, channel) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), channel);
|
||||
s.OnReceivedError += (id, error, reason) => transport.OnServerError.Invoke(id, error, reason);
|
||||
|
||||
try
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
InteropHelp.TestIfAvailableGameServer();
|
||||
#else
|
||||
InteropHelp.TestIfAvailableClient();
|
||||
#endif
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.LogError("SteamWorks not initialized.");
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private LegacyServer(FizzySteamworks transport, int maxConnections) : base(transport)
|
||||
{
|
||||
this.maxConnections = maxConnections;
|
||||
steamToMirrorIds = new BidirectionalDictionary<CSteamID, int>();
|
||||
nextConnectionID = 1;
|
||||
}
|
||||
|
||||
protected override void OnNewConnection(P2PSessionRequest_t result)
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworking.AcceptP2PSessionWithUser(result.m_steamIDRemote);
|
||||
#else
|
||||
SteamNetworking.AcceptP2PSessionWithUser(result.m_steamIDRemote);
|
||||
#endif
|
||||
}
|
||||
|
||||
protected override void OnReceiveInternalData(InternalMessages type, CSteamID clientSteamID)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case InternalMessages.CONNECT:
|
||||
if (steamToMirrorIds.Count >= maxConnections)
|
||||
{
|
||||
SendInternal(clientSteamID, InternalMessages.DISCONNECT);
|
||||
return;
|
||||
}
|
||||
|
||||
SendInternal(clientSteamID, InternalMessages.ACCEPT_CONNECT);
|
||||
|
||||
int connectionId = nextConnectionID++;
|
||||
steamToMirrorIds.Add(clientSteamID, connectionId);
|
||||
OnConnected.Invoke(connectionId);
|
||||
Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}");
|
||||
break;
|
||||
case InternalMessages.DISCONNECT:
|
||||
if (steamToMirrorIds.TryGetValue(clientSteamID, out int connId))
|
||||
{
|
||||
OnDisconnected.Invoke(connId);
|
||||
CloseP2PSessionWithUser(clientSteamID);
|
||||
steamToMirrorIds.Remove(clientSteamID);
|
||||
Debug.Log($"Client with SteamID {clientSteamID} disconnected.");
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
Debug.Log("Received unknown message type");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnReceiveData(byte[] data, CSteamID clientSteamID, int channel)
|
||||
{
|
||||
if (steamToMirrorIds.TryGetValue(clientSteamID, out int connectionId))
|
||||
{
|
||||
OnReceivedData.Invoke(connectionId, data, channel);
|
||||
}
|
||||
else
|
||||
{
|
||||
CloseP2PSessionWithUser(clientSteamID);
|
||||
Debug.LogError("Data received from steam client thats not known " + clientSteamID);
|
||||
OnReceivedError.Invoke(-1, TransportError.DnsResolve, "ERROR Unknown SteamID");
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect(int connectionId)
|
||||
{
|
||||
if (steamToMirrorIds.TryGetValue(connectionId, out CSteamID steamID))
|
||||
{
|
||||
SendInternal(steamID, InternalMessages.DISCONNECT);
|
||||
steamToMirrorIds.Remove(connectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
foreach (KeyValuePair<CSteamID, int> client in steamToMirrorIds)
|
||||
{
|
||||
Disconnect(client.Value);
|
||||
WaitForClose(client.Key);
|
||||
}
|
||||
|
||||
Dispose();
|
||||
}
|
||||
|
||||
public void Send(int connectionId, byte[] data, int channelId)
|
||||
{
|
||||
if (steamToMirrorIds.TryGetValue(connectionId, out CSteamID steamId))
|
||||
{
|
||||
Send(steamId, data, channelId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Trying to send on unknown connection: " + connectionId);
|
||||
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
|
||||
}
|
||||
}
|
||||
|
||||
public string ServerGetClientAddress(int connectionId)
|
||||
{
|
||||
if (steamToMirrorIds.TryGetValue(connectionId, out CSteamID steamId))
|
||||
{
|
||||
return steamId.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
|
||||
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnConnectionFailed(CSteamID remoteId)
|
||||
{
|
||||
int connectionId = steamToMirrorIds.TryGetValue(remoteId, out int connId) ? connId : nextConnectionID++;
|
||||
OnDisconnected.Invoke(connectionId);
|
||||
|
||||
steamToMirrorIds.Remove(remoteId);
|
||||
}
|
||||
public void FlushData() { }
|
||||
}
|
||||
}
|
||||
#endif // !DISABLESTEAMWORKS
|
@ -1,242 +0,0 @@
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public class NextClient : NextCommon, IClient
|
||||
{
|
||||
public bool Connected { get; private set; }
|
||||
public bool Error { get; private set; }
|
||||
|
||||
private TimeSpan ConnectionTimeout;
|
||||
|
||||
private event Action<byte[], int> OnReceivedData;
|
||||
private event Action OnConnected;
|
||||
private event Action OnDisconnected;
|
||||
// CHANGED
|
||||
private event Action<TransportError, string> OnReceivedError;
|
||||
private Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t> c_onConnectionChange = null;
|
||||
|
||||
private CancellationTokenSource cancelToken;
|
||||
private TaskCompletionSource<Task> connectedComplete;
|
||||
private CSteamID hostSteamID = CSteamID.Nil;
|
||||
private HSteamNetConnection HostConnection;
|
||||
private List<Action> BufferedData;
|
||||
|
||||
private NextClient(FizzySteamworks transport)
|
||||
{
|
||||
ConnectionTimeout = TimeSpan.FromSeconds(Math.Max(1, transport.Timeout));
|
||||
BufferedData = new List<Action>();
|
||||
}
|
||||
|
||||
public static NextClient CreateClient(FizzySteamworks transport, string host)
|
||||
{
|
||||
NextClient c = new NextClient(transport);
|
||||
|
||||
c.OnConnected += () => transport.OnClientConnected.Invoke();
|
||||
c.OnDisconnected += () => transport.OnClientDisconnected.Invoke();
|
||||
c.OnReceivedData += (data, ch) => transport.OnClientDataReceived.Invoke(new ArraySegment<byte>(data), ch);
|
||||
// CHANGED
|
||||
c.OnReceivedError += (error, reason) => transport.OnClientError.Invoke(error, reason);
|
||||
|
||||
try
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
|
||||
#else
|
||||
SteamNetworkingUtils.InitRelayNetworkAccess();
|
||||
#endif
|
||||
c.Connect(host);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
c.OnConnectionFailed();
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
private async void Connect(string host)
|
||||
{
|
||||
cancelToken = new CancellationTokenSource();
|
||||
c_onConnectionChange = Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
|
||||
|
||||
try
|
||||
{
|
||||
hostSteamID = new CSteamID(UInt64.Parse(host));
|
||||
connectedComplete = new TaskCompletionSource<Task>();
|
||||
OnConnected += SetConnectedComplete;
|
||||
|
||||
SteamNetworkingIdentity smi = new SteamNetworkingIdentity();
|
||||
smi.SetSteamID(hostSteamID);
|
||||
|
||||
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
|
||||
HostConnection = SteamNetworkingSockets.ConnectP2P(ref smi, 0, options.Length, options);
|
||||
|
||||
Task connectedCompleteTask = connectedComplete.Task;
|
||||
Task timeOutTask = Task.Delay(ConnectionTimeout, cancelToken.Token);
|
||||
|
||||
if (await Task.WhenAny(connectedCompleteTask, timeOutTask) != connectedCompleteTask)
|
||||
{
|
||||
if (cancelToken.IsCancellationRequested)
|
||||
{
|
||||
Debug.LogError($"The connection attempt was cancelled.");
|
||||
}
|
||||
else if (timeOutTask.IsCompleted)
|
||||
{
|
||||
Debug.LogError($"Connection to {host} timed out.");
|
||||
// CHANGED
|
||||
OnReceivedError.Invoke(TransportError.Timeout, $"Connection to {host} timed out.");
|
||||
}
|
||||
|
||||
OnConnected -= SetConnectedComplete;
|
||||
OnConnectionFailed();
|
||||
}
|
||||
|
||||
OnConnected -= SetConnectedComplete;
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
Debug.LogError($"Connection string was not in the right format. Did you enter a SteamId?");
|
||||
// CHANGED
|
||||
OnReceivedError.Invoke(TransportError.DnsResolve, $"Connection string was not in the right format. Did you enter a SteamId?");
|
||||
Error = true;
|
||||
OnConnectionFailed();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex.Message);
|
||||
// CHANGED
|
||||
OnReceivedError.Invoke(TransportError.Unexpected, ex.Message);
|
||||
Error = true;
|
||||
OnConnectionFailed();
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Error)
|
||||
{
|
||||
Debug.LogError("Connection failed.");
|
||||
OnConnectionFailed();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t param)
|
||||
{
|
||||
ulong clientSteamID = param.m_info.m_identityRemote.GetSteamID64();
|
||||
if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
|
||||
{
|
||||
Connected = true;
|
||||
OnConnected.Invoke();
|
||||
Debug.Log("Connection established.");
|
||||
|
||||
if (BufferedData.Count > 0)
|
||||
{
|
||||
Debug.Log($"{BufferedData.Count} received before connection was established. Processing now.");
|
||||
{
|
||||
foreach (Action a in BufferedData)
|
||||
{
|
||||
a();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer || param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
|
||||
{
|
||||
Debug.Log($"Connection was closed by peer, {param.m_info.m_szEndDebug}");
|
||||
// CHANGED
|
||||
OnReceivedError.Invoke(TransportError.ConnectionClosed, $"Connection was closed by peer, {param.m_info.m_szEndDebug}");
|
||||
Disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Connection state changed: {param.m_info.m_eState.ToString()} - {param.m_info.m_szEndDebug}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect()
|
||||
{
|
||||
cancelToken?.Cancel();
|
||||
Dispose();
|
||||
|
||||
if (HostConnection.m_HSteamNetConnection != 0)
|
||||
{
|
||||
Debug.Log("Sending Disconnect message");
|
||||
SteamNetworkingSockets.CloseConnection(HostConnection, 0, "Graceful disconnect", false);
|
||||
HostConnection.m_HSteamNetConnection = 0;
|
||||
}
|
||||
}
|
||||
|
||||
protected void Dispose()
|
||||
{
|
||||
if (c_onConnectionChange != null)
|
||||
{
|
||||
c_onConnectionChange.Dispose();
|
||||
c_onConnectionChange = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void InternalDisconnect()
|
||||
{
|
||||
Connected = false;
|
||||
OnDisconnected.Invoke();
|
||||
Debug.Log("Disconnected.");
|
||||
SteamNetworkingSockets.CloseConnection(HostConnection, 0, "Disconnected", false);
|
||||
}
|
||||
|
||||
public void ReceiveData()
|
||||
{
|
||||
IntPtr[] ptrs = new IntPtr[MAX_MESSAGES];
|
||||
int messageCount;
|
||||
|
||||
if ((messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(HostConnection, ptrs, MAX_MESSAGES)) > 0)
|
||||
{
|
||||
for (int i = 0; i < messageCount; i++)
|
||||
{
|
||||
(byte[] data, int ch) = ProcessMessage(ptrs[i]);
|
||||
if (Connected)
|
||||
{
|
||||
OnReceivedData(data, ch);
|
||||
}
|
||||
else
|
||||
{
|
||||
BufferedData.Add(() => OnReceivedData(data, ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(byte[] data, int channelId)
|
||||
{
|
||||
EResult res = SendSocket(HostConnection, data, channelId);
|
||||
|
||||
if (res == EResult.k_EResultNoConnection || res == EResult.k_EResultInvalidParam)
|
||||
{
|
||||
Debug.Log($"Connection to server was lost.");
|
||||
// CHANGED
|
||||
OnReceivedError.Invoke(TransportError.ConnectionClosed, $"Connection to server was lost.");
|
||||
InternalDisconnect();
|
||||
}
|
||||
else if (res != EResult.k_EResultOK)
|
||||
{
|
||||
Debug.LogError($"Could not send: {res.ToString()}");
|
||||
// CHANGED
|
||||
OnReceivedError.Invoke(TransportError.Unexpected, $"Could not send: {res.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SetConnectedComplete() => connectedComplete.SetResult(connectedComplete.Task);
|
||||
private void OnConnectionFailed() => OnDisconnected.Invoke();
|
||||
public void FlushData()
|
||||
{
|
||||
SteamNetworkingSockets.FlushMessagesOnConnection(HostConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLESTEAMWORKS
|
@ -1,48 +0,0 @@
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public abstract class NextCommon
|
||||
{
|
||||
protected const int MAX_MESSAGES = 256;
|
||||
|
||||
protected EResult SendSocket(HSteamNetConnection conn, byte[] data, int channelId)
|
||||
{
|
||||
Array.Resize(ref data, data.Length + 1);
|
||||
data[data.Length - 1] = (byte)channelId;
|
||||
|
||||
GCHandle pinnedArray = GCHandle.Alloc(data, GCHandleType.Pinned);
|
||||
IntPtr pData = pinnedArray.AddrOfPinnedObject();
|
||||
int sendFlag = channelId == Channels.Unreliable ? Constants.k_nSteamNetworkingSend_Unreliable : Constants.k_nSteamNetworkingSend_Reliable;
|
||||
#if UNITY_SERVER
|
||||
EResult res = SteamGameServerNetworkingSockets.SendMessageToConnection(conn, pData, (uint)data.Length, sendFlag, out long _);
|
||||
#else
|
||||
EResult res = SteamNetworkingSockets.SendMessageToConnection(conn, pData, (uint)data.Length, sendFlag, out long _);
|
||||
#endif
|
||||
if (res != EResult.k_EResultOK)
|
||||
{
|
||||
Debug.LogWarning($"Send issue: {res}");
|
||||
}
|
||||
|
||||
pinnedArray.Free();
|
||||
return res;
|
||||
}
|
||||
|
||||
protected (byte[], int) ProcessMessage(IntPtr ptrs)
|
||||
{
|
||||
SteamNetworkingMessage_t data = Marshal.PtrToStructure<SteamNetworkingMessage_t>(ptrs);
|
||||
byte[] managedArray = new byte[data.m_cbSize];
|
||||
Marshal.Copy(data.m_pData, managedArray, 0, data.m_cbSize);
|
||||
SteamNetworkingMessage_t.Release(ptrs);
|
||||
|
||||
int channel = managedArray[managedArray.Length - 1];
|
||||
Array.Resize(ref managedArray, managedArray.Length - 1);
|
||||
return (managedArray, channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLESTEAMWORKS
|
@ -1,246 +0,0 @@
|
||||
#if !DISABLESTEAMWORKS
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Mirror.FizzySteam
|
||||
{
|
||||
public class NextServer : NextCommon, IServer
|
||||
{
|
||||
private event Action<int> OnConnected;
|
||||
private event Action<int, byte[], int> OnReceivedData;
|
||||
private event Action<int> OnDisconnected;
|
||||
private event Action<int, TransportError, string> OnReceivedError;
|
||||
|
||||
private BidirectionalDictionary<HSteamNetConnection, int> connToMirrorID;
|
||||
private BidirectionalDictionary<CSteamID, int> steamIDToMirrorID;
|
||||
private int maxConnections;
|
||||
private int nextConnectionID;
|
||||
|
||||
private HSteamListenSocket listenSocket;
|
||||
|
||||
private Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t> c_onConnectionChange = null;
|
||||
|
||||
private NextServer(int maxConnections)
|
||||
{
|
||||
this.maxConnections = maxConnections;
|
||||
connToMirrorID = new BidirectionalDictionary<HSteamNetConnection, int>();
|
||||
steamIDToMirrorID = new BidirectionalDictionary<CSteamID, int>();
|
||||
nextConnectionID = 1;
|
||||
c_onConnectionChange = Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
|
||||
}
|
||||
|
||||
public static NextServer CreateServer(FizzySteamworks transport, int maxConnections)
|
||||
{
|
||||
NextServer s = new NextServer(maxConnections);
|
||||
|
||||
s.OnConnected += (id) => transport.OnServerConnected.Invoke(id);
|
||||
s.OnDisconnected += (id) => transport.OnServerDisconnected.Invoke(id);
|
||||
s.OnReceivedData += (id, data, ch) => transport.OnServerDataReceived.Invoke(id, new ArraySegment<byte>(data), ch);
|
||||
s.OnReceivedError += (id, error, reason) => transport.OnServerError.Invoke(id, error, reason);
|
||||
|
||||
try
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingUtils.InitRelayNetworkAccess();
|
||||
#else
|
||||
SteamNetworkingUtils.InitRelayNetworkAccess();
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogException(ex);
|
||||
}
|
||||
|
||||
s.Host();
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
private void Host()
|
||||
{
|
||||
SteamNetworkingConfigValue_t[] options = new SteamNetworkingConfigValue_t[] { };
|
||||
#if UNITY_SERVER
|
||||
listenSocket = SteamGameServerNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
|
||||
#else
|
||||
listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
|
||||
#endif
|
||||
}
|
||||
|
||||
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t param)
|
||||
{
|
||||
ulong clientSteamID = param.m_info.m_identityRemote.GetSteamID64();
|
||||
if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting)
|
||||
{
|
||||
if (connToMirrorID.Count >= maxConnections)
|
||||
{
|
||||
Debug.Log($"Incoming connection {clientSteamID} would exceed max connection count. Rejecting.");
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingSockets.CloseConnection(param.m_hConn, 0, "Max Connection Count", false);
|
||||
#else
|
||||
SteamNetworkingSockets.CloseConnection(param.m_hConn, 0, "Max Connection Count", false);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
EResult res;
|
||||
|
||||
#if UNITY_SERVER
|
||||
if ((res = SteamGameServerNetworkingSockets.AcceptConnection(param.m_hConn)) == EResult.k_EResultOK)
|
||||
#else
|
||||
if ((res = SteamNetworkingSockets.AcceptConnection(param.m_hConn)) == EResult.k_EResultOK)
|
||||
#endif
|
||||
{
|
||||
Debug.Log($"Accepting connection {clientSteamID}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Connection {clientSteamID} could not be accepted: {res.ToString()}");
|
||||
}
|
||||
}
|
||||
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected)
|
||||
{
|
||||
int connectionId = nextConnectionID++;
|
||||
connToMirrorID.Add(param.m_hConn, connectionId);
|
||||
steamIDToMirrorID.Add(param.m_info.m_identityRemote.GetSteamID(), connectionId);
|
||||
OnConnected.Invoke(connectionId);
|
||||
Debug.Log($"Client with SteamID {clientSteamID} connected. Assigning connection id {connectionId}");
|
||||
}
|
||||
else if (param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer || param.m_info.m_eState == ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally)
|
||||
{
|
||||
if (connToMirrorID.TryGetValue(param.m_hConn, out int connId))
|
||||
{
|
||||
InternalDisconnect(connId, param.m_hConn);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Connection {clientSteamID} state changed: {param.m_info.m_eState.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private void InternalDisconnect(int connId, HSteamNetConnection socket)
|
||||
{
|
||||
OnDisconnected.Invoke(connId);
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingSockets.CloseConnection(socket, 0, "Graceful disconnect", false);
|
||||
#else
|
||||
SteamNetworkingSockets.CloseConnection(socket, 0, "Graceful disconnect", false);
|
||||
#endif
|
||||
connToMirrorID.Remove(connId);
|
||||
steamIDToMirrorID.Remove(connId);
|
||||
Debug.Log($"Client with ConnectionID {connId} disconnected.");
|
||||
}
|
||||
|
||||
public void Disconnect(int connectionId)
|
||||
{
|
||||
if (connToMirrorID.TryGetValue(connectionId, out HSteamNetConnection conn))
|
||||
{
|
||||
Debug.Log($"Connection id {connectionId} disconnected.");
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingSockets.CloseConnection(conn, 0, "Disconnected by server", false);
|
||||
#else
|
||||
SteamNetworkingSockets.CloseConnection(conn, 0, "Disconnected by server", false);
|
||||
#endif
|
||||
steamIDToMirrorID.Remove(connectionId);
|
||||
connToMirrorID.Remove(connectionId);
|
||||
OnDisconnected(connectionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Trying to disconnect unknown connection id: " + connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
public void FlushData()
|
||||
{
|
||||
foreach (HSteamNetConnection conn in connToMirrorID.FirstTypes)
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingSockets.FlushMessagesOnConnection(conn);
|
||||
#else
|
||||
SteamNetworkingSockets.FlushMessagesOnConnection(conn);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void ReceiveData()
|
||||
{
|
||||
foreach (HSteamNetConnection conn in connToMirrorID.FirstTypes.ToList())
|
||||
{
|
||||
if (connToMirrorID.TryGetValue(conn, out int connId))
|
||||
{
|
||||
IntPtr[] ptrs = new IntPtr[MAX_MESSAGES];
|
||||
int messageCount;
|
||||
|
||||
#if UNITY_SERVER
|
||||
if ((messageCount = SteamGameServerNetworkingSockets.ReceiveMessagesOnConnection(conn, ptrs, MAX_MESSAGES)) > 0)
|
||||
#else
|
||||
if ((messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(conn, ptrs, MAX_MESSAGES)) > 0)
|
||||
#endif
|
||||
{
|
||||
for (int i = 0; i < messageCount; i++)
|
||||
{
|
||||
(byte[] data, int ch) = ProcessMessage(ptrs[i]);
|
||||
OnReceivedData(connId, data, ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(int connectionId, byte[] data, int channelId)
|
||||
{
|
||||
if (connToMirrorID.TryGetValue(connectionId, out HSteamNetConnection conn))
|
||||
{
|
||||
EResult res = SendSocket(conn, data, channelId);
|
||||
|
||||
if (res == EResult.k_EResultNoConnection || res == EResult.k_EResultInvalidParam)
|
||||
{
|
||||
Debug.Log($"Connection to {connectionId} was lost.");
|
||||
InternalDisconnect(connectionId, conn);
|
||||
}
|
||||
else if (res != EResult.k_EResultOK)
|
||||
{
|
||||
Debug.LogError($"Could not send: {res.ToString()}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Trying to send on unknown connection: " + connectionId);
|
||||
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
|
||||
}
|
||||
}
|
||||
|
||||
public string ServerGetClientAddress(int connectionId)
|
||||
{
|
||||
if (steamIDToMirrorID.TryGetValue(connectionId, out CSteamID steamId))
|
||||
{
|
||||
return steamId.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError("Trying to get info on unknown connection: " + connectionId);
|
||||
OnReceivedError.Invoke(connectionId, TransportError.Unexpected, "ERROR Unknown Connection");
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public void Shutdown()
|
||||
{
|
||||
#if UNITY_SERVER
|
||||
SteamGameServerNetworkingSockets.CloseListenSocket(listenSocket);
|
||||
#else
|
||||
SteamNetworkingSockets.CloseListenSocket(listenSocket);
|
||||
#endif
|
||||
|
||||
if (c_onConnectionChange != null)
|
||||
{
|
||||
c_onConnectionChange.Dispose();
|
||||
c_onConnectionChange = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // !DISABLESTEAMWORKS
|
18
QSB.sln
18
QSB.sln
@ -19,8 +19,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MirrorWeaver", "MirrorWeaver\MirrorWeaver.csproj", "{DA8A467E-15BA-456C-9034-6EB80BAF1FF9}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FizzySteamworks", "FizzySteamworks\FizzySteamworks.csproj", "{D47034D8-B92D-47DA-884C-C76F735E2D5D}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamRerouter", "SteamRerouter\SteamRerouter.csproj", "{750563ED-4F2A-42DD-81A3-3A85DEBB691E}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mirror", "Mirror", "{851AB4AD-366C-4D69-9F4E-E9EDC7CCD2BB}"
|
||||
@ -38,6 +36,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QSBPatcher", "QSBPatcher\QS
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QSB-NH", "QSB-NH\QSB-NH.csproj", "{74F84A39-1C9D-4EF7-889A-485D33B7B324}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamTransport", "SteamTransport\SteamTransport.csproj", "{83E8A790-CD63-4642-81D1-E406FC9F8EED}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamTransportTest", "SteamTransportTest\SteamTransportTest.csproj", "{8E74FA1D-466F-4BE5-981E-8D95E9B2D9AE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@ -52,10 +54,6 @@ Global
|
||||
{DA8A467E-15BA-456C-9034-6EB80BAF1FF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DA8A467E-15BA-456C-9034-6EB80BAF1FF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DA8A467E-15BA-456C-9034-6EB80BAF1FF9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D47034D8-B92D-47DA-884C-C76F735E2D5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D47034D8-B92D-47DA-884C-C76F735E2D5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D47034D8-B92D-47DA-884C-C76F735E2D5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D47034D8-B92D-47DA-884C-C76F735E2D5D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{750563ED-4F2A-42DD-81A3-3A85DEBB691E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{750563ED-4F2A-42DD-81A3-3A85DEBB691E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{750563ED-4F2A-42DD-81A3-3A85DEBB691E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@ -72,6 +70,14 @@ Global
|
||||
{74F84A39-1C9D-4EF7-889A-485D33B7B324}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{74F84A39-1C9D-4EF7-889A-485D33B7B324}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{74F84A39-1C9D-4EF7-889A-485D33B7B324}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{83E8A790-CD63-4642-81D1-E406FC9F8EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{83E8A790-CD63-4642-81D1-E406FC9F8EED}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{83E8A790-CD63-4642-81D1-E406FC9F8EED}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{83E8A790-CD63-4642-81D1-E406FC9F8EED}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8E74FA1D-466F-4BE5-981E-8D95E9B2D9AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8E74FA1D-466F-4BE5-981E-8D95E9B2D9AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8E74FA1D-466F-4BE5-981E-8D95E9B2D9AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8E74FA1D-466F-4BE5-981E-8D95E9B2D9AE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -98,7 +98,7 @@ public class AnimatorMirror : MonoBehaviour
|
||||
{
|
||||
if (_networkAnimator != null)
|
||||
{
|
||||
DebugLog.DebugWrite($"Set {fromParam.name} on netanim");
|
||||
// DebugLog.DebugWrite($"Set {fromParam.name} on netanim");
|
||||
_networkAnimator.SetTrigger(fromParam.nameHash);
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ public class AnimatorMirror : MonoBehaviour
|
||||
{
|
||||
if (_networkAnimator != null)
|
||||
{
|
||||
DebugLog.DebugWrite($"Reset {fromParam.name} on netanim");
|
||||
// DebugLog.DebugWrite($"Reset {fromParam.name} on netanim");
|
||||
_networkAnimator.ResetTrigger(fromParam.nameHash);
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ public class DeathPatches : QSBPatch
|
||||
}
|
||||
|
||||
var deadPlayersCount = QSBPlayerManager.PlayerList.Count(x => x.IsDead);
|
||||
if (deadPlayersCount == QSBPlayerManager.PlayerList.Count - 1 && !QSBCore.DebugSettings.DisableLoopDeath)
|
||||
if (deadPlayersCount == QSBPlayerManager.PlayerList.Count - 1)
|
||||
{
|
||||
new EndLoopMessage().Send();
|
||||
DebugLog.DebugWrite($"- All players are dead.");
|
||||
|
@ -331,7 +331,7 @@ public class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
DisconnectPopup = QSBCore.Helper.MenuHelper.PopupMenuManager.CreateTwoChoicePopup(QSBLocalization.Current.DisconnectAreYouSure, QSBLocalization.Current.Yes, QSBLocalization.Current.No);
|
||||
DisconnectPopup.OnPopupConfirm += Disconnect;
|
||||
|
||||
DisconnectButton = QSBCore.Helper.MenuHelper.PauseMenuManager.MakeMenuOpenButton(QSBLocalization.Current.PauseMenuDisconnect, DisconnectPopup, 0, true);
|
||||
DisconnectButton = QSBCore.Helper.MenuHelper.PauseMenuManager.MakeMenuOpenButton(QSBLocalization.Current.PauseMenuDisconnect, DisconnectPopup, 0, false);
|
||||
|
||||
QuitButton = FindObjectOfType<PauseMenuManager>()._exitToMainMenuAction.gameObject;
|
||||
|
||||
@ -490,7 +490,8 @@ public class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
LoadGame(PlayerData.GetWarpedToTheEye());
|
||||
// wait until scene load and then wait until Start has ran
|
||||
// why is this done? GameStateMessage etc works on title screen since nonhost has to deal with that
|
||||
// this is done cuz otherwise client can get into scene before server. could start on transition but eh
|
||||
// see issue #710
|
||||
Delay.RunWhen(() => TimeLoop._initialized, () =>
|
||||
{
|
||||
QSBNetworkManager.singleton.StartHost();
|
||||
@ -506,7 +507,8 @@ public class MenuManager : MonoBehaviour, IAddComponentOnStart
|
||||
{
|
||||
LoadGame(PlayerData.GetWarpedToTheEye());
|
||||
// wait until scene load and then wait until Start has ran
|
||||
// why is this done? GameStateMessage etc works on title screen since nonhost has to deal with that
|
||||
// this is done cuz otherwise client can get into scene before server. could start on transition but eh
|
||||
// see issue #710
|
||||
Delay.RunWhen(() => TimeLoop._initialized, () =>
|
||||
{
|
||||
QSBNetworkManager.singleton.StartHost();
|
||||
|
@ -62,13 +62,6 @@ public class PlayerJoinMessage : QSBMessage
|
||||
{
|
||||
if (QSBCore.IsHost)
|
||||
{
|
||||
if (QSBCore.DebugSettings.KickEveryone)
|
||||
{
|
||||
DebugLog.ToConsole($"Kicking {PlayerName} because of DebugSettings.KickEveryone", MessageType.Error);
|
||||
new PlayerKickMessage(From, "This server has DebugSettings.KickEveryone enabled.").Send();
|
||||
return;
|
||||
}
|
||||
|
||||
if (QSBVersion != QSBCore.QSBVersion)
|
||||
{
|
||||
DebugLog.ToConsole($"Error - Client {PlayerName} connecting with wrong QSB version. (Client:{QSBVersion}, Server:{QSBCore.QSBVersion})", MessageType.Error);
|
||||
|
@ -66,9 +66,6 @@
|
||||
</None>
|
||||
<None Remove="AssetBundles\AssetBundles" />
|
||||
<None Remove="AssetBundles\*.manifest" />
|
||||
<None Condition="Exists('debugsettings.json')" Include="debugsettings.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Include="..\Lib\*.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
@ -77,7 +74,7 @@
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.15.1018" IncludeAssets="compile" />
|
||||
<PackageReference Include="OWML" Version="2.15.1" IncludeAssets="compile" />
|
||||
<Reference Include="..\Lib\*.dll" />
|
||||
<ProjectReference Include="..\FizzySteamworks\FizzySteamworks.csproj" />
|
||||
<ProjectReference Include="..\SteamTransport\SteamTransport.csproj" />
|
||||
<ProjectReference Include="..\SteamRerouter\SteamRerouter.csproj" />
|
||||
<ProjectReference Include="..\QSBPatcher\QSBPatcher.csproj" />
|
||||
<ProjectReference Include="..\MirrorWeaver\MirrorWeaver.csproj" ReferenceOutputAssembly="false" />
|
||||
|
105
QSB/QSBCore.cs
105
QSB/QSBCore.cs
@ -17,7 +17,6 @@ using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using QSB.API;
|
||||
using QSB.BodyCustomization;
|
||||
using QSB.Player.Messages;
|
||||
@ -72,15 +71,14 @@ public class QSBCore : ModBehaviour
|
||||
public static bool TextChatInput { get; private set; }
|
||||
public static string SkinVariation { get; private set; } = "Default";
|
||||
public static string JetpackVariation { get; private set; } = "Orange";
|
||||
public static int Timeout { get; private set; }
|
||||
|
||||
public static GameVendor GameVendor { get; private set; } = GameVendor.None;
|
||||
public static bool IsStandalone => GameVendor is GameVendor.Epic or GameVendor.Steam;
|
||||
public static IProfileManager ProfileManager => IsStandalone
|
||||
? QSBStandaloneProfileManager.SharedInstance
|
||||
: QSBMSStoreProfileManager.SharedInstance;
|
||||
public static DebugSettings DebugSettings { get; private set; } = new();
|
||||
|
||||
private static string randomSkinType;
|
||||
private static string randomJetpackType;
|
||||
public static readonly DebugSettings DebugSettings = new();
|
||||
|
||||
public static Assembly QSBNHAssembly = null;
|
||||
|
||||
@ -217,32 +215,6 @@ public class QSBCore : ModBehaviour
|
||||
|
||||
CheckNewHorizons();
|
||||
|
||||
DebugSettings = Helper.Storage.Load<DebugSettings>("debugsettings.json") ?? new DebugSettings();
|
||||
|
||||
if (DebugSettings.HookDebugLogs)
|
||||
{
|
||||
Application.logMessageReceived += (condition, stackTrace, logType) =>
|
||||
DebugLog.ToConsole(
|
||||
$"[Debug] {condition}" +
|
||||
(stackTrace != string.Empty ? $"\nStacktrace: {stackTrace}" : string.Empty),
|
||||
logType switch
|
||||
{
|
||||
LogType.Error => MessageType.Error,
|
||||
LogType.Assert => MessageType.Error,
|
||||
LogType.Warning => MessageType.Warning,
|
||||
LogType.Log => MessageType.Message,
|
||||
LogType.Exception => MessageType.Error,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(logType), logType, null)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (DebugSettings.AutoStart)
|
||||
{
|
||||
UseKcpTransport = true;
|
||||
DebugSettings.DebugMode = true;
|
||||
}
|
||||
|
||||
RegisterAddons();
|
||||
|
||||
InitAssemblies();
|
||||
@ -273,19 +245,6 @@ public class QSBCore : ModBehaviour
|
||||
QSBWorldSync.Managers = components.OfType<WorldObjectManager>().ToArray();
|
||||
QSBPatchManager.OnPatchType += OnPatchType;
|
||||
QSBPatchManager.OnUnpatchType += OnUnpatchType;
|
||||
|
||||
if (DebugSettings.RandomizeSkins)
|
||||
{
|
||||
var skinSetting = (JObject)ModHelper.Config.Settings["skinType"];
|
||||
var skinOptions = skinSetting["options"].ToObject<string[]>();
|
||||
randomSkinType = skinOptions[UnityEngine.Random.Range(0, skinOptions.Length - 1)];
|
||||
|
||||
var jetpackSetting = (JObject)ModHelper.Config.Settings["jetpackType"];
|
||||
var jetpackOptions = jetpackSetting["options"].ToObject<string[]>();
|
||||
randomJetpackType = jetpackOptions[UnityEngine.Random.Range(0, jetpackOptions.Length - 1)];
|
||||
|
||||
Configure(ModHelper.Config);
|
||||
}
|
||||
}
|
||||
|
||||
private AssetBundle LoadBundle(string bundleName)
|
||||
@ -402,6 +361,25 @@ public class QSBCore : ModBehaviour
|
||||
|
||||
public override void Configure(IModConfig config)
|
||||
{
|
||||
DebugSettings.Update(config);
|
||||
|
||||
Application.logMessageReceived -= OnDebugLog;
|
||||
|
||||
if (DebugSettings.HookDebugLogs)
|
||||
{
|
||||
Application.logMessageReceived += OnDebugLog;
|
||||
}
|
||||
|
||||
// Configure gets called before Start, so these might not exist yet
|
||||
if (GetComponent<DebugActions>() != null)
|
||||
{
|
||||
GetComponent<DebugActions>().enabled = DebugSettings.DebugMode;
|
||||
GetComponent<DebugGUI>().enabled = DebugSettings.DebugMode;
|
||||
}
|
||||
|
||||
DebugCameraSettings.UpdateFromDebugSetting();
|
||||
|
||||
Timeout = config.GetSettingsValue<int>("timeout");
|
||||
UseKcpTransport = config.GetSettingsValue<bool>("useKcpTransport") || DebugSettings.AutoStart;
|
||||
var foundValue = config.GetSettingsValue<int>("kcpPort");
|
||||
KcpPort = (ushort)Mathf.Clamp(foundValue, ushort.MinValue, ushort.MaxValue);
|
||||
@ -414,16 +392,8 @@ public class QSBCore : ModBehaviour
|
||||
TextChatInput = config.GetSettingsValue<bool>("textChatInput");
|
||||
AlwaysShowPlanetIcons = config.GetSettingsValue<bool>("alwaysShowPlanetIcons");
|
||||
|
||||
if (DebugSettings.RandomizeSkins)
|
||||
{
|
||||
SkinVariation = randomSkinType;
|
||||
JetpackVariation = randomJetpackType;
|
||||
}
|
||||
else
|
||||
{
|
||||
SkinVariation = config.GetSettingsValue<string>("skinType");
|
||||
JetpackVariation = config.GetSettingsValue<string>("jetpackType");
|
||||
}
|
||||
SkinVariation = config.GetSettingsValue<string>("skinType");
|
||||
JetpackVariation = config.GetSettingsValue<string>("jetpackType");
|
||||
|
||||
if (IsHost)
|
||||
{
|
||||
@ -440,23 +410,28 @@ public class QSBCore : ModBehaviour
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (Keyboard.current[Key.Q].isPressed && Keyboard.current[Key.NumpadEnter].wasPressedThisFrame)
|
||||
{
|
||||
DebugSettings.DebugMode = !DebugSettings.DebugMode;
|
||||
|
||||
GetComponent<DebugActions>().enabled = DebugSettings.DebugMode;
|
||||
GetComponent<DebugGUI>().enabled = DebugSettings.DebugMode;
|
||||
DebugCameraSettings.UpdateFromDebugSetting();
|
||||
|
||||
DebugLog.ToConsole($"DEBUG MODE = {DebugSettings.DebugMode}");
|
||||
}
|
||||
|
||||
if (_steamworksInitialized)
|
||||
{
|
||||
SteamAPI.RunCallbacks();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// called when unity does Debug log
|
||||
/// </summary>
|
||||
private static void OnDebugLog(string condition, string stackTrace, LogType logType)
|
||||
{
|
||||
DebugLog.ToConsole($"[Debug] {condition}" + (stackTrace != string.Empty ? $"\nStacktrace: {stackTrace}" : string.Empty), logType switch
|
||||
{
|
||||
LogType.Error => MessageType.Error,
|
||||
LogType.Assert => MessageType.Error,
|
||||
LogType.Warning => MessageType.Warning,
|
||||
LogType.Log => MessageType.Message,
|
||||
LogType.Exception => MessageType.Error,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(logType), logType, null)
|
||||
});
|
||||
}
|
||||
|
||||
private void CheckNewHorizons()
|
||||
{
|
||||
if (ModHelper.Interaction.ModExists("xen.NewHorizons"))
|
||||
|
@ -1,6 +1,4 @@
|
||||
using Epic.OnlineServices.Logging;
|
||||
using Mirror;
|
||||
using Mirror.FizzySteam;
|
||||
using Mirror;
|
||||
using OWML.Common;
|
||||
using OWML.Utils;
|
||||
using QSB.Anglerfish.TransformSync;
|
||||
@ -66,11 +64,11 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
private GameObject _probePrefab;
|
||||
private bool _everConnected;
|
||||
|
||||
private (TransportError error, string reason) _lastTransportError = (TransportError.Unexpected, "transport did not give an error. uh oh");
|
||||
private (TransportError error, string reason) _lastTransportError = (TransportError.Unexpected, "transport did not give an error. uh oh. report this please!");
|
||||
|
||||
private static LatencySimulation _latencyTransport;
|
||||
private static kcp2k.KcpTransport _kcpTransport;
|
||||
private static FizzySteamworks _steamTransport;
|
||||
private static SteamTransport.SteamTransport _steamTransport;
|
||||
|
||||
public override void Awake()
|
||||
{
|
||||
@ -79,14 +77,16 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
{
|
||||
_kcpTransport = gameObject.AddComponent<kcp2k.KcpTransport>();
|
||||
// KCP uses milliseconds
|
||||
_kcpTransport.Timeout = QSBCore.DebugSettings.Timeout * 1000;
|
||||
_kcpTransport.Timeout = QSBCore.Timeout * 1000;
|
||||
_kcpTransport.Port = QSBCore.KcpPort;
|
||||
}
|
||||
|
||||
{
|
||||
_steamTransport = gameObject.AddComponent<FizzySteamworks>();
|
||||
// Steam uses seconds
|
||||
_steamTransport.Timeout = QSBCore.DebugSettings.Timeout;
|
||||
_steamTransport = gameObject.AddComponent<SteamTransport.SteamTransport>();
|
||||
// Steam uses milliseconds
|
||||
_steamTransport.Timeout = QSBCore.Timeout * 1000;
|
||||
_steamTransport.TestIpAddress = QSBCore.DebugSettings.SteamTestIpAddress;
|
||||
_steamTransport.DoFakeNetworkErrors = QSBCore.DebugSettings.SteamFakeNetworkErrors;
|
||||
}
|
||||
|
||||
{
|
||||
@ -168,6 +168,11 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
}
|
||||
|
||||
_kcpTransport.Port = QSBCore.KcpPort;
|
||||
_kcpTransport.Timeout = QSBCore.Timeout * 1000;
|
||||
|
||||
_steamTransport.Timeout = QSBCore.Timeout * 1000;
|
||||
_steamTransport.TestIpAddress = QSBCore.DebugSettings.SteamTestIpAddress;
|
||||
_steamTransport.DoFakeNetworkErrors = QSBCore.DebugSettings.SteamFakeNetworkErrors;
|
||||
|
||||
if (QSBCore.IsInMultiplayer)
|
||||
{
|
||||
@ -251,10 +256,14 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
return template;
|
||||
}
|
||||
|
||||
// who knows why this is a separate function
|
||||
private void ConfigureNetworkManager()
|
||||
{
|
||||
networkAddress = QSBCore.DefaultServerIP;
|
||||
|
||||
{
|
||||
_steamTransport.Log = s => DebugLog.DebugWrite("[Steam] " + s);
|
||||
}
|
||||
{
|
||||
kcp2k.Log.Info = s =>
|
||||
{
|
||||
@ -269,6 +278,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
kcp2k.Log.Error = s => DebugLog.DebugWrite("[KCP] " + s, MessageType.Error);
|
||||
}
|
||||
|
||||
// this is where the logic for stopping host in the title screen is
|
||||
QSBSceneManager.OnPostSceneLoad += (_, loadScene) =>
|
||||
{
|
||||
if (QSBCore.IsInMultiplayer && loadScene == OWScene.TitleScreen)
|
||||
@ -365,7 +375,7 @@ public class QSBNetworkManager : NetworkManager, IAddComponentOnStart
|
||||
DebugLog.DebugWrite("OnClientDisconnect");
|
||||
base.OnClientDisconnect();
|
||||
OnClientDisconnected?.SafeInvoke(_lastTransportError.error, _lastTransportError.reason);
|
||||
_lastTransportError = (TransportError.Unexpected, "transport did not give an error. uh oh");
|
||||
_lastTransportError = (TransportError.Unexpected, "transport did not give an error. uh oh. report this please!");
|
||||
}
|
||||
|
||||
public override void OnServerDisconnect(NetworkConnectionToClient conn) // Called on the server when any client disconnects
|
||||
|
@ -162,7 +162,7 @@ public class RespawnManager : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
var deadPlayersCount = QSBPlayerManager.PlayerList.Count(x => x.IsDead);
|
||||
|
||||
if (deadPlayersCount == QSBPlayerManager.PlayerList.Count && !QSBCore.DebugSettings.DisableLoopDeath)
|
||||
if (deadPlayersCount == QSBPlayerManager.PlayerList.Count)
|
||||
{
|
||||
new EndLoopMessage().Send();
|
||||
return;
|
||||
|
@ -89,4 +89,4 @@ public class QSBInflationTrigger : QSBTrigger<CosmicInflationController>
|
||||
particles.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,12 +160,34 @@ public class DebugActions : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
if (Keyboard.current[Key.Numpad2].wasPressedThisFrame)
|
||||
{
|
||||
var location = DreamArrivalPoint.Location.Undefined;
|
||||
if (Keyboard.current[Key.Digit1].isPressed)
|
||||
{
|
||||
location = DreamArrivalPoint.Location.Zone1;
|
||||
}
|
||||
else if (Keyboard.current[Key.Digit2].isPressed)
|
||||
{
|
||||
location = DreamArrivalPoint.Location.Zone2;
|
||||
}
|
||||
else if (Keyboard.current[Key.Digit3].isPressed)
|
||||
{
|
||||
location = DreamArrivalPoint.Location.Zone3;
|
||||
}
|
||||
else if (Keyboard.current[Key.Digit4].isPressed)
|
||||
{
|
||||
location = DreamArrivalPoint.Location.Zone4;
|
||||
}
|
||||
|
||||
if (!QSBPlayerManager.LocalPlayer.InDreamWorld)
|
||||
{
|
||||
if (location == DreamArrivalPoint.Location.Undefined)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// modified from DayDream debug thing
|
||||
var relativeLocation = new RelativeLocationData(Vector3.up * 2 + Vector3.forward * 2, Quaternion.identity, Vector3.zero);
|
||||
|
||||
var location = Keyboard.current[Key.LeftShift].isPressed ? DreamArrivalPoint.Location.Zone4 : DreamArrivalPoint.Location.Zone3;
|
||||
var arrivalPoint = Locator.GetDreamArrivalPoint(location);
|
||||
var dreamCampfire = Locator.GetDreamCampfire(location);
|
||||
if (Locator.GetToolModeSwapper().GetItemCarryTool().GetHeldItemType() != ItemType.DreamLantern)
|
||||
@ -187,6 +209,35 @@ public class DebugActions : MonoBehaviour, IAddComponentOnStart
|
||||
var dreamLanternItem = QSBPlayerManager.LocalPlayer.AssignedSimulationLantern.AttachedObject;
|
||||
Locator.GetToolModeSwapper().GetItemCarryTool().PickUpItemInstantly(dreamLanternItem);
|
||||
}
|
||||
|
||||
if (location == DreamArrivalPoint.Location.Undefined)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// copied from DreamWorldController.FixedUpdate
|
||||
|
||||
var relativeLocation = new RelativeLocationData(Vector3.up * 2 + Vector3.forward * 2, Quaternion.identity, Vector3.zero);
|
||||
var arrivalPoint = Locator.GetDreamArrivalPoint(location);
|
||||
var controller = Locator.GetDreamWorldController();
|
||||
|
||||
Locator.GetPlayerBody().MoveToRelativeLocation(relativeLocation, controller._dreamBody, arrivalPoint.transform);
|
||||
GlobalMessenger.FireEvent("WarpPlayer");
|
||||
if (!Physics.autoSyncTransforms)
|
||||
{
|
||||
Physics.SyncTransforms();
|
||||
}
|
||||
|
||||
var playerSectorDetector = Locator.GetPlayerSectorDetector();
|
||||
playerSectorDetector.RemoveFromAllSectors();
|
||||
var sector = arrivalPoint.GetSector();
|
||||
while (sector != null)
|
||||
{
|
||||
sector.GetTriggerVolume().AddObjectToVolume(playerSectorDetector.gameObject);
|
||||
sector = sector.GetParentSector();
|
||||
}
|
||||
|
||||
arrivalPoint.OnEnterDreamWorld(QSBPlayerManager.LocalPlayer.AssignedSimulationLantern.AttachedObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,21 +6,21 @@ public class DebugCameraSettings : MonoBehaviour, IAddComponentOnStart
|
||||
{
|
||||
public static void UpdateFromDebugSetting()
|
||||
{
|
||||
if (QSBCore.DebugSettings.GreySkybox)
|
||||
QSBSceneManager.OnSceneLoaded -= OnSceneLoaded;
|
||||
if (Camera.main)
|
||||
{
|
||||
QSBSceneManager.OnSceneLoaded += OnSceneLoaded;
|
||||
if (Camera.main)
|
||||
{
|
||||
Camera.main.backgroundColor = Color.gray;
|
||||
}
|
||||
Camera.main.backgroundColor = _origColor;
|
||||
}
|
||||
else
|
||||
|
||||
if (!QSBCore.DebugSettings.GreySkybox)
|
||||
{
|
||||
QSBSceneManager.OnSceneLoaded -= OnSceneLoaded;
|
||||
if (Camera.main)
|
||||
{
|
||||
Camera.main.backgroundColor = _origColor;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QSBSceneManager.OnSceneLoaded += OnSceneLoaded;
|
||||
if (Camera.main)
|
||||
{
|
||||
Camera.main.backgroundColor = Color.gray;
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,4 +35,4 @@ public class DebugCameraSettings : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
private static void OnSceneLoaded(OWScene arg1, OWScene arg2, bool arg3)
|
||||
=> Camera.main.backgroundColor = Color.gray;
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ public class DebugGUI : MonoBehaviour, IAddComponentOnStart
|
||||
|
||||
WriteLine(1, $"FPS : {Mathf.Round(1f / Time.smoothDeltaTime)}");
|
||||
WriteLine(1, $"Ping : {Math.Round(NetworkTime.rtt * 1000.0)} ms");
|
||||
if (!QSBCore.DebugSettings.DrawGui)
|
||||
if (!QSBCore.DebugSettings.DrawGUI)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -422,16 +422,6 @@ public class DebugGUI : MonoBehaviour, IAddComponentOnStart
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (QSBCore.DebugSettings.DrawGhostAI)
|
||||
{
|
||||
foreach (var obj in QSBWorldSync.GetWorldObjects<IGhostObject>())
|
||||
{
|
||||
if (obj.ShouldDisplayDebug())
|
||||
{
|
||||
DrawLabel(obj.AttachedObject.transform, obj.ReturnLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnRenderObject() => DrawWorldObjectLines();
|
||||
@ -452,16 +442,6 @@ public class DebugGUI : MonoBehaviour, IAddComponentOnStart
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (QSBCore.DebugSettings.DrawGhostAI)
|
||||
{
|
||||
foreach (var obj in QSBWorldSync.GetWorldObjects<IGhostObject>())
|
||||
{
|
||||
if (obj.ShouldDisplayDebug())
|
||||
{
|
||||
obj.DisplayLines();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawLabel(Transform obj, string label)
|
||||
|
@ -16,7 +16,7 @@ public static class DebugLog
|
||||
|
||||
public static void ToConsole(string message, MessageType type = MessageType.Message)
|
||||
{
|
||||
if (QSBCore.DebugSettings.InstanceIdInLogs)
|
||||
if (QSBCore.DebugSettings.InstanceIDInLogs)
|
||||
{
|
||||
message = $"[{ProcessInstanceId}] " + message;
|
||||
}
|
||||
|
@ -1,63 +1,65 @@
|
||||
using Newtonsoft.Json;
|
||||
using OWML.Common;
|
||||
|
||||
namespace QSB.Utility;
|
||||
|
||||
[JsonObject(MemberSerialization.OptIn)]
|
||||
/// <summary>
|
||||
/// purely organizational class to store all debug settings
|
||||
/// </summary>
|
||||
public class DebugSettings
|
||||
{
|
||||
[JsonProperty("logQSBMessages")]
|
||||
public bool LogQSBMessages;
|
||||
|
||||
[JsonProperty("instanceIdInLogs")]
|
||||
public bool InstanceIdInLogs;
|
||||
|
||||
[JsonProperty("hookDebugLogs")]
|
||||
public bool HookDebugLogs;
|
||||
|
||||
[JsonProperty("avoidTimeSync")]
|
||||
public bool AvoidTimeSync;
|
||||
|
||||
[JsonProperty("autoStart")]
|
||||
public bool AutoStart;
|
||||
|
||||
[JsonProperty("kickEveryone")]
|
||||
public bool KickEveryone;
|
||||
|
||||
[JsonProperty("disableLoopDeath")]
|
||||
public bool DisableLoopDeath;
|
||||
|
||||
[JsonProperty("latencySimulation")]
|
||||
public int LatencySimulation;
|
||||
|
||||
[JsonProperty("randomizeSkins")]
|
||||
public bool RandomizeSkins;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout in seconds
|
||||
/// </summary>
|
||||
[JsonProperty("timeout")]
|
||||
public int Timeout = 40;
|
||||
|
||||
[JsonProperty("debugMode")]
|
||||
public bool DebugMode;
|
||||
|
||||
[JsonProperty("drawGui")]
|
||||
private bool _drawGui;
|
||||
public bool DrawGui => DebugMode && _drawGui;
|
||||
private bool _logQSBMessages;
|
||||
public bool LogQSBMessages => DebugMode && _logQSBMessages;
|
||||
|
||||
private bool _instanceIdInLogs;
|
||||
public bool InstanceIDInLogs => DebugMode && _instanceIdInLogs;
|
||||
|
||||
private bool _hookDebugLogs;
|
||||
public bool HookDebugLogs => DebugMode && _hookDebugLogs;
|
||||
|
||||
private bool _avoidTimeSync;
|
||||
public bool AvoidTimeSync => DebugMode && _avoidTimeSync;
|
||||
|
||||
private bool _autoStart;
|
||||
public bool AutoStart => DebugMode && _autoStart;
|
||||
|
||||
private int _latencySimulation;
|
||||
public int LatencySimulation => DebugMode ? _latencySimulation : 0;
|
||||
|
||||
private bool _drawGUI;
|
||||
public bool DrawGUI => DebugMode && _drawGUI;
|
||||
|
||||
[JsonProperty("drawLines")]
|
||||
private bool _drawLines;
|
||||
public bool DrawLines => DebugMode && _drawLines;
|
||||
|
||||
[JsonProperty("drawLabels")]
|
||||
private bool _drawLabels;
|
||||
public bool DrawLabels => DebugMode && _drawLabels;
|
||||
|
||||
[JsonProperty("drawGhostAI")]
|
||||
private bool _drawGhostAI;
|
||||
public bool DrawGhostAI => DebugMode && _drawGhostAI;
|
||||
|
||||
[JsonProperty("greySkybox")]
|
||||
private bool _greySkybox;
|
||||
public bool GreySkybox => DebugMode && _greySkybox;
|
||||
|
||||
private string _steamTestIpAddress;
|
||||
public string SteamTestIpAddress => DebugMode ? _steamTestIpAddress : null;
|
||||
|
||||
private bool _steamFakeNetworkErrors;
|
||||
public bool SteamFakeNetworkErrors => DebugMode && _steamFakeNetworkErrors;
|
||||
|
||||
public void Update(IModConfig config)
|
||||
{
|
||||
DebugMode = config.GetSettingsValue<bool>("debugMode");
|
||||
|
||||
_instanceIdInLogs = config.GetSettingsValue<bool>("instanceIdInLogs");
|
||||
_hookDebugLogs = config.GetSettingsValue<bool>("hookDebugLogs");
|
||||
_avoidTimeSync = config.GetSettingsValue<bool>("avoidTimeSync");
|
||||
_autoStart = config.GetSettingsValue<bool>("autoStart");
|
||||
_drawGUI = config.GetSettingsValue<bool>("drawGui");
|
||||
_drawLines = config.GetSettingsValue<bool>("drawLines");
|
||||
_drawLabels = config.GetSettingsValue<bool>("drawLabels");
|
||||
_greySkybox = config.GetSettingsValue<bool>("greySkybox");
|
||||
_latencySimulation = config.GetSettingsValue<int>("latencySimulation");
|
||||
_logQSBMessages = config.GetSettingsValue<bool>("logQSBMessages");
|
||||
_steamTestIpAddress = config.GetSettingsValue<string>("steamTestIpAddress");
|
||||
_steamFakeNetworkErrors = config.GetSettingsValue<bool>("steamFakeNetworkErrors");
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,12 @@
|
||||
"value": "localhost",
|
||||
"tooltip": "Used if you leave the connect prompt blank."
|
||||
},
|
||||
"timeout": {
|
||||
"title": "Timeout",
|
||||
"type": "number",
|
||||
"value": 30,
|
||||
"tooltip": "How many seconds it takes for a connection to time out. Used both when connecting and checked when playing."
|
||||
},
|
||||
"showPlayerNames": {
|
||||
"title": "Show Player Names",
|
||||
"type": "toggle",
|
||||
@ -57,12 +63,93 @@
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "If disabled, the planet icon shown for each player will be replaced by a question mark (?) in thematically appropriate situations."
|
||||
} ,
|
||||
},
|
||||
"textChatInput": {
|
||||
"title": "Text Chat Input",
|
||||
"type": "toggle",
|
||||
"value": true,
|
||||
"tooltip": "Disable this if using NomaiVR, or any other mod with conflicting inputs."
|
||||
},
|
||||
"Debug Options": {
|
||||
"type": "separator"
|
||||
},
|
||||
"debugMode": {
|
||||
"title": "[DEBUG] Debug Mode",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Enables debug mode. Prints more helpful logs. If this is disabled, none of the following settings do anything."
|
||||
},
|
||||
"instanceIdInLogs": {
|
||||
"title": "[DEBUG] Instance ID in Logs",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Appends the game instance id to every log message sent."
|
||||
},
|
||||
"hookDebugLogs": {
|
||||
"title": "[DEBUG] Hook Debug Logs",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Prints every Unity warning + error to the logs."
|
||||
},
|
||||
"avoidTimeSync": {
|
||||
"title": "[DEBUG] Disable Time Sync",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] If enabled, you will no longer fast forward / pause to match the server time. THIS CAN BREAK A LOT OF STUFF!"
|
||||
},
|
||||
"autoStart": {
|
||||
"title": "[DEBUG] Auto Start",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Automatically host or connect to localhost on game launch (depending on which instance of the game is opened first)."
|
||||
},
|
||||
"drawGui": {
|
||||
"title": "[DEBUG] Draw GUI",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Draws a lot of helpful text at the top of the screen."
|
||||
},
|
||||
"drawLines": {
|
||||
"title": "[DEBUG] Draw Lines",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Draws a lot of helpful lines between stuff. LAGGY!"
|
||||
},
|
||||
"drawLabels": {
|
||||
"title": "[DEBUG] Draw Labels",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Draws a lot of helpful labels on stuff. LAGGY!"
|
||||
},
|
||||
"greySkybox": {
|
||||
"title": "[DEBUG] Grey Skybox",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Makes the skybox grey."
|
||||
},
|
||||
"latencySimulation": {
|
||||
"title": "[DEBUG] Latency Simulation",
|
||||
"type": "number",
|
||||
"value": 0,
|
||||
"tooltip": "[DEBUG] How many seconds of latency to simulate."
|
||||
},
|
||||
"logQSBMessages": {
|
||||
"title": "[DEBUG] Log QSB Messages",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] If enabled, QSB creates log files of every network message received and transmitted."
|
||||
},
|
||||
"steamTestIpAddress": {
|
||||
"title": "[DEBUG] Steam Test IP Address",
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"tooltip": "[DEBUG] If not empty, the steam transport will use this ip:port when listening/connecting. Host should probably use 0.0.0.0:port."
|
||||
},
|
||||
"steamFakeNetworkErrors": {
|
||||
"title": "[DEBUG] Steam Fake Network Errors",
|
||||
"type": "toggle",
|
||||
"value": false,
|
||||
"tooltip": "[DEBUG] Aggressively simulates common network errors (loss, lag, reordering, duplication) to test resiliency."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
"author": "Nebula, John, Alek, & Rai",
|
||||
"name": "Quantum Space Buddies",
|
||||
"uniqueName": "Raicuparta.QuantumSpaceBuddies",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.0",
|
||||
"owmlVersion": "2.14.0",
|
||||
"dependencies": [ "JohnCorby.VanillaFix" ],
|
||||
"pathsToPreserve": [ "debugsettings.json" ],
|
||||
|
@ -99,9 +99,9 @@ Outer Wilds Online is easier to set up, but much more basic in its features. The
|
||||
|
||||
See [TRANSLATING.md](TRANSLATING.md)
|
||||
|
||||
## Development Setup / Contributing
|
||||
## Development Setup / Contributing / Debugging
|
||||
|
||||
See [DEVELOPMENT.md](DEVELOPMENT.md)
|
||||
See the [QSB Developer Wiki](https://github.com/qsb-dev/quantum-space-buddies/wiki)
|
||||
|
||||
## Authors and Special Thanks
|
||||
|
||||
|
143
SteamTransport/Client.cs
Normal file
143
SteamTransport/Client.cs
Normal file
@ -0,0 +1,143 @@
|
||||
using Mirror;
|
||||
using Steamworks;
|
||||
using System;
|
||||
|
||||
namespace SteamTransport;
|
||||
|
||||
public class Client
|
||||
{
|
||||
private SteamTransport _transport;
|
||||
private Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t> _onStatusChanged;
|
||||
|
||||
public Client(SteamTransport transport)
|
||||
{
|
||||
_transport = transport;
|
||||
|
||||
_onStatusChanged = Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t>.Create(t =>
|
||||
{
|
||||
_transport.Log($"STATUS CHANGED for {t.m_info.m_szConnectionDescription}\n" +
|
||||
$" state = {t.m_info.m_eState}\n" +
|
||||
$" end = {(ESteamNetConnectionEnd)t.m_info.m_eEndReason} {t.m_info.m_szEndDebug}");
|
||||
// SteamNetworkingSockets.GetDetailedConnectionStatus(t.m_hConn, out var status, 1000);
|
||||
// _transport.Log(status);
|
||||
|
||||
switch (t.m_info.m_eState)
|
||||
{
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting:
|
||||
IsConnecting = true;
|
||||
IsConnected = false;
|
||||
break;
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
|
||||
IsConnecting = false;
|
||||
IsConnected = true;
|
||||
_transport.OnClientConnected?.Invoke();
|
||||
break;
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer:
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
||||
var result = SteamNetworkingSockets.CloseConnection(_conn, t.m_info.m_eEndReason, t.m_info.m_szEndDebug, false);
|
||||
if (result != true)
|
||||
{
|
||||
_transport.Log($"[warn] close returned {result}");
|
||||
}
|
||||
IsConnecting = false;
|
||||
IsConnected = false;
|
||||
_transport.OnClientError?.Invoke(TransportError.ConnectionClosed, t.m_info.m_szEndDebug);
|
||||
_transport.OnClientDisconnected?.Invoke();
|
||||
// OnClientDisconnected will cause mirror to shutdown the transport
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsConnecting;
|
||||
public bool IsConnected;
|
||||
|
||||
private HSteamNetConnection _conn;
|
||||
|
||||
|
||||
public void Connect(string address)
|
||||
{
|
||||
var options = Util.MakeOptions(_transport);
|
||||
|
||||
if (!string.IsNullOrEmpty(_transport.TestIpAddress))
|
||||
{
|
||||
var steamAddr = new SteamNetworkingIPAddr();
|
||||
var parsed = steamAddr.ParseString(_transport.TestIpAddress);
|
||||
if (!parsed)
|
||||
{
|
||||
_transport.OnClientError?.Invoke(TransportError.DnsResolve, $"couldnt parse address {_transport.TestIpAddress} when connecting");
|
||||
_transport.OnClientDisconnected?.Invoke(); // will show error box
|
||||
return;
|
||||
}
|
||||
|
||||
_conn = SteamNetworkingSockets.ConnectByIPAddress(ref steamAddr, options.Length, options);
|
||||
_transport.Log($"connecting to {steamAddr.ToDebugString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
var identity = new SteamNetworkingIdentity();
|
||||
var parsed = ulong.TryParse(address, out var steamId);
|
||||
if (!parsed)
|
||||
{
|
||||
_transport.OnClientError?.Invoke(TransportError.DnsResolve, $"couldnt parse address {address} when connecting");
|
||||
_transport.OnClientDisconnected?.Invoke(); // will show error box
|
||||
return;
|
||||
}
|
||||
identity.SetSteamID64(steamId);
|
||||
|
||||
_conn = SteamNetworkingSockets.ConnectP2P(ref identity, 0, options.Length, options);
|
||||
_transport.Log($"connecting to {identity.ToDebugString()}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Send(ArraySegment<byte> segment, int channelId)
|
||||
{
|
||||
var result = _conn.Send(segment, channelId);
|
||||
if (result != EResult.k_EResultOK)
|
||||
{
|
||||
_transport.Log($"[warn] send returned {result}");
|
||||
}
|
||||
_transport.OnClientDataSent?.Invoke(segment, channelId);
|
||||
}
|
||||
|
||||
public void Receive()
|
||||
{
|
||||
var ppOutMessages = new IntPtr[Util.MaxMessages];
|
||||
var numMessages = SteamNetworkingSockets.ReceiveMessagesOnConnection(_conn, ppOutMessages, ppOutMessages.Length);
|
||||
for (var i = 0; i < numMessages; i++)
|
||||
{
|
||||
var (segment, channelId) = Util.Receive(ppOutMessages[i]);
|
||||
_transport.OnClientDataReceived?.Invoke(segment, channelId);
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
var result = SteamNetworkingSockets.FlushMessagesOnConnection(_conn);
|
||||
if (result != EResult.k_EResultOK && result != EResult.k_EResultIgnored) // flush does ignored when connecting. dont log cuz spam
|
||||
{
|
||||
_transport.Log($"[warn] flush returned {result}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
// theres a weird case where we arent doing an intentional disconnect but there isnt a status change disonnect either
|
||||
// not sure whats going on there, but ill slap a stack trace on it
|
||||
|
||||
_transport.Log($"client close\n{Environment.StackTrace}");
|
||||
var result = SteamNetworkingSockets.CloseConnection(_conn, 0, "client closed connection", false);
|
||||
if (result != true)
|
||||
{
|
||||
_transport.Log($"[warn] client close returned {result}");
|
||||
}
|
||||
IsConnecting = false;
|
||||
IsConnected = false;
|
||||
// its not an error for us to close ourselves intentionally
|
||||
// but we do it anyway cuz above comment
|
||||
_transport.OnClientError?.Invoke(TransportError.ConnectionClosed, "client closed connection, but you shouldnt be seeing this in game. make sure [DEBUG] Debug Mode is on and check logs for stack trace. report this please!");
|
||||
_transport.OnClientDisconnected?.Invoke();
|
||||
|
||||
_onStatusChanged.Dispose();
|
||||
}
|
||||
}
|
155
SteamTransport/Server.cs
Normal file
155
SteamTransport/Server.cs
Normal file
@ -0,0 +1,155 @@
|
||||
using Mirror;
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SteamTransport;
|
||||
|
||||
public class Server
|
||||
{
|
||||
private SteamTransport _transport;
|
||||
private Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t> _onStatusChanged;
|
||||
|
||||
public Server(SteamTransport transport)
|
||||
{
|
||||
_transport = transport;
|
||||
|
||||
_onStatusChanged = Steamworks.Callback<SteamNetConnectionStatusChangedCallback_t>.Create(t =>
|
||||
{
|
||||
_transport.Log($"STATUS CHANGED for {t.m_info.m_szConnectionDescription}\n" +
|
||||
$" state = {t.m_info.m_eState}\n" +
|
||||
$" end = {(ESteamNetConnectionEnd)t.m_info.m_eEndReason} {t.m_info.m_szEndDebug}");
|
||||
// SteamNetworkingSockets.GetDetailedConnectionStatus(t.m_hConn, out var status, 1000);
|
||||
// _transport.Log(status);
|
||||
|
||||
switch (t.m_info.m_eState)
|
||||
{
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting:
|
||||
{
|
||||
// mirror handles max connections. client will just get generic disconnect message, but its okay.
|
||||
var result = SteamNetworkingSockets.AcceptConnection(t.m_hConn);
|
||||
if (result != EResult.k_EResultOK)
|
||||
{
|
||||
_transport.Log($"[warn] accept {t.m_info.m_szConnectionDescription} returned {result}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
|
||||
_conns.Add(t.m_hConn);
|
||||
_transport.OnServerConnected?.Invoke((int)t.m_hConn.m_HSteamNetConnection);
|
||||
break;
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer:
|
||||
// this logs an error below even tho it isnt really an error. its fine
|
||||
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
|
||||
{
|
||||
var result = SteamNetworkingSockets.CloseConnection(t.m_hConn, t.m_info.m_eEndReason, t.m_info.m_szEndDebug, false);
|
||||
if (result != true)
|
||||
{
|
||||
_transport.Log($"[warn] close {t.m_info.m_szConnectionDescription} returned {result}");
|
||||
}
|
||||
_conns.Remove(t.m_hConn);
|
||||
_transport.OnServerError?.Invoke((int)t.m_hConn.m_HSteamNetConnection, TransportError.ConnectionClosed, t.m_info.m_szEndDebug);
|
||||
_transport.OnServerDisconnected?.Invoke((int)t.m_hConn.m_HSteamNetConnection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public bool IsListening;
|
||||
private HSteamListenSocket _listenSocket;
|
||||
// mirror connection id is derived from uint to int cast here. seems to do unchecked cast and be fine
|
||||
private readonly List<HSteamNetConnection> _conns = new();
|
||||
|
||||
public void StartListening()
|
||||
{
|
||||
var options = Util.MakeOptions(_transport);
|
||||
|
||||
if (!string.IsNullOrEmpty(_transport.TestIpAddress))
|
||||
{
|
||||
var steamAddr = new SteamNetworkingIPAddr();
|
||||
var parsed = steamAddr.ParseString(_transport.TestIpAddress);
|
||||
if (!parsed)
|
||||
{
|
||||
_transport.OnServerError?.Invoke(-1, TransportError.DnsResolve, $"couldnt parse address {_transport.TestIpAddress} when listening");
|
||||
// dont really need to stop server here. mirror isnt designed to let us fail to listen anyway so this is all kinda silly
|
||||
return;
|
||||
}
|
||||
_listenSocket = SteamNetworkingSockets.CreateListenSocketIP(ref steamAddr, options.Length, options);
|
||||
_transport.Log($"listening on {steamAddr.ToDebugString()}");
|
||||
}
|
||||
else
|
||||
{
|
||||
_listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(0, options.Length, options);
|
||||
_transport.Log($"listening on p2p");
|
||||
}
|
||||
IsListening = true;
|
||||
}
|
||||
|
||||
public void Send(int connectionId, ArraySegment<byte> segment, int channelId)
|
||||
{
|
||||
var conn = new HSteamNetConnection((uint)connectionId);
|
||||
|
||||
var result = conn.Send(segment, channelId);
|
||||
if (result != EResult.k_EResultOK)
|
||||
{
|
||||
_transport.Log($"[warn] send {conn.ToDebugString()} returned {result}");
|
||||
}
|
||||
_transport.OnServerDataSent?.Invoke(connectionId, segment, channelId);
|
||||
}
|
||||
|
||||
public void Receive()
|
||||
{
|
||||
var ppOutMessages = new IntPtr[Util.MaxMessages];
|
||||
|
||||
foreach (var conn in _conns)
|
||||
{
|
||||
var numMessages = SteamNetworkingSockets.ReceiveMessagesOnConnection(conn, ppOutMessages, ppOutMessages.Length);
|
||||
for (var i = 0; i < numMessages; i++)
|
||||
{
|
||||
var (segment, channelId) = Util.Receive(ppOutMessages[i]);
|
||||
_transport.OnServerDataReceived?.Invoke((int)conn.m_HSteamNetConnection, segment, channelId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Flush()
|
||||
{
|
||||
foreach (var conn in _conns)
|
||||
{
|
||||
var result = SteamNetworkingSockets.FlushMessagesOnConnection(conn);
|
||||
if (result != EResult.k_EResultOK)
|
||||
{
|
||||
_transport.Log($"[warn] flush {conn.ToDebugString()} returned {result}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Disconnect(int connectionId)
|
||||
{
|
||||
var conn = new HSteamNetConnection((uint)connectionId);
|
||||
_transport.Log($"disconnect {conn.ToDebugString()}");
|
||||
var result = SteamNetworkingSockets.CloseConnection(conn, 0, "disconnected by server", false);
|
||||
if (result != true)
|
||||
{
|
||||
_transport.Log($"[warn] close {conn.ToDebugString()} returned {result}");
|
||||
}
|
||||
_conns.Remove(conn);
|
||||
// its not an error for us to disconnect a client intentionally
|
||||
_transport.OnServerDisconnected?.Invoke(connectionId);
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
// mirror disconnects all clients for us before this
|
||||
_transport.Log("stop server");
|
||||
var result = SteamNetworkingSockets.CloseListenSocket(_listenSocket);
|
||||
if (result != true)
|
||||
{
|
||||
_transport.Log($"[warn] stop server returned {result}");
|
||||
}
|
||||
IsListening = false;
|
||||
|
||||
_onStatusChanged.Dispose();
|
||||
}
|
||||
}
|
136
SteamTransport/SteamTransport.cs
Normal file
136
SteamTransport/SteamTransport.cs
Normal file
@ -0,0 +1,136 @@
|
||||
// https://github.com/MirrorNetworking/Mirror/blob/master/Assets/Mirror/Core/Transport.cs
|
||||
// https://partner.steamgames.com/doc/api/ISteamNetworkingSockets
|
||||
// https://partner.steamgames.com/doc/api/steamnetworkingtypes
|
||||
|
||||
using Mirror;
|
||||
using Steamworks;
|
||||
using System;
|
||||
|
||||
namespace SteamTransport;
|
||||
|
||||
public class SteamTransport : Transport
|
||||
{
|
||||
private Server _server;
|
||||
private Client _client;
|
||||
|
||||
/// <summary>
|
||||
/// logs will verbosely go here. must be set
|
||||
///
|
||||
/// my policy is to log every potential error here, but otherwise ignore it. then if its an actual issue i do OnClientError and handle it properly.
|
||||
/// </summary>
|
||||
public Action<string> Log;
|
||||
/// <summary>
|
||||
/// if set, will use this ip address and port for listening/connecting
|
||||
/// </summary>
|
||||
public string TestIpAddress;
|
||||
|
||||
/// <summary>
|
||||
/// timeout in ms when connecting, and timeout before detecting a loss in connection.
|
||||
/// after-connection timeout seems to be around 0-10 more seconds than specified.
|
||||
/// </summary>
|
||||
// default from steam https://github.com/ValveSoftware/GameNetworkingSockets/blob/master/src/steamnetworkingsockets/clientlib/csteamnetworkingsockets.cpp#L76
|
||||
public int Timeout = 10000;
|
||||
/// <summary>
|
||||
/// whether or not to simulate fake packet loss, lag, reorder, and dup
|
||||
/// </summary>
|
||||
public bool DoFakeNetworkErrors;
|
||||
|
||||
public override bool Available() => true;
|
||||
|
||||
public override bool ClientConnected() => _client.IsConnected;
|
||||
|
||||
public override void ClientConnect(string address)
|
||||
{
|
||||
_client = new Client(this);
|
||||
_client.Connect(address);
|
||||
}
|
||||
|
||||
public override void ClientSend(ArraySegment<byte> segment, int channelId = 0)
|
||||
{
|
||||
_client.Send(segment, channelId);
|
||||
}
|
||||
|
||||
public override void ClientDisconnect()
|
||||
{
|
||||
// mirror seems to cause this sometimes
|
||||
if (_client == null)
|
||||
{
|
||||
Log("tried to ClientDisconnect when client is null");
|
||||
return;
|
||||
}
|
||||
_client.Close();
|
||||
_client = null;
|
||||
}
|
||||
|
||||
public override Uri ServerUri() => throw new NotImplementedException("dont need to implement this i think");
|
||||
|
||||
public override bool ServerActive() => _server != null && _server.IsListening;
|
||||
|
||||
public override void ServerStart()
|
||||
{
|
||||
_server = new Server(this);
|
||||
_server.StartListening();
|
||||
}
|
||||
|
||||
public override void ServerSend(int connectionId, ArraySegment<byte> segment, int channelId = 0)
|
||||
{
|
||||
_server.Send(connectionId, segment, channelId);
|
||||
}
|
||||
|
||||
public override void ServerDisconnect(int connectionId)
|
||||
{
|
||||
_server.Disconnect(connectionId);
|
||||
}
|
||||
|
||||
public override string ServerGetClientAddress(int connectionId) => throw new NotImplementedException("dont need to implement this i think");
|
||||
|
||||
public override void ServerStop()
|
||||
{
|
||||
// mirror seems to cause this sometimes
|
||||
if (_server == null)
|
||||
{
|
||||
Log("tried to ServerStop when server is null");
|
||||
return;
|
||||
}
|
||||
_server.Close();
|
||||
_server = null;
|
||||
}
|
||||
|
||||
public override int GetMaxPacketSize(int channelId = 0) => Constants.k_cbMaxSteamNetworkingSocketsMessageSizeSend;
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
// gotta null check because might be only one existing
|
||||
if (_client != null)
|
||||
{
|
||||
_client.Close();
|
||||
_client = null;
|
||||
}
|
||||
if (_server != null)
|
||||
{
|
||||
_server.Close();
|
||||
_server = null;
|
||||
}
|
||||
}
|
||||
|
||||
// all of these update functions run all the time, so we must null check
|
||||
public override void ClientEarlyUpdate()
|
||||
{
|
||||
_client?.Receive();
|
||||
}
|
||||
|
||||
public override void ServerEarlyUpdate()
|
||||
{
|
||||
_server?.Receive();
|
||||
}
|
||||
|
||||
public override void ClientLateUpdate()
|
||||
{
|
||||
_client?.Flush();
|
||||
}
|
||||
|
||||
public override void ServerLateUpdate()
|
||||
{
|
||||
_server?.Flush();
|
||||
}
|
||||
}
|
16
SteamTransport/SteamTransport.csproj
Normal file
16
SteamTransport/SteamTransport.csproj
Normal file
@ -0,0 +1,16 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<AssemblyTitle>Steam Transport</AssemblyTitle>
|
||||
<Product>Steam Transport</Product>
|
||||
<Title>Steam Transport</Title>
|
||||
<Description>A custom Mirror transport based on FizzySteamworks</Description>
|
||||
<Authors>William Corby, Henry Pointer</Authors>
|
||||
<Company>William Corby, Henry Pointer</Company>
|
||||
<Copyright>Copyright © William Corby, Henry Pointer 2022-2025</Copyright>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" IncludeAssets="compile" />
|
||||
<PackageReference Include="OWML" Version="2.11.1" IncludeAssets="compile" />
|
||||
<Reference Include="..\Lib\*.dll" />
|
||||
</ItemGroup>
|
||||
</Project>
|
122
SteamTransport/Util.cs
Normal file
122
SteamTransport/Util.cs
Normal file
@ -0,0 +1,122 @@
|
||||
using Mirror;
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SteamTransport;
|
||||
|
||||
public static class Util
|
||||
{
|
||||
public const int MaxMessages = 256; // same as fizzy steamworks
|
||||
|
||||
private static int SendFlag2MirrorChannel(int sendFlag) => sendFlag switch
|
||||
{
|
||||
Constants.k_nSteamNetworkingSend_Reliable => Channels.Reliable,
|
||||
Constants.k_nSteamNetworkingSend_Unreliable => Channels.Unreliable
|
||||
};
|
||||
|
||||
private static int MirrorChannel2SendFlag(int mirrorChannel) => mirrorChannel switch
|
||||
{
|
||||
Channels.Reliable => Constants.k_nSteamNetworkingSend_Reliable,
|
||||
Channels.Unreliable => Constants.k_nSteamNetworkingSend_Unreliable
|
||||
};
|
||||
|
||||
public static string ToDebugString(this HSteamNetConnection conn)
|
||||
{
|
||||
SteamNetworkingSockets.GetConnectionInfo(conn, out var pInfo);
|
||||
return pInfo.m_szConnectionDescription;
|
||||
}
|
||||
|
||||
public static string ToDebugString(this SteamNetworkingIdentity ident)
|
||||
{
|
||||
ident.ToString(out var s);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static string ToDebugString(this SteamNetworkingIPAddr addr)
|
||||
{
|
||||
addr.ToString(out var s, true);
|
||||
return s;
|
||||
}
|
||||
|
||||
public static EResult Send(this HSteamNetConnection conn, ArraySegment<byte> segment, int channelId)
|
||||
{
|
||||
var handle = GCHandle.Alloc(segment.Array, GCHandleType.Pinned); // prevent moving or gc when passing to native function
|
||||
var result = SteamNetworkingSockets.SendMessageToConnection(conn, handle.AddrOfPinnedObject() + segment.Offset, (uint)segment.Count, MirrorChannel2SendFlag(channelId), out _);
|
||||
handle.Free();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static (ArraySegment<byte> segment, int channelId) Receive(IntPtr ppOutMessage)
|
||||
{
|
||||
var msg = SteamNetworkingMessage_t.FromIntPtr(ppOutMessage);
|
||||
var segment = new ArraySegment<byte>(new byte[msg.m_cbSize]);
|
||||
Marshal.Copy(msg.m_pData, segment.Array, 0, msg.m_cbSize);
|
||||
var channelId = SendFlag2MirrorChannel(msg.m_nFlags);
|
||||
SteamNetworkingMessage_t.Release(ppOutMessage);
|
||||
return (segment, channelId);
|
||||
}
|
||||
|
||||
public static SteamNetworkingConfigValue_t[] MakeOptions(SteamTransport transport)
|
||||
{
|
||||
var result = new List<SteamNetworkingConfigValue_t>();
|
||||
|
||||
result.Add(new SteamNetworkingConfigValue_t
|
||||
{
|
||||
m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_TimeoutInitial,
|
||||
m_eDataType = ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Int32,
|
||||
m_val = new SteamNetworkingConfigValue_t.OptionValue
|
||||
{
|
||||
m_int32 = transport.Timeout
|
||||
}
|
||||
});
|
||||
result.Add(new SteamNetworkingConfigValue_t
|
||||
{
|
||||
m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_TimeoutConnected,
|
||||
m_eDataType = ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Int32,
|
||||
m_val = new SteamNetworkingConfigValue_t.OptionValue
|
||||
{
|
||||
m_int32 = transport.Timeout
|
||||
}
|
||||
});
|
||||
|
||||
// 20% change of doing all, all delays are 200 ms. leads to about 1 second of rtt ping if enabled on both ends.
|
||||
if (transport.DoFakeNetworkErrors)
|
||||
{
|
||||
// global scope = dont apply to connection
|
||||
static void SetConfigValue(ESteamNetworkingConfigValue key, object value)
|
||||
{
|
||||
var handle = GCHandle.Alloc(value, GCHandleType.Pinned);
|
||||
SteamNetworkingUtils.SetConfigValue(
|
||||
key,
|
||||
ESteamNetworkingConfigScope.k_ESteamNetworkingConfig_Global,
|
||||
IntPtr.Zero,
|
||||
handle.Target switch
|
||||
{
|
||||
int => ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Int32,
|
||||
float => ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Float
|
||||
},
|
||||
handle.AddrOfPinnedObject()
|
||||
);
|
||||
handle.Free();
|
||||
}
|
||||
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketLoss_Send, (float)20);
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketLoss_Recv, (float)20);
|
||||
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketLag_Send, (int)200);
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketLag_Recv, (int)200);
|
||||
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketReorder_Send, (float)20);
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketReorder_Recv, (float)20);
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketReorder_Time, (int)200);
|
||||
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketDup_Send, (float)20);
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketDup_Recv, (float)20);
|
||||
SetConfigValue(ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_FakePacketDup_TimeMax, (int)200);
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
168
SteamTransportTest/Program.cs
Normal file
168
SteamTransportTest/Program.cs
Normal file
@ -0,0 +1,168 @@
|
||||
using HarmonyLib;
|
||||
using Steamworks;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace SteamTransportTest;
|
||||
|
||||
/// <summary>
|
||||
/// entry point for testing.
|
||||
/// should probably make this a separate project since it copies over steam api. idc rn
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("This is the test mode for the steam transport");
|
||||
|
||||
try
|
||||
{
|
||||
// copied from qsbcore and steamworks.net docs
|
||||
{
|
||||
if (!Packsize.Test())
|
||||
{
|
||||
Console.Error.WriteLine("[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.");
|
||||
}
|
||||
|
||||
if (!DllCheck.Test())
|
||||
{
|
||||
Console.Error.WriteLine("[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.");
|
||||
}
|
||||
|
||||
// yes, these have to be the same. even for connecting to ip address
|
||||
Environment.SetEnvironmentVariable("SteamAppId", "480");
|
||||
Environment.SetEnvironmentVariable("SteamGameId", "480");
|
||||
|
||||
var m_bInitialized = SteamAPI.Init();
|
||||
if (!m_bInitialized)
|
||||
{
|
||||
Console.Error.WriteLine("[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SteamClient.SetWarningMessageHook((severity, text) => Console.WriteLine(text));
|
||||
}
|
||||
|
||||
Console.WriteLine("press 1 for server, 2 for client");
|
||||
switch (Console.ReadKey(true).KeyChar)
|
||||
{
|
||||
case '1':
|
||||
Console.WriteLine("server");
|
||||
DoServer();
|
||||
break;
|
||||
case '2':
|
||||
Console.WriteLine("client");
|
||||
DoClient();
|
||||
break;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
SteamAPI.Shutdown();
|
||||
|
||||
Console.WriteLine("Done. Press any key to exit");
|
||||
Console.ReadKey();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoServer()
|
||||
{
|
||||
var transport = new SteamTransport.SteamTransport();
|
||||
transport.Log = Console.WriteLine;
|
||||
transport.TestIpAddress = "127.0.0.1:1234";
|
||||
// make timeout for client detecting server drop different than timeout for server detecting client drop
|
||||
transport.Timeout = 5000;
|
||||
transport.DoFakeNetworkErrors = false;
|
||||
|
||||
transport.OnServerError = (conn, error, s) => Console.Error.WriteLine($"ERROR {conn} {error} {s}");
|
||||
var theConn = -1;
|
||||
transport.OnServerConnected = conn => theConn = conn;
|
||||
transport.OnServerDataSent = (conn, bytes, i) => Console.WriteLine($"SEND {conn} {i} {bytes.Join()}");
|
||||
transport.OnServerDataReceived = (conn, bytes, i) => Console.WriteLine($"RECV {conn} {i} {bytes.Join()}");
|
||||
|
||||
transport.ServerStart();
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("press c to close, s to send, d to disconnect");
|
||||
var running = true;
|
||||
while (running)
|
||||
{
|
||||
transport.ServerEarlyUpdate();
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
switch (Console.ReadKey(true).KeyChar)
|
||||
{
|
||||
case 'c':
|
||||
// running = false;
|
||||
transport.ServerDisconnect(theConn); // mirror does this for us
|
||||
transport.ServerStop();
|
||||
break;
|
||||
case 's':
|
||||
transport.ServerSend(theConn, new ArraySegment<byte>(new byte[] { 1, 2, (byte)DateTime.Now.Millisecond, 4, 5 }, 1, 5 - 1));
|
||||
break;
|
||||
case 'd':
|
||||
transport.ServerDisconnect(theConn);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SteamAPI.RunCallbacks();
|
||||
transport.ServerLateUpdate();
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
transport.ServerDisconnect(theConn); // mirror does this for us
|
||||
transport.ServerStop();
|
||||
}
|
||||
}
|
||||
|
||||
private static void DoClient()
|
||||
{
|
||||
var transport = new SteamTransport.SteamTransport();
|
||||
transport.Log = Console.WriteLine;
|
||||
transport.TestIpAddress = "127.0.0.1:1234";
|
||||
transport.Timeout = 20000;
|
||||
transport.DoFakeNetworkErrors = true;
|
||||
|
||||
transport.OnClientError = (error, s) => Console.Error.WriteLine($"ERROR {error} {s}");
|
||||
transport.OnClientDataSent = (bytes, i) => Console.WriteLine($"SEND {i} {bytes.Join()}");
|
||||
transport.OnClientDataReceived = (bytes, i) => Console.WriteLine($"RECV {i} {bytes.Join()}");
|
||||
|
||||
transport.ClientConnect("76561198150564286");
|
||||
|
||||
try
|
||||
{
|
||||
Console.WriteLine("press c to close, s to send");
|
||||
|
||||
var running = true;
|
||||
// transport.OnClientDisconnected = () => running = false; // mirror normally does this
|
||||
while (running)
|
||||
{
|
||||
transport.ClientEarlyUpdate();
|
||||
if (Console.KeyAvailable)
|
||||
{
|
||||
switch (Console.ReadKey(true).KeyChar)
|
||||
{
|
||||
case 'c':
|
||||
// running = false;
|
||||
transport.ClientDisconnect();
|
||||
break;
|
||||
case 's':
|
||||
transport.ClientSend(new ArraySegment<byte>(new byte[] { 1, 2, (byte)DateTime.Now.Millisecond, 4, 5 }, 1, 5 - 1), 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
SteamAPI.RunCallbacks();
|
||||
transport.ClientLateUpdate();
|
||||
Thread.Sleep(10);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
transport.ClientDisconnect();
|
||||
}
|
||||
}
|
||||
}
|
26
SteamTransportTest/SteamTransportTest.csproj
Normal file
26
SteamTransportTest/SteamTransportTest.csproj
Normal file
@ -0,0 +1,26 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<AssemblyTitle>Steam Transport Test</AssemblyTitle>
|
||||
<Product>Steam Transport Test</Product>
|
||||
<Title>Steam Transport Test</Title>
|
||||
<Description>Test exe for steam transport</Description>
|
||||
<Authors>William Corby, Henry Pointer</Authors>
|
||||
<Company>William Corby, Henry Pointer</Company>
|
||||
<Copyright>Copyright © William Corby, Henry Pointer 2022-2025</Copyright>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="OuterWildsGameLibs" Version="1.1.14.768" />
|
||||
<ProjectReference Include="..\SteamTransport\SteamTransport.csproj" />
|
||||
<PackageReference Include="OWML" Version="2.11.1" />
|
||||
<Reference Include="..\Lib\*.dll" />
|
||||
</ItemGroup>
|
||||
<Target Name="copy standalone dlls" AfterTargets="PostBuildEvent">
|
||||
<ItemGroup>
|
||||
<_Files Remove="@(_Files)" />
|
||||
<_Files Include="..\Lib\*.dll" />
|
||||
<_Files Include="standaloneLibs\*.dll" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(_Files)" DestinationFolder="$(OutputPath)" />
|
||||
</Target>
|
||||
</Project>
|
BIN
SteamTransportTest/standaloneLibs/UnityEngine.CoreModule.dll
Normal file
BIN
SteamTransportTest/standaloneLibs/UnityEngine.CoreModule.dll
Normal file
Binary file not shown.
BIN
SteamTransportTest/standaloneLibs/steam_api64.dll
Normal file
BIN
SteamTransportTest/standaloneLibs/steam_api64.dll
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user