マリオメーカー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
(ツイートは削除されました)
大元の動画
サンプルコード(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;
}