diff --git a/BackendServices/CastleLibrary/XI5/Reader/TicketReader.cs b/BackendServices/CastleLibrary/XI5/Reader/TicketReader.cs new file mode 100644 index 000000000..2219e65bc --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Reader/TicketReader.cs @@ -0,0 +1,115 @@ +using BitConverterExtension; +using System; +using System.IO; +using System.Text; +using XI5.Types; + +namespace XI5.Reader +{ + public class TicketReader : BinaryReader + { + // setup endian converter + private static readonly BigEndianBitConverter bigEndian = new(); + + public TicketReader(Stream input) : base(input) { } + + #region Big Endian Conversion + + public override short ReadInt16() + { + byte[] bytes = ReadBytes(2); + return bigEndian.ToInt16(bytes, 0); + } + + public override int ReadInt32() + { + byte[] bytes = ReadBytes(4); + return bigEndian.ToInt32(bytes, 0); + } + + public override long ReadInt64() + { + byte[] bytes = ReadBytes(8); + return bigEndian.ToInt64(bytes, 0); + } + + public override ushort ReadUInt16() + { + byte[] bytes = ReadBytes(2); + return bigEndian.ToUInt16(bytes, 0); + } + + public override uint ReadUInt32() + { + byte[] bytes = ReadBytes(4); + return bigEndian.ToUInt32(bytes, 0); + } + + public override ulong ReadUInt64() + { + byte[] bytes = ReadBytes(8); + return bigEndian.ToUInt64(bytes, 0); + } + + #endregion + + internal TicketVersion ReadTicketVersion() => new TicketVersion((byte)(ReadByte() >> 4), ReadByte()); + + internal ushort ReadTicketHeader() + { + ReadBytes(4); // header + return ReadUInt16(); // ticket length + } + + internal TicketDataSection ReadTicketSectionHeader() + { + long position = this.BaseStream.Position; + + byte sectionHeader = this.ReadByte(); + if (sectionHeader != 0x30) + throw new FormatException($"[XI5Ticket] - Expected 0x30 for section header, was {sectionHeader}. Offset is {this.BaseStream.Position}"); + + TicketDataSectionType type = (TicketDataSectionType)this.ReadByte(); + ushort length = this.ReadUInt16(); + + return new TicketDataSection(type, length, position); + } + + private TicketData ReadTicketData(TicketDataType expectedType) + { + TicketData data = new TicketData((TicketDataType)ReadUInt16(), ReadUInt16()); + if (data.Type != expectedType && expectedType != TicketDataType.Empty) + throw new FormatException($"[XI5Ticket] - Expected data type to be {expectedType}, was really {data.Type} ({(int)data.Type})"); + + return data; + } + + internal byte[] ReadTicketBinaryData(TicketDataType type = TicketDataType.Binary) + => ReadBytes(ReadTicketData(type).Length); + internal string ReadTicketStringData(TicketDataType type = TicketDataType.String) + => Encoding.Default.GetString(ReadTicketBinaryData(type)).TrimEnd('\0'); + + internal uint ReadTicketUInt32Data() + { + ReadTicketData(TicketDataType.UInt32); + return ReadUInt32(); + } + + internal ulong ReadTicketUInt64Data() + { + ReadTicketData(TicketDataType.UInt64); + return ReadUInt64(); + } + + internal DateTimeOffset ReadTicketTimestampData() + { + ReadTicketData(TicketDataType.Timestamp); + return DateTimeOffset.FromUnixTimeMilliseconds((long)ReadUInt64()); + } + + internal void SkipTicketEmptyData(int sections = 1) + { + for (int i = 0; i < sections; i++) ReadTicketData(TicketDataType.Empty); + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/StreamExtensions.cs b/BackendServices/CastleLibrary/XI5/StreamExtensions.cs deleted file mode 100644 index cd340543a..000000000 --- a/BackendServices/CastleLibrary/XI5/StreamExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.IO; - -namespace XI5 -{ - internal static class StreamExtensions - { - public static uint ReadUInt(this Stream stream) - { - byte[] buffer = new byte[4]; - stream.Read(buffer, 0, 4); - if (BitConverter.IsLittleEndian) - Array.Reverse(buffer); - return BitConverter.ToUInt32(buffer, 0); - } - - public static ulong ReadULong(this Stream stream) - { - byte[] buffer = new byte[8]; - stream.Read(buffer, 0, 8); - if (BitConverter.IsLittleEndian) - Array.Reverse(buffer); - return BitConverter.ToUInt64(buffer, 0); - } - - public static ushort ReadUShort(this Stream stream) - { - byte[] buffer = new byte[2]; - stream.Read(buffer, 0, 2); - if (BitConverter.IsLittleEndian) - Array.Reverse(buffer); - return BitConverter.ToUInt16(buffer, 0); - } - - public static bool ReadAll(this Stream stream, byte[] buffer, int startIndex, int count) - { - if (stream == null) - return false; - - int offset = 0; - while (offset < count) - { - int readCount = stream.Read(buffer, startIndex + offset, count - offset); - if (readCount == 0) - return false; - offset += readCount; - } - return true; - } - } -} diff --git a/BackendServices/CastleLibrary/XI5/Types/Parsers/TicketParser21.cs b/BackendServices/CastleLibrary/XI5/Types/Parsers/TicketParser21.cs new file mode 100644 index 000000000..d5e9695b9 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Types/Parsers/TicketParser21.cs @@ -0,0 +1,31 @@ +using XI5.Reader; + +namespace XI5.Types.Parsers +{ + internal static class TicketParser21 + { + internal static void ParseTicket(XI5Ticket ticket, TicketReader reader) + { + ticket.SerialId = reader.ReadTicketStringData(TicketDataType.Binary); + + ticket.IssuerId = reader.ReadTicketUInt32Data(); + + ticket.IssuedDate = reader.ReadTicketTimestampData(); + ticket.ExpiryDate = reader.ReadTicketTimestampData(); + + ticket.UserId = reader.ReadTicketUInt64Data(); + ticket.Username = reader.ReadTicketStringData(); + + ticket.Country = reader.ReadTicketStringData(TicketDataType.Binary); // No I am not going to brazil + ticket.Domain = reader.ReadTicketStringData(); + + ticket.ServiceId = reader.ReadTicketStringData(TicketDataType.Binary); + ticket.TitleId = XI5Ticket.ServiceIdRegex.Matches(ticket.ServiceId)[0].ToString(); + + ticket.Status = reader.ReadUInt32(); + + // Skip padding section in ticket + reader.SkipTicketEmptyData(3); + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Types/Parsers/TicketParser30.cs b/BackendServices/CastleLibrary/XI5/Types/Parsers/TicketParser30.cs new file mode 100644 index 000000000..8e401e0bb --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Types/Parsers/TicketParser30.cs @@ -0,0 +1,46 @@ +using System; +using XI5.Reader; + +namespace XI5.Types.Parsers +{ + internal static class TicketParser30 + { + internal static void ParseTicket(XI5Ticket ticket, TicketReader reader) + { + ticket.SerialId = reader.ReadTicketStringData(TicketDataType.Binary); + + ticket.IssuerId = reader.ReadTicketUInt32Data(); + + ticket.IssuedDate = reader.ReadTicketTimestampData(); + ticket.ExpiryDate = reader.ReadTicketTimestampData(); + + ticket.UserId = reader.ReadTicketUInt64Data(); + ticket.Username = reader.ReadTicketStringData(); + + ticket.Country = reader.ReadTicketStringData(TicketDataType.Binary); + ticket.Domain = reader.ReadTicketStringData(); + + ticket.ServiceId = reader.ReadTicketStringData(TicketDataType.Binary); + ticket.TitleId = XI5Ticket.ServiceIdRegex.Matches(ticket.ServiceId)[0].ToString(); + + TicketDataSection header = reader.ReadTicketSectionHeader(); + if (header.Type != TicketDataSectionType.DateOfBirth) + { + throw new FormatException($"[XI5Ticket] - Expected section to be {nameof(TicketDataSectionType.DateOfBirth)}, " + + $"was really {header.Type} ({(int)header.Type})"); + } + + reader.ReadUInt32(); // d.o.b + reader.SkipTicketEmptyData(2); + + header = reader.ReadTicketSectionHeader(); + if (header.Type != TicketDataSectionType.Age) + { + throw new FormatException($"[XI5Ticket] - Expected section to be {nameof(TicketDataSectionType.Age)}, " + + $"was really {header.Type} ({(int)header.Type})"); + } + + reader.SkipTicketEmptyData(); + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Types/TicketData.cs b/BackendServices/CastleLibrary/XI5/Types/TicketData.cs new file mode 100644 index 000000000..d084222b8 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Types/TicketData.cs @@ -0,0 +1,14 @@ +namespace XI5.Types +{ + public readonly struct TicketData + { + public readonly TicketDataType Type; + public readonly ushort Length; + + public TicketData(TicketDataType type, ushort length) + { + Type = type; + Length = length; + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Types/TicketDataSection.cs b/BackendServices/CastleLibrary/XI5/Types/TicketDataSection.cs new file mode 100644 index 000000000..4008c8828 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Types/TicketDataSection.cs @@ -0,0 +1,16 @@ +namespace XI5.Types +{ + public readonly struct TicketDataSection + { + public TicketDataSectionType Type { get; } + public ushort Length { get; } + public int Position { get; } + + public TicketDataSection(TicketDataSectionType type, ushort length, long position) + { + Type = type; + Length = length; + Position = (int)position; + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Types/TicketDataSectionType.cs b/BackendServices/CastleLibrary/XI5/Types/TicketDataSectionType.cs new file mode 100644 index 000000000..550d1ec5b --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Types/TicketDataSectionType.cs @@ -0,0 +1,10 @@ +namespace XI5.Types +{ + public enum TicketDataSectionType : byte + { + Body = 0, + Footer = 2, + Age = 16, + DateOfBirth = 17, + } +} diff --git a/BackendServices/CastleLibrary/XI5/Types/TicketDataType.cs b/BackendServices/CastleLibrary/XI5/Types/TicketDataType.cs new file mode 100644 index 000000000..38d525920 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Types/TicketDataType.cs @@ -0,0 +1,14 @@ +namespace XI5.Types +{ + public enum TicketDataType : ushort + { + Empty = 0, + UInt32 = 1, + UInt64 = 2, + + String = 4, + + Timestamp = 7, + Binary = 8, + } +} diff --git a/BackendServices/CastleLibrary/XI5/Types/TicketVersion.cs b/BackendServices/CastleLibrary/XI5/Types/TicketVersion.cs new file mode 100644 index 000000000..6a91bbded --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Types/TicketVersion.cs @@ -0,0 +1,19 @@ +namespace XI5.Types +{ + public readonly struct TicketVersion + { + public byte Major { get; } + public byte Minor { get; } + + internal TicketVersion(byte major, byte minor) + { + Major = major; + Minor = minor; + } + + public override string ToString() + { + return Major + "." + Minor; + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Verification/ITicketSigningKey.cs b/BackendServices/CastleLibrary/XI5/Verification/ITicketSigningKey.cs new file mode 100644 index 000000000..8e3942a17 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Verification/ITicketSigningKey.cs @@ -0,0 +1,14 @@ +namespace XI5.Verification +{ + /// + /// Defines the parameters for a key used for verification. + /// + public interface ITicketSigningKey + { + string HashAlgorithm { get; } + string CurveTable { get; } + TicketSignatureMessageType MessageType { get; } + string PublicKeyX { get; } + string PublicKeyY { get; } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Verification/Keys/Games/DefaultSigningKey.cs b/BackendServices/CastleLibrary/XI5/Verification/Keys/Games/DefaultSigningKey.cs new file mode 100644 index 000000000..195613fde --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Verification/Keys/Games/DefaultSigningKey.cs @@ -0,0 +1,8 @@ +namespace XI5.Verification.Keys.Games +{ + public class DefaultSigningKey : PsnSigningKey + { + public override string PublicKeyX => "39c62d061d4ee35c5f3f7531de0af3cf918346526edac727"; + public override string PublicKeyY => "a5d578b55113e612bf1878d4cc939d61a41318403b5bdf86"; + } +} diff --git a/BackendServices/CastleLibrary/XI5/Verification/Keys/PsnSigningKey.cs b/BackendServices/CastleLibrary/XI5/Verification/Keys/PsnSigningKey.cs new file mode 100644 index 000000000..e500f5f16 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Verification/Keys/PsnSigningKey.cs @@ -0,0 +1,15 @@ +namespace XI5.Verification.Keys +{ + /// + /// A public signing key used when PS3/PSVita clients connect via official PSN. + /// Since each game series has a different set of curves, you must provide them yourself in a new class. + /// + public abstract class PsnSigningKey : ITicketSigningKey + { + public string HashAlgorithm => "SHA-1"; + public string CurveTable => "secp192r1"; + public TicketSignatureMessageType MessageType => TicketSignatureMessageType.Ticket; + public abstract string PublicKeyX { get; } + public abstract string PublicKeyY { get; } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Verification/Keys/RpcnSigningKey.cs b/BackendServices/CastleLibrary/XI5/Verification/Keys/RpcnSigningKey.cs new file mode 100644 index 000000000..5a41017c6 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Verification/Keys/RpcnSigningKey.cs @@ -0,0 +1,17 @@ +namespace XI5.Verification.Keys +{ + /// + /// The public signing key used when RPCS3 clients connect via RPCN. + /// + public class RpcnSigningKey : ITicketSigningKey + { + public static readonly RpcnSigningKey Instance = new RpcnSigningKey(); + private RpcnSigningKey() { } + + public string HashAlgorithm => "SHA-224"; + public string CurveTable => "secp224k1"; + public TicketSignatureMessageType MessageType => TicketSignatureMessageType.Body; + public string PublicKeyX => "b07bc0f0addb97657e9f389039e8d2b9c97dc2a31d3042e7d0479b93"; + public string PublicKeyY => "d81c42b0abdf6c42191a31e31f93342f8f033bd529c2c57fdb5a0a7d"; + } +} diff --git a/BackendServices/CastleLibrary/XI5/Verification/SigningKeyResolver.cs b/BackendServices/CastleLibrary/XI5/Verification/SigningKeyResolver.cs new file mode 100644 index 000000000..77a84aec1 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Verification/SigningKeyResolver.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using XI5.Verification.Keys; +using XI5.Verification.Keys.Games; + +namespace XI5.Verification +{ + public static class SigningKeyResolver + { + // Map Title IDs to PSN signing keys + private static readonly Dictionary PsnKeys = new(StringComparer.OrdinalIgnoreCase) + { + // game title ID, signing key, e.x + // { "NPUA80093", new WarhawkSigningKey() }, + }; + + /// + /// Returns the appropriate signing key based on the issuer and title ID. + /// + public static ITicketSigningKey GetSigningKey(string issuer, string titleId) + { + // rpcn signing key + if (issuer.Equals("RPCN", StringComparison.OrdinalIgnoreCase)) + return RpcnSigningKey.Instance; + + // psn game signing key + if (!string.IsNullOrWhiteSpace(titleId) && PsnKeys.TryGetValue(titleId, out var psnKey)) + return psnKey; + + // default signing key + return new DefaultSigningKey(); + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/Verification/TicketSignatureMessageTypes.cs b/BackendServices/CastleLibrary/XI5/Verification/TicketSignatureMessageTypes.cs new file mode 100644 index 000000000..6000585e1 --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Verification/TicketSignatureMessageTypes.cs @@ -0,0 +1,8 @@ +namespace XI5.Verification +{ + public enum TicketSignatureMessageType : byte + { + Ticket = 0, + Body = 1, + } +} diff --git a/BackendServices/CastleLibrary/XI5/Verification/TicketVerifier.cs b/BackendServices/CastleLibrary/XI5/Verification/TicketVerifier.cs new file mode 100644 index 000000000..db7673ddc --- /dev/null +++ b/BackendServices/CastleLibrary/XI5/Verification/TicketVerifier.cs @@ -0,0 +1,80 @@ +using Org.BouncyCastle.Asn1.X9; +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Security; +using System; +using System.Linq; + +namespace XI5.Verification +{ + public class TicketVerifier + { + // credit: LittleBigRefresh team + + private readonly TicketSignatureMessageType _messageType; + private readonly XI5Ticket _ticket; + private readonly byte[] _ticketData; + private readonly ISigner _signer; + + public TicketVerifier(byte[] ticketData, XI5Ticket ticket, ITicketSigningKey key) + { + this._messageType = key.MessageType; + this._ticketData = ticketData; + this._ticket = ticket; + + X9ECParameters xParams = ECNamedCurveTable.GetByName(key.CurveTable); + ECDomainParameters domainParams = new ECDomainParameters(xParams.Curve, xParams.G, xParams.N, xParams.H, xParams.GetSeed()); + Org.BouncyCastle.Math.EC.ECPoint ecPoint = domainParams.Curve.CreatePoint(new Org.BouncyCastle.Math.BigInteger(key.PublicKeyX, 16), new Org.BouncyCastle.Math.BigInteger(key.PublicKeyY, 16)); + + ECPublicKeyParameters publicKey = new ECPublicKeyParameters(ecPoint, domainParams); + this._signer = SignerUtilities.GetSigner(key.HashAlgorithm + "withECDSA"); + this._signer.Init(false, publicKey); + } + + // https://github.com/LBPUnion/ProjectLighthouse/blob/80cfb24d6f72ecdf45c8389b29a89fd1c13d0a96/ProjectLighthouse/Tickets/Signature/TicketSignatureVerifier.cs#L30 + // Sometimes psn signatures have one or two extra empty bytes + // This is slow but it's better than carelessly chopping 0's + private static byte[] TrimSignature(byte[] signature) + { + for (int i = 0; i <= 2; i++) + { + try + { + Asn1Object.FromByteArray(signature); + break; + } + catch + { + signature = signature.SkipLast(1).ToArray(); + } + } + + return signature; + } + + public bool IsTicketValid() + { + int inOff; + int inLen; + + switch (this._messageType) + { + case TicketSignatureMessageType.Body: + inOff = this._ticket.BodySection.Position; + inLen = this._ticket.BodySection.Length + 4; + break; + case TicketSignatureMessageType.Ticket: + inOff = 0; + inLen = this._ticketData.Length - this._ticket.SignatureData.Length; + break; + default: + throw new NotImplementedException(this._messageType.ToString()); + } + + this._signer.BlockUpdate(this._ticketData, inOff, inLen); + + return this._signer.VerifySignature(TrimSignature(this._ticket.SignatureData)); + } + } +} diff --git a/BackendServices/CastleLibrary/XI5/XI5Ticket.cs b/BackendServices/CastleLibrary/XI5/XI5Ticket.cs index a12da3131..d840b3d0a 100644 --- a/BackendServices/CastleLibrary/XI5/XI5Ticket.cs +++ b/BackendServices/CastleLibrary/XI5/XI5Ticket.cs @@ -1,229 +1,153 @@ -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Crypto.Signers; -using Org.BouncyCastle.OpenSsl; -using System; -#if NET6_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; -#endif -using System.IO; -using System.Text; - -namespace XI5 -{ - //https://www.psdevwiki.com/ps3/X-I-5-Ticket - //https://github.com/RipleyTom/rpcn/blob/master/src/server/client/ticket.rs - public class XI5Ticket - { - const uint XI5_VER_2_0 = 553648128; - const uint XI5_VER_2_1 = 553713664; - const uint XI5_VER_3_0 = 822083584; - const uint XI5_VER_4_0 = 1090519040; - - private static ECDsaSigner ECDsaRPCN; - - static XI5Ticket() - { - PemReader pr = new PemReader(new StringReader("-----BEGIN PUBLIC KEY-----\r\nME4wEAYHKoZIzj0CAQYFK4EEACADOgAEsHvA8K3bl2V+nziQOejSucl9wqMdMELn\r\n0Eebk9gcQrCr32xCGRox4x+TNC+PAzvVKcLFf9taCn0=\r\n-----END PUBLIC KEY-----")); - ECDsaRPCN = new ECDsaSigner(); - ECDsaRPCN.Init(false, (ECPublicKeyParameters)pr.ReadObject()); - } - - public string TicketVersion { get; private set; } - public string Serial { get; private set; } - public uint IssuerId { get; private set; } - public DateTime? Issued { get; private set; } - public DateTime? Expires { get; private set; } - public ulong UserId { get; private set; } - public string OnlineId { get; private set; } - public string Region { get; private set; } - public string Domain { get; private set; } - public string ServiceId { get; private set; } - public uint Status { get; private set; } - public string IssuerName { get; private set; } - - private byte[] _fullBodyData; - private byte[] _signature; - - public XI5Ticket(byte[] data) - { - using (MemoryStream ms = new MemoryStream(data)) - { - uint version = ms.ReadUInt(); - - if (version != XI5_VER_2_0 && version != XI5_VER_2_1 && version != XI5_VER_3_0 && version != XI5_VER_4_0) - throw new NotSupportedException("Invalid ticket version: " + version); //invalid version - - TicketVersion = version == XI5_VER_2_0 ? "XI5_VER_2_0" : version == XI5_VER_2_1 ? "XI5_VER_2_1" : version == XI5_VER_3_0 ? "XI5_VER_3_0" : version == XI5_VER_4_0 ? "XI5_VER_4_0" : "UNKNOWN"; - if (version != XI5_VER_2_1) - { - Directory.CreateDirectory("bad_xi5"); - File.WriteAllBytes("bad_xi5/" + DateTime.Now.Ticks + ".bin", data); - throw new NotImplementedException("Reading " + TicketVersion + " ticket is not yet implemented."); - } - - uint size = ms.ReadUInt(); - if (size != ms.Length - 8) //invalid data - throw new ArgumentException($"Specified ticket size: {size} | Actual ticket size : {ms.Length - 8}"); - - ParseTicketV2_1(ms); - } - } - -#if NET5_0_OR_GREATER - [MemberNotNull(nameof(Serial), nameof(OnlineId), nameof(Region), nameof(Domain), nameof(ServiceId), nameof(IssuerName), nameof(_signature), nameof(_fullBodyData))] -#endif - private void ParseTicketV2_1(MemoryStream ms) - { - _fullBodyData = ReadFullBody(ms); - byte[] footer = ReadFooter(ms); - - using (MemoryStream bodyStream = new MemoryStream(_fullBodyData)) - { - bodyStream.Seek(4, SeekOrigin.Begin); //skip existing dt and size - - Serial = ReadBinaryAsString(bodyStream); - IssuerId = ReadUInt(bodyStream); - Issued = ReadTime(bodyStream); - Expires = ReadTime(bodyStream); - UserId = ReadULong(bodyStream); - OnlineId = ReadString(bodyStream); - Region = ReadBinaryAsString(bodyStream); - Domain = ReadString(bodyStream); - ServiceId = ReadBinaryAsString(bodyStream); - Status = ReadUInt(bodyStream); - } - - using (MemoryStream footerStream = new MemoryStream(footer)) - { - IssuerName = ReadBinaryAsString(footerStream); - _signature = ReadBinary(footerStream); - } - - //TODO: check out this later - //optionally there is also cookie (Binary Data type) - //and 2 empty entries (Empty data type) - } - - public bool SignedByOfficialRPCN { - get - { - using (Asn1InputStream decoder = new Asn1InputStream(_signature)) - { - if (decoder.ReadObject() is DerSequence seq) - return ECDsaRPCN.VerifySignature(NetHasher.DotNetHasher.ComputeSHA224(_fullBodyData), ((DerInteger)seq[0]).Value, ((DerInteger)seq[1]).Value); - } - - return false; - } - } - - private static byte[] ReadFullField(Stream stream, Datatype expected) - { - Datatype dt = (Datatype)stream.ReadUShort(); - if (dt != expected) - throw new InvalidDataException($"Expected datatype: {expected} | Actual datatype: {dt}"); - - ushort size = stream.ReadUShort(); - byte[] data = new byte[size + 4]; //with datatype and size included - stream.Seek(-4, SeekOrigin.Current); - if (!stream.ReadAll(data, 0, data.Length)) - throw new EndOfStreamException($"Failed to read {size} bytes from stream"); - return data; - } - - private static byte[] ReadField(Stream stream, Datatype expected) - { - Datatype dt = (Datatype)stream.ReadUShort(); - if (dt != expected) - throw new InvalidDataException($"Expected datatype: {expected} | Actual datatype: {dt}"); - - ushort size = stream.ReadUShort(); - byte[] data = new byte[size]; - if (!stream.ReadAll(data, 0, size)) - throw new EndOfStreamException($"Failed to read {size} bytes from stream"); - return data; - } - - private static byte[] ReadBody(Stream stream) => ReadField(stream, Datatype.Body); - private static byte[] ReadFullBody(Stream stream) => ReadFullField(stream, Datatype.Body); - private static byte[] ReadFooter(Stream stream) => ReadField(stream, Datatype.Footer); - private static byte[] ReadFullFooter(Stream stream) => ReadFullField(stream, Datatype.Footer); - private static byte[] ReadBinary(Stream stream) => ReadField(stream, Datatype.Binary); - private static byte[] ReadFullBinary(Stream stream) => ReadFullField(stream, Datatype.Binary); - - private static string ReadBinaryAsString(Stream stream) - { - byte[] data = ReadBinary(stream); - int inx = Array.FindIndex(data, 0, (x) => x == 0);//search for 0 - if (inx >= 0) - return Encoding.UTF8.GetString(data, 0, inx); - return Encoding.UTF8.GetString(data); - } - - private static uint ReadUInt(Stream stream) - { - byte[] data = ReadField(stream, Datatype.UInt); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return BitConverter.ToUInt32(data, 0); - } - - private static DateTime ReadTime(Stream stream) - { - byte[] data = ReadField(stream, Datatype.Time); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return DateTimeOffset.FromUnixTimeMilliseconds((long)BitConverter.ToUInt64(data, 0)).UtcDateTime; - } - - private static ulong ReadULong(Stream stream) - { - byte[] data = ReadField(stream, Datatype.ULong); - if (BitConverter.IsLittleEndian) - Array.Reverse(data); - return BitConverter.ToUInt64(data, 0); - } - - private static string ReadString(Stream stream) - { - byte[] data = ReadField(stream, Datatype.String); - int inx = Array.FindIndex(data, 0, (x) => x == 0);//search for 0 - if (inx >= 0) - return Encoding.UTF8.GetString(data, 0, inx); - return Encoding.UTF8.GetString(data); - } - - public override string ToString() - { - StringBuilder builder = new StringBuilder(); - builder.AppendLine("{"); - builder.AppendLine(" TicketVersion = " + TicketVersion); - builder.AppendLine(" Serial = " + Serial); - builder.AppendLine(" IssuerId = " + IssuerId); - builder.AppendLine(" Issued = " + Issued); - builder.AppendLine(" Expires = " + Expires); - builder.AppendLine(" UserId = " + UserId); - builder.AppendLine(" OnlineId = " + OnlineId); - builder.AppendLine(" Region = " + Region); - builder.AppendLine(" Domain = " + Domain); - builder.AppendLine(" ServiceId = " + ServiceId); - builder.AppendLine(" Status = " + Status); - builder.AppendLine(" IssuerName = " + IssuerName); - builder.AppendLine("}"); - return builder.ToString(); - } - - enum Datatype : ushort - { - Empty = 0, - UInt = 1, - ULong = 2, - String = 4, - Time = 7, - Binary = 8, - Body = 0x3000, - Footer = 0x3002 - } - } +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using XI5.Reader; +using XI5.Types; +using XI5.Types.Parsers; +using XI5.Verification; +using CustomLogger; + +namespace XI5 +{ + // https://www.psdevwiki.com/ps3/X-I-5-Ticket + // https://github.com/RipleyTom/rpcn/blob/master/src/server/client/ticket.rs + // https://github.com/LittleBigRefresh/NPTicket/tree/main + + public class XI5Ticket + { + // constructor + public XI5Ticket() { } + + // fields + public TicketVersion Version { get; set; } + public string SerialId { get; set; } + public uint IssuerId { get; set; } + + public DateTimeOffset IssuedDate { get; set; } + public DateTimeOffset ExpiryDate { get; set; } + + public ulong UserId { get; set; } + public string Username { get; set; } + + public string Country { get; set; } + public string Domain { get; set; } + + public string ServiceId { get; set; } + public string TitleId { get; set; } + + public uint Status { get; set; } + public ushort TicketLength { get; set; } + public TicketDataSection BodySection { get; set; } + + public string SignatureIdentifier { get; set; } + public byte[] SignatureData { get; set; } + public bool Valid { get; protected set; } + + // TODO: Use GeneratedRegex, this is not in netstandard yet + internal static readonly Regex ServiceIdRegex = new Regex("(?<=-)[A-Z0-9]{9}(?=_)", RegexOptions.Compiled); + + public static XI5Ticket ReadFromBytes(byte[] ticketData) + { + using (var ms = new MemoryStream(ticketData)) + { + return ReadFromStream(ms); + } + } + + public static XI5Ticket ReadFromStream(Stream ticketStream) + { + byte[] ticketData; + if (ticketStream is MemoryStream ms && ms.TryGetBuffer(out ArraySegment buffer)) + ticketData = buffer.Array.Take((int)ms.Length).ToArray(); + + else + { + using (var tempMs = new MemoryStream()) + { + ticketStream.CopyTo(tempMs); + ticketData = tempMs.ToArray(); + } + + // reset stream position + ticketStream.Position = 0; + } + + // ticket version (2 bytes), header (4 bytes), ticket length (2 bytes) = 8 bytes + const int headerLength = sizeof(byte) + sizeof(byte) + sizeof(uint) + sizeof(ushort); + Debug.Assert(headerLength == 8, "Header length mismatch."); + + XI5Ticket ticket = new XI5Ticket(); + + using (var reader = new TicketReader(ticketStream)) + { + ticket.Version = reader.ReadTicketVersion(); + ticket.TicketLength = reader.ReadTicketHeader(); + + long actualLength = ticketStream.Length - headerLength; + if (ticket.TicketLength != actualLength) + throw new FormatException($"[XI5Ticket] - Expected ticket length to be {ticket.TicketLength} bytes, but was {actualLength} bytes."); + + ticket.BodySection = reader.ReadTicketSectionHeader(); + if (ticket.BodySection.Type != TicketDataSectionType.Body) + throw new FormatException($"[XI5Ticket] - Expected first section to be {nameof(TicketDataSectionType.Body)}, but was {ticket.BodySection.Type} ({(int)ticket.BodySection.Type})."); + + // ticket 2.1 + if (ticket.Version.Major == 2 && ticket.Version.Minor == 1) + TicketParser21.ParseTicket(ticket, reader); + + // ticket 3.0 + else if (ticket.Version.Major == 3 && ticket.Version.Minor == 0) + TicketParser30.ParseTicket(ticket, reader); + + // unhandled ticket version + else + throw new FormatException($"[XI5Ticket] - Unknown/unhandled ticket version {ticket.Version}."); + + var footer = reader.ReadTicketSectionHeader(); + if (footer.Type != TicketDataSectionType.Footer) + throw new FormatException($"[XI5Ticket] - Expected last section to be {nameof(TicketDataSectionType.Footer)}, but was {footer.Type} ({(int)footer.Type})."); + + ticket.SignatureIdentifier = reader.ReadTicketStringData(TicketDataType.Binary); + ticket.SignatureData = reader.ReadTicketBinaryData(); + } + + // verify ticket signature + ITicketSigningKey signingKey = SigningKeyResolver.GetSigningKey(ticket.SignatureIdentifier, ticket.TitleId); + TicketVerifier ticketVerifier = new TicketVerifier(ticketData, ticket, signingKey); + ticket.Valid = ticketVerifier.IsTicketValid(); + + // ticket invalid + if (!ticket.Valid) + LoggerAccessor.LogWarn($"[XI5Ticket] - Ticket for {ticket.TitleId} has invalid signature!"); + + return ticket; + } + + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendLine($"Version: {Version}"); + sb.AppendLine($"SerialId: {SerialId}"); + sb.AppendLine($"IssuerId: {IssuerId}"); + sb.AppendLine($"IssuedDate: {IssuedDate}"); + sb.AppendLine($"ExpiryDate: {ExpiryDate}"); + sb.AppendLine($"UserId: {UserId}"); + sb.AppendLine($"Username: {Username}"); + sb.AppendLine($"Country: {Country}"); + sb.AppendLine($"Domain: {Domain}"); + sb.AppendLine($"ServiceId: {ServiceId}"); + sb.AppendLine($"TitleId: {TitleId}"); + sb.AppendLine($"Status: {Status}"); + sb.AppendLine($"TicketLength: {TicketLength}"); + sb.AppendLine($"SignatureIdentifier: {SignatureIdentifier}"); + sb.AppendLine($"SignatureData: {(SignatureData != null ? BitConverter.ToString(SignatureData) : "null")}"); + sb.AppendLine($"Valid: {Valid}"); + + return sb.ToString(); + } + } } \ No newline at end of file diff --git a/Servers/Horizon/SERVER/Medius/MAS.cs b/Servers/Horizon/SERVER/Medius/MAS.cs index 559e289f9..1a7a80986 100644 --- a/Servers/Horizon/SERVER/Medius/MAS.cs +++ b/Servers/Horizon/SERVER/Medius/MAS.cs @@ -1,3343 +1,3316 @@ -using CustomLogger; -using DotNetty.Transport.Channels; -using Horizon.RT.Common; -using Horizon.RT.Cryptography; -using Horizon.RT.Cryptography.RSA; -using Horizon.RT.Models; -using Horizon.LIBRARY.Common; -using Horizon.SERVER.Config; -using Horizon.SERVER.PluginArgs; -using Horizon.PluginManager; -using System.Net; -using Horizon.LIBRARY.Database.Models; -using Newtonsoft.Json.Linq; -using System.Globalization; -using System.Text; -using Horizon.HTTPSERVICE; -using System.Buffers; -using NetworkLibrary.Extension; -using XI5; -using EndianTools; -using Horizon.MUM.Models; -using Horizon.SERVER.Extension.PlayStationHome; - -namespace Horizon.SERVER.Medius -{ - public class MAS : BaseMediusComponent - { - public override int TCPPort => MediusClass.Settings.MASPort; - public override int UDPPort => 00000; - - public static ServerSettings Settings = new(); - - public MAS() - { - - } - - public static void ReserveClient(ClientObject client) - { - MediusClass.Manager.AddClient(client); - } - - protected override async Task ProcessMessage(BaseScertMessage message, IChannel clientChannel, ChannelData data) - { - // Get ScertClient data - var scertClient = clientChannel.GetAttribute(LIBRARY.Pipeline.Constants.SCERT_CLIENT).Get(); - var enableEncryption = MediusClass.GetAppSettingsOrDefault(data.ApplicationId).EnableEncryption; - if (scertClient.CipherService != null) - scertClient.CipherService.EnableEncryption = enableEncryption; - - switch (message) - { - case RT_MSG_CLIENT_HELLO clientHello: - { - // send hello - Queue(new RT_MSG_SERVER_HELLO() { RsaPublicKey = enableEncryption ? MediusClass.Settings.DefaultKey.N : Org.BouncyCastle.Math.BigInteger.Zero }, clientChannel); - break; - } - case RT_MSG_CLIENT_CRYPTKEY_PUBLIC clientCryptKeyPublic: - { - if (clientCryptKeyPublic.PublicKey != null) - { - // generate new client session key - scertClient.CipherService?.GenerateCipher(CipherContext.RSA_AUTH, clientCryptKeyPublic.PublicKey.Reverse().ToArray()); - scertClient.CipherService?.GenerateCipher(CipherContext.RC_CLIENT_SESSION); - - Queue(new RT_MSG_SERVER_CRYPTKEY_PEER() { SessionKey = scertClient.CipherService?.GetPublicKey(CipherContext.RC_CLIENT_SESSION) }, clientChannel); - } - break; - } - case RT_MSG_CLIENT_CONNECT_TCP clientConnectTcp: - { - #region Check if AppId from Client matches Server - if (!MediusClass.Manager.IsAppIdSupported(clientConnectTcp.AppId)) - { - LoggerAccessor.LogError($"Client {clientChannel.RemoteAddress} attempting to authenticate with incompatible app id {clientConnectTcp.AppId}"); - await clientChannel.CloseAsync(); - return; - } - #endregion - - List pre108ServerComplete = new() { 10114, 10130, 10164, 10190, 10124, 10284, 10330, 10334, 10414, 10421, 10442, 10538, 10540, 10550, 10582, 10584, 10680, 10681, 10683, 10684, 10724 }; - - /// - /// Some do not post-108 so we have to account for those too! - /// tmheadon 10694 does not for initial - /// - List post108ServerComplete = new() { 10694 }; - - data.ApplicationId = clientConnectTcp.AppId; - scertClient.ApplicationID = clientConnectTcp.AppId; - - Channel? targetChannel = MediusClass.Manager.GetChannelByChannelId(clientConnectTcp.TargetWorldId, data.ApplicationId); - - if (targetChannel == null) - { - Channel DefaultChannel = MediusClass.Manager.GetOrCreateDefaultLobbyChannel(data.ApplicationId, scertClient.MediusVersion ?? 0); - - if (DefaultChannel.Id == clientConnectTcp.TargetWorldId) - targetChannel = DefaultChannel; - - if (targetChannel == null) - { - LoggerAccessor.LogError($"[MAS] - Client: {clientConnectTcp.AccessToken} tried to join, but targetted WorldId:{clientConnectTcp.TargetWorldId} doesn't exist!"); - await clientChannel.CloseAsync(); - break; - } - } - - // If booth are null, it means MAS client wants a new object. - if (!string.IsNullOrEmpty(clientConnectTcp.AccessToken) && !string.IsNullOrEmpty(clientConnectTcp.SessionKey)) - { - data.ClientObject = MediusClass.Manager.GetClientByAccessToken(clientConnectTcp.AccessToken, clientConnectTcp.AppId); - if (data.ClientObject == null) - data.ClientObject = MediusClass.Manager.GetClientBySessionKey(clientConnectTcp.SessionKey, clientConnectTcp.AppId); - - if (data.ClientObject != null) - LoggerAccessor.LogInfo($"[MAS] - Client Connected {clientChannel.RemoteAddress}!"); - else - { - data.Ignore = true; - LoggerAccessor.LogError($"[MAS] - ClientObject could not be granted for {clientChannel.RemoteAddress}: {clientConnectTcp}"); - break; - } - - data.ClientObject.MediusVersion = scertClient.MediusVersion ?? 0; - data.ClientObject.ApplicationId = clientConnectTcp.AppId; - data.ClientObject.OnConnected(); - } - else - { - LoggerAccessor.LogInfo($"[MAS] - Client Connected {clientChannel.RemoteAddress} with new ClientObject!"); - - data.ClientObject = new(scertClient.MediusVersion ?? 0) - { - ApplicationId = clientConnectTcp.AppId - }; - data.ClientObject.OnConnected(); - - ReserveClient(data.ClientObject); // ONLY RESERVE CLIENTS HERE! - } - - await data.ClientObject.JoinChannel(targetChannel); - - #region if PS3 - if (scertClient.IsPS3Client) - { - List ConnectAcceptTCPGames = new() { 20623, 20624, 21564, 21574, 21584, 21594, 22274, 22284, 22294, 22304, 20040, 20041, 20042, 20043, 20044 }; - - //CAC & Warhawk - if (ConnectAcceptTCPGames.Contains(scertClient.ApplicationID)) - { - Queue(new RT_MSG_SERVER_CONNECT_ACCEPT_TCP() - { - PlayerId = 0, - ScertId = GenerateNewScertClientId(), - PlayerCount = 0x0001, - IP = (clientChannel.RemoteAddress as IPEndPoint)?.Address - }, clientChannel); - } - else - Queue(new RT_MSG_SERVER_CONNECT_REQUIRE(), clientChannel); - } - #endregion - else if (scertClient.MediusVersion > 108 && scertClient.ApplicationID != 11484) - Queue(new RT_MSG_SERVER_CONNECT_REQUIRE(), clientChannel); - else - { - //Older Medius titles do NOT use CRYPTKEY_GAME, newer ones have this. - if (scertClient.CipherService != null && scertClient.CipherService.HasKey(CipherContext.RC_CLIENT_SESSION) && scertClient.MediusVersion >= 109) - Queue(new RT_MSG_SERVER_CRYPTKEY_GAME() { GameKey = scertClient.CipherService.GetPublicKey(CipherContext.RC_CLIENT_SESSION) }, clientChannel); - Queue(new RT_MSG_SERVER_CONNECT_ACCEPT_TCP() - { - PlayerId = 0, - ScertId = GenerateNewScertClientId(), - PlayerCount = 0x0001, - IP = (clientChannel.RemoteAddress as IPEndPoint)?.Address - }, clientChannel); - - if (pre108ServerComplete.Contains(data.ApplicationId) || post108ServerComplete.Contains(data.ApplicationId)) - Queue(new RT_MSG_SERVER_CONNECT_COMPLETE() { ClientCountAtConnect = 0x0001 }, clientChannel); - } - - if (MediusClass.Settings.HttpsSVOCheckPatcher) - { - switch (data.ApplicationId) - { - case 20371: - CheatQuery(0x10085d80, 6, clientChannel); // PS Home 1.50 Beta - break; - case 20384: - CheatQuery(0x008625b0, 6, clientChannel); // SingStar Vol3 Retail - CheatQuery(0x00b96850, 6, clientChannel); // SingStar Starter Pack - break; - case 21354: - CheatQuery(0x008625E0, 6, clientChannel); // SingStar Hits v1.00 - break; - case 21574: - CheatQuery(0x0070c068, 6, clientChannel); // Warhawk EU v1.50 - break; - case 21564: - CheatQuery(0x0070BFF8, 6, clientChannel); // Warhawk US v1.50 - break; - case 22924: - CheatQuery(0x00df0008, 6, clientChannel); // Starhawk v1.4 Retail - break; - } - } - - if (data.ApplicationId == 20371 || data.ApplicationId == 20374) - CheatQuery(0x00010000, 512000, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_SHA1_HASH, unchecked((int)0xDEADBEEF)); - - PokePatch(clientChannel, data); - - break; - } - - case RT_MSG_SERVER_CHEAT_QUERY clientCheatQuery: - { - byte[]? QueryData = clientCheatQuery.Data; - - if (QueryData != null) - { - LoggerAccessor.LogDebug($"[MAS] - QUERY CHECK - Client:{data.ClientObject?.IP} Has Data:{QueryData.ToHexString()} in offset: {clientCheatQuery.StartAddress}"); - - if (MediusClass.Settings.HttpsSVOCheckPatcher && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 6 - && QueryData.EqualsTo(new byte[] { 0x68, 0x74, 0x74, 0x70, 0x73, 0x3A })) - PatchHttpsSVOCheck(clientCheatQuery.StartAddress + 4, clientChannel); - - if (data.ApplicationId == 20371 || data.ApplicationId == 20374) - { - if (data.ClientObject?.ClientHomeData != null) - { - switch (data.ClientObject.ClientHomeData.Type) - { - case "HDK With Offline": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.86.09": - switch (clientCheatQuery.StartAddress) - { - case 0x005478dc: - // 4096 character command line limit. - if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) - { - PokeAddress(0x005478dc, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); - PokeAddress(0x00548378, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); - } - break; - case 0x1054e5c0: - // Sets WorldCorePointer. - if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) - data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); - break; - case 0x0016cc6c: - // Patches out the forceInvite command. - if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) - PokeAddress(0x0016cc6c, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); - break; - } - break; - default: - break; - } - break; - case "HDK Online Only": - switch (data.ClientObject.ClientHomeData.Version) - { - default: - break; - } - break; - case "HDK Online Only (Dbg Symbols)": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.82.09": - switch (clientCheatQuery.StartAddress) - { - case 0x00531370: - // 4096 character command line limit. - if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) - { - PokeAddress(0x00531370, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); - PokeAddress(0x00531e08, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); - } - break; - case 0x1053e160: - // Sets WorldCorePointer. - if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) - data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); - break; - case 0x0016b4d0: - // Patches out the forceInvite command. - if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) - PokeAddress(0x0016b4d0, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); - break; - } - break; - default: - break; - } - break; - case "Online Debug": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.83.12": - switch (clientCheatQuery.StartAddress) - { - case 0x00548bc0: - // 4096 character command line limit. - if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) - { - PokeAddress(0x00548bc0, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); - PokeAddress(0x0054964c, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); - } - break; - case 0x1054e1c0: - // Sets WorldCorePointer. - if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) - data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); - break; - case 0x001709e0: - // Patches out the forceInvite command. - if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) - PokeAddress(0x001709e0, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); - break; - } - break; - case "01.86.09": - switch (clientCheatQuery.StartAddress) - { - case 0x00555cb4: - // 4096 character command line limit. - if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) - { - PokeAddress(0x00555cb4, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); - PokeAddress(0x00556740, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); - } - break; - case 0x1054e358: - // Sets WorldCorePointer. - if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) - data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); - break; - case 0x0016dac0: - // Patches out the forceInvite command. - if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) - PokeAddress(0x0016dac0, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); - break; - } - break; - default: - break; - } - break; - case "Retail": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.86.09": - switch (clientCheatQuery.StartAddress) - { - case 0x006f59b8: - // Grant PS Plus for 1.86 retail. - if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x54, 0x63, 0xd9, 0x7e })) - { - byte[] liPatch = new byte[] { 0x38, 0x60, 0x00, 0x01 }; - PokeAddress(0x006f59b8, liPatch, clientChannel); - PokeAddress(0x0073bdb0, liPatch, clientChannel); - } - break; - case 0x002aa960: - // Disable SSFW Reward check for 1.86 retail. - if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x7c, 0x65, 0x1b, 0x78 })) - PokeAddress(0x002aa960, new byte[] { 0x48, 0x40, 0xe2, 0x2c }, clientChannel); - break; - case 0x105c24c8: - // Sets WorldCorePointer. - if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) - data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); - break; - } - break; - default: - break; - } - break; - } - } - - switch (clientCheatQuery.SequenceId) - { - case int.MinValue: - if (data.ClientObject != null) - data.ClientObject.SSFWid = Encoding.ASCII.GetString(clientCheatQuery.Data); - break; - case -559038737: - switch (clientCheatQuery.StartAddress) - { - case 65536: - if (data.ClientObject != null) - { - if (data.ClientObject.ClientHomeData == null) - data.ClientObject.ClientHomeData = MediusClass.HomeOffsetsList.Where(x => !string.IsNullOrEmpty(x.Sha1Hash) && x.Sha1Hash[..^8] - .Equals(clientCheatQuery.Data.ToHexString(), StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); - - if (data.ClientObject.ClientHomeData != null) - { - switch (data.ClientObject.ClientHomeData.Type) - { - case "HDK With Offline": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.86.09": - CheatQuery(0x10244430, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); - break; - default: - break; - } - break; - case "HDK Online Only": - switch (data.ClientObject.ClientHomeData.Version) - { - default: - break; - } - break; - case "HDK Online Only (Dbg Symbols)": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.82.09": - CheatQuery(0x10234440, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); - break; - default: - break; - } - break; - case "Online Debug": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.83.12": - CheatQuery(0x10244439, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); - break; - case "01.86.09": - CheatQuery(0x10244428, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); - break; - default: - break; - } - break; - case "Retail": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.86.09": - CheatQuery(0x101555f0, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); - break; - default: - break; - } - break; - } - } - else if (!MediusClass.Settings.PlaystationHomeAllowAnyEboot) - { - string anticheatMsg = $"[SECURITY] - HOME ANTI-CHEAT - DETECTED UNKNOWN EBOOT - User:{data.ClientObject.IP + ":" + data.ClientObject.AccountName} CID:{data.MachineId}"; - - _ = data.ClientObject.CurrentChannel?.BroadcastSystemMessage(data.ClientObject.CurrentChannel.LocalClients.Where(x => x != data.ClientObject), anticheatMsg, byte.MaxValue); - - LoggerAccessor.LogError(anticheatMsg); - - await HorizonServerConfiguration.Database.BanIp(data.ClientObject.IP).ContinueWith((r) => - { - if (r.IsCompletedSuccessfully && r.Result) - { - // Banned - QueueBanMessage(data); - } - data.ClientObject.ForceDisconnect(); - _ = data.ClientObject.Logout(); - }); - } - } - break; - } - break; - } - } - } - break; - } - - case RT_MSG_CLIENT_CONNECT_READY_REQUIRE clientConnectReadyRequire: - { - if (scertClient.CipherService != null && scertClient.CipherService.HasKey(CipherContext.RC_CLIENT_SESSION) && !scertClient.IsPS3Client) - Queue(new RT_MSG_SERVER_CRYPTKEY_GAME() { GameKey = scertClient.CipherService.GetPublicKey(CipherContext.RC_CLIENT_SESSION) }, clientChannel); - Queue(new RT_MSG_SERVER_CONNECT_ACCEPT_TCP() - { - PlayerId = 0, - ScertId = GenerateNewScertClientId(), - PlayerCount = 0x0001, - IP = (clientChannel.RemoteAddress as IPEndPoint)?.Address - }, clientChannel); - break; - } - - case RT_MSG_CLIENT_CONNECT_READY_TCP clientConnectReadyTcp: - { - Queue(new RT_MSG_SERVER_CONNECT_COMPLETE() { ClientCountAtConnect = 0x0001 }, clientChannel); - - if (scertClient.MediusVersion > 108) - Queue(new RT_MSG_SERVER_ECHO(), clientChannel); - break; - } - - #region Echos - case RT_MSG_SERVER_ECHO serverEchoReply: - { - - break; - } - case RT_MSG_CLIENT_ECHO clientEcho: - { - Queue(new RT_MSG_CLIENT_ECHO() { Value = clientEcho.Value }, clientChannel); - break; - } - #endregion - - case RT_MSG_CLIENT_APP_TOSERVER clientAppToServer: - { - if (clientAppToServer.Message != null) - await ProcessMediusMessage(clientAppToServer.Message, clientChannel, data); - break; - } - - #region Client Disconnect - case RT_MSG_CLIENT_DISCONNECT _: - { - //Medius 1.08 (Used on WRC 4) haven't a state - if (scertClient.MediusVersion > 108) - data.State = ClientState.DISCONNECTED; - - await clientChannel.CloseAsync(); - - LoggerAccessor.LogInfo($"[MAS] - Client disconnected by request with no specific reason\n"); - break; - } - case RT_MSG_CLIENT_DISCONNECT_WITH_REASON clientDisconnectWithReason: - { - if (clientDisconnectWithReason.Reason <= RT_MSG_CLIENT_DISCONNECT_REASON.RT_MSG_CLIENT_DISCONNECT_LENGTH_MISMATCH) - LoggerAccessor.LogInfo($"[MAS] - Disconnected by request with reason of {clientDisconnectWithReason.Reason}\n"); - else - LoggerAccessor.LogInfo($"[MAS] - Disconnected by request with (application specified) reason of {clientDisconnectWithReason.Reason}\n"); - - data.State = ClientState.DISCONNECTED; - await clientChannel.CloseAsync(); - break; - } - #endregion - - default: - { - LoggerAccessor.LogWarn($"UNHANDLED RT MESSAGE: {message}"); - break; - } - } - - return; - } - - protected virtual async Task ProcessMediusMessage(BaseMediusMessage message, IChannel clientChannel, ChannelData data) - { - var scertClient = clientChannel.GetAttribute(LIBRARY.Pipeline.Constants.SCERT_CLIENT).Get(); - if (message == null) - return; - - var appSettings = MediusClass.GetAppSettingsOrDefault(data.ApplicationId); - - switch (message) - { - #region MGCL - Dme - - case MediusServerSessionBeginRequest serverSessionBeginRequest: - { - List nonSecure = new() { 10010, 10031 }; - - if (data.ClientObject != null) - { - // MGCL_SEND_FAILED, MGCL_UNSUCCESSFUL - if (!data.ClientObject.IsConnected) - { - data.ClientObject.Queue(new MediusServerSessionBeginResponse() - { - MessageID = serverSessionBeginRequest.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_UNSUCCESSFUL - }); - } - else - { - IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); - - data.ClientObject.LocationId = serverSessionBeginRequest.LocationID; - data.ClientObject.ServerType = serverSessionBeginRequest.ServerType; - data.ClientObject.ServerVersion = serverSessionBeginRequest.ServerVersion; - data.ClientObject.Port = serverSessionBeginRequest.Port; - data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); - data.ClientObject.BeginServerSession(); - - if (nonSecure.Contains(data.ClientObject.ApplicationId)) - { - // TM:BO Reply unencrypted - data.ClientObject.Queue(new MediusServerSessionBeginResponse() - { - MessageID = serverSessionBeginRequest.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = new RSA_KEY(), - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, - new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerUDP - } - }); - } - else - { - // Default Reply - data.ClientObject.Queue(new MediusServerSessionBeginResponse() - { - MessageID = serverSessionBeginRequest.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = MediusClass.GlobalAuthPublic, - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, - new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerUDP - } - }); - } - - data.ClientObject.KeepAliveUntilNextConnection(); - } - } - break; - } - - case MediusServerSessionBeginRequest1 serverSessionBeginRequest1: - { - if (data.ClientObject != null) - { - IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); - - data.ClientObject.LocationId = serverSessionBeginRequest1.LocationID; - data.ClientObject.ServerType = serverSessionBeginRequest1.ServerType; - data.ClientObject.Port = serverSessionBeginRequest1.Port; - data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); - data.ClientObject.BeginServerSession(); - - LoggerAccessor.LogInfo($"[MAS] - Registered MGCL client for appid {data.ClientObject.ApplicationId} with access token {data.ClientObject.AccessToken}"); - - //Send NAT Service - data.ClientObject.Queue(new MediusServerSessionBeginResponse() - { - MessageID = serverSessionBeginRequest1.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = MediusClass.GlobalAuthPublic, - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, - new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerUDP - } - }); - } - - break; - } - - case MediusServerSessionBeginRequest2 serverSessionBeginRequest2: - { - if (data.ClientObject != null) - { - IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); - - data.ClientObject.LocationId = serverSessionBeginRequest2.LocationID; - data.ClientObject.ServerType = serverSessionBeginRequest2.ServerType; - data.ClientObject.Port = serverSessionBeginRequest2.Port; - data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); - data.ClientObject.BeginServerSession(); - - //Send NAT Service - data.ClientObject.Queue(new MediusServerSessionBeginResponse() - { - MessageID = serverSessionBeginRequest2.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = MediusClass.GlobalAuthPublic, - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, - new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerUDP - } - }); - } - - break; - } - - case MediusServerAuthenticationRequest mgclAuthRequest: - { - List nonSecure = new() { 10010, 10031, 10190 }; - - if (data.ClientObject != null) - { - data.ClientObject.MGCL_TRUST_LEVEL = mgclAuthRequest.TrustLevel; - data.ClientObject.NetConnectionType = NetConnectionType.NetConnectionTypeClientServerTCP; - - if (mgclAuthRequest.AddressList.AddressList[0].AddressType == NetAddressType.NetAddressTypeBinaryExternal) - data.ClientObject.SetIp(ConvertFromIntegerToIpAddress(mgclAuthRequest.AddressList.AddressList[0].BinaryAddress)); - else if (mgclAuthRequest.AddressList.AddressList[0].AddressType == NetAddressType.NetAddressTypeBinaryExternalVport - || mgclAuthRequest.AddressList.AddressList[0].AddressType == NetAddressType.NetAddressTypeBinaryInternalVport) - { - data.ClientObject.SetIp(mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitOne + "." + - mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitTwo + "." + - mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitThree + "." + - mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitFour); - } - else - // NetAddressTypeExternal - data.ClientObject.SetIp(mgclAuthRequest.AddressList.AddressList[0].Address ?? "0.0.0.0"); - - if (nonSecure.Contains(data.ClientObject.ApplicationId)) - { - data.ClientObject.Queue(new MediusServerAuthenticationResponse() - { - MessageID = mgclAuthRequest.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = new RSA_KEY(), - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, - new NetAddress() { AddressType = NetAddressType.NetAddressNone } - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerTCP - } - }); - - // Keep the client alive until the dme objects connects to MPS or times out - data.ClientObject.OnConnected(); - data.ClientObject.KeepAliveUntilNextConnection(); - } - else - { - data.ClientObject.Queue(new MediusServerAuthenticationResponse() - { - MessageID = mgclAuthRequest.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = MediusClass.GlobalAuthPublic, - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, - new NetAddress() { AddressType = NetAddressType.NetAddressNone }, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerTCP - } - }); - - // Keep the client alive until the dme objects connects to MPS or times out - data.ClientObject.OnConnected(); - data.ClientObject.KeepAliveUntilNextConnection(); - } - } - - break; - } - - case MediusServerSetAttributesRequest mgclSetAttrRequest: - { - ClientObject? dmeObject = data.ClientObject; - if (dmeObject == null) - { - LoggerAccessor.LogError($"[MAS] - Non-DME Client sending MGCL messages."); - break; - } - - dmeObject.MGCL_SERVER_ATTRIBUTES = mgclSetAttrRequest.Attributes; - dmeObject.SetIpPort(mgclSetAttrRequest.ListenServerAddress); - - // Reply with success - dmeObject.Queue(new MediusServerSetAttributesResponse() - { - MessageID = mgclSetAttrRequest.MessageID, - Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS - }); - break; - } - - case MediusServerSessionEndRequest sessionEndRequest: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} is trying to end server session without an Client Object"); - break; - } - - data.ClientObject.EndServerSession(); - - Queue(new RT_MSG_SERVER_APP() - { - Message = new MediusServerSessionEndResponse() - { - MessageID = sessionEndRequest.MessageID, - ErrorCode = MGCL_ERROR_CODE.MGCL_SUCCESS - } - }, clientChannel); - - break; - } - - case MediusServerReport serverReport: - { - data.ClientObject?.OnServerReport(serverReport); - break; - } - - #endregion - - #region Session - - case MediusExtendedSessionBeginRequest extendedSessionBeginRequest: - { - if (data.ClientObject != null) - { - data.ClientObject.MediusConnectionType = extendedSessionBeginRequest.ConnectionClass; - - await HorizonServerConfiguration.Database.GetServerFlags().ContinueWith((r) => - { - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.MaintenanceMode != null) - { - // Ensure that maintenance is active - // Ensure that we're past the from date - // Ensure that we're before the to date (if set) - if (r.Result.MaintenanceMode.IsActive - && DateTimeUtils.GetHighPrecisionUtcTime() > r.Result.MaintenanceMode.FromDt - && (!r.Result.MaintenanceMode.ToDt.HasValue - || r.Result.MaintenanceMode.ToDt > DateTimeUtils.GetHighPrecisionUtcTime())) - QueueBanMessage(data, "Server in maintenance mode."); - else - { - // Reply - data.ClientObject.Queue(new MediusSessionBeginResponse() - { - MessageID = extendedSessionBeginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - SessionKey = data.ClientObject.SessionKey - }); - } - } - }); - } - break; - } - case MediusSessionBeginRequest sessionBeginRequest: - { - if (data.ClientObject != null) - { - data.ClientObject.MediusConnectionType = sessionBeginRequest.ConnectionClass; - - LoggerAccessor.LogInfo($"Retrieved ApplicationID {data.ClientObject.ApplicationId} from client connection"); - - await HorizonServerConfiguration.Database.GetServerFlags().ContinueWith((r) => - { - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.MaintenanceMode != null) - { - #region Maintenance Mode? - // Ensure that maintenance is active - // Ensure that we're past the from date - // Ensure that we're before the to date (if set) - if (r.Result.MaintenanceMode.IsActive - && DateTimeUtils.GetHighPrecisionUtcTime() > r.Result.MaintenanceMode.FromDt - && (!r.Result.MaintenanceMode.ToDt.HasValue - || r.Result.MaintenanceMode.ToDt > DateTimeUtils.GetHighPrecisionUtcTime())) - QueueBanMessage(data, "Server in maintenance mode."); - #endregion - - #region Send Response - else - { - // Reply - data.ClientObject.Queue(new MediusSessionBeginResponse() - { - MessageID = sessionBeginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - SessionKey = data.ClientObject.SessionKey - }); - } - #endregion - } - }); - } - - break; - } - - case MediusSessionBeginRequest1 sessionBeginRequest1: - { - if (data.ClientObject != null) - { - data.ClientObject.MediusConnectionType = sessionBeginRequest1.ConnectionClass; - - LoggerAccessor.LogInfo($"Retrieved ApplicationID {data.ClientObject.ApplicationId} from client connection"); - - #region SystemMessageSingleTest Disabled? - if (MediusClass.Settings.SystemMessageSingleTest) - { - await QueueBanMessage(data, "MAS.Notification Test:\nYou have been banned from this server."); - - await data.ClientObject.Logout(); - } - #endregion - else - { - await HorizonServerConfiguration.Database.GetServerFlags().ContinueWith((r) => - { - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.MaintenanceMode != null) - { - #region Maintenance Mode? - // Ensure that maintenance is active - // Ensure that we're past the from date - // Ensure that we're before the to date (if set) - if (r.Result.MaintenanceMode.IsActive - && DateTimeUtils.GetHighPrecisionUtcTime() > r.Result.MaintenanceMode.FromDt - && (!r.Result.MaintenanceMode.ToDt.HasValue - || r.Result.MaintenanceMode.ToDt > DateTimeUtils.GetHighPrecisionUtcTime())) - QueueBanMessage(data, "Server in maintenance mode."); - - #endregion - - #region Send Response - else - { - // Reply - data.ClientObject.Queue(new MediusSessionBeginResponse() - { - MessageID = sessionBeginRequest1.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - SessionKey = data.ClientObject.SessionKey - }); - } - #endregion - } - }); - } - } - break; - } - - case MediusSessionEndRequest sessionEndRequest: - { - ClientObject? clientToEnd = MediusClass.Manager.GetClientBySessionKey(sessionEndRequest.SessionKey, data.ApplicationId); - - if (clientToEnd == null) - { - LoggerAccessor.LogError($"[MAS] - INVALID OPERATION: {clientChannel} is trying to end session of a non-existing Client Object with SessionKey: {sessionEndRequest.SessionKey}"); - break; - } - - clientToEnd.OnDisconnected(); - - Queue(new RT_MSG_SERVER_APP() - { - Message = new MediusSessionEndResponse() - { - MessageID = sessionEndRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - } - }, clientChannel); - - break; - } - - #endregion - - #region Localization - - case MediusSetLocalizationParamsRequest setLocalizationParamsRequest: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {setLocalizationParamsRequest} without a session."); - break; - } - - data.ClientObject.CharacterEncoding = setLocalizationParamsRequest.CharacterEncoding; - data.ClientObject.LanguageType = setLocalizationParamsRequest.Language; - - data.ClientObject.Queue(new MediusStatusResponse() - { - Type = 0xA4, - Class = setLocalizationParamsRequest.PacketClass, - MessageID = setLocalizationParamsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - break; - } - - case MediusSetLocalizationParamsRequest1 setLocalizationParamsRequest1: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {setLocalizationParamsRequest1} without a session."); - break; - } - - data.ClientObject.CharacterEncoding = setLocalizationParamsRequest1.CharacterEncoding; - data.ClientObject.LanguageType = setLocalizationParamsRequest1.Language; - data.ClientObject.TimeZone = setLocalizationParamsRequest1.TimeZone; - data.ClientObject.LocationId = setLocalizationParamsRequest1.LocationID; - - data.ClientObject.Queue(new MediusStatusResponse() - { - Type = 0xA4, - Class = (NetMessageClass)1, - MessageID = setLocalizationParamsRequest1.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - break; - } - case MediusSetLocalizationParamsRequest2 setLocalizationParamsRequest2: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {setLocalizationParamsRequest2} without a session."); - break; - } - - data.ClientObject.CharacterEncoding = setLocalizationParamsRequest2.CharacterEncoding; - data.ClientObject.LanguageType = setLocalizationParamsRequest2.Language; - data.ClientObject.TimeZone = setLocalizationParamsRequest2.TimeZone; - - data.ClientObject.Queue(new MediusStatusResponse() - { - Type = 0xA4, - Class = (NetMessageClass)1, - MessageID = setLocalizationParamsRequest2.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - break; - } - - #endregion - - #region Game - - case MediusGetTotalGamesRequest getTotalGamesRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalGamesRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalGamesRequest} without being logged in."); - break; - } - - data.ClientObject.Queue(new MediusGetTotalGamesResponse() - { - MessageID = getTotalGamesRequest.MessageID, - Total = 0, - StatusCode = MediusCallbackStatus.MediusRequestDenied - }); - break; - } - - #endregion - - #region Channel - - case MediusGetTotalChannelsRequest getTotalChannelsRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalChannelsRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalChannelsRequest} without being logged in."); - break; - } - - data.ClientObject.Queue(new MediusGetTotalChannelsResponse() - { - MessageID = getTotalChannelsRequest.MessageID, - Total = 0, - StatusCode = MediusCallbackStatus.MediusRequestDenied, - }); - break; - } - - case MediusSetLobbyWorldFilterRequest setLobbyWorldFilterRequest: - { - //WRC 4 Sets LobbyWorldFilter Prior to making a session. - // ERROR - Need a session - /* - if (data.ClientObject == null) - throw new InvalidOperationException($"INVALID OPERATION: {clientChannel} sent {setLobbyWorldFilterRequest} without a session."); - */ - // ERROR -- Need to be logged in - /* - if (!data.ClientObject.IsLoggedIn) - throw new InvalidOperationException($"INVALID OPERATION: {clientChannel} sent {setLobbyWorldFilterRequest} without being logged in."); - */ - /* - data.ClientObject.Queue(new MediusSetLobbyWorldFilterResponse() - { - MessageID = setLobbyWorldFilterRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusRequestDenied, - }); - */ - Queue(new RT_MSG_SERVER_APP() - { - Message = new MediusSetLobbyWorldFilterResponse() - { - MessageID = setLobbyWorldFilterRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusRequestDenied - } - }); - - break; - } - #endregion - - #region DNAS CID Check - - case MediusMachineSignaturePost machineSignaturePost: - { - if (Settings.DnasEnablePost == true) - { - //Sets the CachedPlayer's MachineId - data.MachineId = BitConverter.ToString(machineSignaturePost.MachineSignature); - - LoggerAccessor.LogInfo($"Session Key {machineSignaturePost.SessionKey} | Posting Machine signatures"); - - // Then post to the Database if logged in - if (data.ClientObject?.IsLoggedIn ?? false) - await HorizonServerConfiguration.Database.PostMachineId(data.ClientObject.AccountId, data.MachineId); - } - else - { - //DnasEnablePost set to false; - } - - break; - } - - case MediusDnasSignaturePost dnasSignaturePost: - { - if (Settings.DnasEnablePost == true) - { - //If DNAS Signature Post is the PS2/PSP/PS3 Console ID then continue - if (dnasSignaturePost.DnasSignatureType == MediusDnasCategory.DnasConsoleID) - { - data.MachineId = BitConverter.ToString(dnasSignaturePost.DnasSignature); - - LoggerAccessor.LogInfo($"Posting ConsoleID - ConsoleSigSize={dnasSignaturePost.DnasSignatureLength}"); - - // Then post to the Database if logged in - if (data.ClientObject?.IsLoggedIn ?? false) - await HorizonServerConfiguration.Database.PostMachineId(data.ClientObject.AccountId, data.MachineId); - } - - if (dnasSignaturePost.DnasSignatureType == MediusDnasCategory.DnasTitleID) - LoggerAccessor.LogInfo($"DnasSignaturePost Error - Invalid SignatureType"); - - if (dnasSignaturePost.DnasSignatureType == MediusDnasCategory.DnasDiskID) - LoggerAccessor.LogInfo($"Posting DiskID - DiskSigSize={dnasSignaturePost.DnasSignatureLength}"); - } - else - { - //DnasEnablePost false, no Post; - } - break; - } - #endregion - - #region AccessLevel (2.12) - - case MediusGetAccessLevelInfoRequest getAccessLevelInfoRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getAccessLevelInfoRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getAccessLevelInfoRequest} without being logged in."); - break; - } - - //int adminAccessLevel = 4; - - data.ClientObject.Queue(new MediusGetAccessLevelInfoResponse() - { - MessageID = getAccessLevelInfoRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccessLevel = MediusAccessLevelType.MEDIUS_ACCESSLEVEL_MODERATOR, - }); - break; - } - - #endregion - - #region Version Server - case MediusVersionServerRequest mediusVersionServerRequest: - { - List appIdBeforeSession = new() { 10442, 10724 }; - - // ERROR - Need a session - if (data.ClientObject == null && !appIdBeforeSession.Contains(data.ApplicationId)) // KILLZONE PS2 GET VERSION SERVER INFO BEFORE SESSION - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {mediusVersionServerRequest} without a session."); - break; - } - - if (Settings.MediusServerVersionOverride == true) - { - #region F1 2005 PAL - List F12005AppIds = new List { 10952, 10954 }; - // F1 2005 PAL SCES / F1 2005 PAL TCES - if (F12005AppIds.Contains(data.ApplicationId)) - { - data.ClientObject?.Queue(new MediusVersionServerResponse() - { - MessageID = mediusVersionServerRequest.MessageID, - VersionServer = "Medius Authentication Server Version 2.9.0009", - StatusCode = MediusCallbackStatus.MediusSuccess, - }); - } - #endregion - - #region Socom 1 - else if (data.ApplicationId == 10274) - { - data.ClientObject?.Queue(new MediusVersionServerResponse() - { - MessageID = mediusVersionServerRequest.MessageID, - VersionServer = "Medius Authentication Server Version 1.40.PRE8", - StatusCode = MediusCallbackStatus.MediusSuccess, - }); - } - #endregion - - #region EyeToy Chat Beta - else if (data.ApplicationId == 10550) - { - data.ClientObject?.Queue(new MediusVersionServerResponse() - { - MessageID = mediusVersionServerRequest.MessageID, - VersionServer = "Medius Authentication Server Version 1.43.0000", - StatusCode = MediusCallbackStatus.MediusSuccess, - }); - } - #endregion - - #region Killzone Beta/Retail - else if (appIdBeforeSession.Contains(data.ApplicationId)) - { - //data.ClientObject = MediusClass.LobbyServer.ReserveClient(mediusVersionServerRequest); - - data.SendQueue.Enqueue(new RT_MSG_SERVER_APP() - { - Message = new MediusVersionServerResponse() - { - MessageID = mediusVersionServerRequest.MessageID, - VersionServer = "Medius Authentication Server Version 1.50.0009", - StatusCode = MediusCallbackStatus.MediusSuccess, - } - }); - } - else - #endregion - - //Default - { - data.ClientObject?.Queue(new MediusVersionServerResponse() - { - MessageID = mediusVersionServerRequest.MessageID, - VersionServer = "Medius Authentication Server Version 3.05.201109161400", - StatusCode = MediusCallbackStatus.MediusSuccess, - }); - } - } - else - { - // If MediusServerVersionOverride is false, we send our own Version String - // AND if its Killzone PS2 we make the ClientObject BEFORE SESSIONBEGIN - if (appIdBeforeSession.Contains(data.ApplicationId)) - { - //data.ClientObject = Program.LobbyServer.ReserveClient(mediusVersionServerRequest); - data.SendQueue.Enqueue(new RT_MSG_SERVER_APP() - { - Message = new MediusVersionServerResponse() - { - MessageID = mediusVersionServerRequest.MessageID, - VersionServer = "Medius Authentication Server Version 1.50.0009", - StatusCode = MediusCallbackStatus.MediusSuccess, - } - }); - } - else - { - data.ClientObject?.Queue(new MediusVersionServerResponse() - { - MessageID = mediusVersionServerRequest.MessageID, - VersionServer = Settings.MASVersion, - StatusCode = MediusCallbackStatus.MediusSuccess, - }); - } - } - - break; - } - - #endregion - - #region Co-Locations - case MediusGetLocationsRequest getLocationsRequest: - { - // ERROR - Need a session but doesn't need to be logged in - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLocationsRequest} without a session."); - break; - } - - LoggerAccessor.LogInfo($"Get Locations Request Received Sessionkey: {getLocationsRequest.SessionKey}"); - await HorizonServerConfiguration.Database.GetLocations(data.ClientObject.ApplicationId).ContinueWith(r => - { - LocationDTO[]? locations = r.Result; - - if (r.IsCompletedSuccessfully) - { - if (locations?.Length == 0) - { - LoggerAccessor.LogInfo("No Locations found."); - - data.ClientObject.Queue(new MediusGetLocationsResponse() - { - MessageID = getLocationsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusNoResult, - EndOfList = true - }); - } - else - { - var responses = locations?.Select(x => new MediusGetLocationsResponse() - { - MessageID = getLocationsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - LocationId = x.Id, - LocationName = x.Name - }).ToList(); - - if (responses != null) - { - LoggerAccessor.LogInfo("GetLocationsRequest success"); - LoggerAccessor.LogInfo($"NumLocations returned[{responses.Count}]"); - - responses[responses.Count - 1].EndOfList = true; - data.ClientObject.Queue(responses); - } - } - } - else - { - LoggerAccessor.LogError($"GetLocationsRequest failed [{r.Exception}]"); - - data.ClientObject.Queue(new MediusGetLocationsResponse() - { - MessageID = getLocationsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError, - LocationId = -1, - LocationName = "0", - EndOfList = true - }); - } - }); - break; - } - - case MediusPickLocationRequest pickLocationRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {pickLocationRequest} without a session."); - break; - } - - data.ClientObject.LocationId = pickLocationRequest.LocationID; - - data.ClientObject.Queue(new MediusPickLocationResponse() - { - MessageID = pickLocationRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - break; - } - - #endregion - - #region Account - - case MediusAccountRegistrationRequest accountRegRequest: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountRegRequest} without a session."); - break; - } - - // Check that account creation is enabled - if (appSettings.DisableAccountCreation) - { - // Reply error - data.ClientObject.Queue(new MediusAccountRegistrationResponse() - { - MessageID = accountRegRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusFail - }); - - return; - } - - // validate name - if (!MediusClass.PassTextFilter(data.ApplicationId, TextFilterContext.ACCOUNT_NAME, accountRegRequest.AccountName)) - { - data.ClientObject.Queue(new MediusAccountRegistrationResponse() - { - MessageID = accountRegRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusFail, - }); - return; - } - - await HorizonServerConfiguration.Database.CreateAccount(new CreateAccountDTO() - { - AccountName = accountRegRequest.AccountName, - AccountPassword = ComputeSHA256(accountRegRequest.Password), - MachineId = data.MachineId, - MediusStats = Convert.ToBase64String(new byte[Constants.ACCOUNTSTATS_MAXLEN]), - AppId = data.ClientObject.ApplicationId - }, clientChannel).ContinueWith((r) => - { - if (r.IsCompletedSuccessfully && r.Result != null) - { - // Reply with account id - data.ClientObject.Queue(new MediusAccountRegistrationResponse() - { - MessageID = accountRegRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccountID = r.Result.AccountId - }); - } - else - { - // Reply error - data.ClientObject.Queue(new MediusAccountRegistrationResponse() - { - MessageID = accountRegRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusAccountAlreadyExists - }); - } - }); - break; - } - case MediusAccountGetIDRequest accountGetIdRequest: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountGetIdRequest} without a session."); - break; - } - - await HorizonServerConfiguration.Database.GetAccountByName(accountGetIdRequest.AccountName, data.ClientObject.ApplicationId).ContinueWith((r) => - { - if (r.IsCompletedSuccessfully && r.Result != null) - { - // Success - data?.ClientObject?.Queue(new MediusAccountGetIDResponse() - { - MessageID = accountGetIdRequest.MessageID, - AccountID = r.Result.AccountId, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - } - else - { - // Fail - data?.ClientObject?.Queue(new MediusAccountGetIDResponse() - { - MessageID = accountGetIdRequest.MessageID, - AccountID = -1, - StatusCode = MediusCallbackStatus.MediusAccountNotFound - }); - } - }); - - break; - } - case MediusAccountDeleteRequest accountDeleteRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountDeleteRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountDeleteRequest} without being logged in."); - break; - } - - if (!string.IsNullOrEmpty(data.ClientObject.AccountName)) - { - await HorizonServerConfiguration.Database.DeleteAccount(data.ClientObject.AccountName, data.ClientObject.ApplicationId).ContinueWith((r) => - { - if (r.IsCompletedSuccessfully && r.Result) - { - LoggerAccessor.LogInfo($"Logging out {data?.ClientObject?.AccountName}'s account\nDeleting from Medius Server"); - - data?.ClientObject?.Logout(); - - data?.ClientObject?.Queue(new MediusAccountDeleteResponse() - { - MessageID = accountDeleteRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - } - else - { - LoggerAccessor.LogWarn($"Logout FAILED for {data?.ClientObject?.AccountName}'s account\nData still persistent on Medius Server"); - - data?.ClientObject?.Queue(new MediusAccountDeleteResponse() - { - MessageID = accountDeleteRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError - }); - } - }); - } - break; - } - case MediusAnonymousLoginRequest anonymousLoginRequest: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {anonymousLoginRequest} without a session."); - break; - } - - await LoginAnonymous(anonymousLoginRequest, clientChannel, data); - break; - } - case MediusAccountLoginRequest accountLoginRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountLoginRequest} without a session."); - break; - } - - await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_ACCOUNT_LOGIN_REQUEST, new OnAccountLoginRequestArgs() - { - Player = data.ClientObject, - Request = accountLoginRequest - }); - - // Check the client isn't already logged in - if (MediusClass.Manager.GetClientByAccountName(accountLoginRequest.Username, data.ClientObject.ApplicationId)?.IsLoggedIn ?? false) - { - data.ClientObject.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusAccountLoggedIn - }); - } - else - { - #region SystemMessageSingleTest Disabled? - if (MediusClass.Settings.SystemMessageSingleTest != false) - { - await QueueBanMessage(data, "MAS.Notification Test:\nYou have been banned from this server."); - - await data.ClientObject.Logout(); - } - #endregion - else - { - _ = HorizonServerConfiguration.Database.GetAccountByName(accountLoginRequest.Username, data.ClientObject.ApplicationId, true).ContinueWith(async (r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null && data != null && data.ClientObject != null && data.ClientObject.IsConnected) - { - - if (r.Result.IsBanned) - { - // Send ban message - //await QueueBanMessage(data); - - // Account is banned - // Temporary solution is to tell the client the login failed - data?.ClientObject?.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusAccountBanned - }); - - } - else if (appSettings.EnableAccountWhitelist && !appSettings.AccountIdWhitelist.Contains(r.Result.AccountId)) - { - // Account not allowed to sign in - data?.ClientObject?.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusFail - }); - } - else if (MediusClass.Manager.GetClientByAccountName(accountLoginRequest.Username, data.ClientObject.ApplicationId)?.IsLoggedIn ?? false) - { - data.ClientObject.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusAccountLoggedIn - }); - } - - else if (ComputeSHA256(accountLoginRequest.Password) == r.Result.AccountPassword) - await Login(accountLoginRequest.MessageID, clientChannel, data, r.Result, false); - else - { - // Incorrect password - data?.ClientObject?.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusInvalidPassword - }); - } - } - else if (appSettings.CreateAccountOnNotFound) - { - // Account not found, create new and login - // Check that account creation is enabled - if (appSettings.DisableAccountCreation) - { - // Reply error - data?.ClientObject?.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusFail, - }); - return; - } - - // validate name - if (data != null && !MediusClass.PassTextFilter(data.ApplicationId, TextFilterContext.ACCOUNT_NAME, accountLoginRequest.Username)) - { - data.ClientObject?.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusVulgarityFound, - }); - return; - } - - await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PRE_ACCOUNT_CREATE_ON_NOT_FOUND, new OnAccountLoginRequestArgs() - { - Player = data?.ClientObject, - Request = accountLoginRequest - }); - - if (data != null && data.ClientObject != null) - { - _ = HorizonServerConfiguration.Database.CreateAccount(new CreateAccountDTO() - { - AccountName = accountLoginRequest.Username, - AccountPassword = ComputeSHA256(accountLoginRequest.Password), - MachineId = data.MachineId, - MediusStats = Convert.ToBase64String(new byte[Constants.ACCOUNTSTATS_MAXLEN]), - AppId = data.ClientObject.ApplicationId - }, clientChannel).ContinueWith(async (r) => - { - if (r.IsCompletedSuccessfully && r.Result != null) - { - await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_POST_ACCOUNT_CREATE_ON_NOT_FOUND, new OnAccountLoginRequestArgs() - { - Player = data.ClientObject, - Request = accountLoginRequest - }); - await Login(accountLoginRequest.MessageID, clientChannel, data, r.Result, false); - } - else - { - // Reply error - data.ClientObject.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusInvalidPassword - }); - } - }); - } - } - else - { - // Account not found - data?.ClientObject?.Queue(new MediusAccountLoginResponse() - { - MessageID = accountLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusAccountNotFound, - }); - } - }); - } - } - break; - } - - case MediusAccountUpdatePasswordRequest accountUpdatePasswordRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdatePasswordRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdatePasswordRequest} without being logged in."); - break; - } - - // Post New Password to Database - await HorizonServerConfiguration.Database.PostAccountUpdatePassword(data.ClientObject.AccountId, accountUpdatePasswordRequest.OldPassword, accountUpdatePasswordRequest.NewPassword).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result) - { - data.ClientObject.Queue(new MediusAccountUpdatePasswordStatusResponse() - { - MessageID = accountUpdatePasswordRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - } - else - { - data.ClientObject.Queue(new MediusAccountUpdatePasswordStatusResponse() - { - MessageID = accountUpdatePasswordRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError - }); - } - }); - break; - } - - case MediusAccountLogoutRequest accountLogoutRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountLogoutRequest} without a session."); - break; - } - - MediusCallbackStatus result = MediusCallbackStatus.MediusFail; - - // Check token - if (data.ClientObject.IsLoggedIn && accountLogoutRequest.SessionKey == data.ClientObject.SessionKey) - { - result = MediusCallbackStatus.MediusSuccess; - - // Logout - await data.ClientObject.Logout(); - } - - data.ClientObject.Queue(new MediusAccountLogoutResponse() - { - MessageID = accountLogoutRequest.MessageID, - StatusCode = result - }); - break; - } - - case MediusAccountUpdateStatsRequest accountUpdateStatsRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdateStatsRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdateStatsRequest} without being logged in."); - break; - } - - await HorizonServerConfiguration.Database.PostMediusStats(data.ClientObject.AccountId, Convert.ToBase64String(accountUpdateStatsRequest.Stats)).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result) - { - data.ClientObject.Queue(new MediusAccountUpdateStatsResponse() - { - MessageID = accountUpdateStatsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - } - else - { - data.ClientObject.Queue(new MediusAccountUpdateStatsResponse() - { - MessageID = accountUpdateStatsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError - }); - } - }); - break; - } - - case MediusTicketLoginRequest ticketLoginRequest: - { - // ERROR - Need a session and XI5 Ticket - if (data.ClientObject == null || ticketLoginRequest.TicketData == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {ticketLoginRequest} without a session or XI5 Ticket."); - break; - } - - string accountLoggingMsg = string.Empty; - byte[] XI5TicketData = ByteUtils.CombineByteArrays(BitConverter.GetBytes( - BitConverter.IsLittleEndian ? EndianUtils.ReverseUint(ticketLoginRequest.Version) : ticketLoginRequest.Version), BitConverter.GetBytes( - BitConverter.IsLittleEndian ? EndianUtils.ReverseUint(ticketLoginRequest.Size) : ticketLoginRequest.Size), ticketLoginRequest.TicketData); - - // Extract the desired portion of the binary data for a npticket 4.0 - byte[] extractedData = new byte[0x63 - 0x54 + 1]; - - // Copy it - Array.Copy(XI5TicketData, 0x54, extractedData, 0, extractedData.Length); - - // Trim null bytes - int nullByteIndex = Array.IndexOf(extractedData, (byte)0x00); - if (nullByteIndex >= 0) - { - byte[] trimmedData = new byte[nullByteIndex]; - Array.Copy(extractedData, trimmedData, nullByteIndex); - extractedData = trimmedData; - } - - string UserOnlineId = Encoding.UTF8.GetString(extractedData); - XI5Ticket ticker = new XI5Ticket(XI5TicketData); - - if (ByteUtils.FindBytePattern(XI5TicketData, new byte[] { 0x52, 0x50, 0x43, 0x4E }, 184) != -1) - { - if (MediusClass.Settings.ForceOfficialRPCNSignature && !ticker.SignedByOfficialRPCN) - { - LoggerAccessor.LogError($"[MAS] - MediusTicketLoginRequest : User {UserOnlineId} was caught using an invalid RPCN signature!"); - - // Account is banned - // Temporary solution is to tell the client the login failed - data.ClientObject.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusMachineBanned - }); - - break; - } - - accountLoggingMsg = $"[MAS] - MediusTicketLoginRequest : User {UserOnlineId} logged in and is on RPCN"; - data.ClientObject.IsOnRPCN = true; - UserOnlineId += "@RPCN"; - } - else if (UserOnlineId.EndsWith("@RPCN")) - { - LoggerAccessor.LogError($"[MAS] - MediusTicketLoginRequest : User {UserOnlineId} was caught using a RPCN suffix while not on it!"); - - // Account is banned - // Temporary solution is to tell the client the login failed - data.ClientObject.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusMachineBanned - }); - - break; - } - else - accountLoggingMsg = $"[MAS] - MediusTicketLoginRequest : User {UserOnlineId} logged in and is on PSN"; - - _ = data.ClientObject.CurrentChannel?.BroadcastSystemMessage(data.ClientObject.CurrentChannel.LocalClients.Where(client => client != data.ClientObject), accountLoggingMsg, byte.MinValue); - - LoggerAccessor.LogInfo(accountLoggingMsg); - - ClientObject? ExsitingClient = MediusClass.Manager.GetClientByAccountName(UserOnlineId, data.ClientObject.ApplicationId); - - // Check the client isn't already logged in - if (ExsitingClient != null && ExsitingClient.IsLoggedIn) - { - data.ClientObject.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusAccountLoggedIn - }); - - break; - } - - //Check if their MacBanned - await HorizonServerConfiguration.Database.GetIsMacBanned(data.MachineId ?? string.Empty).ContinueWith(async (r) => - { - if (r.IsCompletedSuccessfully && data != null && data.ClientObject != null && data.ClientObject.IsConnected) - { - data.IsBanned = r.IsCompletedSuccessfully && r.Result; - - #region isBanned? - LoggerAccessor.LogInfo($"Is Connected User MAC Banned: {data.IsBanned}"); - - if (data.IsBanned == true) - { - LoggerAccessor.LogError($"Account MachineID {data.MachineId} is BANNED!"); - - // Account is banned - // Temporary solution is to tell the client the login failed - data?.ClientObject?.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusMachineBanned - }); - - // Send ban message - //await QueueBanMessage(data); - } - else - { - await HorizonServerConfiguration.Database.GetAccountByName(UserOnlineId, data.ClientObject.ApplicationId, true).ContinueWith(async (r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null && data != null && data.ClientObject != null && data.ClientObject.IsConnected) - { - bool isHomeRejected = !MediusClass.Settings.PlaystationHomeAllowAnyEboot && (data.ClientObject.ApplicationId == 20371 || data.ClientObject.ApplicationId == 20374) && data.ClientObject.ClientHomeData == null; - - LoggerAccessor.LogInfo($"Account found for AppId from Client: {data.ClientObject.ApplicationId}"); - - if (r.Result.IsBanned || isHomeRejected) - { - // Account is banned - // Respond with Statuscode MediusAccountBanned - data?.ClientObject?.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusAccountBanned - }); - - // Then queue send ban message - await QueueBanMessage(data, isHomeRejected ? "You was caught using an anti Poke/Query eboot" : "Your CID has been banned"); - } - else - { - #region AccountWhitelist Check - if (appSettings.EnableAccountWhitelist && !appSettings.AccountIdWhitelist.Contains(r.Result.AccountId)) - { - LoggerAccessor.LogError($"AppId {data.ClientObject.ApplicationId} has EnableAccountWhitelist enabled or\n" + - $"Contains a AccountIdWhitelist!"); - - // Account not allowed to sign in - data?.ClientObject?.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusFail - }); - } - #endregion - - if (data != null) - await Login(ticketLoginRequest.MessageID, clientChannel, data, r.Result, true); - } - - } - else - { - // Account not found, create new and login - #region AccountCreationDisabled? - // Check that account creation is enabled - if (appSettings.DisableAccountCreation) - { - LoggerAccessor.LogError($"AppId {data?.ClientObject?.ApplicationId} has DisableAllowCreation enabled!"); - - // Reply error - data?.ClientObject?.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusFail, - }); - return; - } - #endregion - - if (data != null) - { - LoggerAccessor.LogInfo($"Account not found for AppId from Client: {data.ClientObject?.ApplicationId}"); - - if (data.ClientObject != null) - { - _ = HorizonServerConfiguration.Database.CreateAccount(new CreateAccountDTO() - { - AccountName = UserOnlineId, - AccountPassword = "UNSET", - MachineId = data.MachineId, - MediusStats = Convert.ToBase64String(new byte[Constants.ACCOUNTSTATS_MAXLEN]), - AppId = data.ClientObject.ApplicationId - }, clientChannel).ContinueWith(async (r) => - { - LoggerAccessor.LogInfo($"Creating New Account for user {UserOnlineId}!"); - - if (r.IsCompletedSuccessfully && r.Result != null) - await Login(ticketLoginRequest.MessageID, clientChannel, data, r.Result, true); - else - { - // Reply error - data.ClientObject.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusDBError - }); - } - }); - } - } - } - }); - } - #endregion - } - else - { - // Reply error - data?.ClientObject?.Queue(new MediusTicketLoginResponse() - { - MessageID = ticketLoginRequest.MessageID, - StatusCodeTicketLogin = MediusCallbackStatus.MediusDBError, - }); - } - }); - break; - } - - #endregion - - #region Policy / Announcements - - case MediusGetAllAnnouncementsRequest getAllAnnouncementsRequest: - { - // Send to plugins - await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PLAYER_ON_GET_ALL_ANNOUNCEMENTS, new OnPlayerRequestArgs() - { - Player = data.ClientObject, - Request = getAllAnnouncementsRequest - }); - - await HorizonServerConfiguration.Database.GetLatestAnnouncementsList(data.ApplicationId).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.Length > 0) - { - List responses = new List(); - foreach (var result in r.Result) - { - responses.Add(new MediusGetAnnouncementsResponse() - { - MessageID = getAllAnnouncementsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Announcement = string.IsNullOrEmpty(result.AnnouncementTitle) ? $"{result.AnnouncementBody}" : $"{result.AnnouncementTitle}\n{result.AnnouncementBody}\n", - AnnouncementID = result.Id++, - EndOfList = false - }); - } - - responses[responses.Count - 1].EndOfList = true; - data.ClientObject.Queue(responses); - } - else - { - data.ClientObject.Queue(new MediusGetAnnouncementsResponse() - { - MessageID = getAllAnnouncementsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Announcement = "", - AnnouncementID = 0, - EndOfList = true - }); - } - }); - break; - } - - case MediusGetAnnouncementsRequest getAnnouncementsRequest: - { - // Send to plugins - await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PLAYER_ON_GET_ANNOUNCEMENTS, new OnPlayerRequestArgs() - { - Player = data.ClientObject, - Request = getAnnouncementsRequest - }); - - await HorizonServerConfiguration.Database.GetLatestAnnouncement(data.ApplicationId).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null) - { - data.ClientObject.Queue(new MediusGetAnnouncementsResponse() - { - MessageID = getAnnouncementsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Announcement = string.IsNullOrEmpty(r.Result.AnnouncementTitle) ? $"{r.Result.AnnouncementBody}" : $"{r.Result.AnnouncementTitle}\n{r.Result.AnnouncementBody}\n", - AnnouncementID = r.Result.Id++, - EndOfList = true - }); - } - else - { - data.ClientObject.Queue(new MediusGetAnnouncementsResponse() - { - MessageID = getAnnouncementsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Announcement = "", - AnnouncementID = 0, - EndOfList = true - }); - } - }); - break; - } - - case MediusGetPolicyRequest getPolicyRequest: - { - // Send to plugins - await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PLAYER_ON_GET_POLICY, new OnPlayerRequestArgs() - { - Player = data.ClientObject, - Request = getPolicyRequest - }); - - switch (getPolicyRequest.Policy) - { - case MediusPolicyType.Privacy: - { - if (data.ClientObject != null) - { - await HorizonServerConfiguration.Database.GetPolicy((int)MediusPolicyType.Privacy, data.ClientObject.ApplicationId).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null) - { - string? txt = r.Result.EulaBody; - if (!string.IsNullOrEmpty(r.Result.EulaTitle)) - txt = r.Result.EulaTitle + "\n" + txt; - else - txt = string.Empty; - LoggerAccessor.LogInfo($"GetPolicy Succeeded:{getPolicyRequest.MessageID}"); - data.ClientObject.Queue(MediusClass.GetPolicyFromText(getPolicyRequest.MessageID, txt)); - } - else if (r.IsCompletedSuccessfully && r.Result == null) - { - LoggerAccessor.LogDebug($"Sending blank Policy since no chunks were found"); - data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = string.Empty, EndOfText = true }); - } - else - { - LoggerAccessor.LogError($"GetPolicy Failed = [{r.Exception}]"); - data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = "NONE", EndOfText = true }); - } - }); - } - break; - } - case MediusPolicyType.Usage: - { - if (data.ClientObject != null) - { - await HorizonServerConfiguration.Database.GetPolicy((int)MediusPolicyType.Usage, data.ClientObject.ApplicationId).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null) - { - string? txt = r.Result.EulaBody; - if (!string.IsNullOrEmpty(r.Result.EulaTitle)) - txt = r.Result.EulaTitle + "\n" + txt; - else - txt = string.Empty; - LoggerAccessor.LogInfo($"GetPolicy Succeeded:{getPolicyRequest.MessageID}"); - data.ClientObject.Queue(MediusClass.GetPolicyFromText(getPolicyRequest.MessageID, txt)); - } - else if (r.IsCompletedSuccessfully && r.Result == null) - { - LoggerAccessor.LogDebug($"Sending blank Policy since no chunks were found"); - data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = string.Empty, EndOfText = true }); - } - else - { - LoggerAccessor.LogError($"GetPolicy Failed = [{r.Exception}]"); - data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = "NONE", EndOfText = true }); - } - }); - } - break; - } - } - break; - } - - #endregion - - #region Ladders - - case MediusGetLadderStatsRequest getLadderStatsRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsRequest} without being logged in."); - break; - } - - switch (getLadderStatsRequest.LadderType) - { - case MediusLadderType.MediusLadderTypePlayer: - { - await HorizonServerConfiguration.Database.GetAccountById(getLadderStatsRequest.AccountID_or_ClanID).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.AccountStats != null) - { - data.ClientObject.Queue(new MediusGetLadderStatsResponse() - { - MessageID = getLadderStatsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Stats = Array.ConvertAll(r.Result.AccountStats, Convert.ToInt32) - }); - } - else - { - data.ClientObject.Queue(new MediusGetLadderStatsResponse() - { - MessageID = getLadderStatsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError - }); - } - }); - break; - } - case MediusLadderType.MediusLadderTypeClan: - { - await HorizonServerConfiguration.Database.GetClanById(getLadderStatsRequest.AccountID_or_ClanID, - data.ClientObject.ApplicationId) - .ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.ClanStats != null) - { - data.ClientObject.Queue(new MediusGetLadderStatsResponse() - { - MessageID = getLadderStatsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Stats = r.Result.ClanStats - }); - } - else - { - data.ClientObject.Queue(new MediusGetLadderStatsResponse() - { - MessageID = getLadderStatsRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError - }); - } - }); - break; - } - default: - { - LoggerAccessor.LogWarn($"Unhandled MediusGetLadderStatsRequest {getLadderStatsRequest}"); - break; - } - } - break; - } - - case MediusGetLadderStatsWideRequest getLadderStatsWideRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsWideRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsWideRequest} without being logged in."); - break; - } - - switch (getLadderStatsWideRequest.LadderType) - { - case MediusLadderType.MediusLadderTypePlayer: - { - await HorizonServerConfiguration.Database.GetAccountById(getLadderStatsWideRequest.AccountID_or_ClanID).ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.AccountWideStats != null) - { - data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() - { - MessageID = getLadderStatsWideRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccountID_or_ClanID = r.Result.AccountId, - Stats = r.Result.AccountWideStats - }); - } - else - { - data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() - { - MessageID = getLadderStatsWideRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError - }); - } - }); - break; - } - case MediusLadderType.MediusLadderTypeClan: - { - await HorizonServerConfiguration.Database.GetClanById(getLadderStatsWideRequest.AccountID_or_ClanID, - data.ClientObject.ApplicationId) - .ContinueWith((r) => - { - if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) - return; - - if (r.IsCompletedSuccessfully && r.Result != null && r.Result.ClanWideStats != null) - { - data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() - { - MessageID = getLadderStatsWideRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccountID_or_ClanID = r.Result.ClanId, - Stats = r.Result.ClanWideStats - }); - } - else - { - data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() - { - MessageID = getLadderStatsWideRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusDBError - }); - } - }); - break; - } - default: - { - LoggerAccessor.LogWarn($"Unhandled MediusGetLadderStatsWideRequest {getLadderStatsWideRequest}"); - break; - } - } - break; - } - - #endregion - - #region Channels - - case MediusChannelListRequest channelListRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {channelListRequest} without a session."); - break; - } - - // ERROR -- Need to be logged in - if (!data.ClientObject.IsLoggedIn) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {channelListRequest} without being logged in."); - break; - } - - List channelResponses = new List(); - - var lobbyChannels = MediusClass.Manager.GetChannelList( - data.ClientObject.ApplicationId, - channelListRequest.PageID, - channelListRequest.PageSize, - ChannelType.Lobby - ); - - foreach (var channel in lobbyChannels) - { - channelResponses.Add(new MediusChannelListResponse() - { - MessageID = channelListRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - MediusWorldID = channel.Id, - LobbyName = channel.Name, - PlayerCount = channel.PlayerCount, - EndOfList = false - }); - } - - if (channelResponses.Count == 0) - { - // Return none - data.ClientObject.Queue(new MediusChannelListResponse() - { - MessageID = channelListRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusNoResult, - EndOfList = true - }); - } - else - { - // Ensure the end of list flag is set - channelResponses[channelResponses.Count - 1].EndOfList = true; - - // Add to responses - data.ClientObject.Queue(channelResponses); - } - - - break; - } - - #endregion - - #region Deadlocked No-op Messages (MAS) - - case MediusGetBuddyList_ExtraInfoRequest getBuddyList_ExtraInfoRequest: - { - Queue(new RT_MSG_SERVER_APP() - { - Message = new MediusGetBuddyList_ExtraInfoResponse() - { - MessageID = getBuddyList_ExtraInfoRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusNoResult, - EndOfList = true - } - }, clientChannel); - break; - } - - case MediusGetIgnoreListRequest getIgnoreListRequest: - { - Queue(new RT_MSG_SERVER_APP() - { - Message = new MediusGetIgnoreListResponse() - { - MessageID = getIgnoreListRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusNoResult, - EndOfList = true - } - }, clientChannel); - break; - } - - case MediusGetMyClansRequest getMyClansRequest: - { - Queue(new RT_MSG_SERVER_APP() - { - Message = new MediusGetMyClansResponse() - { - MessageID = getMyClansRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusNoResult, - EndOfList = true - } - }, clientChannel); - break; - } - - #endregion - - #region TextFilter - - case MediusTextFilterRequest textFilterRequest: - { - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {textFilterRequest} without a session."); - break; - } - - // Deny special characters - // Also trim any whitespace - switch (textFilterRequest.TextFilterType) - { - case MediusTextFilterType.MediusTextFilterPassFail: - { - // validate name - if (!MediusClass.PassTextFilter(data.ApplicationId, Config.TextFilterContext.ACCOUNT_NAME, textFilterRequest.Text)) - { - // Failed due to special characters - data.ClientObject.Queue(new MediusTextFilterResponse() - { - MessageID = textFilterRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusFail - }); - return; - } - else - { - data.ClientObject.Queue(new MediusTextFilterResponse() - { - MessageID = textFilterRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusPass, - Text = textFilterRequest.Text.Trim() - }); - } - break; - } - case MediusTextFilterType.MediusTextFilterReplace: - { - data.ClientObject.Queue(new MediusTextFilterResponse() - { - MessageID = textFilterRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusPass, - Text = MediusClass.FilterTextFilter(data.ApplicationId, TextFilterContext.ACCOUNT_NAME, textFilterRequest.Text).Trim() - }); - break; - } - } - break; - } - - #endregion - - #region Time - case MediusGetServerTimeRequest getServerTimeRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getServerTimeRequest} without a session."); - break; - } - - //Doesn't need to be logged in to get ServerTime - - var time = DateTime.Now; - - await GetTimeZone(time).ContinueWith((r) => - { - if (r.IsCompletedSuccessfully) - { - //Fetched - data.ClientObject.Queue(new MediusGetServerTimeResponse() - { - MessageID = getServerTimeRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Local_server_timezone = r.Result, - }); - } - else - { - //default - data.ClientObject.Queue(new MediusGetServerTimeResponse() - { - MessageID = getServerTimeRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - Local_server_timezone = MediusTimeZone.MediusTimeZone_GMT, - }); - } - }); - break; - } - #endregion - - #region GetMyIP - //Syphon Filter - The Omega Strain Beta - - case MediusGetMyIPRequest getMyIpRequest: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getMyIpRequest} without a session."); - break; - } - - IPAddress? ClientIP = (clientChannel.RemoteAddress as IPEndPoint)?.Address; - - if (ClientIP == null) - { - LoggerAccessor.LogInfo($"Error: Retrieving Client IP address {clientChannel.RemoteAddress} = [{ClientIP}]"); - data.ClientObject.Queue(new MediusGetMyIPResponse() - { - MessageID = getMyIpRequest.MessageID, - IP = null, - StatusCode = MediusCallbackStatus.MediusDMEError - }); - } - else - { - data.ClientObject.Queue(new MediusGetMyIPResponse() - { - MessageID = getMyIpRequest.MessageID, - IP = ClientIP, - StatusCode = MediusCallbackStatus.MediusSuccess - }); - } - - break; - } - - #endregion - - #region UpdateUserState - case MediusUpdateUserState updateUserState: - { - // ERROR - Need a session - if (data.ClientObject == null) - { - LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {updateUserState} without a session."); - break; - } - - // ERROR - Needs to be logged in --Doesn't need to be logged in on older clients - - switch (updateUserState.UserAction) - { - case MediusUserAction.KeepAlive: - { - data.ClientObject.KeepAliveUntilNextConnection(); - break; - } - case MediusUserAction.JoinedChatWorld: - { - LoggerAccessor.LogInfo($"[MAS] - Successfully Joined ChatWorld [{data.ClientObject.CurrentChannel?.Id}] {data.ClientObject.AccountId}:{data.ClientObject.AccountName}"); - break; - } - case MediusUserAction.LeftGameWorld: - { - LoggerAccessor.LogInfo($"[MAS] - Successfully Left GameWorld {data.ClientObject.AccountId}:{data.ClientObject.AccountName}"); - MediusClass.AntiCheatPlugin.mc_anticheat_event_msg_UPDATEUSERSTATE(AnticheatEventCode.anticheatLEAVEGAME, data.ClientObject.MediusWorldID, data.ClientObject.AccountId, MediusClass.AntiCheatClient, updateUserState, 256); - break; - } - case MediusUserAction.LeftPartyWorld: - { - LoggerAccessor.LogInfo($"[MAS] - Successfully Left PartyWorld {data.ClientObject.AccountId}:{data.ClientObject.AccountName}"); - break; - } - default: - { - LoggerAccessor.LogWarn($"[MAS] - Requested a non-existant UserState {data.ClientObject.AccountId}:{data.ClientObject.AccountName}, please report to GITHUB."); - break; - } - } - - break; - } - - #endregion - - default: - { - LoggerAccessor.LogWarn($"Unhandled Medius Message: {message}"); - break; - } - } - } - - #region Login - private async Task Login(MessageId messageId, IChannel clientChannel, ChannelData data, AccountDTO accountDto, bool ticket) - { - var fac = new PS2CipherFactory(); - var rsa = fac.CreateNew(CipherContext.RSA_AUTH) as PS2_RSA; - IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); - - List pre108Secure = new() { 10010, 10031, 10190, 10124, 10680, 10681, 10683, 10684 }; - - if (data.ClientObject!.IP == IPAddress.Any) - data.ClientObject!.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); - - if ((data.ApplicationId == 20371 || data.ApplicationId == 20374) && data.ClientObject.ClientHomeData != null) - { - if (data.ClientObject.IsOnRPCN && data.ClientObject.ClientHomeData.VersionAsDouble >= 01.83) - { - if (!string.IsNullOrEmpty(data.ClientObject.ClientHomeData.Type) && (data.ClientObject.ClientHomeData.Type.Contains("HDK") || data.ClientObject.ClientHomeData.Type == "Online Debug")) - _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "lc Debug.System( 'mlaaenable 0' )"); - else - _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "mlaaenable 0"); - } - /*else if (data.ClientObject.ClientHomeData.VersionAsDouble >= 01.83) // MSAA PS3 Only for now: https://github.com/RPCS3/rpcs3/issues/15719 - { - if (!string.IsNullOrEmpty(data.ClientObject.ClientHomeData?.Type) && (data.ClientObject.ClientHomeData.Type.Contains("HDK") || data.ClientObject.ClientHomeData.Type == "Online Debug")) - _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "lc Debug.System( 'msaaenable 1' )"); - else - _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "msaaenable 1"); - }*/ - - switch (data.ClientObject.ClientHomeData.Type) - { - case "HDK With Offline": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.86.09": - if (MediusClass.Settings.PokePatchOn) - { - CheatQuery(0x005478dc, 4, clientChannel); - - CheatQuery(0x0016cc6c, 4, clientChannel); - } - - CheatQuery(0x1054e5c0, 4, clientChannel); - break; - default: - break; - } - break; - case "HDK Online Only": - switch (data.ClientObject.ClientHomeData.Version) - { - default: - break; - } - break; - case "HDK Online Only (Dbg Symbols)": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.82.09": - if (MediusClass.Settings.PokePatchOn) - { - CheatQuery(0x00531370, 4, clientChannel); - - CheatQuery(0x0016b4d0, 4, clientChannel); - } - - CheatQuery(0x1053e160, 4, clientChannel); - break; - default: - break; - } - break; - case "Online Debug": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.83.12": - if (MediusClass.Settings.PokePatchOn) - { - CheatQuery(0x00548bc0, 4, clientChannel); - - CheatQuery(0x001709e0, 4, clientChannel); - } - - CheatQuery(0x1054e1c0, 4, clientChannel); - break; - case "01.86.09": - if (MediusClass.Settings.PokePatchOn) - { - CheatQuery(0x00555cb4, 4, clientChannel); - - CheatQuery(0x0016dac0, 4, clientChannel); - } - - CheatQuery(0x1054e358, 4, clientChannel); - break; - default: - break; - } - break; - case "Retail": - switch (data.ClientObject.ClientHomeData.Version) - { - case "01.86.09": - if (MediusClass.Settings.PokePatchOn) - { - CheatQuery(0x006f59b8, 4, clientChannel); - CheatQuery(0x002aa960, 4, clientChannel); - } - - CheatQuery(0x105c24c8, 4, clientChannel); - break; - default: - break; - } - break; - } - } - - await data.ClientObject.Login(accountDto); - - #region Update DB IP and CID - await HorizonServerConfiguration.Database.PostAccountIp(accountDto.AccountId, ((IPEndPoint)clientChannel.RemoteAddress).Address.MapToIPv4().ToString()); - - CIDManager.CreateCIDPair(data.ClientObject.AccountName, data.MachineId); - - if (!string.IsNullOrEmpty(data.MachineId)) - await HorizonServerConfiguration.Database.PostMachineId(data.ClientObject.AccountId, data.MachineId); - #endregion - - // Add to logged in clients - MediusClass.Manager.AddOrUpdateLoggedInClient(data.ClientObject); - - LoggerAccessor.LogInfo($"LOGGING IN AS {data.ClientObject.AccountName} with access token {data.ClientObject.AccessToken}"); - - // Tell client - if (ticket) - { - #region IF PS3 Client - data.ClientObject.Queue(new MediusTicketLoginResponse() - { - //TicketLoginResponse - MessageID = messageId, - StatusCodeTicketLogin = MediusCallbackStatus.MediusSuccess, - PasswordType = MediusPasswordType.MediusPasswordNotSet, - - //AccountLoginResponse Wrapped - MessageID2 = messageId, - StatusCodeAccountLogin = MediusCallbackStatus.MediusSuccess, - AccountID = data.ClientObject.AccountId, - AccountType = MediusAccountType.MediusMasterAccount, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = new RSA_KEY(), //MediusStarter.GlobalAuthPublic, - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() {Address = !string.IsNullOrEmpty(MediusClass.Settings.NpMLSIpOverride) ? MediusClass.Settings.NpMLSIpOverride : MediusClass.LobbyServer.IPAddress.ToString(), Port = (MediusClass.Settings.NpMLSPortOverride != -1) ? MediusClass.Settings.NpMLSPortOverride : MediusClass.LobbyServer.TCPPort , AddressType = NetAddressType.NetAddressTypeExternal}, - new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerTCP - }, - }); - #endregion - - // Prepare for transition to lobby server - data.ClientObject.KeepAliveUntilNextConnection(); - } - else - { - #region If PS2/PSP - - if (data.ClientObject.MediusVersion > 108) - { - data.ClientObject.Queue(new MediusAccountLoginResponse() - { - MessageID = messageId, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccountID = data.ClientObject.AccountId, - AccountType = MediusAccountType.MediusMasterAccount, - WorldID = data.ClientObject.CurrentChannel!.Id, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = MediusClass.GlobalAuthPublic, - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, - new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerTCP - }, - }); - } - else if (pre108Secure.Contains(data.ClientObject.ApplicationId)) //10683 / 10684 - { - data.ClientObject.Queue(new MediusAccountLoginResponse() - { - MessageID = messageId, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccountID = data.ClientObject.AccountId, - AccountType = MediusAccountType.MediusMasterAccount, - WorldID = data.ClientObject.CurrentChannel!.Id, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = new RSA_KEY(), - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, - new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerTCP - }, - }); - } - else - { - data.ClientObject.Queue(new MediusAccountLoginResponse() - { - MessageID = messageId, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccountID = data.ClientObject.AccountId, - AccountType = MediusAccountType.MediusMasterAccount, - WorldID = data.ClientObject.CurrentChannel!.Id, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = MediusClass.GlobalAuthPublic, //Some Older Medius games don't set a RSA Key - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, - new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerTCP - }, - }); - } - - // Prepare for transition to lobby server - data.ClientObject.KeepAliveUntilNextConnection(); - #endregion - } - } - #endregion - - #region AnonymousLogin - private async Task LoginAnonymous(MediusAnonymousLoginRequest anonymousLoginRequest, IChannel clientChannel, ChannelData data) - { - IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); - PS2CipherFactory fac = new(); - var rsa = fac.CreateNew(CipherContext.RSA_AUTH) as PS2_RSA; - - int iAccountID = MediusClass.Manager.AnonymousAccountIDGenerator(MediusClass.Settings.AnonymousIDRangeSeed); - LoggerAccessor.LogInfo($"AnonymousIDRangeSeedGenerator AccountID returned {iAccountID}"); - - if (data.ClientObject != null) - { - char[] charsToRemove = { ':', 'f', '{', '}' }; - if (data.ClientObject.IP == IPAddress.Any) - data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(charsToRemove)); - } - - if (data.ClientObject != null) - { - CIDManager.CreateCIDPair("AnonymousClient", data.MachineId); - - // Login - await data.ClientObject.LoginAnonymous(anonymousLoginRequest, iAccountID); - - #region Update DB IP and CID - - //We don't post to the database as anonymous... This ain't it chief - - #endregion - - // Add to logged in clients - MediusClass.Manager.AddOrUpdateLoggedInClient(data.ClientObject); - - LoggerAccessor.LogInfo($"LOGGING IN ANONYMOUSLY AS {data.ClientObject.AccountDisplayName} with access token {data.ClientObject.AccessToken}"); - - // Tell client - data.ClientObject.Queue(new MediusAnonymousLoginResponse() - { - MessageID = anonymousLoginRequest.MessageID, - StatusCode = MediusCallbackStatus.MediusSuccess, - AccountID = iAccountID, - AccountType = MediusAccountType.MediusMasterAccount, - WorldID = data.ClientObject.CurrentChannel!.Id, - ConnectInfo = new NetConnectionInfo() - { - AccessKey = data.ClientObject.AccessToken, - SessionKey = data.ClientObject.SessionKey, - TargetWorldID = data.ClientObject.CurrentChannel!.Id, - ServerKey = new RSA_KEY(), // Null for 108 clients - AddressList = new NetAddressList() - { - AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] - { - new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, - new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, - } - }, - Type = NetConnectionType.NetConnectionTypeClientServerTCP - } - }); - - data.ClientObject.KeepAliveUntilNextConnection(); - } - } - #endregion - - #region TimeZone - public Task GetTimeZone(DateTime time) - { - var tz = TimeZoneInfo.Local; - var tzStanName = tz.StandardName; - - if (tzStanName == "CEST") - return Task.FromResult(MediusTimeZone.MediusTimeZone_CEST); - else if (tz.Id == "Swedish Standard Time") - return Task.FromResult(MediusTimeZone.MediusTimeZone_SWEDISHST); - else if (tz.Id == "FST") - return Task.FromResult(MediusTimeZone.MediusTimeZone_FST); - else if (tz.Id == "Central Africa Time") - return Task.FromResult(MediusTimeZone.MediusTimeZone_CAT); - else if (tzStanName == "South Africa Standard Time") - return Task.FromResult(MediusTimeZone.MediusTimeZone_SAST); - else if (tz.Id == "EET") - return Task.FromResult(MediusTimeZone.MediusTimeZone_EET); - else if (tz.Id == "Israel Standard Time") - return Task.FromResult(MediusTimeZone.MediusTimeZone_ISRAELST); - - return Task.FromResult(MediusTimeZone.MediusTimeZone_GMT); - } - #endregion - - #region ConvertFromIntegerToIpAddress - /// - /// Convert from Binary Ip Address to UInt - /// - /// Binary formatted IP Address - /// - public static string ConvertFromIntegerToIpAddress(uint ipAddress) - { - byte[] bytes = BitConverter.GetBytes(ipAddress); - - if (!BitConverter.IsLittleEndian) - Array.Reverse(bytes); - - return new IPAddress(bytes).ToString(); - } - #endregion - - #region ConvertFromIntegerToPort - /// - /// Convert from Binary Ip Address to UInt - /// - /// Binary formatted IP Address - /// - public static int ConvertFromIntegerToPort(string port) - { - int i = Convert.ToInt32(port, 2); - - // flip little-endian to big-endian(network order) - /* NOT NEEDED - if (BitConverter.IsLittleEndian) - { - Array.Reverse(bytes); - } - */ - return i; - } - #endregion - - #region ConvertFromIntegerToIpAddress - /// - /// Convert from Binary Ip Address to UInt - /// - /// Binary formatted IP Address - /// - public static uint ConvertFromIpAddressToBinary(IPAddress ipAddress) - { - uint Uint = (uint)BitConverter.ToInt32(ipAddress.GetAddressBytes()); - - // flip little-endian to big-endian(network order) - /* NOT NEEDED - if (BitConverter.IsLittleEndian) - { - Array.Reverse(bytes); - } - */ - return Uint; - } - #endregion - - #region PokeEngine - - private void PokePatch(IChannel clientChannel, ChannelData data) - { - if (MediusClass.Settings.PokePatchOn) - { - if (File.Exists(Directory.GetCurrentDirectory() + $"/static/poke_config.json")) - { - try - { - JObject? jsonObject = JObject.Parse(File.ReadAllText(Directory.GetCurrentDirectory() + $"/static/poke_config.json")); - - foreach (JProperty? appProperty in jsonObject.Properties()) - { - string? appId = appProperty.Name; - - if (!string.IsNullOrEmpty(appId) && appId == data.ApplicationId.ToString()) - { - if (appProperty.Value is JObject innerObject) - { - foreach (JProperty? offsetProperty in innerObject.Properties()) - { - string? offset = offsetProperty.Name; - string? valuestr = offsetProperty.Value.ToString(); - - if (!string.IsNullOrEmpty(offset) && !string.IsNullOrEmpty(valuestr) && uint.TryParse(offset.Replace("0x", string.Empty), NumberStyles.HexNumber, null, out uint offsetValue) && uint.TryParse(valuestr, NumberStyles.Any, null, out uint hexValue)) - { - LoggerAccessor.LogInfo($"[MAS] - MemoryPoke sent to appid {appId} with infos : offset:{offset} - value:{valuestr}"); - Queue(new RT_MSG_SERVER_MEMORY_POKE() - { - start_Address = offsetValue, - Payload = BitConverter.GetBytes(hexValue), - SkipEncryption = true - - }, clientChannel); - } - else - LoggerAccessor.LogWarn($"[MAS] - MemoryPoke failed to convert json properties! Check your Json syntax."); - } - } - } - } - } - catch (Exception ex) - { - LoggerAccessor.LogWarn($"[MAS] - MemoryPoke failed to initialise! {ex}."); - } - } - else - LoggerAccessor.LogWarn($"[MAS] - No MemoryPoke config found."); - } - } - - private bool CheatQuery(uint address, int Length, IChannel? clientChannel, CheatQueryType Type = CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int SequenceId = 1) - { - // address = 0, don't read - if (address == 0) - return false; - - // client channel is null, don't read - if (clientChannel == null) - return false; - - // read client memory - Queue(new RT_MSG_SERVER_CHEAT_QUERY() - { - QueryType = Type, - SequenceId = SequenceId, - StartAddress = address, - Length = Length, - }, clientChannel); - - // return read - return true; - } - - private bool PatchHttpsSVOCheck(uint patchLocation, IChannel? clientChannel) - { - // patch location = 0, don't patch - if (patchLocation == 0) - return false; - - // client channel is null, don't patch - if (clientChannel == null) - return false; - - // poke client memory - Queue(new RT_MSG_SERVER_MEMORY_POKE() - { - start_Address = patchLocation, - Payload = new byte[] { 0x3A, 0x2F }, // We patch to :/ instead of s: as the check only compare first 6 characters. - SkipEncryption = false, - }, clientChannel); - - // return patched - return true; - } - - private bool PokeAddress(uint patchLocation, byte[] Payload, IChannel? clientChannel) - { - // patch location = 0, don't patch - if (patchLocation == 0) - return false; - - // client channel is null, don't patch - if (clientChannel == null) - return false; - - // poke client memory - Queue(new RT_MSG_SERVER_MEMORY_POKE() - { - start_Address = patchLocation, - Payload = Payload, - SkipEncryption = false, - }, clientChannel); - - // return patched - return true; - } - #endregion - - #region SHA256 - - /// - /// Compute the SHA256 checksum of a string. - /// Calcul la somme des contr�les en SHA256 d'un string. - /// - /// The input string. - /// A string. - private static string ComputeSHA256(string input) - { - // ComputeHash - returns byte array - byte[] bytes = NetHasher.DotNetHasher.ComputeSHA256(Encoding.UTF8.GetBytes(input)); - - // Convert byte array to a string - StringBuilder builder = new(); - for (int i = 0; i < bytes.Length; i++) - builder.Append(bytes[i].ToString("x2")); - - return builder.ToString(); - } - #endregion - } -} +using CustomLogger; +using DotNetty.Transport.Channels; +using EndianTools; +using Horizon.HTTPSERVICE; +using Horizon.LIBRARY.Common; +using Horizon.LIBRARY.Database.Models; +using Horizon.MUM.Models; +using Horizon.PluginManager; +using Horizon.RT.Common; +using Horizon.RT.Cryptography; +using Horizon.RT.Cryptography.RSA; +using Horizon.RT.Models; +using Horizon.SERVER.Config; +using Horizon.SERVER.Extension.PlayStationHome; +using Horizon.SERVER.PluginArgs; +using NetworkLibrary.Extension; +using Newtonsoft.Json.Linq; +using System.Buffers; +using System.Globalization; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; +using XI5; + +namespace Horizon.SERVER.Medius +{ + public class MAS : BaseMediusComponent + { + public override int TCPPort => MediusClass.Settings.MASPort; + public override int UDPPort => 00000; + + public static ServerSettings Settings = new(); + + public MAS() + { + + } + + public static void ReserveClient(ClientObject client) + { + MediusClass.Manager.AddClient(client); + } + + protected override async Task ProcessMessage(BaseScertMessage message, IChannel clientChannel, ChannelData data) + { + // Get ScertClient data + var scertClient = clientChannel.GetAttribute(LIBRARY.Pipeline.Constants.SCERT_CLIENT).Get(); + var enableEncryption = MediusClass.GetAppSettingsOrDefault(data.ApplicationId).EnableEncryption; + if (scertClient.CipherService != null) + scertClient.CipherService.EnableEncryption = enableEncryption; + + switch (message) + { + case RT_MSG_CLIENT_HELLO clientHello: + { + // send hello + Queue(new RT_MSG_SERVER_HELLO() { RsaPublicKey = enableEncryption ? MediusClass.Settings.DefaultKey.N : Org.BouncyCastle.Math.BigInteger.Zero }, clientChannel); + break; + } + case RT_MSG_CLIENT_CRYPTKEY_PUBLIC clientCryptKeyPublic: + { + if (clientCryptKeyPublic.PublicKey != null) + { + // generate new client session key + scertClient.CipherService?.GenerateCipher(CipherContext.RSA_AUTH, clientCryptKeyPublic.PublicKey.Reverse().ToArray()); + scertClient.CipherService?.GenerateCipher(CipherContext.RC_CLIENT_SESSION); + + Queue(new RT_MSG_SERVER_CRYPTKEY_PEER() { SessionKey = scertClient.CipherService?.GetPublicKey(CipherContext.RC_CLIENT_SESSION) }, clientChannel); + } + break; + } + case RT_MSG_CLIENT_CONNECT_TCP clientConnectTcp: + { + #region Check if AppId from Client matches Server + if (!MediusClass.Manager.IsAppIdSupported(clientConnectTcp.AppId)) + { + LoggerAccessor.LogError($"Client {clientChannel.RemoteAddress} attempting to authenticate with incompatible app id {clientConnectTcp.AppId}"); + await clientChannel.CloseAsync(); + return; + } + #endregion + + List pre108ServerComplete = new() { 10114, 10130, 10164, 10190, 10124, 10284, 10330, 10334, 10414, 10421, 10442, 10538, 10540, 10550, 10582, 10584, 10680, 10681, 10683, 10684, 10724 }; + + /// + /// Some do not post-108 so we have to account for those too! + /// tmheadon 10694 does not for initial + /// + List post108ServerComplete = new() { 10694 }; + + data.ApplicationId = clientConnectTcp.AppId; + scertClient.ApplicationID = clientConnectTcp.AppId; + + Channel? targetChannel = MediusClass.Manager.GetChannelByChannelId(clientConnectTcp.TargetWorldId, data.ApplicationId); + + if (targetChannel == null) + { + Channel DefaultChannel = MediusClass.Manager.GetOrCreateDefaultLobbyChannel(data.ApplicationId, scertClient.MediusVersion ?? 0); + + if (DefaultChannel.Id == clientConnectTcp.TargetWorldId) + targetChannel = DefaultChannel; + + if (targetChannel == null) + { + LoggerAccessor.LogError($"[MAS] - Client: {clientConnectTcp.AccessToken} tried to join, but targetted WorldId:{clientConnectTcp.TargetWorldId} doesn't exist!"); + await clientChannel.CloseAsync(); + break; + } + } + + // If booth are null, it means MAS client wants a new object. + if (!string.IsNullOrEmpty(clientConnectTcp.AccessToken) && !string.IsNullOrEmpty(clientConnectTcp.SessionKey)) + { + data.ClientObject = MediusClass.Manager.GetClientByAccessToken(clientConnectTcp.AccessToken, clientConnectTcp.AppId); + if (data.ClientObject == null) + data.ClientObject = MediusClass.Manager.GetClientBySessionKey(clientConnectTcp.SessionKey, clientConnectTcp.AppId); + + if (data.ClientObject != null) + LoggerAccessor.LogInfo($"[MAS] - Client Connected {clientChannel.RemoteAddress}!"); + else + { + data.Ignore = true; + LoggerAccessor.LogError($"[MAS] - ClientObject could not be granted for {clientChannel.RemoteAddress}: {clientConnectTcp}"); + break; + } + + data.ClientObject.MediusVersion = scertClient.MediusVersion ?? 0; + data.ClientObject.ApplicationId = clientConnectTcp.AppId; + data.ClientObject.OnConnected(); + } + else + { + LoggerAccessor.LogInfo($"[MAS] - Client Connected {clientChannel.RemoteAddress} with new ClientObject!"); + + data.ClientObject = new(scertClient.MediusVersion ?? 0) + { + ApplicationId = clientConnectTcp.AppId + }; + data.ClientObject.OnConnected(); + + ReserveClient(data.ClientObject); // ONLY RESERVE CLIENTS HERE! + } + + await data.ClientObject.JoinChannel(targetChannel); + + #region if PS3 + if (scertClient.IsPS3Client) + { + List ConnectAcceptTCPGames = new() { 20623, 20624, 21564, 21574, 21584, 21594, 22274, 22284, 22294, 22304, 20040, 20041, 20042, 20043, 20044 }; + + //CAC & Warhawk + if (ConnectAcceptTCPGames.Contains(scertClient.ApplicationID)) + { + Queue(new RT_MSG_SERVER_CONNECT_ACCEPT_TCP() + { + PlayerId = 0, + ScertId = GenerateNewScertClientId(), + PlayerCount = 0x0001, + IP = (clientChannel.RemoteAddress as IPEndPoint)?.Address + }, clientChannel); + } + else + Queue(new RT_MSG_SERVER_CONNECT_REQUIRE(), clientChannel); + } + #endregion + else if (scertClient.MediusVersion > 108 && scertClient.ApplicationID != 11484) + Queue(new RT_MSG_SERVER_CONNECT_REQUIRE(), clientChannel); + else + { + //Older Medius titles do NOT use CRYPTKEY_GAME, newer ones have this. + if (scertClient.CipherService != null && scertClient.CipherService.HasKey(CipherContext.RC_CLIENT_SESSION) && scertClient.MediusVersion >= 109) + Queue(new RT_MSG_SERVER_CRYPTKEY_GAME() { GameKey = scertClient.CipherService.GetPublicKey(CipherContext.RC_CLIENT_SESSION) }, clientChannel); + Queue(new RT_MSG_SERVER_CONNECT_ACCEPT_TCP() + { + PlayerId = 0, + ScertId = GenerateNewScertClientId(), + PlayerCount = 0x0001, + IP = (clientChannel.RemoteAddress as IPEndPoint)?.Address + }, clientChannel); + + if (pre108ServerComplete.Contains(data.ApplicationId) || post108ServerComplete.Contains(data.ApplicationId)) + Queue(new RT_MSG_SERVER_CONNECT_COMPLETE() { ClientCountAtConnect = 0x0001 }, clientChannel); + } + + if (MediusClass.Settings.HttpsSVOCheckPatcher) + { + switch (data.ApplicationId) + { + case 20371: + CheatQuery(0x10085d80, 6, clientChannel); // PS Home 1.50 Beta + break; + case 20384: + CheatQuery(0x008625b0, 6, clientChannel); // SingStar Vol3 Retail + CheatQuery(0x00b96850, 6, clientChannel); // SingStar Starter Pack + break; + case 21354: + CheatQuery(0x008625E0, 6, clientChannel); // SingStar Hits v1.00 + break; + case 21574: + CheatQuery(0x0070c068, 6, clientChannel); // Warhawk EU v1.50 + break; + case 21564: + CheatQuery(0x0070BFF8, 6, clientChannel); // Warhawk US v1.50 + break; + case 22924: + CheatQuery(0x00df0008, 6, clientChannel); // Starhawk v1.4 Retail + break; + } + } + + if (data.ApplicationId == 20371 || data.ApplicationId == 20374) + CheatQuery(0x00010000, 512000, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_SHA1_HASH, unchecked((int)0xDEADBEEF)); + + PokePatch(clientChannel, data); + + break; + } + + case RT_MSG_SERVER_CHEAT_QUERY clientCheatQuery: + { + byte[]? QueryData = clientCheatQuery.Data; + + if (QueryData != null) + { + LoggerAccessor.LogDebug($"[MAS] - QUERY CHECK - Client:{data.ClientObject?.IP} Has Data:{QueryData.ToHexString()} in offset: {clientCheatQuery.StartAddress}"); + + if (MediusClass.Settings.HttpsSVOCheckPatcher && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 6 + && QueryData.EqualsTo(new byte[] { 0x68, 0x74, 0x74, 0x70, 0x73, 0x3A })) + PatchHttpsSVOCheck(clientCheatQuery.StartAddress + 4, clientChannel); + + if (data.ApplicationId == 20371 || data.ApplicationId == 20374) + { + if (data.ClientObject?.ClientHomeData != null) + { + switch (data.ClientObject.ClientHomeData.Type) + { + case "HDK With Offline": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.86.09": + switch (clientCheatQuery.StartAddress) + { + case 0x005478dc: + // 4096 character command line limit. + if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) + { + PokeAddress(0x005478dc, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); + PokeAddress(0x00548378, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); + } + break; + case 0x1054e5c0: + // Sets WorldCorePointer. + if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) + data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); + break; + case 0x0016cc6c: + // Patches out the forceInvite command. + if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) + PokeAddress(0x0016cc6c, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); + break; + } + break; + default: + break; + } + break; + case "HDK Online Only": + switch (data.ClientObject.ClientHomeData.Version) + { + default: + break; + } + break; + case "HDK Online Only (Dbg Symbols)": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.82.09": + switch (clientCheatQuery.StartAddress) + { + case 0x00531370: + // 4096 character command line limit. + if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) + { + PokeAddress(0x00531370, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); + PokeAddress(0x00531e08, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); + } + break; + case 0x1053e160: + // Sets WorldCorePointer. + if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) + data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); + break; + case 0x0016b4d0: + // Patches out the forceInvite command. + if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) + PokeAddress(0x0016b4d0, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); + break; + } + break; + default: + break; + } + break; + case "Online Debug": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.83.12": + switch (clientCheatQuery.StartAddress) + { + case 0x00548bc0: + // 4096 character command line limit. + if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) + { + PokeAddress(0x00548bc0, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); + PokeAddress(0x0054964c, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); + } + break; + case 0x1054e1c0: + // Sets WorldCorePointer. + if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) + data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); + break; + case 0x001709e0: + // Patches out the forceInvite command. + if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) + PokeAddress(0x001709e0, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); + break; + } + break; + case "01.86.09": + switch (clientCheatQuery.StartAddress) + { + case 0x00555cb4: + // 4096 character command line limit. + if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x83, 0x00, 0xfe })) + { + PokeAddress(0x00555cb4, new byte[] { 0x2f, 0x83, 0x0f, 0xff }, clientChannel); + PokeAddress(0x00556740, new byte[] { 0x2b, 0x83, 0x0f, 0xff }, clientChannel); + } + break; + case 0x1054e358: + // Sets WorldCorePointer. + if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) + data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); + break; + case 0x0016dac0: + // Patches out the forceInvite command. + if (MediusClass.Settings.PlaystationHomeForceInviteExploitPatch && MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x2f, 0x80, 0x00, 0x00 })) + PokeAddress(0x0016dac0, new byte[] { 0x2f, 0x80, 0x00, 0x02 }, clientChannel); + break; + } + break; + default: + break; + } + break; + case "Retail": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.86.09": + switch (clientCheatQuery.StartAddress) + { + case 0x006f59b8: + // Grant PS Plus for 1.86 retail. + if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x54, 0x63, 0xd9, 0x7e })) + { + byte[] liPatch = new byte[] { 0x38, 0x60, 0x00, 0x01 }; + PokeAddress(0x006f59b8, liPatch, clientChannel); + PokeAddress(0x0073bdb0, liPatch, clientChannel); + } + break; + case 0x002aa960: + // Disable SSFW Reward check for 1.86 retail. + if (MediusClass.Settings.PokePatchOn && clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4 && QueryData.EqualsTo(new byte[] { 0x7c, 0x65, 0x1b, 0x78 })) + PokeAddress(0x002aa960, new byte[] { 0x48, 0x40, 0xe2, 0x2c }, clientChannel); + break; + case 0x105c24c8: + // Sets WorldCorePointer. + if (clientCheatQuery.QueryType == CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY && QueryData.Length == 4) + data.ClientObject.SetWorldCorePointer(BitConverter.ToUInt32(BitConverter.IsLittleEndian ? EndianUtils.ReverseArray(QueryData) : QueryData)); + break; + } + break; + default: + break; + } + break; + } + } + + switch (clientCheatQuery.SequenceId) + { + case int.MinValue: + if (data.ClientObject != null) + data.ClientObject.SSFWid = Encoding.ASCII.GetString(clientCheatQuery.Data); + break; + case -559038737: + switch (clientCheatQuery.StartAddress) + { + case 65536: + if (data.ClientObject != null) + { + if (data.ClientObject.ClientHomeData == null) + data.ClientObject.ClientHomeData = MediusClass.HomeOffsetsList.Where(x => !string.IsNullOrEmpty(x.Sha1Hash) && x.Sha1Hash[..^8] + .Equals(clientCheatQuery.Data.ToHexString(), StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault(); + + if (data.ClientObject.ClientHomeData != null) + { + switch (data.ClientObject.ClientHomeData.Type) + { + case "HDK With Offline": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.86.09": + CheatQuery(0x10244430, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); + break; + default: + break; + } + break; + case "HDK Online Only": + switch (data.ClientObject.ClientHomeData.Version) + { + default: + break; + } + break; + case "HDK Online Only (Dbg Symbols)": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.82.09": + CheatQuery(0x10234440, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); + break; + default: + break; + } + break; + case "Online Debug": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.83.12": + CheatQuery(0x10244439, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); + break; + case "01.86.09": + CheatQuery(0x10244428, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); + break; + default: + break; + } + break; + case "Retail": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.86.09": + CheatQuery(0x101555f0, 36, clientChannel, CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int.MinValue); + break; + default: + break; + } + break; + } + } + else if (!MediusClass.Settings.PlaystationHomeAllowAnyEboot) + { + string anticheatMsg = $"[SECURITY] - HOME ANTI-CHEAT - DETECTED UNKNOWN EBOOT - User:{data.ClientObject.IP + ":" + data.ClientObject.AccountName} CID:{data.MachineId}"; + + _ = data.ClientObject.CurrentChannel?.BroadcastSystemMessage(data.ClientObject.CurrentChannel.LocalClients.Where(x => x != data.ClientObject), anticheatMsg, byte.MaxValue); + + LoggerAccessor.LogError(anticheatMsg); + + await HorizonServerConfiguration.Database.BanIp(data.ClientObject.IP).ContinueWith((r) => + { + if (r.IsCompletedSuccessfully && r.Result) + { + // Banned + QueueBanMessage(data); + } + data.ClientObject.ForceDisconnect(); + _ = data.ClientObject.Logout(); + }); + } + } + break; + } + break; + } + } + } + break; + } + + case RT_MSG_CLIENT_CONNECT_READY_REQUIRE clientConnectReadyRequire: + { + if (scertClient.CipherService != null && scertClient.CipherService.HasKey(CipherContext.RC_CLIENT_SESSION) && !scertClient.IsPS3Client) + Queue(new RT_MSG_SERVER_CRYPTKEY_GAME() { GameKey = scertClient.CipherService.GetPublicKey(CipherContext.RC_CLIENT_SESSION) }, clientChannel); + Queue(new RT_MSG_SERVER_CONNECT_ACCEPT_TCP() + { + PlayerId = 0, + ScertId = GenerateNewScertClientId(), + PlayerCount = 0x0001, + IP = (clientChannel.RemoteAddress as IPEndPoint)?.Address + }, clientChannel); + break; + } + + case RT_MSG_CLIENT_CONNECT_READY_TCP clientConnectReadyTcp: + { + Queue(new RT_MSG_SERVER_CONNECT_COMPLETE() { ClientCountAtConnect = 0x0001 }, clientChannel); + + if (scertClient.MediusVersion > 108) + Queue(new RT_MSG_SERVER_ECHO(), clientChannel); + break; + } + + #region Echos + case RT_MSG_SERVER_ECHO serverEchoReply: + { + + break; + } + case RT_MSG_CLIENT_ECHO clientEcho: + { + Queue(new RT_MSG_CLIENT_ECHO() { Value = clientEcho.Value }, clientChannel); + break; + } + #endregion + + case RT_MSG_CLIENT_APP_TOSERVER clientAppToServer: + { + if (clientAppToServer.Message != null) + await ProcessMediusMessage(clientAppToServer.Message, clientChannel, data); + break; + } + + #region Client Disconnect + case RT_MSG_CLIENT_DISCONNECT _: + { + //Medius 1.08 (Used on WRC 4) haven't a state + if (scertClient.MediusVersion > 108) + data.State = ClientState.DISCONNECTED; + + await clientChannel.CloseAsync(); + + LoggerAccessor.LogInfo($"[MAS] - Client disconnected by request with no specific reason\n"); + break; + } + case RT_MSG_CLIENT_DISCONNECT_WITH_REASON clientDisconnectWithReason: + { + if (clientDisconnectWithReason.Reason <= RT_MSG_CLIENT_DISCONNECT_REASON.RT_MSG_CLIENT_DISCONNECT_LENGTH_MISMATCH) + LoggerAccessor.LogInfo($"[MAS] - Disconnected by request with reason of {clientDisconnectWithReason.Reason}\n"); + else + LoggerAccessor.LogInfo($"[MAS] - Disconnected by request with (application specified) reason of {clientDisconnectWithReason.Reason}\n"); + + data.State = ClientState.DISCONNECTED; + await clientChannel.CloseAsync(); + break; + } + #endregion + + default: + { + LoggerAccessor.LogWarn($"UNHANDLED RT MESSAGE: {message}"); + break; + } + } + + return; + } + + protected virtual async Task ProcessMediusMessage(BaseMediusMessage message, IChannel clientChannel, ChannelData data) + { + var scertClient = clientChannel.GetAttribute(LIBRARY.Pipeline.Constants.SCERT_CLIENT).Get(); + if (message == null) + return; + + var appSettings = MediusClass.GetAppSettingsOrDefault(data.ApplicationId); + + switch (message) + { + #region MGCL - Dme + + case MediusServerSessionBeginRequest serverSessionBeginRequest: + { + List nonSecure = new() { 10010, 10031 }; + + if (data.ClientObject != null) + { + // MGCL_SEND_FAILED, MGCL_UNSUCCESSFUL + if (!data.ClientObject.IsConnected) + { + data.ClientObject.Queue(new MediusServerSessionBeginResponse() + { + MessageID = serverSessionBeginRequest.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_UNSUCCESSFUL + }); + } + else + { + IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); + + data.ClientObject.LocationId = serverSessionBeginRequest.LocationID; + data.ClientObject.ServerType = serverSessionBeginRequest.ServerType; + data.ClientObject.ServerVersion = serverSessionBeginRequest.ServerVersion; + data.ClientObject.Port = serverSessionBeginRequest.Port; + data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); + data.ClientObject.BeginServerSession(); + + if (nonSecure.Contains(data.ClientObject.ApplicationId)) + { + // TM:BO Reply unencrypted + data.ClientObject.Queue(new MediusServerSessionBeginResponse() + { + MessageID = serverSessionBeginRequest.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = new RSA_KEY(), + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, + new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerUDP + } + }); + } + else + { + // Default Reply + data.ClientObject.Queue(new MediusServerSessionBeginResponse() + { + MessageID = serverSessionBeginRequest.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = MediusClass.GlobalAuthPublic, + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, + new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerUDP + } + }); + } + + data.ClientObject.KeepAliveUntilNextConnection(); + } + } + break; + } + + case MediusServerSessionBeginRequest1 serverSessionBeginRequest1: + { + if (data.ClientObject != null) + { + IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); + + data.ClientObject.LocationId = serverSessionBeginRequest1.LocationID; + data.ClientObject.ServerType = serverSessionBeginRequest1.ServerType; + data.ClientObject.Port = serverSessionBeginRequest1.Port; + data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); + data.ClientObject.BeginServerSession(); + + LoggerAccessor.LogInfo($"[MAS] - Registered MGCL client for appid {data.ClientObject.ApplicationId} with access token {data.ClientObject.AccessToken}"); + + //Send NAT Service + data.ClientObject.Queue(new MediusServerSessionBeginResponse() + { + MessageID = serverSessionBeginRequest1.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = MediusClass.GlobalAuthPublic, + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, + new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerUDP + } + }); + } + + break; + } + + case MediusServerSessionBeginRequest2 serverSessionBeginRequest2: + { + if (data.ClientObject != null) + { + IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); + + data.ClientObject.LocationId = serverSessionBeginRequest2.LocationID; + data.ClientObject.ServerType = serverSessionBeginRequest2.ServerType; + data.ClientObject.Port = serverSessionBeginRequest2.Port; + data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); + data.ClientObject.BeginServerSession(); + + //Send NAT Service + data.ClientObject.Queue(new MediusServerSessionBeginResponse() + { + MessageID = serverSessionBeginRequest2.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = MediusClass.GlobalAuthPublic, + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() { Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService }, + new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerUDP + } + }); + } + + break; + } + + case MediusServerAuthenticationRequest mgclAuthRequest: + { + List nonSecure = new() { 10010, 10031, 10190 }; + + if (data.ClientObject != null) + { + data.ClientObject.MGCL_TRUST_LEVEL = mgclAuthRequest.TrustLevel; + data.ClientObject.NetConnectionType = NetConnectionType.NetConnectionTypeClientServerTCP; + + if (mgclAuthRequest.AddressList.AddressList[0].AddressType == NetAddressType.NetAddressTypeBinaryExternal) + data.ClientObject.SetIp(ConvertFromIntegerToIpAddress(mgclAuthRequest.AddressList.AddressList[0].BinaryAddress)); + else if (mgclAuthRequest.AddressList.AddressList[0].AddressType == NetAddressType.NetAddressTypeBinaryExternalVport + || mgclAuthRequest.AddressList.AddressList[0].AddressType == NetAddressType.NetAddressTypeBinaryInternalVport) + { + data.ClientObject.SetIp(mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitOne + "." + + mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitTwo + "." + + mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitThree + "." + + mgclAuthRequest.AddressList.AddressList[0].IPBinaryBitFour); + } + else + // NetAddressTypeExternal + data.ClientObject.SetIp(mgclAuthRequest.AddressList.AddressList[0].Address ?? "0.0.0.0"); + + if (nonSecure.Contains(data.ClientObject.ApplicationId)) + { + data.ClientObject.Queue(new MediusServerAuthenticationResponse() + { + MessageID = mgclAuthRequest.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = new RSA_KEY(), + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, + new NetAddress() { AddressType = NetAddressType.NetAddressNone } + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerTCP + } + }); + + // Keep the client alive until the dme objects connects to MPS or times out + data.ClientObject.OnConnected(); + data.ClientObject.KeepAliveUntilNextConnection(); + } + else + { + data.ClientObject.Queue(new MediusServerAuthenticationResponse() + { + MessageID = mgclAuthRequest.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = MediusClass.GlobalAuthPublic, + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() { Address = MediusClass.ProxyServer.IPAddress.ToString(), Port = MediusClass.ProxyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal }, + new NetAddress() { AddressType = NetAddressType.NetAddressNone }, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerTCP + } + }); + + // Keep the client alive until the dme objects connects to MPS or times out + data.ClientObject.OnConnected(); + data.ClientObject.KeepAliveUntilNextConnection(); + } + } + + break; + } + + case MediusServerSetAttributesRequest mgclSetAttrRequest: + { + ClientObject? dmeObject = data.ClientObject; + if (dmeObject == null) + { + LoggerAccessor.LogError($"[MAS] - Non-DME Client sending MGCL messages."); + break; + } + + dmeObject.MGCL_SERVER_ATTRIBUTES = mgclSetAttrRequest.Attributes; + dmeObject.SetIpPort(mgclSetAttrRequest.ListenServerAddress); + + // Reply with success + dmeObject.Queue(new MediusServerSetAttributesResponse() + { + MessageID = mgclSetAttrRequest.MessageID, + Confirmation = MGCL_ERROR_CODE.MGCL_SUCCESS + }); + break; + } + + case MediusServerSessionEndRequest sessionEndRequest: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} is trying to end server session without an Client Object"); + break; + } + + data.ClientObject.EndServerSession(); + + Queue(new RT_MSG_SERVER_APP() + { + Message = new MediusServerSessionEndResponse() + { + MessageID = sessionEndRequest.MessageID, + ErrorCode = MGCL_ERROR_CODE.MGCL_SUCCESS + } + }, clientChannel); + + break; + } + + case MediusServerReport serverReport: + { + data.ClientObject?.OnServerReport(serverReport); + break; + } + + #endregion + + #region Session + + case MediusExtendedSessionBeginRequest extendedSessionBeginRequest: + { + if (data.ClientObject != null) + { + data.ClientObject.MediusConnectionType = extendedSessionBeginRequest.ConnectionClass; + + await HorizonServerConfiguration.Database.GetServerFlags().ContinueWith((r) => + { + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.MaintenanceMode != null) + { + // Ensure that maintenance is active + // Ensure that we're past the from date + // Ensure that we're before the to date (if set) + if (r.Result.MaintenanceMode.IsActive + && DateTimeUtils.GetHighPrecisionUtcTime() > r.Result.MaintenanceMode.FromDt + && (!r.Result.MaintenanceMode.ToDt.HasValue + || r.Result.MaintenanceMode.ToDt > DateTimeUtils.GetHighPrecisionUtcTime())) + QueueBanMessage(data, "Server in maintenance mode."); + else + { + // Reply + data.ClientObject.Queue(new MediusSessionBeginResponse() + { + MessageID = extendedSessionBeginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + SessionKey = data.ClientObject.SessionKey + }); + } + } + }); + } + break; + } + case MediusSessionBeginRequest sessionBeginRequest: + { + if (data.ClientObject != null) + { + data.ClientObject.MediusConnectionType = sessionBeginRequest.ConnectionClass; + + LoggerAccessor.LogInfo($"Retrieved ApplicationID {data.ClientObject.ApplicationId} from client connection"); + + await HorizonServerConfiguration.Database.GetServerFlags().ContinueWith((r) => + { + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.MaintenanceMode != null) + { + #region Maintenance Mode? + // Ensure that maintenance is active + // Ensure that we're past the from date + // Ensure that we're before the to date (if set) + if (r.Result.MaintenanceMode.IsActive + && DateTimeUtils.GetHighPrecisionUtcTime() > r.Result.MaintenanceMode.FromDt + && (!r.Result.MaintenanceMode.ToDt.HasValue + || r.Result.MaintenanceMode.ToDt > DateTimeUtils.GetHighPrecisionUtcTime())) + QueueBanMessage(data, "Server in maintenance mode."); + #endregion + + #region Send Response + else + { + // Reply + data.ClientObject.Queue(new MediusSessionBeginResponse() + { + MessageID = sessionBeginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + SessionKey = data.ClientObject.SessionKey + }); + } + #endregion + } + }); + } + + break; + } + + case MediusSessionBeginRequest1 sessionBeginRequest1: + { + if (data.ClientObject != null) + { + data.ClientObject.MediusConnectionType = sessionBeginRequest1.ConnectionClass; + + LoggerAccessor.LogInfo($"Retrieved ApplicationID {data.ClientObject.ApplicationId} from client connection"); + + #region SystemMessageSingleTest Disabled? + if (MediusClass.Settings.SystemMessageSingleTest) + { + await QueueBanMessage(data, "MAS.Notification Test:\nYou have been banned from this server."); + + await data.ClientObject.Logout(); + } + #endregion + else + { + await HorizonServerConfiguration.Database.GetServerFlags().ContinueWith((r) => + { + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.MaintenanceMode != null) + { + #region Maintenance Mode? + // Ensure that maintenance is active + // Ensure that we're past the from date + // Ensure that we're before the to date (if set) + if (r.Result.MaintenanceMode.IsActive + && DateTimeUtils.GetHighPrecisionUtcTime() > r.Result.MaintenanceMode.FromDt + && (!r.Result.MaintenanceMode.ToDt.HasValue + || r.Result.MaintenanceMode.ToDt > DateTimeUtils.GetHighPrecisionUtcTime())) + QueueBanMessage(data, "Server in maintenance mode."); + + #endregion + + #region Send Response + else + { + // Reply + data.ClientObject.Queue(new MediusSessionBeginResponse() + { + MessageID = sessionBeginRequest1.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + SessionKey = data.ClientObject.SessionKey + }); + } + #endregion + } + }); + } + } + break; + } + + case MediusSessionEndRequest sessionEndRequest: + { + ClientObject? clientToEnd = MediusClass.Manager.GetClientBySessionKey(sessionEndRequest.SessionKey, data.ApplicationId); + + if (clientToEnd == null) + { + LoggerAccessor.LogError($"[MAS] - INVALID OPERATION: {clientChannel} is trying to end session of a non-existing Client Object with SessionKey: {sessionEndRequest.SessionKey}"); + break; + } + + clientToEnd.OnDisconnected(); + + Queue(new RT_MSG_SERVER_APP() + { + Message = new MediusSessionEndResponse() + { + MessageID = sessionEndRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + } + }, clientChannel); + + break; + } + + #endregion + + #region Localization + + case MediusSetLocalizationParamsRequest setLocalizationParamsRequest: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {setLocalizationParamsRequest} without a session."); + break; + } + + data.ClientObject.CharacterEncoding = setLocalizationParamsRequest.CharacterEncoding; + data.ClientObject.LanguageType = setLocalizationParamsRequest.Language; + + data.ClientObject.Queue(new MediusStatusResponse() + { + Type = 0xA4, + Class = setLocalizationParamsRequest.PacketClass, + MessageID = setLocalizationParamsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + break; + } + + case MediusSetLocalizationParamsRequest1 setLocalizationParamsRequest1: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {setLocalizationParamsRequest1} without a session."); + break; + } + + data.ClientObject.CharacterEncoding = setLocalizationParamsRequest1.CharacterEncoding; + data.ClientObject.LanguageType = setLocalizationParamsRequest1.Language; + data.ClientObject.TimeZone = setLocalizationParamsRequest1.TimeZone; + data.ClientObject.LocationId = setLocalizationParamsRequest1.LocationID; + + data.ClientObject.Queue(new MediusStatusResponse() + { + Type = 0xA4, + Class = (NetMessageClass)1, + MessageID = setLocalizationParamsRequest1.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + break; + } + case MediusSetLocalizationParamsRequest2 setLocalizationParamsRequest2: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {setLocalizationParamsRequest2} without a session."); + break; + } + + data.ClientObject.CharacterEncoding = setLocalizationParamsRequest2.CharacterEncoding; + data.ClientObject.LanguageType = setLocalizationParamsRequest2.Language; + data.ClientObject.TimeZone = setLocalizationParamsRequest2.TimeZone; + + data.ClientObject.Queue(new MediusStatusResponse() + { + Type = 0xA4, + Class = (NetMessageClass)1, + MessageID = setLocalizationParamsRequest2.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + break; + } + + #endregion + + #region Game + + case MediusGetTotalGamesRequest getTotalGamesRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalGamesRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalGamesRequest} without being logged in."); + break; + } + + data.ClientObject.Queue(new MediusGetTotalGamesResponse() + { + MessageID = getTotalGamesRequest.MessageID, + Total = 0, + StatusCode = MediusCallbackStatus.MediusRequestDenied + }); + break; + } + + #endregion + + #region Channel + + case MediusGetTotalChannelsRequest getTotalChannelsRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalChannelsRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getTotalChannelsRequest} without being logged in."); + break; + } + + data.ClientObject.Queue(new MediusGetTotalChannelsResponse() + { + MessageID = getTotalChannelsRequest.MessageID, + Total = 0, + StatusCode = MediusCallbackStatus.MediusRequestDenied, + }); + break; + } + + case MediusSetLobbyWorldFilterRequest setLobbyWorldFilterRequest: + { + //WRC 4 Sets LobbyWorldFilter Prior to making a session. + // ERROR - Need a session + /* + if (data.ClientObject == null) + throw new InvalidOperationException($"INVALID OPERATION: {clientChannel} sent {setLobbyWorldFilterRequest} without a session."); + */ + // ERROR -- Need to be logged in + /* + if (!data.ClientObject.IsLoggedIn) + throw new InvalidOperationException($"INVALID OPERATION: {clientChannel} sent {setLobbyWorldFilterRequest} without being logged in."); + */ + /* + data.ClientObject.Queue(new MediusSetLobbyWorldFilterResponse() + { + MessageID = setLobbyWorldFilterRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusRequestDenied, + }); + */ + Queue(new RT_MSG_SERVER_APP() + { + Message = new MediusSetLobbyWorldFilterResponse() + { + MessageID = setLobbyWorldFilterRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusRequestDenied + } + }); + + break; + } + #endregion + + #region DNAS CID Check + + case MediusMachineSignaturePost machineSignaturePost: + { + if (Settings.DnasEnablePost == true) + { + //Sets the CachedPlayer's MachineId + data.MachineId = BitConverter.ToString(machineSignaturePost.MachineSignature); + + LoggerAccessor.LogInfo($"Session Key {machineSignaturePost.SessionKey} | Posting Machine signatures"); + + // Then post to the Database if logged in + if (data.ClientObject?.IsLoggedIn ?? false) + await HorizonServerConfiguration.Database.PostMachineId(data.ClientObject.AccountId, data.MachineId); + } + else + { + //DnasEnablePost set to false; + } + + break; + } + + case MediusDnasSignaturePost dnasSignaturePost: + { + if (Settings.DnasEnablePost == true) + { + //If DNAS Signature Post is the PS2/PSP/PS3 Console ID then continue + if (dnasSignaturePost.DnasSignatureType == MediusDnasCategory.DnasConsoleID) + { + data.MachineId = BitConverter.ToString(dnasSignaturePost.DnasSignature); + + LoggerAccessor.LogInfo($"Posting ConsoleID - ConsoleSigSize={dnasSignaturePost.DnasSignatureLength}"); + + // Then post to the Database if logged in + if (data.ClientObject?.IsLoggedIn ?? false) + await HorizonServerConfiguration.Database.PostMachineId(data.ClientObject.AccountId, data.MachineId); + } + + if (dnasSignaturePost.DnasSignatureType == MediusDnasCategory.DnasTitleID) + LoggerAccessor.LogInfo($"DnasSignaturePost Error - Invalid SignatureType"); + + if (dnasSignaturePost.DnasSignatureType == MediusDnasCategory.DnasDiskID) + LoggerAccessor.LogInfo($"Posting DiskID - DiskSigSize={dnasSignaturePost.DnasSignatureLength}"); + } + else + { + //DnasEnablePost false, no Post; + } + break; + } + #endregion + + #region AccessLevel (2.12) + + case MediusGetAccessLevelInfoRequest getAccessLevelInfoRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getAccessLevelInfoRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getAccessLevelInfoRequest} without being logged in."); + break; + } + + //int adminAccessLevel = 4; + + data.ClientObject.Queue(new MediusGetAccessLevelInfoResponse() + { + MessageID = getAccessLevelInfoRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccessLevel = MediusAccessLevelType.MEDIUS_ACCESSLEVEL_MODERATOR, + }); + break; + } + + #endregion + + #region Version Server + case MediusVersionServerRequest mediusVersionServerRequest: + { + List appIdBeforeSession = new() { 10442, 10724 }; + + // ERROR - Need a session + if (data.ClientObject == null && !appIdBeforeSession.Contains(data.ApplicationId)) // KILLZONE PS2 GET VERSION SERVER INFO BEFORE SESSION + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {mediusVersionServerRequest} without a session."); + break; + } + + if (Settings.MediusServerVersionOverride == true) + { + #region F1 2005 PAL + List F12005AppIds = new List { 10952, 10954 }; + // F1 2005 PAL SCES / F1 2005 PAL TCES + if (F12005AppIds.Contains(data.ApplicationId)) + { + data.ClientObject?.Queue(new MediusVersionServerResponse() + { + MessageID = mediusVersionServerRequest.MessageID, + VersionServer = "Medius Authentication Server Version 2.9.0009", + StatusCode = MediusCallbackStatus.MediusSuccess, + }); + } + #endregion + + #region Socom 1 + else if (data.ApplicationId == 10274) + { + data.ClientObject?.Queue(new MediusVersionServerResponse() + { + MessageID = mediusVersionServerRequest.MessageID, + VersionServer = "Medius Authentication Server Version 1.40.PRE8", + StatusCode = MediusCallbackStatus.MediusSuccess, + }); + } + #endregion + + #region EyeToy Chat Beta + else if (data.ApplicationId == 10550) + { + data.ClientObject?.Queue(new MediusVersionServerResponse() + { + MessageID = mediusVersionServerRequest.MessageID, + VersionServer = "Medius Authentication Server Version 1.43.0000", + StatusCode = MediusCallbackStatus.MediusSuccess, + }); + } + #endregion + + #region Killzone Beta/Retail + else if (appIdBeforeSession.Contains(data.ApplicationId)) + { + //data.ClientObject = MediusClass.LobbyServer.ReserveClient(mediusVersionServerRequest); + + data.SendQueue.Enqueue(new RT_MSG_SERVER_APP() + { + Message = new MediusVersionServerResponse() + { + MessageID = mediusVersionServerRequest.MessageID, + VersionServer = "Medius Authentication Server Version 1.50.0009", + StatusCode = MediusCallbackStatus.MediusSuccess, + } + }); + } + else + #endregion + + //Default + { + data.ClientObject?.Queue(new MediusVersionServerResponse() + { + MessageID = mediusVersionServerRequest.MessageID, + VersionServer = "Medius Authentication Server Version 3.05.201109161400", + StatusCode = MediusCallbackStatus.MediusSuccess, + }); + } + } + else + { + // If MediusServerVersionOverride is false, we send our own Version String + // AND if its Killzone PS2 we make the ClientObject BEFORE SESSIONBEGIN + if (appIdBeforeSession.Contains(data.ApplicationId)) + { + //data.ClientObject = Program.LobbyServer.ReserveClient(mediusVersionServerRequest); + data.SendQueue.Enqueue(new RT_MSG_SERVER_APP() + { + Message = new MediusVersionServerResponse() + { + MessageID = mediusVersionServerRequest.MessageID, + VersionServer = "Medius Authentication Server Version 1.50.0009", + StatusCode = MediusCallbackStatus.MediusSuccess, + } + }); + } + else + { + data.ClientObject?.Queue(new MediusVersionServerResponse() + { + MessageID = mediusVersionServerRequest.MessageID, + VersionServer = Settings.MASVersion, + StatusCode = MediusCallbackStatus.MediusSuccess, + }); + } + } + + break; + } + + #endregion + + #region Co-Locations + case MediusGetLocationsRequest getLocationsRequest: + { + // ERROR - Need a session but doesn't need to be logged in + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLocationsRequest} without a session."); + break; + } + + LoggerAccessor.LogInfo($"Get Locations Request Received Sessionkey: {getLocationsRequest.SessionKey}"); + await HorizonServerConfiguration.Database.GetLocations(data.ClientObject.ApplicationId).ContinueWith(r => + { + LocationDTO[]? locations = r.Result; + + if (r.IsCompletedSuccessfully) + { + if (locations?.Length == 0) + { + LoggerAccessor.LogInfo("No Locations found."); + + data.ClientObject.Queue(new MediusGetLocationsResponse() + { + MessageID = getLocationsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusNoResult, + EndOfList = true + }); + } + else + { + var responses = locations?.Select(x => new MediusGetLocationsResponse() + { + MessageID = getLocationsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + LocationId = x.Id, + LocationName = x.Name + }).ToList(); + + if (responses != null) + { + LoggerAccessor.LogInfo("GetLocationsRequest success"); + LoggerAccessor.LogInfo($"NumLocations returned[{responses.Count}]"); + + responses[responses.Count - 1].EndOfList = true; + data.ClientObject.Queue(responses); + } + } + } + else + { + LoggerAccessor.LogError($"GetLocationsRequest failed [{r.Exception}]"); + + data.ClientObject.Queue(new MediusGetLocationsResponse() + { + MessageID = getLocationsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError, + LocationId = -1, + LocationName = "0", + EndOfList = true + }); + } + }); + break; + } + + case MediusPickLocationRequest pickLocationRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {pickLocationRequest} without a session."); + break; + } + + data.ClientObject.LocationId = pickLocationRequest.LocationID; + + data.ClientObject.Queue(new MediusPickLocationResponse() + { + MessageID = pickLocationRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + break; + } + + #endregion + + #region Account + + case MediusAccountRegistrationRequest accountRegRequest: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountRegRequest} without a session."); + break; + } + + // Check that account creation is enabled + if (appSettings.DisableAccountCreation) + { + // Reply error + data.ClientObject.Queue(new MediusAccountRegistrationResponse() + { + MessageID = accountRegRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusFail + }); + + return; + } + + // validate name + if (!MediusClass.PassTextFilter(data.ApplicationId, TextFilterContext.ACCOUNT_NAME, accountRegRequest.AccountName)) + { + data.ClientObject.Queue(new MediusAccountRegistrationResponse() + { + MessageID = accountRegRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusFail, + }); + return; + } + + await HorizonServerConfiguration.Database.CreateAccount(new CreateAccountDTO() + { + AccountName = accountRegRequest.AccountName, + AccountPassword = ComputeSHA256(accountRegRequest.Password), + MachineId = data.MachineId, + MediusStats = Convert.ToBase64String(new byte[Constants.ACCOUNTSTATS_MAXLEN]), + AppId = data.ClientObject.ApplicationId + }, clientChannel).ContinueWith((r) => + { + if (r.IsCompletedSuccessfully && r.Result != null) + { + // Reply with account id + data.ClientObject.Queue(new MediusAccountRegistrationResponse() + { + MessageID = accountRegRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccountID = r.Result.AccountId + }); + } + else + { + // Reply error + data.ClientObject.Queue(new MediusAccountRegistrationResponse() + { + MessageID = accountRegRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusAccountAlreadyExists + }); + } + }); + break; + } + case MediusAccountGetIDRequest accountGetIdRequest: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountGetIdRequest} without a session."); + break; + } + + await HorizonServerConfiguration.Database.GetAccountByName(accountGetIdRequest.AccountName, data.ClientObject.ApplicationId).ContinueWith((r) => + { + if (r.IsCompletedSuccessfully && r.Result != null) + { + // Success + data?.ClientObject?.Queue(new MediusAccountGetIDResponse() + { + MessageID = accountGetIdRequest.MessageID, + AccountID = r.Result.AccountId, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + } + else + { + // Fail + data?.ClientObject?.Queue(new MediusAccountGetIDResponse() + { + MessageID = accountGetIdRequest.MessageID, + AccountID = -1, + StatusCode = MediusCallbackStatus.MediusAccountNotFound + }); + } + }); + + break; + } + case MediusAccountDeleteRequest accountDeleteRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountDeleteRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountDeleteRequest} without being logged in."); + break; + } + + if (!string.IsNullOrEmpty(data.ClientObject.AccountName)) + { + await HorizonServerConfiguration.Database.DeleteAccount(data.ClientObject.AccountName, data.ClientObject.ApplicationId).ContinueWith((r) => + { + if (r.IsCompletedSuccessfully && r.Result) + { + LoggerAccessor.LogInfo($"Logging out {data?.ClientObject?.AccountName}'s account\nDeleting from Medius Server"); + + data?.ClientObject?.Logout(); + + data?.ClientObject?.Queue(new MediusAccountDeleteResponse() + { + MessageID = accountDeleteRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + } + else + { + LoggerAccessor.LogWarn($"Logout FAILED for {data?.ClientObject?.AccountName}'s account\nData still persistent on Medius Server"); + + data?.ClientObject?.Queue(new MediusAccountDeleteResponse() + { + MessageID = accountDeleteRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError + }); + } + }); + } + break; + } + case MediusAnonymousLoginRequest anonymousLoginRequest: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {anonymousLoginRequest} without a session."); + break; + } + + await LoginAnonymous(anonymousLoginRequest, clientChannel, data); + break; + } + case MediusAccountLoginRequest accountLoginRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountLoginRequest} without a session."); + break; + } + + await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_ACCOUNT_LOGIN_REQUEST, new OnAccountLoginRequestArgs() + { + Player = data.ClientObject, + Request = accountLoginRequest + }); + + // Check the client isn't already logged in + if (MediusClass.Manager.GetClientByAccountName(accountLoginRequest.Username, data.ClientObject.ApplicationId)?.IsLoggedIn ?? false) + { + data.ClientObject.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusAccountLoggedIn + }); + } + else + { + #region SystemMessageSingleTest Disabled? + if (MediusClass.Settings.SystemMessageSingleTest != false) + { + await QueueBanMessage(data, "MAS.Notification Test:\nYou have been banned from this server."); + + await data.ClientObject.Logout(); + } + #endregion + else + { + _ = HorizonServerConfiguration.Database.GetAccountByName(accountLoginRequest.Username, data.ClientObject.ApplicationId, true).ContinueWith(async (r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null && data != null && data.ClientObject != null && data.ClientObject.IsConnected) + { + + if (r.Result.IsBanned) + { + // Send ban message + //await QueueBanMessage(data); + + // Account is banned + // Temporary solution is to tell the client the login failed + data?.ClientObject?.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusAccountBanned + }); + + } + else if (appSettings.EnableAccountWhitelist && !appSettings.AccountIdWhitelist.Contains(r.Result.AccountId)) + { + // Account not allowed to sign in + data?.ClientObject?.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusFail + }); + } + else if (MediusClass.Manager.GetClientByAccountName(accountLoginRequest.Username, data.ClientObject.ApplicationId)?.IsLoggedIn ?? false) + { + data.ClientObject.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusAccountLoggedIn + }); + } + + else if (ComputeSHA256(accountLoginRequest.Password) == r.Result.AccountPassword) + await Login(accountLoginRequest.MessageID, clientChannel, data, r.Result, false); + else + { + // Incorrect password + data?.ClientObject?.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusInvalidPassword + }); + } + } + else if (appSettings.CreateAccountOnNotFound) + { + // Account not found, create new and login + // Check that account creation is enabled + if (appSettings.DisableAccountCreation) + { + // Reply error + data?.ClientObject?.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusFail, + }); + return; + } + + // validate name + if (data != null && !MediusClass.PassTextFilter(data.ApplicationId, TextFilterContext.ACCOUNT_NAME, accountLoginRequest.Username)) + { + data.ClientObject?.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusVulgarityFound, + }); + return; + } + + await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PRE_ACCOUNT_CREATE_ON_NOT_FOUND, new OnAccountLoginRequestArgs() + { + Player = data?.ClientObject, + Request = accountLoginRequest + }); + + if (data != null && data.ClientObject != null) + { + _ = HorizonServerConfiguration.Database.CreateAccount(new CreateAccountDTO() + { + AccountName = accountLoginRequest.Username, + AccountPassword = ComputeSHA256(accountLoginRequest.Password), + MachineId = data.MachineId, + MediusStats = Convert.ToBase64String(new byte[Constants.ACCOUNTSTATS_MAXLEN]), + AppId = data.ClientObject.ApplicationId + }, clientChannel).ContinueWith(async (r) => + { + if (r.IsCompletedSuccessfully && r.Result != null) + { + await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_POST_ACCOUNT_CREATE_ON_NOT_FOUND, new OnAccountLoginRequestArgs() + { + Player = data.ClientObject, + Request = accountLoginRequest + }); + await Login(accountLoginRequest.MessageID, clientChannel, data, r.Result, false); + } + else + { + // Reply error + data.ClientObject.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusInvalidPassword + }); + } + }); + } + } + else + { + // Account not found + data?.ClientObject?.Queue(new MediusAccountLoginResponse() + { + MessageID = accountLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusAccountNotFound, + }); + } + }); + } + } + break; + } + + case MediusAccountUpdatePasswordRequest accountUpdatePasswordRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdatePasswordRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdatePasswordRequest} without being logged in."); + break; + } + + // Post New Password to Database + await HorizonServerConfiguration.Database.PostAccountUpdatePassword(data.ClientObject.AccountId, accountUpdatePasswordRequest.OldPassword, accountUpdatePasswordRequest.NewPassword).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result) + { + data.ClientObject.Queue(new MediusAccountUpdatePasswordStatusResponse() + { + MessageID = accountUpdatePasswordRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + } + else + { + data.ClientObject.Queue(new MediusAccountUpdatePasswordStatusResponse() + { + MessageID = accountUpdatePasswordRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError + }); + } + }); + break; + } + + case MediusAccountLogoutRequest accountLogoutRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountLogoutRequest} without a session."); + break; + } + + MediusCallbackStatus result = MediusCallbackStatus.MediusFail; + + // Check token + if (data.ClientObject.IsLoggedIn && accountLogoutRequest.SessionKey == data.ClientObject.SessionKey) + { + result = MediusCallbackStatus.MediusSuccess; + + // Logout + await data.ClientObject.Logout(); + } + + data.ClientObject.Queue(new MediusAccountLogoutResponse() + { + MessageID = accountLogoutRequest.MessageID, + StatusCode = result + }); + break; + } + + case MediusAccountUpdateStatsRequest accountUpdateStatsRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdateStatsRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {accountUpdateStatsRequest} without being logged in."); + break; + } + + await HorizonServerConfiguration.Database.PostMediusStats(data.ClientObject.AccountId, Convert.ToBase64String(accountUpdateStatsRequest.Stats)).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result) + { + data.ClientObject.Queue(new MediusAccountUpdateStatsResponse() + { + MessageID = accountUpdateStatsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + } + else + { + data.ClientObject.Queue(new MediusAccountUpdateStatsResponse() + { + MessageID = accountUpdateStatsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError + }); + } + }); + break; + } + + case MediusTicketLoginRequest ticketLoginRequest: + { + // ERROR - Need a session and XI5 Ticket + if (data.ClientObject == null || ticketLoginRequest.TicketData == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {ticketLoginRequest} without a session or XI5 Ticket."); + break; + } + + // get ticket + XI5Ticket ticket = XI5Ticket.ReadFromBytes(ticketLoginRequest.TicketData); + + // setup username + string username = ticket.Username; + + // invalid ticket + if (!ticket.Valid) + { + // log to console + LoggerAccessor.LogWarn($"[MAS] - {username} tried to alter their ticket data"); + + data.ClientObject.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusDBError + }); + + break; + } + + // RPCN + if (ticket.SignatureIdentifier == "RPCN") + { + data.ClientObject.IsOnRPCN = true; + username += "@RPCN"; + + // TODO: not sure if new way validates RPCN sig + if (MediusClass.Settings.ForceOfficialRPCNSignature) + { + + } + } + + // PSN + else + { + + } + + // log to console + LoggerAccessor.LogInfo($"[MAS] - {username} connected"); + + // get account + AccountDTO? account = await HorizonServerConfiguration.Database.GetAccountByName(username, data.ClientObject.ApplicationId); + + // get existing account + ClientObject? existingClient = MediusClass.Manager.GetClientByAccountName(username, data.ClientObject.ApplicationId); + + // account already logged in + if (existingClient != null && existingClient.IsLoggedIn) + { + data.ClientObject.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusAccountLoggedIn + }); + + break; + } + + // account doesn't exist + if (account == null) + { + // account creation disabled + if (appSettings.DisableAccountCreation) + { + LoggerAccessor.LogError($"[MAS] - AppId {data?.ClientObject?.ApplicationId} has account creation disabled"); + + // Reply error + data?.ClientObject?.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusFail, + }); + + break; + } + + // create new account + account = await HorizonServerConfiguration.Database.CreateAccount(new CreateAccountDTO() + { + AccountName = username, + AccountPassword = "UNSET", + MachineId = data.MachineId, + MediusStats = Convert.ToBase64String(new byte[Constants.ACCOUNTSTATS_MAXLEN]), + AppId = data.ClientObject.ApplicationId + }, clientChannel); + + // error creating account + if (account == null) + { + data.ClientObject.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusDBError + }); + + break; + } + } + + // account is banned + if (account.IsBanned) + { + LoggerAccessor.LogWarn($"[MAS] - {username} tried to login with banned account"); + + data?.ClientObject?.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusAccountBanned + }); + + break; + } + + // get home ban record + bool isHomeBanned = !MediusClass.Settings.PlaystationHomeAllowAnyEboot && + (data.ClientObject.ApplicationId == 20371 || data.ClientObject.ApplicationId == 20374) && + data.ClientObject.ClientHomeData == null; + + // account banned from home + if (isHomeBanned) + { + LoggerAccessor.LogWarn($"[MAS] - {username} tried to login while being banned from home"); + + data?.ClientObject?.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusAccountBanned + }); + + break; + } + + // get client ip + string clientIp = Regex.Match(clientChannel.RemoteAddress.ToString(), @"\[(?:.*?:)?(?\d+\.\d+\.\d+\.\d+)\]").Groups["ip"].Value; + + // get ip ban record + bool isIpBanned = await HorizonServerConfiguration.Database.GetIsIpBanned(IPAddress.Parse(clientIp)); + + // ip is banned + if (isIpBanned) + { + LoggerAccessor.LogWarn($"[MAS] - {username} tried to login with banned IP {clientIp}"); + + data?.ClientObject?.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusAccountBanned + }); + + break; + } + + // get cid ban record + bool isCidBanned = await HorizonServerConfiguration.Database.GetIsMacBanned(data.MachineId ?? ""); + + // cid is banned + if (isCidBanned) + { + LoggerAccessor.LogWarn($"[MAS] - {username} tried to login with banned CID {data.MachineId}"); + + data?.ClientObject?.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusAccountBanned + }); + + break; + } + + // account not whitelisted + if (appSettings.EnableAccountWhitelist && + !appSettings.AccountIdWhitelist.Contains(account.AccountId)) + { + LoggerAccessor.LogError($"[MAS] - {username} tried to login without being whitelisted"); + + data?.ClientObject?.Queue(new MediusTicketLoginResponse() + { + MessageID = ticketLoginRequest.MessageID, + StatusCodeTicketLogin = MediusCallbackStatus.MediusFail + }); + + break; + } + + // login user + await Login(ticketLoginRequest.MessageID, clientChannel, data, account, true); + + break; + } + #endregion + + #region Policy / Announcements + + case MediusGetAllAnnouncementsRequest getAllAnnouncementsRequest: + { + // Send to plugins + await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PLAYER_ON_GET_ALL_ANNOUNCEMENTS, new OnPlayerRequestArgs() + { + Player = data.ClientObject, + Request = getAllAnnouncementsRequest + }); + + await HorizonServerConfiguration.Database.GetLatestAnnouncementsList(data.ApplicationId).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.Length > 0) + { + List responses = new List(); + foreach (var result in r.Result) + { + responses.Add(new MediusGetAnnouncementsResponse() + { + MessageID = getAllAnnouncementsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Announcement = string.IsNullOrEmpty(result.AnnouncementTitle) ? $"{result.AnnouncementBody}" : $"{result.AnnouncementTitle}\n{result.AnnouncementBody}\n", + AnnouncementID = result.Id++, + EndOfList = false + }); + } + + responses[responses.Count - 1].EndOfList = true; + data.ClientObject.Queue(responses); + } + else + { + data.ClientObject.Queue(new MediusGetAnnouncementsResponse() + { + MessageID = getAllAnnouncementsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Announcement = "", + AnnouncementID = 0, + EndOfList = true + }); + } + }); + break; + } + + case MediusGetAnnouncementsRequest getAnnouncementsRequest: + { + // Send to plugins + await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PLAYER_ON_GET_ANNOUNCEMENTS, new OnPlayerRequestArgs() + { + Player = data.ClientObject, + Request = getAnnouncementsRequest + }); + + await HorizonServerConfiguration.Database.GetLatestAnnouncement(data.ApplicationId).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null) + { + data.ClientObject.Queue(new MediusGetAnnouncementsResponse() + { + MessageID = getAnnouncementsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Announcement = string.IsNullOrEmpty(r.Result.AnnouncementTitle) ? $"{r.Result.AnnouncementBody}" : $"{r.Result.AnnouncementTitle}\n{r.Result.AnnouncementBody}\n", + AnnouncementID = r.Result.Id++, + EndOfList = true + }); + } + else + { + data.ClientObject.Queue(new MediusGetAnnouncementsResponse() + { + MessageID = getAnnouncementsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Announcement = "", + AnnouncementID = 0, + EndOfList = true + }); + } + }); + break; + } + + case MediusGetPolicyRequest getPolicyRequest: + { + // Send to plugins + await MediusClass.Plugins.OnEvent(PluginEvent.MEDIUS_PLAYER_ON_GET_POLICY, new OnPlayerRequestArgs() + { + Player = data.ClientObject, + Request = getPolicyRequest + }); + + switch (getPolicyRequest.Policy) + { + case MediusPolicyType.Privacy: + { + if (data.ClientObject != null) + { + await HorizonServerConfiguration.Database.GetPolicy((int)MediusPolicyType.Privacy, data.ClientObject.ApplicationId).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null) + { + string? txt = r.Result.EulaBody; + if (!string.IsNullOrEmpty(r.Result.EulaTitle)) + txt = r.Result.EulaTitle + "\n" + txt; + else + txt = string.Empty; + LoggerAccessor.LogInfo($"GetPolicy Succeeded:{getPolicyRequest.MessageID}"); + data.ClientObject.Queue(MediusClass.GetPolicyFromText(getPolicyRequest.MessageID, txt)); + } + else if (r.IsCompletedSuccessfully && r.Result == null) + { + LoggerAccessor.LogDebug($"Sending blank Policy since no chunks were found"); + data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = string.Empty, EndOfText = true }); + } + else + { + LoggerAccessor.LogError($"GetPolicy Failed = [{r.Exception}]"); + data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = "NONE", EndOfText = true }); + } + }); + } + break; + } + case MediusPolicyType.Usage: + { + if (data.ClientObject != null) + { + await HorizonServerConfiguration.Database.GetPolicy((int)MediusPolicyType.Usage, data.ClientObject.ApplicationId).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null) + { + string? txt = r.Result.EulaBody; + if (!string.IsNullOrEmpty(r.Result.EulaTitle)) + txt = r.Result.EulaTitle + "\n" + txt; + else + txt = string.Empty; + LoggerAccessor.LogInfo($"GetPolicy Succeeded:{getPolicyRequest.MessageID}"); + data.ClientObject.Queue(MediusClass.GetPolicyFromText(getPolicyRequest.MessageID, txt)); + } + else if (r.IsCompletedSuccessfully && r.Result == null) + { + LoggerAccessor.LogDebug($"Sending blank Policy since no chunks were found"); + data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = string.Empty, EndOfText = true }); + } + else + { + LoggerAccessor.LogError($"GetPolicy Failed = [{r.Exception}]"); + data.ClientObject.Queue(new MediusGetPolicyResponse() { MessageID = getPolicyRequest.MessageID, StatusCode = MediusCallbackStatus.MediusSuccess, Policy = "NONE", EndOfText = true }); + } + }); + } + break; + } + } + break; + } + + #endregion + + #region Ladders + + case MediusGetLadderStatsRequest getLadderStatsRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsRequest} without being logged in."); + break; + } + + switch (getLadderStatsRequest.LadderType) + { + case MediusLadderType.MediusLadderTypePlayer: + { + await HorizonServerConfiguration.Database.GetAccountById(getLadderStatsRequest.AccountID_or_ClanID).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.AccountStats != null) + { + data.ClientObject.Queue(new MediusGetLadderStatsResponse() + { + MessageID = getLadderStatsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Stats = Array.ConvertAll(r.Result.AccountStats, Convert.ToInt32) + }); + } + else + { + data.ClientObject.Queue(new MediusGetLadderStatsResponse() + { + MessageID = getLadderStatsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError + }); + } + }); + break; + } + case MediusLadderType.MediusLadderTypeClan: + { + await HorizonServerConfiguration.Database.GetClanById(getLadderStatsRequest.AccountID_or_ClanID, + data.ClientObject.ApplicationId) + .ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.ClanStats != null) + { + data.ClientObject.Queue(new MediusGetLadderStatsResponse() + { + MessageID = getLadderStatsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Stats = r.Result.ClanStats + }); + } + else + { + data.ClientObject.Queue(new MediusGetLadderStatsResponse() + { + MessageID = getLadderStatsRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError + }); + } + }); + break; + } + default: + { + LoggerAccessor.LogWarn($"Unhandled MediusGetLadderStatsRequest {getLadderStatsRequest}"); + break; + } + } + break; + } + + case MediusGetLadderStatsWideRequest getLadderStatsWideRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsWideRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getLadderStatsWideRequest} without being logged in."); + break; + } + + switch (getLadderStatsWideRequest.LadderType) + { + case MediusLadderType.MediusLadderTypePlayer: + { + await HorizonServerConfiguration.Database.GetAccountById(getLadderStatsWideRequest.AccountID_or_ClanID).ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.AccountWideStats != null) + { + data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() + { + MessageID = getLadderStatsWideRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccountID_or_ClanID = r.Result.AccountId, + Stats = r.Result.AccountWideStats + }); + } + else + { + data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() + { + MessageID = getLadderStatsWideRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError + }); + } + }); + break; + } + case MediusLadderType.MediusLadderTypeClan: + { + await HorizonServerConfiguration.Database.GetClanById(getLadderStatsWideRequest.AccountID_or_ClanID, + data.ClientObject.ApplicationId) + .ContinueWith((r) => + { + if (data == null || data.ClientObject == null || !data.ClientObject.IsConnected) + return; + + if (r.IsCompletedSuccessfully && r.Result != null && r.Result.ClanWideStats != null) + { + data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() + { + MessageID = getLadderStatsWideRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccountID_or_ClanID = r.Result.ClanId, + Stats = r.Result.ClanWideStats + }); + } + else + { + data.ClientObject.Queue(new MediusGetLadderStatsWideResponse() + { + MessageID = getLadderStatsWideRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusDBError + }); + } + }); + break; + } + default: + { + LoggerAccessor.LogWarn($"Unhandled MediusGetLadderStatsWideRequest {getLadderStatsWideRequest}"); + break; + } + } + break; + } + + #endregion + + #region Channels + + case MediusChannelListRequest channelListRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {channelListRequest} without a session."); + break; + } + + // ERROR -- Need to be logged in + if (!data.ClientObject.IsLoggedIn) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {channelListRequest} without being logged in."); + break; + } + + List channelResponses = new List(); + + var lobbyChannels = MediusClass.Manager.GetChannelList( + data.ClientObject.ApplicationId, + channelListRequest.PageID, + channelListRequest.PageSize, + ChannelType.Lobby + ); + + foreach (var channel in lobbyChannels) + { + channelResponses.Add(new MediusChannelListResponse() + { + MessageID = channelListRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + MediusWorldID = channel.Id, + LobbyName = channel.Name, + PlayerCount = channel.PlayerCount, + EndOfList = false + }); + } + + if (channelResponses.Count == 0) + { + // Return none + data.ClientObject.Queue(new MediusChannelListResponse() + { + MessageID = channelListRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusNoResult, + EndOfList = true + }); + } + else + { + // Ensure the end of list flag is set + channelResponses[channelResponses.Count - 1].EndOfList = true; + + // Add to responses + data.ClientObject.Queue(channelResponses); + } + + + break; + } + + #endregion + + #region Deadlocked No-op Messages (MAS) + + case MediusGetBuddyList_ExtraInfoRequest getBuddyList_ExtraInfoRequest: + { + Queue(new RT_MSG_SERVER_APP() + { + Message = new MediusGetBuddyList_ExtraInfoResponse() + { + MessageID = getBuddyList_ExtraInfoRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusNoResult, + EndOfList = true + } + }, clientChannel); + break; + } + + case MediusGetIgnoreListRequest getIgnoreListRequest: + { + Queue(new RT_MSG_SERVER_APP() + { + Message = new MediusGetIgnoreListResponse() + { + MessageID = getIgnoreListRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusNoResult, + EndOfList = true + } + }, clientChannel); + break; + } + + case MediusGetMyClansRequest getMyClansRequest: + { + Queue(new RT_MSG_SERVER_APP() + { + Message = new MediusGetMyClansResponse() + { + MessageID = getMyClansRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusNoResult, + EndOfList = true + } + }, clientChannel); + break; + } + + #endregion + + #region TextFilter + + case MediusTextFilterRequest textFilterRequest: + { + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {textFilterRequest} without a session."); + break; + } + + // Deny special characters + // Also trim any whitespace + switch (textFilterRequest.TextFilterType) + { + case MediusTextFilterType.MediusTextFilterPassFail: + { + // validate name + if (!MediusClass.PassTextFilter(data.ApplicationId, Config.TextFilterContext.ACCOUNT_NAME, textFilterRequest.Text)) + { + // Failed due to special characters + data.ClientObject.Queue(new MediusTextFilterResponse() + { + MessageID = textFilterRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusFail + }); + return; + } + else + { + data.ClientObject.Queue(new MediusTextFilterResponse() + { + MessageID = textFilterRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusPass, + Text = textFilterRequest.Text.Trim() + }); + } + break; + } + case MediusTextFilterType.MediusTextFilterReplace: + { + data.ClientObject.Queue(new MediusTextFilterResponse() + { + MessageID = textFilterRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusPass, + Text = MediusClass.FilterTextFilter(data.ApplicationId, TextFilterContext.ACCOUNT_NAME, textFilterRequest.Text).Trim() + }); + break; + } + } + break; + } + + #endregion + + #region Time + case MediusGetServerTimeRequest getServerTimeRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getServerTimeRequest} without a session."); + break; + } + + //Doesn't need to be logged in to get ServerTime + + var time = DateTime.Now; + + await GetTimeZone(time).ContinueWith((r) => + { + if (r.IsCompletedSuccessfully) + { + //Fetched + data.ClientObject.Queue(new MediusGetServerTimeResponse() + { + MessageID = getServerTimeRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Local_server_timezone = r.Result, + }); + } + else + { + //default + data.ClientObject.Queue(new MediusGetServerTimeResponse() + { + MessageID = getServerTimeRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + Local_server_timezone = MediusTimeZone.MediusTimeZone_GMT, + }); + } + }); + break; + } + #endregion + + #region GetMyIP + //Syphon Filter - The Omega Strain Beta + + case MediusGetMyIPRequest getMyIpRequest: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {getMyIpRequest} without a session."); + break; + } + + IPAddress? ClientIP = (clientChannel.RemoteAddress as IPEndPoint)?.Address; + + if (ClientIP == null) + { + LoggerAccessor.LogInfo($"Error: Retrieving Client IP address {clientChannel.RemoteAddress} = [{ClientIP}]"); + data.ClientObject.Queue(new MediusGetMyIPResponse() + { + MessageID = getMyIpRequest.MessageID, + IP = null, + StatusCode = MediusCallbackStatus.MediusDMEError + }); + } + else + { + data.ClientObject.Queue(new MediusGetMyIPResponse() + { + MessageID = getMyIpRequest.MessageID, + IP = ClientIP, + StatusCode = MediusCallbackStatus.MediusSuccess + }); + } + + break; + } + + #endregion + + #region UpdateUserState + case MediusUpdateUserState updateUserState: + { + // ERROR - Need a session + if (data.ClientObject == null) + { + LoggerAccessor.LogError($"INVALID OPERATION: {clientChannel} sent {updateUserState} without a session."); + break; + } + + // ERROR - Needs to be logged in --Doesn't need to be logged in on older clients + + switch (updateUserState.UserAction) + { + case MediusUserAction.KeepAlive: + { + data.ClientObject.KeepAliveUntilNextConnection(); + break; + } + case MediusUserAction.JoinedChatWorld: + { + LoggerAccessor.LogInfo($"[MAS] - Successfully Joined ChatWorld [{data.ClientObject.CurrentChannel?.Id}] {data.ClientObject.AccountId}:{data.ClientObject.AccountName}"); + break; + } + case MediusUserAction.LeftGameWorld: + { + LoggerAccessor.LogInfo($"[MAS] - Successfully Left GameWorld {data.ClientObject.AccountId}:{data.ClientObject.AccountName}"); + MediusClass.AntiCheatPlugin.mc_anticheat_event_msg_UPDATEUSERSTATE(AnticheatEventCode.anticheatLEAVEGAME, data.ClientObject.MediusWorldID, data.ClientObject.AccountId, MediusClass.AntiCheatClient, updateUserState, 256); + break; + } + case MediusUserAction.LeftPartyWorld: + { + LoggerAccessor.LogInfo($"[MAS] - Successfully Left PartyWorld {data.ClientObject.AccountId}:{data.ClientObject.AccountName}"); + break; + } + default: + { + LoggerAccessor.LogWarn($"[MAS] - Requested a non-existant UserState {data.ClientObject.AccountId}:{data.ClientObject.AccountName}, please report to GITHUB."); + break; + } + } + + break; + } + + #endregion + + default: + { + LoggerAccessor.LogWarn($"Unhandled Medius Message: {message}"); + break; + } + } + } + + #region Login + private async Task Login(MessageId messageId, IChannel clientChannel, ChannelData data, AccountDTO accountDto, bool ticket) + { + var fac = new PS2CipherFactory(); + var rsa = fac.CreateNew(CipherContext.RSA_AUTH) as PS2_RSA; + IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); + + List pre108Secure = new() { 10010, 10031, 10190, 10124, 10680, 10681, 10683, 10684 }; + + if (data.ClientObject!.IP == IPAddress.Any) + data.ClientObject!.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(new char[] { ':', 'f', '{', '}' })); + + if ((data.ApplicationId == 20371 || data.ApplicationId == 20374) && data.ClientObject.ClientHomeData != null) + { + if (data.ClientObject.IsOnRPCN && data.ClientObject.ClientHomeData.VersionAsDouble >= 01.83) + { + if (!string.IsNullOrEmpty(data.ClientObject.ClientHomeData.Type) && (data.ClientObject.ClientHomeData.Type.Contains("HDK") || data.ClientObject.ClientHomeData.Type == "Online Debug")) + _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "lc Debug.System( 'mlaaenable 0' )"); + else + _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "mlaaenable 0"); + } + /*else if (data.ClientObject.ClientHomeData.VersionAsDouble >= 01.83) // MSAA PS3 Only for now: https://github.com/RPCS3/rpcs3/issues/15719 + { + if (!string.IsNullOrEmpty(data.ClientObject.ClientHomeData?.Type) && (data.ClientObject.ClientHomeData.Type.Contains("HDK") || data.ClientObject.ClientHomeData.Type == "Online Debug")) + _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "lc Debug.System( 'msaaenable 1' )"); + else + _ = HomeRTMTools.SendRemoteCommand(data.ClientObject, "msaaenable 1"); + }*/ + + switch (data.ClientObject.ClientHomeData.Type) + { + case "HDK With Offline": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.86.09": + if (MediusClass.Settings.PokePatchOn) + { + CheatQuery(0x005478dc, 4, clientChannel); + + CheatQuery(0x0016cc6c, 4, clientChannel); + } + + CheatQuery(0x1054e5c0, 4, clientChannel); + break; + default: + break; + } + break; + case "HDK Online Only": + switch (data.ClientObject.ClientHomeData.Version) + { + default: + break; + } + break; + case "HDK Online Only (Dbg Symbols)": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.82.09": + if (MediusClass.Settings.PokePatchOn) + { + CheatQuery(0x00531370, 4, clientChannel); + + CheatQuery(0x0016b4d0, 4, clientChannel); + } + + CheatQuery(0x1053e160, 4, clientChannel); + break; + default: + break; + } + break; + case "Online Debug": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.83.12": + if (MediusClass.Settings.PokePatchOn) + { + CheatQuery(0x00548bc0, 4, clientChannel); + + CheatQuery(0x001709e0, 4, clientChannel); + } + + CheatQuery(0x1054e1c0, 4, clientChannel); + break; + case "01.86.09": + if (MediusClass.Settings.PokePatchOn) + { + CheatQuery(0x00555cb4, 4, clientChannel); + + CheatQuery(0x0016dac0, 4, clientChannel); + } + + CheatQuery(0x1054e358, 4, clientChannel); + break; + default: + break; + } + break; + case "Retail": + switch (data.ClientObject.ClientHomeData.Version) + { + case "01.86.09": + if (MediusClass.Settings.PokePatchOn) + { + CheatQuery(0x006f59b8, 4, clientChannel); + CheatQuery(0x002aa960, 4, clientChannel); + } + + CheatQuery(0x105c24c8, 4, clientChannel); + break; + default: + break; + } + break; + } + } + + await data.ClientObject.Login(accountDto); + + #region Update DB IP and CID + await HorizonServerConfiguration.Database.PostAccountIp(accountDto.AccountId, ((IPEndPoint)clientChannel.RemoteAddress).Address.MapToIPv4().ToString()); + + CIDManager.CreateCIDPair(data.ClientObject.AccountName, data.MachineId); + + if (!string.IsNullOrEmpty(data.MachineId)) + await HorizonServerConfiguration.Database.PostMachineId(data.ClientObject.AccountId, data.MachineId); + #endregion + + // Add to logged in clients + MediusClass.Manager.AddOrUpdateLoggedInClient(data.ClientObject); + + LoggerAccessor.LogInfo($"LOGGING IN AS {data.ClientObject.AccountName} with access token {data.ClientObject.AccessToken}"); + + // Tell client + if (ticket) + { + #region IF PS3 Client + data.ClientObject.Queue(new MediusTicketLoginResponse() + { + //TicketLoginResponse + MessageID = messageId, + StatusCodeTicketLogin = MediusCallbackStatus.MediusSuccess, + PasswordType = MediusPasswordType.MediusPasswordNotSet, + + //AccountLoginResponse Wrapped + MessageID2 = messageId, + StatusCodeAccountLogin = MediusCallbackStatus.MediusSuccess, + AccountID = data.ClientObject.AccountId, + AccountType = MediusAccountType.MediusMasterAccount, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = new RSA_KEY(), //MediusStarter.GlobalAuthPublic, + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() {Address = !string.IsNullOrEmpty(MediusClass.Settings.NpMLSIpOverride) ? MediusClass.Settings.NpMLSIpOverride : MediusClass.LobbyServer.IPAddress.ToString(), Port = (MediusClass.Settings.NpMLSPortOverride != -1) ? MediusClass.Settings.NpMLSPortOverride : MediusClass.LobbyServer.TCPPort , AddressType = NetAddressType.NetAddressTypeExternal}, + new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerTCP + }, + }); + #endregion + + // Prepare for transition to lobby server + data.ClientObject.KeepAliveUntilNextConnection(); + } + else + { + #region If PS2/PSP + + if (data.ClientObject.MediusVersion > 108) + { + data.ClientObject.Queue(new MediusAccountLoginResponse() + { + MessageID = messageId, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccountID = data.ClientObject.AccountId, + AccountType = MediusAccountType.MediusMasterAccount, + WorldID = data.ClientObject.CurrentChannel!.Id, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = MediusClass.GlobalAuthPublic, + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, + new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerTCP + }, + }); + } + else if (pre108Secure.Contains(data.ClientObject.ApplicationId)) //10683 / 10684 + { + data.ClientObject.Queue(new MediusAccountLoginResponse() + { + MessageID = messageId, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccountID = data.ClientObject.AccountId, + AccountType = MediusAccountType.MediusMasterAccount, + WorldID = data.ClientObject.CurrentChannel!.Id, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = new RSA_KEY(), + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, + new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerTCP + }, + }); + } + else + { + data.ClientObject.Queue(new MediusAccountLoginResponse() + { + MessageID = messageId, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccountID = data.ClientObject.AccountId, + AccountType = MediusAccountType.MediusMasterAccount, + WorldID = data.ClientObject.CurrentChannel!.Id, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = MediusClass.GlobalAuthPublic, //Some Older Medius games don't set a RSA Key + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, + new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerTCP + }, + }); + } + + // Prepare for transition to lobby server + data.ClientObject.KeepAliveUntilNextConnection(); + #endregion + } + } + #endregion + + #region AnonymousLogin + private async Task LoginAnonymous(MediusAnonymousLoginRequest anonymousLoginRequest, IChannel clientChannel, ChannelData data) + { + IPHostEntry host = Dns.GetHostEntry(MediusClass.Settings.NATIp ?? "natservice.pdonline.scea.com"); + PS2CipherFactory fac = new(); + var rsa = fac.CreateNew(CipherContext.RSA_AUTH) as PS2_RSA; + + int iAccountID = MediusClass.Manager.AnonymousAccountIDGenerator(MediusClass.Settings.AnonymousIDRangeSeed); + LoggerAccessor.LogInfo($"AnonymousIDRangeSeedGenerator AccountID returned {iAccountID}"); + + if (data.ClientObject != null) + { + char[] charsToRemove = { ':', 'f', '{', '}' }; + if (data.ClientObject.IP == IPAddress.Any) + data.ClientObject.SetIp(((IPEndPoint)clientChannel.RemoteAddress).Address.ToString().Trim(charsToRemove)); + } + + if (data.ClientObject != null) + { + CIDManager.CreateCIDPair("AnonymousClient", data.MachineId); + + // Login + await data.ClientObject.LoginAnonymous(anonymousLoginRequest, iAccountID); + + #region Update DB IP and CID + + //We don't post to the database as anonymous... This ain't it chief + + #endregion + + // Add to logged in clients + MediusClass.Manager.AddOrUpdateLoggedInClient(data.ClientObject); + + LoggerAccessor.LogInfo($"LOGGING IN ANONYMOUSLY AS {data.ClientObject.AccountDisplayName} with access token {data.ClientObject.AccessToken}"); + + // Tell client + data.ClientObject.Queue(new MediusAnonymousLoginResponse() + { + MessageID = anonymousLoginRequest.MessageID, + StatusCode = MediusCallbackStatus.MediusSuccess, + AccountID = iAccountID, + AccountType = MediusAccountType.MediusMasterAccount, + WorldID = data.ClientObject.CurrentChannel!.Id, + ConnectInfo = new NetConnectionInfo() + { + AccessKey = data.ClientObject.AccessToken, + SessionKey = data.ClientObject.SessionKey, + TargetWorldID = data.ClientObject.CurrentChannel!.Id, + ServerKey = new RSA_KEY(), // Null for 108 clients + AddressList = new NetAddressList() + { + AddressList = new NetAddress[Constants.NET_ADDRESS_LIST_COUNT] + { + new NetAddress() {Address = MediusClass.LobbyServer.IPAddress.ToString(), Port = MediusClass.LobbyServer.TCPPort, AddressType = NetAddressType.NetAddressTypeExternal}, + new NetAddress() {Address = host.AddressList.First().ToString(), Port = MediusClass.Settings.NATPort, AddressType = NetAddressType.NetAddressTypeNATService}, + } + }, + Type = NetConnectionType.NetConnectionTypeClientServerTCP + } + }); + + data.ClientObject.KeepAliveUntilNextConnection(); + } + } + #endregion + + #region TimeZone + public Task GetTimeZone(DateTime time) + { + var tz = TimeZoneInfo.Local; + var tzStanName = tz.StandardName; + + if (tzStanName == "CEST") + return Task.FromResult(MediusTimeZone.MediusTimeZone_CEST); + else if (tz.Id == "Swedish Standard Time") + return Task.FromResult(MediusTimeZone.MediusTimeZone_SWEDISHST); + else if (tz.Id == "FST") + return Task.FromResult(MediusTimeZone.MediusTimeZone_FST); + else if (tz.Id == "Central Africa Time") + return Task.FromResult(MediusTimeZone.MediusTimeZone_CAT); + else if (tzStanName == "South Africa Standard Time") + return Task.FromResult(MediusTimeZone.MediusTimeZone_SAST); + else if (tz.Id == "EET") + return Task.FromResult(MediusTimeZone.MediusTimeZone_EET); + else if (tz.Id == "Israel Standard Time") + return Task.FromResult(MediusTimeZone.MediusTimeZone_ISRAELST); + + return Task.FromResult(MediusTimeZone.MediusTimeZone_GMT); + } + #endregion + + #region ConvertFromIntegerToIpAddress + /// + /// Convert from Binary Ip Address to UInt + /// + /// Binary formatted IP Address + /// + public static string ConvertFromIntegerToIpAddress(uint ipAddress) + { + byte[] bytes = BitConverter.GetBytes(ipAddress); + + if (!BitConverter.IsLittleEndian) + Array.Reverse(bytes); + + return new IPAddress(bytes).ToString(); + } + #endregion + + #region ConvertFromIntegerToPort + /// + /// Convert from Binary Ip Address to UInt + /// + /// Binary formatted IP Address + /// + public static int ConvertFromIntegerToPort(string port) + { + int i = Convert.ToInt32(port, 2); + + // flip little-endian to big-endian(network order) + /* NOT NEEDED + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + */ + return i; + } + #endregion + + #region ConvertFromIntegerToIpAddress + /// + /// Convert from Binary Ip Address to UInt + /// + /// Binary formatted IP Address + /// + public static uint ConvertFromIpAddressToBinary(IPAddress ipAddress) + { + uint Uint = (uint)BitConverter.ToInt32(ipAddress.GetAddressBytes()); + + // flip little-endian to big-endian(network order) + /* NOT NEEDED + if (BitConverter.IsLittleEndian) + { + Array.Reverse(bytes); + } + */ + return Uint; + } + #endregion + + #region PokeEngine + + private void PokePatch(IChannel clientChannel, ChannelData data) + { + if (MediusClass.Settings.PokePatchOn) + { + if (File.Exists(Directory.GetCurrentDirectory() + $"/static/poke_config.json")) + { + try + { + JObject? jsonObject = JObject.Parse(File.ReadAllText(Directory.GetCurrentDirectory() + $"/static/poke_config.json")); + + foreach (JProperty? appProperty in jsonObject.Properties()) + { + string? appId = appProperty.Name; + + if (!string.IsNullOrEmpty(appId) && appId == data.ApplicationId.ToString()) + { + if (appProperty.Value is JObject innerObject) + { + foreach (JProperty? offsetProperty in innerObject.Properties()) + { + string? offset = offsetProperty.Name; + string? valuestr = offsetProperty.Value.ToString(); + + if (!string.IsNullOrEmpty(offset) && !string.IsNullOrEmpty(valuestr) && uint.TryParse(offset.Replace("0x", string.Empty), NumberStyles.HexNumber, null, out uint offsetValue) && uint.TryParse(valuestr, NumberStyles.Any, null, out uint hexValue)) + { + LoggerAccessor.LogInfo($"[MAS] - MemoryPoke sent to appid {appId} with infos : offset:{offset} - value:{valuestr}"); + Queue(new RT_MSG_SERVER_MEMORY_POKE() + { + start_Address = offsetValue, + Payload = BitConverter.GetBytes(hexValue), + SkipEncryption = true + + }, clientChannel); + } + else + LoggerAccessor.LogWarn($"[MAS] - MemoryPoke failed to convert json properties! Check your Json syntax."); + } + } + } + } + } + catch (Exception ex) + { + LoggerAccessor.LogWarn($"[MAS] - MemoryPoke failed to initialise! {ex}."); + } + } + else + LoggerAccessor.LogWarn($"[MAS] - No MemoryPoke config found."); + } + } + + private bool CheatQuery(uint address, int Length, IChannel? clientChannel, CheatQueryType Type = CheatQueryType.DME_SERVER_CHEAT_QUERY_RAW_MEMORY, int SequenceId = 1) + { + // address = 0, don't read + if (address == 0) + return false; + + // client channel is null, don't read + if (clientChannel == null) + return false; + + // read client memory + Queue(new RT_MSG_SERVER_CHEAT_QUERY() + { + QueryType = Type, + SequenceId = SequenceId, + StartAddress = address, + Length = Length, + }, clientChannel); + + // return read + return true; + } + + private bool PatchHttpsSVOCheck(uint patchLocation, IChannel? clientChannel) + { + // patch location = 0, don't patch + if (patchLocation == 0) + return false; + + // client channel is null, don't patch + if (clientChannel == null) + return false; + + // poke client memory + Queue(new RT_MSG_SERVER_MEMORY_POKE() + { + start_Address = patchLocation, + Payload = new byte[] { 0x3A, 0x2F }, // We patch to :/ instead of s: as the check only compare first 6 characters. + SkipEncryption = false, + }, clientChannel); + + // return patched + return true; + } + + private bool PokeAddress(uint patchLocation, byte[] Payload, IChannel? clientChannel) + { + // patch location = 0, don't patch + if (patchLocation == 0) + return false; + + // client channel is null, don't patch + if (clientChannel == null) + return false; + + // poke client memory + Queue(new RT_MSG_SERVER_MEMORY_POKE() + { + start_Address = patchLocation, + Payload = Payload, + SkipEncryption = false, + }, clientChannel); + + // return patched + return true; + } + #endregion + + #region SHA256 + + /// + /// Compute the SHA256 checksum of a string. + /// Calcul la somme des contr�les en SHA256 d'un string. + /// + /// The input string. + /// A string. + private static string ComputeSHA256(string input) + { + // ComputeHash - returns byte array + byte[] bytes = NetHasher.DotNetHasher.ComputeSHA256(Encoding.UTF8.GetBytes(input)); + + // Convert byte array to a string + StringBuilder builder = new(); + for (int i = 0; i < bytes.Length; i++) + builder.Append(bytes[i].ToString("x2")); + + return builder.ToString(); + } + #endregion + } +} diff --git a/Servers/SSFWServer/SSFWLogin.cs b/Servers/SSFWServer/SSFWLogin.cs index e30010d0e..48282ccaa 100644 --- a/Servers/SSFWServer/SSFWLogin.cs +++ b/Servers/SSFWServer/SSFWLogin.cs @@ -1,10 +1,9 @@ using CustomLogger; -using NetworkLibrary.Extension; -using System.Text; +using NetHasher; using System.Collections.Concurrent; +using System.Text; using WebAPIService.SSFW; using XI5; -using NetHasher; namespace SSFWServer { @@ -166,21 +165,25 @@ public SSFWLogin(string XHomeClientVersion, string generalsecret, string homeCli extractedData[i] = 0x48; } - XI5Ticket ticket = new XI5Ticket(ticketBuffer); - - if (ByteUtils.FindBytePattern(ticketBuffer, new byte[] { 0x52, 0x50, 0x43, 0x4E }, 184) != -1) - { - if (SSFWServerConfiguration.ForceOfficialRPCNSignature && !ticket.SignedByOfficialRPCN) - { - LoggerAccessor.LogError($"[SSFW] : User {Encoding.ASCII.GetString(extractedData).Replace("H", string.Empty)} was caught using an invalid RPCN signature!"); - return null; - } - - IsRPCN = true; - LoggerAccessor.LogInfo($"[SSFW] : User {Encoding.ASCII.GetString(extractedData).Replace("H", string.Empty)} logged in and is on RPCN"); - } - else - LoggerAccessor.LogInfo($"[SSFW] : {Encoding.ASCII.GetString(extractedData).Replace("H", string.Empty)} logged in and is on PSN"); + XI5Ticket ticket = XI5Ticket.ReadFromBytes(ticketBuffer); + + // invalid ticket + if (!ticket.Valid) + { + LoggerAccessor.LogWarn($"[SSFWLogin] - {ticket.Username} tried to alter their ticket data"); + return null; + } + + // RPCN + if (ticket.SignatureIdentifier == "RPCN") + { + IsRPCN = true; + LoggerAccessor.LogInfo($"[SSFWLogin] - {ticket.Username} logged in and is on RPCN"); + } + + // PSN + else + LoggerAccessor.LogInfo($"[SSFWLogin] - {ticket.Username} logged in and is on PSN"); (string, string) UserNames = new(); (string, string) ResultStrings = new(); @@ -233,7 +236,7 @@ public SSFWLogin(string XHomeClientVersion, string generalsecret, string homeCli if (!string.IsNullOrEmpty(UserNames.Item1) && !SSFWServerConfiguration.SSFWCrossSave) // RPCN confirmed. { - SSFWUserSessionManager.RegisterUser(UserNames.Item1, SessionIDs.Item1!, ResultStrings.Item1!, ticket.OnlineId.Length); + SSFWUserSessionManager.RegisterUser(UserNames.Item1, SessionIDs.Item1!, ResultStrings.Item1!, ticket.Username.Length); if (SSFWAccountManagement.AccountExists(UserNames.Item2, SessionIDs.Item2)) SSFWAccountManagement.CopyAccountProfile(UserNames.Item2, UserNames.Item1, SessionIDs.Item2, SessionIDs.Item1!, key); @@ -244,7 +247,7 @@ public SSFWLogin(string XHomeClientVersion, string generalsecret, string homeCli { IsRPCN = false; - SSFWUserSessionManager.RegisterUser(UserNames.Item2, SessionIDs.Item2, ResultStrings.Item2, ticket.OnlineId.Length); + SSFWUserSessionManager.RegisterUser(UserNames.Item2, SessionIDs.Item2, ResultStrings.Item2, ticket.Username.Length); } int logoncount = SSFWAccountManagement.ReadOrMigrateAccount(extractedData, IsRPCN ? UserNames.Item1 : UserNames.Item2, IsRPCN ? SessionIDs.Item1 : SessionIDs.Item2, key);