sup39
898ea733ac
- Reduced parameters to struct pointer + format string + varargs - Rewrote QFT, Pattern Selector, Customized Display with the new drawText function - Added PAL font (TODO: NTSC-U) - Merged P/A/S Display and Speed Display to Customized Display - Provided background options to Pattern Selector and Customized Display
268 lines
7.3 KiB
JavaScript
268 lines
7.3 KiB
JavaScript
import { parseJSON } from '../codegen.js';
|
|
import { getFillRectParams } from '../asm';
|
|
import { int2hex } from '../utils';
|
|
export const lskey = 'config/qft';
|
|
|
|
export const getPreviewText = () => '0:00.000';
|
|
|
|
export const defaultConfig = {
|
|
x: 16,
|
|
y: 456,
|
|
fontSize: 20,
|
|
fgRGB: 0xffffff,
|
|
fgA: 0xff,
|
|
fgRGB2: null,
|
|
fgA2: null,
|
|
bgRGB: 0x000000,
|
|
bgA: 0x80,
|
|
bgLeft: 0,
|
|
bgRight: 2,
|
|
bgTop: 2,
|
|
bgBot: 0,
|
|
freezeDuration: 30,
|
|
freeze: {
|
|
yellowCoin: false,
|
|
redCoin: true,
|
|
blueCoin: true,
|
|
item: true,
|
|
talk: true,
|
|
demo: true,
|
|
cleaned: true,
|
|
bowser: true, // onBathhubGripDestroyed
|
|
yoshi: true,
|
|
take: true,
|
|
drop: true,
|
|
put: true,
|
|
tripleJump: true,
|
|
spinJump: true,
|
|
ledgeGrab: true,
|
|
wallKick: true,
|
|
ropeJump: true,
|
|
bounce: true,
|
|
},
|
|
};
|
|
|
|
/** @returns {typeof defaultConfig} */
|
|
export function getConfig() {
|
|
const config =
|
|
(typeof localStorage !== 'undefined' && parseJSON(localStorage.getItem(lskey))) || {};
|
|
return {
|
|
...defaultConfig,
|
|
...config,
|
|
freeze: {
|
|
...defaultConfig.freeze,
|
|
...config.freeze,
|
|
},
|
|
text: getPreviewText(),
|
|
};
|
|
}
|
|
|
|
/** @param {number} x */
|
|
const int2gecko = (x) => (x >>> 0).toString(16).toUpperCase().padStart(8, '0');
|
|
|
|
import * as GMSJ01 from './code/GMSJ01.js';
|
|
import * as GMSE01 from './code/GMSE01.js';
|
|
import * as GMSP01 from './code/GMSP01.js';
|
|
import * as GMSJ0A from './code/GMSJ0A.js';
|
|
export const codes = { GMSJ01, GMSE01, GMSP01, GMSJ0A };
|
|
|
|
import { measureText } from '../text.js';
|
|
import statusDB0 from './code/status.js';
|
|
const statusDB = /**@type{Record<string,number[]>}*/ (statusDB0);
|
|
export const statusKeys = Object.keys(statusDB0);
|
|
|
|
/****
|
|
## save freeze frame, load and save QF
|
|
## this function destroys r11 and r12
|
|
077F0348:
|
|
lwz r11, gpMarDirector-_SDA_BASE_(r13)
|
|
lis r12, 0x817F
|
|
lwz r11, 0x5C(r11)
|
|
stw r11, 0xB8(r12)
|
|
li r11, freezeDuration
|
|
stw r11, 0xBC(r12)
|
|
blr
|
|
|
|
## for each hook (over a blr): b 817f0348
|
|
****/
|
|
const freezeCodeAddr = 0x817f0348;
|
|
/**
|
|
* @param {keyof typeof codes} version
|
|
* @param {string=} baseCode
|
|
*/
|
|
export default function codegen(version, baseCode) {
|
|
if (!baseCode) return '';
|
|
|
|
const config = getConfig();
|
|
const { freezeCodeHooks, r13off, onChangeStatusAddr } = codes[version] ?? {};
|
|
|
|
let code = baseCode;
|
|
const { freezeDuration: frame } = config;
|
|
|
|
// freezing code
|
|
const enabledFreezes = [];
|
|
const statuses = [];
|
|
if (frame > 0) {
|
|
for (const [key, enabled] of Object.entries(config.freeze)) {
|
|
if (!enabled) continue;
|
|
// add status
|
|
statuses.push(...(statusDB[key] ?? []));
|
|
// add hook
|
|
const hook = freezeCodeHooks[key];
|
|
if (hook) {
|
|
if (key === 'blueCoin') {
|
|
const addr = /**@type{number}*/ (hook);
|
|
// special: needs to adjust QF -> use separate C2 instead
|
|
code += [
|
|
0xc2000000 + (addr & 0x1ffffff),
|
|
0x00000005,
|
|
0x7c030378,
|
|
0x80a3005c,
|
|
0x38a50003,
|
|
0x54a0003a,
|
|
0x3ca0817f,
|
|
0x900500b8,
|
|
0x38000000 | (frame & 0xffff),
|
|
0x900500bc,
|
|
0x60000000,
|
|
0x00000000,
|
|
]
|
|
.map(int2gecko)
|
|
.join('');
|
|
} else if (typeof hook === 'number') {
|
|
// handle regular freezing code later
|
|
const addr = hook;
|
|
enabledFreezes.push(addr);
|
|
} else {
|
|
// {addr: number, orig: number}
|
|
// separate C2 code to handle orig
|
|
const { addr, orig } = hook;
|
|
code += [
|
|
0xc2000000 + (addr & 0x1ffffff),
|
|
0x00000003,
|
|
0x3d800000 + (freezeCodeAddr >>> 16), // lis r12, freezeCodeAddr@h
|
|
0x618c0000 + (freezeCodeAddr & 0xffff), // ori r12, r12, freezeCodeAddr@l
|
|
0x7d8803a6, // mtlr r12
|
|
0x4e800021, // blrl
|
|
orig,
|
|
0x00000000,
|
|
]
|
|
.map(int2gecko)
|
|
.join('');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// handle regular freezing code
|
|
if (enabledFreezes.length <= 1 && statuses.length === 0) {
|
|
// use C2 directly
|
|
code += enabledFreezes
|
|
.flatMap((addr) => [
|
|
0xc2000000 + (addr & 0x1ffffff),
|
|
0x00000004,
|
|
0x816d0000 | (r13off & 0xffff), // lwz r11, r13off(r13)
|
|
0x3d80817f, // lis r12, 0x817F
|
|
0x816b005c, // lwz r11, 0x5C(r11)
|
|
0x916c00b8, // stw r11, 0xB8(r12)
|
|
0x39600000 | (frame & 0xffff), // li r11, frame
|
|
0x916c00bc, // stw r11, 0xBC(r12)
|
|
0x60000000, // nop
|
|
0x00000000,
|
|
])
|
|
.map(int2gecko)
|
|
.join('');
|
|
} else {
|
|
// could be shorter to turn this into a Gecko loop if enough freezes are enabled
|
|
const hooks = enabledFreezes.flatMap((addr) => [
|
|
0xc6000000 | (addr & 0x1ffffff),
|
|
freezeCodeAddr,
|
|
]);
|
|
|
|
const freezer = [
|
|
0x06000000 | (freezeCodeAddr & 0x1ffffff),
|
|
0x0000001c,
|
|
0x816d0000 | (r13off & 0xffff), // lwz r11, r13off(r13)
|
|
0x3d80817f, // lis r12, 0x817F
|
|
0x816b005c, // lwz r11, 0x5C(r11)
|
|
0x916c00b8, // stw r11, 0xB8(r12)
|
|
0x39600000 | (frame & 0xffff), // li r11, frame
|
|
0x916c00bc, // stw r11, 0xBC(r12)
|
|
0x4e800020, // blr
|
|
0x00000000,
|
|
];
|
|
|
|
// apply code
|
|
code += [...hooks, ...freezer].map(int2gecko).join('');
|
|
}
|
|
|
|
// onChangeStatus hook
|
|
if (statuses.length) {
|
|
const c = [
|
|
// check each status
|
|
...statuses.flatMap((x, i) => {
|
|
const cr = i > 0 ? 0x800000 : 0; // i>0 ? cr1 : cr0
|
|
const c =
|
|
x < 0x10000
|
|
? [
|
|
0x281d0000 + cr + x, // cmplwi crX, r29, $x
|
|
]
|
|
: [
|
|
0x3c000000 + (x >>> 16), // lis r0, $x@h
|
|
0x60000000 + (x & 0xffff), // ori r0, r0, $x@l
|
|
0x7c1d0040 + cr, // cmplw crX, r29, r0
|
|
];
|
|
if (i > 0) {
|
|
// cror 4*cr0+eq, 4*cr0+eq, 4*cr1+eq
|
|
c.push(0x4c423382);
|
|
}
|
|
return c;
|
|
}),
|
|
// freeze
|
|
0x3d800000 + (freezeCodeAddr >>> 16), // lis r12, freezeCodeAddr@h
|
|
0x618c0000 + (freezeCodeAddr & 0xffff), // ori r12, r12, freezeCodeAddr@l
|
|
0x7d8803a6, // mtlr r12
|
|
0x4d820021, // beqlrl
|
|
// orig
|
|
0x38000000, // li r0, 0
|
|
];
|
|
|
|
// pad nop
|
|
if (c.length % 2 === 0) {
|
|
c.push(0x60000000);
|
|
}
|
|
// end of C2
|
|
c.push(0x00000000);
|
|
|
|
// apply code
|
|
code += [
|
|
0xc2000000 + (onChangeStatusAddr & 0x1ffffff),
|
|
c.length >> 1, // line count
|
|
...c,
|
|
]
|
|
.map(int2gecko)
|
|
.join('');
|
|
}
|
|
|
|
// ui
|
|
/* bounds */
|
|
const { x, y, fontSize, bgLeft, bgRight, bgTop, bgBot } = config;
|
|
const rect = getFillRectParams(config, measureText(getPreviewText(), version));
|
|
const [bgColor] = rect.splice(-1);
|
|
const scale = fontSize / 20;
|
|
code += '077F0094 0000001D';
|
|
code += rect.map(int2gecko).join('');
|
|
code += '25753a253032752e2530337500000000'; // fmt
|
|
/**
|
|
* 817F0110 drawTextOpt: {x, y, fontSize, colorTop, colorBot}
|
|
* 817F0120 bgColor
|
|
*/
|
|
code += '077F0110 00000014';
|
|
const fgColor = (config.fgRGB & 0xffffff) * 256 + (config.fgA & 0xff);
|
|
const fgColor2 =
|
|
((config.fgRGB2 ?? config.fgRGB) & 0xffffff) * 256 + ((config.fgA2 ?? config.fgA) & 0xff);
|
|
code += [x, y].map((x) => int2hex(x, 2)).join('');
|
|
code += [fontSize, fgColor, fgColor2, bgColor].map(int2gecko).join('');
|
|
code += '00000000';
|
|
|
|
return code.replace(/\s/g, '');
|
|
}
|