This commit is contained in:
Sardelka
2022-08-10 20:42:47 +08:00
parent 01c668b159
commit ff9e16bd5e
21 changed files with 598 additions and 415 deletions

13
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: [RAGECOOP] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
#patreon: # Replace with a single Patreon username
#open_collective: # Replace with a single Open Collective username
#ko_fi: # Replace with a single Ko-fi username
#tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
#community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
#liberapay: # Replace with a single Liberapay username
#issuehunt: # Replace with a single IssueHunt username
#otechie: # Replace with a single Otechie username
#lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: ['https://www.buymeacoffee.com/xEntenKoeniqx', 'https://patreon.com/Sardelka']

View File

@ -1,6 +1,7 @@
# 🌐 RAGECOOP
[![Downloads][downloads-shield]][downloads-url]
[![Contributors][contributors-shield]][contributors-url]
[![Forks][forks-shield]][forks-url]
[![Stargazers][stars-shield]][stars-url]
@ -55,9 +56,6 @@ You can also download nightly builds [here](https://github.com/RAGECOOP/RAGECOOP
Please note that this is incompatible with all previous versions of ragecoop, remove old files before installing.
# Support us
<a href="https://patreon.com/Sardelka"><img src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3DSardelka%26type%3Dpatrons&style=for-the-badge" /></a>
# 🦆 Special thanks to
- [Makinolo](https://github.com/Makinolo), [oldnapalm](https://github.com/oldnapalm)
@ -70,6 +68,8 @@ Please note that this is incompatible with all previous versions of ragecoop, re
# 📝 License
This project is licensed under [MIT license](https://github.com/RAGECOOP/RAGECOOP-V/blob/main/LICENSE)
[downloads-shield]: https://img.shields.io/github/downloads/RAGECOOP/RAGECOOP-V/total?style=for-the-badge
[downloads-url]: https://github.com/RAGECOOP/RAGECOOP-V/releases
[contributors-shield]: https://img.shields.io/github/contributors/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge
[contributors-url]: https://github.com/RAGECOOP/RAGECOOP-V/graphs/contributors
[forks-shield]: https://img.shields.io/github/forks/RAGECOOP/RAGECOOP-V.svg?style=for-the-badge

View File

@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using RageCoop.Core;
using Lidgren.Network;
using System.Net;
namespace RageCoop.Client
{
internal static partial class HolePunch
{
static HolePunch()
{
// Periodically send hole punch message as needed
var timer=new Timer(1000);
timer.Elapsed+=DoPunch;
timer.Enabled=true;
}
private static void DoPunch(object sender, ElapsedEventArgs e)
{
try
{
if (!Networking.IsOnServer) { return; }
foreach (var p in PlayerList.Players.Values.ToArray())
{
if (p.InternalEndPoint!=null && p.ExternalEndPoint!=null && (p.Connection==null || p.Connection.Status==NetConnectionStatus.Disconnected))
{
Main.Logger.Trace($"Sending HolePunch message to {p.InternalEndPoint},{p.ExternalEndPoint}. {p.Username}:{p.PedID}");
var msg = Networking.Peer.CreateMessage();
new Packets.HolePunch
{
Puncher=Main.LocalPlayerID,
Status=p.HolePunchStatus
}.Pack(msg);
Networking.Peer.SendUnconnectedMessage(msg, new List<IPEndPoint> { p.InternalEndPoint, p.ExternalEndPoint });
}
}
}
catch (Exception ex)
{
Main.Logger.Error(ex);
}
}
public static void Add(Packets.HolePunchInit p)
{
if(PlayerList.Players.TryGetValue(p.TargetID,out var player))
{
Main.Logger.Debug($"{p.TargetID},{player.Username} added to HolePunch target");
player.InternalEndPoint = CoreUtils.StringToEndPoint(p.TargetInternal);
player.ExternalEndPoint = CoreUtils.StringToEndPoint(p.TargetExternal);
player.ConnectWhenPunched=p.Connect;
}
else
{
Main.Logger.Warning("No player with specified TargetID found for hole punching:"+p.TargetID);
}
}
public static void Punched(Packets.HolePunch p,IPEndPoint from)
{
Main.Logger.Debug($"HolePunch message received from:{from}, status:{p.Status}");
if(PlayerList.Players.TryGetValue(p.Puncher,out var puncher))
{
Main.Logger.Debug("Puncher identified as: "+puncher.Username);
puncher.HolePunchStatus=(byte)(p.Status+1);
if (p.Status>=3)
{
Main.Logger.Debug("HolePunch sucess: "+from+", "+puncher.PedID);
if (puncher.ConnectWhenPunched && (puncher.Connection==null || puncher.Connection.Status==NetConnectionStatus.Disconnected))
{
Main.Logger.Debug("Connecting to peer: "+from);
var msg = Networking.Peer.CreateMessage();
new Packets.P2PConnect { ID=Main.LocalPlayerID }.Pack(msg);
puncher.Connection=Networking.Peer.Connect(from,msg);
Networking.Peer.FlushSendQueue();
}
}
}
}
}
}

View File

@ -146,19 +146,20 @@ namespace RageCoop.Client
PedID = packet.PedID,
Username= packet.Username,
};
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected.");
PlayerList.SetPlayer(packet.PedID, packet.Username);
Main.Logger.Debug($"player connected:{p.Username}");
Main.QueueAction(() =>
GTA.UI.Notification.Show($"~h~{p.Username}~h~ connected."));
}
private static void PlayerDisconnect(Packets.PlayerDisconnect packet)
{
var name = PlayerList.GetPlayer(packet.PedID).Username;
GTA.UI.Notification.Show($"~h~{name}~h~ left.");
PlayerList.RemovePlayer(packet.PedID);
EntityPool.RemoveAllFromPlayer(packet.PedID);
Main.QueueAction(() =>
GTA.UI.Notification.Show($"~h~{name}~h~ left."));
}
#endregion // -- PLAYER --

View File

@ -70,16 +70,22 @@ namespace RageCoop.Client
Main.Logger.Info(">> Connected <<");
}
else if (PlayerList.PendingConnections.TryGetValue(message.SenderEndPoint.ToString(),out var player))
else
{
PlayerList.PendingConnections.Remove(message.SenderEndPoint.ToString());
Main.Logger.Debug($"Connection to {player.Username},{player.PedID},{player.Connection.Status} established, sending ID...");
// Inform target our ID
SendTo(new Packets.ConnectionEstablished()
// Self-initiated connection
if (message.SenderConnection.RemoteHailMessage==null) { return; }
var p = message.SenderConnection.RemoteHailMessage.GetPacket<Packets.P2PConnect>();
if (PlayerList.Players.TryGetValue(p.ID,out var player))
{
ID=Main.LocalPlayerID
}, player.Connection, ConnectionChannel.Default, NetDeliveryMethod.ReliableOrdered);
Peer.FlushSendQueue();
player.Connection=message.SenderConnection;
Main.Logger.Debug($"Direct connectionn to {player.Username} established");
}
else
{
Main.Logger.Info($"Unidentified peer connection from {message.SenderEndPoint} was rejected.");
message.SenderConnection.Disconnect("eat poop");
}
}
break;
case NetConnectionStatus.Disconnected:
@ -103,22 +109,6 @@ namespace RageCoop.Client
break;
}
break;
case NetIncomingMessageType.NatIntroductionSuccess:
{
var playerID = int.Parse(message.ReadString());
// Main.Logger.Debug("NatIntroductionSuccess received from "+message.SenderEndPoint+" "+playerID);
if (PlayerList.Players.TryGetValue(playerID, out var player) && (player.Connection==null || player.Connection.Status==NetConnectionStatus.Disconnected))
{
Main.Logger.Debug($"Establishing direct connection to {player.Username},{player.PedID}");
player.Connection=Peer.Connect(message.SenderEndPoint);
var ep = message.SenderEndPoint.ToString();
if (!PlayerList.PendingConnections.ContainsKey(ep))
{
PlayerList.PendingConnections.Add(ep, player);
}
}
break;
}
case NetIncomingMessageType.Data:
{
@ -184,14 +174,23 @@ namespace RageCoop.Client
var packetType = (PacketType)message.ReadByte();
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
if (packetType==PacketType.PublicKeyResponse)
switch (packetType)
{
var packet = new Packets.PublicKeyResponse();
packet.Deserialize(data);
Security.SetServerPublicKey(packet.Modulus, packet.Exponent);
_publicKeyReceived.Set();
}
case PacketType.HolePunch:
{
HolePunch.Punched(data.GetPacket<Packets.HolePunch>(), message.SenderEndPoint);
break;
}
case PacketType.PublicKeyResponse:
{
var packet = data.GetPacket<Packets.PublicKeyResponse>();
Security.SetServerPublicKey(packet.Modulus, packet.Exponent);
_publicKeyReceived.Set();
break;
}
}
break;
}
case NetIncomingMessageType.DebugMessage:
@ -211,69 +210,33 @@ namespace RageCoop.Client
switch (packetType)
{
case PacketType.ConnectionEstablished:
{
var p=new Packets.ConnectionEstablished();
p.Deserialize(data);
Main.Logger.Debug("Connection message received from "+senderConnection.RemoteEndPoint+","+p.ID);
if(PlayerList.Players.TryGetValue(p.ID,out var player)){
Main.Logger.Debug($"Direct connection to {player.Username},{player.PedID} has been established");
player.Connection=senderConnection;
}
break;
}
case PacketType.HolePunchInit:
HolePunch.Add(data.GetPacket<Packets.HolePunchInit>());
break;
case PacketType.PlayerConnect:
{
Packets.PlayerConnect packet = new Packets.PlayerConnect();
packet.Deserialize(data);
Main.QueueAction(() => PlayerConnect(packet));
}
PlayerConnect(data.GetPacket<Packets.PlayerConnect>());
break;
case PacketType.PlayerDisconnect:
{
Packets.PlayerDisconnect packet = new Packets.PlayerDisconnect();
packet.Deserialize(data);
Main.QueueAction(() => PlayerDisconnect(packet));
}
PlayerDisconnect(data.GetPacket<Packets.PlayerDisconnect>());
break;
case PacketType.PlayerInfoUpdate:
{
var packet = new Packets.PlayerInfoUpdate();
packet.Deserialize(data);
PlayerList.UpdatePlayer(packet);
break;
}
#region ENTITY SYNC
case PacketType.VehicleSync:
{
Packets.VehicleSync packet = new Packets.VehicleSync();
packet.Deserialize(data);
VehicleSync(packet);
}
PlayerList.UpdatePlayer(data.GetPacket<Packets.PlayerInfoUpdate>());
break;
case PacketType.VehicleSync:
VehicleSync(data.GetPacket<Packets.VehicleSync>());
break;
case PacketType.PedSync:
{
Packets.PedSync packet = new Packets.PedSync();
packet.Deserialize(data);
PedSync(packet);
}
PedSync(data.GetPacket<Packets.PedSync>());
break;
case PacketType.ProjectileSync:
{
Packets.ProjectileSync packet = new Packets.ProjectileSync();
packet.Deserialize(data);
ProjectileSync(packet);
break;
}
#endregion
ProjectileSync(data.GetPacket<Packets.ProjectileSync>());
break;
case PacketType.ChatMessage:
{
@ -284,10 +247,9 @@ namespace RageCoop.Client
packet.Deserialize(data);
Main.QueueAction(() => { Main.MainChat.AddMessage(packet.Username, packet.Message); return true; });
}
break;
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
@ -295,6 +257,7 @@ namespace RageCoop.Client
Scripting.API.Events.InvokeCustomEventReceived(packet);
}
break;
case PacketType.CustomEventQueued:
{
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
@ -305,6 +268,7 @@ namespace RageCoop.Client
});
}
break;
case PacketType.FileTransferChunk:
{
Packets.FileTransferChunk packet = new Packets.FileTransferChunk();
@ -312,10 +276,11 @@ namespace RageCoop.Client
DownloadManager.Write(packet.ID, packet.FileChunk);
}
break;
default:
if (packetType.IsSyncEvent())
{
// Dispatch to main thread
// Dispatch to script thread
Main.QueueAction(() => { SyncEvents.HandleEvent(packetType, data); return true; });
}
break;

View File

@ -108,7 +108,7 @@ namespace RageCoop.Client
OwnerID=v.OwnerID,
Flags = veh.GetVehicleFlags(),
SteeringAngle = veh.SteeringAngle,
Position = veh.PredictPosition(),
Position = veh.Position,
Velocity=veh.Velocity,
Quaternion=veh.ReadQuaternion(),
RotationVelocity=veh.RotationVelocity,
@ -160,11 +160,11 @@ namespace RageCoop.Client
ID =sp.ID,
ShooterID=sp.ShooterID,
Rotation=p.Rotation,
Position=p.Position,
Velocity=p.Velocity,
WeaponHash=(uint)p.WeaponHash,
Exploded=p.IsDead
};
packet.Position=p.Position+packet.Velocity*Latency;
if (p.IsDead) { EntityPool.RemoveProjectile(sp.ID, "Dead"); }
Send(packet, ConnectionChannel.ProjectileSync);
}

View File

@ -18,7 +18,6 @@ namespace RageCoop.Client
public static bool LeftAlign = true;
public static Dictionary<int, Player> Players = new Dictionary<int, Player> { };
public static Dictionary<string, Player> PendingConnections = new Dictionary<string, Player>();
public static void Tick()
{
if (!Networking.IsOnServer)
@ -138,6 +137,7 @@ namespace RageCoop.Client
internal class Player
{
public byte HolePunchStatus { get; set; } = 1;
public string Username { get; internal set; }
/// <summary>
/// Universal character ID.
@ -146,6 +146,9 @@ namespace RageCoop.Client
{
get; internal set;
}
public IPEndPoint InternalEndPoint { get; set; }
public IPEndPoint ExternalEndPoint { get; set; }
public bool ConnectWhenPunched { get; set; }
public Blip FakeBlip { get; set; }
public Vector3 Position { get; set; }
public SyncedPed Character { get; set; }

View File

@ -37,6 +37,7 @@
<Compile Include="Menus\Sub\UpdateMenu.cs" />
<Compile Include="Networking\Chat.cs" />
<Compile Include="Networking\DownloadManager.cs" />
<Compile Include="Networking\HolePunch.cs" />
<Compile Include="Networking\Networking.cs" />
<Compile Include="Networking\Receive.cs" />
<Compile Include="Networking\Send.cs" />

View File

@ -221,7 +221,8 @@ namespace RageCoop.Client
new ScaledText(toDraw, Player.Username, 0.4f, GTA.UI.Font.ChaletLondon)
{
Outline = true,
Alignment = GTA.UI.Alignment.Center
Alignment = GTA.UI.Alignment.Center,
Color=Owner.HasDirectConnection? Color.FromArgb(179, 229, 252) : Color.White,
}.Draw();
}

View File

@ -67,7 +67,7 @@ namespace RageCoop.Client
CreateProjectile();
return;
}
MainProjectile.Velocity=Velocity+(Position+Networking.Latency*Velocity-MainProjectile.Position);
MainProjectile.Velocity=Velocity+(Position+Shooter.Owner.PacketTravelTime*Velocity-MainProjectile.Position);
MainProjectile.Rotation=Rotation;
LastUpdated=Main.Ticked;
}

View File

@ -353,23 +353,22 @@ namespace RageCoop.Client
LastUpdated=Main.Ticked;
}
float _elapsed;
Vector3 _predictedVel;
Vector3 _predictedPos;
void DisplayVehicle(bool touching)
{
// predict velocity/position
_elapsed = Owner.PacketTravelTime+0.001f*LastSyncedStopWatch.ElapsedMilliseconds;
// new LemonUI.Elements.ScaledText(new System.Drawing.PointF(50, 50), Owner.HasDirectConnection+" "+LastSyncedStopWatch.ElapsedMilliseconds).Draw();
_predictedVel = Velocity+Acceleration*_elapsed;
_predictedPos = Position+_elapsed*(LastVelocity+_predictedVel)/2;
LastVelocity=_predictedVel;
// _predictedVel = Velocity+Acceleration*_elapsed;
_predictedPos = Position+_elapsed*Velocity;
// LastVelocity=_predictedVel;
var current = MainVehicle.ReadPosition();
var dist = current.DistanceTo(Position);
var cali = ((Velocity.Length()<0.1 && !touching)?dist*4:dist)*(_predictedPos - current);
if (dist<8)
{
MainVehicle.Velocity = _predictedVel;
MainVehicle.Velocity = Velocity;
MainVehicle.ApplyForce(cali);
if (IsFlipped)
{
@ -393,7 +392,7 @@ namespace RageCoop.Client
else
{
MainVehicle.Position=_predictedPos;
MainVehicle.Velocity=_predictedVel;
MainVehicle.Velocity=Velocity;
MainVehicle.Quaternion=Quaternion;
}
}

View File

@ -161,10 +161,6 @@ namespace RageCoop.Client
}
}
public static Vector3 PredictPosition(this Entity e)
{
return e.ReadPosition()+e.Velocity*(Networking.Latency);
}
public static Vehicle CreateVehicle(Model model, Vector3 position, float heading = 0f)
{

View File

@ -9,6 +9,7 @@ using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Runtime.CompilerServices;
using Lidgren.Network;
[assembly: InternalsVisibleTo("RageCoop.Server")]
[assembly: InternalsVisibleTo("RageCoop.Client")]
@ -259,6 +260,18 @@ namespace RageCoop.Core
return new List<byte[]>() { BitConverter.GetBytes(qua.X), BitConverter.GetBytes(qua.Y), BitConverter.GetBytes(qua.Z), BitConverter.GetBytes(qua.W) }.Join(4);
}
public static T GetPacket<T>(this NetIncomingMessage msg) where T : Packet, new()
{
msg.ReadByte();
return GetPacket<T>(msg.ReadBytes(msg.ReadInt32()));
}
public static T GetPacket<T>(this byte[] data) where T : Packet, new()
{
T p = new T();
p.Deserialize(data);
return p;
}
public static bool HasPedFlag(this PedDataFlags flagToCheck, PedDataFlags flag)
{
return (flagToCheck & flag)!=0;

View File

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using Lidgren.Network;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class HolePunchInit : Packet
{
public override PacketType Type => PacketType.HolePunchInit;
public int TargetID { get; set; }
public string TargetInternal { get; set; }
public string TargetExternal { get; set; }
public bool Connect { get; set; }
public override byte[] Serialize()
{
List<byte> byteArray = new List<byte>();
byteArray.AddInt(TargetID);
byteArray.AddString(TargetInternal);
byteArray.AddString(TargetExternal);
byteArray.AddBool(Connect);
return byteArray.ToArray();
}
public override void Deserialize(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
TargetID = reader.ReadInt32();
TargetInternal = reader.ReadString();
TargetExternal = reader.ReadString();
Connect=reader.ReadBoolean();
#endregion
}
}
internal class HolePunch : Packet
{
public override PacketType Type => PacketType.HolePunch;
public int Puncher { get; set; }
/// <summary>
/// 1:initial, 2:acknowledged, 3:confirmed
/// </summary>
public byte Status { get;set;}
public override byte[] Serialize()
{
List<byte> byteArray = new List<byte>();
byteArray.AddInt(Puncher);
byteArray.Add(Status);
return byteArray.ToArray();
}
public override void Deserialize(byte[] array)
{
#region NetIncomingMessageToPacket
BitReader reader = new BitReader(array);
Puncher = reader.ReadInt32();
Status = reader.ReadByte();
#endregion
}
}
}
}

View File

@ -29,10 +29,10 @@ namespace RageCoop.Core
/// <summary>
/// Sent to the host when a direct connection has been established
/// </summary>
internal class ConnectionEstablished : Packet
internal class P2PConnect : Packet
{
public int ID { get; set; }
public override PacketType Type => PacketType.ConnectionEstablished;
public override PacketType Type => PacketType.P2PConnect;
public override byte[] Serialize()
{
var data = new List<byte>(10);

View File

@ -31,13 +31,13 @@ namespace RageCoop.Core
CustomEventQueued = 17,
ConnectionRequest=18,
ConnectionEstablished = 19,
P2PConnect = 19,
HolePunchInit=20,
HolePunch=21,
#region Sync
#region INTERVAL
VehicleSync = 20,
PedSync = 22,
ProjectileSync=24,
VehicleSync = 23,
ProjectileSync =24,
#endregion
#region EVENT
@ -53,7 +53,6 @@ namespace RageCoop.Core
#endregion
#endregion
Unknown=255
}
internal static class PacketExtensions

View File

@ -0,0 +1,198 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using RageCoop.Server.Scripting;
namespace RageCoop.Server
{
public partial class Server
{
private void DisconnectAndLog(NetConnection senderConnection, PacketType type, Exception e)
{
Logger?.Error($"Error receiving a packet of type {type}");
Logger?.Error(e.Message);
Logger?.Error(e.StackTrace);
senderConnection.Disconnect(e.Message);
}
private void GetHandshake(NetConnection connection, Packets.Handshake packet)
{
Logger?.Debug("New handshake from: [Name: " + packet.Username + " | Address: " + connection.RemoteEndPoint.Address.ToString() + "]");
if (!packet.ModVersion.StartsWith(_compatibleVersion))
{
connection.Deny($"RAGECOOP version {_compatibleVersion.Replace('_', '.')}.x required!");
return;
}
if (string.IsNullOrWhiteSpace(packet.Username))
{
connection.Deny("Username is empty or contains spaces!");
return;
}
if (packet.Username.Any(p => !_allowedCharacterSet.Contains(p)))
{
connection.Deny("Username contains special chars!");
return;
}
if (ClientsByNetHandle.Values.Any(x => x.Username.ToLower() == packet.Username.ToLower()))
{
connection.Deny("Username is already taken!");
return;
}
try
{
Security.AddConnection(connection.RemoteEndPoint, packet.AesKeyCrypted, packet.AesIVCrypted);
var args = new HandshakeEventArgs()
{
EndPoint=connection.RemoteEndPoint,
ID=packet.PedID,
Username=packet.Username,
PasswordHash=Security.Decrypt(packet.PasswordEncrypted, connection.RemoteEndPoint).GetString().GetSHA256Hash().ToHexString(),
};
API.Events.InvokePlayerHandshake(args);
if (args.Cancel)
{
connection.Deny(args.DenyReason);
return;
}
}
catch (Exception ex)
{
Logger?.Error($"Cannot process handshake packet from {connection.RemoteEndPoint}");
Logger?.Error(ex);
connection.Deny("Malformed handshak packet!");
return;
}
var handshakeSuccess = MainNetServer.CreateMessage();
var currentClients = ClientsByID.Values.ToArray();
var players = new Packets.PlayerData[currentClients.Length];
for (int i = 0; i<players.Length; i++)
{
players[i]=new Packets.PlayerData()
{
ID=currentClients[i].Player.ID,
Username=currentClients[i].Username,
};
}
new Packets.HandshakeSuccess()
{
Players=players
}.Pack(handshakeSuccess);
connection.Approve(handshakeSuccess);
Client tmpClient;
// Add the player to Players
lock (ClientsByNetHandle)
{
var player = new ServerPed(this)
{
ID= packet.PedID,
};
Entities.Add(player);
ClientsByNetHandle.Add(connection.RemoteUniqueIdentifier,
tmpClient = new Client(this)
{
NetHandle = connection.RemoteUniqueIdentifier,
Connection=connection,
Username=packet.Username,
Player = player,
InternalEndPoint=packet.InternalEndPoint,
}
);
ClientsByName.Add(packet.Username.ToLower(), tmpClient);
ClientsByID.Add(player.ID, tmpClient);
if (ClientsByNetHandle.Count==1)
{
_hostClient=tmpClient;
}
}
Logger?.Debug($"Handshake sucess, Player:{packet.Username} PedID:{packet.PedID}");
}
// The connection has been approved, now we need to send all other players to the new player and the new player to all players
private void PlayerConnected(Client newClient)
{
if (newClient==_hostClient)
{
API.SendCustomEvent(new() { newClient }, CustomEvents.IsHost, true);
}
// Send new client to all players
var cons = MainNetServer.Connections.Exclude(newClient.Connection);
if (cons.Count!=0)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.PlayerConnect()
{
PedID=newClient.Player.ID,
Username = newClient.Username
}.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, cons, NetDeliveryMethod.ReliableOrdered, 0);
}
// Send all props to this player
BaseScript.SendServerPropsTo(new(Entities.ServerProps.Values), new() { newClient });
// Send all blips to this player
BaseScript.SendServerBlipsTo(new(Entities.Blips.Values), new() { newClient });
// Create P2P connection
if (Settings.UseP2P)
{
ClientsByNetHandle.Values.ForEach(target =>
{
if (target==newClient) { return; }
HolePunch(target,newClient);
});
}
Logger?.Info($"Player {newClient.Username} connected!");
if (!string.IsNullOrEmpty(Settings.WelcomeMessage))
{
SendChatMessage("Server", Settings.WelcomeMessage, newClient);
}
}
// Send all players a message that someone has left the server
private void PlayerDisconnected(Client localClient)
{
var cons = MainNetServer.Connections.Exclude(localClient.Connection);
if (cons.Count!=0)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.PlayerDisconnect()
{
PedID=localClient.Player.ID,
}.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, cons, NetDeliveryMethod.ReliableOrdered, 0);
}
Entities.CleanUp(localClient);
_worker.QueueJob(() => API.Events.InvokePlayerDisconnected(localClient));
Logger?.Info($"Player {localClient.Username} disconnected! ID:{localClient.Player.ID}");
if (ClientsByNetHandle.ContainsKey(localClient.NetHandle)) { ClientsByNetHandle.Remove(localClient.NetHandle); }
if (ClientsByName.ContainsKey(localClient.Username.ToLower())) { ClientsByName.Remove(localClient.Username.ToLower()); }
if (ClientsByID.ContainsKey(localClient.Player.ID)) { ClientsByID.Remove(localClient.Player.ID); }
if (localClient==_hostClient)
{
_hostClient = ClientsByNetHandle.Values.FirstOrDefault();
_hostClient?.SendCustomEvent(CustomEvents.IsHost, true);
}
Security.RemoveConnection(localClient.Connection.RemoteEndPoint);
}
}
}

View File

@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using RageCoop.Server.Scripting;
namespace RageCoop.Server
{
public partial class Server
{
private void PedSync(Packets.PedSync packet, Client client)
{
_worker.QueueJob(() => Entities.Update(packet, client));
bool isPlayer = packet.ID==client.Player.ID;
if (isPlayer)
{
_worker.QueueJob(() => API.Events.InvokePlayerUpdate(client));
}
if (Settings.UseP2P) { return; }
foreach (var c in ClientsByNetHandle.Values)
{
// Don't send data back
if (c.NetHandle==client.NetHandle) { continue; }
// Check streaming distance
if (isPlayer)
{
if ((Settings.PlayerStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.PlayerStreamingDistance))
{
continue;
}
}
else if ((Settings.NpcStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.NpcStreamingDistance))
{
continue;
}
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private void VehicleSync(Packets.VehicleSync packet, Client client)
{
_worker.QueueJob(() => Entities.Update(packet, client));
bool isPlayer = packet.ID==client.Player?.LastVehicle?.ID;
if (Settings.UseP2P) { return; }
foreach (var c in ClientsByNetHandle.Values)
{
if (c.NetHandle==client.NetHandle) { continue; }
if (isPlayer)
{
// Player's vehicle
if ((Settings.PlayerStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.PlayerStreamingDistance))
{
continue;
}
}
else if ((Settings.NpcStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.NpcStreamingDistance))
{
continue;
}
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private void ProjectileSync(Packets.ProjectileSync packet, Client client)
{
if (Settings.UseP2P) { return; }
foreach (var c in ClientsByNetHandle.Values)
{
if (c.NetHandle==client.NetHandle) { continue; }
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
}
}

View File

@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Lidgren.Network;
using RageCoop.Core;
using RageCoop.Core.Scripting;
using RageCoop.Server.Scripting;
namespace RageCoop.Server
{
public partial class Server
{
private void HolePunch(Client host, Client client)
{
// Send to host
Send(new Packets.HolePunchInit
{
Connect=false,
TargetID=client.Player.ID,
TargetInternal=client.InternalEndPoint.ToString(),
TargetExternal=client.EndPoint.ToString()
}, host, ConnectionChannel.Default, NetDeliveryMethod.ReliableOrdered);
// Send to client
Send(new Packets.HolePunchInit
{
Connect=true,
TargetID=host.Player.ID,
TargetInternal=host.InternalEndPoint.ToString(),
TargetExternal=host.EndPoint.ToString()
}, client, ConnectionChannel.Default, NetDeliveryMethod.ReliableOrdered);
}
}
}

View File

@ -32,7 +32,7 @@ namespace RageCoop.Server
/// <summary>
/// The instantiable RageCoop server class
/// </summary>
public class Server
public partial class Server
{
/// <summary>
/// The API for controlling server and hooking events.
@ -108,7 +108,7 @@ namespace RageCoop.Server
Logger?.Error(ex);
}
}
Thread.Sleep(5000);
Thread.Sleep(1000);
}
});
_announceThread=new Thread(async () =>
@ -316,10 +316,7 @@ namespace RageCoop.Server
{
int len = message.ReadInt32();
byte[] data = message.ReadBytes(len);
Packets.Handshake packet = new();
packet.Deserialize(data);
GetHandshake(message.SenderConnection, packet);
GetHandshake(message.SenderConnection, data.GetPacket<Packets.Handshake>());
}
catch (Exception e)
{
@ -457,33 +454,20 @@ namespace RageCoop.Server
}
private void HandlePacket(PacketType type,byte[] data,Client sender)
{
try
{
switch (type)
{
case PacketType.PedSync:
{
PedSync(data.GetPacket<Packets.PedSync>(), sender);
break;
Packets.PedSync packet = new();
packet.Deserialize(data);
PedSync(packet, sender);
}
break;
case PacketType.VehicleSync:
{
Packets.VehicleSync packet = new();
packet.Deserialize(data);
VehicleSync(packet, sender);
}
VehicleSync(data.GetPacket<Packets.VehicleSync>(), sender);
break;
case PacketType.ProjectileSync:
{
Packets.ProjectileSync packet = new();
packet.Deserialize(data);
ProjectileSync(packet, sender);
}
ProjectileSync(data.GetPacket<Packets.ProjectileSync>(), sender);
break;
case PacketType.ChatMessage:
@ -496,6 +480,7 @@ namespace RageCoop.Server
ChatMessageReceived(packet.Username,packet.Message, sender);
}
break;
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent();
@ -503,19 +488,10 @@ namespace RageCoop.Server
_worker.QueueJob(() => API.Events.InvokeCustomEventReceived(packet, sender));
}
break;
case PacketType.ConnectionRequest:
{
var p=new Packets.ConnectionRequest();
p.Deserialize(data);
if (ClientsByID.TryGetValue(p.TargetID,out var target))
{
MainNetServer.Introduce(target.InternalEndPoint,target.EndPoint,sender.InternalEndPoint,sender.EndPoint,Guid.NewGuid().ToString());
}
break;
}
default:
Logger?.Error("Unhandled Data / Packet type");
break;
}
}
catch (Exception e)
@ -524,268 +500,6 @@ namespace RageCoop.Server
}
}
private void DisconnectAndLog(NetConnection senderConnection,PacketType type, Exception e)
{
Logger?.Error($"Error receiving a packet of type {type}");
Logger?.Error(e.Message);
Logger?.Error(e.StackTrace);
senderConnection.Disconnect(e.Message);
}
private void GetHandshake(NetConnection connection, Packets.Handshake packet)
{
Logger?.Debug("New handshake from: [Name: " + packet.Username + " | Address: " + connection.RemoteEndPoint.Address.ToString() + "]");
if (!packet.ModVersion.StartsWith(_compatibleVersion))
{
connection.Deny($"RAGECOOP version {_compatibleVersion.Replace('_', '.')}.x required!");
return;
}
if (string.IsNullOrWhiteSpace(packet.Username))
{
connection.Deny("Username is empty or contains spaces!");
return;
}
if (packet.Username.Any(p => !_allowedCharacterSet.Contains(p)))
{
connection.Deny("Username contains special chars!");
return;
}
if (ClientsByNetHandle.Values.Any(x => x.Username.ToLower() == packet.Username.ToLower()))
{
connection.Deny("Username is already taken!");
return;
}
try
{
Security.AddConnection(connection.RemoteEndPoint, packet.AesKeyCrypted,packet.AesIVCrypted);
var args = new HandshakeEventArgs()
{
EndPoint=connection.RemoteEndPoint,
ID=packet.PedID,
Username=packet.Username,
PasswordHash=Security.Decrypt(packet.PasswordEncrypted, connection.RemoteEndPoint).GetString().GetSHA256Hash().ToHexString(),
};
API.Events.InvokePlayerHandshake(args);
if (args.Cancel)
{
connection.Deny(args.DenyReason);
return;
}
}
catch (Exception ex)
{
Logger?.Error($"Cannot process handshake packet from {connection.RemoteEndPoint}");
Logger?.Error(ex);
connection.Deny("Malformed handshak packet!");
return;
}
var handshakeSuccess = MainNetServer.CreateMessage();
var currentClients = ClientsByID.Values.ToArray();
var players = new Packets.PlayerData[currentClients.Length];
for(int i= 0; i<players.Length; i++)
{
players[i]=new Packets.PlayerData()
{
ID=currentClients[i].Player.ID,
Username=currentClients[i].Username,
};
}
new Packets.HandshakeSuccess()
{
Players=players
}.Pack(handshakeSuccess);
connection.Approve(handshakeSuccess);
Client tmpClient;
// Add the player to Players
lock (ClientsByNetHandle)
{
var player = new ServerPed(this)
{
ID= packet.PedID,
};
Entities.Add(player);
ClientsByNetHandle.Add(connection.RemoteUniqueIdentifier,
tmpClient = new Client(this)
{
NetHandle = connection.RemoteUniqueIdentifier,
Connection=connection,
Username=packet.Username,
Player = player,
InternalEndPoint=packet.InternalEndPoint,
}
);
ClientsByName.Add(packet.Username.ToLower(), tmpClient);
ClientsByID.Add(player.ID, tmpClient);
if (ClientsByNetHandle.Count==1) {
_hostClient=tmpClient;
}
}
Logger?.Debug($"Handshake sucess, Player:{packet.Username} PedID:{packet.PedID}");
}
// The connection has been approved, now we need to send all other players to the new player and the new player to all players
private void PlayerConnected(Client newClient)
{
if (newClient==_hostClient)
{
API.SendCustomEvent(new() { newClient },CustomEvents.IsHost, true);
}
// Send new client to all players
var cons = MainNetServer.Connections.Exclude(newClient.Connection);
if (cons.Count!=0)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.PlayerConnect()
{
PedID=newClient.Player.ID,
Username = newClient.Username
}.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, cons, NetDeliveryMethod.ReliableOrdered, 0);
}
// Send all props to this player
BaseScript.SendServerPropsTo( new(Entities.ServerProps.Values), new() { newClient});
// Send all blips to this player
BaseScript.SendServerBlipsTo(new(Entities.Blips.Values), new() { newClient});
// Create P2P connection
if (Settings.UseP2P)
{
ClientsByNetHandle.Values.ForEach(target =>
{
if (target==newClient) { return; }
MainNetServer.Introduce(target.InternalEndPoint, target.EndPoint, newClient.InternalEndPoint, newClient.EndPoint, target.Player.ID.ToString());
});
}
Logger?.Info($"Player {newClient.Username} connected!");
if (!string.IsNullOrEmpty(Settings.WelcomeMessage))
{
SendChatMessage("Server",Settings.WelcomeMessage , newClient);
}
}
// Send all players a message that someone has left the server
private void PlayerDisconnected(Client localClient)
{
var cons = MainNetServer.Connections.Exclude(localClient.Connection);
if (cons.Count!=0)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
new Packets.PlayerDisconnect()
{
PedID=localClient.Player.ID,
}.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage,cons , NetDeliveryMethod.ReliableOrdered, 0);
}
Entities.CleanUp(localClient);
_worker.QueueJob(() => API.Events.InvokePlayerDisconnected(localClient));
Logger?.Info($"Player {localClient.Username} disconnected! ID:{localClient.Player.ID}");
if (ClientsByNetHandle.ContainsKey(localClient.NetHandle)) { ClientsByNetHandle.Remove(localClient.NetHandle); }
if (ClientsByName.ContainsKey(localClient.Username.ToLower())) { ClientsByName.Remove(localClient.Username.ToLower()); }
if (ClientsByID.ContainsKey(localClient.Player.ID)) { ClientsByID.Remove(localClient.Player.ID); }
if (localClient==_hostClient)
{
_hostClient = ClientsByNetHandle.Values.FirstOrDefault();
_hostClient?.SendCustomEvent(CustomEvents.IsHost, true);
}
Security.RemoveConnection(localClient.Connection.RemoteEndPoint);
}
#region -- SYNC --
#region SyncEntities
private void PedSync(Packets.PedSync packet, Client client)
{
_worker.QueueJob(() => Entities.Update(packet, client));
bool isPlayer = packet.ID==client.Player.ID;
if (isPlayer)
{
_worker.QueueJob(() => API.Events.InvokePlayerUpdate(client));
}
if (Settings.UseP2P) { return; }
foreach (var c in ClientsByNetHandle.Values)
{
// Don't send data back
if (c.NetHandle==client.NetHandle) { continue; }
// Check streaming distance
if (isPlayer)
{
if ((Settings.PlayerStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.PlayerStreamingDistance))
{
continue;
}
}
else if ((Settings.NpcStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.NpcStreamingDistance))
{
continue;
}
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage,c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private void VehicleSync(Packets.VehicleSync packet, Client client)
{
_worker.QueueJob(() => Entities.Update(packet, client));
bool isPlayer = packet.ID==client.Player?.LastVehicle?.ID;
if (Settings.UseP2P) { return; }
foreach (var c in ClientsByNetHandle.Values)
{
if (c.NetHandle==client.NetHandle) { continue; }
if (isPlayer)
{
// Player's vehicle
if ((Settings.PlayerStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.PlayerStreamingDistance))
{
continue;
}
}
else if((Settings.NpcStreamingDistance!=-1)&&(packet.Position.DistanceTo(c.Player.Position)>Settings.NpcStreamingDistance))
{
continue;
}
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
private void ProjectileSync(Packets.ProjectileSync packet, Client client)
{
if (Settings.UseP2P) { return; }
foreach (var c in ClientsByNetHandle.Values)
{
if (c.NetHandle==client.NetHandle) { continue; }
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
packet.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, c.Connection, NetDeliveryMethod.UnreliableSequenced, (byte)ConnectionChannel.PedSync);
}
}
#endregion
// Send a message to targets or all players
internal void ChatMessageReceived(string name, string message,Client sender=null)
{
@ -831,8 +545,6 @@ namespace RageCoop.Server
}.Pack(msg);
MainNetServer.SendMessage(msg, target.Connection, NetDeliveryMethod.ReliableOrdered, (int)ConnectionChannel.Chat);
}
#endregion
internal void RegisterCommand(string name, string usage, short argsLength, Action<CommandContext> callback)
{

View File

@ -88,6 +88,6 @@
/// <summary>
/// Whether to use direct connection between players to send entity information
/// </summary>
public bool UseP2P { get; set; } = true;
public bool UseP2P { get; set; } = false;
}
}