gctGenerator/site/.vuepress/components/codes/asm.js
2023-05-17 22:28:41 +09:00

327 lines
7.8 KiB
JavaScript

import charInfoJP from '../../data/charInfo-JP.json';
import charInfoEU from '../../data/charInfo-EU.json';
import charInfoUS from '../../data/charInfo-US.json';
/**
* @typedef {number} Inst
*
* @typedef {(
* rT: number,
* rA: number,
* D: number,
* ) => Inst} InstD
* @typedef {(
* rS: number,
* rA: number,
* D: number,
* ) => Inst} InstDS
*
* @typedef {(
* rA: number,
* rS: number,
* SH: number,
* MB: number,
* ME: number,
* Rc: number|boolean,
* ) => Inst} InstM
*
* @typedef {(
* rT: number,
* rA: number,
* rB: number,
* Rc?: number|boolean,
* ) => Inst} InstX
* @typedef {(
* rS: number,
* rA: number,
* rB: number,
* Rc: number|boolean,
* ) => Inst} InstXS
*
* @typedef {(
* LL: number,
* LK: number|boolean,
* AA?: number|boolean,
* ) => Inst} InstI
*/
/**
* @param {number} op
* @param {number} rT
* @param {number} rA
* @param {number} D
*/
const InstD = (op, rT, rA, D) =>
((op & 0x3f) << 26) | ((rT & 0x1f) << 21) | ((rA & 0x1f) << 16) | (D & 0xffff);
/**
* @param {number} op
* @param {number} rT
* @param {number} rA
* @param {number} rB
* @param {number} op2
* @param {number} Rc
*/
const InstX = (op, rT, rA, rB, op2, Rc) =>
((op & 0x3f) << 26) |
((rT & 0x1f) << 21) |
((rA & 0x1f) << 16) |
((rB & 0x1f) << 11) |
((op2 & 0x3ff) << 1) |
Rc;
/**
* @param {number} op
* @param {number} RS
* @param {number} RA
* @param {number} SH
* @param {number} MB
* @param {number} ME
* @param {number} Rc
*/
const InstM = (op, RA, RS, SH, MB, ME, Rc) =>
((op & 0x3f) << 26) |
((RS & 0x1f) << 21) |
((RA & 0x1f) << 16) |
((SH & 0x1f) << 11) |
((MB & 0x1f) << 6) |
((ME & 0x1f) << 1) |
Rc;
/**
* @param {number} op
* @param {number} LL
* @param {number} AA
* @param {number} LK
*/
const InstI = (op, LL, AA, LK) =>
((op & 0x3f) << 26) | ((LL & 0xffffff) << 2) | ((AA & 1) << 1) | (LK & 1);
/** @type {(op: number) => InstD} */
const makeInstD = (op) => (rT, rA, D) => InstD(op, rT, rA, D);
/** @type {(op: number) => InstDS} */
const makeInstDS = (op) => (rA, rS, D) => InstD(op, rA, rS, D);
/** @type {(op: number, op2: number) => InstX} */
const makeInstX =
(op, op2) =>
(rT, rA, rB, Rc = 0) =>
InstX(op, rT, rA, rB, op2, +Rc);
/** @type {(op: number, op2: number) => InstXS} */
const makeInstXS = (op, op2) => (rA, rS, rB, Rc) => InstX(op, rA, rS, rB, op2, +Rc);
/** @type {(op: number) => InstM} */
const makeInstM = (op) => (rA, rS, SH, MB, ME, Rc) => InstM(op, rA, rS, SH, MB, ME, +Rc);
/** @type {(op: number) => InstI} */
const makeInstI =
(op) =>
(LL, LK, AA = 0) =>
InstI(op, LL >> 2, +AA, +LK);
export const ASM = {
// store rT, rA, D
stb: makeInstD(38),
sth: makeInstD(44),
stw: makeInstD(36),
stfs: makeInstD(52),
stfd: makeInstD(54),
stwu: makeInstD(37),
// load rS, rA, D
lbz: makeInstD(34),
lhz: makeInstD(40),
lha: makeInstD(42),
lwz: makeInstD(32),
lfs: makeInstD(48),
lfd: makeInstD(50),
// add
add: makeInstX(31, 266),
// li rT, D
addi: makeInstD(14),
li: (/**@type{number}*/ rT, /**@type{number}*/ D) => InstD(14, rT, 0, D),
addis: makeInstD(15),
lis: (/**@type{number}*/ rT, /**@type{number}*/ D) => InstD(15, rT, 0, D),
// ori rA, rS, D
ori: makeInstDS(24),
// or rA, rS, rB, flag
or: makeInstXS(31, 444),
mr: (/**@type{number}*/ rT, /**@type{number}*/ rA, flag = 0) => InstX(31, rA, rT, rA, 444, flag),
// mask
rlwinm: makeInstM(21),
rlwimi: makeInstM(20),
// b
b: makeInstI(18),
bctr: (/**@type{number|boolean}*/ LK = 0) => InstX(19, 0b10100, 0, 0, 528, LK ? 1 : 0),
// mflr
mflr: (/**@type{number}*/ rT) => InstX(31, rT, 8, 0, 339, 0),
mfctr: (/**@type{number}*/ rT) => InstX(31, rT, 9, 0, 339, 0),
// mtlr
mtlr: (/**@type{number}*/ rS) => InstX(31, rS, 8, 0, 467, 0),
mtctr: (/**@type{number}*/ rS) => InstX(31, rS, 9, 0, 467, 0),
// cr
crset: (/**@type{number}*/ B) => InstX(19, B, B, B, 289, 0),
crclr: (/**@type{number}*/ B) => InstX(19, B, B, B, 193, 0),
// float
fmr: (/**@type{number}*/ fT, /**@type{number}*/ fB, Rc = 0) => InstX(63, fT, 0, fB, 72, Rc),
};
export const $load = {
8: ASM.lbz,
16: ASM.lhz,
32: ASM.lwz,
[-16]: ASM.lha,
[-32]: ASM.lwz,
float: ASM.lfs,
};
export const $store = {
8: ASM.stb,
16: ASM.sth,
32: ASM.stw,
[-16]: ASM.sth,
[-32]: ASM.stw,
float: ASM.stfs,
double: ASM.stfd,
};
/**
* @param {number} rT
* @param {number} D
*/
export function liDX(rT, D) {
if (-0x8000 <= D && D < 0x8000) {
return [ASM.li(rT, D)];
} else if ((D & 0xffff) === 0) {
return [ASM.lis(rT, D >>> 16)];
} else {
const h = D >>> 16;
const l = D & 0xffff;
return [ASM.lis(rT, h), ASM.ori(rT, rT, l)];
}
}
/**
* @param {string} s
* @param {string} version
*/
export function str2bytes(s, version) {
/** @type {Record<string, (typeof charInfoJP)[' ']>} */
const charInfo = version.startsWith('GMSJ')
? charInfoJP
: version === 'GMSE01'
? charInfoUS
: charInfoEU;
const fmtbuf = Array.from(s).flatMap((c) => {
const code = charInfo[c]?.code ?? c.charCodeAt(0); // TODO multi-byte invalid char
return code >= 0x100 ? [code >> 8, code & 0xff] : [code];
});
fmtbuf.push(0); // NUL terminated
return fmtbuf;
}
/**
* @param {string} s
* @param {string} version
*/
export const str2hex = (s, version) =>
str2bytes(s, version)
.map((x) => x.toString(16).toUpperCase().padStart(2, '0'))
.join('');
/**
* @param {string} s
* @param {string} version
*/
export function str2inst(s, version) {
const fmtbuf = str2bytes(s, version);
const fmtlen = fmtbuf.length;
const fmtlen3 = fmtlen & 3;
const pad = fmtlen3 ? 4 - fmtlen3 : 0;
fmtbuf.push(...Array(pad).fill(0));
const dv = new DataView(Uint8Array.from(fmtbuf).buffer);
const insts = Array((fmtlen + pad) >> 2)
.fill(0)
.map((_, i) => dv.getUint32(i << 2, false));
return insts;
}
/**
* @param {number} pc
* @param {string} [hex]
*/
export const makeProgram = (pc, hex = '') => ({
pc,
hex,
/**
* @param {number} dst
* @param {boolean} LL
*/
b(dst, LL = false) {
// TODO check overflow
this.push(ASM.b(dst - this.pc, LL));
},
/** @param {number} dst */
bl(dst) {
this.b(dst, true);
},
/** @param {Inst[]} codes */
push(...codes) {
this.hex += codes.map(inst2gecko).join('');
this.pc += codes.length << 2;
},
/** @param {string} data */
pushHex(data) {
this.hex += data;
this.pc += data.length >> 1;
},
align() {
const l4 = this.pc % 4;
if (l4) {
const diff = 4 - l4;
this.hex += ''.padEnd(diff << 1, '0');
this.pc += diff;
}
},
});
/** @param {number} x */
export const inst2gecko = (x) => (x >>> 0).toString(16).toUpperCase().padStart(8, '0');
/** @param {Inst[]} insts */
export const insts2hex = (insts) => insts.map(inst2gecko).join('');
/**
* @param {{
* x: number,
* y: number,
* fontSize: number,
* bgRGB: number,
* bgA: number,
* bgLeft: number,
* bgRight: number,
* bgTop: number,
* bgBot: number
* }} opt
* @param {{width: number, height: number}} size
**/
export const getFillRectParams = (
{ x, y, fontSize, bgRGB, bgA, bgLeft, bgRight, bgTop, bgBot },
{ width, height },
) => [
// rect
x - bgLeft, // x0
y - fontSize - bgTop, // y0
x + Math.ceil((width * fontSize) / 20) + bgRight, // x1
y - fontSize + Math.ceil((height * fontSize) / 20) + bgBot, // y1
// color
(bgRGB << 8) | bgA,
];
/**
* @param {{
* x: number,
* y: number,
* fontSize: number,
* fgRGB: number
* fgA: number
* fgRGB2: number | null
* fgA2: number | null
* }} config
*/
export function getDrawTextOpt({ x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2 }) {
const colorTop = (fgRGB << 8) | fgA;
const colorBot = fgRGB2 == null || fgA2 == null ? colorTop : (fgRGB2 << 8) | fgA;
return [((x & 0xffff) << 16) | (y & 0xffff), fontSize, colorTop, colorBot];
}