Skip to content

Commit

Permalink
Merge pull request #69 from project-fika/natpunch-3.9-dev
Browse files Browse the repository at this point in the history
Natpunch 3.9 dev
  • Loading branch information
Lacyway authored Jun 20, 2024
2 parents bb8e5f8 + 0e5d79f commit 10e08c2
Show file tree
Hide file tree
Showing 50 changed files with 2,393 additions and 62 deletions.
12 changes: 11 additions & 1 deletion Fika.Core/Coop/GameMode/CoopGame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using EFT.Counters;
using EFT.EnvironmentEffect;
using EFT.Game.Spawning;
using EFT.HealthSystem;
using EFT.Interactive;
using EFT.InventoryLogic;
using EFT.UI;
Expand All @@ -29,6 +28,7 @@
using Fika.Core.Networking;
using Fika.Core.Networking.Http;
using Fika.Core.Networking.Http.Models;
using Fika.Core.Networking.NatPunch;
using Fika.Core.Networking.Packets.GameWorld;
using Fika.Core.UI.Models;
using HarmonyLib;
Expand Down Expand Up @@ -924,6 +924,16 @@ public override async Task<LocalPlayer> vmethod_2(int playerId, Vector3 position

await WaitForPlayers();

if(isServer && FikaPlugin.UseNatPunching.Value)
{
FikaNatPunchServer natPunchServer = Singleton<FikaServer>.Instance.FikaNatPunchServer;

if (natPunchServer != null && natPunchServer.Connected)
{
natPunchServer.Close();
}
}

fikaDebug = gameObject.AddComponent<FikaDebug>();

Destroy(customButton);
Expand Down
2 changes: 2 additions & 0 deletions Fika.Core/Coop/Utils/FikaBackendUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public static class FikaBackendUtils
public static WeatherClass[] Nodes = null;
public static string RemoteIp;
public static int RemotePort;
public static int LocalPort = 0;
public static bool IsHostNatPunch = false;
private static string groupId;

public static MatchmakerTimeHasCome.GClass3187 ScreenController;
Expand Down
34 changes: 31 additions & 3 deletions Fika.Core/Coop/Utils/NetManagerUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ public static class NetManagerUtils
private static ManualLogSource logger = BepInEx.Logging.Logger.CreateLogSource("NetManagerUtils");
public static GameObject FikaGameObject;

public static void CreateFikaGameObject()
{
FikaGameObject = new GameObject("FikaGameObject");
Object.DontDestroyOnLoad(FikaGameObject);
logger.LogInfo("FikaGameObject has been created!");
}

public static void CreateNetManager(bool isServer)
{
if (FikaGameObject == null)
{
FikaGameObject = new GameObject("FikaGameObject");
Object.DontDestroyOnLoad(FikaGameObject);
logger.LogInfo("FikaGameObject has been created!");
CreateFikaGameObject();
}

if (isServer)
Expand All @@ -36,6 +41,18 @@ public static void CreateNetManager(bool isServer)
}
}

public static void CreatePingingClient()
{
if (FikaGameObject == null)
{
CreateFikaGameObject();
}

FikaPingingClient pingingClient = FikaGameObject.AddComponent<FikaPingingClient>();
Singleton<FikaPingingClient>.Create(pingingClient);
logger.LogInfo("FikaPingingClient has started!");
}

public static void DestroyNetManager(bool isServer)
{
if (FikaGameObject != null)
Expand All @@ -55,6 +72,17 @@ public static void DestroyNetManager(bool isServer)
}
}

public static void DestroyPingingClient()
{
if(FikaGameObject != null)
{
Singleton<FikaPingingClient>.Instance.StopKeepAliveRoutine();
Singleton<FikaPingingClient>.Instance.NetClient.Stop();
Singleton<FikaPingingClient>.TryRelease(Singleton<FikaPingingClient>.Instance);
logger.LogInfo("Destroyed FikaPingingClient");
}
}

public static Task InitNetManager(bool isServer)
{
if (FikaGameObject != null)
Expand Down
3 changes: 3 additions & 0 deletions Fika.Core/Fika.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
<Reference Include="Unity.TextMeshPro" HintPath="..\References\Unity.TextMeshPro.dll" Private="false" />
<Reference Include="UnityEngine" HintPath="..\References\UnityEngine.dll" Private="false" />
<Reference Include="UnityEngine.UI" HintPath="..\References\UnityEngine.UI.dll" Private="false" />
<Reference Include="websocket-sharp">
<HintPath>..\References\websocket-sharp.dll</HintPath>
</Reference>
</ItemGroup>

<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition=" '$(OS)' == 'Windows_NT' ">
Expand Down
17 changes: 10 additions & 7 deletions Fika.Core/FikaPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ public class FikaPlugin : BaseUnityPlugin
public static ConfigEntry<float> AutoRefreshRate { get; set; }
public static ConfigEntry<int> UDPPort { get; set; }
public static ConfigEntry<bool> UseUPnP { get; set; }
public static ConfigEntry<bool> UseNatPunching { get; set; }
public static ConfigEntry<int> ConnectionTimeout { get; set; }

// Gameplay
Expand All @@ -192,6 +193,7 @@ protected void Awake()
{
Instance = this;

GetClientConfig();
SetupConfig();

new FikaVersionLabel_Patch().Enable();
Expand Down Expand Up @@ -227,7 +229,6 @@ protected void Awake()
BundleLoaderPlugin.Create();

FikaAirdropUtil.GetConfigFromServer();
GetClientConfig();
BotSettingsRepoClass.Init();

if (AllowItemSending)
Expand Down Expand Up @@ -395,17 +396,19 @@ private void SetupConfig()

// Network

NativeSockets = Config.Bind(section: "Network", "Native Sockets", false, new ConfigDescription("Use NativeSockets for gameplay traffic. This uses direct socket calls for send/receive to drastically increase speed and reduce GC pressure. Only for Windows/Linux and might not always work.", tags: new ConfigurationManagerAttributes() { Order = 7 }));
NativeSockets = Config.Bind(section: "Network", "Native Sockets", false, new ConfigDescription("Use NativeSockets for gameplay traffic. This uses direct socket calls for send/receive to drastically increase speed and reduce GC pressure. Only for Windows/Linux and might not always work.", tags: new ConfigurationManagerAttributes() { Order = 8 }));

ForceIP = Config.Bind("Network", "Force IP", "", new ConfigDescription("Forces the server when hosting to use this IP when broadcasting to the backend instead of automatically trying to fetch it. Leave empty to disable.", tags: new ConfigurationManagerAttributes() { Order = 7 }));

ForceIP = Config.Bind("Network", "Force IP", "", new ConfigDescription("Forces the server when hosting to use this IP when broadcasting to the backend instead of automatically trying to fetch it. Leave empty to disable.", tags: new ConfigurationManagerAttributes() { Order = 6 }));
ForceBindIP = Config.Bind("Network", "Force Bind IP", "", new ConfigDescription("Forces the server when hosting to use this local IP when starting the server. Useful if you are hosting on a VPN.", new AcceptableValueList<string>(GetLocalAddresses()), new ConfigurationManagerAttributes() { Order = 6 }));

ForceBindIP = Config.Bind("Network", "Force Bind IP", "", new ConfigDescription("Forces the server when hosting to use this local IP when starting the server. Useful if you are hosting on a VPN.", new AcceptableValueList<string>(GetLocalAddresses()), new ConfigurationManagerAttributes() { Order = 5 }));
AutoRefreshRate = Config.Bind("Network", "Auto Server Refresh Rate", 10f, new ConfigDescription("Every X seconds the client will ask the server for the list of matches while at the lobby screen.", new AcceptableValueRange<float>(3f, 60f), new ConfigurationManagerAttributes() { Order = 5 }));

AutoRefreshRate = Config.Bind("Network", "Auto Server Refresh Rate", 10f, new ConfigDescription("Every X seconds the client will ask the server for the list of matches while at the lobby screen.", new AcceptableValueRange<float>(3f, 60f), new ConfigurationManagerAttributes() { Order = 4 }));
UDPPort = Config.Bind("Network", "UDP Port", 25565, new ConfigDescription("Port to use for UDP gameplay packets.", tags: new ConfigurationManagerAttributes() { Order = 4 }));

UDPPort = Config.Bind("Network", "UDP Port", 25565, new ConfigDescription("Port to use for UDP gameplay packets.", tags: new ConfigurationManagerAttributes() { Order = 3 }));
UseUPnP = Config.Bind("Network", "Use UPnP", false, new ConfigDescription("Attempt to open ports using UPnP. Useful if you cannot open ports yourself but the router supports UPnP.", tags: new ConfigurationManagerAttributes() { Order = 3 }));

UseUPnP = Config.Bind("Network", "Use UPnP", false, new ConfigDescription("Attempt to open ports using UPnP. Useful if you cannot open ports yourself but the router supports UPnP.", tags: new ConfigurationManagerAttributes() { Order = 2 }));
UseNatPunching = Config.Bind("Network", "Use NAT Punching", false, new ConfigDescription("Use NAT punching as a NAT traversal method for hosting a raid. Only works with fullcone NAT type routers. UPnP, Force IP and Force Bind IP are disabled in this mode.", tags: new ConfigurationManagerAttributes() { Order = 2 }));

ConnectionTimeout = Config.Bind("Network", "Connection Timeout", 15, new ConfigDescription("How long it takes for a connection to be considered dropped if no packets are received.", new AcceptableValueRange<int>(5, 60), new ConfigurationManagerAttributes() { Order = 1 }));

Expand Down
7 changes: 6 additions & 1 deletion Fika.Core/Networking/FikaClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,12 @@ public void Init()
EnableStatistics = true
};

_netClient.Start();
if (FikaBackendUtils.IsHostNatPunch)
{
NetManagerUtils.DestroyPingingClient();
}

_netClient.Start(FikaBackendUtils.LocalPort);

string ip = FikaBackendUtils.RemoteIp;
int port = FikaBackendUtils.RemotePort;
Expand Down
119 changes: 93 additions & 26 deletions Fika.Core/Networking/FikaPingingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,31 @@
using Fika.Core.Coop.Utils;
using Fika.Core.Networking.Http;
using Fika.Core.Networking.Http.Models;
using Fika.Core.Networking.NatPunch;
using LiteNetLib;
using LiteNetLib.Utils;
using SPT.Common.Http;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using UnityEngine;

namespace Fika.Core.Networking
{
internal class FikaPingingClient(string serverId) : INetEventListener
public class FikaPingingClient : MonoBehaviour, INetEventListener
{
public NetManager NetClient;
private readonly ManualLogSource _logger = Logger.CreateLogSource("Fika.PingingClient");
private readonly string serverId = serverId;
private readonly ManualLogSource _logger = BepInEx.Logging.Logger.CreateLogSource("Fika.PingingClient");
private IPEndPoint remoteEndPoint;
private IPEndPoint localEndPoint;
private IPEndPoint remoteStunEndPoint;
private int localPort = 0;
public bool Received = false;
private Coroutine keepAliveRoutine;
private Task natPunchRequestTask;

public bool Init()
public bool Init(string serverId)
{
NetClient = new(this)
{
Expand All @@ -28,6 +36,8 @@ public bool Init()
GetHostRequest body = new(serverId);
GetHostResponse result = FikaRequestHandler.GetHost(body);

FikaBackendUtils.IsHostNatPunch = result.NatPunch;

string ip = result.Ips[0];
string localIp = null;
if (result.Ips.Length > 1)
Expand All @@ -54,26 +64,84 @@ public bool Init()
localEndPoint = new(IPAddress.Parse(localIp), port);
}

NetClient.Start();
if (FikaBackendUtils.IsHostNatPunch)
{
natPunchRequestTask = Task.Run(() => NatPunchRequest(serverId));
}

NetClient.Start(localPort);

return true;
}

public void PingEndPoint()
public void PingEndPoint(string message)
{
if (Received)
{
return;
}

NetDataWriter writer = new();
writer.Put("fika.hello");
writer.Put(message);

NetClient.SendUnconnectedMessage(writer, remoteEndPoint);
if (localEndPoint != null)
{
NetClient.SendUnconnectedMessage(writer, localEndPoint);
}

if (remoteStunEndPoint != null && natPunchRequestTask.IsCompleted)
{
NetClient.SendUnconnectedMessage(writer, remoteStunEndPoint);
}
}

public async void NatPunchRequest(string serverId)
{
FikaNatPunchClient fikaNatPunchClient = new FikaNatPunchClient();
fikaNatPunchClient.Connect();

if (!fikaNatPunchClient.Connected)
{
_logger.LogError("Unable to connect to NatPunchRelayService.");
return;
}

StunIPEndPoint localStunEndPoint = NatPunchUtils.CreateStunEndPoint();

if (localStunEndPoint == null)
{
_logger.LogError("Nat Punch Request failed: Stun Endpoint is null.");
return;
}

GetHostStunRequest getStunRequest = new GetHostStunRequest(serverId, RequestHandler.SessionId, localStunEndPoint.Remote.Address.ToString(), localStunEndPoint.Remote.Port);
GetHostStunResponse getStunResponse = await fikaNatPunchClient.GetHostStun(getStunRequest);

fikaNatPunchClient.Close();

remoteStunEndPoint = new IPEndPoint(IPAddress.Parse(getStunResponse.StunIp), getStunResponse.StunPort);

localPort = localStunEndPoint.Local.Port;
}

public void StartKeepAliveRoutine()
{
keepAliveRoutine = StartCoroutine(KeepAlive());
}

public void StopKeepAliveRoutine()
{
if(keepAliveRoutine != null)
{
StopCoroutine(keepAliveRoutine);
}
}

public IEnumerator KeepAlive()
{
while(true)
{
PingEndPoint("fika.keepalive");
NetClient.PollEvents();

yield return new WaitForSeconds(1.0f);
}
}

public void OnConnectionRequest(ConnectionRequest request)
Expand All @@ -98,23 +166,22 @@ public void OnNetworkReceive(NetPeer peer, NetPacketReader reader, byte channelN

public void OnNetworkReceiveUnconnected(IPEndPoint remoteEndPoint, NetPacketReader reader, UnconnectedMessageType messageType)
{
if (Received)
{
return;
}
_logger.LogInfo("Received response from server, parsing...");

if (reader.TryGetString(out string result))
{
if (result == "fika.hello")
{
Received = true;
FikaBackendUtils.RemoteIp = remoteEndPoint.Address.ToString();
FikaBackendUtils.RemotePort = remoteEndPoint.Port;
}
else
switch(result)
{
_logger.LogError("Data was not as expected");
case "fika.hello":
Received = true;
FikaBackendUtils.RemoteIp = remoteEndPoint.Address.ToString();
FikaBackendUtils.RemotePort = remoteEndPoint.Port;
FikaBackendUtils.LocalPort = localPort;
break;
case "fika.keepalive":
// Do nothing
break;
default:
_logger.LogError("Data was not as expected");
break;
}
}
else
Expand Down
Loading

0 comments on commit 10e08c2

Please sign in to comment.