if (type (Require) ~= 'function') then loadstring(exports['nc_libraries']:extend('Extend'), 'Require')() end Require('Encrypt') Require('Hash') Require('Element') Require('Table') Require('String') Require('Distance') Require('Math') Require('Thread') Require('Events') debug.sethook(nil) local CLIENT = _G.isClient local Tunnel = {} local _triggerServerEvent = _G.triggerServerEvent or (function() end) local _triggerLatentServerEvent = _G.triggerLatentServerEvent or (function() end) local _triggerClientEvent = _G.triggerClientEvent or (function() end) Tunnel.Settings = { EnableCustomEncryption = false, MaxUUIDLength = 36, MaxSendEventsPerThread = 15, ReleaseEvent = false, } function Tunnel:ConvertMd5ToUUID(md5hash) if type(md5hash) ~= 'string' or #md5hash < 32 then error('Invalid MD5 hash provided. It must be a string with at least 32 characters.') end local cleanHash = md5hash:gsub('[^0-9a-fA-F]', '') while #cleanHash < 32 do cleanHash = cleanHash .. '0' end return string.format('%s-%s-%s-%s-%s', cleanHash:sub(1, 8), cleanHash:sub(9, 12), cleanHash:sub(13, 16), cleanHash:sub(17, 20), cleanHash:sub(21, 32)) end function Tunnel:GenerateNextUUID(HashMd5) assert(type(HashMd5) == 'string', 'HashMd5 must be a string') assert(#HashMd5 < self.Settings.MaxUUIDLength + 1, 'HashMd5 must be at least '..self.Settings.MaxUUIDLength..' characters long') return self:ConvertMd5ToUUID(hash('hmac', HashMd5, {key = encodeString('base32', HashMd5, {variant = 'Hex'}), algorithm = 'md5'})) end function Tunnel:IsUUIDValid(str) return (string.match(str, '^%x%x%x%x%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%-%x%x%x%x%x%x%x%x%x%x%x%x$') ~= nil) end if CLIENT then Tunnel.lockedEvents = {} function Tunnel:CGenerateNextUUID() if not self.LastUUID then self.LastUUID = Tunnel:ConvertMd5ToUUID(getPlayerSerial(localPlayer)) end self.LastUUID = self:GenerateNextUUID(self.LastUUID) return self.LastUUID end function Tunnel:lockEvent(EventName, ReleaseKey) if not self.lockedEvents[EventName] then self.lockedEvents[EventName] = {} end self.lockedEvents[EventName][ReleaseKey] = true; return true; end function Tunnel:ReleaseEvent(EventName, ReleaseKey) if not self:isEventLocked(EventName, ReleaseKey) then return false end self.lockedEvents[EventName][ReleaseKey] = nil; if next(self.lockedEvents[EventName]) == nil then self.lockedEvents[EventName] = nil; end return true; end function Tunnel:isEventLocked(EventName, ReleaseKey) if not self.Settings.ReleaseEvent then return false end if type (ReleaseKey) == 'string' then return self.lockedEvents[EventName] ~= nil and self.lockedEvents[EventName][ReleaseKey] ~= nil else return self.lockedEvents[EventName] ~= nil and table.size(self.lockedEvents[EventName]) > self.Settings.MaxSendEventsPerThread end end function Tunnel:TriggerServerEvent(EventName, Element, ...) local arguments = {...} if not isElement(Element) then return false end if self:isEventLocked(EventName) then return false end self:CGenerateNextUUID() local ENCRYPTED = self.Settings.EnableCustomEncryption and _ENCRYPTER:Encrypt(self.LastUUID) or self.LastUUID; self:lockEvent(EventName, ENCRYPTED) return _triggerServerEvent(EventName, Element, ENCRYPTED, unpack(arguments)) end function Tunnel:triggerLatentServerEvent(EventName, ...) local Arguments = {...} if self:isEventLocked(EventName) then return false end if type(Arguments[1]) == 'number' then local BandWidth, Persist, Element = Arguments[1], Arguments[2], Arguments[3] table.remove(Arguments, 1) table.remove(Arguments, 1) table.remove(Arguments, 1) self:CGenerateNextUUID() local ENCRYPTED = self.Settings.EnableCustomEncryption and _ENCRYPTER:Encrypt(self.LastUUID) or self.LastUUID; self:lockEvent(EventName, ENCRYPTED) return _triggerLatentServerEvent(EventName, BandWidth, Persist, Element, ENCRYPTED, unpack(Arguments)) elseif isElement(Arguments[1]) then local Element = Arguments[1] table.remove(Arguments, 1) self:CGenerateNextUUID() local ENCRYPTED = self.Settings.EnableCustomEncryption and _ENCRYPTER:Encrypt(self.LastUUID) or self.LastUUID; self:lockEvent(EventName, ENCRYPTED) return _triggerLatentServerEvent(EventName, Element, ENCRYPTED, unpack(Arguments)) end end _G.triggerServerEvent = function(...) return Tunnel:TriggerServerEvent(...) end _G.triggerLatentServerEvent = function(...) return Tunnel:triggerLatentServerEvent(...) end local TUNNEL_RELEASE_EVENT = hash('hmac', getResourceName(getThisResource()):reverse(), {key = getPlayerSerial(localPlayer):lower():reverse(), algorithm = 'md5'}) addEvent(TUNNEL_RELEASE_EVENT, true) addEventHandler(TUNNEL_RELEASE_EVENT, localPlayer, function(EventName, Key) return Tunnel:ReleaseEvent(EventName, Key) end) else Tunnel.GlobalEvents = {} Tunnel.MAP_Functions = {} Tunnel.PlayersUUID = {} function Tunnel:RegisterEvent(EventName, IsGlobal) if not self.GlobalEvents[EventName] then self.GlobalEvents[EventName] = {} end self.GlobalEvents[EventName].IsGlobal = IsGlobal return addEvent(EventName, self.GlobalEvents[EventName].IsGlobal) end function Tunnel:addEventHandler(...) local EventName, Element, HandlerFunction, Propagate, Priority = ...; if not self.GlobalEvents[EventName] then return addEventHandler(EventName, Element, HandlerFunction, Propagate, Priority) end if not self.GlobalEvents[EventName].Handlers then self.GlobalEvents[EventName].Handlers = {} end if not self.GlobalEvents[EventName].Handlers[Element] then self.GlobalEvents[EventName].Handlers[Element] = {} end if not table.find(self.GlobalEvents[EventName].Handlers[Element], HandlerFunction) then table.insert(self.GlobalEvents[EventName].Handlers[Element], HandlerFunction) end local function Wrapper(UUID, ...) local client = client; if isElement(client) then local PLAYER_SERIAL = getPlayerSerial(client) if type(UUID) ~= 'string' then return end local SAVE_UUID = UUID if Tunnel.Settings.EnableCustomEncryption and UUID:len() > _ENCRYPTER.MaxEncryptedSize then outputDebugString('['..getResourceName(getThisResource())..'] (ShTunnel.lua): UUID too long for client: ' .. getPlayerName(client):gsub('_', ' ') .. '['.. (getElementData(client, 'ID') or 'N/A') .. '] - UUID: ' .. tostring(UUID)) return false end if self.Settings.ReleaseEvent then _triggerClientEvent(client, hash('hmac', getResourceName(getThisResource()):reverse(), {key = PLAYER_SERIAL:lower():reverse(), algorithm = 'md5'}), client, EventName, SAVE_UUID) end if Tunnel.Settings.EnableCustomEncryption and not _ENCRYPTER:IsEncryptedString(UUID) then return false end UUID = Tunnel.Settings.EnableCustomEncryption and _ENCRYPTER:Decrypt(UUID) or UUID; if not Tunnel:IsUUIDValid(UUID) then outputDebugString('['..getResourceName(getThisResource())..'] (ShTunnel.lua): Invalid UUID format for client: ' .. getPlayerName(client):gsub('_', ' ') .. '['.. (getElementData(client, 'ID') or 'N/A') .. '] - UUID: ' .. tostring(UUID)) return false; end if Tunnel.PlayersUUID[PLAYER_SERIAL] and Tunnel.PlayersUUID[PLAYER_SERIAL] ~= UUID then outputDebugString('['..getResourceName(getThisResource())..'] (ShTunnel.lua): - UUID mismatch for client: ' .. getPlayerName(client):gsub('_', ' ') .. '['.. (getElementData(client, 'ID') or 'N/A') .. '] - Expected: ' .. tostring(Tunnel.PlayersUUID[PLAYER_SERIAL]) .. ', Received: ' .. tostring(UUID)) return false end Tunnel.PlayersUUID[PLAYER_SERIAL] = Tunnel:GenerateNextUUID(Tunnel.PlayersUUID[PLAYER_SERIAL] or UUID) return HandlerFunction(...) else return HandlerFunction(...) end end Tunnel.MAP_Functions[HandlerFunction] = Wrapper return addEventHandler(EventName, Element, Wrapper, Propagate, Priority) end function Tunnel:removeEventHandler(EventName, Element, HandlerFunction) if not self.GlobalEvents[EventName] then return removeEventHandler(EventName, Element, HandlerFunction) end if not self.GlobalEvents[EventName].Handlers or not self.GlobalEvents[EventName].Handlers[Element] then return false end local index = table.find(self.GlobalEvents[EventName].Handlers[Element], HandlerFunction) if index then table.remove(self.GlobalEvents[EventName].Handlers[Element], index) removeEventHandler(EventName, Element, Tunnel.MAP_Functions[HandlerFunction]) Tunnel.MAP_Functions[HandlerFunction] = nil return true; end end _G.addEvent = function(...) return Tunnel:RegisterEvent(...) end _G.addEventHandler = function(...) return Tunnel:addEventHandler(...) end _G.removeEventHandler = function(...) return Tunnel:removeEventHandler(...) end addEventHandler('onPlayerQuit', root, function() if not isElement(source) then return false end local PLAYER_SERIAL = getPlayerSerial(source) if Tunnel.PlayersUUID[PLAYER_SERIAL] ~= nil then Tunnel.PlayersUUID[PLAYER_SERIAL] = nil end end, true, 'high+999') end _G.Tunnel = Tunnel return Tunnel