#include "ExHeaderProcess.h"
#include <tc/io.h>
#include <tc/cli.h>
#include <tc/crypto.h>
#include <tc/ArgumentNullException.h>

ctrtool::ExHeaderProcess::ExHeaderProcess() :
	mModuleLabel("ctrtool::ExHeaderProcess"),
	mInputStream(),
	mKeyBag(),
	mShowInfo(false),
	mVerbose(false),
	mVerify(false),
	mShowSyscallNames(false),
	mValidSignature(ValidState::Unchecked),
	mValidLocalCaps()
{
	memset((byte_t*)&mHeader, 0, sizeof(ntd::n3ds::ExtendedHeader));
	memset((byte_t*)&mDesc, 0, sizeof(ntd::n3ds::AccessDescriptor));
}

void ctrtool::ExHeaderProcess::setInputStream(const std::shared_ptr<tc::io::IStream>& input_stream)
{
	mInputStream = input_stream;
}

void ctrtool::ExHeaderProcess::setKeyBag(const ctrtool::KeyBag& key_bag)
{
	mKeyBag = key_bag;
}

void ctrtool::ExHeaderProcess::setCliOutputMode(bool show_info)
{
	mShowInfo = show_info;
}

void ctrtool::ExHeaderProcess::setVerboseMode(bool verbose)
{
	mVerbose = verbose;
}

void ctrtool::ExHeaderProcess::setVerifyMode(bool verify)
{
	mVerify = verify;
}

void ctrtool::ExHeaderProcess::setShowSyscallName(bool show_name)
{
	mShowSyscallNames = show_name;
}

void ctrtool::ExHeaderProcess::process()
{
	// begin processing
	importExHeader();
	if (mVerify)
		verifyExHeader();
	if (mShowInfo)
		printExHeader();
}

void ctrtool::ExHeaderProcess::importExHeader()
{
	// validate input stream
	if (mInputStream == nullptr)
	{
		throw tc::ArgumentNullException(mModuleLabel, "Input stream was null.");
	}
	if (mInputStream->canRead() == false || mInputStream->canSeek() == false)
	{
		throw tc::InvalidOperationException(mModuleLabel, "Input stream requires read/seek permissions.");
	}	

	// import header
	if (mInputStream->length() < (sizeof(ntd::n3ds::ExtendedHeader) + sizeof(ntd::n3ds::AccessDescriptor)))
	{
		throw tc::InvalidOperationException(mModuleLabel, "Input stream too small.");
	}
	mInputStream->seek(0, tc::io::SeekOrigin::Begin);
	mInputStream->read((byte_t*)&mHeader, sizeof(ntd::n3ds::ExtendedHeader));
	mInputStream->read((byte_t*)&mDesc, sizeof(ntd::n3ds::AccessDescriptor));

}

void ctrtool::ExHeaderProcess::verifyExHeader()
{
	std::array<byte_t, tc::crypto::Sha256Generator::kHashSize> desc_hash;
	
	tc::crypto::GenerateSha256Hash(desc_hash.data(), (byte_t*)&mDesc.ncch_rsa_modulus, sizeof(mDesc) - sizeof(mDesc.signature));

	if (mKeyBag.rsa_key.find(mKeyBag.RSAKEY_ACCESSDESC) != mKeyBag.rsa_key.end())
	{
		tc::crypto::RsaKey pubkey = mKeyBag.rsa_key[mKeyBag.RSAKEY_ACCESSDESC];

		mValidSignature = tc::crypto::VerifyRsa2048Pkcs1Sha256(mDesc.signature.data(), desc_hash.data(), pubkey) ? ValidState::Good : ValidState::Fail;
	}
	else
	{
		fmt::print(stderr, "Could not read AccessDescriptor public key.\n");
		mValidSignature = ValidState::Fail;
	}
	
	mValidLocalCaps.system_save_id[0] = ValidState::Good;
	mValidLocalCaps.system_save_id[1] = ValidState::Good;
	mValidLocalCaps.access_info = ValidState::Good;
	mValidLocalCaps.core_version = ValidState::Good;
	mValidLocalCaps.program_id = ValidState::Good;
	mValidLocalCaps.priority = ValidState::Good;
	mValidLocalCaps.affinity_mask = ValidState::Good;
	mValidLocalCaps.ideal_processor = ValidState::Good;
	mValidLocalCaps.old3ds_system_mode = ValidState::Good;
	mValidLocalCaps.new3ds_system_mode = ValidState::Good;
	mValidLocalCaps.enable_l2_cache = ValidState::Good;
	mValidLocalCaps.new3ds_cpu_speed = ValidState::Good;
	mValidLocalCaps.service_control = ValidState::Good;

	byte_t* exhdr_program_id = (byte_t*)&mHeader.access_control_info.program_id;
	byte_t* desc_program_id = (byte_t*)&mDesc.access_control_info.program_id;
	for (size_t i = 0; i < sizeof(uint64_t); i++)
	{
		if (exhdr_program_id[i] == desc_program_id[i] || desc_program_id[i] == 0xFF)
			continue;

		mValidLocalCaps.program_id = ValidState::Fail;
		break;
	}

	/*
	// this does not appear to be correct given working examples: SystemUpdater exhdr: 0x1, desc: 0x2
	auto exhdr_core_version = mHeader.access_control_info.core_version.unwrap();
	auto desc_core_version = mDesc.access_control_info.core_version.unwrap();
	if (exhdr_core_version != desc_core_version)
		mValidLocalCaps.core_version = ValidState::Fail;
	*/

	auto exhdr_thread_priority = mHeader.access_control_info.flags.unwrap().thread_priority;
	auto desc_thread_priority = mDesc.access_control_info.flags.unwrap().thread_priority;
	if (exhdr_thread_priority < desc_thread_priority)
		mValidLocalCaps.priority = ValidState::Fail;

	auto exhdr_ideal_processor = mHeader.access_control_info.flags.unwrap().ideal_processor;
	auto desc_ideal_processor = mDesc.access_control_info.flags.unwrap().ideal_processor;
	if((1<<exhdr_ideal_processor & desc_ideal_processor) == 0)
		mValidLocalCaps.ideal_processor = ValidState::Fail;
	
	auto exhdr_affinity_mask = mHeader.access_control_info.flags.unwrap().affinity_mask;
	auto desc_affinity_mask = mDesc.access_control_info.flags.unwrap().affinity_mask;
	if (exhdr_affinity_mask & ~desc_affinity_mask)
		mValidLocalCaps.affinity_mask = ValidState::Fail;

	auto exhdr_system_mode = mHeader.access_control_info.flags.unwrap().system_mode;
	auto desc_system_mode = mDesc.access_control_info.flags.unwrap().system_mode;
	if (exhdr_system_mode > desc_system_mode)
		mValidLocalCaps.old3ds_system_mode = ValidState::Fail;

	auto exhdr_system_mode_ext = mHeader.access_control_info.flags.unwrap().system_mode_ext;
	auto desc_system_mode_ext = mDesc.access_control_info.flags.unwrap().system_mode_ext;
	if (exhdr_system_mode_ext > desc_system_mode_ext)
		mValidLocalCaps.new3ds_system_mode = ValidState::Fail;

	auto exhdr_enable_l2_cache = mHeader.access_control_info.flags.unwrap().enable_l2_cache;
	auto desc_enable_l2_cache = mDesc.access_control_info.flags.unwrap().enable_l2_cache;
	if (exhdr_enable_l2_cache != desc_enable_l2_cache)
		mValidLocalCaps.enable_l2_cache = ValidState::Fail;

	auto exhdr_cpu_speed = mHeader.access_control_info.flags.unwrap().cpu_speed;
	auto desc_cpu_speed = mDesc.access_control_info.flags.unwrap().cpu_speed;
	if (exhdr_cpu_speed != desc_cpu_speed)
		mValidLocalCaps.new3ds_cpu_speed = ValidState::Fail;


	// Storage Info Verify
	auto exhdr_system_savedata_id = mHeader.access_control_info.system_savedata_id;
	auto desc_system_savedata_id = mDesc.access_control_info.system_savedata_id;
	if(exhdr_system_savedata_id[0].unwrap() & ~desc_system_savedata_id[0].unwrap())
		mValidLocalCaps.system_save_id[0] = ValidState::Fail;
	if(exhdr_system_savedata_id[1].unwrap() & ~desc_system_savedata_id[1].unwrap())
		mValidLocalCaps.system_save_id[1] = ValidState::Fail;

	auto exhdr_fs_access = mHeader.access_control_info.fs_access;
	auto desc_fs_access = mDesc.access_control_info.fs_access;
	for (size_t fs_bit = 0; fs_bit < exhdr_fs_access.bit_size(); fs_bit++)
	{
		if (exhdr_fs_access.test(fs_bit) == true && desc_fs_access.test(fs_bit) == false)
		{
			mValidLocalCaps.access_info = ValidState::Fail;
			if (mVerbose)
			{
				fmt::print("[LOG/ExHeader] FsAccess Bit {:d} was not permitted\n", fs_bit);
			}
		}
	}

	// Service Access Control
	auto exhdr_service_access_control = mHeader.access_control_info.service_access_control;
	auto desc_service_access_control = mDesc.access_control_info.service_access_control;
	bool found_service;
	for (size_t i = 0, j; i < exhdr_service_access_control.size(); i++) {
		// skip if empty string
		if (exhdr_service_access_control[i].decode().empty())
			break;

		found_service = false;

		// locate entry in desc
		for (j = 0; j < desc_service_access_control.size(); j++) {
			if (exhdr_service_access_control[i].decode() == desc_service_access_control[j].decode())
				found_service = true;
		}

		if (found_service == false)
		{
			mValidLocalCaps.service_control = Fail;
			if (mVerbose)
			{
				fmt::print("[LOG/ExHeader] Service \"{}\" was not permitted\n", exhdr_service_access_control[i].decode());
			}
		}
	}
}

void ctrtool::ExHeaderProcess::printExHeader()
{
	fmt::print("\n");
	fmt::print("Extended header:\n");
	fmt::print("Signature: {:6}       {}", getValidString(mValidSignature), tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mDesc.signature.data(), mDesc.signature.size(), true, "", 0x20, 24, false));
	fmt::print("NCCH Hdr RSA Modulus:   {}", tc::cli::FormatUtil::formatBytesAsStringWithLineLimit(mDesc.ncch_rsa_modulus.data(), mDesc.ncch_rsa_modulus.size(), true, "", 0x20, 24, false));
	printSystemControlInfo(mHeader.system_control_info);
	printARM11SystemLocalCapabilities(mHeader.access_control_info, mValidLocalCaps);
	printARM11KernelCapabilities(mHeader.access_control_info);
	printARM9AccessControlInfo(mHeader.access_control_info);
}

void ctrtool::ExHeaderProcess::printSystemControlInfo(const ntd::n3ds::SystemControlInfo& info)
{
	//fmt::print("[SystemControlInfo]\n");
	// basic info
	fmt::print("Name:                   {}\n", info.name.decode());
	fmt::print("Flags:                  {:02X}", (uint32_t)info.flags.raw);
	if (info.flags.bitarray.test(info.Flags_CompressExefsPartition0))
		fmt::print(" [compressed]");
	if (info.flags.bitarray.test(info.Flags_SdmcApplication))
		fmt::print(" [sd app]");
	fmt::print("\n");
	fmt::print("Remaster version:       {:04x}\n", (uint32_t)info.remaster_version.unwrap());

	// code set info
	fmt::print("Code text address:      0x{:08X}\n", info.text.address.unwrap());
	fmt::print("Code text size:         0x{:08X}\n", info.text.code_size.unwrap());
	fmt::print("Code text max pages:    0x{:08X} (0x{:08X})\n", info.text.num_max_pages.unwrap(), info.text.num_max_pages.unwrap() * 0x1000);

	fmt::print("Code ro address:        0x{:08X}\n", info.rodata.address.unwrap());
	fmt::print("Code ro size:           0x{:08X}\n", info.rodata.code_size.unwrap());
	fmt::print("Code ro max pages:      0x{:08X} (0x{:08X})\n", info.rodata.num_max_pages.unwrap(), info.rodata.num_max_pages.unwrap() * 0x1000);

	fmt::print("Code data address:      0x{:08X}\n", info.data.address.unwrap());
	fmt::print("Code data size:         0x{:08X}\n", info.data.code_size.unwrap());
	fmt::print("Code data max pages:    0x{:08X} (0x{:08X})\n", info.data.num_max_pages.unwrap(), info.data.num_max_pages.unwrap() * 0x1000);

	fmt::print("Code bss size:          0x{:08X}\n", info.bss_size.unwrap());
	fmt::print("Code stack size:        0x{:08X}\n", info.stack_size.unwrap());

	// Dependency
	for (size_t i = 0; i < info.dependency_list.size(); i++)
	{
		if (info.dependency_list[i].unwrap() != 0)
		{
			fmt::print("Dependency:             {:016x}\n", info.dependency_list[i].unwrap());
		}
	}

	// savedata size
	fmt::print("Savedata size:          ");
	uint64_t savedata_size = info.savedata_size.unwrap();
	if (savedata_size < (1024)) // < KB
	{
		fmt::print("0x{:x}", savedata_size);
	}
	else if (savedata_size < (1024 * 1024)) // < MB
	{
		fmt::print("{:d}K", (savedata_size >> 10));
	}
	else
	{
		fmt::print("{:d}M", (savedata_size >> 20));
	}
	fmt::print("\n");

	fmt::print("Jump id:                {:016x}\n", info.jump_id.unwrap());
}

void ctrtool::ExHeaderProcess::printARM11SystemLocalCapabilities(const ntd::n3ds::AccessControlInfo& info, const ValidARM11SystemLocalCapabilities& valid)
{
	//fmt::print("[ARM11SystemLocalCapabilities]\n");
	fmt::print("Program id:             {:016x} {}\n", info.program_id.unwrap(), getValidString(valid.program_id));
	fmt::print("Core version:           0x{:08x}\n", info.core_version.unwrap());
	fmt::print("System mode:            {} (AppMemory: {}) {}\n", getSystemModeString(info.flags.unwrap().system_mode), getSystemModeAppMemorySizeString(info.flags.unwrap().system_mode), getValidString(valid.old3ds_system_mode));
	fmt::print("System mode (New3DS):   {} (AppMemory: {}) {}\n", getSystemModeExtString(info.flags.unwrap().system_mode_ext, info.flags.unwrap().system_mode), getSystemModeExtAppMemorySizeString(info.flags.unwrap().system_mode_ext, info.flags.unwrap().system_mode), getValidString(valid.new3ds_system_mode));
	fmt::print("CPU Speed (New3DS):     {} {}\n", (info.flags.unwrap().cpu_speed ? "804MHz" : "268MHz"), getValidString(valid.new3ds_cpu_speed));
	fmt::print("Enable L2 Cache:        {} {}\n", (info.flags.unwrap().enable_l2_cache ? "YES" : "NO"), getValidString(valid.enable_l2_cache));
	fmt::print("Ideal processor:        {:d} {}\n", (uint32_t)info.flags.unwrap().ideal_processor, getValidString(valid.ideal_processor));
	fmt::print("Affinity mask:          {:d} {}\n", (uint32_t)info.flags.unwrap().affinity_mask, getValidString(valid.affinity_mask));
	fmt::print("Main thread priority:   {:d} {}\n", (uint32_t)info.flags.unwrap().thread_priority, getValidString(valid.priority));
	fmt::print("MaxCpu:                 {:d}\n", info.resource_limit_descriptor[info.ResourceLimitDescriptorIndex_MaxCpu].unwrap());

	std::vector<uint32_t> accessible_save_ids;
	uint64_t ext_savedata_id = 0;
	std::array<uint32_t, 3> other_user_save_ids = {0, 0, 0};
	bool use_other_variation_savedata = false;
	if (info.other_attributes.test(info.OtherAttribute_UseExtendedSavedataAccessControl))
	{
		uint32_t id;

		if (0 != (id = info.accessible_unique_ids_0.unwrap().save_id0))
			accessible_save_ids.push_back(id);
		
		if (0 != (id = info.accessible_unique_ids_0.unwrap().save_id1))
			accessible_save_ids.push_back(id);
		
		if (0 != (id = info.accessible_unique_ids_0.unwrap().save_id2))
			accessible_save_ids.push_back(id);
		
		if (0 != (id = info.accessible_unique_ids_1.unwrap().save_id0))
			accessible_save_ids.push_back(id);
		
		if (0 != (id = info.accessible_unique_ids_1.unwrap().save_id1))
			accessible_save_ids.push_back(id);

		if (0 != (id = info.accessible_unique_ids_1.unwrap().save_id2))
			accessible_save_ids.push_back(id);
	}
	else
	{
		ext_savedata_id = info.ext_savedata_id.unwrap();

		other_user_save_ids[0] = info.accessible_unique_ids_0.unwrap().save_id0;
		other_user_save_ids[1] = info.accessible_unique_ids_0.unwrap().save_id1;
		other_user_save_ids[2] = info.accessible_unique_ids_0.unwrap().save_id2;
	}
	use_other_variation_savedata = info.accessible_unique_ids_0.unwrap().flag;

	fmt::print("Ext savedata id:        0x{:016x}\n", ext_savedata_id);
	for (size_t i = 0; i < info.system_savedata_id.size(); i++)
	{
		fmt::print("System savedata id {:d}:   0x{:08x} {}\n", i+1, info.system_savedata_id[i].unwrap(), getValidString(valid.system_save_id[i]));
	}
	for (size_t i = 0; i < other_user_save_ids.size(); i++)
	{
		fmt::print("OtherUserSaveDataId{:d}:   0x{:05x}\n", i+1, other_user_save_ids[i]);
	}
	fmt::print("Accessible Savedata Ids: {}\n", (accessible_save_ids.size() == 0 ? "None" : ""));
	for (size_t i = 0; i < accessible_save_ids.size(); i++)
	{
		fmt::print(" > 0x{:05x}\n", accessible_save_ids[i]);
	}
	fmt::print("Other Variation Saves:  {}\n", (use_other_variation_savedata ? "Accessible" : "Inaccessible"));
	uint64_t fs_access_raw = ((((tc::bn::le64<uint64_t>*)&info.fs_access)->unwrap() << 8) >> 8); // clearing the upper 8 bits since fs_access is 56 bits
	fmt::print("Access info: {:6}     0x{:014x}\n", getValidString(valid.access_info), fs_access_raw);
	for (size_t i = 0; i < info.fs_access.bit_size(); i++)
	{
		if (info.fs_access.test(i))
		{
			fmt::print(" > {}\n", getFsAccessBitString(i));
		}
	}

	fmt::print("Service access: {}\n", getValidString(mValidLocalCaps.service_control));
	auto& service_access = info.service_access_control;
	for (size_t i = 0; i < service_access.size(); i++)
	{
		if (service_access[i].decode().empty())
			break;

		fmt::print(" > {}\n", service_access[i].decode());
	}
	fmt::print("Reslimit category:      {:02X}\n", (uint32_t)info.resource_limit_category);
}

void ctrtool::ExHeaderProcess::printARM11KernelCapabilities(const ntd::n3ds::AccessControlInfo& info)
{
	size_t i, j;
	std::vector<byte_t> syscall_list;
	std::vector<byte_t> interrupt_list;
	std::vector<uint32_t> unknown_desc_list;

	union KernDesc
	{
		uint32_t raw;
		tc::bn::bitarray<4> bits;
		ntd::n3ds::AccessControlInfo::InterruptDescriptor interrupt;
		ntd::n3ds::AccessControlInfo::SystemCallDescriptor syscall;
		ntd::n3ds::AccessControlInfo::ReleaseKernelVersionDescriptor kernel_ver;
		ntd::n3ds::AccessControlInfo::HandleTableSizeDescriptor handle_table;
		ntd::n3ds::AccessControlInfo::OtherCapabilitiesDescriptor other_cap;
		ntd::n3ds::AccessControlInfo::MappingStaticDescriptor mapping_static;
		ntd::n3ds::AccessControlInfo::MappingIODescriptor mapping_io;
	};

	KernDesc prev_desc;
	prev_desc.raw = 0;

	for (i = 0; i < info.kernel_descriptors.size(); i++)
	{
		KernDesc desc;
		desc.raw = info.kernel_descriptors[i].unwrap();
		
		uint32_t prefix_bits;
		for (prefix_bits = 0; prefix_bits < 32; prefix_bits++)
		{
			if (desc.bits.test(31 - prefix_bits) == false)
			{
				break;
			}
		}

		if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_InterruptNumList)
		{
			if (desc.interrupt.interrupt_0 != 0)
				interrupt_list.push_back(desc.interrupt.interrupt_0);
			if (desc.interrupt.interrupt_1 != 0)
				interrupt_list.push_back(desc.interrupt.interrupt_1);
			if (desc.interrupt.interrupt_2 != 0)
				interrupt_list.push_back(desc.interrupt.interrupt_2);
			if (desc.interrupt.interrupt_3 != 0)
				interrupt_list.push_back(desc.interrupt.interrupt_3);
		}
		else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_SysCallList)
		{
			for (j = 0; j < 24; j++)
			{
				if ((desc.syscall.systemcall_lower_bitarray >> j) & 1)
					syscall_list.push_back(byte_t(desc.syscall.systemcall_upper * 24) + byte_t(j));
			}
		}
		else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_ReleaseKernelVersion)
		{
			fmt::print("Kernel release version: {:d}.{:d}\n", ((desc.kernel_ver.version >> 8) & 0xFF), ((desc.kernel_ver.version >> 0) & 0xFF));
		}
		else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_HandleTableSize)
		{
			fmt::print("Handle table size:      0x{:X}\n", (uint32_t)desc.handle_table.size);
		}
		else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_OtherCapabilities)
		{
			fmt::print("Kernel flags:           \n");
			fmt::print(" > Allow debug:         {}\n", (desc.other_cap.permit_debug ? "YES" : "NO"));
			fmt::print(" > Force debug:         {}\n", (desc.other_cap.force_debug ? "YES" : "NO"));
			fmt::print(" > Allow non-alphanum:  {}\n", (desc.other_cap.can_use_non_alphabet_and_number ? "YES" : "NO"));
			fmt::print(" > Shared page writing: {}\n", (desc.other_cap.can_write_shared_page ? "YES" : "NO"));
			fmt::print(" > Privilege priority:  {}\n", (desc.other_cap.can_use_privileged_priority ? "YES" : "NO"));
			fmt::print(" > Allow main() args:   {}\n", (desc.other_cap.permit_main_function_argument ? "YES" : "NO"));
			fmt::print(" > Shared device mem:   {}\n", (desc.other_cap.can_share_device_memory ? "YES" : "NO"));
			fmt::print(" > Memory Type:         {}\n", getMemoryTypeString(desc.other_cap.memory_type));
			fmt::print(" > Runnable on sleep:   {}\n", (desc.other_cap.runnable_on_sleep ? "YES" : "NO"));
			fmt::print(" > Special memory:      {}\n", (desc.other_cap.special_memory_layout ? "YES" : "NO"));
			fmt::print(" > Access Core 2:       {}\n", (desc.other_cap.can_access_core2 ? "YES" : "NO"));
		}
		else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_MappingStatic)
		{
			if (prev_desc.raw == 0)
			{
				prev_desc.raw = desc.raw;
			}
			else
			{
				fmt::print("{:24}0x{:X}-0x{:X}{}\n", 
					(desc.mapping_static.flag ? "StaticMapping:" : "IoMapping:"),
					(prev_desc.mapping_static.page << 12),
					(desc.mapping_static.page << 12)-1,
					(prev_desc.mapping_static.flag ? ":r" : "")
				);
				prev_desc.raw = 0;
			}
		}
		else if (prefix_bits == ntd::n3ds::AccessControlInfo::DescriptorPrefix_MappingIo)
		{
			fmt::print("IoMapping:              0x{:X}\n", (desc.mapping_io.page << 12));
		}
		else if (prefix_bits == 32)
		{
			continue;
		}
		else
		{
			unknown_desc_list.push_back(desc.raw);
		}
	}

	fmt::print("Allowed systemcalls:    ");
	if (syscall_list.size() > 0)
	{
		if (!mShowSyscallNames)
		{
			std::vector<std::string> string_list;
			for (size_t i = 0; i < syscall_list.size(); i++)
			{
				string_list.push_back(getByteHexString(syscall_list[i]));
			}

			fmt::print("{}", tc::cli::FormatUtil::formatListWithLineLimit(string_list, 46, 24, false));
		}
		else
		{
			fmt::print("\n");
			for (size_t i = 0; i < syscall_list.size(); i++)
			{
				fmt::print(" > {} {}\n", getByteHexString(syscall_list[i]), getSysCallName(syscall_list[i]));
			}
		}
		
	}
	else
	{
		fmt::print("none\n");
	}

	fmt::print("Allowed interrupts:     ");
	if (interrupt_list.size() > 0)
	{
		std::vector<std::string> string_list;
		for (size_t i = 0; i < interrupt_list.size(); i++)
		{
			string_list.push_back(getByteHexString(interrupt_list[i]));
		}

		fmt::print("{}", tc::cli::FormatUtil::formatListWithLineLimit(string_list, 46, 24, false));
	}
	else
	{
		fmt::print("none\n");
	}

	for (i = 0; i < unknown_desc_list.size(); i++)
	{
		fmt::print("Unknown descriptor:     {:08X}\n", unknown_desc_list[i]);
	}
}

void ctrtool::ExHeaderProcess::printARM9AccessControlInfo(const ntd::n3ds::AccessControlInfo& info)
{
	//fmt::print("[ARM9AccessControlInfo]\n");
	
	// collect arm9 caps as a vector of strings
	std::vector<std::string> arm9_caps_str;
	for (size_t i = 0; i < info.arm9_access_control.bit_size(); i++)
	{
		if (info.arm9_access_control.test(i))
		{
			arm9_caps_str.push_back(getArm9CapabilityBitString(i));
		}
	}
	
	// print arm9 caps
	fmt::print("Arm9Capability:         {}\n", (arm9_caps_str.size() == 0 ? "none" : ""));
	for (size_t i = 0; i < arm9_caps_str.size(); i++)
	{
		fmt::print(" > {}\n", arm9_caps_str[i]);
	}
	
	// print desc version
	fmt::print("Desc Version:           0x{:x}\n", (uint32_t)info.desc_version);
}

std::string ctrtool::ExHeaderProcess::getValidString(byte_t validstate)
{
	std::string ret_str;

	switch (validstate)
	{
		case ValidState::Unchecked:
			ret_str =  "";
			break;
		case ValidState::Good:
			ret_str =  "(GOOD)";
			break;
		case ValidState::Fail:
		default:
			ret_str =  "(FAIL)";
			break;
	}

	return ret_str;
}

std::string ctrtool::ExHeaderProcess::getSystemModeString(byte_t system_mode)
{
	std::string str;

	switch (system_mode)
	{
		case ntd::n3ds::AccessControlInfo::SystemMode_PROD :
			str = "prod";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV1 :
			str = "dev1";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV2 :
			str = "dev2";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV3 :
			str = "dev3";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV4 :
			str = "dev4";
			break;
		default:
			str = fmt::format("Unknown (0x{:x})", (uint32_t)system_mode);
			break;
	}

	return str;
}

std::string ctrtool::ExHeaderProcess::getSystemModeExtString(byte_t system_mode_ext, byte_t system_mode)
{
	std::string str;

	switch (system_mode_ext)
	{
		case ntd::n3ds::AccessControlInfo::SystemModeExt_LEGACY :
			//str = "Legacy";
			str = fmt::format("ctr {}", getSystemModeString(system_mode));
			break;
		case ntd::n3ds::AccessControlInfo::SystemModeExt_PROD :
			str = "snake prod";
			break;
		case ntd::n3ds::AccessControlInfo::SystemModeExt_DEV1 :
			str = "snake dev1";
			break;
		default:
			str = fmt::format("Unknown (0x{:x})", (uint32_t)system_mode_ext);
			break;
	}

	return str;
}

std::string ctrtool::ExHeaderProcess::getSystemModeAppMemorySizeString(byte_t system_mode)
{
	std::string str;

	switch (system_mode)
	{
		case ntd::n3ds::AccessControlInfo::SystemMode_PROD :
			str = "64MB";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV1 :
			str = "96MB";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV2 :
			str = "80MB";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV3 :
			str = "72MB";
			break;
		case ntd::n3ds::AccessControlInfo::SystemMode_DEV4 :
			str = "32MB";
			break;
		default:
			str = "Unknown";
			break;
	}

	return str;
}

std::string ctrtool::ExHeaderProcess::getSystemModeExtAppMemorySizeString(byte_t system_mode_ext, byte_t system_mode)
{
	std::string str;

	switch (system_mode_ext)
	{
		case ntd::n3ds::AccessControlInfo::SystemModeExt_LEGACY :
			str = getSystemModeAppMemorySizeString(system_mode);
			break;
		case ntd::n3ds::AccessControlInfo::SystemModeExt_PROD :
			str = "124MB";
			break;
		case ntd::n3ds::AccessControlInfo::SystemModeExt_DEV1 :
			str = "178MB";
			break;
		default:
			str = "Unknown";
			break;
	}

	return str;
}

std::string ctrtool::ExHeaderProcess::getFsAccessBitString(size_t bit)
{
	std::string str;

	switch(bit)
	{
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategorySystemApplication : 
			str = "Category System Application";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategoryHardwareCheck : 
			str = "Category Hardware Check";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategoryFileSystemTool : 
			str = "Category File System Tool";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_Debug : 
			str = "Debug";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_TwlCardBackup : 
			str = "TWL Card Backup";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_TwlNandData : 
			str = "TWL Nand Data";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_Boss : 
			str = "BOSS";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_DirectSdmc : 
			str = "Direct SDMC";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_Core : 
			str = "Core";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CtrNandRo : 
			str = "CTR NAND RO";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CtrNandRw : 
			str = "CTR NAND RW";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CtrNandRoWrite : 
			str = "CTR NAND RO (Write Access)";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategorySystemSettings : 
			str = "Category System Settings";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CardBoard : 
			str = "CARD BOARD";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_ExportImportIvs : 
			str = "Export Import IVS";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_DirectSdmcWrite : 
			str = "Direct SDMC (Write Only)";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_SwitchCleanup : 
			str = "Switch Cleanup";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_SaveDataMove : 
			str = "Save Data Move";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_Shop : 
			str = "Shop";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_Shell : 
			str = "Shell";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_CategoryHomeMenu : 
			str = "Category HomeMenu";
			break;
		case ntd::n3ds::AccessControlInfo::FileSystemAccess_ExternalSeed : 
			str = "External Seed (Seed DB)";
			break;
		default : 
			str = fmt::format("Bit {:d} (unknown)", bit);
			break;
	}
	
	return str;
}

std::string ctrtool::ExHeaderProcess::getMemoryTypeString(byte_t memory_type)
{
	std::string str;

	switch(memory_type)
	{
		case ntd::n3ds::AccessControlInfo::MemoryType_Application : 
			str = "APPLICATION";
			break;
		case ntd::n3ds::AccessControlInfo::MemoryType_System : 
			str = "SYSTEM";
			break;
		case ntd::n3ds::AccessControlInfo::MemoryType_Base : 
			str = "BASE";
			break;
		default : 
			str = fmt::format("Unknown ({:d})", memory_type);
			break;
	}
	
	return str;
}

std::string ctrtool::ExHeaderProcess::getByteHexString(byte_t byte)
{
	return fmt::format("{:02X}", byte);
}

std::string ctrtool::ExHeaderProcess::getSysCallName(byte_t syscall)
{
	// List of 3DS system calls.  NULL indicates unknown.
	static const size_t kSysCallNum = 128;
	static const char *const kSysCallList[kSysCallNum] =
	{
		NULL,                                // 00
		"ControlMemory",                     // 01
		"QueryMemory",                       // 02
		"ExitProcess",                       // 03
		"GetProcessAffinityMask",            // 04
		"SetProcessAffinityMask",            // 05
		"GetProcessIdealProcessor",          // 06
		"SetProcessIdealProcessor",          // 07
		"CreateThread",                      // 08
		"ExitThread",                        // 09
		"SleepThread",                       // 0A
		"GetThreadPriority",                 // 0B
		"SetThreadPriority",                 // 0C
		"GetThreadAffinityMask",             // 0D
		"SetThreadAffinityMask",             // 0E
		"GetThreadIdealProcessor",           // 0F
		"SetThreadIdealProcessor",           // 10
		"GetCurrentProcessorNumber",         // 11
		"Run",                               // 12
		"CreateMutex",                       // 13
		"ReleaseMutex",                      // 14
		"CreateSemaphore",                   // 15
		"ReleaseSemaphore",                  // 16
		"CreateEvent",                       // 17
		"SignalEvent",                       // 18
		"ClearEvent",                        // 19
		"CreateTimer",                       // 1A
		"SetTimer",                          // 1B
		"CancelTimer",                       // 1C
		"ClearTimer",                        // 1D
		"CreateMemoryBlock",                 // 1E
		"MapMemoryBlock",                    // 1F
		"UnmapMemoryBlock",                  // 20
		"CreateAddressArbiter",              // 21
		"ArbitrateAddress",                  // 22
		"CloseHandle",                       // 23
		"WaitSynchronization1",              // 24
		"WaitSynchronizationN",              // 25
		"SignalAndWait",                     // 26
		"DuplicateHandle",                   // 27
		"GetSystemTick",                     // 28
		"GetHandleInfo",                     // 29
		"GetSystemInfo",                     // 2A
		"GetProcessInfo",                    // 2B
		"GetThreadInfo",                     // 2C
		"ConnectToPort",                     // 2D
		"SendSyncRequest1",                  // 2E
		"SendSyncRequest2",                  // 2F
		"SendSyncRequest3",                  // 30
		"SendSyncRequest4",                  // 31
		"SendSyncRequest",                   // 32
		"OpenProcess",                       // 33
		"OpenThread",                        // 34
		"GetProcessId",                      // 35
		"GetProcessIdOfThread",              // 36
		"GetThreadId",                       // 37
		"GetResourceLimit",                  // 38
		"GetResourceLimitLimitValues",       // 39
		"GetResourceLimitCurrentValues",     // 3A
		"GetThreadContext",                  // 3B
		"Break",                             // 3C
		"OutputDebugString",                 // 3D
		"ControlPerformanceCounter",         // 3E
		NULL,                                // 3F
		NULL,                                // 40
		NULL,                                // 41
		NULL,                                // 42
		NULL,                                // 43
		NULL,                                // 44
		NULL,                                // 45
		NULL,                                // 46
		"CreatePort",                        // 47
		"CreateSessionToPort",               // 48
		"CreateSession",                     // 49
		"AcceptSession",                     // 4A
		"ReplyAndReceive1",                  // 4B
		"ReplyAndReceive2",                  // 4C
		"ReplyAndReceive3",                  // 4D
		"ReplyAndReceive4",                  // 4E
		"ReplyAndReceive",                   // 4F
		"BindInterrupt",                     // 50
		"UnbindInterrupt",                   // 51
		"InvalidateProcessDataCache",        // 52
		"StoreProcessDataCache",             // 53
		"FlushProcessDataCache",             // 54
		"StartInterProcessDma",              // 55
		"StopDma",                           // 56
		"GetDmaState",                       // 57
		"RestartDma",                        // 58
		"SetGpuProt",                        // 59
		"SetWifiEnabled",                    // 5A
		NULL,                                // 5B
		NULL,                                // 5C
		NULL,                                // 5D
		NULL,                                // 5E
		NULL,                                // 5F
		"DebugActiveProcess",                // 60
		"BreakDebugProcess",                 // 61
		"TerminateDebugProcess",             // 62
		"GetProcessDebugEvent",              // 63
		"ContinueDebugEvent",                // 64
		"GetProcessList",                    // 65
		"GetThreadList",                     // 66
		"GetDebugThreadContext",             // 67
		"SetDebugThreadContext",             // 68
		"QueryDebugProcessMemory",           // 69
		"ReadProcessMemory",                 // 6A
		"WriteProcessMemory",                // 6B
		"SetHardwareBreakPoint",             // 6C
		"GetDebugThreadParam",               // 6D
		NULL,                                // 6E
		NULL,                                // 6F
		"ControlProcessMemory",              // 70
		"MapProcessMemory",                  // 71
		"UnmapProcessMemory",                // 72
		"CreateCodeSet",                     // 73
		NULL,                                // 74
		"CreateProcess",                     // 75
		"TerminateProcess",                  // 76
		"SetProcessResourceLimits",          // 77
		"CreateResourceLimit",               // 78
		"SetResourceLimitValues",            // 79
		"AddCodeSegment",                    // 7A
		"Backdoor",                          // 7B
		"KernelSetState",                    // 7C
		"QueryProcessMemory",                // 7D
		NULL,                                // 7E
		NULL,                                // 7F
	};

	std::string str;

	if (syscall >= kSysCallNum)
		return std::string();

	if (kSysCallList[syscall] != nullptr)
	{
		str = kSysCallList[syscall];
	}
	else
	{
		str = fmt::format("Unknown {:02X}", syscall);;
	}

	return str;
}

std::string ctrtool::ExHeaderProcess::getArm9CapabilityBitString(size_t bit)
{
	std::string str;

	switch(bit)
	{
		case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountNand : 
			str = "FsMountNand";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountNandRoWrite : 
			str = "FsMountNandRoWrite";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountTwln : 
			str = "FsMountTwln";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountWnand : 
			str = "FsMountWnand";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_FsMountCardSpi : 
			str = "FsMountCardSpi";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_UseSdif3 : 
			str = "UseSdif3";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_CreateSeed : 
			str = "CreateSeed";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_UseCardSpi : 
			str = "UseCardSpi";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_SdApplication : 
			str = "SdApplication";
			break;
		case ntd::n3ds::AccessControlInfo::Arm9Capability_UseDirectSdmc : 
			str = "UseDirectSdmc";
			break;
		default : 
			str = fmt::format("Bit {:d} (unknown)", bit);
			break;
	}
	
	return str;
}