Merge pull request #32 from RAGECOOP/voice-chat

Add voice chat
This commit is contained in:
Sardelka9515 2022-08-13 10:55:16 +08:00 committed by GitHub
commit 4cca75ee35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 302 additions and 58 deletions

View File

@ -20,7 +20,6 @@ namespace RageCoop.Client
/// </summary>
internal class Main : Script
{
private bool _gameLoaded = false;
internal static readonly string CurrentVersion = "V0_5_0";
@ -110,6 +109,8 @@ namespace RageCoop.Client
private bool _lastDead;
private void OnTick(object sender, EventArgs e)
{
P= Game.Player.Character;
PlayerPosition=P.ReadPosition();
@ -220,6 +221,20 @@ namespace RageCoop.Client
}
if (Networking.IsOnServer)
{
if (Sync.Voice.WasInitialized())
{
if (Game.IsControlPressed(GTA.Control.PushToTalk))
{
Sync.Voice.StartRecording();
return;
}
else if (Sync.Voice.IsRecording)
{
Sync.Voice.StopRecording();
return;
}
}
if (Game.IsControlPressed(GTA.Control.FrontendPause))
{
Function.Call(Hash.ACTIVATE_FRONTEND_MENU, Function.Call<int>(Hash.GET_HASH_KEY, "FE_MENU_VERSION_SP_PAUSE"), false, 0);
@ -290,10 +305,10 @@ namespace RageCoop.Client
public static void CleanUp()
{
MainChat.Clear();
Sync.Voice.ClearAll();
EntityPool.Cleanup();
PlayerList.Cleanup();
Main.LocalPlayerID=default;
}
private static void DoQueuedActions()
{

View File

@ -6,9 +6,6 @@ using System.Windows.Forms;
namespace RageCoop.Client.Menus
{
/// <summary>
/// Don't use it!
/// </summary>
internal static class SettingsMenu
{
public static NativeMenu Menu = new NativeMenu("RAGECOOP", "Settings", "Go to the settings")
@ -20,43 +17,53 @@ namespace RageCoop.Client.Menus
private static readonly NativeCheckboxItem _disableTrafficItem = new NativeCheckboxItem("Disable Traffic (NPCs/Vehicles)", "Local traffic only", Main.Settings.DisableTraffic);
private static readonly NativeCheckboxItem _flipMenuItem = new NativeCheckboxItem("Flip menu", Main.Settings.FlipMenu);
private static readonly NativeCheckboxItem _disablePauseAlt = new NativeCheckboxItem("Disable Alternate Pause", "Don't freeze game time when Esc pressed", Main.Settings.DisableTraffic);
private static readonly NativeCheckboxItem _disableVoice = new NativeCheckboxItem("Enable/Disable the voice", Main.Settings.Voice);
private static NativeItem _menuKey = new NativeItem("Menu Key", "The key to open menu", Main.Settings.MenuKey.ToString());
private static NativeItem _passengerKey = new NativeItem("Passenger Key", "The key to enter a vehicle as passenger", Main.Settings.PassengerKey.ToString());
private static NativeItem _vehicleSoftLimit = new NativeItem("Vehicle limit (soft)", "The game won't spawn more NPC traffic if the limit is exceeded. \n-1 for unlimited (not recommended).", Main.Settings.WorldVehicleSoftLimit.ToString());
/// <summary>
/// Don't use it!
/// </summary>
static SettingsMenu()
{
Menu.Banner.Color = Color.FromArgb(225, 0, 0, 0);
Menu.Title.Color = Color.FromArgb(255, 165, 0);
_disableTrafficItem.CheckboxChanged += DisableTrafficCheckboxChanged;
_disablePauseAlt.CheckboxChanged+=_disablePauseAlt_CheckboxChanged;
_disablePauseAlt.CheckboxChanged+= DisablePauseAltCheckboxChanged;
_disableVoice.CheckboxChanged += DisableVoiceCheckboxChanged;
_flipMenuItem.CheckboxChanged += FlipMenuCheckboxChanged;
_menuKey.Activated+=ChaneMenuKey;
_passengerKey.Activated+=ChangePassengerKey;
_vehicleSoftLimit.Activated+=vehicleSoftLimit_Activated;
_menuKey.Activated+= ChaneMenuKey;
_passengerKey.Activated+= ChangePassengerKey;
_vehicleSoftLimit.Activated+= VehicleSoftLimitActivated;
Menu.Add(_disableTrafficItem);
Menu.Add(_disablePauseAlt);
Menu.Add(_flipMenuItem);
Menu.Add(_disableVoice);
Menu.Add(_menuKey);
Menu.Add(_passengerKey);
Menu.Add(_vehicleSoftLimit);
}
private static void DisableVoiceCheckboxChanged(object sender, EventArgs e)
{
if (_disableVoice.Checked && !Sync.Voice.WasInitialized())
{
Sync.Voice.InitRecording();
} else {
Sync.Voice.ClearAll();
}
Main.Settings.Voice = _disableVoice.Checked;
Util.SaveSettings();
}
private static void _disablePauseAlt_CheckboxChanged(object sender, EventArgs e)
private static void DisablePauseAltCheckboxChanged(object sender, EventArgs e)
{
Main.Settings.DisableAlternatePause=_disablePauseAlt.Checked;
Util.SaveSettings();
}
private static void vehicleSoftLimit_Activated(object sender, EventArgs e)
private static void VehicleSoftLimitActivated(object sender, EventArgs e)
{
try
{
@ -110,6 +117,5 @@ namespace RageCoop.Client.Menus
Main.Settings.FlipMenu = _flipMenuItem.Checked;
Util.SaveSettings();
}
}
}

View File

@ -77,6 +77,10 @@ namespace RageCoop.Client
{
CoopMenu.ConnectedMenuSetting();
Main.MainChat.Init();
if (Main.Settings.Voice && !Sync.Voice.WasInitialized())
{
Sync.Voice.InitRecording();
}
GTA.UI.Notification.Show("~g~Connected!");
});
@ -259,6 +263,21 @@ namespace RageCoop.Client
}
break;
case PacketType.Voice:
{
if (Main.Settings.Voice)
{
Packets.Voice packet = new Packets.Voice();
packet.Deserialize(data);
Main.QueueAction(() =>
{
Sync.Voice.AddVoiceData(packet.Buffer, packet.Recorded);
});
}
}
break;
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent(_resolveHandle);
@ -366,7 +385,6 @@ namespace RageCoop.Client
v.ThrottlePower=packet.ThrottlePower;
v.BrakePower=packet.BrakePower;
v.Velocity=packet.Velocity;
v.Acceleration=packet.Acceleration;
v.RotationVelocity=packet.RotationVelocity;
v.DeluxoWingRatio=packet.DeluxoWingRatio;
v.LastSynced=Main.Ticked;

View File

@ -109,7 +109,6 @@ namespace RageCoop.Client
packet.ThrottlePower = veh.ThrottlePower;
packet.BrakePower = veh.BrakePower;
if (v.LastVelocity==default) {v.LastVelocity=packet.Velocity; }
packet.Acceleration = (packet.Velocity-v.LastVelocity)*1000/v.LastSentStopWatch.ElapsedMilliseconds;
v.LastSentStopWatch.Restart();
v.LastVelocity= packet.Velocity;
if (packet.Flags.HasVehFlag(VehicleDataFlags.IsDeluxoHovering)) { packet.DeluxoWingRatio=v.MainVehicle.GetDeluxoWingRatio(); }
@ -179,5 +178,9 @@ namespace RageCoop.Client
{ Username = Main.Settings.Username, Message = message },ServerConnection, ConnectionChannel.Chat, NetDeliveryMethod.ReliableOrdered);
Peer.FlushSendQueue();
}
public static void SendVoiceMessage(byte[] buffer, int recorded)
{
SendSync(new Packets.Voice() { Buffer = buffer, Recorded = recorded }, ConnectionChannel.Voice, NetDeliveryMethod.ReliableOrdered);
}
}
}

View File

@ -57,6 +57,7 @@
<Compile Include="Sync\Entities\SyncedVehicle.cs" />
<Compile Include="Sync\EntityPool.cs" />
<Compile Include="Sync\SyncEvents.cs" />
<Compile Include="Sync\Voice.cs" />
<Compile Include="Util\Memory.cs" />
<Compile Include="Util\NativeCaller.cs" />
<Compile Include="Util\PedConfigFlags.cs" />
@ -97,6 +98,30 @@
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Win32.Registry, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Win32.Registry.4.7.0\lib\net461\Microsoft.Win32.Registry.dll</HintPath>
</Reference>
<Reference Include="NAudio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.2.1.0\lib\net472\NAudio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Asio, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Asio.2.1.0\lib\netstandard2.0\NAudio.Asio.dll</HintPath>
</Reference>
<Reference Include="NAudio.Core, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Core.2.1.0\lib\netstandard2.0\NAudio.Core.dll</HintPath>
</Reference>
<Reference Include="NAudio.Midi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Midi.2.1.0\lib\netstandard2.0\NAudio.Midi.dll</HintPath>
</Reference>
<Reference Include="NAudio.Wasapi, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.Wasapi.2.1.0\lib\netstandard2.0\NAudio.Wasapi.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinForms, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.WinForms.2.1.0\lib\net472\NAudio.WinForms.dll</HintPath>
</Reference>
<Reference Include="NAudio.WinMM, Version=2.1.0.0, Culture=neutral, PublicKeyToken=e279aa5131008a41, processorArchitecture=MSIL">
<HintPath>..\packages\NAudio.WinMM.2.1.0\lib\netstandard2.0\NAudio.WinMM.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\libs\Newtonsoft.Json.dll</HintPath>
@ -205,6 +230,9 @@
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.AccessControl, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.AccessControl.4.7.0\lib\net461\System.Security.AccessControl.dll</HintPath>
</Reference>
<Reference Include="System.Security.Cryptography.Algorithms, Version=4.2.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net463\System.Security.Cryptography.Algorithms.dll</HintPath>
<Private>True</Private>
@ -225,6 +253,9 @@
<Private>True</Private>
<Private>True</Private>
</Reference>
<Reference Include="System.Security.Principal.Windows, Version=4.1.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Security.Principal.Windows.4.7.0\lib\net461\System.Security.Principal.Windows.dll</HintPath>
</Reference>
<Reference Include="System.Text.RegularExpressions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Text.RegularExpressions.4.3.0\lib\net463\System.Text.RegularExpressions.dll</HintPath>
<Private>True</Private>

View File

@ -27,6 +27,10 @@ namespace RageCoop.Client
/// Don't use it!
/// </summary>
public bool FlipMenu { get; set; } = false;
/// <summary>
/// Don't use it!
/// </summary>
public bool Voice { get; set; } = false;
/// <summary>
/// LogLevel for RageCoop.

View File

@ -11,11 +11,11 @@ namespace RageCoop.Client
ID=EntityPool.RequestNewID();
MainProjectile = p;
Origin=p.Position;
var shooter = EntityPool.GetPedByHandle((p.Owner?.Handle).GetValueOrDefault());
var shooter = EntityPool.GetPedByHandle((p.OwnerEntity?.Handle).GetValueOrDefault());
if (shooter==null)
{
// Owner will be the vehicle if projectile is shot with a vehicle
var shooterVeh = EntityPool.GetVehicleByHandle((p.Owner?.Handle).GetValueOrDefault());
var shooterVeh = EntityPool.GetVehicleByHandle((p.OwnerEntity?.Handle).GetValueOrDefault());
if (shooterVeh!=null && shooterVeh.MainVehicle.Driver!=null)
{
shooter=shooterVeh.MainVehicle.Driver?.GetSyncEntity();

View File

@ -57,7 +57,6 @@ namespace RageCoop.Client
#endregion
#region -- CRITICAL STUFF --
internal Vector3 Acceleration { get; set; }
internal Vector3 RotationVelocity { get; set; }
internal float SteeringAngle { get; set; }
internal float ThrottlePower { get; set; }

View File

@ -0,0 +1,104 @@
using System.Threading;
using NAudio.Wave;
namespace RageCoop.Client.Sync
{
internal static class Voice
{
private static bool _initialized = false;
public static bool IsRecording = false;
private static WaveInEvent _waveIn;
private static BufferedWaveProvider _waveProvider = new BufferedWaveProvider(new WaveFormat(16000, 16, 1));
private static Thread _thread;
public static bool WasInitialized() => _initialized;
public static void ClearAll()
{
_waveProvider.ClearBuffer();
StopRecording();
if (_thread != null && _thread.IsAlive)
{
_thread.Abort();
_thread = null;
}
_initialized = false;
}
public static void StopRecording()
{
if (_waveIn != null)
{
_waveIn.StopRecording();
_waveIn.Dispose();
_waveIn = null;
}
IsRecording = false;
}
public static void InitRecording()
{
if (_initialized)
return;
// I tried without thread but the game will lag without
_thread = new Thread(new ThreadStart(() =>
{
while (true)
{
using (var wo = new WaveOutEvent())
{
wo.Init(_waveProvider);
wo.Play();
while (wo.PlaybackState == PlaybackState.Playing)
{
Thread.Sleep(100);
}
}
}
}));
_thread.Start();
_initialized = true;
}
public static void StartRecording()
{
if (IsRecording)
return;
IsRecording = true;
_waveIn = new WaveInEvent
{
DeviceNumber = 0,
BufferMilliseconds = 20,
NumberOfBuffers = 1,
WaveFormat = _waveProvider.WaveFormat
};
_waveIn.DataAvailable += WaveInDataAvailable;
_waveIn.StartRecording();
}
public static void AddVoiceData(byte[] buffer, int recorded)
{
_waveProvider.AddSamples(buffer, 0, recorded);
}
private static void WaveInDataAvailable(object sender, WaveInEventArgs e)
{
if (_waveIn == null || !IsRecording)
return;
Networking.SendVoiceMessage(e.Buffer, e.BytesRecorded);
}
}
}

View File

@ -5,6 +5,14 @@
<package id="Microsoft.Extensions.ObjectPool" version="6.0.8" targetFramework="net48" />
<package id="Microsoft.NETCore.Platforms" version="1.1.0" targetFramework="net48" />
<package id="Microsoft.Win32.Primitives" version="4.3.0" targetFramework="net48" />
<package id="Microsoft.Win32.Registry" version="4.7.0" targetFramework="net481" />
<package id="NAudio" version="2.1.0" targetFramework="net481" />
<package id="NAudio.Asio" version="2.1.0" targetFramework="net481" />
<package id="NAudio.Core" version="2.1.0" targetFramework="net481" />
<package id="NAudio.Midi" version="2.1.0" targetFramework="net481" />
<package id="NAudio.Wasapi" version="2.1.0" targetFramework="net481" />
<package id="NAudio.WinForms" version="2.1.0" targetFramework="net481" />
<package id="NAudio.WinMM" version="2.1.0" targetFramework="net481" />
<package id="NETStandard.Library" version="1.6.1" targetFramework="net48" />
<package id="SharpZipLib" version="1.3.3" targetFramework="net48" />
<package id="System.AppContext" version="4.3.0" targetFramework="net48" />
@ -38,10 +46,12 @@
<package id="System.Runtime.InteropServices" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.InteropServices.RuntimeInformation" version="4.3.0" targetFramework="net48" />
<package id="System.Runtime.Numerics" version="4.3.0" targetFramework="net48" />
<package id="System.Security.AccessControl" version="4.7.0" targetFramework="net481" />
<package id="System.Security.Cryptography.Algorithms" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.Primitives" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Cryptography.X509Certificates" version="4.3.0" targetFramework="net48" />
<package id="System.Security.Principal.Windows" version="4.7.0" targetFramework="net481" />
<package id="System.Text.Encoding" version="4.3.0" targetFramework="net48" />
<package id="System.Text.Encoding.Extensions" version="4.3.0" targetFramework="net48" />
<package id="System.Text.RegularExpressions" version="4.3.0" targetFramework="net48" />

View File

@ -34,10 +34,13 @@ namespace RageCoop.Core
P2PConnect = 19,
HolePunchInit=20,
HolePunch=21,
Voice = 22,
#region Sync
PedSync = 22,
VehicleSync = 23,
ProjectileSync =24,
PedSync = 23,
VehicleSync = 24,
ProjectileSync =25,
#endregion
#region EVENT
@ -66,17 +69,18 @@ namespace RageCoop.Core
internal enum ConnectionChannel
{
Default = 0,
Chat = 5,
Native = 6,
Mod = 7,
File = 8,
Event = 9,
RequestResponse=10,
PingPong = 11,
VehicleSync =20,
PedSync=21,
ProjectileSync = 22,
SyncEvents =30,
Chat = 1,
Voice = 2,
Native = 3,
Mod = 4,
File = 5,
Event = 6,
RequestResponse=7,
PingPong = 8,
VehicleSync = 9,
PedSync= 10,
ProjectileSync = 11,
SyncEvents = 12,
}
[Flags]

View File

@ -26,7 +26,6 @@ namespace RageCoop.Core
// public Vector3 Rotation { get; set; }
public Vector3 Velocity { get; set; }
public Vector3 Acceleration { get; set; }
public Vector3 RotationVelocity { get; set; }
@ -75,7 +74,6 @@ namespace RageCoop.Core
byteArray.AddVector3(Position);
byteArray.AddQuaternion(Quaternion);
byteArray.AddVector3(Velocity);
byteArray.AddVector3(Acceleration);
byteArray.AddVector3(RotationVelocity);
byteArray.AddFloat(ThrottlePower);
byteArray.AddFloat(BrakePower);
@ -189,8 +187,6 @@ namespace RageCoop.Core
// Read velocity
Velocity =reader.ReadVector3();
Acceleration=reader.ReadVector3();
// Read rotation velocity
RotationVelocity=reader.ReadVector3();

View File

@ -0,0 +1,27 @@
using System.Collections.Generic;
namespace RageCoop.Core
{
internal partial class Packets
{
internal class Voice : Packet
{
public byte[] Buffer { get; set; }
public int Recorded { get; set; }
public override PacketType Type => PacketType.Voice;
public override byte[] Serialize()
{
var data = new List<byte>();
data.AddArray(Buffer);
data.AddInt(Recorded);
return data.ToArray();
}
public override void Deserialize(byte[] array)
{
var reader = new BitReader(array);
Buffer = reader.ReadByteArray();
Recorded = reader.ReadInt32();
}
}
}
}

View File

@ -484,6 +484,12 @@ namespace RageCoop.Server
}
break;
case PacketType.Voice:
{
Forward(data.GetPacket<Packets.Voice>(),sender,ConnectionChannel.Voice);
}
break;
case PacketType.CustomEvent:
{
Packets.CustomEvent packet = new Packets.CustomEvent();
@ -714,5 +720,17 @@ namespace RageCoop.Server
p.Pack(outgoingMessage);
MainNetServer.SendMessage(outgoingMessage, client.Connection,method,(int)channel);
}
internal void Forward(Packet p, Client except, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
p.Pack(outgoingMessage);
MainNetServer.SendToAll(outgoingMessage, except.Connection, method, (int)channel);
}
internal void SendToAll(Packet p, ConnectionChannel channel = ConnectionChannel.Default, NetDeliveryMethod method = NetDeliveryMethod.UnreliableSequenced)
{
NetOutgoingMessage outgoingMessage = MainNetServer.CreateMessage();
p.Pack(outgoingMessage);
MainNetServer.SendToAll(outgoingMessage, method, (int)channel);
}
}
}

View File

@ -95,6 +95,11 @@
/// </summary>
public bool UseZeroTier { get; set; } = false;
/// <summary>
/// Use in-game voice chat to communicate with other players
/// </summary>
public bool UseVoice { get; set; } = false;
/// <summary>
/// The zerotier network id to join, default value is zerotier's public Earth network.
/// </summary>

View File

@ -4,10 +4,9 @@ using System.Xml;
using System.Xml.Serialization;
using System.Linq;
using System.Collections.Generic;
using RageCoop.Core;
using Lidgren.Network;
using System.Net;
using System.Net.Sockets;
using System.Net.Http;
namespace RageCoop.Server
{
@ -22,9 +21,14 @@ namespace RageCoop.Server
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12;
ServicePointManager.ServerCertificateValidationCallback = delegate { return true; };
WebClient client = new();
return client.DownloadString(url);
HttpClient client = new();
HttpRequestMessage request = new(HttpMethod.Get, url);
HttpResponseMessage response = client.Send(request);
using var reader = new StreamReader(response.Content.ReadAsStream());
string responseBody = reader.ReadToEnd();
return responseBody;
}
catch
{
@ -113,15 +117,17 @@ namespace RageCoop.Server
string newUrl = url;
do
{
HttpWebRequest req = null;
HttpWebResponse resp = null;
try
{
req = (HttpWebRequest)HttpWebRequest.Create(url);
req.Method = "HEAD";
req.AllowAutoRedirect = false;
resp = (HttpWebResponse)req.GetResponse();
switch (resp.StatusCode)
HttpClientHandler handler = new()
{
AllowAutoRedirect = false
};
HttpClient client = new(handler);
HttpRequestMessage request = new(HttpMethod.Head, url);
HttpResponseMessage response = client.Send(request);
switch (response.StatusCode)
{
case HttpStatusCode.OK:
return newUrl;
@ -129,11 +135,13 @@ namespace RageCoop.Server
case HttpStatusCode.MovedPermanently:
case HttpStatusCode.RedirectKeepVerb:
case HttpStatusCode.RedirectMethod:
newUrl = resp.Headers["Location"];
newUrl = response.Headers.Location.ToString();
if (newUrl == null)
return url;
if (newUrl.IndexOf("://", System.StringComparison.Ordinal) == -1)
string newUrlString = newUrl;
if (!newUrlString.Contains("://"))
{
// Doesn't have a URL Schema, meaning it's a relative or absolute URL
Uri u = new Uri(new Uri(url), newUrl);
@ -143,6 +151,7 @@ namespace RageCoop.Server
default:
return newUrl;
}
url = newUrl;
}
catch (WebException)
@ -154,11 +163,6 @@ namespace RageCoop.Server
{
return null;
}
finally
{
if (resp != null)
resp.Close();
}
} while (maxRedirCount-- > 0);
return newUrl;