#include <std_include.hpp>
#include "loader/component_loader.hpp"

#include "auth.hpp"

#include <game/game.hpp>

#include <utils/nt.hpp>
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/smbios.hpp>
#include <utils/cryptography.hpp>

namespace auth
{
	namespace
	{
		std::string get_hdd_serial()
		{
			DWORD serial{};
			if (!GetVolumeInformationA("C:\\", nullptr, 0, &serial, nullptr, nullptr, nullptr, 0))
			{
				return {};
			}

			return utils::string::va("%08X", serial);
		}

		std::string get_hw_profile_guid()
		{
			HW_PROFILE_INFO info;
			if (!GetCurrentHwProfileA(&info))
			{
				return {};
			}

			return std::string{info.szHwProfileGuid, sizeof(info.szHwProfileGuid)};
		}

		std::string get_protected_data()
		{
			std::string input = "momo5502-boiii-auth";

			DATA_BLOB data_in{}, data_out{};
			data_in.pbData = reinterpret_cast<uint8_t*>(input.data());
			data_in.cbData = static_cast<DWORD>(input.size());
			if (CryptProtectData(&data_in, nullptr, nullptr, nullptr, nullptr, CRYPTPROTECT_LOCAL_MACHINE,
			                     &data_out) != TRUE)
			{
				return {};
			}

			const auto size = std::min(data_out.cbData, 52ul);
			std::string result{reinterpret_cast<char*>(data_out.pbData), size};
			LocalFree(data_out.pbData);

			return result;
		}

		std::string get_key_entropy()
		{
			std::string entropy{};
			entropy.append(utils::smbios::get_uuid());
			entropy.append(get_hw_profile_guid());
			entropy.append(get_protected_data());
			entropy.append(get_hdd_serial());

			if (entropy.empty())
			{
				entropy.resize(32);
				utils::cryptography::random::get_data(entropy.data(), entropy.size());
			}

			return entropy;
		}

		utils::cryptography::ecc::key& get_key()
		{
			static auto key = utils::cryptography::ecc::generate_key(512, get_key_entropy());
			return key;
		}

		bool is_second_instance()
		{
			static const auto is_first = []
			{
				static utils::nt::handle<> mutex = CreateMutexA(nullptr, FALSE, "boiii_mutex");
				return mutex && GetLastError() != ERROR_ALREADY_EXISTS;
			}();

			return !is_first;
		}
	}

	uint64_t get_guid()
	{
		static const auto guid = []() -> uint64_t
		{
			if (game::is_server() || is_second_instance())
			{
				return 0x110000100000000 | (::utils::cryptography::random::get_integer() & ~0x80000000);
			}

			return get_key().get_hash();
		}();

		return guid;
	}

	struct component final : client_component
	{
		void post_unpack() override
		{
			// Patch steam id bit check
			std::vector<std::pair<size_t, size_t>> patches{};
			const auto p = [&patches](const size_t a, const size_t b)
			{
				patches.emplace_back(a, b);
			};

			if (game::is_server())
			{
				p(0x1404747C6_g, 0x140474806_g);
				p(0x140474A24_g, 0x140474A68_g);
				p(0x140474A85_g, 0x140474AC6_g);
				p(0x140457ED0_g, 0x140457F26_g);
				p(0x140473DD8_g, 0x140473E19_g);
				p(0x1404743D5_g, 0x140474423_g);
				p(0x1404744FD_g, 0x140474553_g);
				p(0x14047462D_g, 0x140474677_g);
				p(0x140475057_g, 0x14047509F_g); // ?
				p(0x140475672_g, 0x1404756B5_g);
				p(0x140477322_g, 0x140477365_g); // ?
			}
			else
			{
				p(0x141E19CED_g, 0x141E19D3B_g);
				p(0x141EB2C76_g, 0x141EB2CB6_g);
				p(0x141EB2DAD_g, 0x141EB2DF2_g);
				p(0x141EB3C35_g, 0x141EB3C76_g);
				p(0x141E19AD0_g, 0x141E19B26_g);
				//
				p(0x141EB0EE8_g, 0x141EB0F29_g);
				p(0x141EB0FA8_g, 0x141EB0FE9_g);
				p(0x141EB2525_g, 0x141EB2573_g);
				p(0x141EB264D_g, 0x141EB26A3_g);
				p(0x141EB277D_g, 0x141EB27C7_g);

				p(0x141EB2AEA_g, 0x141EB2AFA_g);
				p(0x141EB2B01_g, 0x141EB2B33_g);

				p(0x141EB3137_g, 0x141EB3147_g);
				p(0x141EB314E_g, 0x141EB317F_g);

				p(0x141EB5377_g, 0x141EB53BF_g); // ?
				p(0x141EB5992_g, 0x141EB59D5_g);
				p(0x141EB74D2_g, 0x141EB7515_g); // ?
			}

			for (const auto& patch : patches)
			{
				utils::hook::jump(patch.first, patch.second);
			}
		}
	};
}

REGISTER_COMPONENT(auth::component)
