Initial commit for Public repository
This commit is contained in:
commit
47065e0b69
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
14
Lagrange.Core.Test/Lagrange.Core.Test.csproj
Normal file
14
Lagrange.Core.Test/Lagrange.Core.Test.csproj
Normal file
|
@ -0,0 +1,14 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lagrange.Core\Lagrange.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
19
Lagrange.Core.Test/Program.cs
Normal file
19
Lagrange.Core.Test/Program.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using System.Text.Json;
|
||||
using Lagrange.Core.Test.Tests;
|
||||
using Lagrange.Core.Utility.Binary.JceStruct;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
|
||||
|
||||
var jce = "10022C3C4C56235151536572766963652E436F6E666967507573685376632E4D61696E53657276616E746607507573685265717D000104050800010607507573685265711800010612436F6E666967507573682E507573685265711D000103DD0A10012D000103CB19000A0A160D33392E3135362E3132352E32302136B030014C5C600870018602746A9602636DAC0B0A160D33362E3135352E3136332E32352101BB30014C5C60087001860273689602636DAC0B0A160E33392E3135362E3134342E313133211F9030014C5C600870018602746A9602636DAC0B0A160E3132302E3233332E31372E313437205030014C5C600870018602737A9602636DAC0B0A160E33362E3135352E3230372E3233302136B030014C5C60087001860273689602636DAC0B0A160E33392E3135362E3132362E3137382101BB30014C5C600870018602746A9602636DAC0B0A160E3132302E3233322E33312E323530205030014C5C600870018602737A9602636DAC0B0A160D33362E3135352E3234352E3136205030014C5C60087001860273689602636DAC0B0A16116D7366776966692E33672E71712E636F6D211F9030014C5C60087C86066F746865727396066F7468657273AC0B0A160E33392E3135362E3132362E313738205030014C5C600870018602746A9602636DAC0B39000A0A160D33392E3135362E3132352E32302136B030014C5C600870018602746A9602636DAC0B0A160D33362E3135352E3136332E32352101BB30014C5C60087001860273689602636DAC0B0A160E33392E3135362E3134342E313133211F9030014C5C600870018602746A9602636DAC0B0A160E3132302E3233332E31372E313437205030014C5C600870018602737A9602636DAC0B0A160E33362E3135352E3230372E3233302136B030014C5C60087001860273689602636DAC0B0A160E33392E3135362E3132362E3137382101BB30014C5C600870018602746A9602636DAC0B0A160E3132302E3233322E33312E323530205030014C5C600870018602737A9602636DAC0B0A160D33362E3135352E3234352E3136205030014C5C60087001860273689602636DAC0B0A16116D7366776966692E33672E71712E636F6D211F9030014C5C60087C86066F746865727396066F7468657273AC0B0A160E33392E3135362E3132362E313738205030014C5C600870018602746A9602636DAC0B4C5C6C70018900020A160E33362E3135352E3231332E313832205030014C500360087C860273689602636DAC0B0A160F3132302E3233322E3133302E313032205030014C500360087C8602737A9602636DAC0B9900020A160E33362E3135352E3231332E313832205030014C500360087C860273689602636DAC0B0A160F3132302E3233322E3133302E313032205030014C500360087C8602737A9602636DAC0BA90CB90CC90CD90CE002FC0FF61014514449414F5F504F4C4943595F44454641554C54F01101330000000A8D41253D0B8C980CA80C".UnHex();
|
||||
var decoded = new UniPacket(jce);
|
||||
var data = decoded.Buffer;
|
||||
|
||||
File.WriteAllText("JceStruct.json", JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }));
|
||||
|
||||
await new NTLoginTest().LoginByPassword();
|
||||
// await new WtLoginTest().FetchQrCode();
|
||||
|
||||
public class Test
|
||||
{
|
||||
public string Name { get; set; } = "";
|
||||
}
|
24
Lagrange.Core.Test/Protobuf.cs
Normal file
24
Lagrange.Core.Test/Protobuf.cs
Normal file
|
@ -0,0 +1,24 @@
|
|||
using Lagrange.Core.Core.Packets.Login.Ecdh;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Test;
|
||||
|
||||
public class Protobuf
|
||||
{
|
||||
public void Test()
|
||||
{
|
||||
|
||||
var test = new SsoKeyExchange()
|
||||
{
|
||||
PubKey = new byte[] { 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11 },
|
||||
GcmCalc2 = new byte[] { 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11 },
|
||||
GcmCalc1 = new byte[] { 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11 },
|
||||
Timestamp = 23456789,
|
||||
Type = 1
|
||||
};
|
||||
using var stream = new MemoryStream();
|
||||
Serializer.Serialize(stream, test);
|
||||
Console.WriteLine(stream.ToArray().Hex(false, true));
|
||||
}
|
||||
}
|
32
Lagrange.Core.Test/Tests/BinaryTest.cs
Normal file
32
Lagrange.Core.Test/Tests/BinaryTest.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using Lagrange.Core.Utility.Binary;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
|
||||
namespace Lagrange.Core.Test.Tests;
|
||||
|
||||
public class BinaryTest
|
||||
{
|
||||
public class Binary
|
||||
{
|
||||
[BinaryProperty] public uint Value { get; set; } = 114514;
|
||||
|
||||
[BinaryProperty] public ulong Value2 { get; set; } = 1919810;
|
||||
}
|
||||
|
||||
public void Test()
|
||||
{
|
||||
var binary = new Binary();
|
||||
var bytes = BinarySerializer.Serialize(binary);
|
||||
Console.WriteLine(bytes.ToArray().Hex());
|
||||
Console.WriteLine(bytes.Length);
|
||||
|
||||
var binary2 = new BinaryPacket();
|
||||
binary2.WriteUint(114514, false);
|
||||
binary2.WriteUlong(1919810, false);
|
||||
Console.WriteLine(binary2.ToArray().Hex());
|
||||
|
||||
var newPacket = new BinaryPacket(bytes.ToArray());
|
||||
var binary3 = newPacket.Deserialize<Binary>();
|
||||
Console.WriteLine(binary3.Value);
|
||||
Console.WriteLine(binary3.Value2);
|
||||
}
|
||||
}
|
43
Lagrange.Core.Test/Tests/NTLoginTest.cs
Normal file
43
Lagrange.Core.Test/Tests/NTLoginTest.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Common.Interface;
|
||||
using Lagrange.Core.Common.Interface.Api;
|
||||
|
||||
namespace Lagrange.Core.Test.Tests;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
||||
public class NTLoginTest
|
||||
{
|
||||
public async Task LoginByPassword()
|
||||
{
|
||||
var deviceInfo = WtLoginTest.GetDeviceInfo();
|
||||
var keyStore = WtLoginTest.LoadKeystore();
|
||||
|
||||
if (keyStore == null)
|
||||
{
|
||||
Console.WriteLine("Please login by QrCode first");
|
||||
return;
|
||||
}
|
||||
|
||||
var bot = BotFactory.Create(new BotConfig()
|
||||
{
|
||||
UseIPv6Network = false,
|
||||
GetOptimumServer = true,
|
||||
AutoReconnect = true,
|
||||
Protocol = Protocols.Linux
|
||||
}, deviceInfo, keyStore);
|
||||
|
||||
bot.Invoker.OnBotLogEvent += (context, @event) =>
|
||||
{
|
||||
Console.WriteLine(@event.ToString());
|
||||
};
|
||||
|
||||
bot.Invoker.OnBotOnlineEvent += (context, @event) =>
|
||||
{
|
||||
Console.WriteLine(@event.ToString());
|
||||
WtLoginTest.SaveKeystore(bot.UpdateKeystore());
|
||||
};
|
||||
|
||||
await bot.LoginByPassword();
|
||||
}
|
||||
}
|
78
Lagrange.Core.Test/Tests/WtLoginTest.cs
Normal file
78
Lagrange.Core.Test/Tests/WtLoginTest.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Common.Interface;
|
||||
using Lagrange.Core.Common.Interface.Api;
|
||||
|
||||
namespace Lagrange.Core.Test.Tests;
|
||||
|
||||
public class WtLoginTest
|
||||
{
|
||||
public async Task FetchQrCode()
|
||||
{
|
||||
var deviceInfo = GetDeviceInfo();
|
||||
var keyStore = LoadKeystore() ?? throw new Exception();
|
||||
|
||||
var bot = BotFactory.Create(new BotConfig
|
||||
{
|
||||
UseIPv6Network = false,
|
||||
GetOptimumServer = true,
|
||||
AutoReconnect = true,
|
||||
Protocol = Protocols.Linux
|
||||
}, deviceInfo, keyStore);
|
||||
|
||||
bot.Invoker.OnBotLogEvent += (context, @event) =>
|
||||
{
|
||||
Console.WriteLine(@event.ToString());
|
||||
};
|
||||
|
||||
bot.Invoker.OnBotOnlineEvent += (context, @event) =>
|
||||
{
|
||||
Console.WriteLine(@event.ToString());
|
||||
SaveKeystore(bot.UpdateKeystore());
|
||||
};
|
||||
|
||||
var qrCode = await bot.FetchQrCode();
|
||||
if (qrCode != null)
|
||||
{
|
||||
await File.WriteAllBytesAsync("qr.png", qrCode);
|
||||
await bot.LoginByQrCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static BotDeviceInfo GetDeviceInfo()
|
||||
{
|
||||
if (File.Exists("Test/DeviceInfo.json"))
|
||||
{
|
||||
var info = JsonSerializer.Deserialize<BotDeviceInfo>(File.ReadAllText("Test/DeviceInfo.json"));
|
||||
if (info != null) return info;
|
||||
|
||||
info = BotDeviceInfo.GenerateInfo();
|
||||
File.WriteAllText("Test/DeviceInfo.json", JsonSerializer.Serialize(info));
|
||||
return info;
|
||||
}
|
||||
|
||||
var deviceInfo = BotDeviceInfo.GenerateInfo();
|
||||
File.WriteAllText("Test/DeviceInfo.json", JsonSerializer.Serialize(deviceInfo));
|
||||
return deviceInfo;
|
||||
}
|
||||
|
||||
public static void SaveKeystore(BotKeystore keystore) =>
|
||||
File.WriteAllText("Test/Keystore.json", JsonSerializer.Serialize(keystore));
|
||||
|
||||
public static BotKeystore? LoadKeystore()
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText("Test/Keystore.json");
|
||||
return JsonSerializer.Deserialize<BotKeystore>(text, new JsonSerializerOptions()
|
||||
{
|
||||
ReferenceHandler = ReferenceHandler.Preserve
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
61
Lagrange.Core.Test/Utility/ProtoGen.cs
Normal file
61
Lagrange.Core.Test/Utility/ProtoGen.cs
Normal file
|
@ -0,0 +1,61 @@
|
|||
using System.Reflection;
|
||||
using System.Text;
|
||||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Test.Utility;
|
||||
|
||||
internal static class ProtoGen
|
||||
{
|
||||
public static void GenerateProtoFiles()
|
||||
{
|
||||
var assembly = typeof(Lagrange.Core.Utility.ServiceInjector).Assembly;
|
||||
var types = assembly.GetTypes();
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine("syntax = \"proto3\";");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("package Lagrange.Core;");
|
||||
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.Namespace?.StartsWith("Lagrange.Core.Packets") != true) continue;
|
||||
|
||||
sb.AppendLine($"message {type.Name} {{");
|
||||
var properties = type.GetProperties();
|
||||
foreach (var property in properties)
|
||||
{
|
||||
string typeString = ParseType(property.PropertyType);
|
||||
sb.AppendLine($" {GetLastToken(typeString, '.')} {property.Name} = {property.GetCustomAttribute<ProtoMemberAttribute>()?.Tag};");
|
||||
}
|
||||
sb.AppendLine("}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
string proto = sb.ToString();
|
||||
|
||||
File.WriteAllText("packets.proto", proto);
|
||||
}
|
||||
|
||||
private static string ParseType(Type type)
|
||||
{
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
|
||||
{
|
||||
return $"repeated {ParseType(type.GetGenericArguments()[0])}";
|
||||
}
|
||||
|
||||
return type.ToString() switch
|
||||
{
|
||||
"System.UInt64" => "varint",
|
||||
"System.UInt32" => "varint",
|
||||
"System.UInt16" => "varint",
|
||||
"System.Int64" => "varint",
|
||||
"System.Int32" => "varint",
|
||||
"System.String" => "string",
|
||||
"System.Boolean" => "bool",
|
||||
"System.Byte[]" => "bytes",
|
||||
_ => type.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetLastToken(string str, char separator) => str.Split(separator)[^1];
|
||||
}
|
35
Lagrange.Core.Test/Utility/Tlv.cs
Normal file
35
Lagrange.Core.Test/Utility/Tlv.cs
Normal file
|
@ -0,0 +1,35 @@
|
|||
using System.Text;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
using static Lagrange.Core.Utility.Binary.BitConverter;
|
||||
|
||||
namespace Lagrange.Core.Test.Utility;
|
||||
|
||||
public static class Tlv
|
||||
{
|
||||
public static Dictionary<string, string> GetTlvDictionary(byte[] tlvs, bool isCommand = true)
|
||||
{
|
||||
var result = new Dictionary<string, string>();
|
||||
|
||||
using var reader = new BinaryReader(new MemoryStream(tlvs));
|
||||
|
||||
ushort command;
|
||||
if (isCommand)
|
||||
{
|
||||
command = ToUInt16(reader.ReadBytes(2), false);
|
||||
}
|
||||
|
||||
ushort tlvCount = ToUInt16(reader.ReadBytes(2), false);
|
||||
|
||||
for (int i = 0; i < tlvCount; i++)
|
||||
{
|
||||
ushort tlvTag = ToUInt16(reader.ReadBytes(2), false);
|
||||
ushort tlvLength = ToUInt16(reader.ReadBytes(2), false);
|
||||
byte[] tlvValue = reader.ReadBytes(tlvLength);
|
||||
|
||||
result.Add($"0x{tlvTag:X} {tlvLength}", tlvValue.Hex());
|
||||
result.Add($"0x{tlvTag:X} UTF8 {tlvLength}", Encoding.UTF8.GetString(tlvValue));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
34
Lagrange.Core.sln
Normal file
34
Lagrange.Core.sln
Normal file
|
@ -0,0 +1,34 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lagrange.Core", "Lagrange.Core\Lagrange.Core.csproj", "{909C99CC-0CB7-4A34-8C75-AD25E6AEA535}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lagrange.Core.Test", "Lagrange.Core.Test\Lagrange.Core.Test.csproj", "{D64B6BAB-CD20-4660-8A6E-BCC936652204}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lagrange.OneBot", "Lagrange.OneBot\Lagrange.OneBot.csproj", "{37AEDD3B-9B9F-4782-ADD5-BA2436FB2507}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lagrange.Signature", "Lagrange.Signature\Lagrange.Signature.csproj", "{FE894A94-407C-4C8D-AE50-1758EBAB6E8C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{909C99CC-0CB7-4A34-8C75-AD25E6AEA535}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{909C99CC-0CB7-4A34-8C75-AD25E6AEA535}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{909C99CC-0CB7-4A34-8C75-AD25E6AEA535}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{909C99CC-0CB7-4A34-8C75-AD25E6AEA535}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D64B6BAB-CD20-4660-8A6E-BCC936652204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D64B6BAB-CD20-4660-8A6E-BCC936652204}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D64B6BAB-CD20-4660-8A6E-BCC936652204}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{D64B6BAB-CD20-4660-8A6E-BCC936652204}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{37AEDD3B-9B9F-4782-ADD5-BA2436FB2507}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{37AEDD3B-9B9F-4782-ADD5-BA2436FB2507}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{37AEDD3B-9B9F-4782-ADD5-BA2436FB2507}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{37AEDD3B-9B9F-4782-ADD5-BA2436FB2507}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FE894A94-407C-4C8D-AE50-1758EBAB6E8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FE894A94-407C-4C8D-AE50-1758EBAB6E8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FE894A94-407C-4C8D-AE50-1758EBAB6E8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FE894A94-407C-4C8D-AE50-1758EBAB6E8C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
4
Lagrange.Core/AssemblyInfo.cs
Normal file
4
Lagrange.Core/AssemblyInfo.cs
Normal file
|
@ -0,0 +1,4 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Lagrange.Core.Test")] // Allow unit test to access internal members
|
||||
[assembly: InternalsVisibleTo("Lagrange.OneBot")] // OneBot Implementation
|
41
Lagrange.Core/BotContext.cs
Normal file
41
Lagrange.Core/BotContext.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Context;
|
||||
using Lagrange.Core.Core.Event;
|
||||
|
||||
namespace Lagrange.Core;
|
||||
|
||||
public class BotContext : IDisposable
|
||||
{
|
||||
public readonly EventInvoker Invoker;
|
||||
|
||||
internal readonly Utility.TaskScheduler Scheduler;
|
||||
|
||||
internal readonly ContextCollection ContextCollection;
|
||||
|
||||
private readonly BotAppInfo _appInfo;
|
||||
|
||||
private readonly BotConfig _config;
|
||||
|
||||
private readonly BotDeviceInfo _deviceInfo;
|
||||
|
||||
private readonly BotKeystore _keystore;
|
||||
|
||||
internal BotContext(BotConfig config, BotDeviceInfo deviceInfo, BotKeystore keystore)
|
||||
{
|
||||
Invoker = new EventInvoker(this);
|
||||
Scheduler = new Utility.TaskScheduler();
|
||||
|
||||
_config = config;
|
||||
_appInfo = BotAppInfo.ProtocolToAppInfo[config.Protocol];
|
||||
_deviceInfo = deviceInfo;
|
||||
_keystore = keystore;
|
||||
|
||||
ContextCollection = new ContextCollection(keystore, _appInfo, deviceInfo, Invoker, Scheduler);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Invoker.Dispose();
|
||||
}
|
||||
}
|
111
Lagrange.Core/Common/BotAppInfo.cs
Normal file
111
Lagrange.Core/Common/BotAppInfo.cs
Normal file
|
@ -0,0 +1,111 @@
|
|||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Common;
|
||||
|
||||
public class BotAppInfo
|
||||
{
|
||||
public string Os { get; private set; }
|
||||
|
||||
public string BaseVersion { get; private set; }
|
||||
|
||||
public string CurrentVersion { get; private set; }
|
||||
|
||||
public int BuildVersion { get; private set; }
|
||||
|
||||
public int MiscBitmap { get; private set; }
|
||||
|
||||
public string PtVersion { get; private set; }
|
||||
|
||||
public int PtOsVersion { get; private set; }
|
||||
|
||||
public string PackageName { get; private set; }
|
||||
|
||||
public string WtLoginSdk { get; private set; }
|
||||
|
||||
/// <summary>Or Known as QUA</summary>
|
||||
public string PackageSign { get; private set; }
|
||||
|
||||
public int UnknownIdentifier { get; private set; }
|
||||
|
||||
public int AppId { get; private set; }
|
||||
|
||||
/// <summary>Or known as pubId in tencent log</summary>
|
||||
public int SubAppId { get; private set; }
|
||||
|
||||
public int AppIdQrCode { get; private set; }
|
||||
|
||||
public ushort AppClientVersion { get; private set; }
|
||||
|
||||
public uint MainSigMap { get; private set; }
|
||||
|
||||
public ushort SubSigMap { get; private set; }
|
||||
|
||||
private static readonly BotAppInfo Linux = new()
|
||||
{
|
||||
Os = "Linux",
|
||||
|
||||
BaseVersion = "3.1.1-11223",
|
||||
CurrentVersion = "3.1.2-13107",
|
||||
BuildVersion = 13107,
|
||||
MiscBitmap = 32764,
|
||||
PtVersion = "2.0.0",
|
||||
PtOsVersion = 19,
|
||||
PackageName = "com.tencent.qq",
|
||||
WtLoginSdk = "nt.wtlogin.0.0.1",
|
||||
PackageSign = "V1_LNX_NQ_3.1.2-13107_RDM_B",
|
||||
|
||||
UnknownIdentifier = 70644224,
|
||||
AppId = 1600001615,
|
||||
SubAppId = 537146866,
|
||||
AppIdQrCode = 13697054,
|
||||
AppClientVersion = 13172,
|
||||
|
||||
MainSigMap = 169742560,
|
||||
SubSigMap = 0
|
||||
};
|
||||
|
||||
private static readonly BotAppInfo MacOs = new()
|
||||
{
|
||||
Os = "MacOS",
|
||||
|
||||
BaseVersion = "6.9.17-12118",
|
||||
CurrentVersion = "6.9.17-12118",
|
||||
BuildVersion = 12118,
|
||||
PtVersion = "2.0.0",
|
||||
MiscBitmap = 32764,
|
||||
PtOsVersion = 23,
|
||||
PackageName = "com.tencent.qqdesktop",
|
||||
WtLoginSdk = "nt.wtlogin.0.0.1",
|
||||
PackageSign = "V1_MAC_NQ_6.9.17-12118_RDM_B",
|
||||
|
||||
AppId = 1600001602,
|
||||
AppIdQrCode = 13697054,
|
||||
AppClientVersion = 13172
|
||||
};
|
||||
|
||||
private static readonly BotAppInfo Windows = new()
|
||||
{
|
||||
Os = "Windows",
|
||||
|
||||
BaseVersion = "9.8.3-13183",
|
||||
CurrentVersion = "9.8.3-13183",
|
||||
BuildVersion = 13183,
|
||||
PtVersion = "2.0.0",
|
||||
MiscBitmap = 32764,
|
||||
PtOsVersion = 23,
|
||||
PackageName = "com.tencent.qq",
|
||||
WtLoginSdk = "nt.wtlogin.0.0.1",
|
||||
PackageSign = "V1_WIN_NQ_9.8.3-13183_RDM_B",
|
||||
|
||||
AppId = 1600001604,
|
||||
AppIdQrCode = 13697054,
|
||||
AppClientVersion = 13172
|
||||
};
|
||||
|
||||
public static readonly Dictionary<Protocols, BotAppInfo> ProtocolToAppInfo = new()
|
||||
{
|
||||
{ Protocols.Windows, Windows },
|
||||
{ Protocols.Linux, Linux },
|
||||
{ Protocols.MacOs, MacOs },
|
||||
};
|
||||
}
|
38
Lagrange.Core/Common/BotConfig.cs
Normal file
38
Lagrange.Core/Common/BotConfig.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
namespace Lagrange.Core.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration for The bot client
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class BotConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The protocol for the client, default is Linux
|
||||
/// </summary>
|
||||
public Protocols Protocol { get; set; } = Protocols.Linux;
|
||||
|
||||
/// <summary>
|
||||
/// Auto reconnect to server when disconnected
|
||||
/// </summary>
|
||||
public bool AutoReconnect { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Use the IPv6 to connect to server, only if your network support IPv6
|
||||
/// </summary>
|
||||
public bool UseIPv6Network { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Get optimum server from Tencent MSF server, set to false to use hardcode server
|
||||
/// </summary>
|
||||
public bool GetOptimumServer { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Protocol for the client
|
||||
/// </summary>
|
||||
public enum Protocols
|
||||
{
|
||||
Windows = 0,
|
||||
MacOs = 1,
|
||||
Linux = 2
|
||||
}
|
28
Lagrange.Core/Common/BotDeviceInfo.cs
Normal file
28
Lagrange.Core/Common/BotDeviceInfo.cs
Normal file
|
@ -0,0 +1,28 @@
|
|||
using Lagrange.Core.Utility.Generator;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Common;
|
||||
|
||||
[Serializable]
|
||||
public class BotDeviceInfo
|
||||
{
|
||||
public Guid Guid { get; set; }
|
||||
|
||||
public byte[] MacAddress { get; set; }
|
||||
|
||||
public string DeviceName { get; set; }
|
||||
|
||||
public string SystemKernel { get; set; }
|
||||
|
||||
public string KernelVersion { get; set; }
|
||||
|
||||
public static BotDeviceInfo GenerateInfo() => new()
|
||||
{
|
||||
Guid = Guid.NewGuid(),
|
||||
MacAddress = ByteGen.GenRandomBytes(6),
|
||||
DeviceName = $"Lagrange-{StringGen.GenerateHex(6).ToUpper()}",
|
||||
SystemKernel = "Windows 10.0.19042",
|
||||
KernelVersion = "10.0.19042.0"
|
||||
};
|
||||
}
|
110
Lagrange.Core/Common/BotKeystore.cs
Normal file
110
Lagrange.Core/Common/BotKeystore.cs
Normal file
|
@ -0,0 +1,110 @@
|
|||
using System.Text;
|
||||
using System.Text.Json.Serialization;
|
||||
using Lagrange.Core.Utility.Crypto;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
using Lagrange.Core.Utility.Generator;
|
||||
|
||||
|
||||
namespace Lagrange.Core.Common;
|
||||
|
||||
public class BotKeystore // TODO: 你自己不恶心吗,穿件衣服吧你
|
||||
{
|
||||
[JsonConstructor]
|
||||
#pragma warning disable CS8618
|
||||
public BotKeystore()
|
||||
#pragma warning restore CS8618
|
||||
{
|
||||
SecpImpl = new EcdhImpl(EcdhImpl.CryptMethod.Secp192K1);
|
||||
PrimeImpl = new EcdhImpl(EcdhImpl.CryptMethod.Prime256V1, false);
|
||||
TeaImpl = new TeaImpl();
|
||||
|
||||
Stub = new KeyCollection();
|
||||
|
||||
var tempPwd = Session?.TempPassword;
|
||||
Session = tempPwd != null ? new WtLoginSession { TempPassword = tempPwd } : new WtLoginSession();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create the Bot keystore
|
||||
/// </summary>
|
||||
/// <param name="uin">Set this field 0 to use QrCode Login</param>
|
||||
/// <param name="password">Password Raw</param>
|
||||
internal BotKeystore(uint uin, string password)
|
||||
{
|
||||
Uin = uin;
|
||||
PasswordMd5 = Encoding.UTF8.GetBytes(password).Md5();
|
||||
|
||||
SecpImpl = new EcdhImpl(EcdhImpl.CryptMethod.Secp192K1);
|
||||
PrimeImpl = new EcdhImpl(EcdhImpl.CryptMethod.Prime256V1, false);
|
||||
TeaImpl = new TeaImpl();
|
||||
Session = new WtLoginSession();
|
||||
|
||||
Stub = new KeyCollection();
|
||||
}
|
||||
|
||||
public uint Uin { get; set; }
|
||||
|
||||
public string? Uid { get; set; }
|
||||
|
||||
public string PasswordMd5 { get; set; }
|
||||
|
||||
internal EcdhImpl SecpImpl { get; set; }
|
||||
internal EcdhImpl PrimeImpl { get; set; }
|
||||
internal TeaImpl TeaImpl { get; set; }
|
||||
|
||||
internal KeyCollection Stub { get; }
|
||||
|
||||
public WtLoginSession Session { get; set; }
|
||||
|
||||
public BotInfo? Info { get; internal set; }
|
||||
|
||||
[Serializable]
|
||||
public class KeyCollection
|
||||
{
|
||||
public byte[] RandomKey { get; set; } = ByteGen.GenRandomBytes(16);
|
||||
public byte[] TgtgtKey { get; set; } = new byte[16];
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class WtLoginSession
|
||||
{
|
||||
internal byte[] D2Key { get; set; } = new byte[16];
|
||||
internal byte[] D2 { get; set; } = Array.Empty<byte>();
|
||||
internal byte[] Tgt { get; set; } = Array.Empty<byte>();
|
||||
|
||||
internal byte[]? QrSign { get; set; } // size: 24
|
||||
internal byte[]? KeySign { get; set; }
|
||||
internal byte[]? UnusualSign { get; set; }
|
||||
internal string? QrString { get; set; }
|
||||
|
||||
internal byte[]? ExchangeKey { get; set; }
|
||||
|
||||
public byte[]? TempPassword { get; set; }
|
||||
internal byte[]? NoPicSig { get; set; } // size: 16, may be from Tlv19, for Tlv16A
|
||||
|
||||
private ushort _sequence;
|
||||
internal ushort Sequence
|
||||
{
|
||||
get => _sequence++;
|
||||
set => _sequence = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class BotInfo
|
||||
{
|
||||
internal BotInfo(byte age, byte gender, string name)
|
||||
{
|
||||
Age = age;
|
||||
Gender = gender;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public byte Age { get; }
|
||||
|
||||
public byte Gender { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public override string ToString() => $"Bot name: {Name} | Gender: {Gender} | Age: {Age}";
|
||||
}
|
||||
}
|
86
Lagrange.Core/Common/BotScheduler.cs
Normal file
86
Lagrange.Core/Common/BotScheduler.cs
Normal file
|
@ -0,0 +1,86 @@
|
|||
// ReSharper disable UnusedType.Global
|
||||
// ReSharper disable UnusedMember.Global
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Global
|
||||
// ReSharper disable UnusedAutoPropertyAccessor.Local
|
||||
|
||||
namespace Lagrange.Core.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Task Scheduler
|
||||
/// </summary>
|
||||
public class Scheduler
|
||||
{
|
||||
public const int Infinity = int.MaxValue;
|
||||
|
||||
private BotContext Bot { get; }
|
||||
|
||||
private Utility.TaskScheduler Instance { get; }
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public Action<BotContext> Action { get; }
|
||||
|
||||
internal Scheduler(BotContext bot, string name, Action<BotContext> action)
|
||||
{
|
||||
Bot = bot;
|
||||
Name = name;
|
||||
Action = action;
|
||||
Instance = bot.Scheduler;
|
||||
}
|
||||
|
||||
~Scheduler() => Cancel();
|
||||
|
||||
/// <summary>
|
||||
/// Create a task scheduler
|
||||
/// </summary>
|
||||
/// <param name="bot"><b>[In]</b> Bot instance</param>
|
||||
/// <param name="name"><b>[In]</b> Task identity name</param>
|
||||
/// <param name="action"><b>[In]</b> Task callback action</param>
|
||||
/// <returns></returns>
|
||||
public static Scheduler Create(BotContext bot, string name, Action<BotContext> action) => new(bot, name, action);
|
||||
|
||||
/// <summary>
|
||||
/// Execute the task with a specific interval
|
||||
/// </summary>
|
||||
/// <param name="interval"><b>[In]</b> Interval in milliseconds</param>
|
||||
/// <param name="times"><b>[In]</b> Execute times</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public void Interval(int interval, int times) => Instance.Interval(Name, interval, times, () => Action(Bot));
|
||||
|
||||
/// <summary>
|
||||
/// Execute the task with a specific interval infinity
|
||||
/// </summary>
|
||||
/// <param name="interval"><b>[In]</b> Interval in milliseconds</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public void Interval(int interval) => Instance.Interval(Name, interval, Infinity, () => Action(Bot));
|
||||
|
||||
/// <summary>
|
||||
/// Execute the task once
|
||||
/// </summary>
|
||||
/// <param name="delay"><b>[In]</b> Delay time in milliseconds</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public void RunOnce(int delay) => Instance.RunOnce(Name, delay, () => Action(Bot));
|
||||
|
||||
/// <summary>
|
||||
/// Execute the task once
|
||||
/// </summary>
|
||||
/// <param name="date"><b>[In]</b> Execute date</param>
|
||||
/// <exception cref="ArgumentNullException"></exception>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public void RunOnce(DateTime date) => Instance.RunOnce(Name, date, () => Action(Bot));
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a task to run
|
||||
/// </summary>
|
||||
public void Trigger() => Instance.Trigger(Name);
|
||||
|
||||
/// <summary>
|
||||
/// Cancel the task
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException"></exception>
|
||||
public void Cancel() => Instance.Cancel(Name);
|
||||
}
|
22
Lagrange.Core/Common/Interface/Api/BotExt.cs
Normal file
22
Lagrange.Core/Common/Interface/Api/BotExt.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
namespace Lagrange.Core.Common.Interface.Api;
|
||||
|
||||
public static class BotExt
|
||||
{
|
||||
public static async Task<byte[]?> FetchQrCode(this BotContext bot)
|
||||
=> await bot.ContextCollection.Business.WtExchangeLogic.FetchQrCode();
|
||||
|
||||
/// <summary>
|
||||
/// Use this method to login by QrCode, you should call <see cref="FetchQrCode"/> first
|
||||
/// </summary>
|
||||
public static async Task LoginByQrCode(this BotContext bot)
|
||||
=> await bot.ContextCollection.Business.WtExchangeLogic.LoginByQrCode();
|
||||
|
||||
/// <summary>
|
||||
/// Use this method to login by password, EasyLogin may be preformed if there is sig in <see cref="BotKeystore"/>
|
||||
/// </summary>
|
||||
public static async Task<bool> LoginByPassword(this BotContext bot)
|
||||
=> await bot.ContextCollection.Business.WtExchangeLogic.LoginByPassword();
|
||||
|
||||
public static BotKeystore UpdateKeystore(this BotContext bot)
|
||||
=> bot.ContextCollection.Keystore;
|
||||
}
|
7
Lagrange.Core/Common/Interface/BotFactory.cs
Normal file
7
Lagrange.Core/Common/Interface/BotFactory.cs
Normal file
|
@ -0,0 +1,7 @@
|
|||
namespace Lagrange.Core.Common.Interface;
|
||||
|
||||
public static class BotFactory
|
||||
{
|
||||
public static BotContext Create(BotConfig config, BotDeviceInfo deviceInfo, BotKeystore keystore) =>
|
||||
new(config, deviceInfo, keystore);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
namespace Lagrange.Core.Core.Context.Attributes;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
internal class BusinessLogicAttribute : Attribute
|
||||
{
|
||||
public string Name { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
public BusinessLogicAttribute(string name, string description)
|
||||
{
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
}
|
135
Lagrange.Core/Core/Context/BusinessContext.cs
Normal file
135
Lagrange.Core/Core/Context/BusinessContext.cs
Normal file
|
@ -0,0 +1,135 @@
|
|||
using System.Reflection;
|
||||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Context.Attributes;
|
||||
using Lagrange.Core.Core.Context.Logic;
|
||||
using Lagrange.Core.Core.Context.Logic.Implementation;
|
||||
using Lagrange.Core.Core.Event.Protocol;
|
||||
using Lagrange.Core.Core.Service;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
internal class BusinessContext : ContextBase
|
||||
{
|
||||
private const string Tag = nameof(BusinessContext);
|
||||
|
||||
private readonly Dictionary<Type, List<LogicBase>> _businessLogics;
|
||||
|
||||
#region Business Logics
|
||||
|
||||
internal MessagingLogic MessagingLogic { get; private set; }
|
||||
|
||||
internal WtExchangeLogic WtExchangeLogic { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public BusinessContext(ContextCollection collection, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(collection, keystore, appInfo, device)
|
||||
{
|
||||
_businessLogics = new Dictionary<Type, List<LogicBase>>();
|
||||
|
||||
RegisterLogics();
|
||||
}
|
||||
|
||||
private void RegisterLogics()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
foreach (var logic in assembly.GetTypeByAttributes<BusinessLogicAttribute>(out _))
|
||||
{
|
||||
var constructor = logic.GetConstructors(BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
var instance = (LogicBase)constructor[0].Invoke(new object[] { Collection });
|
||||
|
||||
foreach (var @event in logic.GetCustomAttributes<EventSubscribeAttribute>())
|
||||
{
|
||||
if (!_businessLogics.TryGetValue(@event.EventType, out var list))
|
||||
{
|
||||
list = new List<LogicBase>();
|
||||
_businessLogics.Add(@event.EventType, list);
|
||||
}
|
||||
list.Add(instance); // Append logics
|
||||
}
|
||||
|
||||
switch (instance)
|
||||
{
|
||||
case WtExchangeLogic wtExchangeLogic:
|
||||
WtExchangeLogic = wtExchangeLogic;
|
||||
break;
|
||||
case MessagingLogic messagingLogic:
|
||||
MessagingLogic = messagingLogic;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> PushEvent(ProtocolEvent @event)
|
||||
{
|
||||
try
|
||||
{
|
||||
var packets = Collection.Service.ResolvePacketByEvent(@event);
|
||||
foreach (var packet in packets) await Collection.Packet.PostPacket(packet);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send Event to the Server, goes through the given context
|
||||
/// </summary>
|
||||
public async Task<List<ProtocolEvent>> SendEvent(ProtocolEvent @event)
|
||||
{
|
||||
var result = new List<ProtocolEvent>();
|
||||
|
||||
try
|
||||
{
|
||||
var packets = Collection.Service.ResolvePacketByEvent(@event);
|
||||
foreach (var packet in packets)
|
||||
{
|
||||
var returnVal = await Collection.Packet.SendPacket(packet);
|
||||
var resolved = Collection.Service.ResolveEventByPacket(returnVal);
|
||||
foreach (var protocol in resolved)
|
||||
{
|
||||
await HandleIncomingEvent(protocol);
|
||||
result.Add(protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Collection.Log.LogWarning(Tag, $"Error when processing the event: {@event}");
|
||||
Collection.Log.LogWarning(Tag, e.Message);
|
||||
if (e.StackTrace != null) Collection.Log.LogWarning(Tag, e.StackTrace);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<bool> HandleIncomingEvent(ProtocolEvent @event)
|
||||
{
|
||||
_businessLogics.TryGetValue(typeof(ProtocolEvent), out var baseLogics);
|
||||
_businessLogics.TryGetValue(@event.GetType(), out var normalLogics);
|
||||
|
||||
var logics = new List<LogicBase>();
|
||||
if (baseLogics != null) logics.AddRange(baseLogics);
|
||||
if (normalLogics != null) logics.AddRange(normalLogics);
|
||||
|
||||
foreach (var logic in logics)
|
||||
{
|
||||
try
|
||||
{
|
||||
await logic.Incoming(@event);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Collection.Log.LogFatal(Tag, $"Error occurred while handling event {@event.GetType().Name}");
|
||||
Collection.Log.LogFatal(Tag, e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
22
Lagrange.Core/Core/Context/ContextBase.cs
Normal file
22
Lagrange.Core/Core/Context/ContextBase.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using Lagrange.Core.Common;
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
internal abstract class ContextBase
|
||||
{
|
||||
protected readonly ContextCollection Collection;
|
||||
|
||||
protected readonly BotKeystore Keystore;
|
||||
|
||||
protected readonly BotAppInfo AppInfo;
|
||||
|
||||
protected readonly BotDeviceInfo DeviceInfo;
|
||||
|
||||
protected ContextBase(ContextCollection collection, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
{
|
||||
Collection = collection;
|
||||
Keystore = keystore;
|
||||
AppInfo = appInfo;
|
||||
DeviceInfo = device;
|
||||
}
|
||||
}
|
38
Lagrange.Core/Core/Context/ContextCollection.cs
Normal file
38
Lagrange.Core/Core/Context/ContextCollection.cs
Normal file
|
@ -0,0 +1,38 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Event;
|
||||
using TaskScheduler = Lagrange.Core.Utility.TaskScheduler;
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
internal class ContextCollection
|
||||
{
|
||||
public PacketContext Packet { get; }
|
||||
public SocketContext Socket { get; }
|
||||
public ServiceContext Service { get; }
|
||||
public BusinessContext Business { get; }
|
||||
public LogContext Log { get; }
|
||||
|
||||
public BotKeystore Keystore { get; }
|
||||
public BotAppInfo AppInfo { get; }
|
||||
public BotDeviceInfo Device { get; }
|
||||
|
||||
public TaskScheduler Scheduler { get; }
|
||||
public EventInvoker Invoker { get; }
|
||||
|
||||
public ContextCollection(BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, EventInvoker invoker,
|
||||
TaskScheduler scheduler)
|
||||
{
|
||||
Packet = new PacketContext(this, keystore, appInfo, device);
|
||||
Socket = new SocketContext(this, keystore, appInfo, device);
|
||||
Service = new ServiceContext(this, keystore, appInfo, device);
|
||||
Business = new BusinessContext(this, keystore, appInfo, device);
|
||||
Log = new LogContext(this, keystore, appInfo, device, invoker);
|
||||
|
||||
Keystore = keystore;
|
||||
AppInfo = appInfo;
|
||||
Device = device;
|
||||
|
||||
Scheduler = scheduler;
|
||||
Invoker = invoker;
|
||||
}
|
||||
}
|
34
Lagrange.Core/Core/Context/LogContext.cs
Normal file
34
Lagrange.Core/Core/Context/LogContext.cs
Normal file
|
@ -0,0 +1,34 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Event;
|
||||
using Lagrange.Core.Core.Event.EventArg;
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
/// <summary>
|
||||
/// Log context, all the logs will be dispatched to this context and then to the <see cref="Lagrange.Core.Core.Event.EventArg.BotLogEvent"/>.
|
||||
/// </summary>
|
||||
internal class LogContext : ContextBase
|
||||
{
|
||||
private readonly EventInvoker _invoker;
|
||||
|
||||
public LogContext(ContextCollection collection, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device, EventInvoker invoker)
|
||||
: base(collection, keystore, appInfo, device) => _invoker = invoker;
|
||||
|
||||
public void LogDebug(string tag, string message) =>
|
||||
_invoker.PostEvent(new BotLogEvent(tag, LogLevel.Debug, message));
|
||||
|
||||
public void LogVerbose(string tag, string message) =>
|
||||
_invoker.PostEvent(new BotLogEvent(tag, LogLevel.Verbose, message));
|
||||
|
||||
public void LogInfo(string tag, string message) =>
|
||||
_invoker.PostEvent(new BotLogEvent(tag, LogLevel.Information, message));
|
||||
|
||||
public void LogWarning(string tag, string message) =>
|
||||
_invoker.PostEvent(new BotLogEvent(tag, LogLevel.Warning, message));
|
||||
|
||||
public void LogFatal(string tag, string message) =>
|
||||
_invoker.PostEvent(new BotLogEvent(tag, LogLevel.Fatal, message));
|
||||
|
||||
public void Log(string tag, LogLevel level, string message) =>
|
||||
_invoker.PostEvent(new BotLogEvent(tag, level, message));
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using Lagrange.Core.Core.Context.Attributes;
|
||||
using Lagrange.Core.Core.Event.Protocol;
|
||||
using Lagrange.Core.Core.Event.Protocol.Message;
|
||||
using Lagrange.Core.Core.Service;
|
||||
|
||||
namespace Lagrange.Core.Core.Context.Logic.Implementation;
|
||||
|
||||
[EventSubscribe(typeof(PushMessageEvent))]
|
||||
[EventSubscribe(typeof(SendMessageEvent))]
|
||||
[BusinessLogic("MessagingLogic", "Manage the receiving and sending of messages")]
|
||||
internal class MessagingLogic : LogicBase
|
||||
{
|
||||
private const string Tag = nameof(MessagingLogic);
|
||||
|
||||
internal MessagingLogic(ContextCollection collection) : base(collection) { }
|
||||
|
||||
public override async Task Incoming(ProtocolEvent e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case PushMessageEvent push:
|
||||
Collection.Log.LogInfo(Tag, "Message Received, Detail to be implemented");
|
||||
break;
|
||||
case SendMessageEvent send:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,239 @@
|
|||
using System.Text.Json;
|
||||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Context.Attributes;
|
||||
using Lagrange.Core.Core.Event.EventArg;
|
||||
using Lagrange.Core.Core.Event.Protocol;
|
||||
using Lagrange.Core.Core.Event.Protocol.Login;
|
||||
using Lagrange.Core.Core.Event.Protocol.Login.Ecdh;
|
||||
using Lagrange.Core.Core.Event.Protocol.System;
|
||||
using Lagrange.Core.Core.Packets.Login.NTLogin;
|
||||
using Lagrange.Core.Core.Packets.Login.WtLogin.Entity;
|
||||
using Lagrange.Core.Core.Service;
|
||||
using Lagrange.Core.Utility.Crypto;
|
||||
using Lagrange.Core.Utility.Network;
|
||||
|
||||
// ReSharper disable AsyncVoidLambda
|
||||
|
||||
namespace Lagrange.Core.Core.Context.Logic.Implementation;
|
||||
|
||||
[EventSubscribe(typeof(TransEmpEvent))]
|
||||
[EventSubscribe(typeof(LoginEvent))]
|
||||
[EventSubscribe(typeof(KickNTEvent))]
|
||||
[BusinessLogic("WtExchangeLogic", "Manage the online task of the Bot")]
|
||||
internal class WtExchangeLogic : LogicBase
|
||||
{
|
||||
private const string Tag = nameof(WtExchangeLogic);
|
||||
|
||||
private const string Interface = "https://ntlogin.qq.com/qr/getFace";
|
||||
|
||||
private const string QueryEvent = "wtlogin.trans_emp CMD0x12";
|
||||
|
||||
internal WtExchangeLogic(ContextCollection collection) : base(collection) { }
|
||||
|
||||
public override async Task Incoming(ProtocolEvent e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case KickNTEvent kick:
|
||||
Collection.Log.LogFatal(Tag, $"KickNTEvent: {kick.Tag}: {kick.Message}");
|
||||
Collection.Log.LogFatal(Tag, "Bot will be offline in 5 seconds...");
|
||||
await Task.Delay(5000);
|
||||
|
||||
Collection.Invoker.PostEvent(new BotOfflineEvent()); // TODO: Fill in the reason of offline
|
||||
Collection.Scheduler.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <para>1. resolve wtlogin.trans_emp CMD0x31 packet</para>
|
||||
/// <para>2. Schedule wtlogin.trans_emp CMD0x12 Task</para>
|
||||
/// </summary>
|
||||
public async Task<byte[]?> FetchQrCode()
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "Connecting Servers...");
|
||||
if (!await Collection.Socket.Connect()) return null;
|
||||
Collection.Scheduler.Interval("Heartbeat.Alive", 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create()));
|
||||
|
||||
var transEmp = TransEmpEvent.Create(TransEmpEvent.State.FetchQrCode);
|
||||
var result = await Collection.Business.SendEvent(transEmp);
|
||||
|
||||
if (result.Count != 0)
|
||||
{
|
||||
var @event = (TransEmpEvent)result[0];
|
||||
Collection.Keystore.Session.QrString = @event.QrSig;
|
||||
Collection.Keystore.Session.QrSign = @event.Signature;
|
||||
|
||||
Collection.Log.LogInfo(Tag, $"QrCode Fetched, Expiration: {@event.Expiration} seconds");
|
||||
return @event.QrCode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task LoginByQrCode() =>
|
||||
await Task.Run(() => Collection.Scheduler.Interval(QueryEvent, 2 * 1000, async () => await QueryQrCodeState()));
|
||||
|
||||
public async Task<bool> LoginByPassword()
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "Trying to Login by Keystore and Password...");
|
||||
|
||||
if (!Collection.Socket.Connected) // if socket not connected, try to connect
|
||||
{
|
||||
if (!await Collection.Socket.Connect()) return false;
|
||||
Collection.Scheduler.Interval("Heartbeat.Alive", 10 * 1000, async () => await Collection.Business.PushEvent(AliveEvent.Create()));
|
||||
}
|
||||
|
||||
var keyExchangeEvent = KeyExchangeEvent.Create();
|
||||
var exchangeResult = await Collection.Business.SendEvent(keyExchangeEvent);
|
||||
if (exchangeResult.Count != 0)
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "Key Exchange successfully!");
|
||||
|
||||
if (Collection.Keystore.Session.TempPassword != null) // try EasyLogin
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "Trying to Login by EasyLogin...");
|
||||
var easyLoginEvent = EasyLoginEvent.Create();
|
||||
var easyLoginResult = await Collection.Business.SendEvent(easyLoginEvent);
|
||||
|
||||
if (easyLoginResult.Count != 0)
|
||||
{
|
||||
var @event = (EasyLoginEvent)easyLoginResult[0];
|
||||
if (@event is { Success: true, UnusualVerify: false })
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "Login Success");
|
||||
|
||||
var onlineEvent = new BotOnlineEvent();
|
||||
Collection.Invoker.PostEvent(onlineEvent);
|
||||
|
||||
var registerEvent = StatusRegisterEvent.Create();
|
||||
var registerResponse = await Collection.Business.SendEvent(registerEvent);
|
||||
Collection.Log.LogInfo(Tag, $"Register Status: {((StatusRegisterEvent)registerResponse[0]).Message}");
|
||||
Collection.Scheduler.Interval("trpc.qq_new_tech.status_svc.StatusService.SsoHeartBeat",
|
||||
5 * 60 * 1000, async () => await Collection.Business.PushEvent(SsoAliveEvent.Create()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (@event is { Success: true, UnusualVerify: true })
|
||||
{
|
||||
throw new NotImplementedException(); // TODO: UnusualVerify
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException(); // TODO: Login by Password
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> DoWtLogin()
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "Doing Login...");
|
||||
Collection.Keystore.Session.Sequence = 0;
|
||||
|
||||
Collection.Keystore.SecpImpl = new EcdhImpl(EcdhImpl.CryptMethod.Secp192K1);
|
||||
var loginEvent = LoginEvent.Create();
|
||||
var result = await Collection.Business.SendEvent(loginEvent);
|
||||
|
||||
if (result.Count != 0)
|
||||
{
|
||||
var @event = (LoginEvent)result[0];
|
||||
if (@event.ResultCode == 0)
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "Login Success");
|
||||
Collection.Keystore.Info = new BotKeystore.BotInfo(@event.Age, @event.Sex, @event.Name);
|
||||
Collection.Log.LogInfo(Tag, Collection.Keystore.Info.ToString());
|
||||
|
||||
var registerEvent = StatusRegisterEvent.Create();
|
||||
var registerResponse = await Collection.Business.SendEvent(registerEvent);
|
||||
Collection.Log.LogInfo(Tag, $"Register Status: {((StatusRegisterEvent)registerResponse[0]).Message}");
|
||||
Collection.Scheduler.Interval("trpc.qq_new_tech.status_svc.StatusService.SsoHeartBeat", 5 * 60 * 1000,
|
||||
async () => await Collection.Business.PushEvent(SsoAliveEvent.Create()));
|
||||
|
||||
var onlineEvent = new BotOnlineEvent();
|
||||
Collection.Invoker.PostEvent(onlineEvent);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Collection.Log.LogFatal(Tag, $"Login failed: {@event.ResultCode}");
|
||||
Collection.Log.LogFatal(Tag, $"Tag: {@event.Tag}\nState: {@event.Message}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private async Task<bool> QueryQrCodeState()
|
||||
{
|
||||
if (Collection.Keystore.Session.QrString == null)
|
||||
{
|
||||
Collection.Log.LogFatal(Tag, "QrString is null, Please Fetch QrCode First");
|
||||
return false;
|
||||
}
|
||||
|
||||
var request = new NTLoginHttpRequest
|
||||
{
|
||||
Appid = Collection.AppInfo.AppId,
|
||||
Qrsig = Collection.Keystore.Session.QrString,
|
||||
FaceUpdateTime = 0
|
||||
};
|
||||
var payload = JsonSerializer.SerializeToUtf8Bytes(request);
|
||||
var response = await Http.PostAsync(Interface, payload, "application/json");
|
||||
var info = JsonSerializer.Deserialize<NTLoginHttpResponse>(response);
|
||||
if (info != null) Collection.Keystore.Uin = info.Uin;
|
||||
|
||||
var transEmp = TransEmpEvent.Create(TransEmpEvent.State.QueryResult);
|
||||
var result = await Collection.Business.SendEvent(transEmp);
|
||||
|
||||
if (result.Count != 0)
|
||||
{
|
||||
var @event = (TransEmpEvent)result[0];
|
||||
var state = (TransEmp12.State)@event.ResultCode;
|
||||
Collection.Log.LogInfo(Tag, $"QrCode State Queried: {state} Uin: {Collection.Keystore.Uin}");
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case TransEmp12.State.Confirmed:
|
||||
{
|
||||
Collection.Log.LogInfo(Tag, "QrCode Confirmed, Logging in with A1 sig...");
|
||||
Collection.Scheduler.Cancel(QueryEvent); // cancel query task
|
||||
|
||||
if (@event.TgtgtKey != null)
|
||||
{
|
||||
Collection.Keystore.Stub.TgtgtKey = @event.TgtgtKey;
|
||||
Collection.Keystore.Session.TempPassword = @event.TempPassword;
|
||||
Collection.Keystore.Session.NoPicSig = @event.NoPicSig;
|
||||
|
||||
return await DoWtLogin();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TransEmp12.State.CodeExpired:
|
||||
{
|
||||
Collection.Log.LogWarning(Tag, "QrCode Expired, Please Fetch QrCode Again");
|
||||
Collection.Scheduler.Cancel(QueryEvent);
|
||||
Collection.Scheduler.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
case TransEmp12.State.Canceled:
|
||||
{
|
||||
Collection.Log.LogWarning(Tag, "QrCode Canceled, Please Fetch QrCode Again");
|
||||
Collection.Scheduler.Cancel(QueryEvent);
|
||||
Collection.Scheduler.Dispose();
|
||||
|
||||
return false;
|
||||
}
|
||||
case TransEmp12.State.WaitingForConfirm:
|
||||
case TransEmp12.State.WaitingForScan:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
12
Lagrange.Core/Core/Context/Logic/LogicBase.cs
Normal file
12
Lagrange.Core/Core/Context/Logic/LogicBase.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Lagrange.Core.Core.Event.Protocol;
|
||||
|
||||
namespace Lagrange.Core.Core.Context.Logic;
|
||||
|
||||
internal abstract class LogicBase
|
||||
{
|
||||
protected readonly ContextCollection Collection;
|
||||
|
||||
protected LogicBase(ContextCollection collection) => Collection = collection;
|
||||
|
||||
public virtual Task Incoming(ProtocolEvent e) => throw new NotImplementedException();
|
||||
}
|
104
Lagrange.Core/Core/Context/PacketContext.cs
Normal file
104
Lagrange.Core/Core/Context/PacketContext.cs
Normal file
|
@ -0,0 +1,104 @@
|
|||
using System.Collections.Concurrent;
|
||||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Packets;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
#pragma warning disable CS4014
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Translate the protocol event into SSOPacket and further ServiceMessage</para>
|
||||
/// <para>And Dispatch the packet from <see cref="SocketContext"/> by managing the sequence from Tencent's server</para>
|
||||
/// <para>Every Packet should be send and received from this context instead of being directly send to <see cref="SocketContext"/></para>
|
||||
/// </summary>
|
||||
internal class PacketContext : ContextBase
|
||||
{
|
||||
private readonly ConcurrentDictionary<uint, TaskCompletionSource<SsoPacket>> _pendingTasks;
|
||||
|
||||
public PacketContext(ContextCollection collection, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(collection, keystore, appInfo, device)
|
||||
{
|
||||
_pendingTasks = new ConcurrentDictionary<uint, TaskCompletionSource<SsoPacket>>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send the packet and wait for the corresponding response by the packet's sequence number.
|
||||
/// </summary>
|
||||
public Task<SsoPacket> SendPacket(SsoPacket packet)
|
||||
{
|
||||
var task = new TaskCompletionSource<SsoPacket>();
|
||||
_pendingTasks.TryAdd(packet.Sequence, task);
|
||||
|
||||
switch (packet.PacketType)
|
||||
{
|
||||
case 12:
|
||||
{
|
||||
var sso = SsoPacker.Build(packet, AppInfo, DeviceInfo, Keystore);
|
||||
var service = ServicePacker.Build(sso, Keystore);
|
||||
bool _ = Collection.Socket.Send(service.ToArray()).Result;
|
||||
break;
|
||||
}
|
||||
case 13:
|
||||
{
|
||||
var service = ServicePacker.BuildProtocol13(packet.Payload, packet.Command, packet.Sequence);
|
||||
bool _ = Collection.Socket.Send(service.ToArray()).Result;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return task.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send the packet and don't wait for the corresponding response by the packet's sequence number.
|
||||
/// </summary>
|
||||
public async Task<bool> PostPacket(SsoPacket packet)
|
||||
{
|
||||
switch (packet.PacketType)
|
||||
{
|
||||
case 12:
|
||||
{
|
||||
var sso = SsoPacker.Build(packet, AppInfo, DeviceInfo, Keystore);
|
||||
var service = ServicePacker.Build(sso, Keystore);
|
||||
return await Collection.Socket.Send(service.ToArray());
|
||||
}
|
||||
case 13:
|
||||
{
|
||||
var service = ServicePacker.BuildProtocol13(packet.Payload, packet.Command, packet.Sequence);
|
||||
return await Collection.Socket.Send(service.ToArray());
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the incoming packet with new sequence number.
|
||||
/// </summary>
|
||||
public async Task<bool> HandleServerPacket(SsoPacket packet)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
var events = Collection.Service.ResolveEventByPacket(packet);
|
||||
foreach (var @event in events)
|
||||
{
|
||||
var isSuccessful = await Collection.Business.HandleIncomingEvent(@event);
|
||||
if (!isSuccessful) break;
|
||||
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void DispatchPacket(BinaryPacket packet)
|
||||
{
|
||||
var service = ServicePacker.Parse(packet, Keystore);
|
||||
if (service.Length == 0) return;
|
||||
|
||||
var sso = SsoPacker.Parse(service);
|
||||
|
||||
if (_pendingTasks.TryRemove(sso.Sequence, out var task)) task.SetResult(sso);
|
||||
else HandleServerPacket(sso);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
using System.Collections.Concurrent;
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
internal partial class ServiceContext
|
||||
{
|
||||
private class SequenceProvider
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, int> _sessionSequence;
|
||||
|
||||
private int _sequence;
|
||||
|
||||
public SequenceProvider()
|
||||
{
|
||||
_sessionSequence = new ConcurrentDictionary<string, int>();
|
||||
_sequence = Random.Shared.Next(5000000, 9900000);
|
||||
}
|
||||
|
||||
public int GetNewSequence()
|
||||
{
|
||||
Interlocked.CompareExchange(ref _sequence, 5000000, 9900000);
|
||||
return Interlocked.Increment(ref _sequence);
|
||||
}
|
||||
|
||||
public int RegisterSession(string sessionId) =>
|
||||
_sessionSequence.GetOrAdd(sessionId, _ => GetNewSequence());
|
||||
}
|
||||
}
|
115
Lagrange.Core/Core/Context/ServiceContext.cs
Normal file
115
Lagrange.Core/Core/Context/ServiceContext.cs
Normal file
|
@ -0,0 +1,115 @@
|
|||
using System.Reflection;
|
||||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Event.Protocol;
|
||||
using Lagrange.Core.Core.Packets;
|
||||
using Lagrange.Core.Core.Service;
|
||||
using Lagrange.Core.Core.Service.Abstraction;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
// ReSharper disable ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Manage the service and packet translation of the Bot</para>
|
||||
/// <para>Instantiate the Service by <see cref="System.Reflection"/> and store such</para>
|
||||
/// <para>Translate the event into <see cref="ProtocolEvent"/>, you may manually dispatch the packet to <see cref="PacketContext"/></para>
|
||||
/// </summary>
|
||||
internal partial class ServiceContext : ContextBase
|
||||
{
|
||||
private const string Tag = nameof(ServiceContext);
|
||||
|
||||
private readonly SequenceProvider _sequenceProvider;
|
||||
private readonly Dictionary<string, IService> _services;
|
||||
private readonly Dictionary<Type, List<(ServiceAttribute Attribute, IService Instance)>> _servicesEventType;
|
||||
|
||||
public ServiceContext(ContextCollection collection, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(collection, keystore, appInfo, device)
|
||||
{
|
||||
_sequenceProvider = new SequenceProvider();
|
||||
_services = new Dictionary<string, IService>();
|
||||
_servicesEventType = new Dictionary<Type, List<(ServiceAttribute, IService)>>();
|
||||
|
||||
RegisterServices();
|
||||
}
|
||||
|
||||
private void RegisterServices()
|
||||
{
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
foreach (var type in assembly.GetDerivedTypes<ProtocolEvent>())
|
||||
{
|
||||
_servicesEventType[type] = new List<(ServiceAttribute, IService)>();
|
||||
}
|
||||
|
||||
foreach (var type in assembly.GetTypeByAttributes<ServiceAttribute>(out _))
|
||||
{
|
||||
var serviceAttribute = type.GetCustomAttribute<ServiceAttribute>();
|
||||
|
||||
if (serviceAttribute != null)
|
||||
{
|
||||
var service = (IService)type.CreateInstance();
|
||||
_services[serviceAttribute.Command] = service;
|
||||
|
||||
foreach (var attribute in type.GetCustomAttributes<EventSubscribeAttribute>())
|
||||
{
|
||||
_servicesEventType[attribute.EventType].Add((serviceAttribute, service));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the outgoing packet by the event
|
||||
/// </summary>
|
||||
public List<SsoPacket> ResolvePacketByEvent(ProtocolEvent protocolEvent)
|
||||
{
|
||||
var result = new List<SsoPacket>();
|
||||
if (!_servicesEventType.TryGetValue(protocolEvent.GetType(), out var serviceList)) return result; // 没找到 滚蛋吧
|
||||
|
||||
foreach (var (attribute, instance) in serviceList)
|
||||
{
|
||||
bool success = instance.Build(protocolEvent, Keystore, AppInfo, DeviceInfo, out var binary, out var extraPackets);
|
||||
|
||||
if (success && binary != null)
|
||||
{
|
||||
result.Add(new SsoPacket(attribute.PacketType, attribute.Command, (uint)_sequenceProvider.GetNewSequence(), binary));
|
||||
|
||||
if (extraPackets != null)
|
||||
{
|
||||
result.AddRange(extraPackets.Select(extra => new SsoPacket(attribute.PacketType, attribute.Command, (uint)_sequenceProvider.GetNewSequence(), extra)));
|
||||
}
|
||||
|
||||
Collection.Log.LogVerbose(Tag, $"Outgoing SSOFrame: {attribute.Command}");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolve the incoming event by the packet
|
||||
/// </summary>
|
||||
public List<ProtocolEvent> ResolveEventByPacket(SsoPacket packet)
|
||||
{
|
||||
var result = new List<ProtocolEvent>();
|
||||
if (!_services.TryGetValue(packet.Command, out var service))
|
||||
{
|
||||
Collection.Log.LogWarning(Tag, $"Unsupported SSOFrame Received: {packet.Command}");
|
||||
var payload = packet.Payload.ReadBytes(BinaryPacket.Prefix.Uint32 | BinaryPacket.Prefix.WithPrefix);
|
||||
Collection.Log.LogDebug(Tag, $"Unsuccessful SSOFrame Payload: {payload.Hex()}");
|
||||
return result; // 没找到 滚蛋吧
|
||||
}
|
||||
|
||||
bool success = service.Parse(packet, Keystore, AppInfo, DeviceInfo, out var @event, out var extraEvents);
|
||||
|
||||
if (success)
|
||||
{
|
||||
result.Add(@event);
|
||||
if (extraEvents != null) result.AddRange(extraEvents);
|
||||
|
||||
Collection.Log.LogVerbose(Tag, $"Incoming SSOFrame: {packet.Command}");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
134
Lagrange.Core/Core/Context/SocketContext.cs
Normal file
134
Lagrange.Core/Core/Context/SocketContext.cs
Normal file
|
@ -0,0 +1,134 @@
|
|||
using System.Buffers.Binary;
|
||||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Network.Tcp;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
using Lagrange.Core.Utility.Network;
|
||||
|
||||
namespace Lagrange.Core.Core.Context;
|
||||
|
||||
/// <summary>
|
||||
/// <para>Provide Low-Allocation Tcp Client which connects to the Tencent's SSO Server</para>
|
||||
/// <para>Internal Implementation, Packet Received would be dispatched to <see cref="PacketContext"/> for decryption and unpack</para>
|
||||
/// <para>MSF Service is also implemented here</para>
|
||||
/// </summary>
|
||||
internal class SocketContext : ContextBase, IClientListener
|
||||
{
|
||||
private const string Tag = nameof(SocketContext);
|
||||
|
||||
private readonly ClientListener _tcpClient;
|
||||
|
||||
private Uri? ServerUri { get; set; }
|
||||
|
||||
public uint HeaderSize => 4;
|
||||
|
||||
public bool Connected => _tcpClient.Connected;
|
||||
|
||||
public SocketContext(ContextCollection collection, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(collection, keystore, appInfo, device)
|
||||
{
|
||||
_tcpClient = new CallbackClientListener(this);
|
||||
}
|
||||
|
||||
public async Task<bool> Connect(bool useIPv6Network = false)
|
||||
{
|
||||
if (_tcpClient.Connected) return true;
|
||||
|
||||
var servers = await OptimumServer(false, useIPv6Network);
|
||||
ServerUri = servers.First();
|
||||
return await _tcpClient.Connect(ServerUri.Host, ServerUri.Port);
|
||||
}
|
||||
|
||||
public async Task<bool> Reconnect()
|
||||
{
|
||||
if (ServerUri != null) return await _tcpClient.Connect(ServerUri.Host, ServerUri.Port);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public async Task<bool> Send(byte[] packet) => await _tcpClient.Send(packet);
|
||||
|
||||
public uint GetPacketLength(ReadOnlySpan<byte> header) => BinaryPrimitives.ReadUInt32BigEndian(header);
|
||||
|
||||
public void OnRecvPacket(ReadOnlySpan<byte> packet)
|
||||
{
|
||||
var binary = new BinaryPacket(packet.ToArray());
|
||||
Collection.Packet.DispatchPacket(binary);
|
||||
}
|
||||
|
||||
public void OnDisconnect()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void OnSocketError(Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
}
|
||||
|
||||
private static readonly Uri[] MsfUris =
|
||||
{
|
||||
new("http://msfwifi.3g.qq.com:8080"), // IPv4
|
||||
new("http://msfwifiv6.3g.qq.com:14000") // IPv6
|
||||
};
|
||||
|
||||
private static readonly Uri[] HardCodeIPv4Uris =
|
||||
{
|
||||
new("http://msfwifi.3g.qq.com:8080"),
|
||||
new("http://163.177.89.195:14000"),
|
||||
new("http://120.232.18.27:443"),
|
||||
new("http://157.255.13.77:443"),
|
||||
new("http://140.207.123.177:8080"),
|
||||
new("http://221.198.69.96:443"),
|
||||
new("http://123.150.76.143:14000"),
|
||||
new("http://183.3.235.162:443"),
|
||||
new("http://61.129.6.101:8080"),
|
||||
new("http://42.81.169.100:443"),
|
||||
new("http://183.232.94.44:14000"),
|
||||
new("http://msfxg.3g.qq.com:80"),
|
||||
new("http://117.144.244.33:8080"),
|
||||
new("http://111.30.138.152:443"),
|
||||
new("http://203.205.255.221:14000"),
|
||||
new("http://203.205.255.224:443"),
|
||||
new("http://183.3.235.162:8080"),
|
||||
new("http://183.47.102.193:8080")
|
||||
};
|
||||
|
||||
private static readonly Uri[] HardCodeIPv6Uris =
|
||||
{
|
||||
new("http://msfwifiv6.3g.qq.com:14000")
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 好像这才是真货
|
||||
/// </summary>
|
||||
private static readonly Uri[] TestIPv4HardCodes =
|
||||
{
|
||||
new("http://183.47.102.193:8080"),
|
||||
new("http://14.22.9.84:8080"),
|
||||
new("http://119.147.190.138:8080")
|
||||
};
|
||||
|
||||
private async Task<List<Uri>> OptimumServer(bool requestMsf, bool useIPv6Network = false)
|
||||
{
|
||||
Uri[] result;
|
||||
|
||||
if (requestMsf)
|
||||
{
|
||||
result = Array.Empty<Uri>();
|
||||
// Implement MSF server request
|
||||
// TODO: tx自己用的都tm是硬编码 实现尼玛
|
||||
}
|
||||
else
|
||||
{
|
||||
result = useIPv6Network ? HardCodeIPv6Uris : TestIPv4HardCodes;
|
||||
}
|
||||
|
||||
var latencyTasks = result.Select(uri => Icmp.PingAsync(uri)).ToArray();
|
||||
var latency = await Task.WhenAll(latencyTasks);
|
||||
Array.Sort(latency, result);
|
||||
|
||||
var list = result.ToList();
|
||||
for (int i = 0; i < list.Count; i++) Collection.Log.LogVerbose(Tag, $"Server: {list[i]} Latency: {latency[i]}");
|
||||
return list;
|
||||
}
|
||||
}
|
7
Lagrange.Core/Core/Core.md
Normal file
7
Lagrange.Core/Core/Core.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Lagrange.Core
|
||||
## Core
|
||||
|
||||
This is the core of the Lagrange framework. It contains the basic classes and interfaces that are used throughout the framework.
|
||||
|
||||
|
||||
Most of the internal data structures are implemented in this namespace
|
30
Lagrange.Core/Core/Event/EventArg/BotLogEvent.cs
Normal file
30
Lagrange.Core/Core/Event/EventArg/BotLogEvent.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
namespace Lagrange.Core.Core.Event.EventArg;
|
||||
|
||||
public enum LogLevel
|
||||
{
|
||||
Debug,
|
||||
Verbose,
|
||||
Information,
|
||||
Warning,
|
||||
Exception,
|
||||
Fatal
|
||||
}
|
||||
|
||||
public class BotLogEvent : EventBase
|
||||
{
|
||||
private const string DateFormat = "yyyy-MM-dd HH:mm:ss";
|
||||
|
||||
public string Tag { get; }
|
||||
|
||||
public LogLevel Level { get; }
|
||||
|
||||
internal BotLogEvent(string tag, LogLevel level, string content)
|
||||
{
|
||||
Tag = tag;
|
||||
Level = level;
|
||||
EventMessage = content;
|
||||
}
|
||||
|
||||
public override string ToString() =>
|
||||
$"[{EventTime.ToString(DateFormat)}] [{Tag}] [{Level.ToString().ToUpper()}]: {EventMessage}";
|
||||
}
|
6
Lagrange.Core/Core/Event/EventArg/BotOfflineEvent.cs
Normal file
6
Lagrange.Core/Core/Event/EventArg/BotOfflineEvent.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Lagrange.Core.Core.Event.EventArg;
|
||||
|
||||
public class BotOfflineEvent : EventBase
|
||||
{
|
||||
|
||||
}
|
6
Lagrange.Core/Core/Event/EventArg/BotOnlineEvent.cs
Normal file
6
Lagrange.Core/Core/Event/EventArg/BotOnlineEvent.cs
Normal file
|
@ -0,0 +1,6 @@
|
|||
namespace Lagrange.Core.Core.Event.EventArg;
|
||||
|
||||
public class BotOnlineEvent : EventBase
|
||||
{
|
||||
|
||||
}
|
22
Lagrange.Core/Core/Event/EventBase.cs
Normal file
22
Lagrange.Core/Core/Event/EventBase.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
namespace Lagrange.Core.Core.Event;
|
||||
|
||||
/// <summary>
|
||||
/// Event that exposed to user
|
||||
/// </summary>
|
||||
public abstract class EventBase : EventArgs
|
||||
{
|
||||
public DateTime EventTime { get; }
|
||||
|
||||
public string EventMessage { get; protected set; }
|
||||
|
||||
internal EventBase()
|
||||
{
|
||||
EventTime = DateTime.Now;
|
||||
EventMessage = "[Empty Event Message]";
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"[{EventTime:HH:mm:ss}] {EventMessage}";
|
||||
}
|
||||
}
|
12
Lagrange.Core/Core/Event/EventInvoker.Events.cs
Normal file
12
Lagrange.Core/Core/Event/EventInvoker.Events.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Lagrange.Core.Core.Event.EventArg;
|
||||
|
||||
namespace Lagrange.Core.Core.Event;
|
||||
|
||||
public partial class EventInvoker
|
||||
{
|
||||
public event LagrangeEvent<BotOnlineEvent>? OnBotOnlineEvent;
|
||||
|
||||
public event LagrangeEvent<BotOfflineEvent>? OnBotOfflineEvent;
|
||||
|
||||
public event LagrangeEvent<BotLogEvent>? OnBotLogEvent;
|
||||
}
|
41
Lagrange.Core/Core/Event/EventInvoker.cs
Normal file
41
Lagrange.Core/Core/Event/EventInvoker.cs
Normal file
|
@ -0,0 +1,41 @@
|
|||
using Lagrange.Core.Core.Event.EventArg;
|
||||
|
||||
namespace Lagrange.Core.Core.Event;
|
||||
|
||||
public partial class EventInvoker : IDisposable
|
||||
{
|
||||
private readonly Dictionary<Type, Action<EventBase>> _events;
|
||||
|
||||
public delegate void LagrangeEvent<in TEvent>(BotContext context, TEvent e) where TEvent : EventBase;
|
||||
|
||||
internal EventInvoker(BotContext context)
|
||||
{
|
||||
_events = new Dictionary<Type, Action<EventBase>>
|
||||
{
|
||||
{ typeof(BotOnlineEvent), e => OnBotOnlineEvent?.Invoke(context, (BotOnlineEvent)e) },
|
||||
{ typeof(BotOfflineEvent), e => OnBotOfflineEvent?.Invoke(context, (BotOfflineEvent)e) },
|
||||
{ typeof(BotLogEvent), e => OnBotLogEvent?.Invoke(context, (BotLogEvent)e) }
|
||||
};
|
||||
}
|
||||
|
||||
internal void PostEvent(EventBase e)
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_events.TryGetValue(e.GetType(), out var action)) action(e);
|
||||
else PostEvent(new BotLogEvent("BotContext", LogLevel.Warning, $"Event {e.GetType().Name} is not registered but pushed to invoker"));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
PostEvent(new BotLogEvent("BotContext", LogLevel.Exception, $"{ex.StackTrace}\n{ex.Message}"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_events.Clear();
|
||||
}
|
||||
}
|
26
Lagrange.Core/Core/Event/Protocol/Login/EasyLoginEvent.cs
Normal file
26
Lagrange.Core/Core/Event/Protocol/Login/EasyLoginEvent.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.Login;
|
||||
|
||||
internal class EasyLoginEvent : ProtocolEvent
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public bool UnusualVerify { get; set; }
|
||||
|
||||
protected EasyLoginEvent() : base(true) { }
|
||||
|
||||
protected EasyLoginEvent(bool success, bool unusualVerify) : base(0)
|
||||
{
|
||||
Success = success;
|
||||
UnusualVerify = unusualVerify;
|
||||
}
|
||||
|
||||
public static EasyLoginEvent Create()
|
||||
{
|
||||
return new EasyLoginEvent();
|
||||
}
|
||||
|
||||
public static EasyLoginEvent Result(bool success, bool unusualVerify = false)
|
||||
{
|
||||
return new EasyLoginEvent(success, unusualVerify);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.Login.Ecdh;
|
||||
|
||||
internal class KeyExchangeEvent : ProtocolEvent
|
||||
{
|
||||
private KeyExchangeEvent() : base(true)
|
||||
{
|
||||
}
|
||||
|
||||
private KeyExchangeEvent(int resultCode) : base(resultCode)
|
||||
{
|
||||
}
|
||||
|
||||
public static KeyExchangeEvent Create()
|
||||
{
|
||||
return new KeyExchangeEvent(0);
|
||||
}
|
||||
|
||||
|
||||
public static KeyExchangeEvent Result()
|
||||
{
|
||||
return new KeyExchangeEvent();
|
||||
}
|
||||
}
|
40
Lagrange.Core/Core/Event/Protocol/Login/LoginEvent.cs
Normal file
40
Lagrange.Core/Core/Event/Protocol/Login/LoginEvent.cs
Normal file
|
@ -0,0 +1,40 @@
|
|||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Event.Protocol.Login;
|
||||
|
||||
internal class LoginEvent : ProtocolEvent
|
||||
{
|
||||
public byte Age { get; }
|
||||
public byte Sex { get; }
|
||||
public string Name { get; }
|
||||
public string Tag { get; } = string.Empty;
|
||||
|
||||
public string Message { get; } = string.Empty;
|
||||
|
||||
private LoginEvent() : base(true) { }
|
||||
|
||||
private LoginEvent(int resultCode) : base(resultCode) { }
|
||||
|
||||
private LoginEvent(int resultCode, byte age, byte sex, string name) : base(resultCode)
|
||||
{
|
||||
Age = age;
|
||||
Sex = sex;
|
||||
Name = name;
|
||||
}
|
||||
|
||||
private LoginEvent(int resultCode, string tag, string message) : base(resultCode)
|
||||
{
|
||||
Tag = tag;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static LoginEvent Create() => new();
|
||||
|
||||
public static LoginEvent Result(int resultCode, byte age, byte sex, string name)
|
||||
=> new(resultCode, age, sex, name);
|
||||
|
||||
public static LoginEvent Result(int resultCode) => new(resultCode);
|
||||
|
||||
|
||||
public static LoginEvent Result(int resultCode, string tag, string message) => new(resultCode, tag, message);
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.Login;
|
||||
|
||||
internal class PasswordLoginEvent : ProtocolEvent
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
|
||||
public bool UnusualVerify { get; set; }
|
||||
|
||||
protected PasswordLoginEvent() : base(true) { }
|
||||
|
||||
protected PasswordLoginEvent(bool success, bool unusualVerify) : base(0)
|
||||
{
|
||||
Success = success;
|
||||
UnusualVerify = unusualVerify;
|
||||
}
|
||||
|
||||
public static PasswordLoginEvent Create()
|
||||
{
|
||||
return new PasswordLoginEvent();
|
||||
}
|
||||
|
||||
public static PasswordLoginEvent Result(bool success, bool unusualVerify = false)
|
||||
{
|
||||
return new PasswordLoginEvent(success, unusualVerify);
|
||||
}
|
||||
}
|
69
Lagrange.Core/Core/Event/Protocol/Login/TransEmpEvent.cs
Normal file
69
Lagrange.Core/Core/Event/Protocol/Login/TransEmpEvent.cs
Normal file
|
@ -0,0 +1,69 @@
|
|||
using Lagrange.Core.Core.Packets.Login.WtLogin.Entity;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
namespace Lagrange.Core.Core.Event.Protocol.Login;
|
||||
|
||||
internal class TransEmpEvent : ProtocolEvent
|
||||
{
|
||||
public State EventState { get; set; }
|
||||
|
||||
#region TransEmp CMD0x31
|
||||
|
||||
public byte[] QrCode { get; }
|
||||
|
||||
public uint Expiration { get; }
|
||||
|
||||
public string Url { get; }
|
||||
|
||||
public string QrSig { get; }
|
||||
|
||||
public byte[] Signature { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region TransEmp CMD0x12
|
||||
|
||||
public byte[]? TgtgtKey { get; }
|
||||
|
||||
public byte[]? TempPassword { get; }
|
||||
|
||||
public byte[]? NoPicSig { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
private TransEmpEvent(State eventState) : base(true) => EventState = eventState;
|
||||
|
||||
private TransEmpEvent(int result, byte[] qrCode, uint expiration, string url, string qrSig, byte[] signature)
|
||||
: base(result)
|
||||
{
|
||||
EventState = State.FetchQrCode;
|
||||
|
||||
QrCode = qrCode;
|
||||
Expiration = expiration;
|
||||
Url = url;
|
||||
QrSig = qrSig;
|
||||
Signature = signature;
|
||||
}
|
||||
|
||||
private TransEmpEvent(int result, byte[]? tgtgtKey, byte[]? tempPassword, byte[]? noPicSig) : base(result)
|
||||
{
|
||||
TgtgtKey = tgtgtKey;
|
||||
TempPassword = tempPassword;
|
||||
NoPicSig = noPicSig;
|
||||
EventState = State.QueryResult;
|
||||
}
|
||||
|
||||
public static TransEmpEvent Create(State eventState) => new(eventState);
|
||||
|
||||
public static TransEmpEvent Result(byte[] qrCode, uint expiration, string url, string qrSig, byte[] signature) =>
|
||||
new(0, qrCode, expiration, url, qrSig, signature);
|
||||
|
||||
public static TransEmpEvent Result(TransEmp12.State state, byte[]? tgtgtKey, byte[]? tempPassword, byte[]? noPicSig) =>
|
||||
new((int)state, tgtgtKey, tempPassword, noPicSig);
|
||||
|
||||
public enum State : byte
|
||||
{
|
||||
FetchQrCode = 0x31,
|
||||
QueryResult = 0x12
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.Message;
|
||||
|
||||
internal class PushMessageEvent : ProtocolEvent
|
||||
{
|
||||
private PushMessageEvent() : base(false)
|
||||
{
|
||||
}
|
||||
|
||||
private PushMessageEvent(int resultCode) : base(resultCode)
|
||||
{
|
||||
}
|
||||
|
||||
public static PushMessageEvent Create() => new(0);
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.Message;
|
||||
|
||||
internal class SendMessageEvent : ProtocolEvent
|
||||
{
|
||||
protected SendMessageEvent(bool waitResponse) : base(true)
|
||||
{
|
||||
}
|
||||
|
||||
protected SendMessageEvent(int resultCode) : base(resultCode)
|
||||
{
|
||||
}
|
||||
}
|
12
Lagrange.Core/Core/Event/Protocol/ProtocolEvent.cs
Normal file
12
Lagrange.Core/Core/Event/Protocol/ProtocolEvent.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol;
|
||||
|
||||
internal class ProtocolEvent
|
||||
{
|
||||
public bool WaitResponse { get; }
|
||||
|
||||
public int ResultCode { get; private set; }
|
||||
|
||||
protected ProtocolEvent(bool waitResponse) => WaitResponse = waitResponse;
|
||||
|
||||
protected ProtocolEvent(int resultCode) => ResultCode = resultCode;
|
||||
}
|
12
Lagrange.Core/Core/Event/Protocol/System/AliveEvent.cs
Normal file
12
Lagrange.Core/Core/Event/Protocol/System/AliveEvent.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.System;
|
||||
|
||||
internal class AliveEvent : ProtocolEvent
|
||||
{
|
||||
protected AliveEvent() : base(false) { }
|
||||
|
||||
protected AliveEvent(int resultCode) : base(resultCode) { }
|
||||
|
||||
public static AliveEvent Create() => new();
|
||||
|
||||
public static AliveEvent Result() => new(0);
|
||||
}
|
16
Lagrange.Core/Core/Event/Protocol/System/CorrectTimeEvent.cs
Normal file
16
Lagrange.Core/Core/Event/Protocol/System/CorrectTimeEvent.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.System;
|
||||
|
||||
internal class CorrectTimeEvent : ProtocolEvent
|
||||
{
|
||||
protected CorrectTimeEvent() : base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected CorrectTimeEvent(int resultCode) : base(resultCode)
|
||||
{
|
||||
}
|
||||
|
||||
public static CorrectTimeEvent Create() => new();
|
||||
|
||||
public static CorrectTimeEvent Result() => new(0);
|
||||
}
|
18
Lagrange.Core/Core/Event/Protocol/System/KickNTEvent.cs
Normal file
18
Lagrange.Core/Core/Event/Protocol/System/KickNTEvent.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.System;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
||||
internal class KickNTEvent : ProtocolEvent
|
||||
{
|
||||
public string Tag { get; set; }
|
||||
|
||||
public string Message { get; set; }
|
||||
|
||||
private KickNTEvent(string tag, string message) : base(0)
|
||||
{
|
||||
Tag = tag;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static KickNTEvent Create(string tag, string message) => new(tag, message);
|
||||
}
|
12
Lagrange.Core/Core/Event/Protocol/System/SsoAliveEvent.cs
Normal file
12
Lagrange.Core/Core/Event/Protocol/System/SsoAliveEvent.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
namespace Lagrange.Core.Core.Event.Protocol.System;
|
||||
|
||||
internal class SsoAliveEvent : ProtocolEvent
|
||||
{
|
||||
private SsoAliveEvent() : base(true) { }
|
||||
|
||||
private SsoAliveEvent(int resultCode) : base(resultCode) { }
|
||||
|
||||
public static SsoAliveEvent Create() => new();
|
||||
|
||||
public static SsoAliveEvent Result() => new(0);
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Event.Protocol.System;
|
||||
|
||||
internal class StatusRegisterEvent : ProtocolEvent
|
||||
{
|
||||
public string Message { get; set; }
|
||||
|
||||
private StatusRegisterEvent() : base(true) { }
|
||||
|
||||
private StatusRegisterEvent(string result) : base(0) => Message = result;
|
||||
|
||||
public static StatusRegisterEvent Create() => new();
|
||||
|
||||
public static StatusRegisterEvent Result(string result) => new(result);
|
||||
}
|
18
Lagrange.Core/Core/Network/Tcp/CallbackClientListener.cs
Normal file
18
Lagrange.Core/Core/Network/Tcp/CallbackClientListener.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
namespace Lagrange.Core.Core.Network.Tcp;
|
||||
|
||||
internal sealed class CallbackClientListener : ClientListener
|
||||
{
|
||||
public override uint HeaderSize => _listener.HeaderSize;
|
||||
|
||||
private readonly IClientListener _listener;
|
||||
|
||||
public CallbackClientListener(IClientListener listener) => _listener = listener;
|
||||
|
||||
public override uint GetPacketLength(ReadOnlySpan<byte> header) => _listener.GetPacketLength(header);
|
||||
|
||||
public override void OnDisconnect() => _listener.OnDisconnect();
|
||||
|
||||
public override void OnRecvPacket(ReadOnlySpan<byte> packet) => _listener.OnRecvPacket(packet);
|
||||
|
||||
public override void OnSocketError(Exception e) => _listener.OnSocketError(e);
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
using System.Net.Sockets;
|
||||
|
||||
namespace Lagrange.Core.Core.Network.Tcp;
|
||||
|
||||
internal abstract partial class ClientListener
|
||||
{
|
||||
protected sealed class SocketSession : IDisposable
|
||||
{
|
||||
public Socket Socket { get; }
|
||||
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public CancellationToken Token { get; }
|
||||
|
||||
public SocketSession()
|
||||
{
|
||||
Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
_cts = new CancellationTokenSource();
|
||||
Token = _cts.Token;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var cts = Interlocked.Exchange(ref _cts, null);
|
||||
if (cts == null) return;
|
||||
|
||||
cts.Cancel();
|
||||
cts.Dispose();
|
||||
Socket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
175
Lagrange.Core/Core/Network/Tcp/ClientListener.cs
Normal file
175
Lagrange.Core/Core/Network/Tcp/ClientListener.cs
Normal file
|
@ -0,0 +1,175 @@
|
|||
using System.Net.Sockets;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
|
||||
namespace Lagrange.Core.Core.Network.Tcp;
|
||||
|
||||
internal abstract partial class ClientListener : IClientListener
|
||||
{
|
||||
/// <summary>
|
||||
/// Socket connected
|
||||
/// </summary>
|
||||
public bool Connected => Session?.Socket.Connected ?? false;
|
||||
|
||||
public abstract uint HeaderSize { get; }
|
||||
|
||||
protected SocketSession? Session;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a tcp client
|
||||
/// </summary>
|
||||
public ClientListener() { }
|
||||
|
||||
private async Task<bool> InternalConnectAsync(SocketSession session, string host, int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
await session.Socket.ConnectAsync(host, port);
|
||||
_ = ReceiveLoop(session);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
RemoveSession(session);
|
||||
OnSocketError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Connect to the server
|
||||
/// </summary>
|
||||
/// <param name="host"></param>
|
||||
/// <param name="port"></param>
|
||||
/// <returns></returns>
|
||||
public Task<bool> Connect(string host, int port)
|
||||
{
|
||||
SocketSession? previousSession = Session, createdSession = null;
|
||||
if (previousSession != null || // The client has been connected
|
||||
Interlocked.CompareExchange(ref Session, createdSession = new SocketSession(), null) != null) // Another connect request before this request
|
||||
{
|
||||
createdSession?.Dispose();
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
return InternalConnectAsync(createdSession, host, port); // Connect to server
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnect
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void Disconnect()
|
||||
{
|
||||
if (Session is { } session) RemoveSession(session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send data
|
||||
/// </summary>
|
||||
/// <param name="buffer"></param>
|
||||
/// <param name="timeout"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> Send(byte[] buffer, int timeout = -1)
|
||||
{
|
||||
try
|
||||
{
|
||||
var session = Session; // Send the data
|
||||
if (session == null) return false;
|
||||
|
||||
CancellationTokenSource? userCts;
|
||||
CancellationTokenSource? linkedCts;
|
||||
CancellationToken token;
|
||||
if (timeout == -1)
|
||||
{
|
||||
userCts = null;
|
||||
linkedCts = null;
|
||||
token = session.Token;
|
||||
}
|
||||
else
|
||||
{
|
||||
userCts = new CancellationTokenSource(timeout);
|
||||
linkedCts = CancellationTokenSource.CreateLinkedTokenSource(session.Token, userCts.Token);
|
||||
token = linkedCts.Token;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return await session.Socket.SendAsync(buffer, SocketFlags.None, token) == buffer.Length;
|
||||
}
|
||||
finally
|
||||
{
|
||||
userCts?.Dispose();
|
||||
linkedCts?.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnSocketError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Receive the data
|
||||
/// </summary>
|
||||
private async Task ReceiveLoop(SocketSession session, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.CompletedTask.ForceAsync();
|
||||
Socket socket = session.Socket;
|
||||
byte[] buffer = new byte[Math.Max(HeaderSize, 2048)];
|
||||
int headerSize = (int)HeaderSize;
|
||||
while (true)
|
||||
{
|
||||
await socket.ReceiveFullyAsync(buffer.AsMemory(0, headerSize), token);
|
||||
int packetLength = (int)GetPacketLength(buffer.AsSpan(0, headerSize));
|
||||
if (packetLength > 1024 * 1024 * 64) // limit to 64MiB
|
||||
{
|
||||
throw new InvalidDataException($"PackageLength was too long({packetLength}).");
|
||||
}
|
||||
if (packetLength > buffer.Length)
|
||||
{
|
||||
byte[] newBuffer = new byte[packetLength];
|
||||
Unsafe.CopyBlock(ref newBuffer[0], ref buffer[0], (uint)headerSize);
|
||||
buffer = newBuffer;
|
||||
}
|
||||
await socket.ReceiveFullyAsync(buffer.AsMemory(headerSize, packetLength - headerSize), token);
|
||||
OnRecvPacket(buffer.AsSpan(0, packetLength));
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException) when (token.IsCancellationRequested)
|
||||
{
|
||||
|
||||
}
|
||||
catch (SocketException e) when (e.SocketErrorCode == SocketError.OperationAborted)
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OnSocketError(e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemoveSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSession(SocketSession session)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref Session, null, session) == session)
|
||||
{
|
||||
session.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract uint GetPacketLength(ReadOnlySpan<byte> header);
|
||||
|
||||
public abstract void OnRecvPacket(ReadOnlySpan<byte> packet);
|
||||
|
||||
public abstract void OnDisconnect();
|
||||
|
||||
public abstract void OnSocketError(Exception e);
|
||||
}
|
27
Lagrange.Core/Core/Network/Tcp/IClientListener.cs
Normal file
27
Lagrange.Core/Core/Network/Tcp/IClientListener.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
namespace Lagrange.Core.Core.Network.Tcp;
|
||||
|
||||
internal interface IClientListener
|
||||
{
|
||||
uint HeaderSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Dissect a stream
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public uint GetPacketLength(ReadOnlySpan<byte> header);
|
||||
|
||||
/// <summary>
|
||||
/// On handle a packet
|
||||
/// </summary>
|
||||
public void OnRecvPacket(ReadOnlySpan<byte> packet);
|
||||
|
||||
/// <summary>
|
||||
/// On client disconnect
|
||||
/// </summary>
|
||||
public void OnDisconnect();
|
||||
|
||||
/// <summary>
|
||||
/// On socket error
|
||||
/// </summary>
|
||||
public void OnSocketError(Exception e);
|
||||
}
|
17
Lagrange.Core/Core/Packets/Action/FirstViewReq.cs
Normal file
17
Lagrange.Core/Core/Packets/Action/FirstViewReq.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
using ProtoBuf;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action;
|
||||
|
||||
[ProtoContract]
|
||||
internal class FirstViewReq
|
||||
{
|
||||
[ProtoMember(1)] public int LastMsgTime { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int Seq { get; set; }
|
||||
|
||||
[ProtoMember(4)] public int DirectMessageFlag { get; set; }
|
||||
|
||||
[ProtoMember(5)] public int UdcFlag { get; set; }
|
||||
}
|
25
Lagrange.Core/Core/Packets/Action/GetGroupMsgReq.cs
Normal file
25
Lagrange.Core/Core/Packets/Action/GetGroupMsgReq.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action;
|
||||
|
||||
[ProtoContract]
|
||||
internal class GetGroupMsgReq
|
||||
{
|
||||
[ProtoMember(1)] public ulong GroupCode { get; set; }
|
||||
|
||||
[ProtoMember(2)] public ulong BeginSeq { get; set; }
|
||||
|
||||
[ProtoMember(3)] public ulong EndSeq { get; set; }
|
||||
|
||||
[ProtoMember(4)] public uint Filter { get; set; }
|
||||
|
||||
[ProtoMember(5)] public ulong MemberSeq { get; set; }
|
||||
|
||||
[ProtoMember(6)] public bool PublicGroup { get; set; }
|
||||
|
||||
[ProtoMember(7)] public uint ShieldFlag { get; set; }
|
||||
|
||||
[ProtoMember(8)] public uint SaveTrafficFlag { get; set; }
|
||||
}
|
21
Lagrange.Core/Core/Packets/Action/GetGroupMsgResp.cs
Normal file
21
Lagrange.Core/Core/Packets/Action/GetGroupMsgResp.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action;
|
||||
|
||||
[ProtoContract]
|
||||
internal class GetGroupMsgResp
|
||||
{
|
||||
[ProtoMember(1)] public uint Result { get; set; }
|
||||
|
||||
[ProtoMember(2)] public string ErrMsg { get; set; }
|
||||
|
||||
[ProtoMember(3)] public ulong GroupCode { get; set; }
|
||||
|
||||
[ProtoMember(4)] public ulong ReturnBeginSeq { get; set; }
|
||||
|
||||
[ProtoMember(5)] public ulong ReturnEndSeq { get; set; }
|
||||
|
||||
[ProtoMember(6)] public List<Message.Message> Msg { get; set; }
|
||||
}
|
30
Lagrange.Core/Core/Packets/Action/HttpConn/HttpConn.cs
Normal file
30
Lagrange.Core/Core/Packets/Action/HttpConn/HttpConn.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.HttpConn;
|
||||
|
||||
internal class HttpConn
|
||||
{
|
||||
[ProtoMember(1)] public int Field1 { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int Field2 { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int Field3 { get; set; }
|
||||
|
||||
[ProtoMember(4)] public int Field4 { get; set; }
|
||||
|
||||
[ProtoMember(5)] public string Tgt { get; set; }
|
||||
|
||||
[ProtoMember(6)] public int Field6 { get; set; }
|
||||
|
||||
[ProtoMember(7)] public List<int> Field7 { get; set; }
|
||||
|
||||
[ProtoMember(9)] public int Field9 { get; set; }
|
||||
|
||||
[ProtoMember(10)] public int Field10 { get; set; }
|
||||
|
||||
[ProtoMember(11)] public int Field11 { get; set; }
|
||||
|
||||
[ProtoMember(15)] public string Ver { get; set; }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using ProtoBuf;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.HttpConn;
|
||||
|
||||
internal class HttpConn0x6ff_501
|
||||
{
|
||||
[ProtoMember(0x501)] public HttpConn HttpConn { get; set; }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.Pb;
|
||||
|
||||
[ProtoContract]
|
||||
internal class PbGetOneDayRoamMsgReq
|
||||
{
|
||||
[ProtoMember(1)] public ulong PeerUin { get; set; }
|
||||
|
||||
[ProtoMember(2)] public ulong LastMsgTime { get; set; }
|
||||
|
||||
[ProtoMember(3)] public ulong Random { get; set; }
|
||||
|
||||
[ProtoMember(4)] public uint ReadCnt { get; set; }
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.Pb;
|
||||
|
||||
[ProtoContract]
|
||||
internal class PbGetOneDayRoamMsgResp
|
||||
{
|
||||
[ProtoMember(1)] public uint Result { get; set; }
|
||||
|
||||
[ProtoMember(2)] public string ErrMsg { get; set; }
|
||||
|
||||
[ProtoMember(3)] public ulong PeerUin { get; set; }
|
||||
|
||||
[ProtoMember(4)] public ulong LastMsgTime { get; set; }
|
||||
|
||||
[ProtoMember(5)] public ulong Random { get; set; }
|
||||
|
||||
[ProtoMember(6)] public List<Message.Message> Msg { get; set; }
|
||||
|
||||
[ProtoMember(7)] public uint IsComplete { get; set; }
|
||||
}
|
13
Lagrange.Core/Core/Packets/Action/Pb/PbMultiMsgItem.cs
Normal file
13
Lagrange.Core/Core/Packets/Action/Pb/PbMultiMsgItem.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.Pb;
|
||||
|
||||
[ProtoContract]
|
||||
internal class PbMultiMsgItem
|
||||
{
|
||||
[ProtoMember(1)] public string FileName { get; set; }
|
||||
|
||||
[ProtoMember(2)] public PbMultiMsgNew Buffer { get; set; }
|
||||
}
|
11
Lagrange.Core/Core/Packets/Action/Pb/PbMultiMsgNew.cs
Normal file
11
Lagrange.Core/Core/Packets/Action/Pb/PbMultiMsgNew.cs
Normal file
|
@ -0,0 +1,11 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.Pb;
|
||||
|
||||
[ProtoContract]
|
||||
internal class PbMultiMsgNew
|
||||
{
|
||||
[ProtoMember(1)] public List<Message.Message> Msg { get; set; }
|
||||
}
|
13
Lagrange.Core/Core/Packets/Action/Pb/PbMultiMsgTransmit.cs
Normal file
13
Lagrange.Core/Core/Packets/Action/Pb/PbMultiMsgTransmit.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.Pb;
|
||||
|
||||
[ProtoContract]
|
||||
internal class PbMultiMsgTransmit
|
||||
{
|
||||
[ProtoMember(1)] public List<Message.Message> Msg { get; set; }
|
||||
|
||||
[ProtoMember(2)] public List<PbMultiMsgItem> PbItemList { get; set; }
|
||||
}
|
21
Lagrange.Core/Core/Packets/Action/Pb/PbPushMsg.cs
Normal file
21
Lagrange.Core/Core/Packets/Action/Pb/PbPushMsg.cs
Normal file
|
@ -0,0 +1,21 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action.Pb;
|
||||
|
||||
[ProtoContract]
|
||||
internal class PbPushMsg
|
||||
{
|
||||
[ProtoMember(1)] public Message.Message Msg { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int Svrip { get; set; }
|
||||
|
||||
[ProtoMember(3)] public byte[] PushToken { get; set; }
|
||||
|
||||
[ProtoMember(4)] public uint PingFlag { get; set; }
|
||||
|
||||
[ProtoMember(9)] public uint GeneralFlag { get; set; }
|
||||
|
||||
[ProtoMember(10)] public ulong BindUin { get; set; }
|
||||
}
|
20
Lagrange.Core/Core/Packets/Action/PushMessagePacket.cs
Normal file
20
Lagrange.Core/Core/Packets/Action/PushMessagePacket.cs
Normal file
|
@ -0,0 +1,20 @@
|
|||
using Lagrange.Core.Core.Packets.System;
|
||||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action;
|
||||
|
||||
[ProtoContract]
|
||||
internal class PushMessagePacket
|
||||
{
|
||||
[ProtoMember(1)] public Message.Message Message { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int Status { get; set; }
|
||||
|
||||
[ProtoMember(4)] public NTSysEvent NtEvent { get; set; }
|
||||
|
||||
[ProtoMember(5)] public int PingFLag { get; set; }
|
||||
|
||||
[ProtoMember(9)] public int GeneralFlag { get; set; }
|
||||
}
|
37
Lagrange.Core/Core/Packets/Action/SendMessageRequest.cs
Normal file
37
Lagrange.Core/Core/Packets/Action/SendMessageRequest.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using Lagrange.Core.Core.Packets.Message;
|
||||
using Lagrange.Core.Core.Packets.Message.Routing;
|
||||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action;
|
||||
|
||||
[ProtoContract]
|
||||
internal class SendMessageRequest
|
||||
{
|
||||
[ProtoMember(1)] public int State { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int SizeCache { get; set; }
|
||||
|
||||
[ProtoMember(3)] public byte[] UnknownFields { get; set; }
|
||||
|
||||
[ProtoMember(4)] public RoutingHead RoutingHead { get; set; }
|
||||
|
||||
[ProtoMember(5)] public ContentHead ContentHead { get; set; }
|
||||
|
||||
[ProtoMember(6)] public MessageBody MessageBody { get; set; }
|
||||
|
||||
[ProtoMember(7)] public int MsgSeq { get; set; }
|
||||
|
||||
[ProtoMember(8)] public int MsgRand { get; set; }
|
||||
|
||||
[ProtoMember(9)] public byte[] SyncCookie { get; set; }
|
||||
|
||||
[ProtoMember(10)] public int MsgVia { get; set; }
|
||||
|
||||
[ProtoMember(11)] public int DataStatist { get; set; }
|
||||
|
||||
[ProtoMember(12)] public MessageControl MessageControl { get; set; }
|
||||
|
||||
[ProtoMember(13)] public int MultiSendSeq { get; set; }
|
||||
}
|
13
Lagrange.Core/Core/Packets/Action/SendMessageResponse.cs
Normal file
13
Lagrange.Core/Core/Packets/Action/SendMessageResponse.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action;
|
||||
|
||||
[ProtoContract]
|
||||
internal class SendMessageResponse
|
||||
{
|
||||
[ProtoMember(1)] public int Result { get; set; }
|
||||
|
||||
[ProtoMember(2)] public string ErrMsg { get; set; }
|
||||
}
|
25
Lagrange.Core/Core/Packets/Action/SyncCookie.cs
Normal file
25
Lagrange.Core/Core/Packets/Action/SyncCookie.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Action;
|
||||
|
||||
[ProtoContract]
|
||||
internal class SyncCookie
|
||||
{
|
||||
[ProtoMember(1)] public long Time1 { get; set; }
|
||||
|
||||
[ProtoMember(2)] public long Time { get; set; }
|
||||
|
||||
[ProtoMember(3)] public long Ran1 { get; set; }
|
||||
|
||||
[ProtoMember(4)] public long Ran2 { get; set; }
|
||||
|
||||
[ProtoMember(5)] public long Const1 { get; set; }
|
||||
|
||||
[ProtoMember(11)] public long Const2 { get; set; }
|
||||
|
||||
[ProtoMember(12)] public long Const3 { get; set; }
|
||||
|
||||
[ProtoMember(13)] public long LastSyncTime { get; set; }
|
||||
|
||||
[ProtoMember(14)] public long Const4 { get; set; }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.Ecdh.Plain;
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoKeyExchangeDecrypted
|
||||
{
|
||||
[ProtoMember(1)] public byte[] GcmKey { get; set; }
|
||||
|
||||
[ProtoMember(2)] public byte[] Sign { get; set; }
|
||||
|
||||
[ProtoMember(3)] public uint Expiration { get; set; }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.Ecdh.Plain;
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoKeyExchangePlain
|
||||
{
|
||||
[ProtoMember(1)] public string? Uin { get; set; }
|
||||
|
||||
[ProtoMember(2)] public byte[]? Guid { get; set; }
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using Lagrange.Core.Utility.Binary;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.Ecdh.Plain;
|
||||
|
||||
/// <summary>
|
||||
/// Sha256 Encrypt this packet to get content of GcmCalc2, Key is constant
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class SsoKeyExchangePlain2
|
||||
{
|
||||
[BinaryProperty(BinaryPacket.Prefix.None)] public byte[] PublicKey { get; set; }
|
||||
|
||||
[BinaryProperty] public uint Type { get; set; }
|
||||
|
||||
[BinaryProperty(BinaryPacket.Prefix.None)] public byte[] EncryptedGcm { get; set; }
|
||||
|
||||
[BinaryProperty] public uint Const { get; set; } = 0;
|
||||
|
||||
[BinaryProperty] public uint Timestamp { get; set; }
|
||||
}
|
22
Lagrange.Core/Core/Packets/Login/Ecdh/SsoKeyExchange.cs
Normal file
22
Lagrange.Core/Core/Packets/Login/Ecdh/SsoKeyExchange.cs
Normal file
|
@ -0,0 +1,22 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.Ecdh;
|
||||
|
||||
/// <summary>
|
||||
/// Request for trpc.login.ecdh.EcdhService.SsoKeyExchange
|
||||
/// </summary>
|
||||
[ProtoContract]
|
||||
internal class SsoKeyExchange
|
||||
{
|
||||
[ProtoMember(1)] public byte[] PubKey { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int Type { get; set; }
|
||||
|
||||
[ProtoMember(3)] public byte[] GcmCalc1 { get; set; }
|
||||
|
||||
[ProtoMember(4)] public long Timestamp { get; set; }
|
||||
|
||||
[ProtoMember(5)] public byte[] GcmCalc2 { get; set; }
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.Ecdh;
|
||||
|
||||
/// <summary>
|
||||
/// Response for trpc.login.ecdh.EcdhService.SsoKeyExchange
|
||||
/// </summary>
|
||||
[ProtoContract]
|
||||
internal class SsoKeyExchangeResponse
|
||||
{
|
||||
[ProtoMember(1)] public byte[] GcmEncrypted { get; set; }
|
||||
|
||||
[ProtoMember(2)] public byte[] Body { get; set; } // 腾讯你个傻逼这什么东西啊怎么还带Tag是0的Member
|
||||
|
||||
[ProtoMember(3)] public byte[] PublicKey { get; set; }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin;
|
||||
|
||||
public class NTLoginHttpRequest
|
||||
{
|
||||
[JsonPropertyName("appid")] public long Appid { get; set; }
|
||||
|
||||
[JsonPropertyName("faceUpdateTime")] public long FaceUpdateTime { get; set; }
|
||||
|
||||
[JsonPropertyName("qrsig")] public string Qrsig { get; set; }
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin;
|
||||
|
||||
public class NTLoginHttpResponse
|
||||
{
|
||||
[JsonPropertyName("retCode")] public int RetCode { get; set; }
|
||||
|
||||
[JsonPropertyName("errMsg")] public string ErrMsg { get; set; }
|
||||
|
||||
[JsonPropertyName("qrSig")] public string QrSig { get; set; }
|
||||
|
||||
[JsonPropertyName("uin")] public uint Uin { get; set; }
|
||||
|
||||
[JsonPropertyName("faceUrl")] public string FaceUrl { get; set; }
|
||||
|
||||
[JsonPropertyName("faceUpdateTime")] public long FaceUpdateTime { get; set; }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Body;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginEasyLogin
|
||||
{
|
||||
[ProtoMember(1)] public byte[]? TempPassword { get; set; }
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using Lagrange.Core.Utility.Binary;
|
||||
using static Lagrange.Core.Utility.Binary.BinaryPacket;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Body;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
||||
/// <summary>
|
||||
/// Should be serialized with <see cref="BinarySerializer"/> and encrypted with Tea, key to be investigated
|
||||
/// </summary>
|
||||
internal class SsoNTLoginPasswordLogin
|
||||
{
|
||||
[BinaryProperty] public ushort Const4 { get; set; } = 4;
|
||||
|
||||
[BinaryProperty] public uint Random { get; set; }
|
||||
|
||||
[BinaryProperty] public uint UnknownConst2 { get; set; } = 0;
|
||||
|
||||
[BinaryProperty] public uint Ver { get; set; } = 8001;
|
||||
|
||||
[BinaryProperty] public uint AppId { get; set; }
|
||||
|
||||
[BinaryProperty] public ushort UnknownConst3 { get; set; } = 0;
|
||||
|
||||
[BinaryProperty] public uint Uin { get; set; }
|
||||
|
||||
[BinaryProperty] public uint Timestamp { get; set; }
|
||||
|
||||
[BinaryProperty] public uint UnknownConst4 { get; set; } = 0;
|
||||
|
||||
[BinaryProperty] public byte Flag { get; set; } = 1;
|
||||
|
||||
[BinaryProperty(Prefix.None)] public byte[] PasswordMd5 { get; set; }
|
||||
|
||||
[BinaryProperty(Prefix.None)] public byte[] RandomBytes { get; set; }
|
||||
|
||||
[BinaryProperty] public uint UnknownConst5 { get; set; } = 0;
|
||||
|
||||
[BinaryProperty] public byte Flag2 { get; set; } = 1;
|
||||
|
||||
[BinaryProperty(Prefix.None)] public byte[] Guid { get; set; }
|
||||
|
||||
[BinaryProperty] public uint UnknownConst6 { get; set; } = 1;
|
||||
|
||||
[BinaryProperty] public uint UnknownConst7 { get; set; } = 1;
|
||||
|
||||
[BinaryProperty(Prefix.Uint16 | Prefix.LengthOnly)] public string UinString { get; set; }
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain;
|
||||
|
||||
// Resharper disable InconsistentNaming
|
||||
|
||||
/// <summary>
|
||||
/// <para>Base for trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLogin and trpc.login.ecdh.EcdhService.SsoNTLoginEasyLogin</para>
|
||||
/// <para>Should be encrypted and put into <see cref="SsoNTLoginEncryptedData"/></para>
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Body Type</typeparam>
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginBase<T> where T : class
|
||||
{
|
||||
[ProtoMember(1)] public SsoNTLoginHeader? Header { get; set; }
|
||||
|
||||
[ProtoMember(2)] public T? Body { get; set; }
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
using Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginHeader
|
||||
{
|
||||
[ProtoMember(1)] public SsoNTLoginUin? Uin { get; set; }
|
||||
|
||||
[ProtoMember(2)] public SsoNTLoginSystem? System { get; set; }
|
||||
|
||||
[ProtoMember(3)] public SsoNTLoginVersion? Version { get; set; }
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
using Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
||||
/// <summary>
|
||||
/// Response for trpc.login.ecdh.EcdhService.SsoNTLoginPasswordLogin
|
||||
/// </summary>
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginResponse
|
||||
{
|
||||
[ProtoMember(1)] public SsoNTLoginCredentials? Credentials { get; set; }
|
||||
|
||||
[ProtoMember(3)] public SsoNTLoginUnusual? Unusual { get; set; }
|
||||
|
||||
[ProtoMember(4)] public SsoNTLoginUid? Uid { get; set; }
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginCredentials
|
||||
{
|
||||
[ProtoMember(3)] public byte[] TempPassword { get; set; }
|
||||
|
||||
[ProtoMember(4)] public byte[] Tgt { get; set; }
|
||||
|
||||
[ProtoMember(5)] public byte[] D2 { get; set; }
|
||||
|
||||
[ProtoMember(6)] public byte[] D2Key { get; set; }
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginSystem
|
||||
{
|
||||
[ProtoMember(1)] public string? Os { get; set; }
|
||||
|
||||
[ProtoMember(2)] public string? DeviceName { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int Type { get; set; } = 7; // ?
|
||||
|
||||
[ProtoMember(4)] public byte[]? Guid { get; set; }
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginUid
|
||||
{
|
||||
[ProtoMember(2)] public string Uid { get; set; }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginUin
|
||||
{
|
||||
[ProtoMember(1)] public string? Uin { get; set; }
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginUnusual
|
||||
{
|
||||
[ProtoMember(2)] public byte[]? Sig { get; set; }
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin.Plain.Universal;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginVersion
|
||||
{
|
||||
[ProtoMember(1)] public string? KernelVersion { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int AppId { get; set; }
|
||||
|
||||
[ProtoMember(3)] public string? PackageName { get; set; }
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
using Lagrange.Core.Core.Packets.Login.Ecdh.Plain;
|
||||
using ProtoBuf;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.NTLogin;
|
||||
|
||||
[ProtoContract]
|
||||
internal class SsoNTLoginEncryptedData
|
||||
{
|
||||
/// <summary>From <see cref="SsoKeyExchangeDecrypted"/> Sign field, just simply store that in keystore</summary>
|
||||
[ProtoMember(1)] public byte[]? Sign { get; set; }
|
||||
|
||||
[ProtoMember(3)] public byte[]? GcmCalc { get; set; }
|
||||
|
||||
[ProtoMember(4)] public int Type { get; set; }
|
||||
}
|
68
Lagrange.Core/Core/Packets/Login/WtLogin/Entity/Login.cs
Normal file
68
Lagrange.Core/Core/Packets/Login/WtLogin/Entity/Login.cs
Normal file
|
@ -0,0 +1,68 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Packets.Tlv;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
using Lagrange.Core.Utility.Binary.Tlv;
|
||||
using Lagrange.Core.Utility.Extension;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.WtLogin.Entity;
|
||||
|
||||
internal class Login : WtLoginBase
|
||||
{
|
||||
private const string PacketCommand = "wtlogin.login";
|
||||
|
||||
private const ushort WtLoginCommand = 2064;
|
||||
|
||||
private const ushort InternalCommand = 0x09;
|
||||
|
||||
private static readonly ushort[] ConstructTlvs =
|
||||
{
|
||||
0x106, 0x144, 0x116, 0x142, 0x145, 0x018, 0x141, 0x177, 0x191, 0x100, 0x107, 0x318, 0x16A, 0x166, 0x521
|
||||
};
|
||||
|
||||
public Login(BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(PacketCommand, WtLoginCommand, keystore, appInfo, device) { }
|
||||
|
||||
protected override BinaryPacket ConstructBody()
|
||||
{
|
||||
var packet = new BinaryPacket()
|
||||
.WriteUshort(InternalCommand, false)
|
||||
.WritePacket(TlvPacker.Pack(ConstructTlvs));
|
||||
|
||||
Console.WriteLine(packet.ToArray().Hex());
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static Dictionary<ushort, TlvBody> Deserialize(BinaryPacket packet, BotKeystore keystore, out State state)
|
||||
{
|
||||
packet = DeserializePacket(keystore, packet);
|
||||
|
||||
ushort command = packet.ReadUshort(false);
|
||||
if (command != InternalCommand) throw new Exception("Invalid command");
|
||||
|
||||
state = (State)packet.ReadByte();
|
||||
if (state == State.Success)
|
||||
{
|
||||
var tlvs = TlvPacker.ReadTlvCollections(packet);
|
||||
if (tlvs[0x119] is Tlv119 tlv119)
|
||||
{
|
||||
var decrypted = keystore.TeaImpl.Decrypt(tlv119.EncryptedTlv, keystore.Stub.TgtgtKey);
|
||||
var tlv119Packet = new BinaryPacket(decrypted);
|
||||
return TlvPacker.ReadTlvCollections(tlv119Packet);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return TlvPacker.ReadTlvCollections(packet);
|
||||
}
|
||||
|
||||
return new Dictionary<ushort, TlvBody>();
|
||||
}
|
||||
|
||||
public enum State : byte
|
||||
{
|
||||
Success = 0,
|
||||
Slider = 2,
|
||||
SmsRequired = 160,
|
||||
}
|
||||
}
|
67
Lagrange.Core/Core/Packets/Login/WtLogin/Entity/TransEmp.cs
Normal file
67
Lagrange.Core/Core/Packets/Login/WtLogin/Entity/TransEmp.cs
Normal file
|
@ -0,0 +1,67 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.WtLogin.Entity;
|
||||
|
||||
internal abstract class TransEmp : WtLoginBase
|
||||
{
|
||||
private readonly ushort _qrCodeCommand;
|
||||
|
||||
private const string PacketCommand = "wtlogin.trans_emp";
|
||||
private const ushort WtLoginCommand = 2066;
|
||||
|
||||
protected TransEmp(ushort qrCmd, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(PacketCommand, WtLoginCommand, keystore, appInfo, device)
|
||||
{
|
||||
_qrCodeCommand = qrCmd;
|
||||
}
|
||||
|
||||
protected override BinaryPacket ConstructBody()
|
||||
{
|
||||
var packet = new BinaryPacket().WriteByte(0); // known const
|
||||
|
||||
packet.Barrier(typeof(ushort), () =>
|
||||
{
|
||||
var writer = new BinaryPacket()
|
||||
.WriteUint((uint)AppInfo.AppId, false)
|
||||
.WriteUint(0x00000072, false) // const
|
||||
.WriteUshort(0, false) // const 0
|
||||
.WriteByte(0) // const 0
|
||||
.WriteUint((uint)DateTimeOffset.Now.ToUnixTimeSeconds(), false) // length actually starts here
|
||||
.WriteByte(0x02); // header for packet, counted into length of next barrier manually
|
||||
|
||||
writer.Barrier(typeof(ushort), () => new BinaryPacket()
|
||||
.WriteUshort(_qrCodeCommand, false)
|
||||
.WriteUlong(0, false) // const 0
|
||||
.WriteUint(0, false) // const 0
|
||||
.WriteUlong(0, false) // const 0
|
||||
.WriteUshort(3, false) // const 3
|
||||
.WriteUshort(0, false) // const 0
|
||||
.WriteUshort(50, false) // unknown const
|
||||
.WriteUlong(0, false)
|
||||
.WriteUint(0, false)
|
||||
.WriteUshort(0, false)
|
||||
.WriteUint((uint)AppInfo.AppId, false)
|
||||
.WritePacket(ConstructTransEmp()), false, true, 1);
|
||||
|
||||
return writer;
|
||||
}, false, true, -13);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static BinaryPacket DeserializeBody(BotKeystore keystore, BinaryPacket packet, out ushort command)
|
||||
{
|
||||
packet = DeserializePacket(keystore, packet);
|
||||
|
||||
uint packetLength = packet.ReadUint(false);
|
||||
packet.Skip(4); // misc unknown data
|
||||
command = packet.ReadUshort(false);
|
||||
packet.Skip(40);
|
||||
uint appId = packet.ReadUint(false);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
protected abstract BinaryPacket ConstructTransEmp();
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
using Lagrange.Core.Utility.Binary.Tlv;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.WtLogin.Entity;
|
||||
|
||||
internal class TransEmp12 : TransEmp
|
||||
{
|
||||
private const ushort QrCodeCommand = 0x12;
|
||||
|
||||
public TransEmp12(BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(QrCodeCommand, keystore, appInfo, device) { }
|
||||
|
||||
protected override BinaryPacket ConstructTransEmp()
|
||||
{
|
||||
if (Keystore.Session.QrSign != null)
|
||||
{
|
||||
return new BinaryPacket()
|
||||
.WriteBytes(Keystore.Session.QrSign, BinaryPacket.Prefix.Uint16 | BinaryPacket.Prefix.LengthOnly)
|
||||
.WriteUlong(0, false) // const 0
|
||||
.WriteUint(0, false) // const 0
|
||||
.WriteByte(0) // const 0
|
||||
.WriteByte(0x03); // packet end
|
||||
}
|
||||
|
||||
throw new Exception("QrSign is null");
|
||||
}
|
||||
|
||||
public static Dictionary<ushort, TlvBody> Deserialize(BinaryPacket packet, out State qrState)
|
||||
{
|
||||
qrState = (State)packet.ReadByte();
|
||||
if (qrState == State.Confirmed)
|
||||
{
|
||||
packet.Skip(12); // misc unknown data
|
||||
return TlvPacker.ReadTlvCollections(packet, true);
|
||||
}
|
||||
|
||||
return new Dictionary<ushort, TlvBody>();
|
||||
}
|
||||
|
||||
internal enum State : byte
|
||||
{
|
||||
Confirmed = 0,
|
||||
CodeExpired = 17,
|
||||
WaitingForScan = 48,
|
||||
WaitingForConfirm = 53,
|
||||
Canceled = 54,
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Core.Packets.Tlv;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
using Lagrange.Core.Utility.Binary.Tlv;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.WtLogin.Entity;
|
||||
|
||||
internal class TransEmp31 : TransEmp
|
||||
{
|
||||
private const ushort QrCodeCommand = 0x31;
|
||||
|
||||
private static readonly ushort[] ConstructTlvs =
|
||||
{
|
||||
0x016, 0x01B, 0x01D, 0x033, 0x035, 0x066, 0x0D1
|
||||
};
|
||||
|
||||
private static readonly ushort[] ConstructTlvsPassword =
|
||||
{
|
||||
0x011, 0x016, 0x01B, 0x01D, 0x033, 0x035, 0x066, 0x0D1
|
||||
};
|
||||
|
||||
public TransEmp31(BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
: base(QrCodeCommand, keystore, appInfo, device) { }
|
||||
|
||||
protected override BinaryPacket ConstructTransEmp() => new BinaryPacket()
|
||||
.WriteUshort(0, false)
|
||||
.WriteUlong(0, false)
|
||||
.WriteByte(0)
|
||||
.WritePacket(TlvPacker.PackQrCode(Keystore.Session.UnusualSign == null ? ConstructTlvs : ConstructTlvsPassword))
|
||||
.WriteByte(0x03);
|
||||
|
||||
public static Dictionary<ushort, TlvBody> Deserialize(BinaryPacket packet, BotKeystore keystore, out byte[] signature)
|
||||
{
|
||||
byte dummy = packet.ReadByte();
|
||||
signature = packet.ReadBytes(BinaryPacket.Prefix.Uint16 | BinaryPacket.Prefix.LengthOnly);
|
||||
keystore.Session.QrSign = signature;
|
||||
|
||||
return TlvPacker.ReadTlvCollections(packet, true);
|
||||
}
|
||||
}
|
78
Lagrange.Core/Core/Packets/Login/WtLogin/WtLoginBase.cs
Normal file
78
Lagrange.Core/Core/Packets/Login/WtLogin/WtLoginBase.cs
Normal file
|
@ -0,0 +1,78 @@
|
|||
using Lagrange.Core.Common;
|
||||
using Lagrange.Core.Utility.Binary;
|
||||
using Lagrange.Core.Utility.Binary.Tlv;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Login.WtLogin;
|
||||
|
||||
internal abstract class WtLoginBase
|
||||
{
|
||||
protected readonly string Command;
|
||||
protected readonly ushort Cmd;
|
||||
protected readonly BotKeystore Keystore;
|
||||
protected readonly BotAppInfo AppInfo;
|
||||
protected readonly BotDeviceInfo Device;
|
||||
|
||||
protected readonly TlvPacker TlvPacker;
|
||||
|
||||
protected WtLoginBase(string command, ushort cmd, BotKeystore keystore, BotAppInfo appInfo, BotDeviceInfo device)
|
||||
{
|
||||
Command = command;
|
||||
Cmd = cmd;
|
||||
Keystore = keystore;
|
||||
AppInfo = appInfo;
|
||||
Device = device;
|
||||
TlvPacker = new TlvPacker(appInfo, keystore, device);
|
||||
}
|
||||
|
||||
public BinaryPacket ConstructPacket()
|
||||
{
|
||||
var body = ConstructBody();
|
||||
var encrypt = Keystore.SecpImpl.Encrypt(body.ToArray());
|
||||
|
||||
var packet = new BinaryPacket().WriteByte(2); // packet start
|
||||
|
||||
packet.Barrier(typeof(ushort), () => new BinaryPacket()
|
||||
.WriteUshort(8001, false) // ver
|
||||
.WriteUshort(Cmd, false) // cmd: wtlogin.trans_emp: 2066, wtlogin.login: 2064
|
||||
.WriteUshort(Keystore.Session.Sequence, false) // unique wtLoginSequence for wtlogin packets only, should be stored in KeyStore
|
||||
.WriteUint(Keystore.Uin, false) // uin, 0 for wtlogin.trans_emp
|
||||
.WriteByte(3) // extVer
|
||||
.WriteByte(135) // cmdVer
|
||||
.WriteUint(0, false) // actually unknown const 0
|
||||
.WriteByte(19) // pubId
|
||||
.WriteUshort(0, false) // insId
|
||||
.WriteUshort(AppInfo.AppClientVersion, false) // cliType
|
||||
.WriteUint(0, false) // retryTime
|
||||
.WriteByte(1) // const
|
||||
.WriteByte(1) // const
|
||||
.WriteBytes(Keystore.Stub.RandomKey.AsSpan()) // randKey
|
||||
.WriteUshort(0x102, false) // unknown const, 腾讯你妈妈死啦
|
||||
.WriteBytes(Keystore.SecpImpl.GetPublicKey(), BinaryPacket.Prefix.Uint16 | BinaryPacket.Prefix.LengthOnly) // pubKey
|
||||
.WriteBytes(encrypt.AsSpan())
|
||||
.WriteByte(3), false, true, 1); // 0x03 is the packet end
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
protected static BinaryPacket DeserializePacket(BotKeystore keystore, BinaryPacket packet)
|
||||
{
|
||||
uint packetLength = packet.ReadUint(false);
|
||||
if (packet.ReadByte() != 0x02) return new BinaryPacket(); // packet header
|
||||
|
||||
ushort internalLength = packet.ReadUshort(false);
|
||||
ushort ver = packet.ReadUshort(false);
|
||||
ushort cmd = packet.ReadUshort(false);
|
||||
ushort sequence = packet.ReadUshort(false);
|
||||
uint uin = packet.ReadUint(false);
|
||||
byte flag = packet.ReadByte();
|
||||
ushort retryTime = packet.ReadUshort(false);
|
||||
|
||||
var encrypted = packet.ReadBytes((int)(packet.Remaining - 1));
|
||||
var decrypted = new BinaryPacket(keystore.SecpImpl.Decrypt(encrypted));
|
||||
if (packet.ReadByte() != 0x03) throw new Exception("Packet end not found"); // packet end
|
||||
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
protected abstract BinaryPacket ConstructBody();
|
||||
}
|
19
Lagrange.Core/Core/Packets/Message/C2C/C2C.cs
Normal file
19
Lagrange.Core/Core/Packets/Message/C2C/C2C.cs
Normal file
|
@ -0,0 +1,19 @@
|
|||
using ProtoBuf;
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Message.C2C;
|
||||
|
||||
[ProtoContract]
|
||||
internal class C2C
|
||||
{
|
||||
[ProtoMember(1)] public uint? Uin { get; set; }
|
||||
|
||||
[ProtoMember(2)] public string? Uid { get; set; }
|
||||
|
||||
[ProtoMember(3)] public uint? Field3 { get; set; }
|
||||
|
||||
[ProtoMember(4)] public uint? Sig { get; set; }
|
||||
|
||||
[ProtoMember(5)] public uint? ReceiverUin { get; set; }
|
||||
|
||||
[ProtoMember(6)] public string? ReceiverUid { get; set; }
|
||||
}
|
33
Lagrange.Core/Core/Packets/Message/C2C/C2CMsgInfo.cs
Normal file
33
Lagrange.Core/Core/Packets/Message/C2C/C2CMsgInfo.cs
Normal file
|
@ -0,0 +1,33 @@
|
|||
using Lagrange.Core.Core.Packets.Message.Routing;
|
||||
using ProtoBuf;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Message.C2C;
|
||||
|
||||
[ProtoContract]
|
||||
internal class C2CMsgInfo
|
||||
{
|
||||
[ProtoMember(1)] public ulong FromUin { get; set; }
|
||||
|
||||
[ProtoMember(2)] public ulong ToUin { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int MsgSeq { get; set; }
|
||||
|
||||
[ProtoMember(4)] public long MsgUid { get; set; }
|
||||
|
||||
[ProtoMember(5)] public long MsgTime { get; set; }
|
||||
|
||||
[ProtoMember(6)] public int MsgRandom { get; set; }
|
||||
|
||||
[ProtoMember(7)] public int PkgNum { get; set; }
|
||||
|
||||
[ProtoMember(8)] public int PkgIndex { get; set; }
|
||||
|
||||
[ProtoMember(9)] public int DivSeq { get; set; }
|
||||
|
||||
[ProtoMember(10)] public int MsgType { get; set; }
|
||||
|
||||
[ProtoMember(20)] public RoutingHead RoutingHead { get; set; }
|
||||
}
|
18
Lagrange.Core/Core/Packets/Message/C2C/C2CMsgWithDrawReq.cs
Normal file
18
Lagrange.Core/Core/Packets/Message/C2C/C2CMsgWithDrawReq.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using ProtoBuf;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Message.C2C;
|
||||
|
||||
[ProtoContract]
|
||||
internal class C2CMsgWithDrawReq
|
||||
{
|
||||
[ProtoMember(1)] public C2CMsgInfo[] MsgInfo { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int LongMessageFlag { get; set; }
|
||||
|
||||
[ProtoMember(3)] public byte[] Reserved { get; set; }
|
||||
|
||||
[ProtoMember(4)] public int SubCmd { get; set; }
|
||||
}
|
14
Lagrange.Core/Core/Packets/Message/C2C/C2CMsgWithDrawResp.cs
Normal file
14
Lagrange.Core/Core/Packets/Message/C2C/C2CMsgWithDrawResp.cs
Normal file
|
@ -0,0 +1,14 @@
|
|||
using ProtoBuf;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Message.C2C;
|
||||
|
||||
[ProtoContract]
|
||||
internal class C2CMsgWithDrawResp
|
||||
{
|
||||
[ProtoMember(1)] public int Result { get; set; }
|
||||
|
||||
[ProtoMember(2)] public string ErrMsg { get; set; }
|
||||
}
|
32
Lagrange.Core/Core/Packets/Message/C2C/C2CTempMessageHead.cs
Normal file
32
Lagrange.Core/Core/Packets/Message/C2C/C2CTempMessageHead.cs
Normal file
|
@ -0,0 +1,32 @@
|
|||
using ProtoBuf;
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Message.C2C;
|
||||
|
||||
[ProtoContract]
|
||||
internal class C2CTempMessageHead
|
||||
{
|
||||
[ProtoMember(1)] public ulong GroupUin { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int C2CType { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int ServiceType { get; set; }
|
||||
|
||||
[ProtoMember(4)] public string Card { get; set; }
|
||||
|
||||
[ProtoMember(5)] public byte[] Sig { get; set; }
|
||||
|
||||
[ProtoMember(6)] public int SigType { get; set; }
|
||||
|
||||
[ProtoMember(7)] public string FromPhone { get; set; }
|
||||
|
||||
[ProtoMember(8)] public string ToPhone { get; set; }
|
||||
|
||||
[ProtoMember(9)] public int LockDisplay { get; set; }
|
||||
|
||||
[ProtoMember(10)] public int DirectionFlag { get; set; }
|
||||
|
||||
[ProtoMember(11)] public byte[] Reserved { get; set; }
|
||||
}
|
29
Lagrange.Core/Core/Packets/Message/Component/Attr.cs
Normal file
29
Lagrange.Core/Core/Packets/Message/Component/Attr.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using ProtoBuf;
|
||||
|
||||
#pragma warning disable CS8618
|
||||
|
||||
namespace Lagrange.Core.Core.Packets.Message.Component;
|
||||
|
||||
[ProtoContract]
|
||||
internal class Attr
|
||||
{
|
||||
[ProtoMember(1)] public int CodePage { get; set; }
|
||||
|
||||
[ProtoMember(2)] public int Time { get; set; }
|
||||
|
||||
[ProtoMember(3)] public int Random { get; set; }
|
||||
|
||||
[ProtoMember(4)] public int Color { get; set; }
|
||||
|
||||
[ProtoMember(5)] public int Size { get; set; }
|
||||
|
||||
[ProtoMember(6)] public int Effect { get; set; }
|
||||
|
||||
[ProtoMember(7)] public int CharSet { get; set; }
|
||||
|
||||
[ProtoMember(8)] public int PitchAndFamily { get; set; }
|
||||
|
||||
[ProtoMember(9)] public string FontName { get; set; }
|
||||
|
||||
[ProtoMember(10)] public byte[] ReserveData { get; set; }
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue