/*
	Copyright (c) 2004/2005 KJK::Hyperion

	Permission is hereby granted, free of charge, to any person obtaining a
	copy of this software and associated documentation files (the "Software"),
	to deal in the Software without restriction, including without limitation
	the rights to use, copy, modify, merge, publish, distribute, sublicense,
	and/or sell copies of the Software, and to permit persons to whom the
	Software is furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
	DEALINGS IN THE SOFTWARE.
*/

#define _NTSYSTEM_
#define STRICT
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <pseh/pseh.h>
#include <pseh/framebased/internal.h>
#include <pseh/excpt.h>
#include <pseh/framebased.h>

#include <excpt.h>

/* Tracing */
#ifdef _SEH_ENABLE_TRACE
extern unsigned long __cdecl DbgPrint(const char * format, ...);

#define _SEH_TRACE_HEADER_(FRAME_) \
	DbgPrint("[PSEH:%p]%s:%d:", FRAME_, __FILE__, __LINE__);

#define _SEH_TRACE_TRAILER_ \
	DbgPrint("\n");

#define _SEH_FILTER_RET_STRING_(RET_) \
	(((int)(RET_) < 0) ? "_SEH_CONTINUE_EXECUTION" : (((int)(RET_) > 0) ? "_SEH_EXECUTE_HANDLER" : "_SEH_CONTINUE_SEARCH"))

#define _SEH_TRACE_LINE_(FRAME_, ARGS_) \
{ \
	_SEH_TRACE_HEADER_(FRAME_); \
	DbgPrint ARGS_; \
	_SEH_TRACE_TRAILER_; \
}

#define _SEH_TRACE_ENTER(FRAME_, FUNCNAME_, ARGS_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_ENTER_LEAVE) \
	{ \
		_SEH_TRACE_HEADER_(FRAME_); \
		DbgPrint(">>> %s(", (FUNCNAME_)); \
		DbgPrint ARGS_; \
		DbgPrint(")"); \
		_SEH_TRACE_TRAILER_; \
	} \
}

#define _SEH_TRACE_LEAVE(FRAME_, FUNCNAME_, ARGS_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_ENTER_LEAVE) \
	{ \
		_SEH_TRACE_HEADER_(FRAME_); \
		DbgPrint("<<< %s => ", (FUNCNAME_)); \
		DbgPrint ARGS_; \
		_SEH_TRACE_TRAILER_; \
	} \
}

#define _SEH_TRACE_EXCEPTION_RECORD(FRAME_, ER_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_EXCEPTION_RECORD) \
	{ \
		_SEH_TRACE_LINE_ \
		( \
			(FRAME_), \
			( \
				"ExceptionRecord %p = { ExceptionCode : %08X, ExceptionFlags : %08X, ExceptionRecord : %p, ExceptionAddress : %p }", \
				(ER_), \
				(ER_)->ExceptionCode, \
				(ER_)->ExceptionFlags, \
				(ER_)->ExceptionRecord, \
				(ER_)->ExceptionAddress \
			) \
		); \
	} \
}

#ifdef _X86_
#define _SEH_TRACE_CONTEXT(FRAME_, CONTEXT_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_CONTEXT) \
	{ \
		if(((CONTEXT_)->ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER) \
		{ \
			_SEH_TRACE_LINE_ \
			( \
				(FRAME_), \
				( \
					"eax=%08X ebx=%08X ecx=%08X edx=%08X esi=%08X edi=%08X", \
					(CONTEXT_)->Eax, \
					(CONTEXT_)->Ebx, \
					(CONTEXT_)->Ecx, \
					(CONTEXT_)->Edx, \
					(CONTEXT_)->Esi, \
					(CONTEXT_)->Edi \
				) \
			); \
		} \
	\
		if(((CONTEXT_)->ContextFlags & CONTEXT_CONTROL) == CONTEXT_CONTROL) \
		{ \
			_SEH_TRACE_LINE_ \
			( \
				(FRAME_), \
				( \
					"eip=%08X esp=%08X ebp=%08X efl=%08X cs=%08X ss=%08X", \
					(CONTEXT_)->Eip, \
					(CONTEXT_)->Esp, \
					(CONTEXT_)->Ebp, \
					(CONTEXT_)->EFlags, \
					(CONTEXT_)->SegCs, \
					(CONTEXT_)->SegSs \
				) \
			); \
		} \
	\
		if(((CONTEXT_)->ContextFlags & CONTEXT_SEGMENTS) == CONTEXT_SEGMENTS) \
		{ \
			_SEH_TRACE_LINE_ \
			( \
				(FRAME_), \
				( \
					"ds=%08X es=%08X fs=%08X gs=%08X", \
					(CONTEXT_)->SegDs, \
					(CONTEXT_)->SegEs, \
					(CONTEXT_)->SegFs, \
					(CONTEXT_)->SegGs \
				) \
			); \
		} \
	} \
}
#else
#define _SEH_TRACE_CONTEXT(FRAME_, CONTEXT_)
#endif

#define _SEH_TRACE_UNWIND(FRAME_, ARGS_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_UNWIND) \
	{ \
		_SEH_TRACE_LINE_((FRAME_), ARGS_); \
	} \
}

#define _SEH_TRACE_TRYLEVEL(FRAME_, TRYLEVEL_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_TRYLEVEL) \
	{ \
		_SEH_TRACE_LINE_((FRAME_), ("trylevel %p, filter %p", (TRYLEVEL_), (TRYLEVEL_)->SPT_Handlers.SH_Filter)); \
	} \
}

#define _SEH_TRACE_ENTER_CALL_FILTER(FRAME_, TRYLEVEL_, ER_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_CALL_FILTER) \
	{ \
		_SEH_TRACE_LINE_ \
		( \
			(FRAME_), \
			( \
				"trylevel %p, calling filter %p, ExceptionCode %08X", \
				(TRYLEVEL_), \
				(TRYLEVEL_)->SPT_Handlers.SH_Filter, \
				(ER_)->ExceptionCode \
			) \
		); \
	} \
}

#define _SEH_TRACE_LEAVE_CALL_FILTER(FRAME_, TRYLEVEL_, RET_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_CALL_FILTER) \
	{ \
		_SEH_TRACE_LINE_ \
		( \
			(FRAME_), \
			( \
				"trylevel %p, filter %p => %s", \
				(TRYLEVEL_), \
				(TRYLEVEL_)->SPT_Handlers.SH_Filter, \
				_SEH_FILTER_RET_STRING_(RET_) \
			) \
		); \
	} \
}

#define _SEH_TRACE_FILTER(FRAME_, TRYLEVEL_, RET_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_FILTER) \
	{ \
		_SEH_TRACE_LINE_ \
		( \
			(FRAME_), \
			( \
				"trylevel %p => %s", \
				(TRYLEVEL_), \
				_SEH_FILTER_RET_STRING_(RET_) \
			) \
		); \
	} \
}

#define _SEH_TRACE_ENTER_CALL_HANDLER(FRAME_, TRYLEVEL_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_CALL_HANDLER) \
	{ \
		_SEH_TRACE_LINE_((FRAME_), ("trylevel %p, handling", (TRYLEVEL_))); \
	} \
}

#define _SEH_TRACE_ENTER_CALL_FINALLY(FRAME_, TRYLEVEL_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_CALL_FINALLY) \
	{ \
		_SEH_TRACE_LINE_ \
		( \
			(FRAME_), \
			( \
				"trylevel %p, calling exit routine %p", \
				(TRYLEVEL_), \
				(TRYLEVEL_)->SPT_Handlers.SH_Finally \
			) \
		); \
	} \
}

#define _SEH_TRACE_LEAVE_CALL_FINALLY(FRAME_, TRYLEVEL_) \
{ \
	if((FRAME_)->SPF_Tracing & _SEH_DO_TRACE_CALL_FINALLY) \
	{ \
		_SEH_TRACE_LINE_ \
		( \
			(FRAME_), \
			( \
				"trylevel %p, exit routine %p returned", \
				(TRYLEVEL_), \
				(TRYLEVEL_)->SPT_Handlers.SH_Finally \
			) \
		); \
	} \
}

#else
#define _SEH_TRACE_ENTER(FRAME_, FUNCNAME_, ARGS_)
#define _SEH_TRACE_LEAVE(FRAME_, FUNCNAME_, ARGS_)
#define _SEH_TRACE_EXCEPTION_RECORD(FRAME_, ER_)
#define _SEH_TRACE_CONTEXT(FRAME_, CONTEXT_)
#define _SEH_TRACE_UNWIND(FRAME_, ARGS_)
#define _SEH_TRACE_TRYLEVEL(FRAME_, TRYLEVEL_)
#define _SEH_TRACE_ENTER_CALL_FILTER(FRAME_, TRYLEVEL_, ER_)
#define _SEH_TRACE_LEAVE_CALL_FILTER(FRAME_, TRYLEVEL_, RET_)
#define _SEH_TRACE_FILTER(FRAME_, TRYLEVEL_, RET_)
#define _SEH_TRACE_ENTER_CALL_HANDLER(FRAME_, TRYLEVEL_)
#define _SEH_TRACE_ENTER_CALL_FINALLY(FRAME_, TRYLEVEL_)
#define _SEH_TRACE_LEAVE_CALL_FINALLY(FRAME_, TRYLEVEL_)
#endif

/* Assembly helpers, see i386/framebased.asm */
extern void __cdecl _SEHCleanHandlerEnvironment(void);
extern struct __SEHRegistration * __cdecl _SEHRegisterFrame(_SEHRegistration_t *);
extern void __cdecl _SEHUnregisterFrame(void);
extern void __cdecl _SEHGlobalUnwind(_SEHPortableFrame_t *);
extern _SEHRegistration_t * __cdecl _SEHCurrentRegistration(void);

static void __stdcall _SEHLocalUnwind
(
	_SEHPortableFrame_t * frame,
	_SEHPortableTryLevel_t * dsttrylevel
)
{
	_SEHPortableTryLevel_t * trylevel;

	_SEH_TRACE_UNWIND(frame, ("enter local unwind from %p to %p", frame->SPF_TopTryLevel, dsttrylevel));

	for
	(
		trylevel = frame->SPF_TopTryLevel;
		trylevel != dsttrylevel;
		trylevel = trylevel->SPT_Next
	)
	{
		_SEHFinally_t pfnFinally;

		/* ASSERT(trylevel); */

		pfnFinally = trylevel->SPT_Handlers.SH_Finally;

		if(pfnFinally)
		{
			_SEH_TRACE_ENTER_CALL_FINALLY(frame, trylevel);
			pfnFinally(frame);
			_SEH_TRACE_LEAVE_CALL_FINALLY(frame, trylevel);
		}
	}

	_SEH_TRACE_UNWIND(frame, ("leave local unwind from %p to %p", frame->SPF_TopTryLevel, dsttrylevel));
}

static void __cdecl _SEHCallHandler
(
	_SEHPortableFrame_t * frame,
	_SEHPortableTryLevel_t * trylevel
)
{
	_SEHGlobalUnwind(frame);
	_SEHLocalUnwind(frame, trylevel);
	_SEH_TRACE_ENTER_CALL_HANDLER(frame, trylevel);
	frame->SPF_Handler(trylevel);
	/* ASSERT(0); */
}

static int __cdecl _SEHFrameHandler
(
	struct _EXCEPTION_RECORD * ExceptionRecord,
	void * EstablisherFrame,
	struct _CONTEXT * ContextRecord,
	void * DispatcherContext
)
{
	_SEHPortableFrame_t * frame;

	_SEHCleanHandlerEnvironment();

	frame = EstablisherFrame;

	_SEH_TRACE_ENTER
	(
		frame,
		"_SEHFrameHandler",
		(
			"%p, %p, %p, %p",
			ExceptionRecord,
			EstablisherFrame,
			ContextRecord,
			DispatcherContext
		)
	);

	_SEH_TRACE_EXCEPTION_RECORD(frame, ExceptionRecord);
	_SEH_TRACE_CONTEXT(frame, ContextRecord);

	/* Unwinding */
	if(ExceptionRecord->ExceptionFlags & (4 | 2))
	{
		_SEH_TRACE_UNWIND(frame, ("enter forced unwind"));
		_SEHLocalUnwind(frame, NULL);
		_SEH_TRACE_UNWIND(frame, ("leave forced unwind"));
	}
	/* Handling */
	else
	{
		int ret;
		_SEHPortableTryLevel_t * trylevel;

		if(ExceptionRecord->ExceptionCode)
			frame->SPF_Code = ExceptionRecord->ExceptionCode;
		else
			frame->SPF_Code = 0xC0000001;

		for
		(
			trylevel = frame->SPF_TopTryLevel;
			trylevel != NULL;
			trylevel = trylevel->SPT_Next
		)
		{
			_SEHFilter_t pfnFilter = trylevel->SPT_Handlers.SH_Filter;

			_SEH_TRACE_TRYLEVEL(frame, trylevel);

			switch((UINT_PTR)pfnFilter)
			{
				case __SEH_STATIC_FILTER(_SEH_EXECUTE_HANDLER):
				case __SEH_STATIC_FILTER(_SEH_CONTINUE_SEARCH):
				case __SEH_STATIC_FILTER(_SEH_CONTINUE_EXECUTION):
				{
					ret = (int)((UINT_PTR)pfnFilter) - 2;
					break;
				}

				default:
				{
					if(trylevel->SPT_Handlers.SH_Filter)
					{
						EXCEPTION_POINTERS ep;

						ep.ExceptionRecord = ExceptionRecord;
						ep.ContextRecord = ContextRecord;

						_SEH_TRACE_ENTER_CALL_FILTER(frame, trylevel, ExceptionRecord);
						ret = pfnFilter(&ep, frame);
						_SEH_TRACE_LEAVE_CALL_FILTER(frame, trylevel, ret);
					}
					else
						ret = _SEH_CONTINUE_SEARCH;

					break;
				}
			}

			_SEH_TRACE_FILTER(frame, trylevel, ret);

			/* _SEH_CONTINUE_EXECUTION */
			if(ret < 0)
			{
				_SEH_TRACE_LEAVE(frame, "_SEHFrameHandler", ("ExceptionContinueExecution"));
				return ExceptionContinueExecution;
			}
			/* _SEH_EXECUTE_HANDLER */
			else if(ret > 0)
				_SEHCallHandler(frame, trylevel);
			/* _SEH_CONTINUE_SEARCH */
			else
				continue;
		}

		/* FALLTHROUGH */
	}

	_SEH_TRACE_LEAVE(frame, "_SEHFrameHandler", ("ExceptionContinueSearch"));
	return ExceptionContinueSearch;
}

void __stdcall _SEHEnterFrame_s(_SEHPortableFrame_t * frame)
{
	_SEHEnterFrame_f(frame);
}

void __stdcall _SEHLeaveFrame_s(void)
{
	_SEHLeaveFrame_f();
}

void __stdcall _SEHReturn_s(void)
{
	_SEHReturn_f();
}

void _SEH_FASTCALL _SEHEnterFrame_f(_SEHPortableFrame_t * frame)
{
	/* ASSERT(frame); */
	/* ASSERT(trylevel); */
	frame->SPF_Registration.SER_Handler = _SEHFrameHandler;
	frame->SPF_Code = 0;
	_SEHRegisterFrame(&frame->SPF_Registration);
}

void _SEH_FASTCALL _SEHLeaveFrame_f(void)
{
	_SEHPortableFrame_t * frame;

	frame = _SEH_CONTAINING_RECORD
	(
		_SEHCurrentRegistration(),
		_SEHPortableFrame_t,
		SPF_Registration
	);

	/* ASSERT(frame); */
	/* ASSERT(frame->SPF_TopTryLevel == NULL) */

	_SEHUnregisterFrame();
}

void _SEH_FASTCALL _SEHReturn_f(void)
{
	_SEHPortableFrame_t * frame;

	frame = _SEH_CONTAINING_RECORD
	(
		_SEHCurrentRegistration(),
		_SEHPortableFrame_t,
		SPF_Registration
	);

	_SEHLocalUnwind(frame, NULL);
	_SEHUnregisterFrame();
}

/* EOF */
