Initial commit for Public repository

This commit is contained in:
Linwenxuan05 2023-06-28 11:24:41 +08:00
commit 47065e0b69
339 changed files with 11241 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

View 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>

View 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; } = "";
}

View 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));
}
}

View 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);
}
}

View 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();
}
}

View 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;
}
}
}

View 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];
}

View 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
View 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

View 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

View 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();
}
}

View 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 },
};
}

View 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
}

View 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"
};
}

View 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}";
}
}

View 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);
}

View 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;
}

View 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);
}

View File

@ -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;
}
}

View 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;
}
}

View 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;
}
}

View 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;
}
}

View 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));
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View 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();
}

View 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);
}
}

View File

@ -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());
}
}

View 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;
}
}

View 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;
}
}

View 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

View 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}";
}

View File

@ -0,0 +1,6 @@
namespace Lagrange.Core.Core.Event.EventArg;
public class BotOfflineEvent : EventBase
{
}

View File

@ -0,0 +1,6 @@
namespace Lagrange.Core.Core.Event.EventArg;
public class BotOnlineEvent : EventBase
{
}

View 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}";
}
}

View 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;
}

View 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();
}
}

View 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);
}
}

View File

@ -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();
}
}

View 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);
}

View File

@ -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);
}
}

View 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
}
}

View File

@ -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);
}

View File

@ -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)
{
}
}

View 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;
}

View 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);
}

View 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);
}

View 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);
}

View 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);
}

View File

@ -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);
}

View 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);
}

View File

@ -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();
}
}
}

View 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);
}

View 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);
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View 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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View File

@ -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; }
}

View 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,
}
}

View 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();
}

View File

@ -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,
}
}

View File

@ -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);
}
}

View 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();
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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; }
}

View 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