リオメーカー2のコースIDの仕様

マリオメーカー2のコースIDで使用できる英数字はA,E,I,O,U,Zを除く20字のアルファベットと0-9の計30文字です。これを30進数として考えます。

まず、コースIDの並び順を逆にします。逆順にしたIDを2進数に変換します。変換した2進数を4、6、20、1、1、12桁の6つに分ける(それぞれの桁数を A から F の6つの記号で表す)。

A は1000、Eは1、Dはコースの種類(0が通常コース、1が職人ID)で確定します。

F、Cの順に並べた32桁の2進数と、定数 1680E07C(16)の排他的論理和を計算し、Nとする。

B はN - 31から64を割った余りと等しくなります。

最後にNから3000000を引いた数字が、投稿されたコースの通し番号となります。

これを利用することによりIDの並びだけで通常コースか職人か、または間違っていないかを判定することが可能です。但し、投稿後削除されてしまったコースは判定することは出来ません。

また、逆の処理をすることにより通し番号からIDを生成することが可能です。


具体例 ”5D1N7X8LF”

〇IDを30進数にして逆順に

 DI8S7K1C5(30)

〇2進数に変換

1000 000111 11000011100110011010 0 1 000101000001

 A           B                                C                       D   E                 F

〇Nの計算

00010100000111000011100110011010(2) xor 1680E07C(16)

=43833830(10)

〇通し番号

43833830-3000000=40833830

TGRでの確認(tgrでは"data_id”の欄にNの数値のまま入っています)

https://tgrcode.com/mm2/level_info/5D1N7X8LF

参考文献

ツイート

https://twitter.com/smm2_shin/status/1560135021351956485?s=20

(ツイートは削除されました)

大元の動画

https://t.co/hJzxdOW8Z0


サンプルコード(JavaScript)

/**

 * 渡されたIDを解析し、各部分を抽出します。

 * @param {string} id - 解析するID(ハイフンを含んでいても可)

 * @returns {string} - 各部分の解析結果を文字列で返します。

 */

function analyzeId(id = "5D1N7X8LF"){

  // ハイフンを取り除く

  const cleanedInput = id.replace(/-/g, "");

  // 9桁ではない場合は無効

  if (cleanedInput.length !== 9) {

    return "invalid";

  }

  // 文字列を30進数に変換して逆順に

  const reversedBase30 = cleanedInput.split('').map(convertToBase30).reverse();

  // 配列を文字列に

  const base30String = reversedBase30.join("");

  // 30進数を10進数に変換

  const decimalNumber = parseInt(base30String, 30);

  // 10進数を2進数に変換

  const binaryString = decimalNumber.toString(2);

  // 入力文字列を分割する長さの配列

  const lengths = [4, 6, 20, 1, 1, 12];

  let currentPosition = 0;

  const result = [];

 

  for (const length of lengths) {

    result.push(binaryString.slice(currentPosition, currentPosition + length));

    currentPosition += length;

  }

  const [A,B,C,D,E,F] = result;

  const N = (parseInt(F + C,2)  ^ parseInt("1680E07C",16)) - 3000000;

  console.log(`Aは"${A}"\nBは"${B}"\nCは"${C}"\nDは"${D}"\nEは"${E}"\nFは"${F}"\nN(番号)は"${N}"`);

  return N;

}


/**

 * 文字を30進数表記に変換します。

 * 入力文字が数字の場合、そのままの文字を返します。

 * 入力文字が指定された文字(B、C、D、...)のいずれかの場合、その30進数表記を返します。

 * 入力文字が認識されない場合、undefinedを返します。

 *

 * @param {string} char - 変換する入力文字です。

 * @returns {string|undefined} - 入力文字の30進数表記、または認識されない場合はundefinedを返します。

 */

function convertToBase30(char) {

 

  // 入力文字が数字の場合、そのままの文字を返す

  if (/^[0-9]$/.test(char)) {

    return char;

  }

 

  // 文字を30進数表記に変換するマップ

  const charMap = {

    "B": 10, "C": 11, "D": 12, "F": 13, "G": 14,

    "H": 15, "J": 16, "K": 17, "L": 18, "M": 19,

    "N": 20, "P": 21, "Q": 22, "R": 23, "S": 24,

    "T": 25, "V": 26, "W": 27, "X": 28, "Y": 29

  };

  // 入力文字を30進数表記に変換

  const x = charMap[char];

 

  // 文字が認識された場合、その30進数表記を返す

  // それ以外の場合、undefinedを返す

  return x !== undefined ? x.toString(30) : undefined;

}


/**

 * 指定された数値から一意のIDを生成します。

 *

 * @param {number} number - 生成するIDに対応する数値です。

 * @param {number} [type=0] - 生成するIDの種類を指定する数値です。0の場合はコースID、1の場合は職人IDを生成します。

 * @returns {string} - 生成されたIDです。

 */

function getIDFromNumber(number = 40833830,type = 0){

  const A = "1000";

  const E = "1";

  const D = type;

  number += 3000000;

  const B = ("00000" + ((number-31) % 64).toString(2)).slice(-6)

  //ゼロパティング

  const binaryNumber = ("00000000000000000000000000000000" + (number ^ parseInt("1680E07c",16)).toString(2)).slice(-32)

  const C = binaryNumber.slice(-20);

  const F = binaryNumber.slice(0,12);

  const base30Number = parseInt((A + B + C + D + E + F),2).toString(30);

  const id = base30Number.split('').map(convertToChar).reduce((acc, val, index) => index % 3 === 0 ? `${val}-${acc}` : val + acc, '');

  const idType = type === 0 ? "コースID" : "職人ID";

  console.log(`${id}(${idType})`);

  return id;

}


/**

 * 30進数表記の数字を文字に変換します。

 * 入力が数字の場合、そのままの文字を返します。

 * 入力が指定された30進数表記の数字(10以上の場合)の場合、対応する文字を返します。

 * 入力が認識されない場合、undefinedを返します。

 *

 * @param {string} number - 変換する入力30進数表記の数字です。

 * @returns {string|undefined} - 入力30進数表記の数字に対応する文字、または認識されない場合はundefinedを返します。

 */

function convertToChar(number) {

  // 入力が数字の場合、そのままの文字を返す

  if (/^[0-9]$/.test(number)) {

    return number;

  }

  number = parseInt(number,30);

  // 30進数表記の数字に対応する文字を取得するマップ

  const charMap = {

    "10": "B", "11": "C", "12": "D", "13": "F", "14": "G",

    "15": "H", "16": "J", "17": "K", "18": "L", "19": "M",

    "20": "N", "21": "P", "22": "Q", "23": "R", "24": "S",

    "25": "T", "26": "V", "27": "W", "28": "X", "29": "Y"

  };

  // 入力30進数表記の数字に対応する文字を取得

  const char = charMap[number];

  // 文字が認識された場合、対応する文字を返す

  // それ以外の場合、undefinedを返す

  return char !== undefined ? char : undefined;

}