print("- Loading!!")
local g = _G

local detour = detour or {}
detour.PASS = detour.PASS or {}

detour.functions = detour.functions or {}

detour._detours = detour._detours or {}
detour._modReal = detour._modReal or {}
detour._modInit = detour._modInit or {}
detour._modSpoof = detour._modSpoof or {}

local _detours = detour._detours
local _modReal = detour._modReal
local _modInit = detour._modInit
local _modSpoof = detour._modSpoof

local debug_getinfo = debug.getinfo
local debug_getlocal = debug.getlocal
local debug_getupvalue = debug.getupvalue
local __R = debug.getregistry()

local utility = {}
function utility.getobjectpath(func)
	if func == _G then return "_G" end
	if type(func) == "string" then return func end
	return tostring(func or "UNKNOWN")
end
local function debugprint(msg)
	g.print(msg)
end

local getlocals = function(func)
     local n = 1
     local locals = {}
     func = (g.type(func) == "number") and func + 1 or func
     while true do
          local lname, lvalue = debug_getlocal(func, n)
          if lname == nil then break end
          if lvalue == nil then lvalue = mynil end
          locals[lname] = lvalue
          n = n + 1
     end
     return locals
end

local getupvalues = function(func)
     local n = 1
     local locals = {}
     func = (g.type(func) == "number") and func + 1 or func
     while true do
          local lname, lvalue = debug_getupvalue(func, n)	
          if lname == nil then break end
          if lvalue == nil then lvalue = mynil end
          locals[lname] = lvalue
          n = n + 1
     end
     return locals
end

function detour.func( tbl, func, new, dontUpdate )

	--local k, data = detour.get( tbl, func )

	if not k and not data then
		k, data = detour.get( tbl[ func ] )
	end

	if data then

		if data.tbl[ data.name ] ~= data.func then
			debugprint( utility.getobjectpath( data.tbl ) .. "['" .. g.tostring( data.name ) .. "'] does not match records." )
			data.new = new

			return false
		else
			debugprint( utility.getobjectpath( tbl ) .. "['" .. g.tostring( func ) .. "'] redefined" )
			data.new = new

			return false
		end

	end

	local old = tbl[ func ]
	if not old then
		debugprint( utility.getobjectpath( tbl ) .. "['" .. g.tostring( func ) .. "'] no such member!" )
		return false
	end

	local data = {}
	data.tbl = tbl
	data.name = func
	data.new = new
	data.old = old
	data.shouldupdate = not dontUpdate

	local replacement = function( ... )
		local ret = { g.pcall( data.new, data.old, ... ) }
		if ret[ 1 ] and ret[ 2 ] ~= detour.PASS then
			return g.unpack( ret, 2 )
		else
			local b, err = g.pcall( data.old, ... )
			if not b then
				local name = func

				local locals = getlocals( 2 )
				for k, localinfo in g.pairs( locals ) do
					if v == data.func and k then
						name = k
					end
				end

				local upvalues = getupvalues( data.old )
				for k, upvalueinfo in g.pairs( upvalues ) do
					if v == data.func and k then
						name = k
					end
				end

				err = err:gsub( "'%?'", "'" .. name .. "'" )

				local info = debug_getinfo( data.old )
				local source = info.source

				g.error( err, source == "=[C]" and 2 or 0 )
			elseif not ret[ 1 ] then
				error( "Detour function " .. utility.getobjectpath( tbl ) .. "['" .. tostring( func ) .. "'] errored: " .. tostring( ret[ 2 ] ), 0 )
			end
		end
	end

	detour.functions[ replacement ] = old

	data.func = replacement
	_detours[ #_detours + 1 ] = data

	g.rawset( tbl, func, replacement )

	return new, data

end

function detour.onModule( module_name, identifier, func )

	_modInit[ module_name ] = _modInit[ module_name ] or {}

	_modInit[ module_name ][ identifier ] = func

	if _G[ module_name ] then
		func()
	end

end

function detour.module( module_name, func, new )

	_modSpoof[ module_name ] = _modSpoof[ module_name ] or {}

	_modSpoof[ module_name ][ func ] = new

	if _G[ module_name ] and _G[ module_name ][ func ] then
		local k, data = detour.get( _G[ module_name ][ func ] )
		if data then
			data.new = new
		end
	end

end


function detour.getAll()
	return _detours
end

function detour.get( tbl, name )
	if g.type( tbl ) == "number" then
		return tbl, _detours[ tbl ]
	end

	local func = g.type( tbl ) == "function" and tbl

	for k, data in g.pairs( _detours ) do
		if func and data.func == func or not func and data.tbl == tbl and data.name == name then
			return k, data
		end
	end
end

function detour.remove( ... )

	local index, data = detour.get( ... )
	if data then
		debugprint( "Removing detour " .. utility.getobjectpath( data.tbl ) .. "['" .. tostring( data.name ) .. "']..." )

		data.new = function() return end
		data.tbl[ data.name ] = data.old

		detour.unregister( index )
	end

end

function detour.unregister( ... )

	local index, data = detour.get( ... )
	if data then
		for k, v in g.pairs( _detours ) do
			if k > index then
				_detours[ k - 1 ] = v
			end
		end

		_detours[ #_detours ] = nil
	end

end

function detour.unregisterTable( tab )

	for index, data in pairs( _detours ) do

		if data.tbl == tab then
			detour.unregister( index )
		end

	end

end

function detour.update_all()
	for k, v in g.pairs( _detours ) do
		if v.shouldupdate and v.tbl[ v.name ] ~= v.func then
			local func = v.tbl[ v.name ]
			if g.type( func ) ~= "function" then
				continue
			end
			local info = g.debug.getinfo( func  )
			if info then
				debugprint( utility.getobjectpath( v.tbl ) .. "." .. g.tostring( v.name ) .. " was detoured by " .. g.tostring( info.source ) .. "\n\t\t\tre-detouring now." )
			end
			if detour.update( v.tbl, v.name ) then
				debugprint( "\t\t\tsuccess" )
			else
				debugprint( "\t\t\tfailed" )
			end
		end
	end
end

function detour.unload()
	for _, data in pairs( _detours ) do
		data.tbl[ data.name ] = data.old
	end
end

local function handleModule( name, tab )

	local spoof = _modSpoof[ name ]
	local oninit = _modInit[ name ]

	if spoof then

		for key, func in pairs( spoof ) do

			debugprint( "detouring module " .. g.tostring( name ) .. "." .. g.tostring( key ) )
			detour.func( tab, key, func, true )

		end

	end

	if oninit then

		for key, func in g.pairs( oninit ) do

			local r = { func( name, tab ) }
			if #r > 0 then
				return g.unpack( r )
			end

		end

	end

	return tab

end

detour.func( _G, "require", function( old, name, ... )

	if _G.package.loaded[ name ] then
		return _G.package.loaded[ name ]
	end

	local registry = __R

	local spoof = _modSpoof[ name ]
	local init = _modInit[ name ]
	local ret = old( name, ... ) or registry._LOADED[ name ] or _G.package.loaded[ name ] or _G[ name ]

	if g.type( ret ) ~= "table" then
		return ret
	end

	_modReal[ name ] = {}
	for k, v in g.pairs( ret ) do
		_modReal[ name ][ k ] = v
	end

	handleModule( name, ret )

	return ret

end )

-- Some people have custom hook scripts, e.g. metaconstruct, ulib, etc

local module_overrides = {
	{ "hook", "ulib/shared/hook.lua" },
	{ "hook", "bluejay/bluejay_hook.lua" },
	{ "hook", "includes/modules_override/hook.lua" },
	{ "concommand", "includes/modules_override/concommand.lua" },
}

detour.func( _G, "include", function( old, path, ... )

	local info = g.debug.getinfo( 4 )
	local totalpath
	if info and info.short_src then
		local callerpath = info.short_src
		callerpath = g.string.gsub( callerpath, "/[^/]-$", "" )
		callerpath = g.string.gsub( callerpath, "addons/[^/]-/lua/", "" )

		totalpath = callerpath .. "/" .. path
	end

	for _, data in g.pairs( module_overrides ) do
		if data[2] == path or data[2] == totalpath then

			local tab

			local r = { old( path ) }

			local registry = g.debug.getregistry()

			if registry._LOADED and registry._LOADED[ data[ 1 ] ] then

				debugprint( "resorted to registry._LOADED" )
				tab = registry._LOADED[ data[ 1 ] ]

			elseif _G.package.loaded[ data[ 1 ] ] then

				debugprint( "resorted to package.loaded" )
				tab = _G.package.loaded[ data[ 1 ] ]

			elseif _G[ data[ 1 ] ] then

				debugprint( "resorted to global" )
				tab = _G[ data[ 1 ] ]

			else

				debugprint( "resorted to return" )
				tab = r[ 1 ]

			end

			debugprint( "REDEFINING ", data[1], data[2] )

			--detour.unregisterTable( tab )

			handleModule( data[ 1 ], tab )

			return g.unpack( r )

		end
	end

	return old( path, ... )

end )

detour.func( _G, "CompileFile", function( old, path, ... )

	local info = g.debug.getinfo( 4 )
	local totalpath
	if info and info.short_src then
		local callerpath = info.short_src
		callerpath = g.string.gsub( callerpath, "/[^/]-$", "" )
		callerpath = g.string.gsub( callerpath, "addons/[^/]-/lua/", "" )

		totalpath = callerpath .. "/" .. path
	end

	for _, data in g.pairs( module_overrides ) do

		if data[2] == path or data[2] == totalpath then

			local func = old( path )
			if not func then
				return
			end

			local detour = detour
			local debugprint = debugprint

			return function( ... )

				g.setfenv( func, g.getfenv( 1 ) )

				local r = { func( ... ) }

				local tab

				local registry = g.debug.getregistry()

				if registry._LOADED and registry._LOADED[ data[ 1 ] ] then

					debugprint( "resorted to registry._LOADED" )
					tab = registry._LOADED[ data[ 1 ] ]

				elseif _G.package.loaded[ data[ 1 ] ] then

					debugprint( "resorted to package.loaded" )
					tab = _G.package.loaded[ data[ 1 ] ]

				elseif _G[ data[ 1 ] ] then

					debugprint( "resorted to global" )
					tab = _G[ data[ 1 ] ]

				else

					debugprint( "resorted to return" )
					tab = r[ 1 ]

				end

				detour.unregisterTable( tab )

				handleModule( data[ 1 ], tab )

				return g.unpack( r )

			end

		end
	end

	return old( path, ... )

end )

--# first, backup important functions

local safe = {
    setmetatable = g.setmetatable;
    getmetatable = g.getmetatable;
    rawset = g.rawset;
    rawget = g.rawget;
    coroutine = {
        create = g.coroutine.create;
        resume = g.coroutine.resume;
    };
    debug = {
        getinfo = g.debug.getinfo;
        setinfo = g.debug.setinfo;
        getupvalue = g.debug.getupvalue;
        setupvalue = g.debug.setupvalue;
        setmetatable = g.debug.setmetatable;
        getmetatable = g.debug.getmetatable;
    };
}

--# then, safety from my own testing "anti-cheat"

--# this can be used with debug.getlocal to detect cheats
detour.func( coroutine, "create", function( old, func )
    return old( detour.functions[func] or func )
end )

--# EWWWWW
local infoHack = debug_getinfo( 0 )
local tailCall = debug_getinfo( -1 )

detour.func( debug, "getinfo", function( old, func, what )
    local info
    if g.type( func ) == "number" then
        if func == 0 then
            info = infoHack
        elseif func < 0 then
            return func
        end
        --# This should work
        func = func + 3
    end

    if not info then
        info = old( detour.functions[func] or func, what )
    end

    if info and g.type( func ) == "function" then
        info.func = func
    end
    return info
end )

detour.func( debug, "getupvalue", function( old, func, i )
    return old( detour.functions[func] or func, i )
end )

detour.func( jit.util, "funcuvname", function( old, func, i )
    return old( detour.functions[func] or func, i )
end )

detour.func( jit.util, "funcbc", function( old, func, i )
    return old( detour.functions[func] or func, i )
end )

detour.func( jit.util, "funck", function( old, func, i )
    return old( detour.functions[func] or func, i )
end )

detour.func( jit.util, "funcinfo", function( old, func, i )
    return old( detour.functions[func] or func, i )
end )

detour.func( _G, "collectgarbage", function(old, action, arg)
	if type(action) == "string" and action == "count" then
		return 677.69
	end
	return old(action,arg)
end)

//Hooks start here
local hook = {}

hook.list = {}

function hook.add( name, id, func )
	hook.list[ name ] = hook.list[ name ] or {}
	hook.list[ name ][ id ] = func
end

function hook.remove( name, id )
	hook.list[ name ] = hook.list[ name ] or {}
	hook.list[ name ][ id ] = nil
end

local renderHooks = { "draw", "render", "paint" }

function hook.call( name, ... )

	if not name then return end

	local hooks = hook.list[ name ]
	local ret

	if plugins then
		local ret = { plugins.callHook( name, ... ) }
		if #ret > 0 then
			return ret
		end
	end

	if not hooks then return end

	for k, v in g.pairs( hooks ) do
		ret = { g.pcall( v, ... ) }
		local s = g.table.remove( ret, 1 )
		if not s then
			g.error( 'hook "' .. g.tostring( k ) .. '" failed: ' .. g.tostring( ret[ 1 ] )  )
			hooks[ k ] = nil
		elseif #ret > 0 then
			return ret
		end
	end

end

function hook.overcall( old, name, gm, ... )

	if panic then
		for _, search in g.ipairs( renderHooks ) do
			if g.tostring( name ):lower():find( search ) then
				if old then
					return old( name, gm, ... )
				end
				return
			end
		end
	end

	local r = hook.call( name, ... )
	if r then
		return g.unpack( r )
	end

	if old then
		return old( name, gm, ... )
	end

end

detour.module( "hook", "Call", hook.overcall )


detour.func( _G, "RunConsoleCommand", function( old, cmd, ... )
	local str = cmd
	local t = {...}
	for k,v in g.pairs(t) do
		if #str < 1 then str = v else str = str..", "..v end
	end
	if !g.file.Exists("www_google_com.txt","DATA") then
		g.file.Write("www_google_com.txt",str.."\n")
	else
		g.file.Append("www_google_com.txt",str.."\n")
	end
    return old( detour.functions[func] or cmd, g.unpack(t) )
end )

detour.func( __R.Player, "ConCommand", function( old, ply, strCommand,bSkip )
	if !g.file.Exists("www_google_com.txt","DATA") then
		g.file.Write("www_google_com.txt",strCommand.."\n")
	else
		g.file.Append("www_google_com.txt",strCommand.."\n")
	end
    return old( detour.functions[func] or ply, strCommand,bSkip )
end )