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

local OHDetours = {};

OHDetours.MetaCache = {}; // { meta = old meta table, detours = list of detoured functions } }
OHDetours.Modes = { DETOUR_NORMAL = 1, DETOUR_NOOVERWRITE = 2 }

--
local arg = { ... }; // [ 1 ] = g
local g = arg[ 1 ];
--

// Detours a function using the metatable of the table that the function is inside.
function OHDetours.Add( tab, tabKey, detour, mode )

	if ( !OHDetours.MetaCache[ g.tostring( tab ) ] ) then 

		OHDetours.MetaCache[ g.tostring( tab ) ] = { meta = g.getmetatable( tab ), detours = { }, mode = mode };

	end

	g.setmetatable( tab, {
		__index = function( t, k )

			if ( OHDetours.MetaCache[ g.tostring( t ) ] && OHDetours.MetaCache[ g.tostring( t ) ].detours[ k ] && OHDetours.MetaCache[ g.tostring( t ) ].detours[ k ].original ) then

				return OHDetours.MetaCache[ g.tostring( t ) ].detours[ k ].detour;

			else 

				return g.rawget( t, k );

			end

		end,
		__newindex = function( t, k, v )

			if ( OHDetours.MetaCache[ g.tostring( t ) ] && OHDetours.MetaCache[ g.tostring( t ) ].detours[ k ] && OHDetours.MetaCache[ g.tostring( t ) ].detours[ k ].mode == 2 ) then

				OHDetours.MetaCache[ g.tostring( t ) ].detours[ k ].original = v;
				return true;

			else 

				g.rawset( t, k, v );

			end

		end
	} );

	// Add the detour
	OHDetours.MetaCache[ g.tostring( tab ) ].detours[ tabKey ] = { detour = detour, original = tab[ tabKey ], mode = mode };

	// __index isn't called if the index existed for some reanon, so make sure we destroy the original.
	tab[ tabKey ] = nil;

end

// Remove a detour
function OHDetours.Remove( tab, tabKey )

	if ( OHDetours.MetaCache[ g.tostring( tab ) ] ) then 

		tab[tabKey] = OHDetours.MetaCache[ g.tostring( tab ) ].detours[ tabKey ].original;
		OHDetours.MetaCache[ g.tostring( tab ) ].detours[ tabKey ] = nil;

	end

end

// Get the original function before a detour.
function OHDetours.GetOriginal( tab, tabKey )

	return OHDetours.MetaCache[ g.tostring( tab ) ] and OHDetours.MetaCache[ g.tostring( tab ) ].detours[ tabKey ].original or ( tab[ tabKey ] or nil );

end

// Get the original function before a detour.
function OHDetours.GetOriginalFromClone( func )

	for name, tab in g.pairs( OHDetours.MetaCache ) do 

		for name, data in g.pairs( tab.detours ) do 

			if ( data.original == func ) then return data.detour; end
			if ( data.detour == func ) then return data.original; end
 
		end

	end

	return func;

end

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// Protect against people overwriting the metatables

/* ========================================

=========================================== */

OHDetours.Add( _G, "getmetatable", function( tab ) 

	if ( OHDetours.MetaCache[ g.tostring( tab ) ] ) then 

		return OHDetours.MetaCache[ g.tostring( tab ) ].meta;

	else 

		return OHDetours.GetOriginal( _G, "getmetatable" )( tab );

	end

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.debug, "getmetatable", function( tab ) 

	if ( OHDetours.MetaCache[ g.tostring( tab ) ] ) then 

		return OHDetours.MetaCache[ g.tostring( tab ) ].meta;

	else 

		return OHDetours.GetOriginal( _G.debug, "getmetatable" )( tab );

	end

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G, "setmetatable", function( tab, meta ) 

	if ( OHDetours.MetaCache[ g.tostring( tab ) ] ) then 

		OHDetours.MetaCache[ g.tostring( tab ) ].meta = meta;

	else 

		return OHDetours.GetOriginal( _G, "setmetatable" )( tab, meta );

	end

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.debug, "setmetatable", function( tab, meta ) 

	if ( OHDetours.MetaCache[ g.tostring( tab ) ] ) then 

		OHDetours.MetaCache[ g.tostring( tab ) ].meta = meta;

	else 

		return OHDetours.GetOriginal( _G.debug, "setmetatable" )( tab, meta );

	end

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

// Make sure they don't use rawget/rawset on our detoured tables.

OHDetours.Add( _G, "rawset", function( tab, key, val ) 

	if ( OHDetours.MetaCache[ g.tostring( tab ) ] && g.getmetatable( tab ).__index ) then 

		return g.getmetatable( tab ).__index( tab, key );

	else 

		return OHDetours.GetOriginal( _G, "rawset" )( tab, key, val );

	end

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G, "rawget", function( tab, key ) 

	if ( OHDetours.MetaCache[ g.tostring( tab ) ] && g.getmetatable( tab ).__index ) then 

		return g.getmetatable( tab ).__index( tab, key );

	else 

		return OHDetours.GetOriginal( _G, "rawget" )( tab, key, val );

	end

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

// Other detour masking

OHDetours.Add( _G.jit.util, "funcbc", function( func, ... )

	return OHDetours.GetOriginal( _G.jit.util, "funcbc" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.jit.util, "funck", function( func, ... )

	return OHDetours.GetOriginal( _G.jit.util, "funck" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.jit.util, "funcinfo", function( func, ... )

	return OHDetours.GetOriginal( _G.jit.util, "funcinfo" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );


/* ========================================

=========================================== */

OHDetours.Add( _G.jit.util, "funcuvname", function( func, ... )

	return OHDetours.GetOriginal( _G.jit.util, "funcuvname" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );


/* ========================================

=========================================== */

OHDetours.Add( _G, "tostring", function( var, ... )

	if ( g.isfunction( var ) ) then 

		var = OHDetours.GetOriginalFromClone( var ); 

	end

	return OHDetours.GetOriginal( _G, "tostring" )( var, ... );

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.debug, "getinfo", function( func, ... )

	return OHDetours.GetOriginal( _G.debug, "getinfo" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.debug, "getupvalue", function( func, ... )

	return OHDetours.GetOriginal( _G.debug, "getupvalue" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.debug, "setupvalue", function( func, ... )

	return OHDetours.GetOriginal( _G.debug, "setupvalue" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.debug, "getlocal", function( func, ... )

	return OHDetours.GetOriginal( _G.debug, "getlocal" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );

/* ========================================

=========================================== */

OHDetours.Add( _G.debug, "setlocal", function( func, ... )

	return OHDetours.GetOriginal( _G.debug, "setlocal" )( OHDetours.GetOriginalFromClone( func ), ... );

end, OHDetours.Modes.DETOUR_NORMAL );

// Return the local table to be used in the main script
return OHDetours;