|
--!native |
|
--!optimize 2 |
|
|
|
local ReplicatedStorage = game:GetService("ReplicatedStorage") |
|
local RunService = game:GetService("RunService") |
|
|
|
local RemoteFunction = ReplicatedStorage:WaitForChild("EarlyResumption") |
|
local InvokeServer = RemoteFunction.InvokeServer |
|
|
|
local function SecureYield(Function, ...) |
|
local Caller = coroutine.running() |
|
|
|
local Bindable = Instance.new("BindableEvent") -- Two separate events are required, won't work with just one |
|
local Waiter = Instance.new("BindableEvent") |
|
|
|
Bindable.Event:Once(function(...) |
|
Bindable = nil |
|
Waiter = nil |
|
|
|
coroutine.resume(Caller, ...) |
|
end) |
|
|
|
task.spawn(function(...) |
|
task.defer(coroutine.resume, coroutine.running()) |
|
|
|
if select("#", Function(...)) ~= 0 then |
|
-- Quick check to see if the function even yields |
|
-- works because we call the function, thread yields, gets resumed by coroutine.resume |
|
-- if function actually yielded, we shouldn't get any arguments back |
|
-- this will obviously get fooled by functions that return nothing |
|
error("Function was tampered with or doesn't yield") |
|
end |
|
|
|
local Results = table.pack(Waiter.Event:Wait()) -- use waiter event to get the return of InvokeServer |
|
Bindable:Fire(table.unpack(Results, 1, Results.n)) |
|
coroutine.yield(1, 2, unpack(table.create(7999, 0))) -- Required to break scripts that spawn a new thread such as Simple Spy |
|
end, ...) |
|
|
|
return coroutine.yield() |
|
end |
|
|
|
print("Performing the Early Resumption Exploit") |
|
|
|
print("Client got:", SecureYield(function() |
|
return RemoteFunction:InvokeServer(false) |
|
end)) |
|
|
|
print("Client got 2:", SecureYield(function() |
|
return RemoteFunction:InvokeServer(true) |
|
end)) |