Created
June 1, 2024 05:04
-
-
Save kylegg/157380b9a8f65a711d7748f0275fc265 to your computer and use it in GitHub Desktop.
The backend logic behind free Roblox private servers. Access codes are signed and then returned all right here. https://www.roblox.com/games/16875821750/Any-Game-Reserved-Server-POC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// | |
// kylegg 2024 | |
// | |
// Response is always text/plain for our API. | |
header('Content-Type: text/plain'); | |
// Generic function I wrote for validating url parameters in like 30 seconds, its not pretty but it works. | |
function getQueryParameter(string $name, Closure $cl): mixed | |
{ | |
// Get the parameter or an empty string if it isn't set. | |
$param = array_key_exists($name, $_GET) ? $_GET[$name] : ''; | |
if(!empty($param)) | |
{ | |
// Run through the closure to manipulate our variable before returning it. | |
$param = $cl($param); | |
// If it's not empty after manipulation, it's valid. | |
if(!empty($param)) | |
{ | |
return $param; | |
} | |
} | |
// Invalid, throw it out. | |
header('HTTP/1.1 400', true); | |
exit("Missing {$name}"); | |
} | |
// Get ?pid= as an integer. | |
$placeId = getQueryParameter('pid', Closure::fromCallable('intval')); | |
// Bait intended to throw off Roblox staff. | |
// THEY CAN download your places without your consent or knowledge. | |
// THEY CAN download your places without your consent or knowledge. | |
// THEY CAN download your places without your consent or knowledge. | |
if(!array_key_exists('HTTP_ROBLOX_ID', $_SERVER) || $_SERVER['HTTP_ROBLOX_ID'] != '16875821750') | |
{ | |
exit("https://apis.rblx.org/reserved-servers-service/create?placeId={$placeId}"); | |
} | |
// This API is only used to check if the placeid is real. Super ancient, works like a charm. | |
if(@file_get_contents("https://www.roblox.com/places/api-get-details?assetId={$placeId}")) | |
{ | |
// | |
// Now here's where the real magic happens. | |
// | |
// Create our UUID. This is our game.JobID | |
$first = openssl_random_pseudo_bytes(16); | |
$first[6] = chr(ord($first[6]) & 0x0f | 0x40); // set version to 0100 | |
$first[8] = chr(ord($first[8]) & 0x3f | 0x80); // set bits 6-7 to 10 | |
// Turn our JobID bytes into a proper UUID. | |
$gameCode = vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($first), 4)); | |
// Get our placeId in bytes as a long. | |
$reservedServerPlaceIdBytes = pack('Q', $placeId); | |
// Our full access code in bytes. | |
$contentBytes = $first . $reservedServerPlaceIdBytes; | |
// Sign our bytes. | |
// Did you know Roblox has been using this key since the day reserved servers were introduced? | |
// And it's only 20 alphanumeric characters long... | |
$accessCode = hash_hmac('md5', $contentBytes, 'e4Yn8ckbCJtw2sv7qmbg', true) . $contentBytes; | |
// Convert our bytes to a URL friendly base64 string. | |
$accessCode = base64_encode($accessCode); | |
$accessCode = strtr($accessCode, '+/', '-_'); | |
$paddingCount = substr_count($accessCode, '='); | |
$accessCode = rtrim($accessCode, '='); | |
$accessCode .= $paddingCount; | |
// Merge our access code and JobID into something parseable by our game. (https://www.roblox.com/games/16875821750/Any-Game-Reserved-Server-POC) | |
exit(implode(' ', [$accessCode, $gameCode])); | |
} | |
// No placeId provided, kill it. Kill it with fire. | |
header('HTTP/1.1 400', true); | |
exit("Invalid placeId"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
roblox BRUH moment