322 lines
7.7 KiB
JavaScript
322 lines
7.7 KiB
JavaScript
import charInfoJP from '../../data/charInfo-JP.json';
|
|
import charInfoEU from '../../data/charInfo-EU.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 : charInfoEU; // TODO US
|
|
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];
|
|
}
|