add QFT freeze options

- When Mario holds, throws, puts down an object
- When Mario triple jumps, spin jumps, ledge grabs, wall kicks, bounces, rope jumps
This commit is contained in:
sup39 2023-01-10 16:55:28 +09:00
parent 0888e03db5
commit 9b767a4cbb
9 changed files with 145 additions and 15 deletions

View file

@ -1,4 +1,10 @@
# Changelog # Changelog
## Jan 10, 2023
### Updated 'Quarterframe Timer'
Added the following options to freeze QFT:
- When Mario holds, throws, puts down an object
- When Mario triple jumps, spin jumps, ledge grabs, wall kicks, bounces, rope jumps
## Jan 7, 2023 ## Jan 7, 2023
### Updated 'Quarterframe Timer' ### Updated 'Quarterframe Timer'
Reworked the existing freezes and added the option to freeze when mounting Yoshi Reworked the existing freezes and added the option to freeze when mounting Yoshi

View file

@ -1,7 +1,8 @@
export const r13off = -0x6048; export const r13off = -0x6048;
export const onChangeStatusAddr = 0x802541c8;
/** /**
* @type {{[key: string]: number}} * @type {{[key: string]: number|{addr: number, orig: number}}}
*/ */
export const freezeCodeHooks = { export const freezeCodeHooks = {
yellowCoin: 0x801bee10, yellowCoin: 0x801bee10,
@ -13,4 +14,6 @@ export const freezeCodeHooks = {
cleaned: 0x80215c6c, cleaned: 0x80215c6c,
bowser: 0x801fb7ac, bowser: 0x801fb7ac,
yoshi: 0x802704d4, yoshi: 0x802704d4,
take: { addr: 0x8023f9a8, orig: 0x801f0384 },
drop: { addr: 0x802437d4, orig: 0x38000000 },
}; };

View file

@ -1,7 +1,8 @@
export const r13off = -0x6818; export const r13off = -0x6818;
export const onChangeStatusAddr = 0x801335b8;
/** /**
* @type {{[key: string]: number}} * @type {{[key: string]: number|{addr: number, orig: number}}}
*/ */
export const freezeCodeHooks = { export const freezeCodeHooks = {
yellowCoin: 0x80196cb0, yellowCoin: 0x80196cb0,
@ -13,4 +14,6 @@ export const freezeCodeHooks = {
cleaned: 0x8017a3d4, cleaned: 0x8017a3d4,
bowser: 0x801d3380, bowser: 0x801d3380,
yoshi: 0x8014f830, yoshi: 0x8014f830,
take: { addr: 0x8011eae4, orig: 0x801f0384 },
drop: { addr: 0x80122964, orig: 0x38000000 },
}; };

View file

@ -1,7 +1,8 @@
export const r13off = -0x6188; export const r13off = -0x6188;
export const onChangeStatusAddr = 0x80233f18;
/** /**
* @type {{[key: string]: number}} * @type {{[key: string]: number|{addr: number, orig: number}}}
*/ */
export const freezeCodeHooks = { export const freezeCodeHooks = {
yellowCoin: 0x8019eb98, yellowCoin: 0x8019eb98,
@ -13,4 +14,6 @@ export const freezeCodeHooks = {
cleaned: 0x801f5b0c, cleaned: 0x801f5b0c,
bowser: 0x801db550, bowser: 0x801db550,
yoshi: 0x80250224, yoshi: 0x80250224,
take: { addr: 0x8021f6f0, orig: 0x801f0384 },
drop: { addr: 0x8022351c, orig: 0x38000000 },
}; };

View file

@ -1,7 +1,8 @@
export const r13off = -0x6120; export const r13off = -0x6120;
export const onChangeStatusAddr = 0x8024bf54;
/** /**
* @type {{[key: string]: number}}} * @type {{[key: string]: number|{addr: number, orig: number}}}
*/ */
export const freezeCodeHooks = { export const freezeCodeHooks = {
yellowCoin: 0x801b6cc8, yellowCoin: 0x801b6cc8,
@ -13,4 +14,6 @@ export const freezeCodeHooks = {
cleaned: 0x8020db50, cleaned: 0x8020db50,
bowser: 0x801f3690, bowser: 0x801f3690,
yoshi: 0x80268260, yoshi: 0x80268260,
take: { addr: 0x80237734, orig: 0x801f0384 },
drop: { addr: 0x8023b560, orig: 0x38000000 },
}; };

View file

@ -0,0 +1,9 @@
export default {
put: [0x80000387],
tripleJump: [0x882],
spinJump: [0x895, 0x896],
ledgeGrab: [0x3800034b],
wallKick: [0x2000886],
bounce: [0x884],
ropeJump: [0x892, 0x893],
};

View file

@ -23,6 +23,15 @@ export const defaultConfig = {
cleaned: true, cleaned: true,
bowser: true, // onBathhubGripDestroyed bowser: true, // onBathhubGripDestroyed
yoshi: true, yoshi: true,
take: true,
drop: true,
put: true,
tripleJump: true,
spinJump: true,
ledgeGrab: true,
wallKick: true,
ropeJump: true,
bounce: true,
}, },
}; };
@ -48,6 +57,9 @@ import * as GMSP01 from './code/GMSP01.js';
import * as GMSJ0A from './code/GMSJ0A.js'; import * as GMSJ0A from './code/GMSJ0A.js';
export const codes = { GMSJ01, GMSE01, GMSP01, GMSJ0A }; export const codes = { GMSJ01, GMSE01, GMSP01, GMSJ0A };
import statusDB from './code/status.js';
export const statusKeys = Object.keys(statusDB);
/**** /****
## save freeze frame, load and save QF ## save freeze frame, load and save QF
## this function destroys r11 and r12 ## this function destroys r11 and r12
@ -71,18 +83,24 @@ export default function codegen(version, baseCode) {
if (!baseCode) return ''; if (!baseCode) return '';
const config = getConfig(); const config = getConfig();
const { freezeCodeHooks, r13off } = codes[version] ?? {}; const { freezeCodeHooks, r13off, onChangeStatusAddr } = codes[version] ?? {};
let code = baseCode; let code = baseCode;
const { freezeDuration: frame } = config; const { freezeDuration: frame } = config;
// freezing code // freezing code
const enabledFreezes = []; const enabledFreezes = [];
const statuses = [];
if (frame > 0) { if (frame > 0) {
for (const [key, enabled] of Object.entries(config.freeze)) { for (const [key, enabled] of Object.entries(config.freeze)) {
const addr = freezeCodeHooks[key]; if (!enabled) continue;
if (enabled && addr) { // add status
statuses.push(...(statusDB[key] ?? []));
// add hook
const hook = freezeCodeHooks[key];
if (hook) {
if (key === 'blueCoin') { if (key === 'blueCoin') {
const addr = hook;
// special: needs to adjust QF -> use separate C2 instead // special: needs to adjust QF -> use separate C2 instead
code += [ code += [
0xc2000000 + (addr & 0x1ffffff), 0xc2000000 + (addr & 0x1ffffff),
@ -100,15 +118,32 @@ export default function codegen(version, baseCode) {
] ]
.map(int2gecko) .map(int2gecko)
.join(''); .join('');
} else { } else if (typeof hook === 'number') {
// handle regular freezing code later // handle regular freezing code later
const addr = hook;
enabledFreezes.push(addr); 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 // handle regular freezing code
if (enabledFreezes.length <= 1) { if (enabledFreezes.length <= 1 && statuses.length === 0) {
// use C2 directly // use C2 directly
code += enabledFreezes code += enabledFreezes
.flatMap((addr) => [ .flatMap((addr) => [
@ -149,6 +184,54 @@ export default function codegen(version, baseCode) {
code += [...hooks, ...freezer].map(int2gecko).join(''); 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 // ui
/* bounds */ /* bounds */
const { x, y, fontSize, width } = config; const { x, y, fontSize, width } = config;

View file

@ -35,14 +35,13 @@
</template> </template>
<script> <script>
import { getConfig, lskey, codes } from './codegen.js'; import { getConfig, lskey, codes, statusKeys } from './codegen.js';
import { rgbI2S, rgbS2I, rgbaI2S } from '../utils'; import { rgbI2S, rgbS2I, rgbaI2S } from '../utils';
import labels from './labels.json'; import labels from './labels.json';
import TextConfig from '../TextConfig.vue'; import TextConfig from '../TextConfig.vue';
function updateConfig() { function updateConfig() {
const { x, y, fontSize, width, fgRGB, fgA, fgRGB2, fgA2, bgRGB, bgA, freeze, freezeDuration } = const { x, y, fontSize, width, fgRGB, fgA, fgRGB2, fgA2, bgRGB, bgA, freeze, freezeDuration } = this;
this;
const config = { const config = {
x, x,
y, y,
@ -104,7 +103,10 @@ export default {
freeze, freeze,
freezeDuration, freezeDuration,
// const // const
freezeKeys: Object.keys(codes[this.version]?.freezeCodeHooks ?? {}), freezeKeys: [
...Object.keys(codes[this.version]?.freezeCodeHooks ?? {}),
...statusKeys,
],
}; };
}, },
computed: { computed: {

View file

@ -25,7 +25,16 @@
"demo": "カットシーン開始時", "demo": "カットシーン開始時",
"cleaned": "NPCを洗った時", "cleaned": "NPCを洗った時",
"bowser": "クッパ戦の足場を破壊した時", "bowser": "クッパ戦の足場を破壊した時",
"yoshi": "ヨッシーに乗った時" "yoshi": "ヨッシーに乗った時",
"take": "オブジェクトを持った時",
"drop": "オブジェクトを投げた時",
"put": "オブジェクトを置いた時",
"tripleJump": "三段ジャンプした時",
"spinJump": "スピンジャンプした時",
"ledgeGrab": "崖掴まりした時",
"wallKick": "壁キックした時",
"ropeJump": "ロープ、大車輪でジャンプした時",
"bounce": "アメンボやジャンプ台で跳ねた時"
} }
} }
}, },
@ -55,7 +64,16 @@
"demo": "When a cutscene starts", "demo": "When a cutscene starts",
"cleaned": "When an NPC is cleaned", "cleaned": "When an NPC is cleaned",
"bowser": "When a platform is destroyed in the Bowser fight", "bowser": "When a platform is destroyed in the Bowser fight",
"yoshi": "When Yoshi is mounted" "yoshi": "When Yoshi is mounted",
"take": "When Mario holds an object",
"drop": "When Mario throws an object",
"put": "When Mario puts down an object",
"tripleJump": "When Mario triple jumps",
"spinJump": "When Mario spin jumps",
"ledgeGrab": "When Mario ledge grabs",
"wallKick": "When Mario wall kicks",
"ropeJump": "When Mario jumps from a rope",
"bounce": "When Mario bounces (e.g. on a roof)"
} }
} }
}, },