Переглянути джерело

完成 tlv 部分,tlv400 之上的无需完成

akemimadoka 6 роки тому
батько
коміт
d7fdf4f15d
12 змінених файлів з 1097 додано та 86 видалено
  1. 3 0
      CMakeLists.txt
  2. 139 0
      Docs/libcodecwrapperV2.h
  3. 6 6
      YumeBot.Test/JceTest.cpp
  4. 3 1
      YumeBot/CMakeLists.txt
  5. 4 10
      YumeBot/Jce.cpp
  6. 8 13
      YumeBot/Jce.h
  7. 21 0
      YumeBot/Misc.h
  8. 9 0
      YumeBot/Tlv.cpp
  9. 822 4
      YumeBot/Tlv.h
  10. 60 32
      YumeBot/Utility.h
  11. 16 14
      YumeBot/Wup.cpp
  12. 6 6
      YumeBot/Wup.h

+ 3 - 0
CMakeLists.txt

@@ -10,6 +10,9 @@ endif()
 
 include(${CMAKE_BINARY_DIR}/conan.cmake)
 
+conan_add_remote(NAME Cafe
+    URL https://api.bintray.com/conan/chino/Cafe)
+
 conan_cmake_run(CONANFILE conanfile.txt
                 BASIC_SETUP CMAKE_TARGETS
                 BUILD missing

+ 139 - 0
Docs/libcodecwrapperV2.h

@@ -0,0 +1,139 @@
+#include <string>
+#include <vector>
+#include <jni.h>
+
+class CCodecWarpper
+{
+public:
+    CCodecWarpper(JavaVM *vm);
+    virtual ~CCodecWarpper();
+
+	std::uint32_t CreatePhSigLcIdCheckPacket(std::string&, int, std::vector<std::string>&, const char*);
+	int FixAppid();
+	void ParseOtherResp(_JNIEnv*, int, CSSOData const&, int);
+	void ParsePhSigCheck(_JNIEnv*, CSSOData const&);
+	void ParseRecvData(_JNIEnv*);
+	void encodeRequest(_JNIEnv*, int, _jstring*, _jstring*, _jstring*, _jstring*, _jstring*,
+	                   _jbyteArray*, int, int, _jstring*, signed char, signed char, _jbyteArray*,
+	                   uchar);
+	jbyteArray getFileStoreKey(_JNIEnv*);
+
+	std::int32_t getMaxPackageSize()
+	{
+		return m_MaxPackageSize;
+	}
+
+private:
+    std::uint32_t m_UnknownInt = 0x0000C350;	// 0x04
+    std::string m_UnknownStr;					// 0x08
+    std::uint32_t m_SignState = 0;				// 0x20
+    JavaVM* m_Vm;								// 0x24
+	// 指向 com.tencent.qphone.base.util.CodecWarpper
+    jclass m_CodecWarpper;						// 0x28
+	// 指向 android.content.Context 的实例,当前的 context
+    jobject m_Context;							// 0x2C
+	// 指向 com.tencent.qphone.base.remote.FromServiceMsg
+    jclass m_FromServiceMsg;					// 0x30
+    std::vector<int> m_UnknownVec;				// 0x34
+    std::int32_t m_MaxPackageSize = 0x00100000;	// 0x40
+    std::uint32_t m_UnknownInt2 = 0x21;			// 0x44
+    std::string m_Ksid;							// 0x48
+};
+
+// sizeof(CCodecWarpper) == 0x60
+
+class CSSOHead
+{
+public:
+    virtual ~CSSOHead();
+
+private:
+    std::uint8_t m_UnknownByte = 2;     // 0x04
+    std::uint32_t m_KSSOVersion = 8;    // 0x08
+    std::string m_D2 = "";      // 0x0C
+    std::uint8_t m_UnknownByte2 = 0;    // 0x24
+    std::string m_Uin = "";     // 0x28
+                                // Uin 即用户的 qq 号
+};
+
+// sizeof(CSSOHead) == 0x40
+
+class CSSOReqHead
+{
+public:
+    CSSOReqHead();
+    virtual ~CSSOReqHead();
+
+    std::uint32_t Length();
+    std::uint32_t Length_ver9();
+    bool deSerialize()
+
+private:
+    std::int32_t m_SsoSeq;          // 0x04
+    std::int32_t m_AppId = -1;    // 0x08
+    std::int32_t m_MsfAppId = -1;    // 0x0C
+    std::uint8_t m_B2Value = 2;     // 0x10
+    std::uint8_t m_UnknownArray[0x0B]{} // 0x11
+    std::string m_A2 = "";      // 0x1C
+    std::string m_ServiceCmd = "";     // 0x34
+    std::string m_Data = "";     // 0x4C, m_Cookie?
+    std::string m_ImeiValue = "";     // 0x64
+    std::string m_ImsiValue = "";     // 0x7C
+    std::string m_TimeStat = "";     // 0x94
+    std::string m_Ksid = "";     // 0xAC
+    std::int32_t m_UnknownInt4 = 0;     // 0xC4
+    std::string m_UnknownStr8 = "";     // 0xC8
+    std::int32_t m_UnknownInt = 0;     // 0xE0
+};
+
+// sizeof(CSSOReqHead) == 0xE4
+
+class CSSOData
+{
+public:
+    virtual ~CSSOData();
+
+private:
+    CSSOHead m_Head;                // 0x0004
+    CSSOReqHead m_ReqHead;          // 0x0044
+    std::string m_WupBuffer = "";  // 0x0128
+    std::uint32_t m_UnknownInt = 0; // 0x0144
+};
+
+// sizeof(CSSOData) == 0x0148
+
+class CAuthData
+{
+public:
+    virtual ~CAuthData();
+
+private:
+    std::string m_A1;       // 0x04
+    std::string m_A2;      // 0x1C
+    std::string m_A3;      // 0x34
+    std::string m_D1;      // 0x4C
+    std::string m_D2;      // 0x64
+    std::string m_S2;      // 0x7C
+    std::string m_Key;      // 0x94
+    std::string m_UnknownData;      // 0xAC
+    std::string m_Sid;      // 0xC4
+    std::string m_UnknownData2;     // 0xDC
+};
+
+// sizeof(CAuthData) == 0xF4
+
+namespace KQQConfig
+{
+    class SignatureReq
+        : public taf::JceStructBase
+    {
+    public:
+    private:
+        std::vector<std::string> m_UnknownVecStr;   // 0x00
+        std::uint32_t m_AppId;                      // 0x0C
+        std::vector<std::string> m_UnknownVecStr2;  // 0x10
+        std::uint8_t m_UnknownByte;                 // 0x1C
+    };
+}
+
+// sizeof(KQQConfig::SignatureReq) == 0x1D?

+ 6 - 6
YumeBot.Test/JceTest.cpp

@@ -20,7 +20,7 @@ TEST_CASE("Jce", "[Jce]")
 		Cafe::Io::MemoryStream memoryStream;
 
 		{
-			JceOutputStream outputStream{ Cafe::Io::BinaryWriter(&memoryStream) };
+			JceOutputStream outputStream{ &memoryStream };
 			outputStream.Write(0, test);
 		}
 
@@ -29,7 +29,7 @@ TEST_CASE("Jce", "[Jce]")
 		std::shared_ptr<JceTest> ptr;
 
 		{
-			JceInputStream inputStream{ Cafe::Io::BinaryReader(&memoryStream) };
+			JceInputStream inputStream{ &memoryStream };
 			const auto readSucceed = inputStream.Read(0, ptr);
 			REQUIRE(readSucceed);
 		}
@@ -59,7 +59,7 @@ TEST_CASE("Jce", "[Jce]")
 		}
 
 		Cafe::Io::MemoryStream memoryStream;
-		uniAttribute.Encode(Cafe::Io::BinaryWriter(&memoryStream));
+		uniAttribute.Encode(&memoryStream);
 
 		const auto span = memoryStream.GetInternalStorage();
 
@@ -67,7 +67,7 @@ TEST_CASE("Jce", "[Jce]")
 
 		{
 			OldUniAttribute readAttribute{};
-			readAttribute.Decode(Cafe::Io::BinaryReader(&memoryStream));
+			readAttribute.Decode(&memoryStream);
 
 			std::int32_t intValue;
 			REQUIRE(readAttribute.Get(u8"SomeInt"_s, intValue));
@@ -96,13 +96,13 @@ TEST_CASE("Jce", "[Jce]")
 		packet.GetRequestPacket().SetsServantName(u8"ServantName?"_sv);
 
 		Cafe::Io::MemoryStream memoryStream;
-		packet.Encode(Cafe::Io::BinaryWriter(&memoryStream));
+		packet.Encode(&memoryStream);
 
 		memoryStream.SeekFromBegin(0);
 
 		{
 			UniPacket readPacket;
-			readPacket.Decode(Cafe::Io::BinaryReader(&memoryStream));
+			readPacket.Decode(&memoryStream);
 
 			const auto& attribute = readPacket.GetAttribute();
 			std::int32_t intValue;

+ 3 - 1
YumeBot/CMakeLists.txt

@@ -1,12 +1,14 @@
 set(SOURCE_FILES
     Cryptography.cpp
     Jce.cpp
+    Tlv.cpp
     Wup.cpp)
 
 set(HEADERS
     Cryptography.h
     Jce.h
     JceStructDef.h
+    Misc.h
     Tlv.h
     Utility.h
     Wup.h)
@@ -24,7 +26,7 @@ target_include_directories(YumeBot INTERFACE
     PRIVATE ${OPENSSL_INCLUDE_DIR})
 
 target_link_libraries(YumeBot
-    PUBLIC CONAN_PKG::Cafe
+    PUBLIC CONAN_PKG::Cafe CONAN_PKG::asio
     PRIVATE ${OPENSSL_LIBRARIES})
 
 install(TARGETS YumeBot

+ 4 - 10
YumeBot/Jce.cpp

@@ -8,12 +8,9 @@ JceStruct::~JceStruct()
 {
 }
 
-JceInputStream::JceInputStream(Cafe::Io::BinaryReader reader) : m_Reader{ std::move(reader) }
+JceInputStream::JceInputStream(Cafe::Io::InputStream* stream)
+    : m_Reader{ stream, std::endian::little }
 {
-	if (m_Reader.GetUsingEndian() != std::endian::little)
-	{
-		CAFE_THROW(JceDecodeException, u8"reader should use little endian."_sv);
-	}
 }
 
 Cafe::Io::BinaryReader& JceInputStream::GetReader() noexcept
@@ -441,12 +438,9 @@ bool JceInputStream::doRead(std::uint32_t tag, gsl::span<std::byte> const& value
 	return false;
 }
 
-JceOutputStream::JceOutputStream(Cafe::Io::BinaryWriter writer) : m_Writer{ std::move(writer) }
+JceOutputStream::JceOutputStream(Cafe::Io::OutputStream* stream)
+    : m_Writer{ stream, std::endian::little }
 {
-	if (m_Writer.GetUsingEndian() != std::endian::little)
-	{
-		CAFE_THROW(JceDecodeException, u8"writer should use little endian."_sv);
-	}
 }
 
 Cafe::Io::BinaryWriter& JceOutputStream::GetWriter() noexcept

+ 8 - 13
YumeBot/Jce.h

@@ -1,5 +1,6 @@
 #pragma once
 
+#include "Misc.h"
 #include "Utility.h"
 #include <Cafe/Encoding/CodePage/UTF-8.h>
 #include <Cafe/ErrorHandling/ErrorHandling.h>
@@ -18,13 +19,6 @@ namespace YumeBot::Jce
 	CAFE_DEFINE_GENERAL_EXCEPTION(JceDecodeException, JceException);
 	CAFE_DEFINE_GENERAL_EXCEPTION(JceEncodeException, JceException);
 
-	static_assert(
-	    std::numeric_limits<float>::is_iec559 && std::numeric_limits<double>::is_iec559,
-	    "Jce assumed float and double fulfill the requirements of IEEE 754(IEC 559) standard.");
-
-	using UsingString = Cafe::Encoding::String<Cafe::Encoding::CodePage::Utf8>;
-	using UsingStringView = Cafe::Encoding::StringView<Cafe::Encoding::CodePage::Utf8>;
-
 	// Jce 中 Byte 是有符号的,此处表示为无符号,使用时需注意
 	// String 均不包含结尾空字符且编码不明,猜测为 GB2312,需注意
 	class JceStruct
@@ -70,7 +64,7 @@ namespace YumeBot::Jce
 
 		static constexpr std::size_t MaxStringLength = 0x06400000;
 
-		~JceStruct();
+		virtual ~JceStruct();
 
 		[[nodiscard]] virtual UsingStringView GetJceStructName() const noexcept = 0;
 	};
@@ -99,7 +93,7 @@ namespace YumeBot::Jce
 	class JceInputStream
 	{
 	public:
-		explicit JceInputStream(Cafe::Io::BinaryReader reader);
+		explicit JceInputStream(Cafe::Io::InputStream* stream);
 
 		[[nodiscard]] Cafe::Io::BinaryReader& GetReader() noexcept;
 
@@ -117,9 +111,10 @@ namespace YumeBot::Jce
 		///	@param	value	要写入的值
 		///	@return	读取是否成功
 		///	@remark 对于 JceStruct,若传入的引用指针为 const
-		///限定的,则直接就地修改,否则将总是会创建新的实例并写入 			这是由于新的 JceStruct
-		///总是默认将引用指针初始化为空,而实际中未必总是需要新的实例引发的问题 			若传入 JceStruct
-		///派生的实例,也将就地修改
+		///限定的,则直接就地修改,否则将总是会创建新的实例并写入
+		///         这是由于新的 JceStruct
+		///         总是默认将引用指针初始化为空,而实际中未必总是需要新的实例引发的问题 若传入
+		///         JceStruct 派生的实例,也将就地修改
 		template <typename T>
 		[[nodiscard]] bool Read(std::uint32_t tag, T& value, Detail::NoneType = Detail::None)
 		{
@@ -385,7 +380,7 @@ namespace YumeBot::Jce
 	class JceOutputStream
 	{
 	public:
-		explicit JceOutputStream(Cafe::Io::BinaryWriter writer);
+		explicit JceOutputStream(Cafe::Io::OutputStream* stream);
 
 		[[nodiscard]] Cafe::Io::BinaryWriter& GetWriter() noexcept;
 

+ 21 - 0
YumeBot/Misc.h

@@ -0,0 +1,21 @@
+#pragma once
+#include <Cafe/Encoding/CodePage/UTF-8.h>
+
+namespace YumeBot
+{
+	enum class LocaleId
+	{
+		EN_US = 1033,
+		ZH_CN = 2052,
+		ZH_HK = 1028,
+	};
+
+	constexpr LocaleId UsingLocaleId = LocaleId::ZH_CN;
+
+	static_assert(
+	    std::numeric_limits<float>::is_iec559 && std::numeric_limits<double>::is_iec559,
+	    "Jce assumed float and double fulfill the requirements of IEEE 754(IEC 559) standard.");
+
+	using UsingString = Cafe::Encoding::String<Cafe::Encoding::CodePage::Utf8>;
+	using UsingStringView = Cafe::Encoding::StringView<Cafe::Encoding::CodePage::Utf8>;
+} // namespace YumeBot

+ 9 - 0
YumeBot/Tlv.cpp

@@ -0,0 +1,9 @@
+#include "Tlv.h"
+
+using namespace YumeBot;
+using namespace Tlv;
+
+TlvBuilder::TlvBuilder(Cafe::Io::OutputStream *stream) : m_Writer{ stream, std::endian::big }
+{
+	assert(dynamic_cast<Cafe::Io::SeekableStreamBase*>(stream) && "stream should be seekable.");
+}

+ 822 - 4
YumeBot/Tlv.h

@@ -1,19 +1,837 @@
 #pragma once
+#include "Cryptography.h"
+#include "Misc.h"
 #include <Cafe/Io/StreamHelpers/BinaryWriter.h>
+#include <Cafe/Io/Streams/MemoryStream.h>
+#include <asio/ip/address_v4.hpp>
+#include <random>
 
 namespace YumeBot::Tlv
 {
+	template <std::uint16_t Cmd>
+	struct TlvT
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+		}
+	};
+
 	class TlvBuilder
 	{
 	public:
-		explicit TlvBuilder(Cafe::Io::BinaryWriter writer)
-			: m_Writer{ std::move(writer) }
+		explicit TlvBuilder(Cafe::Io::OutputStream* stream);
+
+		template <std::uint16_t Cmd>
+		void WriteTlv(TlvT<Cmd> const& tlv)
 		{
-		}
+			const auto seekableStream = dynamic_cast<Cafe::Io::SeekableStreamBase*>(m_Writer.GetStream());
+			assert(seekableStream);
 
+			m_Writer.Write(Cmd);
+			const auto lengthPos = seekableStream->GetPosition();
+			m_Writer.Write(std::uint16_t{});
 
+			tlv.Write(m_Writer);
+
+			const auto length = seekableStream->GetPosition() - lengthPos - 2;
+			assert(length <= std::numeric_limits<std::uint16_t>::max());
+			seekableStream->SeekFromBegin(lengthPos);
+			m_Writer.Write(static_cast<std::uint16_t>(length));
+		}
 
 	private:
 		Cafe::Io::BinaryWriter m_Writer;
 	};
-}
+
+	template <>
+	struct TlvT<0x1>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(Uin);
+			writer.Write(ServerTime);
+			if (!ClientIp.is_unspecified())
+			{
+				writer.GetStream()->WriteBytes(gsl::as_bytes(gsl::make_span(ClientIp.to_bytes())));
+			}
+		}
+
+		std::uint32_t Uin;
+		std::uint32_t ServerTime;
+		asio::ip::address_v4 ClientIp = {};
+	};
+
+	template <>
+	struct TlvT<0x2>
+	{
+		static constexpr std::uint16_t SigVer = 0;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(SigVer);
+			const auto codeSize = Code.size();
+			const auto keySize = Key.size();
+
+			assert(codeSize <= std::numeric_limits<std::uint16_t>::max() &&
+			       keySize <= std::numeric_limits<std::uint16_t>::max());
+
+			writer.Write(static_cast<std::uint16_t>(codeSize));
+			writer.GetStream()->WriteBytes(Code);
+			writer.Write(static_cast<std::uint16_t>(keySize));
+			writer.GetStream()->WriteBytes(Key);
+		}
+
+		gsl::span<const std::byte> Code;
+		gsl::span<const std::byte> Key;
+	};
+
+	template <>
+	struct TlvT<0x8>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(TimeZoneVer);
+			writer.Write(LocaleId);
+			writer.Write(TimeZoneOffset);
+		}
+
+		std::uint16_t TimeZoneVer = 0;
+		std::uint32_t LocaleId = static_cast<std::uint32_t>(UsingLocaleId);
+		std::uint16_t TimeZoneOffset = 0;
+	};
+
+	template <>
+	struct TlvT<0x18>
+	{
+		static constexpr std::uint16_t PingVersion = 1;
+		static constexpr std::uint32_t SsoVersion = 0x0600;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(PingVersion);
+			writer.Write(SsoVersion);
+			writer.Write(AppId);
+			writer.Write(ClientVersion);
+			writer.Write(Uin);
+			writer.Write(Rc);
+			writer.Write(std::uint16_t{});
+		}
+
+		std::uint32_t AppId;
+		std::uint32_t ClientVersion;
+		std::uint32_t Uin;
+		std::uint16_t Rc;
+	};
+
+	template <>
+	struct TlvT<0x100>
+	{
+		static constexpr std::uint16_t DbBufVer = 1;
+		static constexpr std::uint32_t SsoVer = 5;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(DbBufVer);
+			writer.Write(SsoVer);
+			writer.Write(AppId);
+			writer.Write(WxAppId);
+			writer.Write(ClientVer);
+			writer.Write(GetSig);
+		}
+
+		std::uint32_t AppId;
+		std::uint32_t WxAppId;
+		std::uint32_t ClientVer;
+		std::uint32_t GetSig;
+	};
+
+	template <>
+	struct TlvT<0x104>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(SigSession);
+		}
+
+		gsl::span<const std::byte> SigSession;
+	};
+
+	template <>
+	struct TlvT<0x106>
+	{
+		static constexpr std::uint16_t TGTGTVer = 3;
+		static constexpr std::uint32_t SsoVer = 5;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			std::byte unencryptedBody[98]{};
+			Cafe::Io::ExternalMemoryOutputStream unencryptedBodyStream{ gsl::make_span(unencryptedBody) };
+			Cafe::Io::BinaryWriter unencryptedBodyWriter{ &unencryptedBodyStream, std::endian::big };
+
+			unencryptedBodyWriter.Write(TGTGTVer);
+
+			std::default_random_engine ran{ std::random_device{}() };
+			std::uniform_int_distribution<std::uint32_t> dist{ 0,
+				                                                 std::numeric_limits<std::int32_t>::max() };
+
+			unencryptedBodyWriter.Write(dist(ran));
+
+			unencryptedBodyWriter.Write(SsoVer);
+			unencryptedBodyWriter.Write(AppId);
+			unencryptedBodyWriter.Write(ClientVer);
+			unencryptedBodyWriter.Write(Uin);
+			unencryptedBodyStream.WriteBytes(InitTime);
+			if (!ClientIp.is_unspecified())
+			{
+				unencryptedBodyStream.WriteBytes(gsl::as_bytes(gsl::make_span(ClientIp.to_bytes())));
+			}
+			unencryptedBodyWriter.Write(SavePwd);
+			unencryptedBodyStream.WriteBytes(Md5);
+			unencryptedBodyStream.WriteBytes(TGTGTKey);
+			unencryptedBodyWriter.Write(std::uint32_t{});
+			unencryptedBodyWriter.Write(ReadFlg);
+
+			if (Guid.empty())
+			{
+				std::uniform_int_distribution<std::uint32_t> dist{};
+				for (std::size_t i = 0; i < 4; ++i)
+				{
+					unencryptedBodyWriter.Write(dist(ran));
+				}
+			}
+			else
+			{
+				unencryptedBodyStream.WriteBytes(Guid);
+			}
+
+			unencryptedBodyWriter.Write(SubAppId);
+			unencryptedBodyWriter.Write(SigSrc);
+
+			std::byte encryptKeyContent[24];
+			Cafe::Io::ExternalMemoryOutputStream encryptKeyStream{ gsl::make_span(encryptKeyContent) };
+			Cafe::Io::BinaryWriter encryptKeyWriter{ &encryptKeyStream, std::endian::big };
+
+			encryptKeyStream.WriteBytes(Md5);
+			if (MSalt)
+			{
+				encryptKeyWriter.Write(MSalt);
+			}
+			else
+			{
+				encryptKeyWriter.Write(Uin);
+			}
+
+			std::uint32_t encryptKey[4];
+			Cryptography::Md5::Calculate(gsl::make_span(encryptKeyContent),
+			                             gsl::as_writeable_bytes(gsl::make_span(encryptKey)));
+
+			std::byte encryptedBody[Cryptography::Tea::CalculateOutputSize(std::size(unencryptedBody))];
+			const auto size =
+			    Cryptography::Tea::Encrypt(gsl::make_span(unencryptedBody), gsl::make_span(encryptedBody),
+			                               gsl::make_span(encryptKey));
+
+			writer.GetStream()->WriteBytes(gsl::make_span(encryptedBody, size));
+		}
+
+		std::uint32_t AppId;
+		std::uint32_t SubAppId;
+		std::uint32_t ClientVer;
+		std::uint64_t Uin;
+		gsl::span<const std::byte> InitTime;
+		asio::ip::address_v4 ClientIp;
+		bool SavePwd;
+		gsl::span<const std::byte> Md5;
+		std::uint64_t MSalt;
+		gsl::span<const std::byte> TGTGTKey;
+		bool ReadFlg;
+		gsl::span<const std::byte> Guid;
+		std::uint32_t SigSrc;
+	};
+
+	template <>
+	struct TlvT<0x107>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(PicType);
+			writer.Write(CapType);
+			writer.Write(PicSize);
+			writer.Write(RetType);
+		}
+
+		std::uint16_t PicType;
+		std::uint8_t CapType;
+		std::uint16_t PicSize;
+		std::uint8_t RetType;
+	};
+
+	template <>
+	struct TlvT<0x108>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(Ksid);
+		}
+
+		gsl::span<const std::byte> Ksid;
+	};
+
+	template <>
+	struct TlvT<0x109>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(Imei);
+		}
+
+		gsl::span<const std::byte> Imei;
+	};
+
+	template <>
+	struct TlvT<0x10A>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(TGT);
+		}
+
+		gsl::span<const std::byte> TGT;
+	};
+
+	template <>
+	struct TlvT<0x112>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(gsl::as_bytes(Name.GetTrimmedSpan()));
+		}
+
+		UsingStringView Name;
+	};
+
+	template <>
+	struct TlvT<0x116>
+	{
+		static constexpr std::uint8_t Ver = 0;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(Ver);
+			writer.Write(Bitmap);
+			writer.Write(GetSig);
+			const auto size = AppId.size();
+			assert(size <= std::numeric_limits<std::uint8_t>::max());
+			writer.Write(static_cast<std::uint8_t>(size));
+			for (const auto appId : AppId)
+			{
+				writer.Write(appId);
+			}
+		}
+
+		std::uint32_t Bitmap;
+		std::uint32_t GetSig;
+		gsl::span<const std::uint32_t> AppId;
+	};
+
+	template <>
+	struct TlvT<0x124>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto osTypeTrimmedSpan = gsl::as_bytes(OsType.GetTrimmedSpan());
+			const auto osType =
+			    osTypeTrimmedSpan.subspan(0, std::min(osTypeTrimmedSpan.size(), std::ptrdiff_t{ 16 }));
+
+			const auto osVerTrimmedSpan = gsl::as_bytes(OsVer.GetTrimmedSpan());
+			const auto osVer =
+			    osVerTrimmedSpan.subspan(0, std::min(osVerTrimmedSpan.size(), std::ptrdiff_t{ 16 }));
+
+			const auto netDetailTrimmedSpan = gsl::as_bytes(NetDetail.GetTrimmedSpan());
+			const auto netDetail = netDetailTrimmedSpan.subspan(
+			    0, std::min(netDetailTrimmedSpan.size(), std::ptrdiff_t{ 16 }));
+
+			const auto addrTrimmedSpan = gsl::as_bytes(Addr.GetTrimmedSpan());
+			const auto addr =
+			    addrTrimmedSpan.subspan(0, std::min(addrTrimmedSpan.size(), std::ptrdiff_t{ 32 }));
+
+			const auto apnTrimmedSpan = gsl::as_bytes(Apn.GetTrimmedSpan());
+			const auto apn =
+			    apnTrimmedSpan.subspan(0, std::min(apnTrimmedSpan.size(), std::ptrdiff_t{ 16 }));
+
+			writer.Write(static_cast<std::uint16_t>(osType.size()));
+			writer.GetStream()->WriteBytes(osType);
+			writer.Write(static_cast<std::uint16_t>(osVer.size()));
+			writer.GetStream()->WriteBytes(osVer);
+			writer.Write(NetType);
+			writer.Write(static_cast<std::uint16_t>(netDetail.size()));
+			writer.GetStream()->WriteBytes(netDetail);
+			writer.Write(static_cast<std::uint16_t>(addr.size()));
+			writer.GetStream()->WriteBytes(addr);
+			writer.Write(static_cast<std::uint16_t>(apn.size()));
+			writer.GetStream()->WriteBytes(apn);
+		}
+
+		UsingStringView OsType;
+		UsingStringView OsVer;
+		std::uint16_t NetType;
+		UsingStringView NetDetail;
+		UsingStringView Addr;
+		UsingStringView Apn;
+	};
+
+	template <>
+	struct TlvT<0x127>
+	{
+		static constexpr std::uint16_t Version = 0;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(Version);
+
+			const auto codeSize = Code.size();
+			const auto randomSize = Random.size();
+
+			assert(codeSize <= std::numeric_limits<std::uint16_t>::max() &&
+			       randomSize <= std::numeric_limits<std::uint16_t>::max());
+
+			writer.Write(static_cast<std::uint16_t>(codeSize));
+			writer.GetStream()->WriteBytes(Code);
+			writer.Write(static_cast<std::uint16_t>(randomSize));
+			writer.GetStream()->WriteBytes(Random);
+		}
+
+		gsl::span<const std::byte> Code;
+		gsl::span<const std::byte> Random;
+	};
+
+	template <>
+	struct TlvT<0x128>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto deviceTypeTrimmedSpan = gsl::as_bytes(DeviceType.GetTrimmedSpan());
+			const auto deviceType = deviceTypeTrimmedSpan.subspan(
+			    0, std::min(deviceTypeTrimmedSpan.size(), std::ptrdiff_t{ 32 }));
+
+			const auto guidTrimmedSpan = gsl::as_bytes(Guid.GetTrimmedSpan());
+			const auto guid =
+			    guidTrimmedSpan.subspan(0, std::min(guidTrimmedSpan.size(), std::ptrdiff_t{ 16 }));
+
+			const auto brandTrimmedSpan = gsl::as_bytes(Brand.GetTrimmedSpan());
+			const auto brand =
+			    brandTrimmedSpan.subspan(0, std::min(brandTrimmedSpan.size(), std::ptrdiff_t{ 16 }));
+
+			writer.Write(std::uint16_t{});
+			writer.Write(NewIns);
+			writer.Write(ReadGuid);
+			writer.Write(GuidChg);
+			writer.Write(Flag);
+
+			writer.Write(static_cast<std::uint16_t>(deviceType.size()));
+			writer.GetStream()->WriteBytes(deviceType);
+
+			writer.Write(static_cast<std::uint16_t>(guid.size()));
+			writer.GetStream()->WriteBytes(guid);
+
+			writer.Write(static_cast<std::uint16_t>(brand.size()));
+			writer.GetStream()->WriteBytes(brand);
+		}
+
+		std::uint8_t NewIns;
+		std::uint8_t ReadGuid;
+		std::uint8_t GuidChg;
+		std::uint32_t Flag;
+		UsingStringView DeviceType;
+		UsingStringView Guid;
+		UsingStringView Brand;
+	};
+
+	template <>
+	struct TlvT<0x141>
+	{
+		static constexpr std::uint16_t Version = 1;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto operatorName = gsl::as_bytes(OperatorName.GetTrimmedSpan());
+			const auto apn = gsl::as_bytes(Apn.GetTrimmedSpan());
+
+			const auto operatorNameSize = operatorName.size();
+			const auto apnSize = apn.size();
+
+			assert(operatorNameSize <= std::numeric_limits<std::uint16_t>::max() &&
+			       apnSize <= std::numeric_limits<std::uint16_t>::max());
+
+			writer.Write(Version);
+
+			writer.Write(static_cast<std::uint16_t>(operatorNameSize));
+			writer.GetStream()->WriteBytes(operatorName);
+
+			writer.Write(NetworkType);
+
+			writer.Write(static_cast<std::uint16_t>(apnSize));
+			writer.GetStream()->WriteBytes(apn);
+		}
+
+		UsingStringView OperatorName;
+		std::uint16_t NetworkType;
+		UsingStringView Apn;
+	};
+
+	template <>
+	struct TlvT<0x142>
+	{
+		static constexpr std::uint16_t Version = 0;
+
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto id = gsl::as_bytes(Id.GetTrimmedSpan());
+			const auto idSize = id.size();
+			assert(idSize <= std::numeric_limits<std::uint16_t>::max());
+
+			writer.Write(Version);
+
+			writer.Write(static_cast<std::uint16_t>(idSize));
+			writer.GetStream()->WriteBytes(id);
+		}
+
+		UsingStringView Id;
+	};
+
+	template <>
+	struct TlvT<0x143>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(B);
+		}
+
+		gsl::span<const std::byte> B;
+	};
+
+	template <>
+	struct TlvT<0x144>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const std::uint16_t tlvCount = !_109.empty() + !_124.empty() + !_128.empty() + !_148.empty() +
+			                               !_151.empty() + !_153.empty() + !_16e.empty();
+			Cafe::Io::MemoryStream unencryptedDataStream;
+			Cafe::Io::BinaryWriter unencryptedDataWriter{ &unencryptedDataStream, std::endian::big };
+			unencryptedDataWriter.Write(tlvCount);
+			unencryptedDataStream.WriteBytes(_109);
+			unencryptedDataStream.WriteBytes(_124);
+			unencryptedDataStream.WriteBytes(_128);
+			unencryptedDataStream.WriteBytes(_148);
+			unencryptedDataStream.WriteBytes(_151);
+			unencryptedDataStream.WriteBytes(_153);
+			unencryptedDataStream.WriteBytes(_16e);
+
+			std::vector<std::byte> encrypedData(
+			    Cryptography::Tea::CalculateOutputSize(unencryptedDataStream.GetTotalSize()));
+			const auto size = Cryptography::Tea::Encrypt(unencryptedDataStream.GetInternalStorage(),
+			                                             gsl::make_span(encrypedData), Key);
+
+			writer.GetStream()->WriteBytes(gsl::make_span(encrypedData.data(), size));
+		}
+
+		gsl::span<const std::byte> _109;
+		gsl::span<const std::byte> _124;
+		gsl::span<const std::byte> _128;
+		gsl::span<const std::byte> _148;
+		gsl::span<const std::byte> _151;
+		gsl::span<const std::byte> _153;
+		gsl::span<const std::byte> _16e;
+		gsl::span<const std::uint32_t, 4> Key;
+	};
+
+	template <>
+	struct TlvT<0x145>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(Guid);
+		}
+
+		gsl::span<const std::byte> Guid;
+	};
+
+	template <>
+	struct TlvT<0x147>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto appVerTrimmedSpan = gsl::as_bytes(AppVer.GetTrimmedSpan());
+			const auto appVer =
+			    appVerTrimmedSpan.subspan(0, std::min(appVerTrimmedSpan.size(), std::ptrdiff_t{ 32 }));
+
+			const auto appSignTrimmedSpan = gsl::as_bytes(AppSign.GetTrimmedSpan());
+			const auto appSign =
+			    appSignTrimmedSpan.subspan(0, std::min(appSignTrimmedSpan.size(), std::ptrdiff_t{ 32 }));
+
+			writer.Write(AppVerId);
+
+			writer.Write(static_cast<std::uint16_t>(appVer.size()));
+			writer.GetStream()->WriteBytes(appVer);
+
+			writer.Write(static_cast<std::uint16_t>(appSign.size()));
+			writer.GetStream()->WriteBytes(appSign);
+		}
+
+		std::uint32_t AppVerId;
+		UsingStringView AppVer;
+		UsingStringView AppSign;
+	};
+
+	template <>
+	struct TlvT<0x148>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto appNameTrimmedSpan = gsl::as_bytes(AppName.GetTrimmedSpan());
+			const auto appName =
+			    appNameTrimmedSpan.subspan(0, std::min(appNameTrimmedSpan.size(), std::ptrdiff_t{ 32 }));
+
+			const auto appVerTrimmedSpan = gsl::as_bytes(AppVer.GetTrimmedSpan());
+			const auto appVer =
+			    appVerTrimmedSpan.subspan(0, std::min(appVerTrimmedSpan.size(), std::ptrdiff_t{ 32 }));
+
+			const auto appSignTrimmedSpan = gsl::as_bytes(AppSign.GetTrimmedSpan());
+			const auto appSign =
+			    appSignTrimmedSpan.subspan(0, std::min(appSignTrimmedSpan.size(), std::ptrdiff_t{ 32 }));
+
+			writer.Write(static_cast<std::uint16_t>(appName.size()));
+			writer.GetStream()->WriteBytes(appName);
+
+			writer.Write(SsoVer);
+			writer.Write(AppID);
+			writer.Write(SubAppID);
+
+			writer.Write(static_cast<std::uint16_t>(appVer.size()));
+			writer.GetStream()->WriteBytes(appVer);
+
+			writer.Write(static_cast<std::uint16_t>(appSign.size()));
+			writer.GetStream()->WriteBytes(appSign);
+		}
+
+		UsingStringView AppName;
+		std::uint32_t SsoVer;
+		std::uint32_t AppID;
+		std::uint32_t SubAppID;
+		UsingStringView AppVer;
+		UsingStringView AppSign;
+	};
+
+	template <>
+	struct TlvT<0x153>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(IsRoot);
+		}
+
+		std::uint16_t IsRoot;
+	};
+
+	template <>
+	struct TlvT<0x154>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(SsoSeq);
+		}
+
+		std::uint32_t SsoSeq;
+	};
+
+	template <>
+	struct TlvT<0x166>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(ImgType);
+		}
+
+		std::uint8_t ImgType;
+	};
+
+	template <>
+	struct TlvT<0x16A>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(NoPicSig);
+		}
+
+		gsl::span<const std::byte> NoPicSig;
+	};
+
+	template <>
+	struct TlvT<0x16B>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto domainSize = Domains.size();
+			assert(domainSize <= std::numeric_limits<std::uint16_t>::max());
+			writer.Write(static_cast<std::uint16_t>(domainSize));
+
+			for (const auto& domain : Domains)
+			{
+				const auto trimedDomain = domain.Trim();
+				const auto size = trimedDomain.GetSize();
+				assert(size <= std::numeric_limits<std::uint16_t>::max());
+				writer.Write(static_cast<std::uint16_t>(size));
+				writer.GetStream()->WriteBytes(gsl::as_bytes(trimedDomain.GetSpan()));
+			}
+		}
+
+		gsl::span<const UsingStringView> Domains;
+	};
+
+	template <>
+	struct TlvT<0x16E>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(gsl::as_bytes(DeviceName.GetTrimmedSpan()));
+		}
+
+		UsingStringView DeviceName;
+	};
+
+	template <>
+	struct TlvT<0x172>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(RollbackSig);
+		}
+
+		gsl::span<const std::byte> RollbackSig;
+	};
+
+	template <>
+	struct TlvT<0x174>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(SecSig);
+		}
+
+		gsl::span<const std::byte> SecSig;
+	};
+
+	template <>
+	struct TlvT<0x177>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto buildVersion = gsl::as_bytes(BuildVersion.GetTrimmedSpan());
+			const auto buildVersionSize = buildVersion.size();
+			assert(buildVersionSize <= std::numeric_limits<std::uint16_t>::max());
+
+			writer.Write(std::uint8_t{ 1 });
+			writer.Write(BuildTime);
+
+			writer.Write(static_cast<std::uint16_t>(buildVersionSize));
+			writer.GetStream()->WriteBytes(buildVersion);
+		}
+
+		std::uint32_t BuildTime;
+		UsingStringView BuildVersion;
+	};
+
+	template <>
+	struct TlvT<0x17A>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(SmsAppId);
+		}
+
+		std::uint32_t SmsAppId;
+	};
+
+	template <>
+	struct TlvT<0x17C>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto smsCodeSize = SmsCode.size();
+			assert(smsCodeSize <= std::numeric_limits<std::uint16_t>::max());
+
+			writer.Write(static_cast<std::uint16_t>(smsCodeSize));
+			writer.GetStream()->WriteBytes(SmsCode);
+		}
+
+		gsl::span<const std::byte> SmsCode;
+	};
+
+	template <>
+	struct TlvT<0x183>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(MSalt);
+		}
+
+		std::uint64_t MSalt;
+	};
+
+	template <>
+	struct TlvT<0x184>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			const auto mPassword = gsl::as_bytes(MPassword.GetTrimmedSpan());
+
+			std::byte body[24];
+			Cryptography::Md5::Calculate(mPassword, gsl::make_span(body).subspan(0, 16));
+
+			Cafe::Io::ExternalMemoryOutputStream stream{ gsl::make_span(body) };
+			Cafe::Io::BinaryWriter{ &stream, std::endian::big }.Write(MSalt);
+
+			std::byte md5Body[16];
+			Cryptography::Md5::Calculate(gsl::make_span(body), gsl::make_span(md5Body));
+
+			writer.GetStream()->WriteBytes(gsl::make_span(md5Body));
+		}
+
+		std::uint64_t MSalt;
+		UsingStringView MPassword;
+	};
+
+	template <>
+	struct TlvT<0x185>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.Write(std::uint8_t{ 1 });
+			writer.Write(Flag);
+		}
+
+		std::uint8_t Flag;
+	};
+
+	template <>
+	struct TlvT<0x187>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(Mac);
+		}
+
+		gsl::span<const std::byte, 16> Mac;
+	};
+
+	template <>
+	struct TlvT<0x188>
+	{
+		void Write(Cafe::Io::BinaryWriter& writer) const
+		{
+			writer.GetStream()->WriteBytes(AndroidID);
+		}
+
+		gsl::span<const std::byte, 16> AndroidID;
+	};
+} // namespace YumeBot::Tlv

+ 60 - 32
YumeBot/Utility.h

@@ -1,4 +1,5 @@
 #pragma once
+#include <chrono>
 #include <cstddef>
 #include <gsl/span>
 #include <type_traits>
@@ -23,13 +24,16 @@ namespace YumeBot::Utility
 	};
 
 	template <typename T, std::size_t N>
-	typename MayAddConst<T, std::byte>::Type(&ToByteArray(T(&arr)[N]) noexcept)[sizeof(T) * N / sizeof(std::byte)]
+	typename MayAddConst<T, std::byte>::Type (
+	    &ToByteArray(T (&arr)[N]) noexcept)[sizeof(T) * N / sizeof(std::byte)]
 	{
-		return reinterpret_cast<typename MayAddConst<T, std::byte>::Type(&)[sizeof(T) * N / sizeof(std::byte)]>(arr);
+		return reinterpret_cast<
+		    typename MayAddConst<T, std::byte>::Type(&)[sizeof(T) * N / sizeof(std::byte)]>(arr);
 	}
 
 	template <typename T, std::size_t N>
-	gsl::span<typename MayAddConst<T, std::byte>::Type, sizeof(T) * N / sizeof(std::byte)> ToByteSpan(T(&arr)[N]) noexcept
+	gsl::span<typename MayAddConst<T, std::byte>::Type, sizeof(T) * N / sizeof(std::byte)>
+	    ToByteSpan(T (&arr)[N]) noexcept
 	{
 		return ToByteArray(arr);
 	}
@@ -41,14 +45,15 @@ namespace YumeBot::Utility
 	};
 
 	template <typename TypeSequence, template <typename> class Predicate, typename FallbackType>
-	struct GetFirstOr
-		: ResultType<FallbackType>
+	struct GetFirstOr : ResultType<FallbackType>
 	{
 	};
 
-	template <template <typename...> class TypeSequenceTemplate, typename FirstArg, typename... RestArgs, template <typename> class Predicate, typename FallbackType>
+	template <template <typename...> class TypeSequenceTemplate, typename FirstArg,
+	          typename... RestArgs, template <typename> class Predicate, typename FallbackType>
 	struct GetFirstOr<TypeSequenceTemplate<FirstArg, RestArgs...>, Predicate, FallbackType>
-		: std::conditional_t<Predicate<FirstArg>::value, ResultType<FirstArg>, GetFirstOr<TypeSequenceTemplate<RestArgs...>, Predicate, FallbackType>>
+	    : std::conditional_t<Predicate<FirstArg>::value, ResultType<FirstArg>,
+	                         GetFirstOr<TypeSequenceTemplate<RestArgs...>, Predicate, FallbackType>>
 	{
 	};
 
@@ -70,7 +75,8 @@ namespace YumeBot::Utility
 		using Type = ToTemplate<From>;
 	};
 
-	template <template <typename...> class FromTemplate, typename... Args, template <typename...> class ToTemplate>
+	template <template <typename...> class FromTemplate, typename... Args,
+	          template <typename...> class ToTemplate>
 	struct ApplyToTrait<FromTemplate<Args...>, ToTemplate>
 	{
 		using Type = ToTemplate<Args...>;
@@ -80,14 +86,12 @@ namespace YumeBot::Utility
 	using ApplyTo = typename ApplyToTrait<From, ToTemplate>::Type;
 
 	template <typename T, template <typename...> class Template>
-	struct IsTemplateOf
-		: std::false_type
+	struct IsTemplateOf : std::false_type
 	{
 	};
 
 	template <template <typename...> class Template, typename... Args>
-	struct IsTemplateOf<Template<Args...>, Template>
-		: std::true_type
+	struct IsTemplateOf<Template<Args...>, Template> : std::true_type
 	{
 	};
 
@@ -116,10 +120,12 @@ namespace YumeBot::Utility
 		return TemplatePlaceholder<Template>{};
 	}
 
-	template <template <typename...> class Template, typename FirstOperation, typename... RestOperations>
+	template <template <typename...> class Template, typename FirstOperation,
+	          typename... RestOperations>
 	constexpr auto RecursiveApply()
 	{
-		return RecursiveApply<typename FirstOperation::template Apply<Template>::Type, RestOperations...>();
+		return RecursiveApply<typename FirstOperation::template Apply<Template>::Type,
+		                      RestOperations...>();
 	}
 
 	template <template <typename> class Predicate, typename FallbackType>
@@ -128,7 +134,8 @@ namespace YumeBot::Utility
 		return FallbackType{};
 	}
 
-	template <template <typename> class Predicate, typename FallbackType, typename FirstArg, typename... Args>
+	template <template <typename> class Predicate, typename FallbackType, typename FirstArg,
+	          typename... Args>
 	constexpr decltype(auto) ReturnFirst(FirstArg&& firstArg, Args&&... args)
 	{
 		if constexpr (Predicate<FirstArg&&>::value)
@@ -158,36 +165,40 @@ namespace YumeBot::Utility
 	template <template <typename...> class Trait, typename... T>
 	using GetType = typename Trait<T...>::type;
 
-	// Workaround: 临时糊掉 C2210:pack expansions cannot be used as arguments to non-packed parameters in alias templates
+	// Workaround: 临时糊掉 C2210:pack expansions cannot be used as arguments to non-packed
+	// parameters in alias templates
 	template <typename... T>
 	using RemoveCvRef = std::remove_cv_t<GetType<std::remove_reference, T...>>;
 
 	template <typename T, template <typename> class Template>
-	struct MayRemoveTemplate
-		: ResultType<T>
+	struct MayRemoveTemplate : ResultType<T>
 	{
 	};
 
 	template <typename T, template <typename> class Template>
-	struct MayRemoveTemplate<Template<T>, Template>
-		: ResultType<T>
+	struct MayRemoveTemplate<Template<T>, Template> : ResultType<T>
 	{
 	};
 
 	template <typename T, typename U>
-	constexpr std::enable_if_t<std::is_integral_v<RemoveCvRef<T>> && std::is_integral_v<RemoveCvRef<U>>, bool> InRangeOf(U&& value)
+	constexpr std::enable_if_t<
+	    std::is_integral_v<RemoveCvRef<T>> && std::is_integral_v<RemoveCvRef<U>>, bool>
+	InRangeOf(U&& value)
 	{
 		using TType = RemoveCvRef<T>;
 		using UType = RemoveCvRef<U>;
 
 		// 因为有 char 所以必须 signed 和 unsigned 都测试
-		if constexpr (sizeof(TType) >= sizeof(UType) && std::is_signed_v<TType> == std::is_signed_v<UType> && std::is_unsigned_v<TType> == std::is_unsigned_v<UType>)
+		if constexpr (sizeof(TType) >= sizeof(UType) &&
+		              std::is_signed_v<TType> == std::is_signed_v<UType> &&
+		              std::is_unsigned_v<TType> == std::is_unsigned_v<UType>)
 		{
 			return true;
 		}
 		else
 		{
-			return value >= static_cast<UType>(std::numeric_limits<TType>::min()) && value <= static_cast<UType>(std::numeric_limits<TType>::max());
+			return value >= static_cast<UType>(std::numeric_limits<TType>::min()) &&
+			       value <= static_cast<UType>(std::numeric_limits<TType>::max());
 		}
 	}
 
@@ -202,7 +213,8 @@ namespace YumeBot::Utility
 	{
 		if constexpr (Trait<Arg>::value)
 		{
-			return std::tuple_cat(std::forward_as_tuple<Arg>(arg), Filter<Trait>(std::forward<Args>(args)...));
+			return std::tuple_cat(std::forward_as_tuple<Arg>(arg),
+			                      Filter<Trait>(std::forward<Args>(args)...));
 		}
 		else
 		{
@@ -213,17 +225,33 @@ namespace YumeBot::Utility
 	namespace Detail
 	{
 		template <typename T, typename Tuple, std::size_t... Indexes>
-		constexpr void InitializeWithTuple(T& obj, Tuple&& args, std::index_sequence<Indexes...>)
-			noexcept(std::is_nothrow_constructible_v<T, std::tuple_element_t<Indexes, std::remove_reference_t<Tuple>>...>)
+		constexpr void
+		InitializeWithTuple(T& obj, Tuple&& args, std::index_sequence<Indexes...>) noexcept(
+		    std::is_nothrow_constructible_v<
+		        T, std::tuple_element_t<Indexes, std::remove_reference_t<Tuple>>...>)
 		{
-			new (static_cast<void*>(std::addressof(obj))) T(std::get<Indexes>(std::forward<Tuple>(args))...);
+			new (static_cast<void*>(std::addressof(obj)))
+			    T(std::get<Indexes>(std::forward<Tuple>(args))...);
 		}
-	}
+	} // namespace Detail
 
 	template <typename T, typename Tuple>
-	constexpr void InitializeWithTuple(T& obj, Tuple&& args)
-		noexcept(noexcept(Detail::InitializeWithTuple(obj, std::forward<Tuple>(args), std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>())))
+	constexpr void
+	InitializeWithTuple(T& obj, Tuple&& args) noexcept(noexcept(Detail::InitializeWithTuple(
+	    obj, std::forward<Tuple>(args),
+	    std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>())))
+	{
+		Detail::InitializeWithTuple(
+		    obj, std::forward<Tuple>(args),
+		    std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>());
+	}
+
+	inline std::uint32_t GetPosixTime() noexcept
 	{
-		Detail::InitializeWithTuple(obj, std::forward<Tuple>(args), std::make_index_sequence<std::tuple_size_v<std::remove_reference_t<Tuple>>>());
+		const auto count = std::chrono::duration_cast<std::chrono::seconds>(
+		                       std::chrono::system_clock::now().time_since_epoch())
+		                       .count();
+		assert(count <= std::numeric_limits<std::uint32_t>::max());
+		return static_cast<std::uint32_t>(count);
 	}
-}
+} // namespace YumeBot::Utility

+ 16 - 14
YumeBot/Wup.cpp

@@ -20,15 +20,15 @@ bool OldUniAttribute::Remove(UsingString const& name)
 	return !!m_Data.erase(name);
 }
 
-void OldUniAttribute::Encode(BinaryWriter const& writer) const
+void OldUniAttribute::Encode(Cafe::Io::OutputStream* stream) const
 {
-	JceOutputStream output{ writer };
+	JceOutputStream output{ stream };
 	output.Write(0, m_Data);
 }
 
-void OldUniAttribute::Decode(BinaryReader const& reader)
+void OldUniAttribute::Decode(Cafe::Io::InputStream* stream)
 {
-	JceInputStream input{ reader };
+	JceInputStream input{ stream };
 	m_Data.clear();
 	if (!input.Read(0, m_Data))
 	{
@@ -40,20 +40,22 @@ UniPacket::UniPacket() : m_OldRespIRet{}
 {
 }
 
-void UniPacket::Encode(BinaryWriter const& writer)
+void UniPacket::Encode(Cafe::Io::OutputStream* stream)
 {
 	MemoryStream tmpBuffer;
-	m_UniAttribute.Encode(Cafe::Io::BinaryWriter{ &tmpBuffer });
+	m_UniAttribute.Encode(&tmpBuffer);
 	const auto buffer = tmpBuffer.GetInternalStorage();
 	m_RequestPacket.GetsBuffer().assign(buffer.data(), buffer.data() + buffer.size());
 
-	const auto underlyingStream = dynamic_cast<SeekableStreamBase*>(writer.GetStream());
+	Cafe::Io::BinaryWriter writer{ stream, std::endian::little };
+
+	const auto underlyingStream = dynamic_cast<SeekableStreamBase*>(stream);
 	assert(underlyingStream);
 	const auto sizePos = underlyingStream->GetPosition();
 	// 占位 4 字节以便之后返回写入长度信息
 	writer.Write(std::uint32_t{});
 
-	JceOutputStream os{ writer };
+	JceOutputStream os{ stream };
 	os.Write(0, m_RequestPacket);
 	const auto endPos = underlyingStream->GetPosition();
 	const auto length = endPos - sizePos;
@@ -62,20 +64,20 @@ void UniPacket::Encode(BinaryWriter const& writer)
 	underlyingStream->SeekFromBegin(endPos);
 }
 
-void UniPacket::Decode(BinaryReader const& reader)
+void UniPacket::Decode(Cafe::Io::InputStream* stream)
 {
-	const auto underlyingStream = dynamic_cast<SeekableStreamBase*>(reader.GetStream());
+	const auto underlyingStream = dynamic_cast<SeekableStreamBase*>(stream);
 	assert(underlyingStream);
 	underlyingStream->Seek(SeekOrigin::Current, 4);
-	JceInputStream is{ reader };
+	JceInputStream is{ stream };
 	if (!is.Read(0, m_RequestPacket))
 	{
 		CAFE_THROW(CafeException, u8"Read RequestPacket failed."_sv);
 	}
 
 	const auto& buffer = m_RequestPacket.GetsBuffer();
-	ExternalMemoryInputStream stream{ gsl::as_bytes(gsl::make_span(buffer.data(), buffer.size())) };
-	m_UniAttribute.Decode(Cafe::Io::BinaryReader{ &stream });
+	ExternalMemoryInputStream bufferStream{ gsl::as_bytes(gsl::make_span(buffer.data(), buffer.size())) };
+	m_UniAttribute.Decode(&bufferStream);
 }
 
 UniPacket UniPacket::CreateResponse()
@@ -91,7 +93,7 @@ UniPacket UniPacket::CreateResponse()
 void UniPacket::CreateOldRespEncode(JceOutputStream& os)
 {
 	MemoryStream memoryStream;
-	m_UniAttribute.Encode(Cafe::Io::BinaryWriter{ &memoryStream });
+	m_UniAttribute.Encode(&memoryStream);
 
 	os.Write(1, m_RequestPacket.GetiVersion());
 	os.Write(2, m_RequestPacket.GetcPacketType());

+ 6 - 6
YumeBot/Wup.h

@@ -128,7 +128,7 @@ namespace YumeBot::Jce::Wup
 		void Put(UsingString const& name, T const& value)
 		{
 			Cafe::Io::MemoryStream memoryStream;
-			JceOutputStream out{ Cafe::Io::BinaryWriter{ &memoryStream } };
+			JceOutputStream out{ &memoryStream };
 			out.Write(0, value);
 			memoryStream.SeekFromBegin(0);
 			const auto internalStorage = memoryStream.GetInternalStorage();
@@ -157,15 +157,15 @@ namespace YumeBot::Jce::Wup
 
 			Cafe::Io::ExternalMemoryInputStream stream{ gsl::as_bytes(
 				  gsl::make_span(fieldIter->second.data(), fieldIter->second.size())) };
-			JceInputStream in{ Cafe::Io::BinaryReader{ &stream } };
+			JceInputStream in{ &stream };
 
 			return in.Read(0, result);
 		}
 
 		bool Remove(UsingString const& name);
 
-		void Encode(Cafe::Io::BinaryWriter const& writer) const;
-		void Decode(Cafe::Io::BinaryReader const& reader);
+		void Encode(Cafe::Io::OutputStream* stream) const;
+		void Decode(Cafe::Io::InputStream* stream);
 
 	private:
 		std::unordered_map<UsingString, std::unordered_map<UsingString, std::vector<std::byte>>> m_Data;
@@ -176,8 +176,8 @@ namespace YumeBot::Jce::Wup
 	public:
 		UniPacket();
 
-		void Encode(Cafe::Io::BinaryWriter const& writer);
-		void Decode(Cafe::Io::BinaryReader const& reader);
+		void Encode(Cafe::Io::OutputStream* stream);
+		void Decode(Cafe::Io::InputStream* stream);
 
 		UniPacket CreateResponse();
 		void CreateOldRespEncode(JceOutputStream& os);