Răsfoiți Sursa

实现 JceOutputStream,修复 bug,添加 Jce 单元测试

akemimadoka 7 ani în urmă
părinte
comite
25aaa3060c

+ 1 - 1
Extern/NatsuLib

@@ -1 +1 @@
-Subproject commit 8a309e2f50247cac023883adb696e94651aa4cd7
+Subproject commit ccaad4e75ef4ca0e25e8f2d11db663a8a178f273

+ 1 - 0
YumeBot.Test/CryptographyTest.cpp

@@ -1,5 +1,6 @@
 #include "pch.h"
 #include "pch.h"
 #include <catch.hpp>
 #include <catch.hpp>
+#include <Cryptography.h>
 
 
 using namespace YumeBot;
 using namespace YumeBot;
 
 

+ 42 - 0
YumeBot.Test/JceTest.cpp

@@ -0,0 +1,42 @@
+#include "pch.h"
+#include <catch.hpp>
+#include <Jce.h>
+#include <natStream.h>
+
+using namespace NatsuLib;
+using namespace YumeBot;
+
+TEST_CASE("Jce", "[Jce]")
+{
+	using namespace Jce;
+
+	SECTION("Serialization")
+	{
+		const auto test = make_ref<TlvTest>();
+		test->SetTestFloat(2.0f);
+		test->SetTestInt(233);
+		test->GetTestMap()[1] = 2.0f;
+		test->GetTestMap()[3] = 5.0f;
+
+		const auto memoryStream = make_ref<natMemoryStream>(0, true, true, true);
+
+		{
+			JceOutputStream outputStream{ make_ref<natBinaryWriter>(memoryStream) };
+			outputStream.Write(0, test);
+		}
+
+		memoryStream->SetPositionFromBegin(0);
+
+		natRefPointer<TlvTest> ptr;
+
+		{
+			JceInputStream inputStream{ make_ref<natBinaryReader>(memoryStream) };
+			const auto readSucceed = inputStream.Read(0, ptr);
+			REQUIRE(readSucceed);
+		}
+
+		CHECK(ptr->GetTestFloat() == test->GetTestFloat());
+		CHECK(ptr->GetTestInt() == test->GetTestInt());
+		CHECK(ptr->GetTestMap() == test->GetTestMap());
+	}
+}

+ 9 - 8
YumeBot.Test/YumeBot.Test.vcxproj

@@ -72,23 +72,23 @@
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <LinkIncremental>true</LinkIncremental>
     <LinkIncremental>true</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <LinkIncremental>true</LinkIncremental>
     <LinkIncremental>true</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <LinkIncremental>false</LinkIncremental>
     <LinkIncremental>false</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>
     <LinkIncremental>false</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <ClCompile>
     <ClCompile>
@@ -171,6 +171,7 @@
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="CryptographyTest.cpp" />
     <ClCompile Include="CryptographyTest.cpp" />
+    <ClCompile Include="JceTest.cpp" />
     <ClCompile Include="pch.cpp">
     <ClCompile Include="pch.cpp">
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
       <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>

+ 3 - 0
YumeBot.Test/YumeBot.Test.vcxproj.filters

@@ -29,5 +29,8 @@
     <ClCompile Include="YumeBot.Test.cpp">
     <ClCompile Include="YumeBot.Test.cpp">
       <Filter>源文件</Filter>
       <Filter>源文件</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="JceTest.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

BIN
YumeBot.Test/pch.h


+ 3 - 3
YumeBot/Jce.cpp

@@ -26,7 +26,7 @@ JceOutputStream::~JceOutputStream()
 {
 {
 }
 }
 
 
-#define TLV_CODE(name, code, ...) \
+#define TLV_CODE(name, code) \
 	name::~name()\
 	name::~name()\
 	{\
 	{\
 	}\
 	}\
@@ -36,9 +36,9 @@ JceOutputStream::~JceOutputStream()
 		return #name;\
 		return #name;\
 	}\
 	}\
 	\
 	\
-	TypeEnum name::GetJceStructType() const noexcept\
+	JceCode name::GetJceStructType() const noexcept\
 	{\
 	{\
-		return TypeEnum::name;\
+		return JceCode::name;\
 	}
 	}
 
 
 #include "TlvCodeDef.h"
 #include "TlvCodeDef.h"

+ 238 - 49
YumeBot/Jce.h

@@ -9,6 +9,9 @@ namespace YumeBot::Jce
 	DeclareException(JceDecodeException, JceException, u8"YumeBot::Jce::JceDecodeException");
 	DeclareException(JceDecodeException, JceException, u8"YumeBot::Jce::JceDecodeException");
 	DeclareException(JceEncodeException, JceException, u8"YumeBot::Jce::JceEncodeException");
 	DeclareException(JceEncodeException, JceException, u8"YumeBot::Jce::JceEncodeException");
 
 
+	enum class JceCode;
+
+	// Jce 中 Byte 是有符号的,此处表示为无符号,使用时需注意
 	class JceStruct
 	class JceStruct
 		: public NatsuLib::natRefObj
 		: public NatsuLib::natRefObj
 	{
 	{
@@ -41,7 +44,7 @@ namespace YumeBot::Jce
 		~JceStruct();
 		~JceStruct();
 
 
 		virtual std::string_view GetJceStructName() const noexcept = 0;
 		virtual std::string_view GetJceStructName() const noexcept = 0;
-		virtual TypeEnum GetJceStructType() const noexcept = 0;
+		virtual JceCode GetJceStructType() const noexcept = 0;
 	};
 	};
 
 
 	template <typename T>
 	template <typename T>
@@ -63,7 +66,7 @@ namespace YumeBot::Jce
 	{
 	{
 		template <typename T>
 		template <typename T>
 		struct Apply
 		struct Apply
-			: Utility::ResultType<std::optional<T>>
+			: Utility::ResultType<std::conditional_t<Utility::IsTemplateOf<T, NatsuLib::natRefPointer>::value, T, std::optional<T>>>
 		{
 		{
 		};
 		};
 	};
 	};
@@ -92,16 +95,25 @@ namespace YumeBot::Jce
 
 
 #undef FIELD_TYPE_BUILDER_OP
 #undef FIELD_TYPE_BUILDER_OP
 
 
-	class JceInputStream
-		: NatsuLib::noncopyable
+	namespace Detail
 	{
 	{
-	public:
-		struct HeadData
+		struct NoneType
 		{
 		{
-			std::uint32_t Tag;
-			JceStruct::TypeEnum Type;
 		};
 		};
 
 
+		constexpr NoneType None{};
+	}
+
+	struct HeadData
+	{
+		std::uint32_t Tag;
+		JceStruct::TypeEnum Type;
+	};
+
+	class JceInputStream
+		: NatsuLib::noncopyable
+	{
+	public:
 		explicit JceInputStream(NatsuLib::natRefPointer<NatsuLib::natBinaryReader> reader);
 		explicit JceInputStream(NatsuLib::natRefPointer<NatsuLib::natBinaryReader> reader);
 		~JceInputStream();
 		~JceInputStream();
 
 
@@ -259,13 +271,13 @@ namespace YumeBot::Jce
 		}
 		}
 
 
 		template <typename T>
 		template <typename T>
-		bool Read(std::size_t tag, T& value, std::nullptr_t = nullptr)
+		bool Read(std::uint32_t tag, T& value, Detail::NoneType = Detail::None)
 		{
 		{
 			return Reader<T>::DoRead(*this, tag, value);
 			return Reader<T>::DoRead(*this, tag, value);
 		}
 		}
 
 
 		template <typename T, typename U>
 		template <typename T, typename U>
-		std::enable_if_t<std::is_assignable_v<T&, U&&>, std::true_type> Read(std::size_t tag, T& value, U&& defaultValue)
+		std::enable_if_t<std::is_assignable_v<T&, U&&>, std::true_type> Read(std::uint32_t tag, T& value, U&& defaultValue)
 		{
 		{
 			bool readSucceed;
 			bool readSucceed;
 			if constexpr (Utility::IsTemplateOf<T, std::optional>::value)
 			if constexpr (Utility::IsTemplateOf<T, std::optional>::value)
@@ -295,7 +307,7 @@ namespace YumeBot::Jce
 		template <>
 		template <>
 		struct Reader<std::uint8_t>
 		struct Reader<std::uint8_t>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, std::uint8_t& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, std::uint8_t& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -322,7 +334,7 @@ namespace YumeBot::Jce
 		template <>
 		template <>
 		struct Reader<std::int16_t>
 		struct Reader<std::int16_t>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, std::int16_t& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, std::int16_t& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -330,7 +342,7 @@ namespace YumeBot::Jce
 					switch (head.Type)
 					switch (head.Type)
 					{
 					{
 					case JceStruct::TypeEnum::Byte:
 					case JceStruct::TypeEnum::Byte:
-						value = self.m_Reader->ReadPod<std::uint8_t>();
+						value = self.m_Reader->ReadPod<std::int8_t>();
 						break;
 						break;
 					case JceStruct::TypeEnum::Short:
 					case JceStruct::TypeEnum::Short:
 						self.m_Reader->ReadPod(value);
 						self.m_Reader->ReadPod(value);
@@ -352,7 +364,7 @@ namespace YumeBot::Jce
 		template <>
 		template <>
 		struct Reader<std::int32_t>
 		struct Reader<std::int32_t>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, std::int32_t& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, std::int32_t& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -360,7 +372,7 @@ namespace YumeBot::Jce
 					switch (head.Type)
 					switch (head.Type)
 					{
 					{
 					case JceStruct::TypeEnum::Byte:
 					case JceStruct::TypeEnum::Byte:
-						value = self.m_Reader->ReadPod<std::uint8_t>();
+						value = self.m_Reader->ReadPod<std::int8_t>();
 						break;
 						break;
 					case JceStruct::TypeEnum::Short:
 					case JceStruct::TypeEnum::Short:
 						value = self.m_Reader->ReadPod<std::int16_t>();
 						value = self.m_Reader->ReadPod<std::int16_t>();
@@ -385,7 +397,7 @@ namespace YumeBot::Jce
 		template <>
 		template <>
 		struct Reader<std::int64_t>
 		struct Reader<std::int64_t>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, std::int64_t& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, std::int64_t& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -393,7 +405,7 @@ namespace YumeBot::Jce
 					switch (head.Type)
 					switch (head.Type)
 					{
 					{
 					case JceStruct::TypeEnum::Byte:
 					case JceStruct::TypeEnum::Byte:
-						value = self.m_Reader->ReadPod<std::uint8_t>();
+						value = self.m_Reader->ReadPod<std::int8_t>();
 						break;
 						break;
 					case JceStruct::TypeEnum::Short:
 					case JceStruct::TypeEnum::Short:
 						value = self.m_Reader->ReadPod<std::int16_t>();
 						value = self.m_Reader->ReadPod<std::int16_t>();
@@ -421,7 +433,7 @@ namespace YumeBot::Jce
 		template <>
 		template <>
 		struct Reader<float>
 		struct Reader<float>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, float& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, float& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -448,7 +460,7 @@ namespace YumeBot::Jce
 		template <>
 		template <>
 		struct Reader<double>
 		struct Reader<double>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, double& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, double& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -478,7 +490,7 @@ namespace YumeBot::Jce
 		template <>
 		template <>
 		struct Reader<std::string>
 		struct Reader<std::string>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, std::string& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, std::string& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -515,7 +527,7 @@ namespace YumeBot::Jce
 		template <typename Key, typename Value>
 		template <typename Key, typename Value>
 		struct Reader<std::unordered_map<Key, Value>>
 		struct Reader<std::unordered_map<Key, Value>>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, std::unordered_map<Key, Value>& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, std::unordered_map<Key, Value>& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -569,7 +581,7 @@ namespace YumeBot::Jce
 		template <typename T>
 		template <typename T>
 		struct Reader<std::vector<T>>
 		struct Reader<std::vector<T>>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, std::vector<T>& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, std::vector<T>& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -642,9 +654,9 @@ namespace YumeBot::Jce
 		};
 		};
 
 
 		template <typename T>
 		template <typename T>
-		struct Reader<T, std::enable_if_t<std::is_base_of_v<JceStruct, T>>>
+		struct Reader<NatsuLib::natRefPointer<T>, std::enable_if_t<std::is_base_of_v<JceStruct, T>>>
 		{
 		{
-			static bool DoRead(JceInputStream& self, std::size_t tag, T& value)
+			static bool DoRead(JceInputStream& self, std::uint32_t tag, NatsuLib::natRefPointer<T>& value)
 			{
 			{
 				if (self.SkipToTag(tag))
 				if (self.SkipToTag(tag))
 				{
 				{
@@ -673,14 +685,36 @@ namespace YumeBot::Jce
 		explicit JceOutputStream(NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> writer);
 		explicit JceOutputStream(NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> writer);
 		~JceOutputStream();
 		~JceOutputStream();
 
 
-		template <JceStruct::TypeEnum Type, typename T>
-		void Write(std::size_t tag, T const& value)
+		NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> GetWriter() const noexcept
 		{
 		{
-			Writer<Type, T>::DoWrite(*this, tag, value);
+			return m_Writer;
 		}
 		}
 
 
-		template <JceStruct::TypeEnum Type, typename T>
-		void Write(std::size_t tag, std::optional<T> const& value)
+		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);
+			}
+		}
+
+		template <typename T>
+		void Write(std::uint32_t tag, T const& value)
+		{
+			Writer<T>::DoWrite(*this, tag, value);
+		}
+
+		template <typename T>
+		void Write(std::uint32_t tag, std::optional<T> const& value)
 		{
 		{
 			if (value.has_value())
 			if (value.has_value())
 			{
 			{
@@ -688,25 +722,178 @@ namespace YumeBot::Jce
 			}
 			}
 		}
 		}
 
 
+		template <typename T>
+		void Write(std::uint32_t tag, NatsuLib::natRefPointer<T> const& value)
+		{
+			if (value)
+			{
+				Writer<NatsuLib::natRefPointer<T>>::DoWrite(*this, tag, value);
+			}
+		}
+
 	private:
 	private:
 		NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> m_Writer;
 		NatsuLib::natRefPointer<NatsuLib::natBinaryWriter> m_Writer;
 
 
-		template <JceStruct::TypeEnum Type, typename T, typename = void>
+		template <typename T, typename = void>
 		struct Writer;
 		struct Writer;
 
 
-		template <JceStruct::TypeEnum Type, typename T>
-		struct Writer<Type, T, std::enable_if_t<std::is_pod_v<T>>>
+		template <>
+		struct Writer<std::uint8_t>
 		{
 		{
-			static void DoWrite(JceOutputStream& self, std::size_t tag, T const& value)
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, std::uint8_t value)
 			{
 			{
-				
+				if (!value)
+				{
+					self.WriteHead({ tag, JceStruct::TypeEnum::ZeroTag });
+				}
+				else
+				{
+					self.WriteHead({ tag, JceStruct::TypeEnum::Byte });
+					self.m_Writer->WritePod(value);
+				}
+			}
+		};
+
+		template <>
+		struct Writer<std::int16_t>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, std::int16_t value)
+			{
+				if (Utility::InRangeOf<std::int8_t>(value))
+				{
+					self.Write(tag, static_cast<std::uint8_t>(value));
+				}
+				else
+				{
+					self.WriteHead({ tag, JceStruct::TypeEnum::Short });
+					self.m_Writer->WritePod(value);
+				}
+			}
+		};
+
+		template <>
+		struct Writer<std::int32_t>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, std::int32_t value)
+			{
+				if (Utility::InRangeOf<std::int16_t>(value))
+				{
+					self.Write(tag, static_cast<std::int16_t>(value));
+				}
+				else
+				{
+					self.WriteHead({ tag, JceStruct::TypeEnum::Int });
+					self.m_Writer->WritePod(value);
+				}
+			}
+		};
+
+		template <>
+		struct Writer<std::int64_t>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, std::int64_t value)
+			{
+				if (Utility::InRangeOf<std::int32_t>(value))
+				{
+					self.Write(tag, static_cast<std::int32_t>(value));
+				}
+				else
+				{
+					self.WriteHead({ tag, JceStruct::TypeEnum::Long });
+					self.m_Writer->WritePod(value);
+				}
+			}
+		};
+
+		template <>
+		struct Writer<float>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, float value)
+			{
+				self.WriteHead({ tag, JceStruct::TypeEnum::Float });
+				self.m_Writer->WritePod(value);
+			}
+		};
+
+		template <>
+		struct Writer<double>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, double value)
+			{
+				self.WriteHead({ tag, JceStruct::TypeEnum::Double });
+				self.m_Writer->WritePod(value);
+			}
+		};
+
+		template <>
+		struct Writer<std::string>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, std::string const& value)
+			{
+				const auto strSize = value.size();
+				if (strSize <= std::numeric_limits<std::uint8_t>::max())
+				{
+					self.WriteHead({ tag, JceStruct::TypeEnum::String1 });
+					self.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);
+					}
+
+					self.WriteHead({ tag, JceStruct::TypeEnum::String4 });
+					self.m_Writer->WritePod(static_cast<std::uint32_t>(strSize));
+				}
+				self.m_Writer->GetUnderlyingStream()->WriteBytes(reinterpret_cast<ncData>(value.data()), strSize);
+			}
+		};
+
+		template <typename Key, typename Value>
+		struct Writer<std::unordered_map<Key, Value>>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, std::unordered_map<Key, Value> const& value)
+			{
+				self.WriteHead({ tag, JceStruct::TypeEnum::Map });
+				self.Write(0, static_cast<std::int32_t>(value.size()));
+				for (const auto& item : value)
+				{
+					self.Write(0, item.first);
+					self.Write(1, item.second);
+				}
+			}
+		};
+
+		template <typename T>
+		struct Writer<std::vector<T>>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, std::vector<T> const& value)
+			{
+				self.WriteHead({ tag, JceStruct::TypeEnum::List });
+				self.Write(0, static_cast<std::int32_t>(value.size()));
+				for (const auto& item : value)
+				{
+					self.Write(0, item);
+				}
+			}
+		};
+
+		template <typename T>
+		struct Writer<NatsuLib::natRefPointer<T>, std::enable_if_t<std::is_base_of_v<JceStruct, T>>>
+		{
+			static void DoWrite(JceOutputStream& self, std::uint32_t tag, NatsuLib::natRefPointer<T> const& value)
+			{
+				self.WriteHead({ tag, JceStruct::TypeEnum::StructBegin });
+				TlvSerializer<T>::Serialize(self, value);
+				self.WriteHead({ 0, JceStruct::TypeEnum::StructEnd });
 			}
 			}
 		};
 		};
 	};
 	};
 
 
-	enum class Code
+	enum class JceCode
 	{
 	{
-#define TLV_CODE(name, code, ...) name = code,
+#define TLV_CODE(name, code) name = code,
 #include "TlvCodeDef.h"
 #include "TlvCodeDef.h"
 	};
 	};
 
 
@@ -742,7 +929,7 @@ namespace YumeBot::Jce
 			return tag;\
 			return tag;\
 		}
 		}
 
 
-#define TLV_CODE(name, code, ...) \
+#define TLV_CODE(name, code) \
 	class name\
 	class name\
 		: public NatsuLib::natRefObjImpl<name, JceStruct>\
 		: public NatsuLib::natRefObjImpl<name, JceStruct>\
 	{\
 	{\
@@ -750,14 +937,14 @@ namespace YumeBot::Jce
 		~name();\
 		~name();\
 		\
 		\
 		std::string_view GetJceStructName() const noexcept override;\
 		std::string_view GetJceStructName() const noexcept override;\
-		TypeEnum GetJceStructType() const noexcept override;\
-		\
-		__VA_ARGS__\
+		JceCode GetJceStructType() const noexcept override;
+
+#define END_TLV_CODE(name) \
 	};
 	};
 
 
 #include "TlvCodeDef.h"
 #include "TlvCodeDef.h"
 
 
-#define NO_OP nullptr
+#define NO_OP Detail::None
 
 
 #define IS_OPTIONAL(defaultValue) defaultValue
 #define IS_OPTIONAL(defaultValue) defaultValue
 
 
@@ -766,35 +953,37 @@ namespace YumeBot::Jce
 	{\
 	{\
 		using FieldType = typename Utility::MayRemoveTemplate<Utility::RemoveCvRef<decltype(ret->Get##name())>, std::optional>::Type;\
 		using FieldType = typename Utility::MayRemoveTemplate<Utility::RemoveCvRef<decltype(ret->Get##name())>, std::optional>::Type;\
 		if (!stream.Read(tag, ret->Get##name(),\
 		if (!stream.Read(tag, ret->Get##name(),\
-			Utility::ReturnFirst<Utility::ConcatTrait<Utility::BindTrait<std::is_same, std::nullptr_t>::template Result, std::negation>::template Result, std::nullptr_t>(__VA_ARGS__)))\
+			Utility::ReturnFirst<Utility::ConcatTrait<Utility::BindTrait<std::is_same, Detail::NoneType>::template Result, std::negation>::template Result, Detail::NoneType>(__VA_ARGS__)))\
 		{\
 		{\
 			nat_Throw(JceDecodeException, u8"Deserializing failed : Failed to read field \"" #name "\" which is not optional.");\
 			nat_Throw(JceDecodeException, u8"Deserializing failed : Failed to read field \"" #name "\" which is not optional.");\
 		}\
 		}\
 	}
 	}
 
 
-#define TLV_CODE(name, code, ...) \
+#define TLV_CODE(name, code) \
 	template <>\
 	template <>\
 	struct TlvDeserializer<name>\
 	struct TlvDeserializer<name>\
 	{\
 	{\
 		static NatsuLib::natRefPointer<name> Deserialize(JceInputStream& stream)\
 		static NatsuLib::natRefPointer<name> Deserialize(JceInputStream& stream)\
 		{\
 		{\
-			auto ret = NatsuLib::make_ref<name>();\
-			__VA_ARGS__\
+			auto ret = NatsuLib::make_ref<name>();
+
+#define END_TLV_CODE(name) \
 			return ret;\
 			return ret;\
 		}\
 		}\
 	};
 	};
 
 
 #include "TlvCodeDef.h"
 #include "TlvCodeDef.h"
 
 
-#define FIELD(name, tag, type, ...) stream->Write<JceStruct::TypeEnum::type>(tag, value->Get##name());
+#define FIELD(name, tag, type, ...) stream.Write(tag, value->Get##name());
 
 
-#define TLV_CODE(name, code, ...) \
+#define TLV_CODE(name, code) \
 	template <>\
 	template <>\
 	struct TlvSerializer<name>\
 	struct TlvSerializer<name>\
 	{\
 	{\
 		static void Serialize(JceOutputStream& stream, NatsuLib::natRefPointer<name> const& value)\
 		static void Serialize(JceOutputStream& stream, NatsuLib::natRefPointer<name> const& value)\
-		{\
-			__VA_ARGS__\
+		{
+
+#define END_TLV_CODE(name) \
 		}\
 		}\
 	};
 	};
 
 

+ 11 - 2
YumeBot/TlvCodeDef.h

@@ -1,5 +1,9 @@
 #ifndef TLV_CODE
 #ifndef TLV_CODE
-#define TLV_CODE(name, code, ...)
+#define TLV_CODE(name, code)
+#endif
+
+#ifndef END_TLV_CODE
+#define END_TLV_CODE(name)
 #endif
 #endif
 
 
 #ifndef NO_OP
 #ifndef NO_OP
@@ -70,7 +74,11 @@
 #define SIMPLE_LIST(name, tag, ...) FIELD(name, tag, SimpleList, __VA_ARGS__)
 #define SIMPLE_LIST(name, tag, ...) FIELD(name, tag, SimpleList, __VA_ARGS__)
 #endif
 #endif
 
 
-TLV_CODE(TlvTest, 0, INT(TestInt, 0), FLOAT(TestFloat, 1, IS_OPTIONAL(1.0f)), MAP(TestMap, 2, TEMPLATE_ARGUMENT(int, float)))
+TLV_CODE(TlvTest, 0)
+	INT(TestInt, 0)
+	FLOAT(TestFloat, 1, IS_OPTIONAL(1.0f))
+	MAP(TestMap, 2, TEMPLATE_ARGUMENT(int, float))
+END_TLV_CODE(TlvTest)
 
 
 #undef SIMPLE_LIST
 #undef SIMPLE_LIST
 #undef ZERO_TAG
 #undef ZERO_TAG
@@ -94,4 +102,5 @@ TLV_CODE(TlvTest, 0, INT(TestInt, 0), FLOAT(TestFloat, 1, IS_OPTIONAL(1.0f)), MA
 #undef NO_OP
 #undef NO_OP
 #undef ATTRIBUTE_SET
 #undef ATTRIBUTE_SET
 
 
+#undef END_TLV_CODE
 #undef TLV_CODE
 #undef TLV_CODE

+ 16 - 0
YumeBot/Utility.h

@@ -169,4 +169,20 @@ namespace YumeBot::Utility
 		: ResultType<T>
 		: 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)
+	{
+		using TType = RemoveCvRef<T>;
+		using UType = RemoveCvRef<U>;
+
+		if constexpr (sizeof(TType) >= sizeof(UType))
+		{
+			return true;
+		}
+		else
+		{
+			return value >= static_cast<UType>(std::numeric_limits<TType>::min()) && value <= static_cast<UType>(std::numeric_limits<TType>::max());
+		}
+	}
 }
 }

+ 13 - 8
YumeBot/YumeBot.vcxproj

@@ -30,6 +30,11 @@
     <ClCompile Include="Jce.cpp" />
     <ClCompile Include="Jce.cpp" />
     <ClCompile Include="YumeBot.cpp" />
     <ClCompile Include="YumeBot.cpp" />
   </ItemGroup>
   </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Extern\NatsuLib\NatsuLib\NatsuLib.vcxproj">
+      <Project>{e4f66019-9964-4b86-b538-e10f66124689}</Project>
+    </ProjectReference>
+  </ItemGroup>
   <PropertyGroup Label="Globals">
   <PropertyGroup Label="Globals">
     <VCProjectVersion>15.0</VCProjectVersion>
     <VCProjectVersion>15.0</VCProjectVersion>
     <ProjectGuid>{EC6FB333-F85F-4AD3-8792-33E1C62734BA}</ProjectGuid>
     <ProjectGuid>{EC6FB333-F85F-4AD3-8792-33E1C62734BA}</ProjectGuid>
@@ -84,23 +89,23 @@
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Label="UserMacros" />
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <LinkIncremental>true</LinkIncremental>
     <LinkIncremental>true</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
     <LinkIncremental>true</LinkIncremental>
     <LinkIncremental>true</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
     <LinkIncremental>false</LinkIncremental>
     <LinkIncremental>false</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
     <LinkIncremental>false</LinkIncremental>
     <LinkIncremental>false</LinkIncremental>
-    <OutDir>bin/$(Platform)/$(Configuration)</OutDir>
-    <IntDir>obj/$(Platform)/$(Configuration)</IntDir>
+    <OutDir>bin/$(Platform)/$(Configuration)/</OutDir>
+    <IntDir>obj/$(Platform)/$(Configuration)/</IntDir>
   </PropertyGroup>
   </PropertyGroup>
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
   <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
     <ClCompile>
     <ClCompile>