I have a function decoding CS:GO crosshair codes into key value object.
(Previously I asked question about how to decode share codes from CS:GO Here)
How can it be reversed from decoding these values, to encoding them into "share code" which consist of alphanumeric characters?
Function decoding share codes:
const BigNumber = require("bignumber.js");
// Intentionally no 0 and 1 number in DICTIONARY
const DICTIONARY = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789";
const DICTIONARY_LENGTH = DICTIONARY.length;
const SHARECODE_PATTERN = /CSGO(-?[\w]{5}){5}$/;
const bigNumberToByteArray = big => {
const str = big.toString(16).padStart(36, "0");
const bytes = [];
for (let i = 0; i < str.length; i += 2) {
bytes.push(parseInt(str.slice(i, i + 2), 16));
}
return bytes;
}
const parseBytes = bytes => {
return {
cl_crosshairgap: Int8Array.of(bytes[2])[0] / 10.0,
cl_crosshair_outlinethickness: (bytes[3] & 7) / 2.0,
cl_crosshaircolor_r: bytes[4],
cl_crosshaircolor_g: bytes[5],
cl_crosshaircolor_b: bytes[6],
cl_crosshairalpha: bytes[7],
cl_crosshair_dynamic_splitdist: bytes[8],
cl_fixedcrosshairgap: Int8Array.of(bytes[9])[0] / 10.0,
cl_crosshaircolor: bytes[10] & 7,
cl_crosshair_drawoutline: bytes[10] & 8 ? 1 : 0,
cl_crosshair_dynamic_splitalpha_innermod: ((bytes[10] & 0xF0) >> 4) / 10.0,
cl_crosshair_dynamic_splitalpha_outermod: (bytes[11] & 0xF) / 10.0,
cl_crosshair_dynamic_maxdist_splitratio: ((bytes[11] & 0xF0) >> 4) / 10.0,
cl_crosshairthickness: (bytes[12] & 0x3F) / 10.0,
cl_crosshairstyle: (bytes[13] & 0xE) >> 1,
cl_crosshairdot: bytes[13] & 0x10 ? 1 : 0,
cl_crosshairgap_useweaponvalue: bytes[13] & 0x20 ? 1 : 0,
cl_crosshairusealpha: bytes[13] & 0x40 ? 1 : 0,
cl_crosshair_t: bytes[13] & 0x80 ? 1 : 0,
cl_crosshairsize: (((bytes[15] & 0x1f) << 8) + bytes[14]) / 10.0
};
}
const decode = shareCode => {
if (!shareCode.match(SHARECODE_PATTERN)) {
throw new Error('Invalid share code');
}
shareCode = shareCode.replace(/CSGO|-/g, '');
const chars = Array.from(shareCode).reverse();
let big = new BigNumber(0);
for (let i = 0; i < chars.length; i++) {
big = big.multipliedBy(DICTIONARY_LENGTH).plus(DICTIONARY.indexOf(chars[i]));
}
return parseBytes(bigNumberToByteArray(big));
}
console.log(decode('CSGO-O4Jsi-V36wY-rTMGK-9w7qF-jQ8WB'))
// OUTPUT:
// {
// cl_crosshairgap: 1,
// cl_crosshair_outlinethickness: 1.5,
// cl_crosshaircolor_r: 50,
// cl_crosshaircolor_g: 250,
// cl_crosshaircolor_b: 84,
// cl_crosshairalpha: 200,
// cl_crosshair_dynamic_splitdist: 127,
// cl_fixedcrosshairgap: -10,
// cl_crosshaircolor: 5,
// cl_crosshair_drawoutline: 0,
// cl_crosshair_dynamic_splitalpha_innermod: 0.6,
// cl_crosshair_dynamic_splitalpha_outermod: 0.8,
// cl_crosshair_dynamic_maxdist_splitratio: 0.3,
// cl_crosshairthickness: 4.1,
// cl_crosshairstyle: 2,
// cl_crosshairdot: 1,
// cl_crosshairgap_useweaponvalue: 0,
// cl_crosshairusealpha: 0,
// cl_crosshair_t: 1,
// cl_crosshairsize: 33
// }
So values below:
{
cl_crosshairgap: 1,
cl_crosshair_outlinethickness: 1.5,
cl_crosshaircolor_r: 50,
cl_crosshaircolor_g: 250,
cl_crosshaircolor_b: 84,
cl_crosshairalpha: 200,
cl_crosshair_dynamic_splitdist: 127,
cl_fixedcrosshairgap: -10,
cl_crosshaircolor: 5,
cl_crosshair_drawoutline: 0,
cl_crosshair_dynamic_splitalpha_innermod: 0.6,
cl_crosshair_dynamic_splitalpha_outermod: 0.8,
cl_crosshair_dynamic_maxdist_splitratio: 0.3,
cl_crosshairthickness: 4.1,
cl_crosshairstyle: 2,
cl_crosshairdot: 1,
cl_crosshairgap_useweaponvalue: 0,
cl_crosshairusealpha: 0,
cl_crosshair_t: 1,
cl_crosshairsize: 33
}
Should be encoded into: CSGO-O4Jsi-V36wY-rTMGK-9w7qF-jQ8WB
Function encoding match share codes, which probably could be a base to encoding crosshair codes:
const BigNumber = require("bignumber.js");
// Intentionally no 0 and 1 number in DICTIONARY
const DICTIONARY = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789";
const DICTIONARY_LENGTH = DICTIONARY.length;
const SHARECODE_PATTERN = /CSGO(-?[\w]{5}){5}$/;
function bytesToHex(bytes) {
return Array.from(bytes, (byte) => {
return ('0' + (byte & 0xff).toString(16)).slice(-2);
}).join('');
}
function bigNumberToByteArray(big) {
const str = big.toString(16).padStart(36, '0');
const bytes = [];
for (let i = 0; i < str.length; i += 2) {
bytes.push(parseInt(str.slice(i, i + 2), 16));
}
return bytes;
}
function longToBytesBE(high, low) {
return [
(high >>> 24) & 0xff,
(high >>> 16) & 0xff,
(high >>> 8) & 0xff,
high & 0xff,
(low >>> 24) & 0xff,
(low >>> 16) & 0xff,
(low >>> 8) & 0xff,
low & 0xff,
];
}
function int16ToBytes(number) {
return [(number & 0x0000ff00) >> 8, number & 0x000000ff];
}
function bytesToInt32(bytes) {
let number = 0;
for (let i = 0; i < bytes.length; i++) {
number += bytes[i];
if (i < bytes.length - 1) {
number = number << 8;
}
}
return number;
}
function bigNumberToByteArray(big) {
const str = big.toString(16).padStart(36, "0");
const bytes = [];
for (let i = 0; i < str.length; i += 2) {
bytes.push(parseInt(str.slice(i, i + 2), 16));
}
return bytes;
}
const encode = (matchId, reservationId, tvPort) => {
const matchBytes = longToBytesBE(matchId.high, matchId.low).reverse();
const reservationBytes = longToBytesBE(reservationId.high, reservationId.low).reverse();
const tvBytes = int16ToBytes(tvPort).reverse();
const bytes = Array.prototype.concat(matchBytes, reservationBytes, tvBytes);
const bytesHex = bytesToHex(bytes);
let total = new BigNumber(bytesHex, 16);
// This part would probably be identical
let c = '';
let rem = new BigNumber(0);
for (let i = 0; i < 25; i++) {
rem = total.mod(DICTIONARY_LENGTH);
c += DICTIONARY[rem.integerValue(BigNumber.ROUND_FLOOR).toNumber()];
total = total.div(DICTIONARY_LENGTH);
}
return `CSGO-${c.substr(0, 5)}-${c.substr(5, 5)}-${c.substr(10, 5)}-${c.substr(15, 5)}-${c.substr(20, 5)}`;
};
console.log(encode(
{
low: -2147483492, high: 752192506
},
{
low: 143, high: 752193760
},
55788
));
// OUTPUT:
// CSGO-GADqf-jjyJ8-cSP2r-smZRo-TO2xK
Also I found Python code doing the same (with "match codes", containing less values to encode) - I'm aware it's JS question, and including this only to recognize similarities
import re
dictionary = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789"
def _swap_endianness(number):
result = 0
for n in range(0, 144, 8):
result = (result << 8) + ((number >> n) & 0xFF)
return result
def encode(matchid, outcomeid, token):
a = _swap_endianness((token << 128) | (outcomeid << 64) | matchid)
code = ''
for _ in range(25):
a, r = divmod(a, len(dictionary))
code += dictionary[r]
return "CSGO-%s-%s-%s-%s-%s" % (code[:5], code[5:10], code[10:15], code[15:20], code[20:])
print(encode(250, 34, 10))
# CSGO-t4kTW-mcVyA-TcReG-hviRe-pXNtQ
With the information provided, it would be difficult to impossible to create an endcoder, because there are a lot more encoded bits than you have specified a source for. The code is big enough for 18 bytes. You decoding only uses 14 of them, and not even all of those. Most interesting (to me) is that the least significant bit of byte 13 is unaccounted for.
Even if you come up with reasonable default values for most of these, you have to figure out how to compute byte 0, which appears to be some kind of checksum.
User contributions licensed under CC BY-SA 3.0