Skip to content

Instantly share code, notes, and snippets.

@EastArctica
Last active February 3, 2026 20:10
Show Gist options
  • Select an option

  • Save EastArctica/84baf99dfab7b5db9fc733793b0f4252 to your computer and use it in GitHub Desktop.

Select an option

Save EastArctica/84baf99dfab7b5db9fc733793b0f4252 to your computer and use it in GitHub Desktop.
Discord launch signature generation reverse engineering. Used for client mod detection and reporting. Includes ai written launch_signature.rs
void detect_client_and_set_flag
(int bitfield,uint clientmod_id,undefined4 js_context,HeapStr *strs,int totalStrs)
{
bool bVar1;
uint size;
uint key_0;
int iVar2;
int remaining;
byte *pbVar3;
uint uVar4;
uint status_value;
uint local_14;
astruct decoded_str;
uint key;
/* 24 for BD */
remaining = totalStrs << 3;
do {
if (remaining == 0) {
/* 2 for BD */
pbVar3 = (byte *)(bitfield + (clientmod_id >> 3));
uVar4 = (clientmod_id ^ 0xffffffff) & 7;
*pbVar3 = *pbVar3 & ((byte)(-2 << uVar4) | (byte)(0xfffffffe >> 0x20 - uVar4));
return;
}
decode_hex_xor73(&decoded_str,strs->ptr,strs->len);
size = decoded_str.size;
key_0 = new_string(decoded_str.size,decoded_str.dest_0);
key = key_0;
js_get_property_checked(&status_value,js_context,&key);
uVar4 = local_14;
if ((status_value & 1) == 0) {
iVar2 = isUndefined(local_14);
bVar1 = iVar2 == 1;
}
else {
bVar1 = true;
}
if (0x83 < uVar4) {
drop_js_handle(uVar4);
}
if (0x83 < key_0) {
drop_js_handle(key_0);
}
if (decoded_str.dest != 0) {
dlmalloc::validate_size(size,decoded_str.dest,1);
}
strs = strs + 1;
remaining = remaining + -8;
} while (bVar1);
pbVar3 = (byte *)(bitfield + (clientmod_id >> 3));
*pbVar3 = *pbVar3 | (byte)(1 << ((clientmod_id ^ 0xffffffff) & 7));
return;
}
uint export::generateLaunchSignature(undefined4 globalVar)
{
uint uVar1;
ulonglong uVar2;
ulonglong uVar3;
uint uVar4;
uint finalUuid;
uint someMem;
int iVar5;
undefined8 local_b8;
undefined1 local_b0;
undefined1 local_af;
undefined1 local_ae;
undefined1 local_ad;
undefined1 local_ac;
undefined1 uStack_ab;
undefined1 uStack_aa;
undefined1 uStack_a9;
undefined1 local_a8;
undefined1 uStack_a7;
undefined1 uStack_a6;
undefined1 uStack_a5;
undefined4 local_a4;
undefined8 random_values;
undefined8 local_98;
uint local_88;
uint local_84;
uint local_80;
uint local_7c;
uint jsContext;
ulonglong bitfield;
ulonglong local_68;
byte jqueryStr2;
byte local_5f;
byte local_5e;
byte local_5d;
byte local_5c;
byte local_5b;
byte local_5a;
byte local_59;
byte local_58;
byte local_57;
byte local_56;
byte local_55;
byte local_54;
byte local_53;
byte local_52;
byte local_51;
astruct jqueryStr;
astruct dollarStr;
astruct *targetStrLen;
undefined *targeStrRaw;
undefined8 local_2c;
uint local_24;
uint local_20;
astruct fnStr [2];
someMem = unnamed_function_210();
*(undefined4 *)((ulonglong)someMem * 4 + 0x100000000) = globalVar;
local_98 = 0;
random_values = 0;
jsContext = someMem;
import::./libdiscore_wasm_bg.js::__wbg_getRandomValues_38097e921c2494c3(&random_values,0x10);
uVar1 = DAT_ram_0012c014._4_4_;
/* previous state */
iVar5 = (int)DAT_ram_0012c014;
DAT_ram_0012c014 = 0;
if (iVar5 == 1) {
if (0x83 < uVar1) {
drop_js_handle(uVar1);
}
local_a4 = &PTR_s_/root/.cargo/registry/src/index._ram_001016ef_ram_00103530;
local_a8 = 0x28;
uStack_a7 = 0;
uStack_a6 = 0;
uStack_a5 = 0;
local_ac = 8;
uStack_ab = 0x35;
uStack_aa = 0x10;
uStack_a9 = 0;
local_b8 = 0x2800103508;
unnamed_function_380
(&local_b8,&DAT_ram_0012964c,
&PTR_s_/root/.cargo/registry/src/index._ram_001016ef_ram_00103530,1,0);
do {
halt_trap();
} while( true );
}
local_68 = random_values << 0x38 | (random_values & 0xff00) << 0x28 |
(random_values & 0xff0000) << 0x18 | (random_values & 0xff000000) << 8 |
random_values >> 8 & 0xff000000 | random_values >> 0x18 & 0xff0000 |
random_values >> 0x28 & 0xff00 |
(random_values & 0x3f00000000000000 | 0x8000000000000000) >> 0x38;
bitfield = local_98 << 0x38 | (local_98 & 0xf00 | 0x4000) << 0x28 |
(local_98 & 0xff0000) << 0x18 | (local_98 & 0xff000000) << 8 |
local_98 >> 8 & 0xff000000 | local_98 >> 0x18 & 0xff0000 |
local_98 >> 0x28 & 0xff00 | local_98 >> 0x38;
decode_hex_xor73(&jqueryStr,jquery_hex_obf,0xc);
decode_hex_xor73(&dollarStr,dollar_hex_ofb,2);
decode_hex_xor73(fnStr,fn_hex_obf,4);
decode_hex_xor73((astruct *)&jqueryStr2,jquery_hex_obf_2,0xc);
local_20 = _local_58;
uVar1 = _local_5c;
local_24 = _local_5c;
uVar4 = fnStr[0].size;
local_2c = CONCAT44(fnStr[0].dest_0,fnStr[0].size);
targeStrRaw = (undefined *)jqueryStr.dest_0;
targetStrLen = (astruct *)jqueryStr.size;
unnamed_function_308(&local_80,someMem,&targetStrLen);
if ((local_80 & 1) == 0) {
code_r0x80064b52:
finalUuid = dollarStr.size;
local_20 = _local_58;
local_24 = uVar1;
local_2c = CONCAT44(fnStr[0].dest_0,uVar4);
targeStrRaw = (undefined *)dollarStr.dest_0;
targetStrLen = (astruct *)dollarStr.size;
unnamed_function_308(&local_88,someMem,&targetStrLen);
if ((local_88 & 1) != 0) {
iVar5 = unnamed_function_1028(local_84);
if ((iVar5 != 1) && (iVar5 = isUndefined(local_84), iVar5 != 1)) {
if (0x83 < local_84) {
drop_js_handle(local_84);
}
goto code_r0x80064c44;
}
if (0x83 < local_84) {
drop_js_handle(local_84);
}
}
if (_jqueryStr2 != 0) {
dlmalloc::validate_size(uVar1,_jqueryStr2,1);
}
if (fnStr[0].dest != 0) {
dlmalloc::validate_size(uVar4,fnStr[0].dest,1);
}
if (dollarStr.dest != 0) {
dlmalloc::validate_size(finalUuid,dollarStr.dest,1);
}
if (jqueryStr.dest != 0) {
dlmalloc::validate_size(jqueryStr.size,jqueryStr.dest,1);
}
bitfield = bitfield & 0xffffffffffff7fff;
}
else {
iVar5 = unnamed_function_1028(local_7c);
if ((iVar5 == 1) || (iVar5 = isUndefined(local_7c), iVar5 == 1)) {
if (0x83 < local_7c) {
drop_js_handle(local_7c);
}
goto code_r0x80064b52;
}
if (0x83 < local_7c) {
drop_js_handle(local_7c);
}
code_r0x80064c44:
if (_jqueryStr2 != 0) {
dlmalloc::validate_size(uVar1,_jqueryStr2,1);
}
if (fnStr[0].dest != 0) {
dlmalloc::validate_size(uVar4,fnStr[0].dest,1);
}
if (dollarStr.dest != 0) {
dlmalloc::validate_size(dollarStr.size,dollarStr.dest,1);
}
if (jqueryStr.dest != 0) {
dlmalloc::validate_size(jqueryStr.size,jqueryStr.dest,1);
}
bitfield = bitfield | 0x8000;
}
detect_client_and_set_flag(&bitfield,0b00010011,&jsContext,BetterDiscordStrs,3);
detect_client_and_set_flag(&bitfield,0b00011011,&jsContext,RamboxStrs,1);
detect_client_and_set_flag(&bitfield,0b00100100,&jsContext,RevengeStrs,4);
detect_client_and_set_flag(&bitfield,0b00101011,&jsContext,VencordStrs,4);
detect_client_and_set_flag(&bitfield,0b00110100,&jsContext,RepluggedStrs,2);
detect_client_and_set_flag(&bitfield,0b01000010,&jsContext,LegcordStrs,2);
detect_client_and_set_flag(&bitfield,0b01001000,&jsContext,DorionStrs,3);
detect_client_and_set_flag(&bitfield,0b01001111,&jsContext,GCDPStrs,1);
detect_client_and_set_flag(&bitfield,0b01011001,&jsContext,OpenasarStrs,1);
detect_client_and_set_flag(&bitfield,0b01100111,&jsContext,ShelterStrs,1);
detect_client_and_set_flag(&bitfield,0b01110100,&jsContext,MoonlightStrs,2);
_local_58 = local_68;
uVar3 = _local_58;
_jqueryStr2 = bitfield;
uVar2 = _jqueryStr2;
fnStr[0].dest_0 = 0;
fnStr[0].dest = 0;
fnStr[0].size = 1;
targeStrRaw = &DAT_ram_00102a54;
local_2c = 0x60000020;
targetStrLen = fnStr;
local_ac = 0x2d;
uStack_a7 = 0x2d;
jqueryStr2 = (byte)bitfield;
local_b8._0_5_ = CONCAT14((&DAT_ram_001034e8)[jqueryStr2 >> 4],(undefined4)local_b8);
local_5f = (byte)(bitfield >> 8);
local_b8 = CONCAT17((&DAT_ram_001034e8)[local_5f & 0xf],
CONCAT16((&DAT_ram_001034e8)[local_5f >> 4],
CONCAT15((&DAT_ram_001034e8)[jqueryStr2 & 0xf],(undefined5)local_b8))
);
local_5e = (byte)(bitfield >> 0x10);
local_af = (&DAT_ram_001034e8)[local_5e & 0xf];
local_b0 = (&DAT_ram_001034e8)[local_5e >> 4];
local_5d = (byte)(bitfield >> 0x18);
local_ad = (&DAT_ram_001034e8)[local_5d & 0xf];
local_ae = (&DAT_ram_001034e8)[local_5d >> 4];
local_5c = (byte)(bitfield >> 0x20);
uStack_aa = (&DAT_ram_001034e8)[local_5c & 0xf];
uStack_ab = (&DAT_ram_001034e8)[local_5c >> 4];
local_5b = (byte)(bitfield >> 0x28);
local_a8 = (&DAT_ram_001034e8)[local_5b & 0xf];
uStack_a9 = (&DAT_ram_001034e8)[local_5b >> 4];
local_5a = (byte)(bitfield >> 0x30);
uStack_a6 = (&DAT_ram_001034e8)[local_5a >> 4];
uStack_a5 = (&DAT_ram_001034e8)[local_5a & 0xf];
local_59 = (byte)(bitfield >> 0x38);
local_a4 = (undefined **)
CONCAT22(0x2d,CONCAT11((&DAT_ram_001034e8)[local_59 & 0xf],
(&DAT_ram_001034e8)[local_59 >> 4]));
local_58 = (byte)local_68;
local_a4 = (undefined **)CONCAT13((&DAT_ram_001034e8)[local_58 >> 4],(undefined3)local_a4);
local_57 = (byte)(local_68 >> 8);
random_values._0_3_ =
CONCAT12((&DAT_ram_001034e8)[local_57 & 0xf],
CONCAT11((&DAT_ram_001034e8)[local_57 >> 4],(&DAT_ram_001034e8)[local_58 & 0xf]));
local_56 = (byte)(local_68 >> 0x10);
random_values._0_5_ =
CONCAT14((&DAT_ram_001034e8)[local_56 >> 4],CONCAT13(0x2d,(undefined3)random_values));
local_55 = (byte)(local_68 >> 0x18);
random_values =
CONCAT17((&DAT_ram_001034e8)[local_55 & 0xf],
CONCAT16((&DAT_ram_001034e8)[local_55 >> 4],
CONCAT15((&DAT_ram_001034e8)[local_56 & 0xf],(undefined5)random_values)));
local_54 = (byte)(local_68 >> 0x20);
local_53 = (byte)(local_68 >> 0x28);
local_52 = (byte)(local_68 >> 0x30);
local_51 = (byte)(local_68 >> 0x38);
local_98 = CONCAT17((&DAT_ram_001034e8)[local_51 & 0xf],
CONCAT16((&DAT_ram_001034e8)[local_51 >> 4],
CONCAT15((&DAT_ram_001034e8)[local_52 & 0xf],
CONCAT14((&DAT_ram_001034e8)[local_52 >> 4],
CONCAT13((&DAT_ram_001034e8)[local_53 & 0xf],
CONCAT12((&DAT_ram_001034e8)
[local_53 >> 4],
CONCAT11((&DAT_ram_001034e8)
[local_54 & 0xf],
(&DAT_ram_001034e8)
[local_54 >> 4])))))));
_jqueryStr2 = uVar2;
_local_58 = uVar3;
iVar5 = unnamed_function_864(&targetStrLen,(int)&local_b8 + 4,0x24);
uVar1 = fnStr[0].dest_0;
if (iVar5 != 0) {
unnamed_function_524
(s_a_Display_implementation_returne_ram_00102a7c,0x37,&dollarStr,&DAT_ram_00102a6c,
&PTR_s_/nix/store/lmp2ykgvp9m441iwxcjs8_ram_00101741_ram_00102ab4);
do {
halt_trap();
} while( true );
}
finalUuid = fnStr[0].size;
uVar4 = fnStr[0].dest;
if (0x83 < someMem) {
drop_js_handle(someMem);
}
if (uVar1 < uVar4) {
if (uVar1 == 0) {
dlmalloc::validate_size(finalUuid,uVar4,1);
finalUuid = 1;
}
else {
finalUuid = dlmalloc::realloc(finalUuid,uVar4,1,uVar1);
if (finalUuid == 0) {
panic_capacity_overflow(1,uVar1);
do {
halt_trap();
} while( true );
}
}
}
return finalUuid;
}
void decode_hex_xor73(astruct *ret_data,int hex_str,uint hex_str_len)
{
int iVar1;
undefined2 uVar2;
undefined1 uVar3;
uint size;
uint uVar4;
byte *pbVar5;
uint uVar6;
uint uVar7;
uint uVar8;
int local_4c;
uint local_48;
uint local_44;
int local_40;
uint local_3c;
undefined8 local_38;
int local_30;
uint local_2c;
int local_24;
uint local_18;
int local_14;
uint local_10;
uint local_c;
int str_0;
uint dst_0;
uint dst;
int src;
dst = 0;
local_4c = 1;
if ((hex_str_len & 1) == 0) {
size = hex_str_len >> 1;
if ((hex_str_len != 0) && (local_4c = unnamed_function_955(size,1), local_4c == 0)) {
panic_capacity_overflow(1,size);
do {
halt_trap();
} while( true );
}
local_10 = 0;
local_18 = size;
uVar4 = 0;
for (; local_14 = local_4c, hex_str_len != size; size = size + 1) {
if (uVar4 + 1 < hex_str_len) {
if (hex_str_len < uVar4 + 2) {
assert_fail(uVar4,uVar4 + 2,hex_str_len,
&PTR_s_wasm/src/launch_signature.rs_ram_00101889_ram_00102788);
do {
halt_trap();
} while( true );
}
uVar8 = (uint)(*(char *)(hex_str + uVar4) == '+');
pbVar5 = (byte *)(hex_str + uVar4 + uVar8);
uVar6 = (uint)*pbVar5;
uVar7 = (uVar6 - 0x41 & 0xffffffdf) + 10;
if (uVar6 < 0x3a) {
uVar7 = uVar6 - 0x30;
}
if (uVar7 < 0x10) {
if (uVar8 == 0) {
uVar8 = (uint)pbVar5[1];
uVar6 = (uVar8 - 0x41 & 0xffffffdf) + 10;
if (uVar8 < 0x3a) {
uVar6 = uVar8 - 0x30;
}
if (0xf < uVar6) goto code_r0x8004190e;
uVar7 = uVar7 << 4 | uVar6;
}
if (local_18 == dst) {
unnamed_function_502(&local_18);
}
*(byte *)(local_14 + dst) = (byte)uVar7 ^ 0x73;
dst = dst + 1;
local_10 = dst;
}
}
code_r0x8004190e:
uVar4 = uVar4 + 2;
local_4c = local_14;
}
uVar4 = 0;
local_48 = dst;
unnamed_function_217(&local_30,&local_4c);
size = local_2c;
src = local_30;
uVar7 = local_3c;
if (local_30 == 0) {
dst_0 = 0;
local_c = 0x80000000;
str_0 = 1;
}
else if (local_24 == 0) {
dst_0 = local_2c;
local_c = 0x80000000;
str_0 = local_30;
}
else {
if (dst == 0) {
local_40 = 1;
}
else {
local_40 = unnamed_function_955(dst,1);
if (local_40 == 0) {
panic_capacity_overflow(1,dst);
do {
halt_trap();
} while( true );
}
}
local_3c = 0;
local_44 = dst;
if (dst < size) {
unnamed_function_449(&local_44,0,size);
uVar4 = local_3c;
}
if (size != 0) {
memory_copy(0,0,size,src,local_40 + uVar4);
}
local_3c = size + uVar4;
if (local_44 - local_3c < 3) {
unnamed_function_449(&local_44,local_3c,3);
}
uVar2 = DAT_ram_0012a3ec;
*(undefined2 *)(local_3c + local_40) = DAT_ram_0012a3ec;
uVar3 = DAT_ram_0012a3ee;
*(undefined1 *)((undefined2 *)(local_3c + local_40) + 1) = DAT_ram_0012a3ee;
size = local_3c + 3;
local_38 = CONCAT44(local_48,local_4c);
local_3c = size;
unnamed_function_217(&local_30,&local_38);
dst_0 = local_3c;
src = local_30;
dst = local_2c;
iVar1 = local_24;
while (local_3c = size, uVar7 = dst_0, local_c = local_44, str_0 = local_40, src != 0) {
local_30 = src;
local_2c = dst;
local_24 = iVar1;
if (local_44 - local_3c < dst) {
size = local_3c;
local_3c = dst_0;
unnamed_function_449(&local_44,size,dst);
}
if (dst != 0) {
memory_copy(0,0,dst,src,local_3c + local_40);
}
size = local_3c + dst;
if (iVar1 != 0) {
if (local_44 - size < 3) {
local_3c = size;
unnamed_function_449(&local_44,size,3);
size = local_3c;
}
*(undefined2 *)(size + local_40) = uVar2;
*(undefined1 *)((undefined2 *)(size + local_40) + 1) = uVar3;
size = size + 3;
}
local_3c = size;
unnamed_function_217(&local_30,&local_38);
dst_0 = local_3c;
src = local_30;
dst = local_2c;
iVar1 = local_24;
}
}
local_3c = uVar7;
dst = dst_0;
src = str_0;
if (dst_0 == 0) {
size = 1;
}
else {
size = unnamed_function_955(dst_0,1);
if (size == 0) {
panic_capacity_overflow(1,dst);
do {
halt_trap();
} while( true );
}
}
if (dst != 0) {
memory_copy(0,0,dst,src,size);
}
ret_data->dest_0 = dst;
ret_data->size = size;
ret_data->dest = dst;
if ((local_c | 0x80000000) != 0x80000000) {
dlmalloc::validate_size(src,local_c,1);
}
if (local_18 != 0) {
dlmalloc::validate_size(local_14,local_18,1);
}
}
else {
ret_data->dest_0 = 0;
ret_data->dest = 0;
ret_data->size = 1;
}
return;
}
// This was made by just copy pasting the cpp and the strings over to chatgpt to get it to rewrite it, this gives a general overview on how it functions but you may want to verify it's contents
// Closest “shape” to what this was in Rust (wasm-bindgen + js-sys style).
// Notes:
// - The C-like output is from wasm glue + Rust monomorphization, so names/handles differ.
// - This keeps the same control flow: decode hex -> XOR 0x73 -> Reflect::get -> is_undefined.
// - Bit indexing matches the decomp: bit order is reversed within each byte.
//
// Cargo features you likely had:
// wasm-bindgen = "0.2"
// js-sys = "0.3"
// web-sys = { version = "0.3", features = ["Crypto", "Window"] }
use js_sys::{Reflect, Uint8Array, Object};
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsValue;
const XOR_KEY: u8 = 0x73;
/// Decode the obfuscated hex used in your listing:
/// - reads pairs of hex chars
/// - allows a leading '+' before the first nibble (the decomp checked for '+')
/// - XORs each decoded byte with 0x73
/// - returns UTF-8 string (lossy in case something weird slips in)
fn decode_hex_xor73(hex: &str) -> String {
let bytes = hex.as_bytes();
if bytes.len() % 2 != 0 {
// matches the decomp behavior: odd-length => return empty-ish
return String::new();
}
fn nibble(b: u8) -> Option<u8> {
match b {
b'0'..=b'9' => Some(b - b'0'),
b'a'..=b'f' => Some((b - b'a') + 10),
b'A'..=b'F' => Some((b - b'A') + 10),
_ => None,
}
}
let mut out = Vec::with_capacity(bytes.len() / 2);
let mut i = 0;
while i + 1 < bytes.len() {
// The decomp did:
// uVar8 = (*(char *)(hex_str + uVar4) == '+');
// pb = hex_str + uVar4 + uVar8;
// So allow '+' for the first nibble position of the pair.
let mut j = i;
if bytes[j] == b'+' {
j += 1;
if j >= bytes.len() {
break;
}
}
let hi = match nibble(bytes[j]) {
Some(v) => v,
None => {
i += 2;
continue;
}
};
// If we had '+', the second nibble is the next byte after hi.
// If not, it’s i+1 as usual.
let lo_index = if j == i { i + 1 } else { j + 1 };
if lo_index >= bytes.len() {
break;
}
let lo = match nibble(bytes[lo_index]) {
Some(v) => v,
None => {
i += 2;
continue;
}
};
let b = ((hi << 4) | lo) ^ XOR_KEY;
out.push(b);
i += 2;
}
String::from_utf8_lossy(&out).into_owned()
}
/// Reverse-bit index inside each byte exactly like:
/// byte_index = id >> 3
/// bit_in_byte = (id ^ 0xffffffff) & 7
/// overall_bit = byte_index*8 + bit_in_byte
#[inline]
fn bit_index_from_clientmod_id(clientmod_id: u32) -> u32 {
let byte_base = clientmod_id & !7;
let bit_in_byte = 7 - (clientmod_id & 7);
byte_base + bit_in_byte
}
/// Check a list of property names; if any exists (non-undefined), set the flag bit.
/// If none exists, clear the flag bit.
fn detect_client_and_set_flag(
bitfield: &mut u64,
clientmod_id: u32,
js_context: &JsValue,
obf_hex_keys: &[&'static str],
) {
// In the decomp:
// remaining = totalStrs << 3; each loop subtracts 8; so it's just "for each string".
let mut detected = false;
for hex in obf_hex_keys {
let key = decode_hex_xor73(hex);
// js_get_property_checked(&status_value, js_context, &key)
// Here: Reflect::get(ctx, &JsValue::from_str(&key))
let v = Reflect::get(js_context, &JsValue::from_str(&key))
.unwrap_or(JsValue::UNDEFINED);
// decomp logic:
// if ((status_value & 1) == 0) { bVar1 = isUndefined(local_14) == 1; }
// else { bVar1 = true; }
//
// Interpreting that with js-sys: loop continues while undefined,
// exits (and sets bit) when value is NOT undefined.
if !v.is_undefined() {
detected = true;
break;
}
}
let bit = bit_index_from_clientmod_id(clientmod_id);
let mask = 1u64 << bit;
if detected {
*bitfield |= mask;
} else {
*bitfield &= !mask;
}
}
// ---- Obfuscated strings you provided ----
// jQuery-ish checks
const JQUERY_HEX: &str = "19220616010a"; // jQuery
const DOLLAR_HEX: &str = "57"; // $
const FN_HEX: &str = "151d"; // fn
const JQUERY2_HEX: &str = "19020616010a"; // jquery
// Client detection keys (each entry is a property name to probe on the JS global)
const BETTERDISCORD_KEYS: &[&str] = &[
"311607071601371a00101c0117", // BetterDiscord
"311607071601371a00101c01172301161f1c1217", // BetterDiscordPreload
"311732031a", // BdApi
];
const RAMBOX_KEYS: &[&str] = &[
"01121e111c0b", // rambox
];
const REVENGE_KEYS: &[&str] = &[
"011605161d1416", // revenge
"05161d1716070712", // vendetta
"11061d1d0a", // bunny
"1816070706", // kettu
];
const VENCORD_KEYS: &[&str] = &[
"25161d101c0117", // Vencord
"25161d101c01173d12071a0516", // VencordNative
"25160018071c033d12071a0516", // VesktopNative
"25161d101c01173e1c111a1f16", // VencordMobile
];
const REPLUGGED_KEYS: &[&str] = &[
"0116031f0614141617", // replugged
"2116031f06141416173d12071a0516", // RepluggedNative
];
const LEGCORD_KEYS: &[&str] = &[
"1f1614101c0117", // legcord
"3f1614101c0117212330", // LegcordRPC
];
const DORION_KEYS: &[&str] = &[
"371c011a1c1d", // Dorion
"2c2c373c213a3c3d2c303c3d353a342c2c", // __DORION_CONFIG__
"2c2c373c213a3c3d2c3a3d3a272c2c", // __DORION_INIT__
];
const GCDP_KEYS: &[&str] = &[
"34303723", // GCDP
];
const OPENASAR_KEYS: &[&str] = &[
"1c03161d12001201", // openasar
];
const SHELTER_KEYS: &[&str] = &[
"001b161f071601", // shelter
];
const MOONLIGHT_KEYS: &[&str] = &[
"1e1c1c1d1f1a141b07", // moonlight
"1e1c1c1d1f1a141b073d1c1716", // moonlightNode
];
// ---- “launch signature” generator ----
fn get_random_16() -> [u8; 16] {
let mut buf = [0u8; 16];
// Equivalent to __wbg_getRandomValues(..., 0x10)
// Prefer web_sys::window().crypto(); fall back to js_sys::global crypto if needed.
#[cfg(feature = "web-sys")]
{
if let Some(w) = web_sys::window() {
if let Ok(crypto) = w.crypto() {
// get_random_values_with_u8_array exists on web-sys Crypto
let _ = crypto.get_random_values_with_u8_array(&mut buf);
return buf;
}
}
}
// Generic fallback: (globalThis.crypto.getRandomValues)
let global = js_sys::global();
let crypto = Reflect::get(&global, &JsValue::from_str("crypto"))
.unwrap_or(JsValue::UNDEFINED);
if !crypto.is_undefined() {
let arr = Uint8Array::new_with_length(16);
let _ = Reflect::get(&crypto, &JsValue::from_str("getRandomValues"))
.ok()
.and_then(|f| f.dyn_into::<js_sys::Function>().ok())
.map(|f| f.call1(&crypto, &arr.into()));
arr.copy_to(&mut buf);
}
buf
}
fn uuid_like_from_bytes(b: [u8; 16]) -> String {
// standard 8-4-4-4-12 hex with hyphens, lowercase
let mut s = String::with_capacity(36);
for (i, byte) in b.iter().enumerate() {
if matches!(i, 4 | 6 | 8 | 10) {
s.push('-');
}
s.push_str(&format!("{:02x}", byte));
}
s
}
fn has_defined_property(obj: &JsValue, name: &str) -> bool {
Reflect::get(obj, &JsValue::from_str(name))
.map(|v| !v.is_undefined() && !v.is_null())
.unwrap_or(false)
}
#[wasm_bindgen]
pub fn generate_launch_signature() -> String {
let js_context = js_sys::global();
// random UUID-ish 16 bytes, then enforce version/variant bits
// The decomp’s bit-twiddling corresponds to:
// - version 4 in the "time_hi_and_version" nibble
// - RFC4122 variant in the "clock_seq_hi_and_reserved" bits
let mut rnd = get_random_16();
rnd[6] = (rnd[6] & 0x0f) | 0x40; // version 4
rnd[8] = (rnd[8] & 0x3f) | 0x80; // variant RFC4122
// The decomp also did a “jQuery present?” test and toggled 0x8000.
// This is the closest read of that control flow:
//
// - If globalThis["jQuery"] exists and is not null/undefined => set 0x8000
// - Else if globalThis["$"] exists and is not null/undefined, then check "$.fn"
// and if that looks valid, clear 0x8000; otherwise set it.
let jquery_key = decode_hex_xor73(JQUERY_HEX);
let dollar_key = decode_hex_xor73(DOLLAR_HEX);
let fn_key = decode_hex_xor73(FN_HEX);
let jquery2_key = decode_hex_xor73(JQUERY2_HEX);
let mut client_bitfield: u64 = 0;
let jquery_obj = Reflect::get(&js_context, &JsValue::from_str(&jquery_key))
.unwrap_or(JsValue::UNDEFINED);
if !jquery_obj.is_undefined() && !jquery_obj.is_null() {
client_bitfield |= 0x8000;
} else {
// Try "$"
let dollar_obj = Reflect::get(&js_context, &JsValue::from_str(&dollar_key))
.unwrap_or(JsValue::UNDEFINED);
if !dollar_obj.is_undefined() && !dollar_obj.is_null() {
// If "$.fn" looks usable, clear; otherwise set.
let fn_ok = has_defined_property(&dollar_obj, &fn_key);
if !fn_ok {
client_bitfield |= 0x8000;
} else {
client_bitfield &= !0x8000;
}
} else {
// Check for lowercase jquery
if has_defined_property(&js_context, &jquery2_key) {
client_bitfield |= 0x8000;
} else {
client_bitfield &= !0x8000;
}
}
}
// Client mod probes (ids match the calls in the decomp)
detect_client_and_set_flag(&mut client_bitfield, 0b0001_0011, &js_context, BETTERDISCORD_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0001_1011, &js_context, RAMBOX_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0010_0100, &js_context, REVENGE_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0010_1011, &js_context, VENCORD_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0011_0100, &js_context, REPLUGGED_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0100_0010, &js_context, LEGCORD_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0100_1000, &js_context, DORION_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0100_1111, &js_context, GCDP_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0101_1001, &js_context, OPENASAR_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0110_0111, &js_context, SHELTER_KEYS);
detect_client_and_set_flag(&mut client_bitfield, 0b0111_0100, &js_context, MOONLIGHT_KEYS);
// The decomp strongly suggests they *embed* the client bitfield into the first 8 bytes,
// then use the random UUID bytes for the remaining 8 bytes, and finally print as UUID.
//
// That explains why it converts `bitfield` to hex nibbles first, then `local_68`.
let mut sig_bytes = [0u8; 16];
sig_bytes[..8].copy_from_slice(&client_bitfield.to_le_bytes());
sig_bytes[8..].copy_from_slice(&rnd[8..16]);
uuid_like_from_bytes(sig_bytes)
}
"jQuery"
"19220616010a"
"$"
"57"
"fn"
"151d"
"jquery"
"19020616010a"
"BetterDiscord"
"311607071601371a00101c0117"
"BetterDiscordPreload"
"311607071601371a00101c01172301161f1c1217"
"BdApi"
"311732031a"
"rambox"
"01121e111c0b"
"revenge"
"011605161d1416"
"vendetta"
"05161d1716070712"
"bunny"
"11061d1d0a"
"kettu"
"1816070706"
"Vencord"
"25161d101c0117"
"VencordNative"
"25161d101c01173d12071a0516"
"VesktopNative"
"25160018071c033d12071a0516"
"VencordMobile"
"25161d101c01173e1c111a1f16"
"replugged"
"0116031f0614141617"
"RepluggedNative"
"2116031f06141416173d12071a0516"
"legcord"
"1f1614101c0117"
"LegcordRPC"
"3f1614101c0117212330"
"Dorion"
"371c011a1c1d"
"__DORION_CONFIG__"
"2c2c373c213a3c3d2c303c3d353a342c2c"
"__DORION_INIT__"
"2c2c373c213a3c3d2c3a3d3a272c2c"
"GCDP"
"34303723"
"openasar"
"1c03161d12001201"
"shelter"
"001b161f071601"
"moonlight"
"1e1c1c1d1f1a141b07"
"moonlightNode"
"1e1c1c1d1f1a141b073d1c1716"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment