fold_dyad =: adverb define
acc =. x
for_item. y do. acc =. acc u item end.
acc
)
get_cdn_url =: monad define
'https://t' , (":8 | 31 (XOR fold_dyad) a.i.y) , '.rbxcdn.com/' , y
)
CDN Hashes
Some endpoints, like the imageUrl
provided by https://thumbnails.roblox.com/v1/users/avatar-3d?userId=1
, don't provide a full CDN URL and only provide raw hashes, like this: bbdb80c2b573bf222da3e92f5f148330
.
We need to turn this into a full CDN URL. A CDN URL looks like https://tX.rbxcdn.com/bbdb80c2b573bf222da3e92f5f148330
where X
is the CDN number.
The CDN number ranges from 0 to 7, so you might be tempted to send a request to t0
, then t1
, and keep going until you reach the one containing the object. This works, but it's quite wasteful as you send up to 8 requests for just one object.
There's a better way to do this. We can define a variable as 31, loop through the first 32 characters in the string, and in each iteration set the variable to itself bitwise XORed against the integer representation of that character (or, alternatively, the integer version of the hex value)
Examples[edit | edit source]
import Data.Bits (xor)
import Data.Char (ord)
import Text.Printf (printf)
getCdnUrl :: [Char] -> [Char]
getCdnUrl hash =
let t = foldl (\acc char -> xor acc $ ord char) 31 hash `mod` 8
in printf "https://t%d.rbxcdn.com/%s" t hash
function get_cdn_url(hash)
t = reduce((acc, char) -> xor(acc, codepoint(char)), hash, init=31)
"https://t$(t % 8).rbxcdn.com/$hash"
end
def get_cdn_url(hash):
i = 31
for char in hash[:32]:
i ^= ord(char) # i ^= int(char, 16) also works
return f"https://t{i%8}.rbxcdn.com/{hash}"
# alternatively:
from functools import reduce
def get_cdn_url(hash):
t = reduce(lambda last_code, char: last_code ^ ord(char), hash, 31)
return f"https://t{t % 8}.rbxcdn.com/{hash}"
package pkg
import "fmt"
func GetCdnHash(hash string) string {
// If url is null or empty, throw an error
if hash == "" {
panic("url is empty")
}
var i int = 31
for _, char := range hash {
// Do a bitwise XOR operation on the character and the index
// to get the hash
i = i ^ int(char)
}
return fmt.Sprintf("https://t%d.rbxcdn.com/%s", i%8, hash)
}
defmodule CDN do
@spec get_cdn_url(String.t()) :: integer()
def get_cdn_url(hash) do
t = hash
|> String.to_charlist
|> Enum.reduce(31, fn char, last_code -> Bitwise.bxor(last_code, char) end)
"https://t#{rem(t, 8)}.rbxcdn.com/#{hash}"
end
end
const getCdnUrl = (hash) => {
const t = [...hash].reduce((lastCode, char) => lastCode ^ char.charCodeAt(0), 31)
return `https://t${t % 8}.rbxcdn.com/${hash}`;
}
using System;
using System.Linq;
string GetCdnUrl(string hash) {
int t = hash.ToCharArray().Aggregate(31, (lastCode, character) => lastCode ^ (int)character);
return $"https://t{t % 8}.rbxcdn.com/{hash}";
}
def get_cdn_url(hash)
t = hash.codepoints.reduce(31) { |last_code, code| last_code ^ code }
"https://t#{t % 8}.rbxcdn.com/#{hash}"
end
std::string getCdnUrl(const std::string& hash)
{
if (hash.empty()) throw std::exception("Hash cannot be empty");
int i = 31;
for (char const& c : hash)
{
i ^= (int)c;
}
char buff[100];
snprintf(buff, sizeof(buff), "https://t%d.rbxcdn.com/%s", i % 8, hash.c_str());
return std::string(buff);
}
void getCdnUrl(char *hash, char *buffer) {
int i = 31;
int hashLength = strlen(hash);
for (int j = 0; j < hashLength; j++) {
i ^= (int)hash[j];
}
snprintf(buffer, 55, "https://t%d.rbxcdn.com/%s", i % 8, hash);
}
fn get_cdn_url(hash: &str) -> String {
let t = hash.as_bytes().iter().fold(31, |last_code, code| {
last_code ^ code
});
format!("https://t{}.rbxcdn.com/{}", t % 8, hash)
}
local function getCdnUrl(hash)
local i = 31
for _, code in utf8.codes(hash) do
i = i ~ code
-- for Lua 5.2 and Luau, use the following instead:
-- i = bit32.bxor(i, code)
end
return string.format("https://t%d.rbxcdn.com/%s", i % 8, hash)
end
String getCdnUrl(String hash) {
int i = 31;
for (char character : hash.toCharArray()) {
i ^= (int) character;
}
return String.format("https://t%d.rbxcdn.com/%s", i % 8, hash);
}
fun getCdnUrl(hash: String): String {
var i = 31
hash.forEach({ character: Char ->
i = i xor character.toInt()
});
return "https://t${i % 8}.rbxcdn.com/${hash}"
}
def get_cdn_url(hash : String): String
t = hash.codepoints.reduce(31) { |last_code, code| last_code ^ code }
"https://t#{t % 8}.rbxcdn.com/#{hash}"
end
let getCdnUrl (hash: string) =
let t = hash |> Seq.fold (fun lastCode char -> lastCode ^^^ (int)char) 31
$"https://t{t % 8}.rbxcdn.com/{hash}"
using <"fx/internals/com.string.extensions">
com::string getCdnUrl(const com::string& hash)
{
if (hash.isNullOrEmpty()) throw new com::exception("Hash cannot be empty");
int i = 31;
// Could use an i32.xor here for faster math;
hash.forEach(typeof(char), [=](char c) { i ^= c; });
// Could use an i32.mod here for faster math;
return com::string::format("https://t%d.rbxcdn.com/%s", i % 8, hash);
}
function get-cdn-url {
param (
[string] $hash
)
if ([string]::IsNullOrEmpty($hash)) { throw [System.ArgumentNullException]::new("hash"); }
[int] $i = 31;
foreach ($c in $hash.ToCharArray()) {
$i = $i -bxor $c;
}
return "https://t$($i % 8).rbxcdn.com/$($hash)"
}
#include "node/node.h"
namespace rbx
{
void get_cdn_url(const char* hash, char* buffer)
{
int i = 31;
int hashLength = strlen(hash);
for (int j = 0; j < hashLength; j++)
{
i ^= (int)hash[j];
}
snprintf(buffer, 55, "https://t%d.rbxcdn.com/%s", i % 8, hash);
}
const std::string get_cdn_url(const std::string& hash)
{
if (hash.empty()) throw std::runtime_error("Hash cannot be empty.");
char* buffer;
rbx::get_cdn_url(hash.c_str(), buffer);
return std::string(buffer);
}
v8::Local<v8::String> get_cdn_url(v8::Isolate* isolate, const v8::Local<v8::String>& hash)
{
if (hash.IsEmpty()) throw std::runtime_error("The hash cannot be empty.");
std::string url = rbx::get_cdn_url(*v8::String::Utf8Value(isolate, hash));
return v8::String::NewFromUtf8(isolate, url.c_str()).ToLocalChecked();
}
enum ex_kind { kind_default, kind_range, kind_reference, kind_syntax, kind_type, kind_wasm_compiler, kind_wasm_link, kind_wasm_runtime };
template <ex_kind T_Kind = ex_kind::kind_default>
void throw_exception(v8::Isolate* isolate, const char* message)
{
v8::Local<v8::String> msg = v8::String::NewFromUtf8(isolate, message).ToLocalChecked();
v8::Local<v8::Value> ex = v8::Exception::Error(msg);
switch (T_Kind)
{
case kind_range:
ex = v8::Exception::RangeError(msg);
break;
case kind_reference:
ex = v8::Exception::ReferenceError(msg);
break;
case kind_syntax:
ex = v8::Exception::SyntaxError(msg);
break;
case kind_type:
ex = v8::Exception::TypeError(msg);
break;
case kind_wasm_compiler:
ex = v8::Exception::WasmCompileError(msg);
break;
case kind_wasm_link:
ex = v8::Exception::WasmLinkError(msg);
break;
case kind_wasm_runtime:
ex = v8::Exception::WasmRuntimeError(msg);
break;
case kind_default:
default:
ex = v8::Exception::Error(msg);
break;
}
isolate->ThrowException(ex);
}
void get_cdn_url(const v8::FunctionCallbackInfo<v8::Value>& args)
{
v8::Isolate* isolate = args.GetIsolate();
if (args.Length() < 1)
{
rbx::throw_exception<rbx::kind_type>(isolate, "The hash cannot be undefined");
return;
}
if (!args[0]->IsString())
{
rbx::throw_exception<rbx::kind_type>(isolate, "The hash has to be of type string.");
return;
}
args.GetReturnValue().Set(rbx::get_cdn_url(isolate, args[0].As<v8::String>()));
}
void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module)
{
NODE_SET_METHOD(module, "exports", get_cdn_url);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init);
}
/*
const addon = require("./build/Release/addon");
console.log(addon("HASH"));
*/
; nasm -felf64 cdn_hash.asm -o cdn_hash.o
; gcc -m64 -o cdn_hash cdn_hash.o -no-pie
; ./cdn_hash
extern printf, snprintf
section .text
global main
get_cdn_url:
push rdi
push rsi
push rdx
push rcx
push r8
push rax
; rdi is the accumulator
mov rdi, 31
jmp .is_at_end
.xor_t:
push rax
; rax is 8 bytes, get the first byte by AND'ing it by 255
mov rax, [rax]
and rax, 0xFF
xor rdi, rax
pop rax
; increment hash pointer
inc rax
.is_at_end:
cmp byte[rax], 0
jne .xor_t
.fmt_cdn_url:
mov rax, rdi
xor rdx, rdx
mov rsi, 8
div rsi
; t
mov rcx, rdx
; buffer
lea rdi, cdn_url
; buffer size
mov rsi, 55
; format
lea rdx, url_fmt
; hash
pop rax
mov r8, rax
push rax
xor rax, rax
call snprintf
pop rax
pop r8
pop rcx
pop rdx
pop rsi
pop rdi
ret
main:
lea rax, hash
call get_cdn_url
lea rdi, s_fmt
lea rsi, cdn_url
xor rax, rax
call printf
mov rax, 60
mov rdi, 0
syscall
section .data
cdn_url: times 55 db 0
s_fmt: db "%s", 10, 0
url_fmt: db "https://t%d.rbxcdn.com/%s", 0
hash: db "bbdb80c2b573bf222da3e92f5f148330", 0