Jelajahi Sumber

添加 Wup.UniPacket 部分,修复部分 bug

akemimadoka 7 tahun lalu
induk
melakukan
dc2257bc9f
9 mengubah file dengan 758 tambahan dan 458 penghapusan
  1. 1 1
      Extern/NatsuLib
  2. 23 0
      YumeBot.Test/JceTest.cpp
  3. 476 2
      YumeBot/Jce.cpp
  4. 36 453
      YumeBot/Jce.h
  5. 11 2
      YumeBot/JceStructDef.h
  6. 32 0
      YumeBot/Wup.cpp
  7. 171 0
      YumeBot/Wup.h
  8. 2 0
      YumeBot/YumeBot.vcxproj
  9. 6 0
      YumeBot/YumeBot.vcxproj.filters

+ 1 - 1
Extern/NatsuLib

@@ -1 +1 @@
-Subproject commit 7aa160ed8497d8914da38858bb31fd139e1b1cd3
+Subproject commit e586491f638561bd219cf6530e127b3678cde905

+ 23 - 0
YumeBot.Test/JceTest.cpp

@@ -1,6 +1,7 @@
 #include "pch.h"
 #include <catch.hpp>
 #include <Jce.h>
+#include <Wup.h>
 #include <natStream.h>
 
 using namespace NatsuLib;
@@ -40,4 +41,26 @@ TEST_CASE("Jce", "[Jce]")
 		CHECK(ptr->GetTestMap() == test->GetTestMap());
 		CHECK(ptr->GetTestList() == std::vector{ 1.0, 2.0, 3.0 });
 	}
+
+	SECTION("Wup.UniPacket")
+	{
+		using namespace Wup;
+
+		OldUniAttribute uniAttribute{};
+		uniAttribute.Put(u8"SomeInt"_ns, 1);
+		uniAttribute.Put(u8"SomeFloat"_ns, 1.0f);
+
+		const auto memoryStream = make_ref<natMemoryStream>(0, true, true, true);
+		uniAttribute.Encode(make_ref<natBinaryWriter>(memoryStream));
+
+		memoryStream->SetPositionFromBegin(0);
+
+		{
+			OldUniAttribute readAttribute{};
+			readAttribute.Decode(make_ref<natBinaryReader>(memoryStream));
+
+			CHECK(uniAttribute.Get<std::int32_t>(u8"SomeInt"_ns) == readAttribute.Get<std::int32_t>(u8"SomeInt"_ns));
+			CHECK(uniAttribute.Get<float>(u8"SomeFloat"_ns) == readAttribute.Get<float>(u8"SomeFloat"_ns));
+		}
+	}
 }

+ 476 - 2
YumeBot/Jce.cpp

@@ -21,6 +21,359 @@ JceInputStream::~JceInputStream()
 {
 }
 
+std::pair<HeadData, std::size_t> JceInputStream::ReadHead() const
+{
+	const auto byte = m_Reader->ReadPod<std::uint8_t>();
+	const auto type = static_cast<JceStruct::TypeEnum>(static_cast<std::uint8_t>(byte & 0x0F));
+	const auto tag = static_cast<std::uint32_t>((byte & 0xF0) >> 4);
+
+	if (tag != 0x0F)
+	{
+		return { { tag, type }, 1 };
+	}
+
+	return { { m_Reader->ReadPod<std::uint8_t>(), type }, 2 };
+}
+
+std::pair<HeadData, std::size_t> JceInputStream::PeekHead() const
+{
+	const auto underlyingStream = m_Reader->GetUnderlyingStream();
+	const auto pos = underlyingStream->GetPosition();
+	const auto head = ReadHead();
+	underlyingStream->SetPositionFromBegin(pos);
+	return head;
+}
+
+void JceInputStream::Skip(nLen len)
+{
+	m_Reader->Skip(len);
+}
+
+void JceInputStream::SkipToStructEnd()
+{
+	while (true)
+	{
+		const auto [head, headSize] = ReadHead();
+		SkipField(head.Type);
+		if (head.Type == JceStruct::TypeEnum::StructEnd)
+		{
+			return;
+		}
+	}
+}
+
+void JceInputStream::SkipField()
+{
+	const auto [head, headSize] = ReadHead();
+	SkipField(head.Type);
+}
+
+void JceInputStream::SkipField(JceStruct::TypeEnum type)
+{
+	switch (type)
+	{
+	case JceStruct::TypeEnum::Byte:
+		Skip(1);
+		break;
+	case JceStruct::TypeEnum::Short:
+		Skip(2);
+		break;
+	case JceStruct::TypeEnum::Int:
+		Skip(4);
+		break;
+	case JceStruct::TypeEnum::Long:
+		Skip(8);
+		break;
+	case JceStruct::TypeEnum::Float:
+		Skip(4);
+		break;
+	case JceStruct::TypeEnum::Double:
+		Skip(8);
+		break;
+	case JceStruct::TypeEnum::String1:
+		Skip(m_Reader->ReadPod<std::uint8_t>());
+		break;
+	case JceStruct::TypeEnum::String4:
+		Skip(m_Reader->ReadPod<std::uint32_t>());
+		break;
+	case JceStruct::TypeEnum::Map:
+	{
+		int size;
+		if (!Read(0, size))
+		{
+			nat_Throw(JceDecodeException, u8"Read size failed.");
+		}
+		for (std::size_t i = 0, iterationTime = static_cast<std::size_t>(size) * 2; i < iterationTime; ++i)
+		{
+			SkipField();
+		}
+		break;
+	}
+	case JceStruct::TypeEnum::List:
+	{
+		int size;
+		if (!Read(0, size))
+		{
+			nat_Throw(JceDecodeException, u8"Read size failed.");
+		}
+		for (std::size_t i = 0, iterationTime = static_cast<std::size_t>(size); i < iterationTime; ++i)
+		{
+			SkipField();
+		}
+		break;
+	}
+	case JceStruct::TypeEnum::StructBegin:
+		SkipToStructEnd();
+		break;
+	case JceStruct::TypeEnum::StructEnd:
+	case JceStruct::TypeEnum::ZeroTag:
+		break;
+	case JceStruct::TypeEnum::SimpleList:
+	{
+		const auto [head, headSize] = ReadHead();
+		if (head.Type != JceStruct::TypeEnum::Byte)
+		{
+			nat_Throw(JceDecodeException, u8"Type mismatch, expected 0, got {0}", static_cast<std::uint32_t>(head.Type))
+			;
+		}
+		std::uint8_t size;
+		if (!Read(0, size))
+		{
+			nat_Throw(JceDecodeException, u8"Read size failed.");
+		}
+		Skip(size);
+		break;
+	}
+	default:
+		nat_Throw(JceDecodeException, u8"Invalid type ({0}).", static_cast<std::uint32_t>(type));
+	}
+}
+
+bool JceInputStream::SkipToTag(std::uint32_t tag)
+try
+{
+	HeadData head;  // NOLINT
+	while (true)
+	{
+		std::size_t headSize;
+		std::tie(head, headSize) = PeekHead();
+		if (head.Type == JceStruct::TypeEnum::StructEnd)
+		{
+			return false;
+		}
+		if (tag <= head.Tag)
+		{
+			break;
+		}
+		Skip(headSize);
+		SkipField(head.Type);
+	}
+	return head.Tag == tag;
+}
+catch (JceDecodeException&)
+{
+	return false;
+}
+
+bool JceInputStream::doRead(std::uint32_t tag, std::uint8_t& value)
+{
+	if (SkipToTag(tag))
+	{
+		const auto [head, headSize] = ReadHead();
+		switch (head.Type)
+		{
+		case JceStruct::TypeEnum::Byte:
+			m_Reader->ReadPod(value);
+			break;
+		case JceStruct::TypeEnum::ZeroTag:
+			value = 0;
+			break;
+		default:
+			nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type))
+			;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool JceInputStream::doRead(std::uint32_t tag, std::int16_t& value)
+{
+	if (SkipToTag(tag))
+	{
+		const auto [head, headSize] = ReadHead();
+		switch (head.Type)
+		{
+		case JceStruct::TypeEnum::Byte:
+			value = m_Reader->ReadPod<std::int8_t>();
+			break;
+		case JceStruct::TypeEnum::Short:
+			m_Reader->ReadPod(value);
+			break;
+		case JceStruct::TypeEnum::ZeroTag:
+			value = 0;
+			break;
+		default:
+			nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type))
+			;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool JceInputStream::doRead(std::uint32_t tag, std::int32_t& value)
+{
+	if (SkipToTag(tag))
+	{
+		const auto [head, headSize] = ReadHead();
+		switch (head.Type)
+		{
+		case JceStruct::TypeEnum::Byte:
+			value = m_Reader->ReadPod<std::int8_t>();
+			break;
+		case JceStruct::TypeEnum::Short:
+			value = m_Reader->ReadPod<std::int16_t>();
+			break;
+		case JceStruct::TypeEnum::Int:
+			m_Reader->ReadPod(value);
+			break;
+		case JceStruct::TypeEnum::ZeroTag:
+			value = 0;
+			break;
+		default:
+			nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type))
+			;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool JceInputStream::doRead(std::uint32_t tag, std::int64_t& value)
+{
+	if (SkipToTag(tag))
+	{
+		const auto [head, headSize] = ReadHead();
+		switch (head.Type)
+		{
+		case JceStruct::TypeEnum::Byte:
+			value = m_Reader->ReadPod<std::int8_t>();
+			break;
+		case JceStruct::TypeEnum::Short:
+			value = m_Reader->ReadPod<std::int16_t>();
+			break;
+		case JceStruct::TypeEnum::Int:
+			value = m_Reader->ReadPod<std::int32_t>();
+			break;
+		case JceStruct::TypeEnum::Long:
+			m_Reader->ReadPod(value);
+			break;
+		case JceStruct::TypeEnum::ZeroTag:
+			value = 0;
+			break;
+		default:
+			nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected got {0}", static_cast<std::uint32_t>(head.
+				Type));
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool JceInputStream::doRead(std::uint32_t tag, float& value)
+{
+	if (SkipToTag(tag))
+	{
+		const auto [head, headSize] = ReadHead();
+		switch (head.Type)
+		{
+		case JceStruct::TypeEnum::Float:
+			m_Reader->ReadPod(value);
+			break;
+		case JceStruct::TypeEnum::ZeroTag:
+			value = 0;
+			break;
+		default:
+			nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type))
+			;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool JceInputStream::doRead(std::uint32_t tag, double& value)
+{
+	if (SkipToTag(tag))
+	{
+		const auto [head, headSize] = ReadHead();
+		switch (head.Type)
+		{
+		case JceStruct::TypeEnum::Float:
+			value = m_Reader->ReadPod<float>();
+			break;
+		case JceStruct::TypeEnum::Double:
+			m_Reader->ReadPod(value);
+			break;
+		case JceStruct::TypeEnum::ZeroTag:
+			value = 0;
+			break;
+		default:
+			nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type))
+			;
+		}
+
+		return true;
+	}
+
+	return false;
+}
+
+bool JceInputStream::doRead(std::uint32_t tag, nString& value)
+{
+	if (SkipToTag(tag))
+	{
+		const auto [head, headSize] = ReadHead();
+		std::size_t strSize;
+		switch (head.Type)
+		{
+		case JceStruct::TypeEnum::String1:
+			strSize = m_Reader->ReadPod<std::uint8_t>();
+			break;
+		case JceStruct::TypeEnum::String4:
+			strSize = m_Reader->ReadPod<std::uint32_t>();
+			if (strSize > JceStruct::MaxStringLength)
+			{
+				nat_Throw(JceDecodeException, u8"String too long, {0} sizes requested.", strSize);
+			}
+			break;
+		default:
+			nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type))
+			;
+		}
+
+		// 为了异常安全,构造临时字符串而不是就地修改
+		nString tmpString(0, strSize);
+		m_Reader->GetUnderlyingStream()->ReadBytes(reinterpret_cast<nData>(tmpString.data()), strSize);
+		value = std::move(tmpString);
+
+		return true;
+	}
+
+	return false;
+}
+
 JceOutputStream::JceOutputStream(natRefPointer<natBinaryWriter> writer)
 	: m_Writer{ std::move(writer) }
 {
@@ -34,14 +387,135 @@ JceOutputStream::~JceOutputStream()
 {
 }
 
-#define JCE_STRUCT(name) \
+natRefPointer<natBinaryWriter> JceOutputStream::GetWriter() const noexcept
+{
+	return m_Writer;
+}
+
+void JceOutputStream::WriteHead(HeadData head)
+{
+	if (head.Tag < 15)
+	{
+		m_Writer->WritePod(static_cast<std::uint8_t>((head.Tag << 4) | static_cast<std::uint8_t>(head.Type)));
+	}
+	else if (head.Tag < 256)
+	{
+		m_Writer->WritePod(static_cast<std::uint8_t>(static_cast<std::uint8_t>(head.Type) | 0xF0));
+		m_Writer->WritePod(static_cast<std::uint8_t>(head.Tag));
+	}
+	else
+	{
+		nat_Throw(JceEncodeException, u8"Tag is too big({0}).", head.Tag);
+	}
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, std::uint8_t value)
+{
+	if (!value)
+	{
+		WriteHead({ tag, JceStruct::TypeEnum::ZeroTag });
+	}
+	else
+	{
+		WriteHead({ tag, JceStruct::TypeEnum::Byte });
+		m_Writer->WritePod(value);
+	}
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, std::int16_t value)
+{
+	if (Utility::InRangeOf<std::int8_t>(value))
+	{
+		Write(tag, static_cast<std::uint8_t>(value));
+	}
+	else
+	{
+		WriteHead({ tag, JceStruct::TypeEnum::Short });
+		m_Writer->WritePod(value);
+	}
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, std::int32_t value)
+{
+	if (Utility::InRangeOf<std::int16_t>(value))
+	{
+		Write(tag, static_cast<std::int16_t>(value));
+	}
+	else
+	{
+		WriteHead({ tag, JceStruct::TypeEnum::Int });
+		m_Writer->WritePod(value);
+	}
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, std::int64_t value)
+{
+	if (Utility::InRangeOf<std::int32_t>(value))
+	{
+		Write(tag, static_cast<std::int32_t>(value));
+	}
+	else
+	{
+		WriteHead({ tag, JceStruct::TypeEnum::Long });
+		m_Writer->WritePod(value);
+	}
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, float value)
+{
+	WriteHead({ tag, JceStruct::TypeEnum::Float });
+	m_Writer->WritePod(value);
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, double value)
+{
+	WriteHead({ tag, JceStruct::TypeEnum::Double });
+	m_Writer->WritePod(value);
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, nStrView const& value)
+{
+	const auto strSize = value.size();
+	if (strSize <= std::numeric_limits<std::uint8_t>::max())
+	{
+		WriteHead({ tag, JceStruct::TypeEnum::String1 });
+		m_Writer->WritePod(static_cast<std::uint8_t>(strSize));
+	}
+	else
+	{
+		if (strSize > std::numeric_limits<std::uint32_t>::max())
+		{
+			nat_Throw(JceDecodeException, u8"String is too long({0} bytes).", strSize);
+		}
+
+		WriteHead({ tag, JceStruct::TypeEnum::String4 });
+		m_Writer->WritePod(static_cast<std::uint32_t>(strSize));
+	}
+	m_Writer->GetUnderlyingStream()->WriteBytes(reinterpret_cast<ncData>(value.data()), strSize);
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, nString const& value)
+{
+	doWrite(tag, value.GetView());
+}
+
+void JceOutputStream::doWrite(std::uint32_t tag, std::vector<std::uint8_t> const& value)
+{
+	WriteHead({ tag, JceStruct::TypeEnum::SimpleList });
+	WriteHead({ 0, JceStruct::TypeEnum::Byte });
+	const auto size = value.size();
+	Write(0, static_cast<std::int32_t>(size));
+	m_Writer->GetUnderlyingStream()->WriteBytes(reinterpret_cast<ncData>(value.data()), size);
+}
+
+#define JCE_STRUCT(name, alias) \
 	name::~name()\
 	{\
 	}\
 	\
 	nStrView name::GetJceStructName() const noexcept\
 	{\
-		return u8 ## #name ## _nv;\
+		return u8 ## alias ## _nv;\
 	}
 
 #include "JceStructDef.h"

+ 36 - 453
YumeBot/Jce.h

@@ -10,6 +10,9 @@ namespace YumeBot::Jce
 	DeclareException(JceDecodeException, JceException, u8"YumeBot::Jce::JceDecodeException");
 	DeclareException(JceEncodeException, JceException, u8"YumeBot::Jce::JceEncodeException");
 
+	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.");
+	static_assert(nString::UsingStringType == NatsuLib::StringType::Utf8, "Encoding should be utf-8.");
+
 	// Jce 中 Byte 是有符号的,此处表示为无符号,使用时需注意
 	class JceStruct
 		: public NatsuLib::natRefObj
@@ -22,8 +25,8 @@ namespace YumeBot::Jce
 	OP(Long, 0x03, std::int64_t)\
 	OP(Float, 0x04, float)\
 	OP(Double, 0x05, double)\
-	OP(String1, 0x06, std::string)\
-	OP(String4, 0x07, std::string)\
+	OP(String1, 0x06, nString)\
+	OP(String4, 0x07, nString)\
 	OP(Map, 0x08, std::unordered_map)\
 	OP(List, 0x09, std::vector)\
 	OP(StructBegin, 0x0A, NatsuLib::natRefPointer)\
@@ -57,7 +60,7 @@ namespace YumeBot::Jce
 
 		~JceStruct();
 
-		virtual nStrView GetJceStructName() const noexcept = 0;
+		[[nodiscard]] virtual nStrView GetJceStructName() const noexcept = 0;
 	};
 
 	template <typename T>
@@ -88,161 +91,17 @@ namespace YumeBot::Jce
 		explicit JceInputStream(NatsuLib::natRefPointer<NatsuLib::natBinaryReader> reader);
 		~JceInputStream();
 
-		std::pair<HeadData, std::size_t> ReadHead() const
-		{
-			const auto byte = m_Reader->ReadPod<std::uint8_t>();
-			const auto type = static_cast<JceStruct::TypeEnum>(static_cast<std::uint8_t>(byte & 0x0F));
-			const auto tag = static_cast<std::uint32_t>((byte & 0xF0) >> 4);
-
-			if (tag != 0x0F)
-			{
-				return { { tag, type }, 1 };
-			}
-
-			return { { m_Reader->ReadPod<std::uint8_t>(), type }, 2 };
-		}
-
-		std::pair<HeadData, std::size_t> PeekHead() const
-		{
-			const auto underlyingStream = m_Reader->GetUnderlyingStream();
-			const auto pos = underlyingStream->GetPosition();
-			const auto head = ReadHead();
-			underlyingStream->SetPositionFromBegin(pos);
-			return head;
-		}
-
-		void Skip(nLen len)
-		{
-			m_Reader->Skip(len);
-		}
-
-		void SkipToStructEnd()
-		{
-			while (true)
-			{
-				const auto [head, headSize] = ReadHead();
-				SkipField(head.Type);
-				if (head.Type == JceStruct::TypeEnum::StructEnd)
-				{
-					return;
-				}
-			}
-		}
-
-		void SkipField()
-		{
-			const auto [head, headSize] = ReadHead();
-			SkipField(head.Type);
-		}
-
-		void SkipField(JceStruct::TypeEnum type)
-		{
-			switch (type)
-			{
-			case JceStruct::TypeEnum::Byte:
-				Skip(1);
-				break;
-			case JceStruct::TypeEnum::Short:
-				Skip(2);
-				break;
-			case JceStruct::TypeEnum::Int:
-				Skip(4);
-				break;
-			case JceStruct::TypeEnum::Long:
-				Skip(8);
-				break;
-			case JceStruct::TypeEnum::Float:
-				Skip(4);
-				break;
-			case JceStruct::TypeEnum::Double:
-				Skip(8);
-				break;
-			case JceStruct::TypeEnum::String1:
-				Skip(m_Reader->ReadPod<std::uint8_t>());
-				break;
-			case JceStruct::TypeEnum::String4:
-				Skip(m_Reader->ReadPod<std::uint32_t>());
-				break;
-			case JceStruct::TypeEnum::Map:
-			{
-				int size;
-				if (!Read(0, size))
-				{
-					nat_Throw(JceDecodeException, u8"Read size failed.");
-				}
-				for (std::size_t i = 0, iterationTime = static_cast<std::size_t>(size) * 2; i < iterationTime; ++i)
-				{
-					SkipField();
-				}
-				break;
-			}
-			case JceStruct::TypeEnum::List:
-			{
-				int size;
-				if (!Read(0, size))
-				{
-					nat_Throw(JceDecodeException, u8"Read size failed.");
-				}
-				for (std::size_t i = 0, iterationTime = static_cast<std::size_t>(size); i < iterationTime; ++i)
-				{
-					SkipField();
-				}
-				break;
-			}
-			case JceStruct::TypeEnum::StructBegin:
-				SkipToStructEnd();
-				break;
-			case JceStruct::TypeEnum::StructEnd:
-			case JceStruct::TypeEnum::ZeroTag:
-				break;
-			case JceStruct::TypeEnum::SimpleList:
-			{
-				const auto [head, headSize] = ReadHead();
-				if (head.Type != JceStruct::TypeEnum::Byte)
-				{
-					nat_Throw(JceDecodeException, u8"Type mismatch, expected 0, got {0}", static_cast<std::uint32_t>(head.Type));
-				}
-				std::uint8_t size;
-				if (!Read(0, size))
-				{
-					nat_Throw(JceDecodeException, u8"Read size failed.");
-				}
-				Skip(size);
-				break;
-			}
-			default:
-				nat_Throw(JceDecodeException, u8"Invalid type ({0}).", static_cast<std::uint32_t>(type));
-			}
-		}
+		std::pair<HeadData, std::size_t> ReadHead() const;
+		std::pair<HeadData, std::size_t> PeekHead() const;
 
-		bool SkipToTag(std::uint32_t tag)
-		try
-		{
-			HeadData head;  // NOLINT
-			while (true)
-			{
-				std::size_t headSize;
-				std::tie(head, headSize) = PeekHead();
-				if (head.Type == JceStruct::TypeEnum::StructEnd)
-				{
-					return false;
-				}
-				if (tag <= head.Tag)
-				{
-					break;
-				}
-				Skip(headSize);
-				SkipField(head.Type);
-			}
-			return head.Tag == tag;
-		}
-		catch (JceDecodeException&)
-		{
-			return false;
-		}
+		void Skip(nLen len);
+		void SkipToStructEnd();
+		void SkipField();
+		void SkipField(JceStruct::TypeEnum type);
+		bool SkipToTag(std::uint32_t tag);
 
 		template <typename T>
-		bool Read(std::uint32_t tag, T& value, Detail::NoneType = Detail::None)
+		[[nodiscard]] bool Read(std::uint32_t tag, T& value, Detail::NoneType = Detail::None)
 		{
 			return doRead(tag, value);
 		}
@@ -272,197 +131,13 @@ namespace YumeBot::Jce
 	private:
 		NatsuLib::natRefPointer<NatsuLib::natBinaryReader> m_Reader;
 
-		bool doRead(std::uint32_t tag, std::uint8_t& value)
-		{
-			if (SkipToTag(tag))
-			{
-				const auto[head, headSize] = ReadHead();
-				switch (head.Type)
-				{
-				case JceStruct::TypeEnum::Byte:
-					m_Reader->ReadPod(value);
-					break;
-				case JceStruct::TypeEnum::ZeroTag:
-					value = 0;
-					break;
-				default:
-					nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type));
-				}
-
-				return true;
-			}
-
-			return false;
-		}
-
-		bool doRead(std::uint32_t tag, std::int16_t& value)
-		{
-			if (SkipToTag(tag))
-			{
-				const auto[head, headSize] = ReadHead();
-				switch (head.Type)
-				{
-				case JceStruct::TypeEnum::Byte:
-					value = m_Reader->ReadPod<std::int8_t>();
-					break;
-				case JceStruct::TypeEnum::Short:
-					m_Reader->ReadPod(value);
-					break;
-				case JceStruct::TypeEnum::ZeroTag:
-					value = 0;
-					break;
-				default:
-					nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type));
-				}
-
-				return true;
-			}
-
-			return false;
-		}
-
-		bool doRead(std::uint32_t tag, std::int32_t& value)
-		{
-			if (SkipToTag(tag))
-			{
-				const auto[head, headSize] = ReadHead();
-				switch (head.Type)
-				{
-				case JceStruct::TypeEnum::Byte:
-					value = m_Reader->ReadPod<std::int8_t>();
-					break;
-				case JceStruct::TypeEnum::Short:
-					value = m_Reader->ReadPod<std::int16_t>();
-					break;
-				case JceStruct::TypeEnum::Int:
-					m_Reader->ReadPod(value);
-					break;
-				case JceStruct::TypeEnum::ZeroTag:
-					value = 0;
-					break;
-				default:
-					nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type));
-				}
-
-				return true;
-			}
-
-			return false;
-		}
-
-		bool doRead(std::uint32_t tag, std::int64_t& value)
-		{
-			if (SkipToTag(tag))
-			{
-				const auto[head, headSize] = ReadHead();
-				switch (head.Type)
-				{
-				case JceStruct::TypeEnum::Byte:
-					value = m_Reader->ReadPod<std::int8_t>();
-					break;
-				case JceStruct::TypeEnum::Short:
-					value = m_Reader->ReadPod<std::int16_t>();
-					break;
-				case JceStruct::TypeEnum::Int:
-					value = m_Reader->ReadPod<std::int32_t>();
-					break;
-				case JceStruct::TypeEnum::Long:
-					m_Reader->ReadPod(value);
-					break;
-				case JceStruct::TypeEnum::ZeroTag:
-					value = 0;
-					break;
-				default:
-					nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected got {0}", static_cast<std::uint32_t>(head.Type));
-				}
-
-				return true;
-			}
-
-			return false;
-		}
-
-		bool doRead(std::uint32_t tag, float& value)
-		{
-			if (SkipToTag(tag))
-			{
-				const auto[head, headSize] = ReadHead();
-				switch (head.Type)
-				{
-				case JceStruct::TypeEnum::Float:
-					m_Reader->ReadPod(value);
-					break;
-				case JceStruct::TypeEnum::ZeroTag:
-					value = 0;
-					break;
-				default:
-					nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type));
-				}
-
-				return true;
-			}
-
-			return false;
-		}
-
-		bool doRead(std::uint32_t tag, double& value)
-		{
-			if (SkipToTag(tag))
-			{
-				const auto[head, headSize] = ReadHead();
-				switch (head.Type)
-				{
-				case JceStruct::TypeEnum::Float:
-					value = m_Reader->ReadPod<float>();
-					break;
-				case JceStruct::TypeEnum::Double:
-					m_Reader->ReadPod(value);
-					break;
-				case JceStruct::TypeEnum::ZeroTag:
-					value = 0;
-					break;
-				default:
-					nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type));
-				}
-
-				return true;
-			}
-
-			return false;
-		}
-
-		bool doRead(std::uint32_t tag, std::string& value)
-		{
-			if (SkipToTag(tag))
-			{
-				const auto[head, headSize] = ReadHead();
-				std::size_t strSize;
-				switch (head.Type)
-				{
-				case JceStruct::TypeEnum::String1:
-					strSize = m_Reader->ReadPod<std::uint8_t>();
-					break;
-				case JceStruct::TypeEnum::String4:
-					strSize = m_Reader->ReadPod<std::uint32_t>();
-					if (strSize > JceStruct::MaxStringLength)
-					{
-						nat_Throw(JceDecodeException, u8"String too long, {0} sizes requested.", strSize);
-					}
-					break;
-				default:
-					nat_Throw(JceDecodeException, u8"Type mismatch, got unexpected {0}.", static_cast<std::uint32_t>(head.Type));
-				}
-
-				// 为了异常安全,构造临时字符串而不是就地修改
-				std::string tmpString(strSize, 0);
-				m_Reader->GetUnderlyingStream()->ReadBytes(reinterpret_cast<nData>(tmpString.data()), strSize);
-				value = std::move(tmpString);
-
-				return true;
-			}
-
-			return false;
-		}
+		bool doRead(std::uint32_t tag, std::uint8_t& value);
+		bool doRead(std::uint32_t tag, std::int16_t& value);
+		bool doRead(std::uint32_t tag, std::int32_t& value);
+		bool doRead(std::uint32_t tag, std::int64_t& value);
+		bool doRead(std::uint32_t tag, float& value);
+		bool doRead(std::uint32_t tag, double& value);
+		bool doRead(std::uint32_t tag, nString& value);
 
 		template <typename Key, typename Value>
 		bool doRead(std::uint32_t tag, std::unordered_map<Key, Value>& value)
@@ -616,27 +291,9 @@ namespace YumeBot::Jce
 		explicit JceOutputStream(NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> writer);
 		~JceOutputStream();
 
-		NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> GetWriter() const noexcept
-		{
-			return m_Writer;
-		}
+		[[nodiscard]] NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> GetWriter() const noexcept;
 
-		void WriteHead(HeadData head)
-		{
-			if (head.Tag < 15)
-			{
-				m_Writer->WritePod(static_cast<std::uint8_t>((head.Tag << 4) | static_cast<std::uint8_t>(head.Type)));
-			}
-			else if (head.Tag < 256)
-			{
-				m_Writer->WritePod(static_cast<std::uint8_t>(static_cast<std::uint8_t>(head.Type) | 0xF0));
-				m_Writer->WritePod(static_cast<std::uint8_t>(head.Tag));
-			}
-			else
-			{
-				nat_Throw(JceEncodeException, u8"Tag is too big({0}).", head.Tag);
-			}
-		}
+		void WriteHead(HeadData head);
 
 		template <typename T>
 		void Write(std::uint32_t tag, T const& value)
@@ -665,90 +322,14 @@ namespace YumeBot::Jce
 	private:
 		NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> m_Writer;
 
-		void doWrite(std::uint32_t tag, std::uint8_t value)
-		{
-			if (!value)
-			{
-				WriteHead({ tag, JceStruct::TypeEnum::ZeroTag });
-			}
-			else
-			{
-				WriteHead({ tag, JceStruct::TypeEnum::Byte });
-				m_Writer->WritePod(value);
-			}
-		}
-
-		void doWrite(std::uint32_t tag, std::int16_t value)
-		{
-			if (Utility::InRangeOf<std::int8_t>(value))
-			{
-				Write(tag, static_cast<std::uint8_t>(value));
-			}
-			else
-			{
-				WriteHead({ tag, JceStruct::TypeEnum::Short });
-				m_Writer->WritePod(value);
-			}
-		}
-
-		void doWrite(std::uint32_t tag, std::int32_t value)
-		{
-			if (Utility::InRangeOf<std::int16_t>(value))
-			{
-				Write(tag, static_cast<std::int16_t>(value));
-			}
-			else
-			{
-				WriteHead({ tag, JceStruct::TypeEnum::Int });
-				m_Writer->WritePod(value);
-			}
-		}
-
-		void doWrite(std::uint32_t tag, std::int64_t value)
-		{
-			if (Utility::InRangeOf<std::int32_t>(value))
-			{
-				Write(tag, static_cast<std::int32_t>(value));
-			}
-			else
-			{
-				WriteHead({ tag, JceStruct::TypeEnum::Long });
-				m_Writer->WritePod(value);
-			}
-		}
-
-		void doWrite(std::uint32_t tag, float value)
-		{
-			WriteHead({ tag, JceStruct::TypeEnum::Float });
-			m_Writer->WritePod(value);
-		}
-
-		void doWrite(std::uint32_t tag, double value)
-		{
-			WriteHead({ tag, JceStruct::TypeEnum::Double });
-			m_Writer->WritePod(value);
-		}
-
-		void doWrite(std::uint32_t tag, std::string const& value)
-		{
-			const auto strSize = value.size();
-			if (strSize <= std::numeric_limits<std::uint8_t>::max())
-			{
-				WriteHead({ tag, JceStruct::TypeEnum::String1 });
-				m_Writer->WritePod(static_cast<std::uint8_t>(strSize));
-			}
-			else
-			{
-				if (strSize > std::numeric_limits<std::uint32_t>::max())
-				{
-					nat_Throw(JceDecodeException, u8"String is too long({0} bytes).", strSize);
-				}
-
-				WriteHead({ tag, JceStruct::TypeEnum::String4 });
-				m_Writer->WritePod(static_cast<std::uint32_t>(strSize));
-			}
-			m_Writer->GetUnderlyingStream()->WriteBytes(reinterpret_cast<ncData>(value.data()), strSize);
-		}
+		void doWrite(std::uint32_t tag, std::uint8_t value);
+		void doWrite(std::uint32_t tag, std::int16_t value);
+		void doWrite(std::uint32_t tag, std::int32_t value);
+		void doWrite(std::uint32_t tag, std::int64_t value);
+		void doWrite(std::uint32_t tag, float value);
+		void doWrite(std::uint32_t tag, double value);
+		void doWrite(std::uint32_t tag, nStrView const& value);
+		void doWrite(std::uint32_t tag, nString const& value);
 
 		template <typename Key, typename Value>
 		void doWrite(std::uint32_t tag, std::unordered_map<Key, Value> const& value)
@@ -762,6 +343,8 @@ namespace YumeBot::Jce
 			}
 		}
 
+		void doWrite(std::uint32_t tag, std::vector<std::uint8_t> const& value);
+
 		template <typename T>
 		void doWrite(std::uint32_t tag, std::vector<T> const& value)
 		{
@@ -856,7 +439,7 @@ namespace YumeBot::Jce
 			return tag;\
 		}
 
-#define JCE_STRUCT(name) \
+#define JCE_STRUCT(name, alias) \
 	class name\
 		: public NatsuLib::natRefObjImpl<name, JceStruct>\
 	{\
@@ -887,7 +470,7 @@ namespace YumeBot::Jce
 				}\
 			}
 
-#define JCE_STRUCT(name) \
+#define JCE_STRUCT(name, alias) \
 	template <>\
 	struct JceDeserializer<name>\
 	{\
@@ -905,7 +488,7 @@ namespace YumeBot::Jce
 
 #define FIELD(name, tag, type, ...) stream.Write(tag, value->Get##name());
 
-#define JCE_STRUCT(name) \
+#define JCE_STRUCT(name, alias) \
 	template <>\
 	struct JceSerializer<name>\
 	{\

+ 11 - 2
YumeBot/JceStructDef.h

@@ -1,5 +1,9 @@
 #ifndef JCE_STRUCT
-#define JCE_STRUCT(name)
+#define JCE_STRUCT(name, alias)
+#endif
+
+#ifndef JCE_STRUCT_DEFAULT_ALIAS
+#define JCE_STRUCT_DEFAULT_ALIAS(name) JCE_STRUCT(name, #name)
 #endif
 
 #ifndef END_JCE_STRUCT
@@ -74,13 +78,17 @@
 #define SIMPLE_LIST(name, tag, ...) FIELD(name, tag, SimpleList, __VA_ARGS__)
 #endif
 
-JCE_STRUCT(JceTest)
+JCE_STRUCT_DEFAULT_ALIAS(JceTest)
 	INT(TestInt, 0)
 	FLOAT(TestFloat, 1, IS_OPTIONAL(1.0f))
 	MAP(TestMap, 2, TEMPLATE_ARGUMENT(std::int32_t, float))
 	LIST(TestList, 3, TEMPLATE_ARGUMENT(double), IS_OPTIONAL((FieldType{ 1.0, 2.0, 3.0 })))
 END_JCE_STRUCT(JceTest)
 
+JCE_STRUCT(SignatureReq, "KQQConfig.SignatureReq")
+	LONG(uin, 0)
+END_JCE_STRUCT(SignatureReq)
+
 #undef SIMPLE_LIST
 #undef ZERO_TAG
 #undef STRUCT
@@ -104,4 +112,5 @@ END_JCE_STRUCT(JceTest)
 #undef ATTRIBUTE_SET
 
 #undef END_JCE_STRUCT
+#undef JCE_STRUCT_DEFAULT_ALIAS
 #undef JCE_STRUCT

+ 32 - 0
YumeBot/Wup.cpp

@@ -0,0 +1,32 @@
+#include "Wup.h"
+
+using namespace NatsuLib;
+using namespace YumeBot;
+using namespace Jce;
+using namespace Wup;
+
+nStrView Wup::Detail::GetName(natRefPointer<JceStruct> const& value) noexcept
+{
+	return value->GetJceStructName();
+}
+
+bool OldUniAttribute::Remove(nString const& name)
+{
+	return !!m_Data.erase(name);
+}
+
+void OldUniAttribute::Encode(natRefPointer<natBinaryWriter> const& writer) const
+{
+	JceOutputStream output{ writer };
+	output.Write(0, m_Data);
+}
+
+void OldUniAttribute::Decode(natRefPointer<natBinaryReader> const& reader)
+{
+	JceInputStream input{ reader };
+	m_Data.clear();
+	if (!input.Read(0, m_Data))
+	{
+		nat_Throw(natErrException, NatErr_InternalErr, u8"Data is corrupted"_nv);
+	}
+}

+ 171 - 0
YumeBot/Wup.h

@@ -0,0 +1,171 @@
+#pragma once
+#include "Jce.h"
+#include <natStringUtil.h>
+
+namespace YumeBot::Jce::Wup
+{
+	namespace Detail
+	{
+		template <typename T>
+		struct ImplicitConvertibleIdentityType
+		{
+			using Type = T;
+
+			constexpr ImplicitConvertibleIdentityType() = default;
+
+			template <typename U, std::enable_if_t<std::is_same_v<T, U>, int> = 0>
+			constexpr ImplicitConvertibleIdentityType(U const&) noexcept
+			{
+			}
+		};
+
+		template <typename T>
+		constexpr ImplicitConvertibleIdentityType<Utility::RemoveCvRef<T>> ImplicitConvertibleIdentity{};
+
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<std::uint8_t>) noexcept
+		{
+			using namespace NatsuLib::StringLiterals;
+			return u8"char"_nv;
+		}
+
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<std::int16_t>) noexcept
+		{
+			using namespace NatsuLib::StringLiterals;
+			return u8"short"_nv;
+		}
+
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<std::int32_t>) noexcept
+		{
+			using namespace NatsuLib::StringLiterals;
+			return u8"int32"_nv;
+		}
+
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<std::int64_t>) noexcept
+		{
+			using namespace NatsuLib::StringLiterals;
+			return u8"int64"_nv;
+		}
+
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<float>) noexcept
+		{
+			using namespace NatsuLib::StringLiterals;
+			return u8"float"_nv;
+		}
+
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<double>) noexcept
+		{
+			using namespace NatsuLib::StringLiterals;
+			return u8"double"_nv;
+		}
+
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<std::string>) noexcept
+		{
+			using namespace NatsuLib::StringLiterals;
+			return u8"string"_nv;
+		}
+
+		nStrView GetName(NatsuLib::natRefPointer<JceStruct> const& value) noexcept;
+
+#define JCE_STRUCT(name, alias) \
+		constexpr nStrView GetName(ImplicitConvertibleIdentityType<NatsuLib::natRefPointer<name>>) noexcept\
+		{\
+			using namespace NatsuLib::StringLiterals;\
+			return u8 ## alias ## _nv;\
+		}
+
+#include "JceStructDef.h"
+
+		template <typename T>
+		nString GetName(std::vector<T> const& value)
+		{
+			using namespace NatsuLib::StringLiterals;
+
+			if constexpr (std::is_same_v<JceStruct, T>)
+			{
+				if (value.empty())
+				{
+					return u8"list<?>"_ns;
+				}
+
+				return NatsuLib::natUtil::FormatString(u8"list<{0}>"_nv, GetName(value.front()));
+			}
+			else
+			{
+				return NatsuLib::natUtil::FormatString(u8"list<{0}>"_nv, GetName(ImplicitConvertibleIdentity<T>));
+			}
+		}
+
+		template <typename Key, typename Value>
+		nString GetName(std::unordered_map<Key, Value> const& value)
+		{
+			using namespace NatsuLib::StringLiterals;
+
+			if constexpr (std::is_same_v<JceStruct, Key> || std::is_same_v<JceStruct, Value>)
+			{
+				if (value.empty())
+				{
+					return u8"map<?,?>"_ns;
+				}
+
+				const auto& front = *value.cbegin();
+				return NatsuLib::natUtil::FormatString(u8"map<{0},{1}>"_nv, GetName(front.first), GetName(front.second));
+			}
+			else
+			{
+				return NatsuLib::natUtil::FormatString(u8"map<{0},{1}>"_nv, GetName(ImplicitConvertibleIdentity<Key>), GetName(ImplicitConvertibleIdentity<Value>));
+			}
+		}
+	}
+
+	class OldUniAttribute
+	{
+	public:
+		template <typename T>
+		void Put(nString const& name, T const& value)
+		{
+			const auto memoryStream = NatsuLib::make_ref<NatsuLib::natMemoryStream>(0, false, true, true);
+			JceOutputStream out{ NatsuLib::make_ref<NatsuLib::natBinaryWriter>(memoryStream) };
+			out.Write(0, value);
+			memoryStream->SetPositionFromBegin(0);
+			m_Data[name][Detail::GetName(value)].assign(reinterpret_cast<const std::uint8_t*>(memoryStream->GetInternalBuffer()),
+									  reinterpret_cast<const std::uint8_t*>(memoryStream->GetInternalBuffer() + memoryStream->GetSize()));
+		}
+
+		template <typename T>
+		T Get(nString const& name) const
+		{
+			using namespace NatsuLib;
+
+			const auto iter = m_Data.find(name);
+			if (iter == m_Data.cend())
+			{
+				nat_Throw(natErrException, NatErr_NotFound, u8"No such key(\"{0}\")."_nv, name);
+			}
+
+			constexpr auto fieldTypeName = Detail::GetName(Detail::ImplicitConvertibleIdentity<T>);
+			const auto fieldIter = iter->second.find(fieldTypeName);
+			if (fieldIter == iter->second.cend())
+			{
+				nat_Throw(natErrException, NatErr_NotFound, u8"No such field of type({0})."_nv, fieldTypeName);
+			}
+
+			JceInputStream in{ make_ref<natBinaryReader>(make_ref<natExternMemoryStream>(fieldIter->second.data(), fieldIter->second.size(), true)) };
+
+			T result;
+			if (!in.Read(0, result))
+			{
+				nat_Throw(natErrException, NatErr_InvalidArg, u8"Field of type {0} from key \"{1}\" is corrupted."_nv);
+			}
+
+			return result;
+		}
+
+		bool Remove(nString const& name);
+
+		void Encode(NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> const& writer) const;
+		void Decode(NatsuLib::natRefPointer<NatsuLib::natBinaryReader> const& reader);
+
+	private:
+		std::unordered_map<nString, std::unordered_map<nString, std::vector<std::uint8_t>>> m_Data;
+	};
+}

+ 2 - 0
YumeBot/YumeBot.vcxproj

@@ -22,6 +22,7 @@
     <ClInclude Include="Cryptography.h" />
     <ClInclude Include="Jce.h" />
     <ClInclude Include="Tlv.h" />
+    <ClInclude Include="Wup.h" />
     <ClInclude Include="YumeBot.h" />
     <ClInclude Include="JceStructDef.h" />
     <ClInclude Include="Utility.h" />
@@ -29,6 +30,7 @@
   <ItemGroup>
     <ClCompile Include="Cryptography.cpp" />
     <ClCompile Include="Jce.cpp" />
+    <ClCompile Include="Wup.cpp" />
     <ClCompile Include="YumeBot.cpp" />
   </ItemGroup>
   <ItemGroup>

+ 6 - 0
YumeBot/YumeBot.vcxproj.filters

@@ -33,6 +33,9 @@
     <ClInclude Include="JceStructDef.h">
       <Filter>头文件</Filter>
     </ClInclude>
+    <ClInclude Include="Wup.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Cryptography.cpp">
@@ -44,5 +47,8 @@
     <ClCompile Include="YumeBot.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
+    <ClCompile Include="Wup.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>