将数值编码为字母数字代码(反向解码功能)

Encoding numerical values into alphanumeric code (reversing decoding function)

我有一个函数将 CS:GO 十字准线代码解码为键值对象。

(之前我问过关于如何从 CS:GO 中解码共享代码的问题)

如何将这些值解码成由字母数字字符组成的“共享代码”?

函数解码分享代码:

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
// }

因此值如下:

{
  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
}

应编码为:CSGO-O4Jsi-V36wY-rTMGK-9w7qF-jQ8WB

功能编码匹配共享代码,这可能是编码十字准线代码的基础:

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

我还发现 Python 代码做同样的事情(使用“匹配代码”,包含较少的编码值)——我知道这是 JS 问题,包括这个只是为了识别相似之处

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

根据所提供的信息,创建编码器将是困难甚至不可能的,因为编码位比您指定的源要多得多。代码足够大,可以容纳 18 个字节。您解码仅使用其中的 14 个,甚至不是全部。最有趣的(对我来说)是字节 13 的最低有效位下落不明。

即使您为其中的大多数设置了合理的默认值,您也必须弄清楚如何计算字节 0,它似乎是某种校验和。

以下似乎有效。我在 中解码的对象上进行了尝试,并且所有对象都是无损往返。

const DICTIONARY = "ABCDEFGHJKLMNOPQRSTUVWXYZabcdefhijkmnopqrstuvwxyz23456789";
const DICTIONARY_LENGTH = BigInt(DICTIONARY.length);

const serializeToBytes = info => {
    const bytes = [
        0,
        1,
        (info.cl_crosshairgap * 10) & 0xff,
        (info.cl_crosshair_outlinethickness * 2) & 7,
        info.cl_crosshaircolor_r,
        info.cl_crosshaircolor_g,
        info.cl_crosshaircolor_b,
        info.cl_crosshairalpha,
        info.cl_crosshair_dynamic_splitdist,
        (info.cl_fixedcrosshairgap * 10) & 0xff,
        (info.cl_crosshaircolor & 7) |
            (info.cl_crosshair_drawoutline ? 8 : 0) |
            (info.cl_crosshair_dynamic_splitalpha_innermod * 10) << 4,
        ((info.cl_crosshair_dynamic_splitalpha_outermod * 10) & 0xf) |
            ((info.cl_crosshair_dynamic_maxdist_splitratio * 10) << 4),
        (info.cl_crosshairthickness * 10) & 0x3f,
        ((info.cl_crosshairstyle << 1) & 0xe) |
            (info.cl_crosshairdot ? 0x10 : 0) |
            (info.cl_crosshairgap_useweaponvalue ? 0x20 : 0) |
            (info.cl_crosshairusealpha ? 0x40 : 0) |
            (info.cl_crosshair_t ? 0x80 : 0),
        (info.cl_crosshairsize * 10) & 0xff,
        ((info.cl_crosshairsize * 10) >> 8) & 0x1f,
        0,
        0
    ];

    let sum = 0;
    for (let i = 1; i < bytes.length; ++i) {
        sum += bytes[i];
    }
    bytes[0] = sum & 0xff;

    return bytes;
};

const encode = info => {
    const bytes = serializeToBytes(info);
    
    let acc = 0n;
    let pos = 1n;
    for (let i = bytes.length; i --> 0;) {
        acc += BigInt(bytes[i]) * pos;
        pos *= 256n;
    }
    
    let result = '';
    for (let i = 0; i < 25; ++i) {
        const digit = acc % DICTIONARY_LENGTH;
        acc = acc / DICTIONARY_LENGTH;
        result += DICTIONARY.charAt(Number(digit));
    }
    
    return `CSGO-${result.slice(0, 5)}-${result.slice(5, 10)}-${result.slice(10, 15)}-${result.slice(15, 20)}-${result.slice(20, 25)}`;
};