/* Capstone Disassembly Engine */
/* By Satoshi Tanda <tanda.sat@gmail.com>, 2016 */

#include <ntddk.h>

#include <capstone/platform.h>
#include <capstone/capstone.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "../utils.h"   // for cs_snprintf

#ifdef __cplusplus
}
#endif

EXTERN_C DRIVER_INITIALIZE DriverEntry;

#pragma warning(push)
#pragma warning(disable : 4005)   // 'identifier' : macro redefinition
#pragma warning(disable : 4007)   // 'main': must be '__cdecl'

// Drivers must protect floating point hardware state. See use of float.
// Use KeSaveFloatingPointState/KeRestoreFloatingPointState around floating
// point operations. Display Drivers should use the corresponding Eng... routines.
#pragma warning(disable : 28110)  // Suppress this, as it is false positive.

// "Import" existing tests into this file. All code is encaptured into unique
// namespace so that the same name does not conflict. Beware that those code
// is going to be compiled as C++ source file and not C files because this file
// is C++.

namespace basic {
#include "test_basic.c"
}  // namespace basic

namespace detail {
#include "test_detail.c"
}  // namespace detail

namespace skipdata {
#include "test_skipdata.c"
}  // namespace skipdata

namespace iter {
#include "test_iter.c"
}  // namespace iter

namespace customized_mnem_ {
#include "test_customized_mnem.c"
}  // namespace customized_mnem_

namespace arm {
#include "test_arm.c"
}  // namespace arm

namespace arm64 {
#include "test_aarch64.c"
}  // namespace arm64

namespace mips {
#include "test_mips.c"
}  // namespace mips

namespace m68k {
#include "test_m68k.c"
}  // namespace m68k

namespace ppc {
#include "test_ppc.c"
}  // namespace ppc

namespace sparc {
#include "test_sparc.c"
}  // namespace sparc

namespace systemz {
#include "test_systemz.c"
}  // namespace systemz

namespace x86 {
#include "test_x86.c"
}  // namespace x86

namespace xcore {
#include "test_xcore.c"
}  // namespace xcore

#pragma warning(pop)

// Exercises all existing regression tests
static void test()
{
	KFLOATING_SAVE float_save;
	NTSTATUS status;

	// Any of Capstone APIs cannot be called at IRQL higher than DISPATCH_LEVEL
	// since our malloc implementation using ExAllocatePoolWithTag() is able to
	// allocate memory only up to the DISPATCH_LEVEL level.
	NT_ASSERT(KeGetCurrentIrql() <= DISPATCH_LEVEL);

	// On a 32bit driver, KeSaveFloatingPointState() is required before using any
	// Capstone function because Capstone can access to the MMX/x87 registers and
	// 32bit Windows requires drivers to use KeSaveFloatingPointState() before and
	// KeRestoreFloatingPointState() after accessing them. See "Using Floating
	// Point or MMX in a WDM Driver" on MSDN for more details.
	status = KeSaveFloatingPointState(&float_save);
	if (!NT_SUCCESS(status)) {
		printf("ERROR: Failed to save floating point state!\n");
		return;
	}

	basic::test();
	detail::test();
	skipdata::test();
	iter::test();
	customized_mnem_::test();
	arm::test();
	arm64::test();
	mips::test();
	m68k::test();
	ppc::test();
	sparc::test();
	systemz::test();
	x86::test();
	xcore::test();

	// Restores the nonvolatile floating-point context.
	KeRestoreFloatingPointState(&float_save);
}

// Functional test for cs_winkernel_vsnprintf()
static void cs_winkernel_vsnprintf_test()
{
	char buf[10];
	bool ok = true;
	ok = (ok && cs_snprintf(buf, sizeof(buf), "%s", "") == 0 && strcmp(buf, "") == 0);
	ok = (ok && cs_snprintf(buf, sizeof(buf), "%s", "0") == 1 && strcmp(buf, "0") == 0);
	ok = (ok && cs_snprintf(buf, sizeof(buf), "%s", "012345678") == 9 && strcmp(buf, "012345678") == 0);
	ok = (ok && cs_snprintf(buf, sizeof(buf), "%s", "0123456789") == 10 && strcmp(buf, "012345678") == 0);
	ok = (ok && cs_snprintf(buf, sizeof(buf), "%s", "01234567890") == 11 && strcmp(buf, "012345678") == 0);
	ok = (ok && cs_snprintf(buf, sizeof(buf), "%s", "0123456789001234567890") == 22 && strcmp(buf, "012345678") == 0);
	if (!ok) {
		printf("ERROR: cs_winkernel_vsnprintf_test() did not produce expected results!\n");
	}
}

// Driver entry point
EXTERN_C NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
	UNREFERENCED_PARAMETER(DriverObject);
	UNREFERENCED_PARAMETER(RegistryPath);
	cs_winkernel_vsnprintf_test();
	test();
	return STATUS_CANCELLED;
}

// This functions mimics printf() but does not return the same value as printf()
// would do. printf() is required to exercise regression tests.
_Use_decl_annotations_
int __cdecl printf(const char * format, ...)
{
	NTSTATUS status;
	va_list args;

	va_start(args, format);
	status = vDbgPrintEx(DPFLTR_DEFAULT_ID, DPFLTR_ERROR_LEVEL, format, args);
	va_end(args);
	return NT_SUCCESS(status);
}
