UNPKG

csgo-sharecode

Version:

Encode / decode CS:GO and CS2 share code

170 lines 6.36 kB
const DICTIONARY = 'ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789'; const DICTIONARY_LENGTH = BigInt(DICTIONARY.length); const SHARECODE_PATTERN = /^CSGO(-?[\w]{5}){5}$/; export class InvalidShareCode extends Error { constructor() { super('Invalid share code'); Object.setPrototypeOf(this, InvalidShareCode.prototype); } } export class InvalidCrosshairShareCode extends Error { constructor() { super('Invalid crosshair share code'); Object.setPrototypeOf(this, InvalidCrosshairShareCode.prototype); } } function bytesToHex(bytes) { return Array.from(bytes, (byte) => { return ('0' + (byte & 0xff).toString(16)).slice(-2); }).join(''); } function bytesToBigInt(bytes) { const hex = bytesToHex(bytes); return BigInt(`0x${hex}`); } function stringToBytes(str) { const bytes = []; for (let i = 0; i < str.length; i += 2) { bytes.push(parseInt(str.slice(i, i + 2), 16)); } return bytes; } function int16ToBytes(number) { return [(number & 0x0000ff00) >> 8, number & 0x000000ff]; } function uint8ToInt8(number) { return (number << 24) >> 24; } function sumArray(array) { return array.reduce((previousValue, value) => { return previousValue + value; }, 0); } function shareCodeToBytes(shareCode) { if (!shareCode.match(SHARECODE_PATTERN)) { throw new InvalidShareCode(); } shareCode = shareCode.replace(/CSGO|-/g, ''); const chars = Array.from(shareCode).reverse(); let big = BigInt(0); for (let i = 0; i < chars.length; i++) { big = big * DICTIONARY_LENGTH + BigInt(DICTIONARY.indexOf(chars[i])); } const str = big.toString(16).padStart(36, '0'); const bytes = stringToBytes(str); return bytes; } function bytesToShareCode(bytes) { const hex = bytesToHex(bytes); let total = BigInt(`0x${hex}`); let chars = ''; let rem = BigInt(0); for (let i = 0; i < 25; i++) { rem = total % DICTIONARY_LENGTH; chars += DICTIONARY[Number(rem)]; total = total / DICTIONARY_LENGTH; } return `CSGO-${chars.slice(0, 5)}-${chars.slice(5, 10)}-${chars.slice(10, 15)}-${chars.slice(15, 20)}-${chars.slice(20, 25)}`; } export function encodeMatch({ matchId, reservationId, tvPort }) { const matchBytes = stringToBytes(matchId.toString(16)).reverse(); const reservationBytes = stringToBytes(reservationId.toString(16)).reverse(); const tvBytes = int16ToBytes(tvPort).reverse(); const bytes = [...matchBytes, ...reservationBytes, ...tvBytes]; const shareCode = bytesToShareCode(bytes); return shareCode; } export function decodeMatchShareCode(shareCode) { const bytes = shareCodeToBytes(shareCode); return { matchId: bytesToBigInt(bytes.slice(0, 8).reverse()), reservationId: bytesToBigInt(bytes.slice(8, 16).reverse()), tvPort: Number(bytesToBigInt(bytes.slice(16, 18).reverse())), }; } export function decodeCrosshairShareCode(shareCode) { const bytes = shareCodeToBytes(shareCode); const size = sumArray(bytes.slice(1)) % 256; if (bytes[0] !== size) { throw new InvalidCrosshairShareCode(); } const crosshair = { gap: uint8ToInt8(bytes[2]) / 10, outline: bytes[3] / 2, red: bytes[4], green: bytes[5], blue: bytes[6], alpha: bytes[7], splitDistance: bytes[8] & 7, followRecoil: ((bytes[8] >> 4) & 8) === 8, fixedCrosshairGap: uint8ToInt8(bytes[9]) / 10, color: bytes[10] & 7, outlineEnabled: (bytes[10] & 8) === 8, innerSplitAlpha: (bytes[10] >> 4) / 10, outerSplitAlpha: (bytes[11] & 0xf) / 10, splitSizeRatio: (bytes[11] >> 4) / 10, thickness: bytes[12] / 10, centerDotEnabled: ((bytes[13] >> 4) & 1) === 1, deployedWeaponGapEnabled: ((bytes[13] >> 4) & 2) === 2, alphaEnabled: ((bytes[13] >> 4) & 4) === 4, tStyleEnabled: ((bytes[13] >> 4) & 8) === 8, style: (bytes[13] & 0xf) >> 1, length: bytes[14] / 10, }; return crosshair; } export function encodeCrosshair(crosshair) { const bytes = [ 0, 1, (crosshair.gap * 10) & 0xff, crosshair.outline * 2, crosshair.red, crosshair.green, crosshair.blue, crosshair.alpha, (crosshair.splitDistance & 7) | (Number(crosshair.followRecoil) << 7), (crosshair.fixedCrosshairGap * 10) & 0xff, (crosshair.color & 7) | (Number(crosshair.outlineEnabled) << 3) | ((crosshair.innerSplitAlpha * 10) << 4), (crosshair.outerSplitAlpha * 10) | ((crosshair.splitSizeRatio * 10) << 4), crosshair.thickness * 10, (crosshair.style << 1) | (Number(crosshair.centerDotEnabled) << 4) | (Number(crosshair.deployedWeaponGapEnabled) << 5) | (Number(crosshair.alphaEnabled) << 6) | (Number(crosshair.tStyleEnabled) << 7), crosshair.length * 10, 0, 0, 0, ]; bytes[0] = sumArray(bytes) & 0xff; const shareCode = bytesToShareCode(bytes); return shareCode; } export function crosshairToConVars(crosshair) { return ` cl_crosshair_drawoutline "${Number(crosshair.outlineEnabled)}" cl_crosshair_dynamic_maxdist_splitratio "${crosshair.splitSizeRatio}" cl_crosshair_dynamic_splitalpha_innermod "${crosshair.innerSplitAlpha}" cl_crosshair_dynamic_splitalpha_outermod "${crosshair.outerSplitAlpha}" cl_crosshair_dynamic_splitdist "${crosshair.splitDistance}" cl_crosshair_outlinethickness "${crosshair.outline}" cl_crosshair_t "${Number(crosshair.tStyleEnabled)}" cl_crosshairalpha "${crosshair.alpha}" cl_crosshaircolor "${crosshair.color}" cl_crosshaircolor_b "${crosshair.blue}" cl_crosshaircolor_g "${crosshair.green}" cl_crosshaircolor_r "${crosshair.red}" cl_crosshairdot "${Number(crosshair.centerDotEnabled)}" cl_crosshairgap "${crosshair.gap}" cl_crosshairgap_useweaponvalue "${Number(crosshair.deployedWeaponGapEnabled)}" cl_crosshairsize "${crosshair.length}" cl_crosshairstyle "${crosshair.style}" cl_crosshairthickness "${crosshair.thickness}" cl_crosshairusealpha "${Number(crosshair.alphaEnabled)}" cl_fixedcrosshairgap "${crosshair.fixedCrosshairGap}" cl_crosshair_recoil "${Number(crosshair.followRecoil)}" `; } //# sourceMappingURL=index.js.map