/* * * * * * * * * * * * * * * * 
 * 	 ______     __  __     ______     __  __     ______     ______     __  __    
 *	/\  __ \   /\ \/\ \   /\  == \   /\ \_\ \   /\  __ \   /\  ___\   /\ \/ /    
 *	\ \ \/\ \  \ \ \_\ \  \ \  __<   \ \  __ \  \ \  __ \  \ \ \____  \ \  _"-.  
 *	 \ \_____\  \ \_____\  \ \_____\  \ \_\ \_\  \ \_\ \_\  \ \_____\  \ \_\ \_\ 
 *	  \/_____/   \/_____/   \/_____/   \/_/\/_/   \/_/\/_/   \/_____/   \/_/\/_/ 
 *
 *  - Version : 3L
 *  - By Oubliette
 * * * * * * * * * * * * * * * */


// Headers
#include "oubhack.h"
#include "oubhack_iluainterface.h"
#include "oubhack_iluashared.h"

#include "main.h"
#include "vmthook.h"
#include "inetchannelinfo.h"

#include "clientmode_shared.h"

#include <TlHelp32.h>

// Hooks 
CVMTHookManager* h_pLuaShared;
CVMTHookManager* h_pLuaInterface;

// Classes
ILuaShared* g_pLuaShared;
PDWORD_PTR* pdwClient;

// test
DWORD GetModuleSize(DWORD processID, char* module)
{

    HANDLE hSnap;
    MODULEENTRY32 xModule;
    hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID);
    xModule.dwSize = sizeof(MODULEENTRY32);
    if (Module32First(hSnap, &xModule)) {
        while (Module32Next(hSnap, &xModule)) {
            if (!strncmp((char*)xModule.szModule, module, 8)) {
                CloseHandle(hSnap);
                return (DWORD)xModule.modBaseSize;
            }
        }
    }
    CloseHandle(hSnap);
    return 0;
}

bool DataCompare( const BYTE* bData, const BYTE* bMask, const char* cMask )
{
	for( ; *cMask; ++cMask, ++bData, ++bMask ){

		if( *cMask == 'x' && *bData != *bMask ){

			return false;

		}

	}

	return ( *cMask ) == NULL;
}

DWORD FindPattern( DWORD dwAddress, DWORD dwLen, BYTE *bMask, char * cMask){

	for( DWORD i = NULL; i < dwLen; i++ ){

		if( DataCompare( (BYTE*) ( dwAddress + i ), bMask, cMask ) ){

			return (DWORD)( dwAddress + i );

		}

	}

	return 0;

}

bool bHasRun = false;

/* ========================================
	Name  :	Hooked_RunString
	Notes : ILuaInterface[1]->RunString
=========================================== */
bool __stdcall Hooked_RunString( const char* ccName, const char* ccPath, const char* ccString, bool bRun, bool bErrors ){

	h_pLuaInterface->UnHook();

	bool bRes; 

	if ( !bHasRun ){

		// This is when it's safe to use the ILuaInterface, so we should load things that use that now.

		oubhack->LoadWrappers();

		oubhack->InitLibraries();
		oubhack->InitClientBindings();

		oubhack->Debug( "Running Lua scripts .." );

		oubhack->GetLuaInterface( LUA_STATE_CLIENT )->RunString( "ohbs", "", oubhack->m_pFileIO->ReadDecryptedFile( LUA_CLIENTLOAD ).c_str(), true, true );

		oubhack->Log( Color( 0, 200, 100, 255 ), "Lua scripts loaded!" );

		bHasRun = true;

	}

	if ( oubhack->m_pCHooks->CallRunString( ccName, ccPath, ccString ) ){

		bRes = oubhack->GetLuaInterface( LUA_STATE_CLIENT )->RunString( ccName, ccPath, ccString, bRun, bErrors );

	}
	else {

		bRes = true;
			
	}
	
	h_pLuaInterface->ReHook();

	return bRes;

}

/* ========================================
	Name : OubHack::OubHack
=========================================== */

OubHack::OubHack( ){

	this->Log( Color( 0, 215, 0, 255 ), "OubHack loading .." );

	// Essential library, needed to load lua
	this->m_pFileIO =			new IFileIO();
	

	// Good time to initialize the interfaces ( used in auth )? yup? awesome

	if ( !this->InitInterfaces( ) ){

		// oubhack->Shutdown();
		return;

	}

	// Start rendering the menu

}

/* ========================================
	Name : OubHack::Init
=========================================== */

ClientModeShared* cmode;
CVMTHookManager* hook;
CInput* g_pInput;
typedef void* ( __stdcall* CreateMoveFn )( int, float, bool );

void __stdcall CreateMove( int seq, float frametime, bool active ){

	try {

		((CreateMoveFn)((PDWORD_PTR*)oubhack->Interfaces.pHLClient)[22])( seq, frametime, active );
		
		CUserCmd* cmd = g_pInput->GetUserCmd( seq );

		oubhack->Debug("Lol");


	}
	catch ( ... ){

		oubhack->Error("Exception occured in CHLClient::CreateMove!");

	}

}

void OubHack::Init(){

	// Read auth data from the pipe
	HANDLE pipe = CreateNamedPipe("\\\\.\\pipe\\authpipe", PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND , PIPE_WAIT, 1, sizeof(int64_t) + 256, sizeof(int64_t) + 256, 120 * 1000, NULL);

	if (pipe != INVALID_HANDLE_VALUE){
		ConnectNamedPipe(pipe, NULL);

		DWORD read;

		// Various placeholders for values we're going to read from the pipe
		int64_t l64SessionID;
		int iVersion;
		bool bDebug;

		// Read data from the ppe
		ReadFile(pipe, &l64SessionID, sizeof(int64_t), &read, NULL);
		ReadFile(pipe, &iVersion, sizeof( int ), &read, NULL);
		ReadFile(pipe, &bDebug, sizeof( bool ), &read, NULL );

		// Store the data in OubHack's main class
		this->m_ccIP = "192.168.1.120";

		this->m_l64SessionID = l64SessionID;
		this->m_bDebug = bDebug;
		this->m_iVersion = iVersion;

		CloseHandle(pipe);

		this->LoadAuthListener();
	}
	else {

		this->Error("Failed to link with injector!");

	}

	
	// Run menu scripts
	//this->GetLuaInterface( LUA_STATE_MENU )->RunString( "ohmenu", "", , true, true );

	this->m_pMenuRender =		new CMenuRender();
	this->m_pMenuRender->DoRender();

	// Test

}


/* ========================================
	Name : OubHack::InitLibraries
=========================================== */

void OubHack::InitLibraries(){
	
	oubhack->Debug("Loading libraries..");

	this->m_pCvarInterface =	new ICvarInterface();
	this->m_pLuaLibrary	=		new ILuaLibrary();
	this->m_pConsoleCommands =	new IConsoleCommands();
	this->m_pCHooks =			new ICHook();
	this->m_pLuaExports =		new CLuaExports();

	oubhack->Log( Color( 0, 200, 100, 255 ), "Libraries loaded!" );

}

/* ========================================
	Name : OubHack::OnStateCreated
=========================================== */

void OubHack::OnStateCreated( PDWORD_PTR* pdwState ){

	// Only called when the client state is created, never called with any others

	this->m_pClientLua = new ILuaInterface( pdwState ); 

	h_pLuaInterface = new CVMTHookManager( (PDWORD*)pdwState );
	h_pLuaInterface->dwHookMethod( (DWORD)Hooked_RunString, INDEX_LUAINTERFACE_RUNSTRING ); 

}

/* ========================================
	Name : OubHack::OnStateDestroyed
=========================================== */

void OubHack::OnStateDestroyed( ){

	// This will only ever get called when the client state is cleaned up, because when the menustate is removed
	// OubHack won't be loaded anymore.  The cleanup process requires an interface's functions to clean up.
	if ( this->GetLuaInterface( LUA_STATE_CLIENT ) == NULL ){

		this->Error( "Couldn't clean up Lua classes because ILuaInterace was NULL!" );
		return;

	}

	if ( this->GetLuaInterface( LUA_STATE_CLIENT )->IsClient() ){

		// Allow running of our script again
		bHasRun = false;

		// Tell the lua scripts we're closing shop
		this->m_pCHooks->CallClose();

		// Destroy our Lua based interfaces
		this->m_pLuaLibrary->Close();
		//this->m_pMHooks->Close();
		this->m_pCHooks->Close();
		this->m_pConsoleCommands->Close();
		this->m_pCvarInterface->Close();

	}

}

/* ========================================
	Name : OubHack::GetLuaInterface
=========================================== */

ILuaInterface* OubHack::GetLuaInterface( int iType ){

	if ( iType == LUA_STATE_CLIENT )
		return this->m_pClientLua; 
	else 
		return this->m_pMenuLua; 

}

/* ========================================
	Name : OubHack::GetLuaInterface
=========================================== */

PDWORD_PTR* OubHack::RawGetLuaInterface( int iType ){
	
	// Not implemented yet, nor used yet.
	return NULL;

}

/* ========================================
	Name : OubHack::LoadWrappers
=========================================== */

void OubHack::LoadWrappers( ){
	
	this->m_pClient = new IEngineClient( (PDWORD_PTR*)this->Interfaces.pClient );

}

/* ========================================
	Name : Hooked_CreateLuaInterface
	Returns : PDWORD_PTR*
=========================================== */

PDWORD_PTR* __stdcall Hooked_CreateLuaInterface( unsigned char ucState, bool something ){

	h_pLuaShared->UnHook();

	PDWORD_PTR* pdwState =  g_pLuaShared->CreateLuaInterface( ucState, something );

	h_pLuaShared->ReHook();

	if ( ucState == 0 ){

		oubhack->Debug( "Clientside state found" );

		pdwClient = pdwState;

		oubhack->OnStateCreated( pdwClient );

	}
	else {

		oubhack->Debug( "Strange state created : %u", ucState );

	}

	return pdwState;

}

/* ========================================
	Name : Hooked_CloseLuaInterface
	Returns : void
=========================================== */

void __stdcall Hooked_CloseLuaInterface( PDWORD_PTR* pdwILuaInterface ){

	h_pLuaShared->UnHook();

	ILuaInterface* pLuaInterface = new ILuaInterface( pdwILuaInterface );

	if ( pLuaInterface->IsClient() ){

		oubhack->Debug( "Clientside state destroyed" );
		oubhack->OnStateDestroyed();

	}
	else if ( pLuaInterface->IsServer() ) {

		oubhack->Debug( "Server state destroyed" );

	}
	else {

		oubhack->Debug( "Unknown state destroyed" );

	}

	delete pLuaInterface;

	g_pLuaShared->CloseLuaInterface( pdwILuaInterface );

	h_pLuaShared->ReHook();

}

/* ========================================
	Name : OubHack::OnAuthed
	Returns : void
=========================================== */

void OubHack::OnAuthed(){

	this->Log( Color( 0, 200, 0, 255 ), "Authed as %s (%llu)", oubhack->m_sUsername.c_str(), oubhack->m_llSteamID64 );

	// Attempt to find ILuaShared
	CreateInterfaceFn LuaSharedCreateInterface = (CreateInterfaceFn)GetProcAddress( GetModuleHandle( "lua_shared.dll" ), "CreateInterface" );
	PDWORD_PTR* pdwLuaShared = (PDWORD_PTR*)LuaSharedCreateInterface( LUA_SHARED_INTERFACE, NULL ); 

	// Hook required functions
	if ( pdwLuaShared != NULL ){

		g_pLuaShared = new ILuaShared( (PDWORD_PTR*)pdwLuaShared );
		h_pLuaShared = new CVMTHookManager( (PDWORD*)g_pLuaShared->GetBase() );

		h_pLuaShared->dwHookMethod( (DWORD)Hooked_CreateLuaInterface, 4 ); 
		h_pLuaShared->dwHookMethod( (DWORD)Hooked_CloseLuaInterface, 5 ); 

	}
	else {

		this->Error( "Failed to retrieve ILuaShared!\n" );

	}

}

/* ========================================
	Name : OubHack::~OnAuthed
	Returns : void
=========================================== */

OubHack::~OubHack( ){
	
	this->Log( Color( 0, 215, 0, 255 ), "Shutting down .." );

	TerminateThread( this->m_AuthThread, 0 );
	TerminateThread( this->m_RenderThread, 0 );

	this->Debug("Shut down threads");

	if (h_pLuaInterface != NULL) {
		h_pLuaInterface->UnHook();
		h_pLuaInterface->~CVMTHookManager();
	}

	this->Debug("Shut down ILuaInterface hooks");

	if ( h_pLuaShared != NULL ){
		h_pLuaShared->UnHook();
		h_pLuaShared->~CVMTHookManager();
	}

	this->Debug("Shut down ILuaShared hooks");

	if (this->Interfaces.pClient->IsInGame()){

		this->m_pCHooks->CallClose();

	}

	this->Log( Color( 0, 215, 0, 255 ), "Shut down!" );

}