Customized Display 0.4
- Rewrote with C2 - Implemented a more complex assembler/compiler to support function call - Add options - Invincibility Timer - Pollution Degree - Spin Jump Condition Check
This commit is contained in:
parent
69e8f17a24
commit
3a730390ac
17 changed files with 969 additions and 601 deletions
170
Codes.xml
170
Codes.xml
|
@ -2780,8 +2780,8 @@
|
|||
<title lang="en-US">Customized Display</title>
|
||||
<title lang="ja-JP">カスタマイズ表示</title>
|
||||
<author>sup39(サポミク)</author>
|
||||
<version>0.3</version>
|
||||
<date>Jan 28, 2023</date>
|
||||
<version>0.4</version>
|
||||
<date>Jan 31, 2023</date>
|
||||
<dependencies>drawText</dependencies>
|
||||
<description lang="en-US">
|
||||
Shows metadata at any given time.
|
||||
|
@ -2800,6 +2800,9 @@
|
|||
|`VSpd`|Vertical speed of Mario|float|
|
||||
|`QF`|QF offset|\{0,1,2,3}|
|
||||
|`CAngle`|Camera Angle|uint16|
|
||||
|`invinc`|Invincibility Timer (frame)|int16|
|
||||
|`goop`|Pollution Degree (<600 to complete SB6)|int32|
|
||||
|`spin`|Whether satisfying spin jump condition|Show 🅐 if YES|
|
||||
|
||||
For float data, you can set the *format* to `.{digit}` to specify how many digits to show.
|
||||
|
||||
|
@ -2831,6 +2834,9 @@
|
|||
|`VSpd`|マリオのY速度|float|
|
||||
|`QF`|ずれたQFの数|\{0,1,2,3}|
|
||||
|`CAngle`|カメラの角度|uint16|
|
||||
|`invinc`|無敵時間(フレーム数)|int16|
|
||||
|`goop`|汚れの量(600未満でSB6クリア)|int32|
|
||||
|`spin`|スピン入力の判定|条件を満たせば🅐を表示|
|
||||
|
||||
float(小数)型に対して、「表示のフォーマット」を`.{桁数}`に設定して何桁まで表示するか指定できます。
|
||||
|
||||
|
@ -2848,88 +2854,96 @@
|
|||
#### プレビュー
|
||||
</description>
|
||||
<source version="GMSJ01">
|
||||
C62069D4 817FA000
|
||||
077FA000 00000084
|
||||
806D98B8 C0230010
|
||||
C0430014 C0630018
|
||||
A0A30096 C08300B0
|
||||
C0A300A8 48000059
|
||||
001000C8 00000014
|
||||
FFFFFFFF FFFFFFFF
|
||||
5820506F 7320252E
|
||||
30660A59 20506F73
|
||||
20252E30 660A5A20
|
||||
506F7320 252E3066
|
||||
0A416E67 6C652025
|
||||
68750A48 20537064
|
||||
20252E32 660A5620
|
||||
53706420 252E3266
|
||||
00000000 7C6802A6
|
||||
38830010 4BFF61BD
|
||||
4AA0C958 00000000
|
||||
C22069D4 00000014
|
||||
9421FFF0 806D98B8
|
||||
C0230010 C0430014
|
||||
C0630018 A0A30096
|
||||
C08300B0 C0A300A8
|
||||
48000015 001000C8
|
||||
00000014 FFFFFFFF
|
||||
FFFFFFFF 7C6802A6
|
||||
48000049 5820506F
|
||||
7320252E 30660A59
|
||||
20506F73 20252E30
|
||||
660A5A20 506F7320
|
||||
252E3066 0A416E67
|
||||
6C652025 68750A48
|
||||
20537064 20252E32
|
||||
660A5620 53706420
|
||||
252E3266 00000000
|
||||
7C8802A6 3D80817F
|
||||
618C0238 7D8903A6
|
||||
4E800421 38210010
|
||||
60000000 00000000
|
||||
</source>
|
||||
<source version="GMSJ0A">
|
||||
C6125540 817FA000
|
||||
077FA000 00000084
|
||||
806D9DE8 C0230010
|
||||
C0430014 C0630018
|
||||
A0A30096 C08300B0
|
||||
C0A300A8 48000059
|
||||
001000C8 00000014
|
||||
FFFFFFFF FFFFFFFF
|
||||
5820506F 7320252E
|
||||
30660A59 20506F73
|
||||
20252E30 660A5A20
|
||||
506F7320 252E3066
|
||||
0A416E67 6C652025
|
||||
68750A48 20537064
|
||||
20252E32 660A5620
|
||||
53706420 252E3266
|
||||
00000000 7C6802A6
|
||||
38830010 4BFF61BD
|
||||
4A92B4C4 00000000
|
||||
C2125540 00000014
|
||||
9421FFF0 806D9DE8
|
||||
C0230010 C0430014
|
||||
C0630018 A0A30096
|
||||
C08300B0 C0A300A8
|
||||
48000015 001000C8
|
||||
00000014 FFFFFFFF
|
||||
FFFFFFFF 7C6802A6
|
||||
48000049 5820506F
|
||||
7320252E 30660A59
|
||||
20506F73 20252E30
|
||||
660A5A20 506F7320
|
||||
252E3066 0A416E67
|
||||
6C652025 68750A48
|
||||
20537064 20252E32
|
||||
660A5620 53706420
|
||||
252E3266 00000000
|
||||
7C8802A6 3D80817F
|
||||
618C0238 7D8903A6
|
||||
4E800421 38210010
|
||||
60000000 00000000
|
||||
</source>
|
||||
<source version="GMSE01">
|
||||
C61441B4 817FA000
|
||||
077FA000 00000084
|
||||
806D9F28 C0230010
|
||||
C0430014 C0630018
|
||||
A0A30096 C08300B0
|
||||
C0A300A8 48000059
|
||||
001000C8 00000014
|
||||
FFFFFFFF FFFFFFFF
|
||||
5820506F 7320252E
|
||||
30660A59 20506F73
|
||||
20252E30 660A5A20
|
||||
506F7320 252E3066
|
||||
0A416E67 6C652025
|
||||
68750A48 20537064
|
||||
20252E32 660A5620
|
||||
53706420 252E3266
|
||||
00000000 7C6802A6
|
||||
38830010 4BFF61BD
|
||||
4A94A138 00000000
|
||||
C21441B4 00000014
|
||||
9421FFF0 806D9F28
|
||||
C0230010 C0430014
|
||||
C0630018 A0A30096
|
||||
C08300B0 C0A300A8
|
||||
48000015 001000C8
|
||||
00000014 FFFFFFFF
|
||||
FFFFFFFF 7C6802A6
|
||||
48000049 5820506F
|
||||
7320252E 30660A59
|
||||
20506F73 20252E30
|
||||
660A5A20 506F7320
|
||||
252E3066 0A416E67
|
||||
6C652025 68750A48
|
||||
20537064 20252E32
|
||||
660A5620 53706420
|
||||
252E3266 00000000
|
||||
7C8802A6 3D80817F
|
||||
618C0238 7D8903A6
|
||||
4E800421 38210010
|
||||
60000000 00000000
|
||||
</source>
|
||||
<source version="GMSP01">
|
||||
C6138DF0 817FA000
|
||||
077FA000 00000084
|
||||
806D9E50 C0230010
|
||||
C0430014 C0630018
|
||||
A0A30096 C08300B0
|
||||
C0A300A8 48000059
|
||||
001000C8 00000014
|
||||
FFFFFFFF FFFFFFFF
|
||||
5820506F 7320252E
|
||||
30660A59 20506F73
|
||||
20252E30 660A5A20
|
||||
506F7320 252E3066
|
||||
0A416E67 6C652025
|
||||
68750A48 20537064
|
||||
20252E32 660A5620
|
||||
53706420 252E3266
|
||||
00000000 7C6802A6
|
||||
38830010 4BFF61BD
|
||||
4A93ED74 00000000
|
||||
C2138DF0 00000014
|
||||
9421FFF0 806D9E50
|
||||
C0230010 C0430014
|
||||
C0630018 A0A30096
|
||||
C08300B0 C0A300A8
|
||||
48000015 001000C8
|
||||
00000014 FFFFFFFF
|
||||
FFFFFFFF 7C6802A6
|
||||
48000049 5820506F
|
||||
7320252E 30660A59
|
||||
20506F73 20252E30
|
||||
660A5A20 506F7320
|
||||
252E3066 0A416E67
|
||||
6C652025 68750A48
|
||||
20537064 20252E32
|
||||
660A5620 53706420
|
||||
252E3266 00000000
|
||||
7C8802A6 3D80817F
|
||||
618C0238 7D8903A6
|
||||
4E800421 38210010
|
||||
60000000 00000000
|
||||
</source>
|
||||
</code>
|
||||
<code>
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
# Changelog
|
||||
## Jan 31, 2023
|
||||
### Updated 'Customized Display'
|
||||
- Rewrote with C2
|
||||
- Implemented a more complex assembler/compiler to support function call
|
||||
- Add options
|
||||
- Invincibility Timer
|
||||
- Pollution Degree
|
||||
- Spin Jump Condition Check
|
||||
|
||||
## Jan 28, 2023
|
||||
### Rewrote 'drawText'
|
||||
- Reduced parameters to struct pointer + format string + varargs
|
||||
|
|
16
package-lock.json
generated
16
package-lock.json
generated
|
@ -17,7 +17,6 @@
|
|||
"@types/encoding-japanese": "^2.0.1",
|
||||
"@vuepress/plugin-back-to-top": "1.9.7",
|
||||
"@vuepress/plugin-medium-zoom": "1.9.7",
|
||||
"encoding-japanese": "^2.0.0",
|
||||
"jsdom": "20.0.2",
|
||||
"pre-commit": "1.2.2",
|
||||
"prettier": "2.7.1",
|
||||
|
@ -6574,15 +6573,6 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding-japanese": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz",
|
||||
"integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
|
@ -22759,12 +22749,6 @@
|
|||
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
|
||||
"dev": true
|
||||
},
|
||||
"encoding-japanese": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/encoding-japanese/-/encoding-japanese-2.0.0.tgz",
|
||||
"integrity": "sha512-++P0RhebUC8MJAwJOsT93dT+5oc5oPImp1HubZpAuCZ5kTLnhuuBhKHj2jJeO/Gj93idPBWmIuQ9QWMe5rX3pQ==",
|
||||
"dev": true
|
||||
},
|
||||
"end-of-stream": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
"@types/encoding-japanese": "^2.0.1",
|
||||
"@vuepress/plugin-back-to-top": "1.9.7",
|
||||
"@vuepress/plugin-medium-zoom": "1.9.7",
|
||||
"encoding-japanese": "^2.0.0",
|
||||
"jsdom": "20.0.2",
|
||||
"pre-commit": "1.2.2",
|
||||
"prettier": "2.7.1",
|
||||
|
|
|
@ -132,13 +132,6 @@ export default {
|
|||
codeConfigs: {},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.codeConfigs = {
|
||||
qft: getConfigQFT(),
|
||||
PatternSelector: getConfigPS(),
|
||||
CustomizedDisplay: getConfigCD(this.version),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getLabel(key) {
|
||||
return translate(key, this.$lang);
|
||||
|
@ -166,6 +159,13 @@ export default {
|
|||
JSON.stringify({ version: e }),
|
||||
]);
|
||||
} catch {}
|
||||
|
||||
// update config for preview
|
||||
this.codeConfigs = {
|
||||
qft: getConfigQFT(),
|
||||
PatternSelector: getConfigPS(),
|
||||
CustomizedDisplay: getConfigCD(e),
|
||||
};
|
||||
},
|
||||
onFormatChanged(e) {
|
||||
this.selectedFormat = e;
|
||||
|
|
214
site/.vuepress/components/codes/CustomizedDisplay/assembler.js
Normal file
214
site/.vuepress/components/codes/CustomizedDisplay/assembler.js
Normal file
|
@ -0,0 +1,214 @@
|
|||
import { ASM, liDX, makeProgram, insts2hex } from '../asm.js';
|
||||
import { splitArray } from '../utils.js';
|
||||
|
||||
/**
|
||||
* @typedef {number} Inst
|
||||
* @typedef {{type: 'call', addr: number, prep?: Inst[]}} CallInst -- function call instruction
|
||||
* @typedef {{type: 'struct', reg: number, hex: string}} LoadStructInst -- struct pointer load instruction
|
||||
* @typedef {Inst|CallInst|LoadStructInst} ASMInst
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {ASMInst[]} insts
|
||||
* @param {number} stackFrameSize
|
||||
*/
|
||||
export function assemble(insts, stackFrameSize) {
|
||||
const rAddr = 12;
|
||||
const rData = 31;
|
||||
|
||||
/**
|
||||
* [0]: data
|
||||
* [.]: body
|
||||
*/
|
||||
const p = makeProgram(0);
|
||||
|
||||
/** @type {Map<number, Map<string, number>>} */
|
||||
const callCounts = new Map(); // [addr][prep] = count
|
||||
/** @type {Set<string>} */
|
||||
const structDB = new Set();
|
||||
|
||||
// summarize data
|
||||
let hasRepeatCall = false;
|
||||
let loadStructCount = 0;
|
||||
let sizeWithoutBLTrick = 0; // # of instruction
|
||||
for (const inst of insts) {
|
||||
if (typeof inst === 'number') continue;
|
||||
const { type } = inst;
|
||||
if (type === 'call') {
|
||||
const { addr, prep = [] } = inst;
|
||||
const prepKey = insts2hex(prep);
|
||||
const prepCounts = callCounts.get(addr);
|
||||
if (prepCounts == null) {
|
||||
callCounts.set(addr, new Map([[prepKey, 1]]));
|
||||
} else {
|
||||
prepCounts.set(prepKey, (prepCounts.get(prepKey) ?? 0) + 1);
|
||||
hasRepeatCall = true;
|
||||
}
|
||||
} else if (type === 'struct') {
|
||||
const { hex } = inst;
|
||||
structDB.add(hex);
|
||||
loadStructCount++;
|
||||
sizeWithoutBLTrick += ((hex.length + 7) >>> 3) + 2; // bl L; mflr
|
||||
}
|
||||
}
|
||||
|
||||
const useSharedCall = hasRepeatCall || callCounts.size > 2;
|
||||
const sizeWithBLTrick =
|
||||
((Array.from(structDB.entries()).reduce((a, [hex]) => a + hex.length, 0) + 7) >>> 3) +
|
||||
loadStructCount + // addi rReg, rData, off
|
||||
(stackFrameSize > 0 ? 0 : 2) +
|
||||
(useSharedCall ? 0 : 1) +
|
||||
3; // stw, mflr, lwz
|
||||
const useBLTrick = sizeWithBLTrick < sizeWithoutBLTrick;
|
||||
|
||||
/** @type {Map<number, {fallback: number, preps: Map<string, number>}>} */
|
||||
const offFuncs = new Map();
|
||||
/** @type {number|null} */
|
||||
let offCall = null;
|
||||
/** @type {Map<string, number>} */
|
||||
const offStructs = new Map();
|
||||
|
||||
if (useSharedCall) {
|
||||
let off = 0;
|
||||
/** @type {string[]} */
|
||||
const funcHexs = [];
|
||||
for (const [addr, prepCounts] of callCounts.entries()) {
|
||||
// put repeated call only
|
||||
if (Array.from(prepCounts).reduce((a, [k, c]) => a + c, 0) <= 1) continue;
|
||||
/** @type {Map<string, number>} */
|
||||
const preps = new Map();
|
||||
const prepKeys = Array.from(prepCounts.keys()).filter((k) => k !== '');
|
||||
let hex = '';
|
||||
// TODO optimize only when one prep (excluding '') is used
|
||||
if (prepKeys.length === 1) {
|
||||
const [prep] = prepKeys;
|
||||
preps.set(prep, off);
|
||||
hex += prep;
|
||||
}
|
||||
offFuncs.set(addr, { fallback: off + hex.length / 2, preps });
|
||||
// liDX addr
|
||||
hex += insts2hex(liDX(rAddr, addr));
|
||||
// b call
|
||||
off += 4 + hex.length / 2;
|
||||
// push
|
||||
funcHexs.push(hex);
|
||||
}
|
||||
const offDst = Math.max(0, off - 4);
|
||||
offCall = offDst;
|
||||
|
||||
// make data
|
||||
/** callX: */
|
||||
funcHexs.forEach((hex, i, arr) => {
|
||||
p.pushHex(hex);
|
||||
// b call # except the last 1
|
||||
if (i < arr.length - 1) {
|
||||
p.b(offDst);
|
||||
}
|
||||
});
|
||||
/** call: */
|
||||
p.push(ASM.mtctr(rAddr), ASM.bctr());
|
||||
}
|
||||
|
||||
// add struct data
|
||||
if (useBLTrick) {
|
||||
// [4-byte aligned data, chars data]
|
||||
const [aligned, chars] = splitArray(structDB, (hex) => hex.length % 8 === 0);
|
||||
for (const data of [aligned, chars]) {
|
||||
for (const hex of data) {
|
||||
offStructs.set(hex, p.pc);
|
||||
p.pushHex(hex);
|
||||
}
|
||||
}
|
||||
// make 4-byte alignment
|
||||
p.align();
|
||||
}
|
||||
|
||||
// make body
|
||||
const dataSize = p.pc;
|
||||
/** mflr rData */
|
||||
if (useBLTrick) p.push(ASM.mflr(rData));
|
||||
/** for each ASM instructions */
|
||||
for (const inst of insts) {
|
||||
if (typeof inst === 'number') {
|
||||
p.push(inst);
|
||||
} else if (inst.type === 'call') {
|
||||
const { prep = [], addr } = inst;
|
||||
const prepKey = insts2hex(prep);
|
||||
const off = offFuncs.get(addr);
|
||||
if (off == null) {
|
||||
// fallback to prepare and load addr manually
|
||||
p.push(...prep, ...liDX(rAddr, addr));
|
||||
// call
|
||||
if (offCall == null) {
|
||||
p.push(
|
||||
// mtctr rAddr
|
||||
ASM.mtctr(rAddr),
|
||||
// bctrl
|
||||
ASM.bctr(true),
|
||||
);
|
||||
} else {
|
||||
p.bl(offCall);
|
||||
}
|
||||
} else {
|
||||
// bl to callX directly
|
||||
const { fallback, preps } = off;
|
||||
const dst = preps.get(prepKey);
|
||||
if (dst == null) {
|
||||
// fallback to prepare manually
|
||||
p.pushHex(prepKey);
|
||||
p.bl(fallback);
|
||||
} else {
|
||||
// bl to dst directly
|
||||
p.bl(dst);
|
||||
}
|
||||
}
|
||||
} else if (inst.type === 'struct') {
|
||||
const { reg, hex } = inst;
|
||||
const off = offStructs.get(hex);
|
||||
if (off == null) {
|
||||
// fallback to use BL trick here
|
||||
/** bl L */
|
||||
const d = ((hex.length + 7) >> 3) << 2;
|
||||
p.push(ASM.b(4 + d, true));
|
||||
/** (data) */
|
||||
p.pushHex(hex);
|
||||
p.align();
|
||||
/** L: mflr rReg */
|
||||
p.push(ASM.mflr(reg));
|
||||
} else {
|
||||
// use addi directly
|
||||
p.push(ASM.addi(reg, rData, off));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @type {Inst[]} */
|
||||
const prologue = [];
|
||||
if (useBLTrick) stackFrameSize += 4; // for r31
|
||||
if (stackFrameSize > 0) {
|
||||
// stack frame size: 16-byte align
|
||||
stackFrameSize = ((stackFrameSize + 0xf) >> 4) << 4;
|
||||
// stwu r1
|
||||
prologue.push(ASM.stwu(1, 1, -stackFrameSize));
|
||||
if (useBLTrick) {
|
||||
// stw r31
|
||||
prologue.push(ASM.stw(31, 1, stackFrameSize - 4));
|
||||
}
|
||||
}
|
||||
if (useSharedCall || useBLTrick) {
|
||||
prologue.push(ASM.b(4 + dataSize, true));
|
||||
}
|
||||
|
||||
/** @type {Inst[]} */
|
||||
const epilogue = [];
|
||||
if (stackFrameSize > 0) {
|
||||
if (useBLTrick) {
|
||||
// lwz r31
|
||||
epilogue.push(ASM.lwz(31, 1, stackFrameSize - 4));
|
||||
}
|
||||
// addi r1
|
||||
epilogue.push(ASM.addi(1, 1, stackFrameSize));
|
||||
}
|
||||
|
||||
return insts2hex(prologue) + p.hex + insts2hex(epilogue);
|
||||
}
|
|
@ -1,19 +1,19 @@
|
|||
import { parseJSON } from '../codegen.js';
|
||||
import {
|
||||
ASM,
|
||||
makeInst,
|
||||
liDX,
|
||||
str2inst,
|
||||
makeProgram,
|
||||
inst2gecko,
|
||||
getFillRectParams,
|
||||
} from '../asm.js';
|
||||
import { ASM, liDX, str2inst, makeProgram, inst2gecko, getFillRectParams } from '../asm.js';
|
||||
import { measureText } from '../text.js';
|
||||
import { addrs } from '../addrs.js';
|
||||
import { parseFormat } from './format.js';
|
||||
import { assemble } from './assembler.js';
|
||||
import { drawText, fillRect } from './functions.js';
|
||||
export const lskey = 'config/CustomizedDisplay';
|
||||
|
||||
import configDB from './configDB.js';
|
||||
export const defaultConfig = [configDB.PAS];
|
||||
|
||||
/** @type {(...args: Parameters<typeof parseFormat>) => string} */
|
||||
export const format2previewText = (input, version) => parseFormat(input, version).preview;
|
||||
|
||||
/** @typedef {'GMSJ01'|'GMSJ0A'|'GMSE01'|'GMSP01'} GameVersion */
|
||||
/** @param {GameVersion} version */
|
||||
export function getConfig(version) {
|
||||
/** @type {typeof defaultConfig} */
|
||||
|
@ -27,423 +27,47 @@ export function getConfig(version) {
|
|||
}
|
||||
|
||||
/**
|
||||
* @typedef {number[]} Inst
|
||||
* @typedef {8|16|32|'float'} DataType
|
||||
* @typedef {(gpr: number)=>Inst} GPRHandler -- (src=gpr, dst=gpr)
|
||||
* @typedef {{type: 'gpr'|'fpr'|'sp', index: number}} Dst
|
||||
* @typedef {'GMSJ01'|'GMSJ0A'|'GMSE01'|'GMSP01'} GameVersion
|
||||
*
|
||||
* @typedef {{
|
||||
* offset: number
|
||||
* dtype: DataType
|
||||
* post?: GPRHandler
|
||||
* }} FieldInfo
|
||||
*
|
||||
* @typedef {{
|
||||
* id: string
|
||||
* pre: GPRHandler
|
||||
* }} Base
|
||||
*
|
||||
* @typedef {{
|
||||
* pre: GPRHandler
|
||||
* fields: {info: FieldInfo, dst: Dst}[]
|
||||
* }} Entry
|
||||
*/
|
||||
|
||||
/** @typedef {{[ver in GameVersion]: GPRHandler}} VBase */
|
||||
const bases = {
|
||||
gpMarioOriginal: /**@type{VBase}*/ ({
|
||||
GMSJ01: (rT) => ASM.lwz(rT, 13, -0x6748),
|
||||
GMSE01: (rT) => ASM.lwz(rT, 13, -0x60d8),
|
||||
GMSP01: (rT) => ASM.lwz(rT, 13, -0x61b0),
|
||||
GMSJ0A: (rT) => ASM.lwz(rT, 13, -0x6218),
|
||||
}),
|
||||
gpMarDirector: /**@type{VBase}*/ ({
|
||||
GMSJ01: (rT) => ASM.lwz(rT, 13, -0x6818),
|
||||
GMSE01: (rT) => ASM.lwz(rT, 13, -0x6048),
|
||||
GMSP01: (rT) => ASM.lwz(rT, 13, -0x6120),
|
||||
GMSJ0A: (rT) => ASM.lwz(rT, 13, -0x6188),
|
||||
}),
|
||||
gpCamera: /**@type{VBase}*/ ({
|
||||
GMSJ01: (rT) => ASM.lwz(rT, 13, -0x5750),
|
||||
GMSE01: (rT) => ASM.lwz(rT, 13, -0x7118),
|
||||
GMSP01: (rT) => ASM.lwz(rT, 13, -0x7158),
|
||||
GMSJ0A: (rT) => ASM.lwz(rT, 13, -0x5768),
|
||||
}),
|
||||
};
|
||||
/** @typedef {keyof typeof bases} BaseId */
|
||||
|
||||
/** @type {({id: string, base: BaseId, fmt: string, preview: number} & FieldInfo)[]} */
|
||||
const fields = [
|
||||
{ id: 'x', base: 'gpMarioOriginal', dtype: 'float', offset: 0x10, fmt: '%.0f', preview: 426.39 },
|
||||
{ id: 'y', base: 'gpMarioOriginal', dtype: 'float', offset: 0x14, fmt: '%.0f', preview: -427.39 },
|
||||
{ id: 'z', base: 'gpMarioOriginal', dtype: 'float', offset: 0x18, fmt: '%.0f', preview: 428.39 },
|
||||
{ id: 'angle', base: 'gpMarioOriginal', dtype: 16, offset: 0x96, fmt: '%hu', preview: 1207 },
|
||||
{
|
||||
id: 'HSpd',
|
||||
base: 'gpMarioOriginal',
|
||||
dtype: 'float',
|
||||
offset: 0xb0,
|
||||
fmt: '%.2f',
|
||||
preview: 15.15,
|
||||
},
|
||||
{
|
||||
id: 'VSpd',
|
||||
base: 'gpMarioOriginal',
|
||||
dtype: 'float',
|
||||
offset: 0xa8,
|
||||
fmt: '%.2f',
|
||||
preview: -31.17,
|
||||
},
|
||||
{
|
||||
id: 'QF',
|
||||
base: 'gpMarDirector',
|
||||
dtype: 32,
|
||||
offset: 0x58,
|
||||
fmt: '%u',
|
||||
preview: 0,
|
||||
post: (rT) => ASM.rlwinm(rT, rT, 0, 30, 31, false),
|
||||
},
|
||||
{
|
||||
id: 'CAngle',
|
||||
base: 'gpCamera',
|
||||
dtype: 16,
|
||||
offset: 0xa6,
|
||||
fmt: '%hu',
|
||||
preview: 9,
|
||||
post: (rT) => ASM.addi(rT, rT, -0x8000), // offset by 0x8000
|
||||
},
|
||||
];
|
||||
const fieldDB = Object.fromEntries(
|
||||
fields.map(({ id, base, fmt, preview, ...info }) => [
|
||||
id.toLowerCase(),
|
||||
{ base, info, fmt, preview },
|
||||
]),
|
||||
);
|
||||
|
||||
const store = {
|
||||
8: ASM.stb,
|
||||
16: ASM.sth,
|
||||
32: ASM.stw,
|
||||
float: ASM.stfs,
|
||||
double: ASM.stfd,
|
||||
};
|
||||
const load = {
|
||||
8: ASM.lbz,
|
||||
16: ASM.lhz,
|
||||
32: ASM.lwz,
|
||||
float: ASM.lfs,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} version
|
||||
* @param {{
|
||||
* x: number
|
||||
* y: number
|
||||
* fontSize: number
|
||||
* fgRGB: number
|
||||
* fgA: number
|
||||
* fgRGB2: number | null
|
||||
* fgA2: number | null
|
||||
* }} drawTextOpt
|
||||
*/
|
||||
export function prepareDrawText(version, { x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2 }) {
|
||||
const colorTop = (fgRGB << 8) | fgA;
|
||||
const colorBot = fgRGB2 == null || fgA2 == null ? colorTop : (fgRGB2 << 8) | fgA;
|
||||
|
||||
let gpr = 5;
|
||||
let fpr = 1;
|
||||
let sp = 8;
|
||||
let fmt = '';
|
||||
let hasFloat = false;
|
||||
/** @type {{[id: string]: Entry}} */
|
||||
const entries = {};
|
||||
|
||||
/** @returns {Dst} */
|
||||
function allocInt() {
|
||||
if (gpr <= 10) {
|
||||
return { type: 'gpr', index: gpr++ };
|
||||
} else {
|
||||
/** @type {Dst} */
|
||||
const dst = { type: 'sp', index: sp };
|
||||
sp += 4;
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
/** @returns {Dst} */
|
||||
function allocFloat() {
|
||||
hasFloat = true;
|
||||
if (fpr <= 8) {
|
||||
return { type: 'fpr', index: fpr++ };
|
||||
} else {
|
||||
sp += sp & 4; // align 8
|
||||
/** @type {Dst} */
|
||||
const dst = { type: 'sp', index: sp };
|
||||
sp += 8;
|
||||
return dst;
|
||||
}
|
||||
}
|
||||
/** @param {Base} base */
|
||||
const getEntry = (base) =>
|
||||
entries[base.id] ??
|
||||
(entries[base.id] = {
|
||||
pre: base.pre,
|
||||
fields: [],
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {string} format
|
||||
* @param {Base} base
|
||||
* @param {FieldInfo} field
|
||||
*/
|
||||
pushValue(format, base, field) {
|
||||
fmt += format;
|
||||
getEntry(base).fields.push({
|
||||
info: field,
|
||||
dst: (field.dtype === 'float' ? allocFloat : allocInt)(),
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
pushText(text) {
|
||||
fmt += text.replace(/%/g, '%%');
|
||||
},
|
||||
makeCode() {
|
||||
/** @type {Inst[]} */
|
||||
const insts = [];
|
||||
// sp
|
||||
const spAdd = sp === 8 ? 0 : ((sp >> 4) + (sp & 0xf ? 1 : 0)) << 4;
|
||||
// params
|
||||
for (const { pre, fields: params } of Object.values(entries)) {
|
||||
// load base to gpr
|
||||
const rBase = 3;
|
||||
insts.push(pre(rBase));
|
||||
// load all params
|
||||
const rField = 11; // tmp GPR
|
||||
const fField = 9; // tmp FPR
|
||||
for (const {
|
||||
info: { offset: srcoff, dtype, post },
|
||||
dst,
|
||||
} of params) {
|
||||
if (dst.type === 'sp') {
|
||||
const dstoff = dst.index;
|
||||
if (dtype === 'float') {
|
||||
insts.push(
|
||||
// lfs fField, offset(rBase)
|
||||
load.float(fField, rBase, srcoff),
|
||||
// post
|
||||
post?.(fField) ?? [],
|
||||
// stfd fField, dst.index(r1)
|
||||
store.double(fField, 1, dstoff),
|
||||
);
|
||||
} else {
|
||||
insts.push(
|
||||
// load rField, offset(rBase)
|
||||
load[dtype](rField, rBase, srcoff),
|
||||
// post
|
||||
post?.(rField) ?? [],
|
||||
// stw rField, dst.index(r1)
|
||||
store[32](rField, 1, dstoff),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// load to register
|
||||
const { index: rDst } = dst;
|
||||
insts.push(
|
||||
// load rDst
|
||||
load[dtype](rDst, rBase, srcoff),
|
||||
// post
|
||||
post?.(rDst) ?? [],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// r3 = opt // sizeof(opt) = 0x10
|
||||
// r4 = fmt
|
||||
const fmtbuf = str2inst(fmt, version);
|
||||
insts.push(
|
||||
// bl 4+sizeof(opt)+len4(fmt)
|
||||
ASM.b(0x14 + (fmtbuf.length << 2), true),
|
||||
// opt
|
||||
[((x & 0xffff) << 16) | (y & 0xffff), fontSize, colorTop, colorBot],
|
||||
// .string fmt
|
||||
fmtbuf,
|
||||
// mflr r3
|
||||
ASM.mflr(3),
|
||||
// addi r4, r3, sizeof(opt)
|
||||
ASM.addi(4, 3, 0x10),
|
||||
);
|
||||
// DONE
|
||||
return { code: insts.flatMap((e) => e), spNeed: spAdd };
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const dtype2fmtinfo = {
|
||||
8: { prefix: 'hh', mask: 0xff },
|
||||
16: { prefix: 'h', mask: 0xffff },
|
||||
32: { prefix: '', mask: 0xffffffff },
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {GameVersion} version
|
||||
* @param {ReturnType<typeof prepareDrawText>|null} f
|
||||
*/
|
||||
export function format2previewText(input, version, f = null) {
|
||||
const regex = /<(.*?)>/g;
|
||||
let preview = '';
|
||||
/** @type {RegExpExecArray|null} */
|
||||
let m = null;
|
||||
let i0 = 0;
|
||||
while ((m = regex.exec(input))) {
|
||||
const { index: i } = m;
|
||||
// text
|
||||
const text = input.slice(i0, i);
|
||||
f?.pushText(text);
|
||||
preview += text;
|
||||
// arg
|
||||
const [fieldId, fmt0, pvw0] = m[1].split('|');
|
||||
const field = fieldDB[fieldId.toLowerCase()];
|
||||
if (field) {
|
||||
const { base: baseId, info, fmt: fmt1, preview: pvw1 } = field;
|
||||
const { dtype } = info;
|
||||
const fmt2 = fmt0 || fmt1;
|
||||
let ipvw = +pvw0;
|
||||
if (!pvw0 || !isFinite(ipvw)) ipvw = pvw1;
|
||||
let fmt;
|
||||
let pvw;
|
||||
let padfmt = '';
|
||||
if (dtype === 'float') {
|
||||
const m = fmt2.trim().match(/^(?:%?(\d*)\.)?(\d+)([eEf]?)$/);
|
||||
padfmt = m?.[1] || '';
|
||||
const digit = +(m?.[2] || 0);
|
||||
const suffix = m?.[3] || 'f';
|
||||
fmt = `%${padfmt}.${digit}${suffix}`;
|
||||
pvw = ipvw[suffix === 'f' ? 'toFixed' : 'toExponential'](digit);
|
||||
if (suffix === 'E') pvw = pvw.toUpperCase();
|
||||
} else {
|
||||
const { prefix, mask } = dtype2fmtinfo[dtype];
|
||||
ipvw &= mask;
|
||||
const m = fmt2.trim().match(/^%?(\d*)h{,2}([dioxXu])$/);
|
||||
padfmt = m?.[1] || '';
|
||||
const t = m?.[2] || 'u';
|
||||
fmt = `%${padfmt}${prefix}${t}`;
|
||||
if ('di'.includes(t)) {
|
||||
if (ipvw > mask >>> 1) ipvw -= mask;
|
||||
pvw = ipvw.toString(10);
|
||||
} else if (t === 'o') {
|
||||
pvw = (ipvw >>> 0).toString(8);
|
||||
} else if ('xX'.includes(t)) {
|
||||
pvw = (ipvw >>> 0).toString(16);
|
||||
} else {
|
||||
pvw = (ipvw >>> 0).toString(10);
|
||||
}
|
||||
}
|
||||
pvw = pvw.padStart(+padfmt, padfmt[0] === '0' ? '0' : ' ');
|
||||
f?.pushValue(fmt, { id: baseId, pre: bases[baseId][version] }, info);
|
||||
preview += pvw;
|
||||
} else {
|
||||
// fail to parse
|
||||
f?.pushText(m[0]);
|
||||
preview += m[0];
|
||||
}
|
||||
// next
|
||||
i0 = i + m[0].length;
|
||||
}
|
||||
const text = input.slice(i0);
|
||||
f?.pushText(text);
|
||||
preview += text;
|
||||
// DONE
|
||||
return preview;
|
||||
}
|
||||
|
||||
import addrs from '../addrs.js';
|
||||
const addrOrigOff = -0x2c; // drawWater - [-0x30, -0x18]
|
||||
const addrDst = 0x817fa000;
|
||||
|
||||
/**
|
||||
* @typedef {Parameters<assemble>[0][number]} ASMInst
|
||||
* @param {GameVersion} version
|
||||
*/
|
||||
export default function codegen(version) {
|
||||
const configs = getConfig(version);
|
||||
|
||||
let spOff = 0;
|
||||
const fcodes = /** @type {Inst[]} */ ([]);
|
||||
const bcodes = /** @type {Inst[]} */ ([]);
|
||||
let stackFrameSize = 0;
|
||||
|
||||
/** @type {ASMInst[]} */
|
||||
const asm = [];
|
||||
|
||||
for (const config of configs) {
|
||||
const { fontSize, fmt, bgA } = config;
|
||||
// prepare drawText
|
||||
const f = prepareDrawText(version, config);
|
||||
const text = format2previewText(fmt, version, f);
|
||||
// text code
|
||||
if (fmt.trim()) {
|
||||
// update code and sp
|
||||
const { code, spNeed } = f.makeCode();
|
||||
spOff = Math.max(spOff, spNeed);
|
||||
fcodes.push(code);
|
||||
}
|
||||
// background code
|
||||
const { fmt, bgA } = config;
|
||||
const { preview, format, fields } = parseFormat(fmt, version);
|
||||
|
||||
// fill_rect
|
||||
if (bgA) {
|
||||
const { width, height } = measureText(text, version);
|
||||
const w = Math.ceil((width * fontSize) / 20);
|
||||
const h = Math.ceil((height * fontSize) / 20);
|
||||
bcodes.push(
|
||||
[
|
||||
// bl 4+sizeof(rect)+sizeof(color)
|
||||
ASM.b(0x18, true),
|
||||
// fill_rect params
|
||||
...getFillRectParams(config, measureText(text, version)),
|
||||
// mflr r3
|
||||
ASM.mflr(3),
|
||||
// addi r4, r3, sizeof(rect)
|
||||
ASM.addi(4, 3, 0x10),
|
||||
].flatMap((e) => e),
|
||||
);
|
||||
asm.push(...fillRect(version, config, measureText(preview, version)));
|
||||
}
|
||||
|
||||
// drawText
|
||||
if (fmt.trim()) {
|
||||
const { insts, sp } = drawText(version, config, format, fields);
|
||||
stackFrameSize = Math.max(stackFrameSize, sp);
|
||||
asm.push(...insts);
|
||||
}
|
||||
}
|
||||
|
||||
const addrOrig = addrs.drawWater[version] + addrOrigOff;
|
||||
const addrFillRect = addrs.fillRect[version];
|
||||
let body = assemble(asm, stackFrameSize);
|
||||
// align code
|
||||
if (body.length % 16 === 0) body += '60000000';
|
||||
body += '00000000';
|
||||
|
||||
// program
|
||||
const program = makeProgram(addrDst);
|
||||
// la r3, ctx(r1)
|
||||
// program.push(ASM.addi(3, 1, addrs.ctxSpOff[version]));
|
||||
// addi r1, r1, -spOff
|
||||
if (spOff) program.push(ASM.addi(1, 1, -spOff));
|
||||
// bl J2DGrafContext::setup2D
|
||||
// program.bl(addrs.setup2D[version]);
|
||||
// (fill_rect)
|
||||
for (const code of bcodes) {
|
||||
program.push(code);
|
||||
program.bl(addrFillRect);
|
||||
}
|
||||
// (drawText)
|
||||
for (const code of fcodes) {
|
||||
program.push(code);
|
||||
program.bl(addrs.drawText);
|
||||
}
|
||||
// addi r1, r1, spOff
|
||||
if (spOff) program.push(ASM.addi(1, 1, spOff));
|
||||
// b orig+4
|
||||
program.b(addrOrig + 4);
|
||||
|
||||
// dump code
|
||||
const pcode = program.dump();
|
||||
const psize = pcode.length;
|
||||
return [
|
||||
makeInst((0xc6 << 24) | (addrOrig & 0x1ffffff)),
|
||||
makeInst(addrDst),
|
||||
makeInst((0x06 << 24) | (addrDst & 0x1fffffff)),
|
||||
makeInst(psize << 2),
|
||||
pcode,
|
||||
psize & 1 ? [0] : [],
|
||||
]
|
||||
.flatMap((e) => e)
|
||||
.map(inst2gecko)
|
||||
.join('');
|
||||
const addrDst = addrs.drawWater[version] - 0x2c; // [-0x30, -0x18]
|
||||
return (
|
||||
[
|
||||
0xc2000000 | (addrDst & 0x1fffffff),
|
||||
body.length >>> 4, // 16 hex-digits per line
|
||||
]
|
||||
.flatMap((e) => e)
|
||||
.map(inst2gecko)
|
||||
.join('') + body
|
||||
);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ V Spd <VSpd|.2|-31.17>`,
|
|||
...base,
|
||||
x: 16,
|
||||
y: 192,
|
||||
fontSize: 18,
|
||||
fontSize: 16,
|
||||
fmt: `X <x|.0|39.39>
|
||||
Y <y|.0|1207.39>
|
||||
Z <z|.0|-4193.6>
|
||||
|
@ -43,7 +43,10 @@ A <angle||65535>
|
|||
C <CAngle||9>
|
||||
H <HSpd|.2|15.15>
|
||||
V <VSpd|.2|-31.17>
|
||||
QF <QF||0>`,
|
||||
QF <QF||0>
|
||||
I <invinc||30>
|
||||
G <goop||36368>
|
||||
Spin <spin||>`,
|
||||
},
|
||||
rect: {
|
||||
...base,
|
||||
|
|
125
site/.vuepress/components/codes/CustomizedDisplay/fields.js
Normal file
125
site/.vuepress/components/codes/CustomizedDisplay/fields.js
Normal file
|
@ -0,0 +1,125 @@
|
|||
import { ASM } from '../asm.js';
|
||||
import { makeDirectLoaderASM, makeFunctionLoader, rTmp, fTmp } from './loader.js';
|
||||
import { addrs, r13offs } from '../addrs.js';
|
||||
|
||||
/**
|
||||
* @typedef {ReturnType<import('./loader.js').makeFunctionLoader>} Loader
|
||||
* @typedef {ReturnType<Loader['asm']>[number]} ASMInst
|
||||
* @typedef {Parameters<Loader['asm']>[0]} GameVersion
|
||||
*
|
||||
* @typedef {Parameters<makeDirectLoaderASM>[1]} DirectLoadFunc
|
||||
* @typedef {{
|
||||
* dtype: Loader['dtype'],
|
||||
* base: keyof typeof bases,
|
||||
* offset: number,
|
||||
* postprocess?: (rT: number)=>ASMInst[]
|
||||
* }} DirectLoader
|
||||
*/
|
||||
|
||||
const r13bases = /**@type{{[k in keyof typeof r13offs]: DirectLoadFunc}}*/ (
|
||||
Object.fromEntries(
|
||||
Object.entries(r13offs).map(([k, ver2off]) => [
|
||||
k,
|
||||
/** @type {DirectLoadFunc} */
|
||||
(rT, ver) => [ASM.lwz(rT, 13, ver2off[ver])],
|
||||
]),
|
||||
)
|
||||
);
|
||||
export const bases = {
|
||||
...r13bases,
|
||||
};
|
||||
|
||||
/** @type {Array<(Loader|DirectLoader) & {id:string} & (
|
||||
* {fmt: string, preview: number} |
|
||||
* {fmt: '%s', preview: (ver: GameVersion) => string}
|
||||
* )>}
|
||||
*/
|
||||
export const fields = [
|
||||
{ id: 'x', base: 'gpMarioOriginal', dtype: 'float', offset: 0x10, fmt: '%.0f', preview: 426.39 },
|
||||
{ id: 'y', base: 'gpMarioOriginal', dtype: 'float', offset: 0x14, fmt: '%.0f', preview: -427.39 },
|
||||
{ id: 'z', base: 'gpMarioOriginal', dtype: 'float', offset: 0x18, fmt: '%.0f', preview: 428.39 },
|
||||
{ id: 'angle', base: 'gpMarioOriginal', dtype: 16, offset: 0x96, fmt: '%hu', preview: 1207 },
|
||||
{
|
||||
id: 'HSpd',
|
||||
base: 'gpMarioOriginal',
|
||||
dtype: 'float',
|
||||
offset: 0xb0,
|
||||
fmt: '%.2f',
|
||||
preview: 15.15,
|
||||
},
|
||||
{
|
||||
id: 'VSpd',
|
||||
base: 'gpMarioOriginal',
|
||||
dtype: 'float',
|
||||
offset: 0xa8,
|
||||
fmt: '%.2f',
|
||||
preview: -31.17,
|
||||
},
|
||||
{
|
||||
id: 'QF',
|
||||
base: 'gpMarDirector',
|
||||
dtype: 32,
|
||||
offset: 0x58,
|
||||
fmt: '%u',
|
||||
preview: 0,
|
||||
postprocess: (rT) => [ASM.rlwinm(rT, rT, 0, 30, 31, false)],
|
||||
},
|
||||
{
|
||||
id: 'CAngle',
|
||||
base: 'gpCamera',
|
||||
dtype: 16,
|
||||
offset: 0xa6,
|
||||
fmt: '%hu',
|
||||
preview: 9,
|
||||
postprocess: (rT) => [ASM.addi(rT, rT, -0x8000)], // offset by 0x8000
|
||||
},
|
||||
{
|
||||
id: 'invinc',
|
||||
base: 'gpMarioOriginal',
|
||||
dtype: 16,
|
||||
offset: 0x14c,
|
||||
fmt: '%hd',
|
||||
preview: 30,
|
||||
postprocess: (rT) => [ASM.rlwinm(rT, rT, 30, 2, 31, false)], // QF to frame (>>2)
|
||||
},
|
||||
{
|
||||
id: 'goop',
|
||||
fmt: '%d',
|
||||
preview: 600,
|
||||
...makeFunctionLoader(32, (ver) => [
|
||||
{
|
||||
type: 'call',
|
||||
addr: addrs.getPollutionDegree[ver],
|
||||
prep: [ASM.lwz(3, 13, r13offs.gpPollution[ver])],
|
||||
},
|
||||
]),
|
||||
},
|
||||
{
|
||||
id: 'spin',
|
||||
fmt: '%s',
|
||||
// TODO better char mapping
|
||||
preview: (ver) => String.fromCharCode(ver.startsWith('GMSJ') ? 0xff20 : 0x40),
|
||||
dtype: 32,
|
||||
calling: true,
|
||||
asm: (ver, dst) => [
|
||||
{
|
||||
type: 'call',
|
||||
addr: addrs.checkStickRotate[ver],
|
||||
prep: [
|
||||
ASM.lwz(3, 13, r13offs.gpMarioOriginal[ver]),
|
||||
ASM.stwu(1, 1, -0x10),
|
||||
ASM.addi(4, 1, 8),
|
||||
],
|
||||
},
|
||||
// 0 (A) 0
|
||||
{ type: 'struct', reg: rTmp, hex: ver.startsWith('GMSJ') ? '00819700' : '004000' },
|
||||
...(dst.type === 'stack'
|
||||
? [ASM.add(3, rTmp, 3), ASM.stw(3, 1, dst.off)]
|
||||
: [ASM.add(dst.num, rTmp, 3)]),
|
||||
// finalize
|
||||
ASM.addi(1, 1, 0x10),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const fieldDB = Object.fromEntries(fields.map((o) => [o.id.toLowerCase(), o]));
|
92
site/.vuepress/components/codes/CustomizedDisplay/format.js
Normal file
92
site/.vuepress/components/codes/CustomizedDisplay/format.js
Normal file
|
@ -0,0 +1,92 @@
|
|||
import { fieldDB } from './fields.js';
|
||||
|
||||
/**
|
||||
* @typedef {Parameters<import('./loader.js').Loader['asm']>[0]} GameVersion
|
||||
*/
|
||||
|
||||
const dtype2fmtinfo = {
|
||||
8: { prefix: 'hh', mask: 0xff },
|
||||
16: { prefix: 'h', mask: 0xffff },
|
||||
32: { prefix: '', mask: 0xffffffff },
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} input
|
||||
* @param {GameVersion} version
|
||||
*/
|
||||
export function parseFormat(input, version) {
|
||||
const regex = /<(.*?)>/g;
|
||||
let preview = '';
|
||||
let format = '';
|
||||
/** @type {(typeof fieldDB)[string][]} */
|
||||
const fields = [];
|
||||
/** @type {RegExpExecArray|null} */
|
||||
let m = null;
|
||||
let i0 = 0;
|
||||
while ((m = regex.exec(input))) {
|
||||
const { index: i } = m;
|
||||
// text
|
||||
const text = input.slice(i0, i);
|
||||
preview += text;
|
||||
format += text.replace(/%/g, '%%');
|
||||
// arg
|
||||
const [fieldId, fmt0, pvw0] = m[1].split('|');
|
||||
const field = fieldDB[fieldId.toLowerCase()];
|
||||
if (field) {
|
||||
const { dtype } = field;
|
||||
let fmt;
|
||||
let pvw;
|
||||
if (typeof field.preview === 'function') {
|
||||
// TODO preview of %s field
|
||||
fmt = field.fmt;
|
||||
pvw = field.preview(version);
|
||||
} else {
|
||||
const fmt2 = fmt0 || field.fmt;
|
||||
let ipvw = +pvw0;
|
||||
if (!pvw0 || !isFinite(ipvw)) ipvw = field.preview;
|
||||
let padfmt = '';
|
||||
if (dtype === 'float') {
|
||||
const m = fmt2.trim().match(/^(?:%?(\d*)\.)?(\d+)([eEf]?)$/);
|
||||
padfmt = m?.[1] || '';
|
||||
const digit = +(m?.[2] || 0);
|
||||
const suffix = m?.[3] || 'f';
|
||||
fmt = `%${padfmt}.${digit}${suffix}`;
|
||||
pvw = ipvw[suffix === 'f' ? 'toFixed' : 'toExponential'](digit);
|
||||
if (suffix === 'E') pvw = pvw.toUpperCase();
|
||||
} else {
|
||||
const { prefix, mask } = dtype2fmtinfo[dtype];
|
||||
ipvw &= mask;
|
||||
const m = fmt2.trim().match(/^%?(\d*)h{,2}([dioxXu])$/);
|
||||
padfmt = m?.[1] || '';
|
||||
const t = m?.[2] || 'u';
|
||||
fmt = `%${padfmt}${prefix}${t}`;
|
||||
if ('di'.includes(t)) {
|
||||
if (ipvw > mask >>> 1) ipvw -= mask;
|
||||
pvw = ipvw.toString(10);
|
||||
} else if (t === 'o') {
|
||||
pvw = (ipvw >>> 0).toString(8);
|
||||
} else if ('xX'.includes(t)) {
|
||||
pvw = (ipvw >>> 0).toString(16);
|
||||
} else {
|
||||
pvw = (ipvw >>> 0).toString(10);
|
||||
}
|
||||
}
|
||||
pvw = pvw.padStart(+padfmt, padfmt[0] === '0' ? '0' : ' ');
|
||||
}
|
||||
preview += pvw;
|
||||
format += fmt;
|
||||
fields.push(field);
|
||||
} else {
|
||||
// fail to parse
|
||||
preview += m[0];
|
||||
format += m[0].replace(/%/g, '%%');
|
||||
}
|
||||
// next
|
||||
i0 = i + m[0].length;
|
||||
}
|
||||
const text = input.slice(i0);
|
||||
preview += text;
|
||||
format += text.replace(/%/g, '%%');
|
||||
// DONE
|
||||
return { preview, format, fields };
|
||||
}
|
140
site/.vuepress/components/codes/CustomizedDisplay/functions.js
Normal file
140
site/.vuepress/components/codes/CustomizedDisplay/functions.js
Normal file
|
@ -0,0 +1,140 @@
|
|||
import { ASM, $load, insts2hex, str2hex, getDrawTextOpt, getFillRectParams } from '../asm.js';
|
||||
import { addrs } from '../addrs.js';
|
||||
import { rTmp, fTmp } from './loader.js';
|
||||
import { bases } from './fields.js';
|
||||
|
||||
/**
|
||||
* @typedef {import('./loader.js').Loader} Loader
|
||||
* @typedef {Parameters<Loader['asm']>[0]} GameVersion
|
||||
* @typedef {ReturnType<Loader['asm']>[number]} ASMInst
|
||||
* @typedef {(typeof import('./fields.js').fieldDB)[string]} Field
|
||||
*
|
||||
* @typedef {{type: 'reg', num: number}} LoadDstReg
|
||||
* @typedef {{type: 'stack', off: number}} LoadDstStack
|
||||
* @typedef {LoadDstReg|LoadDstStack} LoadDst
|
||||
* @typedef {{
|
||||
* dtype: Loader['dtype'],
|
||||
* base: keyof typeof import('./fields').bases,
|
||||
* offset: number,
|
||||
* postprocess?: (rT: number)=>ASMInst[]
|
||||
* }} DirectLoader
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {GameVersion} version
|
||||
* @param {Parameters<getDrawTextOpt>[0]} opt
|
||||
* @param {string} fmt
|
||||
* @param {Field[]} fields
|
||||
*/
|
||||
export function drawText(version, opt, fmt, fields) {
|
||||
/** @type {ASMInst[]} */
|
||||
const insts = [];
|
||||
|
||||
let gpr = 5;
|
||||
let fpr = 1;
|
||||
let sp = 8;
|
||||
/** @type {Map<DirectLoader['base'], ({dst: LoadDst} & Pick<DirectLoader, 'dtype'|'offset'|'postprocess'>)[]>} */
|
||||
const simples = new Map();
|
||||
/** @type {{asm: Loader['asm'], dst: LoadDstStack, dtype: Loader['dtype']}[]} */
|
||||
const callingStacks = [];
|
||||
/** @type {{asm: Loader['asm'], dst: LoadDstReg, dtype: Loader['dtype']}[]} */
|
||||
const callingRegs = [];
|
||||
/** @type {{asm: Loader['asm'], dst: LoadDst, dtype: Loader['dtype']}[]} */
|
||||
const directs = [];
|
||||
for (const entry of fields) {
|
||||
const { dtype } = entry;
|
||||
const isFloat = dtype === 'float';
|
||||
/** @type {LoadDst} */
|
||||
let dst;
|
||||
if (isFloat && fpr <= 8) {
|
||||
dst = { type: 'reg', num: fpr++ };
|
||||
} else if (!isFloat && gpr <= 10) {
|
||||
dst = { type: 'reg', num: gpr++ };
|
||||
} else {
|
||||
if (isFloat) sp = ((sp + 7) >> 3) << 3;
|
||||
dst = { type: 'stack', off: sp };
|
||||
sp += isFloat ? 8 : 4;
|
||||
}
|
||||
// push
|
||||
if ('asm' in entry) {
|
||||
const { asm, calling } = entry;
|
||||
(calling ? (dst.type === 'stack' ? callingStacks : callingRegs) : directs).push({
|
||||
dtype,
|
||||
asm,
|
||||
dst,
|
||||
});
|
||||
} else {
|
||||
const { base, offset, postprocess } = entry;
|
||||
const item = { dst, dtype, offset, postprocess };
|
||||
const arr = simples.get(base);
|
||||
if (arr == null) {
|
||||
simples.set(base, [item]);
|
||||
} else {
|
||||
arr.push(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
insts.push(...callingStacks.flatMap((inst) => inst.asm(version, inst.dst)));
|
||||
|
||||
const callingRegLast = callingRegs.pop();
|
||||
/** @type {ASMInst[]} */
|
||||
const instsLoadFromStack = [];
|
||||
callingRegs.forEach((inst) => {
|
||||
const isFloat = inst.dtype === 'float';
|
||||
if (isFloat) sp = ((sp + 7) >> 3) << 3;
|
||||
insts.push(...inst.asm(version, { type: 'stack', off: sp }));
|
||||
instsLoadFromStack.push((isFloat ? ASM.lfd : ASM.lwz)(inst.dst.num, 1, sp));
|
||||
sp += isFloat ? 8 : 4;
|
||||
});
|
||||
// last
|
||||
if (callingRegLast) {
|
||||
insts.push(...callingRegLast.asm(version, callingRegLast.dst));
|
||||
}
|
||||
// load from stack
|
||||
insts.push(...instsLoadFromStack);
|
||||
|
||||
// directs
|
||||
insts.push(...directs.flatMap((inst) => inst.asm(version, inst.dst)));
|
||||
|
||||
// simples
|
||||
const rBase = 3;
|
||||
for (const [base, items] of simples.entries()) {
|
||||
// load base
|
||||
insts.push(...bases[base](rBase, version));
|
||||
// load all var
|
||||
for (const { dtype, offset, dst, postprocess } of items) {
|
||||
if (dst.type === 'stack') {
|
||||
insts.push(
|
||||
$load[dtype](rTmp, rBase, offset),
|
||||
...(postprocess?.(rTmp) ?? []),
|
||||
(dtype === 'float' ? ASM.stfd : ASM.stw)(rTmp, 1, dst.off),
|
||||
);
|
||||
} else {
|
||||
insts.push($load[dtype](dst.num, rBase, offset), ...(postprocess?.(dst.num) ?? []));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// r3 = drawTextOpt
|
||||
insts.push({ type: 'struct', reg: 3, hex: insts2hex(getDrawTextOpt(opt)) });
|
||||
// r4 = fmt
|
||||
insts.push({ type: 'struct', reg: 4, hex: str2hex(fmt, version) });
|
||||
|
||||
// call
|
||||
insts.push({ type: 'call', addr: addrs.drawText });
|
||||
return { insts, sp };
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {GameVersion} version
|
||||
* @param {Parameters<getFillRectParams>[0]} config
|
||||
* @param {Parameters<getFillRectParams>[1]} size
|
||||
* @returns {ASMInst[]}
|
||||
*/
|
||||
export const fillRect = (version, config, size) => [
|
||||
// r3, r4 = opt
|
||||
{ type: 'struct', reg: 3, hex: insts2hex(getFillRectParams(config, size)) },
|
||||
// call
|
||||
{ type: 'call', addr: addrs.fillRect[version], prep: [ASM.addi(4, 3, 0x10)] },
|
||||
];
|
68
site/.vuepress/components/codes/CustomizedDisplay/loader.js
Normal file
68
site/.vuepress/components/codes/CustomizedDisplay/loader.js
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { ASM, $load, $store } from '../asm.js';
|
||||
import { assemble } from './assembler.js';
|
||||
|
||||
export const rTmp = 12;
|
||||
export const fTmp = 12;
|
||||
|
||||
/**
|
||||
* @typedef {Parameters<import('./assembler.js').assemble>[0] extends (infer U)[] ? U : never} ASMInst
|
||||
* @typedef {'GMSJ01'|'GMSJ0A'|'GMSE01'|'GMSP01'} GameVersion
|
||||
* @typedef {{type: 'reg', num: number}} LoadDstReg
|
||||
* @typedef {{type: 'stack', off: number}} LoadDstStack
|
||||
* @typedef {LoadDstReg|LoadDstStack} LoadDst
|
||||
* @typedef {{
|
||||
* asm(version: GameVersion, dst: LoadDst): ASMInst[]
|
||||
* dtype: 8|16|32|'float'
|
||||
* calling: boolean
|
||||
* }} Loader
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {Loader['dtype']} dtype
|
||||
* @param {(reg: number, version: GameVersion)=>ASMInst[]} load
|
||||
* @param {(rD: number, version: GameVersion)=>ASMInst[]} [postprocess]
|
||||
* @returns {Loader['asm']}
|
||||
*/
|
||||
export const makeDirectLoaderASM = (dtype, load, postprocess) => (version, dst) => {
|
||||
const { type } = dst;
|
||||
if (type == 'reg') {
|
||||
const { num } = dst;
|
||||
return [...load(num, version), ...(postprocess?.(num, version) ?? [])];
|
||||
} else {
|
||||
const { num, st } =
|
||||
dtype === 'float' ? { num: fTmp, st: ASM.stfd } : { num: rTmp, st: ASM.stw };
|
||||
return [...load(num, version), ...(postprocess?.(num, version) ?? []), st(num, 1, dst.off)];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Loader['dtype']} dtype
|
||||
* @param {(version: GameVersion)=>ASMInst[]} load
|
||||
* @returns {Loader['asm']}
|
||||
*/
|
||||
export const makeFunctionLoaderASM = (dtype, load) => (version, dst) => {
|
||||
const { type } = dst;
|
||||
const base = load(version);
|
||||
if (type == 'reg') {
|
||||
const { num } = dst;
|
||||
if (dtype === 'float') {
|
||||
return num === 1 ? base : [...base, ASM.fmr(num, 1)];
|
||||
} else {
|
||||
return num === 3 ? base : [...base, ASM.mr(num, 3)];
|
||||
}
|
||||
} else {
|
||||
const { off } = dst;
|
||||
return [...base, dtype === 'float' ? ASM.stfd(1, 1, off) : ASM.stw(3, 1, off)];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {Loader['dtype']} dtype
|
||||
* @param {(version: GameVersion)=>ASMInst[]} load
|
||||
* @returns {Loader}
|
||||
*/
|
||||
export const makeFunctionLoader = (dtype, load) => ({
|
||||
dtype,
|
||||
asm: makeFunctionLoaderASM(dtype, load),
|
||||
calling: true,
|
||||
});
|
|
@ -2,7 +2,7 @@ import { parseJSON } from '../codegen.js';
|
|||
import { ASM, liDX, str2hex, inst2gecko, getFillRectParams } from '../asm.js';
|
||||
import { measureText } from '../text.js';
|
||||
import { int2hex } from '../utils.js';
|
||||
import addrs from '../addrs.js';
|
||||
import { addrs } from '../addrs.js';
|
||||
export const lskey = 'config/PatternSelector';
|
||||
|
||||
import * as GMSJ01 from './code/GMSJ01.js';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export default {
|
||||
export const addrs = {
|
||||
drawText: 0x817f0238,
|
||||
drawWater: {
|
||||
GMSJ01: 0x80206a00,
|
||||
|
@ -18,11 +18,51 @@ export default {
|
|||
GMSE01: 0x802eb6bc,
|
||||
GMSP01: 0x802e3864,
|
||||
},
|
||||
// r1 offset of J2DGrafContext in TGCConsole2::perform()
|
||||
ctxSpOff: {
|
||||
GMSJ01: 0xe90,
|
||||
GMSJ0A: 0xbec,
|
||||
GMSE01: 0xbd0,
|
||||
GMSP01: 0xbe4,
|
||||
getPollutionDegree: {
|
||||
GMSJ01: 0x801ef6b8,
|
||||
GMSE01: 0x8019db20,
|
||||
GMSP01: 0x801963a8,
|
||||
GMSJ0A: 0x8017e26c,
|
||||
},
|
||||
checkStickRotate: {
|
||||
GMSJ01: 0x80130758,
|
||||
GMSE01: 0x80251304,
|
||||
GMSP01: 0x80249090,
|
||||
GMSJ0A: 0x80231054,
|
||||
},
|
||||
};
|
||||
|
||||
export const r13offs = {
|
||||
gpMarioOriginal: {
|
||||
GMSJ01: -0x6748,
|
||||
GMSE01: -0x60d8,
|
||||
GMSP01: -0x61b0,
|
||||
GMSJ0A: -0x6218,
|
||||
},
|
||||
gpMarDirector: {
|
||||
GMSJ01: -0x6818,
|
||||
GMSE01: -0x6048,
|
||||
GMSP01: -0x6120,
|
||||
GMSJ0A: -0x6188,
|
||||
},
|
||||
gpCamera: {
|
||||
GMSJ01: -0x5750,
|
||||
GMSE01: -0x7118,
|
||||
GMSP01: -0x7158,
|
||||
GMSJ0A: -0x5768,
|
||||
},
|
||||
gpPollution: {
|
||||
GMSJ01: -0x6518,
|
||||
GMSE01: -0x62f0,
|
||||
GMSP01: -0x63c8,
|
||||
GMSJ0A: -0x6430,
|
||||
},
|
||||
};
|
||||
|
||||
// r1 offset of J2DGrafContext in TGCConsole2::perform()
|
||||
export const ctxSpOff = {
|
||||
GMSJ01: 0xe90,
|
||||
GMSJ0A: 0xbec,
|
||||
GMSE01: 0xbd0,
|
||||
GMSP01: 0xbe4,
|
||||
};
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import * as Encoding from 'encoding-japanese';
|
||||
import charInfoJP from '../../data/charInfo-JP.json';
|
||||
import charInfoEU from '../../data/charInfo-EU.json';
|
||||
|
||||
/**
|
||||
* @typedef {number[]} Inst
|
||||
* @typedef {number} Inst
|
||||
*
|
||||
* @typedef {(
|
||||
* rT: number,
|
||||
|
@ -27,7 +28,7 @@ import * as Encoding from 'encoding-japanese';
|
|||
* rT: number,
|
||||
* rA: number,
|
||||
* rB: number,
|
||||
* Rc: number|boolean,
|
||||
* Rc?: number|boolean,
|
||||
* ) => Inst} InstX
|
||||
* @typedef {(
|
||||
* rS: number,
|
||||
|
@ -43,13 +44,6 @@ import * as Encoding from 'encoding-japanese';
|
|||
* ) => Inst} InstI
|
||||
*/
|
||||
|
||||
/** @param {number} inst */
|
||||
export const makeInst = (inst) => {
|
||||
// const buf = Inst.alloc(4);
|
||||
// buf.writeUint32BE(inst >>> 0);
|
||||
// return buf;
|
||||
return [inst];
|
||||
};
|
||||
/**
|
||||
* @param {number} op
|
||||
* @param {number} rT
|
||||
|
@ -57,7 +51,7 @@ export const makeInst = (inst) => {
|
|||
* @param {number} D
|
||||
*/
|
||||
const InstD = (op, rT, rA, D) =>
|
||||
makeInst(((op & 0x3f) << 26) | ((rT & 0x1f) << 21) | ((rA & 0x1f) << 16) | (D & 0xffff));
|
||||
((op & 0x3f) << 26) | ((rT & 0x1f) << 21) | ((rA & 0x1f) << 16) | (D & 0xffff);
|
||||
/**
|
||||
* @param {number} op
|
||||
* @param {number} rT
|
||||
|
@ -67,14 +61,12 @@ const InstD = (op, rT, rA, D) =>
|
|||
* @param {number} Rc
|
||||
*/
|
||||
const InstX = (op, rT, rA, rB, op2, Rc) =>
|
||||
makeInst(
|
||||
((op & 0x3f) << 26) |
|
||||
((rT & 0x1f) << 21) |
|
||||
((rA & 0x1f) << 16) |
|
||||
((rB & 0x1f) << 11) |
|
||||
((op2 & 0x3ff) << 1) |
|
||||
Rc,
|
||||
);
|
||||
((op & 0x3f) << 26) |
|
||||
((rT & 0x1f) << 21) |
|
||||
((rA & 0x1f) << 16) |
|
||||
((rB & 0x1f) << 11) |
|
||||
((op2 & 0x3ff) << 1) |
|
||||
Rc;
|
||||
/**
|
||||
* @param {number} op
|
||||
* @param {number} RS
|
||||
|
@ -85,15 +77,13 @@ const InstX = (op, rT, rA, rB, op2, Rc) =>
|
|||
* @param {number} Rc
|
||||
*/
|
||||
const InstM = (op, RA, RS, SH, MB, ME, Rc) =>
|
||||
makeInst(
|
||||
((op & 0x3f) << 26) |
|
||||
((RS & 0x1f) << 21) |
|
||||
((RA & 0x1f) << 16) |
|
||||
((SH & 0x1f) << 11) |
|
||||
((MB & 0x1f) << 6) |
|
||||
((ME & 0x1f) << 1) |
|
||||
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
|
||||
|
@ -101,14 +91,17 @@ const InstM = (op, RA, RS, SH, MB, ME, Rc) =>
|
|||
* @param {number} LK
|
||||
*/
|
||||
const InstI = (op, LL, AA, LK) =>
|
||||
makeInst(((op & 0x3f) << 26) | ((LL & 0xffffff) << 2) | ((AA & 1) << 1) | (LK & 1));
|
||||
((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) => InstX(op, rT, rA, rB, op2, +Rc);
|
||||
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} */
|
||||
|
@ -132,6 +125,9 @@ export const ASM = {
|
|||
lhz: makeInstD(40),
|
||||
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),
|
||||
|
@ -144,8 +140,10 @@ export const ASM = {
|
|||
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),
|
||||
|
@ -155,6 +153,22 @@ export const ASM = {
|
|||
// 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,
|
||||
float: ASM.lfs,
|
||||
};
|
||||
export const $store = {
|
||||
8: ASM.stb,
|
||||
16: ASM.sth,
|
||||
32: ASM.stw,
|
||||
float: ASM.stfs,
|
||||
double: ASM.stfd,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -163,13 +177,13 @@ export const ASM = {
|
|||
*/
|
||||
export function liDX(rT, D) {
|
||||
if (-0x8000 <= D && D < 0x8000) {
|
||||
return ASM.li(rT, D);
|
||||
return [ASM.li(rT, D)];
|
||||
} else if ((D & 0xffff) === 0) {
|
||||
return ASM.lis(rT, D >>> 16);
|
||||
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)];
|
||||
return [ASM.lis(rT, h), ASM.ori(rT, rT, l)];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,15 +192,12 @@ export function liDX(rT, D) {
|
|||
* @param {string} version
|
||||
*/
|
||||
export function str2bytes(s, version) {
|
||||
const enc = version.startsWith('GMSJ') ? 'SJIS' : '';
|
||||
const fmtbuf = version.startsWith('GMSJ')
|
||||
? Encoding.convert(Encoding.stringToCode(s), 'SJIS') // Shift-JIS
|
||||
: Array.from(s, (c) => {
|
||||
// latin1
|
||||
const x = c.charCodeAt(0);
|
||||
// replace the char with space if it is multi-byte
|
||||
return x >= 0x100 ? 0x20 : x;
|
||||
});
|
||||
/** @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);
|
||||
return code >= 0x100 ? [code >> 16, code & 0xff] : [code];
|
||||
});
|
||||
fmtbuf.push(0); // NUL terminated
|
||||
return fmtbuf;
|
||||
}
|
||||
|
@ -217,35 +228,49 @@ export function str2inst(s, version) {
|
|||
return insts;
|
||||
}
|
||||
|
||||
/** @param {number} pc */
|
||||
export function makeProgram(pc) {
|
||||
/** @type {Inst[]} */
|
||||
const bufs = [];
|
||||
return {
|
||||
pc,
|
||||
/**
|
||||
* @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) {
|
||||
bufs.push(...codes);
|
||||
this.pc += codes.reduce((a, e) => a + e.length, 0) << 2;
|
||||
},
|
||||
dump: () => bufs.flatMap((e) => e),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @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 {{
|
||||
|
@ -273,3 +298,20 @@ export const getFillRectParams = (
|
|||
// 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];
|
||||
}
|
||||
|
|
|
@ -11,13 +11,11 @@ const getFontInfo = (version) =>
|
|||
// JP
|
||||
charInfo: /**@type{Record<string, CharInfo>}*/ (charInfoJP),
|
||||
rowSize: 24, // how many char in a row of the img
|
||||
multibyte: true,
|
||||
}
|
||||
: {
|
||||
// EU (TODO US)
|
||||
charInfo: /**@type{Record<string, CharInfo>}*/ (charInfoEU),
|
||||
rowSize: 16, // how many char in a row of the img
|
||||
multibyte: false,
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -25,7 +23,7 @@ const getFontInfo = (version) =>
|
|||
* @param {string} version
|
||||
*/
|
||||
export function measureText(text, version) {
|
||||
const { charInfo, rowSize, multibyte } = getFontInfo(version);
|
||||
const { charInfo, rowSize } = getFontInfo(version);
|
||||
|
||||
/** @type {{x: number, y: number, u: number, v: number}[]} */
|
||||
const chars = [];
|
||||
|
@ -34,8 +32,7 @@ export function measureText(text, version) {
|
|||
let w = 0;
|
||||
let useKerning = false;
|
||||
text.split('').forEach((c) => {
|
||||
const { index, kerning, width } =
|
||||
charInfo[c] ?? (multibyte && c.charCodeAt(0) >= 0x80 ? charInfo['?'] : charInfo[' ']);
|
||||
const { index, kerning, width } = charInfo[c] ?? charInfo['?'];
|
||||
if (c === '\n') {
|
||||
useKerning = false;
|
||||
x = 0;
|
||||
|
|
|
@ -59,3 +59,20 @@ export const makeGetLabel =
|
|||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Iterable<T>} arr
|
||||
* @param {(val: T) => boolean} tester
|
||||
* @returns {[positive: T[], negative: T[]]}
|
||||
*/
|
||||
export function splitArray(arr, tester) {
|
||||
/** @type {T[]} */
|
||||
const positive = [];
|
||||
/** @type {T[]} */
|
||||
const negative = [];
|
||||
for (const val of arr) {
|
||||
(tester(val) ? positive : negative).push(val);
|
||||
}
|
||||
return [positive, negative];
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue