Rewrote drawText and related codes

- 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
This commit is contained in:
sup39 2023-01-29 04:39:18 +09:00
parent 9b767a4cbb
commit 898ea733ac
32 changed files with 3347 additions and 1649 deletions

1189
Codes.xml

File diff suppressed because it is too large Load diff

View file

@ -47,7 +47,9 @@ Some codes store some states in the games memory starting from address 0x817F000
| ![](./docs/reserved.svg) | `0x14` | `0x15` | DPad Functions: Stored Angle (Mario) | | ![](./docs/reserved.svg) | `0x14` | `0x15` | DPad Functions: Stored Angle (Mario) |
| ![](./docs/reserved.svg) | `0x16` | `0x1B` | DPad Functions: Stored Position (Camera) | | ![](./docs/reserved.svg) | `0x16` | `0x1B` | DPad Functions: Stored Position (Camera) |
| ![](./docs/reserved.svg) | `0x20` | `0x23` | Coin Count Savestate: Coin Count | | ![](./docs/reserved.svg) | `0x20` | `0x23` | Coin Count Savestate: Coin Count |
| ![](./docs/unallocated.svg) | `0x24` | `0x93` | Not Allocated | | ![](./docs/reserved.svg) | `0x24` | `0x26` | Pattern Selector: Selected Pattern Numbers |
| ![](./docs/reserved.svg) | `0x27` | `0x27` | Pattern Selector: Cursor Position |
| ![](./docs/unallocated.svg) | `0x28` | `0x93` | Not Allocated |
| ![](./docs/reserved.svg) | `0x94` | `0xA3` | QF Timer: Coordinates of the Text box (LTRB) | | ![](./docs/reserved.svg) | `0x94` | `0xA3` | QF Timer: Coordinates of the Text box (LTRB) |
| ![](./docs/reserved.svg) | `0xA4` | `0xB0` | QF Timer: Timer Format String | | ![](./docs/reserved.svg) | `0xA4` | `0xB0` | QF Timer: Timer Format String |
| ![](./docs/reserved.svg) | `0xB0` | `0xB1` | QF Timer: (Unused) | | ![](./docs/reserved.svg) | `0xB0` | `0xB1` | QF Timer: (Unused) |

View file

@ -533,4 +533,424 @@
60000000 00000000 60000000 00000000
</source> </source>
</code> </code>
<code>
<category>metadata</category>
<id>PASDisplay</id>
<title lang="en-US">Position/angle/speed display</title>
<title lang="de-CH">Position/Winkel/Geschw. Display</title>
<title lang="fr-FR">Affichage de position/angle/vitesse</title>
<title lang="ja-JP">位置/角度/速度表示</title>
<author>Noki Doki, sup39(サポミク)</author>
<version>1.4</version>
<date>Mar 24, 2022</date>
<dependencies version="GMSJ01">drawText</dependencies>
<dependencies version="GMSJ0A">drawText</dependencies>
<description lang="en-US">
Shows Mario's position, angle and speed at any given time.
::: warning
This code is not compatible with the Speed Display code.
:::
</description>
<description lang="de-CH">
Zeigt Mario's Position, Winkel und Geschwindigkeit jederzeit auf dem Bildschirm an.
::: warning
Dieser Code ist nicht kompatibel mit dem Geschwindigkeits-Display Code
:::
</description>
<description lang="fr-FR">Affiche la position, l'angle et la vitesse de Mario à tout moment.</description>
<description lang="ja-JP">常に「マリオの位置」「マリオの角度」「マリオの速度」を表示します。</description>
<source version="GMSE01">
062A6160 00000010
49553F19 60000000
60000000 60000000
042998B8 49560749
04143F14 496B6209
077FA000 000001E8
9421FFE0 7C0802A6
90010024 93E1001C
4AABD6E5 38E00200
38C00320 38A0FFD8
3880000A 38610008
4AAD01B9 814D9FC8
3FE08180 3BFFA1EC
39200000 39000002
3CE08180 38E7A164
80CA0048 38A10008
38800000 7FE3FB78
4AAD67F1 39200001
913F01B0 80010024
7C0803A6 83E1001C
38210020 4E800020
9421FFE8 7C0802A6
9001001C 93A1000C
93C10010 93E10014
7C7F1B78 81230000
83A90064 3D208180
8129A39C 2F890000
409E002C 7FE3FB78
7FA903A6 4E800421
8001001C 7C0803A6
83A1000C 83C10010
83E10014 38210018
4E800020 3D208040
6129E0E8 83C90000
3C608180 3863A1EC
4AAD6B79 C0BE00A8
C09E00B0 A0DE0096
C07E0018 C05E0014
C03E0010 3CA08180
38A5A164 38800080
4CC63242 4AB3F691
4BFFFF94 9421FFF0
7C0802A6 90010014
93E1000C 7C7F1B78
4AAF158D 38E00081
7FE6FB78 38A00000
38800000 3C608180
3863A1EC 4AAD5C5D
80010014 7C0803A6
83E1000C 38210010
4E800020 5820506F
7320252E 30660A59
20506F73 20252E30
660A5A20 506F7320
252E3066 0A416E67
6C652025 68750A48
20537064 20252E32
660A5620 53706420
252E3266 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202000 00000000
C22A66F4 00000003
38600000 3D808180
906CA39C 807F0040
60000000 00000000
</source>
<source version="GMSJ01">
04206734 495F38CD
077FA000 000000B5
9421FFF0 7C0802A6
90010014 93E1000C
7C7F1B78 4A83B215
3D208041 8149A378
3D008180 38600010
3908A074 C0AA00A8
38E0FFFF C08A00B0
38C0FFFF A12A0096
38A00014 C06A0018
388000C8 C04A0014
C02A0010 4CC63242
4BFF61E1 80010014
7FE3FB78 83E1000C
7C0803A6 38210010
4A83B1B8 5820506F
7320252E 30660A59
20506F73 20252E30
660A5A20 506F7320
252E3066 0A416E67
6C652025 68750A48
20537064 20252E32
660A5620 53706420
252E3266 00000000
</source>
<source version="GMSJ0A">
041252A0 496D4D61
077FA000 000000B5
9421FFF0 7C0802A6
90010014 93E1000C
7C7F1B78 4AAD0EB9
3D208040 8149EF88
3D008180 38600010
3908A074 C0AA00A8
38E0FFFF C08A00B0
38C0FFFF A12A0096
38A00014 C06A0018
388000C8 C04A0014
C02A0010 4CC63242
4BFF61E1 80010014
7FE3FB78 83E1000C
7C0803A6 38210010
4AAD0E5C 5820506F
7320252E 30660A59
20506F73 20252E30
660A5A20 506F7320
252E3066 0A416E67
6C652025 68750A48
20537064 20252E32
660A5620 53706420
252E326600000000
</source>
<source version="GMSP01">
0629E070 00000010
4955C009 60000000
60000000 60000000
04291750 495688B1
04138B50 496C15CD
077FA000 000001E8
9421FFE0 7C0802A6
90010024 93E1001C
4AAB56B5 38E00200
38C00320 38A0FFD8
3880000A 38610008
4AAC824D 814D9EF0
3FE08180 3BFFA1EC
39200000 39000002
3CE08180 38E7A164
80CA0048 38A10008
38800000 7FE3FB78
4AACE999 39200001
913F01B0 80010024
7C0803A6 83E1001C
38210020 4E800020
9421FFE8 7C0802A6
9001001C 93A1000C
93C10010 93E10014
7C7F1B78 81230000
83A90064 3D208180
8129A39C 2F890000
409E002C 7FE3FB78
7FA903A6 4E800421
8001001C 7C0803A6
83A1000C 83C10010
83E10014 38210018
4E800020 3D208040
612957B0 83C90000
3C608180 3863A1EC
4AACED21 C0BE00A8
C09E00B0 A0DE0096
C07E0018 C05E0014
C03E0010 3CA08180
38A5A164 38800080
4CC63242 4AB37811
4BFFFF94 9421FFF0
7C0802A6 90010014
93E1000C 7C7F1B78
4AAE9735 38E00081
7FE6FB78 38A00000
38800000 3C608180
3863A1EC 4AACDE05
80010014 7C0803A6
83E1000C 38210010
4E800020 5820506F
7320252E 30660A59
20506F73 20252E30
660A5A20 506F7320
252E3066 0A416E67
6C652025 68750A48
20537064 20252E32
660A5620 53706420
252E3266 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202020 20202020
20202000 00000000
C229E64C 00000003
38600000 3D808180
906CA39C 807F0040
60000000 00000000
</source>
</code>
<code>
<category>metadata</category>
<id>SpeedDisplay</id>
<title lang="en-US">Speed display</title>
<title lang="de-CH">Geschwindigkeits-Display</title>
<title lang="fr-FR">Affichage de vitesse</title>
<title lang="ja-JP">速度表示</title>
<author>Noki Doki, sup39(サポミク)</author>
<version>1.5</version>
<date>Mar 24, 2022</date>
<dependencies version="GMSJ01">drawText</dependencies>
<dependencies version="GMSJ0A">drawText</dependencies>
<description lang="en-US">
Shows Mario's speed at any given time.
::: warning
This code is not compatible with the Position/Angle/Speed Display code.
:::
</description>
<description lang="de-CH">
Zeigt Mario's Geschwindigkeit jederzeit auf dem Bildschirm an.
::: warning
Dieser Code ist nicht kompatibel mit dem Position/Winkel/Gewschw. Display Code
:::
</description>
<description lang="fr-FR">Affiche la vitesse de Mario à tout moment.</description>
<description lang="ja-JP">常に「マリオの速度」を表示します。</description>
<source version="GMSE01">
062A6160 00000010
49553F19 60000000
60000000 60000000
042998B8 49560749
04143F14 496B61F9
077FA000 00000188
9421FFE0 7C0802A6
90010024 93E1001C
4AABD6E5 38E00200
38C00320 38A0FFD8
3880000A 38610008
4AAD01B9 814D9FC8
3FE08180 3BFFA214
39200000 39000002
3CE08180 38E7A154
80CA0048 38A10008
38800000 7FE3FB78
4AAD67F1 39200001
913F01B0 80010024
7C0803A6 83E1001C
38210020 4E800020
9421FFE8 7C0802A6
9001001C 93C10010
93E10014 7C7F1B78
81230000 83C90064
3D208180 8129A3C4
2C090000 40820028
7FE3FB78 7FC903A6
4E800421 8001001C
7C0803A6 83C10010
83E10014 38210018
4E800020 93A1000C
3D208040 6129E0E8
83A90000 3C608180
3863A214 4AAD6B7D
C05D00A8 C03D00B0
3CA08180 38A5A154
38800030 4CC63242
4AB3F6A5 83A1000C
4BFFFFA0 9421FFF0
7C0802A6 90010014
93E1000C 7C7F1B78
4AAF159D 38E00081
7FE6FB78 38A00000
38800000 3C608180
3863A214 4AAD5C6D
80010014 7C0803A6
83E1000C 38210010
4E800020 48205370
6420252E 32660A56
20537064 20252E32
66202020 20202020
20202020 20202020
20202020 20202020
20202000 00000000
C22A66F4 00000003
38600000 3D808180
906CA3C4 807F0040
60000000 00000000
</source>
<source version="GMSJ01">
04206734 495F38CD
077FA000 0000007A
9421FFF0 7C0802A6
90010014 93E1000C
7C7F1B78 4A83B215
3D208041 8129A378
3D008180 38600010
3908A064 C04900A8
38E0FFFF C02900B0
38C0FFFF 38A00014
388000F0 4CC63242
4BFF61F1 80010014
7FE3FB78 83E1000C
7C0803A6 38210010
4A83B1C8 48205370
6420252E 32660A56
20537064 20252E32
66000000 00000000
</source>
<source version="GMSJ0A">
041252A0 496D4D61
077FA000 0000007A
9421FFF0 7C0802A6
90010014 93E1000C
7C7F1B78 4AAD0EB9
3D208040 8129EF88
3D008180 38600010
3908A064 C04900A8
38E0FFFF C02900B0
38C0FFFF 38A00014
388000F0 4CC63242
4BFF61F1 80010014
7FE3FB78 83E1000C
7C0803A6 38210010
4AAD0E6C 48205370
6420252E 32660A56
20537064 20252E32
66000000 00000000
</source>
<source version="GMSP01">
0629E070 00000010
4955C009 60000000
60000000 60000000
04291750 495688B1
04138B50 496C15BD
077FA000 00000188
9421FFE0 7C0802A6
90010024 93E1001C
4AAB56B5 38E00200
38C00320 38A0FFD8
3880000A 38610008
4AAC824D 814D9EF0
3FE08180 3BFFA214
39200000 39000002
3CE08180 38E7A154
80CA0048 38A10008
38800000 7FE3FB78
4AACE999 39200001
913F01B0 80010024
7C0803A6 83E1001C
38210020 4E800020
9421FFE8 7C0802A6
9001001C 93C10010
93E10014 7C7F1B78
81230000 83C90064
3D208180 8129A3C4
2C090000 40820028
7FE3FB78 7FC903A6
4E800421 8001001C
7C0803A6 83C10010
83E10014 38210018
4E800020 93A1000C
3D208040 612957B0
83A90000 3C608180
3863A214 4AACED25
C05D00A8 C03D00B0
3CA08180 38A5A154
38800030 4CC63242
4AB37825 83A1000C
4BFFFFA0 9421FFF0
7C0802A6 90010014
93E1000C 7C7F1B78
4AAE9745 38E00081
7FE6FB78 38A00000
38800000 3C608180
3863A214 4AACDE15
80010014 7C0803A6
83E1000C 38210010
4E800020 48205370
6420252E 32660A56
20537064 20252E32
66202020 20202020
20202020 20202020
20202020 20202020
20202000 00000000
C229E64C 00000003
38600000 3D808180
906CA3C4 807F0040
60000000 00000000
</source>
</code>
</codes> </codes>

View file

@ -1,4 +1,13 @@
# Changelog # Changelog
## Jan 28, 2023
### Rewrote 'drawText'
- Reduced parameters to struct pointer + format string + varargs
- Rewrote QFT, Pattern Selector, Customized Display with the new drawText function
### Improved Preview
- 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
## Jan 10, 2023 ## Jan 10, 2023
### Updated 'Quarterframe Timer' ### Updated 'Quarterframe Timer'
Added the following options to freeze QFT: Added the following options to freeze QFT:

View file

@ -136,8 +136,6 @@ export default {
this.codeConfigs = { this.codeConfigs = {
qft: getConfigQFT(), qft: getConfigQFT(),
PatternSelector: getConfigPS(), PatternSelector: getConfigPS(),
SpeedDisplay: {},
PASDisplay: {},
CustomizedDisplay: getConfigCD(this.version), CustomizedDisplay: getConfigCD(this.version),
}; };
}, },
@ -222,7 +220,10 @@ export default {
.filter(code => !(code.category === category && exclusive)) .filter(code => !(code.category === category && exclusive))
.map(code => code.id)); .map(code => code.id));
ids.add(id); ids.add(id);
return Object.fromEntries(Object.entries(this.codeConfigs).filter(([id]) => ids.has(id))); return {
...Object.fromEntries(Object.entries(this.codeConfigs).filter(([id]) => ids.has(id))),
_version: this.selectedVersion,
};
}, },
}, },
}; };

View file

@ -1,82 +1,30 @@
<template> <template>
<div class="preview-root"> <div class="preview-root">
<div class="preview-ctn"> <div class="preview-ctn">
<div v-if="qft"> <PreviewString :config="config.qft" :version="_version" />
<div :style="qft.bgStyle" /> <PreviewString v-for="mdp,i in (config.CustomizedDisplay || [])"
<PreviewString :x="qft.x" :y="qft.y" :size="qft.fontSize" :color="qft.color" text="0:00:00" /> :key="'mdp'+i" :config="mdp" :version="_version" />
</div> <PreviewString :config="config.PatternSelector" :version="_version" />
<div v-if="mdps">
<PreviewString v-for="mdp,i in mdps" :key="i"
:x="mdp.x" :y="mdp.y" :size="mdp.fontSize" :color="mdp.color" :text="mdp.text" />
</div>
<PreviewString v-if="ps" :x="ps.x" :y="ps.y" :size="ps.fontSize" :color="ps.color" :text="ps.text" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { rgbaI2S, fg2Style } from './codes/utils.js';
export default { export default {
props: { props: {
config: {type: Object}, config: {type: Object},
}, },
computed: { computed: {
mdps() { _version() {
const {config} = this; const {_version} = this.config
if (config.PASDisplay) return [{ return _version;
x: 16,
y: 200,
fontSize: 20,
color: '#fff',
text: 'X Pos -39\nY Pos 1207\nZ Pos -4193\nAngle 65535\nH Spd 15.15\nV Spd -31.17',
}];
if (config.SpeedDisplay) return [{
x: 16,
y: 240,
fontSize: 20,
color: '#fff',
text: 'H Spd 15.15\nV Spd -31.17',
}];
if (config.CustomizedDisplay) {
return config.CustomizedDisplay.map(({fgRGB, fgA, fgRGB2, fgA2, ...o}) => ({
...o,
color: fg2Style(fgRGB, fgA, fgRGB2, fgA2),
}));
}
},
qft() {
const {config: {qft}} = this;
if (qft == null) return;
const {x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2, bgRGB, bgA, width} = qft;
const bg = rgbaI2S(bgRGB, bgA);
return {
x, y, fontSize,
color: fg2Style(fgRGB, fgA, fgRGB2, fgA2),
bgStyle: {
left: x+'px',
top: (y-fontSize)+'px',
width: (width*fontSize/20)+'px',
height: fontSize+'px',
background: bg,
},
};
},
ps() {
const {config: {PatternSelector: ps}} = this;
if (ps == null) return;
const {x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2, label} = ps;
return {
x, y, fontSize,
color: fg2Style(fgRGB, fgA, fgRGB2, fgA2),
text: label+'#0 0 0',
};
}, },
}, },
} }
</script> </script>
<style scoped> <style scoped>
div.preview-root { .preview-root {
position: relative; position: relative;
width: 600px; width: 600px;
height: 448px; height: 448px;
@ -84,11 +32,8 @@ div.preview-root {
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
} }
div.preview-ctn { .preview-ctn {
position: absolute; position: absolute;
top: -16px; top: -16px;
} }
div.preview-ctn * {
position: absolute;
}
</style> </style>

View file

@ -1,47 +1,34 @@
<template> <template>
<div v-if="text" class="preview-str" :style="styles.root" > <div v-if="config">
<div v-for="style, i in styles.chars" :key="i" class="char-ctn" :style="style.ctn"> <div :style="styles.bg" />
<div class="char-bg" :style="style.bg" /> <div :class="previewCssClass" :style="styles.root" >
<div class="char-mask" :style="style.mask" /> <div v-for="style, i in styles.chars" :key="i" class="char-ctn" :style="style.ctn">
<div class="char-bg" :style="style.bg" />
<div class="char-mask" :style="style.mask" />
</div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import charInfo from '../data/font-jp.json'; import {rgbaI2S} from './codes/utils.js';
import {measureText} from './codes/text.js';
export default { export default {
props: { props: {
x: {type: Number}, config: {type: Object},
y: {type: Number}, version: {type: String},
size: {type: Number},
color: {type: String},
text: {type: String},
}, },
computed: { computed: {
previewCssClass() {
// TODO US
return `preview-str preview-${['GMSJ01', 'GMSJ0A'].includes(this.version) ? 'JP' : 'EU'}`;
},
styles() { styles() {
const {x: x0, y: y0, size: fontSize, color, text} = this; const {config, version} = this;
const {x: x0, y: y0, fontSize, fgRGB, fgA, fgRGB2, fgA2, bgRGB, bgA, bgLeft, bgRight, bgTop, bgBot, text} = config;
/** @type {{x: number, y: number, u: number, v: number}[]} */ const fgColor = rgbaI2S(fgRGB, fgA);
const chars = []; const {width, height, chars} = measureText(text, version);
let x = 0;
let y = 0;
let useKerning = false;
text.split('').forEach(c => {
const {index, kerning, width} = charInfo[c] ?? charInfo[' '];
if (c === '\n') {
useKerning = false;
x = 0;
y += 20;
return;
}
if (useKerning) x -= kerning;
useKerning = true;
// uv
const [u, v] = [index%25*20, (index/25|0)*20];
chars.push({x, y, u, v});
// next
x += width + kerning;
});
return { return {
root: { root: {
@ -60,10 +47,18 @@ export default {
mask: { mask: {
'mask-position': offset, 'mask-position': offset,
'-webkit-mask-position': offset, '-webkit-mask-position': offset,
background: color, background: fgRGB2 == null || fgA2 == null ? fgColor :
`linear-gradient(180deg, ${fgColor}, ${rgbaI2S(fgRGB2, fgA2)})`,
}, },
}; };
}), }),
bg: {
left: x0 - bgLeft + 'px',
top: y0 - fontSize - bgTop + 'px',
width: (width * fontSize) / 20 + bgLeft + bgRight + 'px',
height: (height * fontSize) / 20 + bgTop + bgBot + 'px',
background: rgbaI2S(bgRGB, bgA),
},
}; };
}, },
}, },
@ -71,27 +66,42 @@ export default {
</script> </script>
<style scoped> <style scoped>
* {
position: absolute;
}
.preview-str { .preview-str {
position: relative; position: relative;
} }
.preview-str * { .preview-str * {
position: absolute; position: absolute;
} }
div.char-ctn { .char-ctn {
isolation: isolate; isolation: isolate;
} }
div.char-ctn > div { .char-ctn > div {
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
div.char-bg { .char-mask {
background: url(/img/preview/font-jp.png);
}
div.char-mask {
mask-image: url(/img/preview/font-jp.png);
-webkit-mask-image: url(/img/preview/font-jp.png);
mask-repeat: no-repeat; mask-repeat: no-repeat;
-webkit-mask-repeat: no-repeat; -webkit-mask-repeat: no-repeat;
mix-blend-mode: multiply; mix-blend-mode: multiply;
} }
.preview-JP .char-bg {
background: url(/img/preview/font-JP.png);
}
.preview-JP .char-mask {
mask-image: url(/img/preview/font-JP.png);
-webkit-mask-image: url(/img/preview/font-JP.png);
}
.preview-EU .char-bg {
background: url(/img/preview/font-EU.png);
}
.preview-EU .char-mask {
mask-image: url(/img/preview/font-EU.png);
-webkit-mask-image: url(/img/preview/font-EU.png);
}
/* TODO US */
</style> </style>

View file

@ -1,31 +1,25 @@
import { parseJSON } from '../codegen.js'; import { parseJSON } from '../codegen.js';
import { ASM, makeInst, liDX, str2inst, makeProgram, inst2gecko } from '../asm.js'; import {
ASM,
makeInst,
liDX,
str2inst,
makeProgram,
inst2gecko,
getFillRectParams,
} from '../asm.js';
import { measureText } from '../text.js';
export const lskey = 'config/CustomizedDisplay'; export const lskey = 'config/CustomizedDisplay';
export const defaultConfig = [ import configDB from './configDB.js';
{ export const defaultConfig = [configDB.PAS];
x: 16,
y: 192,
fontSize: 18,
fgRGB: 0xffffff,
fgA: 0xff,
fgRGB2: null,
fgA2: null,
fmt: `X <x|.0|39.39>
Y <y|.0|1207.39>
Z <z|.0|-4193.6>
A <angle||65535>
H <HSpd|.2|15.15>
V <VSpd|.2|-31.17>
QF <QF||0>`,
},
];
/** @param {GameVersion} version */ /** @param {GameVersion} version */
export function getConfig(version) { export function getConfig(version) {
/** @type {typeof defaultConfig} */ /** @type {typeof defaultConfig} */
const config = typeof localStorage !== 'undefined' && parseJSON(localStorage.getItem(lskey)); const config = typeof localStorage !== 'undefined' && parseJSON(localStorage.getItem(lskey));
return (config instanceof Array ? config : defaultConfig).map(({ fmt, ...o }) => ({ return (config instanceof Array ? config : defaultConfig).map(({ fmt, ...o }) => ({
...defaultConfig[0],
...o, ...o,
fmt, fmt,
text: format2previewText(fmt, version), text: format2previewText(fmt, version),
@ -142,14 +136,22 @@ const load = {
}; };
/** /**
* @param {number} x * @param {string} version
* @param {number} y * @param {{
* @param {number} fontSize * x: number
* @param {number} colorTop * y: number
* @param {number} colorBot * fontSize: number
* fgRGB: number
* fgA: number
* fgRGB2: number | null
* fgA2: number | null
* }} drawTextOpt
*/ */
export function prepareDrawText(x, y, fontSize, colorTop, colorBot) { export function prepareDrawText(version, { x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2 }) {
let gpr = 9; const colorTop = (fgRGB << 8) | fgA;
const colorBot = fgRGB2 == null || fgA2 == null ? colorTop : (fgRGB2 << 8) | fgA;
let gpr = 5;
let fpr = 1; let fpr = 1;
let sp = 8; let sp = 8;
let fmt = ''; let fmt = '';
@ -219,8 +221,8 @@ export function prepareDrawText(x, y, fontSize, colorTop, colorBot) {
const rBase = 3; const rBase = 3;
insts.push(pre(rBase)); insts.push(pre(rBase));
// load all params // load all params
const rField = 5; const rField = 11; // tmp GPR
const fField = 9; const fField = 9; // tmp FPR
for (const { for (const {
info: { offset: srcoff, dtype, post }, info: { offset: srcoff, dtype, post },
dst, dst,
@ -258,32 +260,21 @@ export function prepareDrawText(x, y, fontSize, colorTop, colorBot) {
} }
} }
} }
// r8 = fmt // r3 = opt // sizeof(opt) = 0x10
const fmtbuf = str2inst(fmt); // r4 = fmt
const fmtbuf = str2inst(fmt, version);
insts.push( insts.push(
// bl 4+len4(fmt) // bl 4+sizeof(opt)+len4(fmt)
ASM.b(4 + (fmtbuf.length << 2), true), ASM.b(0x14 + (fmtbuf.length << 2), true),
// opt
[((x & 0xffff) << 16) | (y & 0xffff), fontSize, colorTop, colorBot],
// .string fmt // .string fmt
fmtbuf, fmtbuf,
// mflr r8 // mflr r3
ASM.mflr(8), ASM.mflr(3),
// addi r4, r3, sizeof(opt)
ASM.addi(4, 3, 0x10),
); );
/*
* r3 = x
* r4 = y
* r5 = fontSize
* r6 = colorTop
* r7 = colorBot
*/
insts.push(
liDX(3, x),
liDX(4, y),
liDX(5, fontSize),
liDX(6, colorTop),
colorTop === colorBot ? ASM.mr(7, 6) : liDX(7, colorBot),
);
// cr{set|clr} 6
insts.push((hasFloat ? ASM.crset : ASM.crclr)(6));
// DONE // DONE
return { code: insts.flatMap((e) => e), spNeed: spAdd }; return { code: insts.flatMap((e) => e), spNeed: spAdd };
}, },
@ -369,58 +360,72 @@ export function format2previewText(input, version, f = null) {
return preview; return preview;
} }
const addrsOrig = { import addrs from '../addrs.js';
GMSJ01: 0x80206a00 - 0x2c, const addrOrigOff = -0x2c; // drawWater - [-0x30, -0x18]
GMSJ0A: 0x8012556c - 0x2c,
GMSE01: 0x801441e0 - 0x2c,
GMSP01: 0x80138e1c - 0x2c,
};
const addrsSetup2D = {
GMSJ01: 0x80035228,
GMSJ0A: 0x802caecc,
GMSE01: 0x802eb6bc,
GMSP01: 0x802e3864,
};
const addrDrawText = 0x817f0238;
const addrDst = 0x817fa000; const addrDst = 0x817fa000;
/** /**
* @param {GameVersion} version * @param {GameVersion} version
*/ */
export default function codegen(version) { export default function codegen(version) {
const config = getConfig(version); const configs = getConfig(version);
let spOff = 0; let spOff = 0;
const fcodes = /** @type {Inst[]} */ ([]); const fcodes = /** @type {Inst[]} */ ([]);
const bcodes = /** @type {Inst[]} */ ([]);
for (const { x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2, fmt } of config) { for (const config of configs) {
// color const { fontSize, fmt, bgA } = config;
const colorTop = (fgRGB << 8) | fgA;
const colorBot = fgRGB2 == null || fgA2 == null ? colorTop : (fgRGB2 << 8) | fgA;
// prepare drawText // prepare drawText
const f = prepareDrawText(x, y, fontSize, colorTop, colorBot); const f = prepareDrawText(version, config);
format2previewText(fmt, version, f); const text = format2previewText(fmt, version, f);
// update code and sp // text code
const { code, spNeed } = f.makeCode(); if (fmt.trim()) {
spOff = Math.max(spOff, spNeed); // update code and sp
fcodes.push(code); const { code, spNeed } = f.makeCode();
spOff = Math.max(spOff, spNeed);
fcodes.push(code);
}
// background code
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),
);
}
} }
const addrOrig = addrsOrig[version]; const addrOrig = addrs.drawWater[version] + addrOrigOff;
const addrSetup2D = addrsSetup2D[version]; const addrFillRect = addrs.fillRect[version];
// program // program
const program = makeProgram(addrDst); const program = makeProgram(addrDst);
// addi r3, r1, 0xE90 // la r3, ctx(r1)
program.push(ASM.addi(3, 1, 0xe90)); // program.push(ASM.addi(3, 1, addrs.ctxSpOff[version]));
// addi r1, r1, -spOff // addi r1, r1, -spOff
if (spOff) program.push(ASM.addi(1, 1, -spOff)); if (spOff) program.push(ASM.addi(1, 1, -spOff));
// bl setup // bl J2DGrafContext::setup2D
program.bl(addrSetup2D); // program.bl(addrs.setup2D[version]);
// (fill_rect)
for (const code of bcodes) {
program.push(code);
program.bl(addrFillRect);
}
// (drawText) // (drawText)
for (const code of fcodes) { for (const code of fcodes) {
program.push(code); program.push(code);
program.bl(addrDrawText); program.bl(addrs.drawText);
} }
// addi r1, r1, spOff // addi r1, r1, spOff
if (spOff) program.push(ASM.addi(1, 1, spOff)); if (spOff) program.push(ASM.addi(1, 1, spOff));

View file

@ -2,19 +2,26 @@
<div> <div>
<Preview :config="previewConfig" /> <Preview :config="previewConfig" />
<div v-for="c,i in config" :key="i" class="textcell"> <div v-for="c,i in config" :key="i" class="textcell">
<button @click="config.splice(i, 1)" class="textcell-remove">&#215;</button> <button class="textcell-remove" @click="deletionConfirm(i)">&#215;</button>
<Cell :value="c" @input="$event => config.splice(i, 1, $event)" :version="version" /> <Cell :value="c" @input="$event => config.splice(i, 1, $event)" :version="version" />
</div> </div>
<div> <div class="btn-ctn">
<button @click="config.push(defaultConfigCell)" class="textcell-add">+</button> <button @click="config.push(db.PAS)">{{l('add.PAS')}}</button>
<button @click="config.push(db.speed)">{{l('add.speed')}}</button>
<button @click="config.push(db.detailed)">{{l('add.detailed')}}</button>
<button @click="config.push(db.rect)">{{l('add.rect')}}</button>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import labels from './labels.json';
import { defaultConfig, getConfig, lskey, format2previewText } from './codegen.js'; import { defaultConfig, getConfig, lskey, format2previewText } from './codegen.js';
import configDB from './configDB.js';
import { makeUpdateConfig, makeGetLabel } from '../utils.js';
import Cell from './Cell.vue'; import Cell from './Cell.vue';
/** @typedef {'GMSJ01'|'GMSJ0A'|'GMSE01'|'GMSP01'} GameVersion */
export default { export default {
components: { components: {
Cell, Cell,
@ -23,21 +30,35 @@ export default {
version: {type: String}, version: {type: String},
previewConfig: {type: Object}, previewConfig: {type: Object},
}, },
computed: {
l() {
return makeGetLabel(labels, this.$lang);
},
db() {
const version = /**@type{GameVersion}*/(this.version);
return Object.fromEntries(Object.entries(configDB).map(([k, v]) => [
k,
{...v, text: format2previewText(v.fmt, version)},
]));
},
},
data() { data() {
const config = getConfig(); const version = /**@type{GameVersion}*/(this.version);
const config = getConfig(version);
const defaultConfigCell = { const defaultConfigCell = {
text: format2previewText(defaultConfig[0].fmt, this.version), text: format2previewText(defaultConfig[0].fmt, version),
...defaultConfig[0], ...defaultConfig[0],
}; };
return {config, defaultConfigCell}; return {config, defaultConfigCell};
}, },
watch: { watch: {
config(config) { config: makeUpdateConfig(lskey, defaultConfig),
// save config },
const sconf = config.map(({text, ...o}) => ({...o})); methods: {
localStorage.setItem(lskey, JSON.stringify(sconf)); /** @param {number} i */
// emit deletionConfirm(i) {
this.$emit('config', config); // if (window.confirm(this.l('deletionConfirm'))) {
this.config.splice(i, 1);
}, },
}, },
} }
@ -57,4 +78,7 @@ export default {
color: red; color: red;
cursor: pointer; cursor: pointer;
} }
.btn-ctn button {
display: block;
}
</style> </style>

View file

@ -0,0 +1,58 @@
const base = {
fontSize: 20,
fgRGB: 0xffffff,
fgA: 0xff,
fgRGB2: null,
fgA2: null,
bgRGB: 0,
bgA: 0,
bgLeft: 0,
bgRight: 0,
bgTop: 0,
bgBot: 0,
};
export default {
PAS: {
...base,
x: 16,
y: 200,
fmt: `X Pos <x|.0|39.39>
Y Pos <y|.0|1207.39>
Z Pos <z|.0|-4193.6>
Angle <angle||65535>
H Spd <HSpd|.2|15.15>
V Spd <VSpd|.2|-31.17>`,
},
speed: {
...base,
x: 16,
y: 240,
fmt: `H Spd <HSpd|.2|15.15>
V Spd <VSpd|.2|-31.17>`,
},
detailed: {
...base,
x: 16,
y: 192,
fontSize: 18,
fmt: `X <x|.0|39.39>
Y <y|.0|1207.39>
Z <z|.0|-4193.6>
A <angle||65535>
C <CAngle||9>
H <HSpd|.2|15.15>
V <VSpd|.2|-31.17>
QF <QF||0>`,
},
rect: {
...base,
x: 32,
y: 48,
fontSize: 0,
fmt: '',
bgRight: 536,
bgBot: 384,
bgA: 0x7f,
},
};

View file

@ -1,8 +1,22 @@
{ {
"ja-JP": { "ja-JP": {
"format": "フォーマット:" "format": "フォーマット:",
"deletionConfirm": "本当に削除しますか?",
"add": {
"PAS": "+ 位置/角度/速度表示",
"speed": "+ 速度表示",
"detailed": "+ 欲張りセット",
"rect": "+ 長方形"
}
}, },
"en-US": { "en-US": {
"format": "Format:" "format": "Format:",
"deletionConfirm": "Are you sure to delete?",
"add": {
"PAS": "+ Position/Angle/Speed Display",
"speed": "+ Speed Display",
"detailed": "+ Detailed Display",
"rect": "+ Rectangle"
}
} }
} }

View file

@ -22,15 +22,13 @@
</template> </template>
<script> <script>
import {getConfig, lskey, buttonValues} from './codegen.js'; import { makeUpdateConfig } from '../utils.js';
import {getConfig, defaultConfig, lskey, buttonValues} from './codegen.js';
import labels from './labels.json'; import labels from './labels.json';
import {getLabels} from '../codegen.js';
export default { export default {
methods: { methods: {
updateConfig() { updateConfig: makeUpdateConfig(lskey, defaultConfig),
localStorage.setItem(lskey, JSON.stringify({button: this.button}));
},
toggleButton(event, value) { toggleButton(event, value) {
this.button = event.target.checked ? this.button = event.target.checked ?
this.button | value : // ON this.button | value : // ON

View file

@ -0,0 +1,46 @@
export const code04 = `
0403B6FC 497BD905
041441BC 496B4ED8
`;
export const codeBase = `
801F0000 3D00817F
61089147 3D40817F
614A0024 3D80803B
618C6178 7C006040
41820048 3D80803B
618C3F88 7C006040
40820034 819F0004
898C0013 508C442E
39280031 88080007
7CEA00AE A0080005
7C006040 41820024
39080007 7C084840
4180FFE4 4A854070
2C05FFFF 4082FFF8
88EA0000 39080031
7CE73B79 4182FFE8
7C0838AE 280000FF
4182FFDC 7C030378
4E800020 3C808040
A4044486 3C60817F
85630024 70000040
41A20050 88840019
548007FE 7D605850
5480FFFE 7D6B0214
556B07BE 5480F7BF
41A20030 280B0003
41A00008 39600000
5480EFFE 5080077A
7D8358AE 7D8C0214
280C0005 41A00008
398CFFFB 7D8359AE
99630003 3C002020
60002023 556C183E
5C00603E 5405063E
5407C63E 5409863E
88C30000 89030001
89430002 3C60817F
60639138 38830045
4BFF7109 4A94B08C
`;

View file

@ -1,11 +1,11 @@
export const addrDraw2D = 0x802069dc;
export const code04 = ` export const code04 = `
0424F32C 495A9CD5 0424F32C 495A9CD5
042069DC 495F26B8 042069DC 495F26B8
`; `;
export const codes = [
/* 077F9000 000001AF */ ` export const codeBase = `
3D40817F 801F0000 3D00817F
61089147 3D40817F
614A0024 3D80803D 614A0024 3D80803D
618CA9C0 7C006040 618CA9C0 7C006040
41820048 3D80803D 41820048 3D80803D
@ -22,38 +22,25 @@ export const codes = [
7CE73B79 4182FFE8 7CE73B79 4182FFE8
7C0838AE 280000FF 7C0838AE 280000FF
4182FFDC 7C030378 4182FFDC 7C030378
4E800020 38610E90 4E800020 3C808040
4A83C191 3C808040
A4040D82 3C60817F A4040D82 3C60817F
84A30024 70000040 85630024 70000040
41A20050 88840019 41A20050 88840019
548007FE 7CA02850 548007FE 7D605850
5480FFFE 7CA50214 5480FFFE 7D6B0214
54A507BE 5480F7BF 556B07BE 5480F7BF
41A20030 28050003 41A20030 280B0003
41A00008 38A00000 41A00008 39600000
5480EFFE 5080077A 5480EFFE 5080077A
7D8328AE 7D8C0214 7D8358AE 7D8C0214
280C0005 41A00008 280C0005 41A00008
398CFFFB 7D8329AE 398CFFFB 7D8359AE
98A30003 3821FFF0 99630003 3C002020
`, 60002023 556C183E
/* li r8 */ ` 5C00603E 5405063E
3C002020 60002023 5407C63E 5409863E
54AC183E 5C00603E 88C30000 89030001
`, 89430002 3C60817F
/* stb r0, fmtCS0+fmtCSD*i(r8) */ ` 60639138 38830045
89230000 4BFF7109 4AA0D8AC
89430001 89630002 `;
91610008
`,
/* li r3~r7, drawText, b */ `
452020FF 213200FF
621CFF1D 32005025
25252630 01FF4520
20213001 FFFF621C
1D300151 0707FF08
3102FF36 01FF0231
021E6E20 FF
` /* fmt */,
];

View file

@ -1,12 +1,11 @@
export const addrDraw2D = 0x80125548;
export const code04 = ` export const code04 = `
04027900 497D1701 04027900 497D1701
04125548 496D3B4C 04125548 496D3B4C
`; `;
export const codes = [ export const codeBase = `
` 801F0000 3D00817F
3D40817F 61089147 3D40817F
614A0024 3D80803A 614A0024 3D80803A
618C6D70 7C006040 618C6D70 7C006040
41820048 3D80803A 41820048 3D80803A
@ -23,38 +22,25 @@ export const codes = [
7CE73B79 4182FFE8 7CE73B79 4182FFE8
7C0838AE 280000FF 7C0838AE 280000FF
4182FFDC 7C030378 4182FFDC 7C030378
4E800020 38610E90 4E800020 3C80803F
4AAD1E35 3C80803F
A404545A 3C60817F A404545A 3C60817F
84A30024 70000040 85630024 70000040
41A20050 88840019 41A20050 88840019
548007FE 7CA02850 548007FE 7D605850
5480FFFE 7CA50214 5480FFFE 7D6B0214
54A507BE 5480F7BF 556B07BE 5480F7BF
41A20030 28050003 41A20030 280B0003
41A00008 38A00000 41A00008 39600000
5480EFFE 5080077A 5480EFFE 5080077A
7D8328AE 7D8C0214 7D8358AE 7D8C0214
280C0005 41A00008 280C0005 41A00008
398CFFFB 7D8329AE 398CFFFB 7D8359AE
98A30003 3821FFF0 99630003 3C002020
`, 60002023 556C183E
` 5C00603E 5405063E
3C002020 60002023 5407C63E 5409863E
54AC183E 5C00603E 88C30000 89030001
`, 89430002 3C60817F
` 60639138 38830045
89230000 4BFF7109 4A92C418
89430001 89630002 `;
91610008
`,
`
452020FF 213200FF
621CFF1D 32005025
25252630 01FF4520
20213001 FFFF621C
1D300151 0707FF08
3102FF36 01FF0231
021E6E20 FF
`,
];

View file

@ -0,0 +1,46 @@
export const code04 = `
0403B54C 497BDAB5
04138DF8 496C029C
`;
export const codeBase = `
801F0000 3D00817F
61089147 3D40817F
614A0024 3D80803A
618CDF98 7C006040
41820048 3D80803A
618CBDA8 7C006040
40820034 819F0004
898C0013 508C442E
39280031 88080007
7CEA00AE A0080005
7C006040 41820024
39080007 7C084840
4180FFE4 4A8531C4
2C05FFFF 4082FFF8
88EA0000 39080031
7CE73B79 4182FFE8
7C0838AE 280000FF
4182FFDC 7C030378
4E800020 3C808040
A404BC26 3C60817F
85630024 70000040
41A20050 88840019
548007FE 7D605850
5480FFFE 7D6B0214
556B07BE 5480F7BF
41A20030 280B0003
41A00008 39600000
5480EFFE 5080077A
7D8358AE 7D8C0214
280C0005 41A00008
398CFFFB 7D8359AE
99630003 3C002020
60002023 556C183E
5C00603E 5405063E
5407C63E 5409863E
88C30000 89030001
89430002 3C60817F
60639138 38830045
4BFF7109 4A93FCC8
`;

View file

@ -1,12 +1,15 @@
import { parseJSON } from '../codegen.js'; import { parseJSON } from '../codegen.js';
import { ASM, liDX, strlen, str2inst, inst2gecko } from '../asm.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';
export const lskey = 'config/PatternSelector'; export const lskey = 'config/PatternSelector';
import * as GMSJ01 from './code/GMSJ01.js'; import * as GMSJ01 from './code/GMSJ01.js';
import * as GMSJ0A from './code/GMSJ0A.js'; import * as GMSJ0A from './code/GMSJ0A.js';
// import * as GMSE01 from './code/GMSE01.js'; // import * as GMSE01 from './code/GMSE01.js';
// import * as GMSP01 from './code/GMSP01.js'; import * as GMSP01 from './code/GMSP01.js';
const codes = { GMSJ01, GMSJ0A }; const codes = { GMSJ01, GMSJ0A, GMSP01 };
export const defaultConfig = { export const defaultConfig = {
x: 16, x: 16,
@ -16,94 +19,91 @@ export const defaultConfig = {
fgA: 0xff, fgA: 0xff,
fgRGB2: null, fgRGB2: null,
fgA2: null, fgA2: null,
bgRGB: 0,
bgA: 0,
bgLeft: 0,
bgRight: 0,
bgTop: 0,
bgBot: 0,
label: 'Pattern ', label: 'Pattern ',
}; };
/** @returns {typeof defaultConfig} */ /** @returns {typeof defaultConfig} */
export function getConfig() { export function getConfig() {
const config = const config =
(typeof localStorage !== 'undefined' && parseJSON(localStorage.getItem(lskey))) || {}; (typeof localStorage !== 'undefined' && parseJSON(localStorage.getItem(lskey))) || {};
return { ...defaultConfig, ...config }; const o = { ...defaultConfig, ...config };
return { ...o, text: getPreviewText(o) };
} }
const addrDrawText = 0x817f0238; /** @param {typeof defaultConfig} config */
const addrCodeBase = 0x817f9000; export const getPreviewText = ({ label }) => label + '#0 0 0';
const addrPV1Data1 = 0x817f9167;
const addrFmt0 = 0x817f919d; const codePattern = `
452020FF 213200FF
621CFF1D 32005025
25252630 01FF4520
20213001 FFFF621C
1D300151 0707FF08
3102FF36 01FF0231
021E6E20 FF
`;
/** @param {keyof typeof codes} version */ /** @param {keyof typeof codes} version */
export default function codegen(version) { export default function codegen(version) {
const { x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2, label } = getConfig(); const config = getConfig();
const { x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2, bgA, label } = config;
const colorTop = (fgRGB << 8) | fgA; const colorTop = (fgRGB << 8) | fgA;
const colorBot = fgRGB2 == null || fgA2 == null ? colorTop : (fgRGB2 << 8) | fgA; const colorBot = fgRGB2 == null || fgA2 == null ? colorTop : (fgRGB2 << 8) | fgA;
const text = label + '>%X>%X>%X'; const text = label + '%c%X%c%X%c%X';
const fmtCS0 = strlen(label);
const fmtCSD = 3;
const { code04, addrDraw2D, codes: cs0 } = codes[version]; const { code04, codeBase } = codes[version];
const cs = cs0.map((e) => e.replace(/\s+/g, '')); const code07 = [
const params = [ codeBase,
liDX(3, x), // drawTextOpt
liDX(4, y), int2hex(x, 2),
liDX(5, fontSize), int2hex(y, 2),
liDX(6, colorTop), int2hex(fontSize, 4),
colorTop === colorBot ? ASM.mr(7, 6) : liDX(7, colorBot), int2hex(colorTop, 4),
].flatMap((e) => e); int2hex(colorBot, 4),
const extraOffset = (params.length - 5) << 2; // default: 5 inst // pattern.s
let pc; codePattern,
// 07 // fmt
let code07 = '801F0000'; str2hex(text, version),
/* li32 rEntry, .data.patterns.PV1-1 */
code07 += liDX(8, addrPV1Data1 + extraOffset)
.map(inst2gecko)
.join('');
code07 += cs[0];
/* li r8 */
code07 += liDX(8, addrFmt0 + extraOffset)
.map(inst2gecko)
.join('');
code07 += cs[1];
/* stb r0/12/12, fmtCS0+fmtCSD*i(r8) */
code07 += [
ASM.stb(0, 8, fmtCS0),
0x540cc63e,
ASM.stb(12, 8, fmtCS0 + fmtCSD),
0x540c863e,
ASM.stb(12, 8, fmtCS0 + fmtCSD * 2),
] ]
.flatMap((e) => e) .map((s) => s.replace(/\s+/g, ''))
.map(inst2gecko)
.join(''); .join('');
/* (code) */
code07 += cs[2];
/* r3~r7 */
code07 += params.map(inst2gecko).join('');
code07 += '4CC63182'; // crclr 6
/* bl drawText */
pc = addrCodeBase + (code07.length >> 1);
code07 += ASM.b(addrDrawText - pc, true)
.map(inst2gecko)
.join('');
/* addi */
code07 += '38210010';
/* b 4+$b$.draw2d */
pc = addrCodeBase + (code07.length >> 1);
code07 += ASM.b(4 + addrDraw2D - pc, false)
.map(inst2gecko)
.join('');
/* (code) */
code07 += cs[3];
/* fmt */
// prepend 1 dummy char as 'FF' in code
code07 += str2inst('.' + text)
.map(inst2gecko)
.join('')
.slice(2);
const head07 = [0x06000000 | (addrCodeBase & 0x1ffffff), code07.length >> 1] const head07 = [
.map(inst2gecko) '077F9000',
.join(''); // byte count = hex length >> 1
// align code 07 (8 digit = 4 byte) int2hex(code07.length >> 1, 4),
if (code07.length & 8) code07 += '00000000'; ].join('');
return (code04 + head07 + code07).replace(/\s+/g, ''); // align code 07 (1 line = 16 hex digits)
const tail07 = ''.padEnd(code07.length % 16 ? 16 - (code07.length % 16) : 0, '0');
// background
const addrFillRect = addrs.fillRect[version];
const codeBg = bgA
? [
0xc2000000 + ((addrs.drawWater[version] - 0x28) & 0x01ffffff),
0x00000007,
0x48000019, // bl trick
// rect, color
...getFillRectParams(config, measureText(label + '#0 0 0', version)),
0x7c6802a6, // mtlr r3
0x38830010, // addi r4, r3, 0x10
0x3d800000 | (addrFillRect >>> 16), // lis r12, fill_rect@h
0x618c0000 | (addrFillRect & 0xffff), // ori r12, r12, fill_rect@l
0x7d8803a6, // mtlr r12
0x4e800021, // blrl
0x60000000, // nop
0x00000000, // End of C2
]
.map(inst2gecko)
.join('')
: '';
return (code04 + head07 + code07 + tail07 + codeBg).replace(/\s+/g, '');
} }

View file

@ -11,19 +11,12 @@
</template> </template>
<script> <script>
import {getConfig, lskey} from './codegen.js'; import {getConfig, defaultConfig, lskey, getPreviewText} from './codegen.js';
import labels from './labels.json'; import labels from './labels.json';
import TextConfig from '../TextConfig.vue'; import TextConfig from '../TextConfig.vue';
import { makeUpdateConfig } from '../utils.js';
function updateConfig() { const updateConfig = makeUpdateConfig(lskey, defaultConfig, getPreviewText);
const {x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2, label} = this;
const config = {
x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2, label,
};
localStorage.setItem(lskey, JSON.stringify(config));
this.$emit('config', config);
}
export default { export default {
components: { components: {
TextConfig, TextConfig,

View file

@ -15,6 +15,23 @@
<span>{{l.fgColor2}}</span><input type="color" :value="rgbI2S(fgRGB2)" @change="fgRGB2 = rgbS2I($event.target.value)"> <span>{{l.fgColor2}}</span><input type="color" :value="rgbI2S(fgRGB2)" @change="fgRGB2 = rgbS2I($event.target.value)">
<span>{{l.alpha}}</span><input type="number" min="0" max="255" v-model.number="fgA2"><span>/255={{(fgA2/2.55).toFixed(1)}}%</span> <span>{{l.alpha}}</span><input type="number" min="0" max="255" v-model.number="fgA2"><span>/255={{(fgA2/2.55).toFixed(1)}}%</span>
</div> </div>
<div>
<div>
<span>{{ l.bgColor }}</span
><input type="color" :value="rgbI2S(bgRGB)" @change="bgRGB = rgbS2I($event.target.value)" />
<span>{{ l.alpha }}</span
><input type="number" min="0" max="254" v-model.number="bgA" /><span
>/255={{ (bgA / 2.55).toFixed(1) }}%</span
>
</div>
<div>
<span>{{ l.bgOffset }}</span>
<span>{{ l.left }}</span><input type="number" v-model.number="bgLeft" />
<span>{{ l.right }}</span><input type="number" v-model.number="bgRight" />
<span>{{ l.top }}</span><input type="number" v-model.number="bgTop" />
<span>{{ l.bottom }}</span><input type="number" v-model.number="bgBot" />
</div>
</div>
</div> </div>
</template> </template>
@ -41,6 +58,7 @@ export default {
}, },
...Object.fromEntries([ ...Object.fromEntries([
'x', 'y', 'fontSize', 'fgRGB', 'fgA', 'fgRGB2', 'fgA2', 'x', 'y', 'fontSize', 'fgRGB', 'fgA', 'fgRGB2', 'fgA2',
'bgRGB', 'bgA', 'bgLeft', 'bgRight', 'bgTop', 'bgBot',
].map(k => [k, makeField(k)])), ].map(k => [k, makeField(k)])),
}, },
methods: { methods: {
@ -72,7 +90,7 @@ input[type=number], td.right {
text-align: right; text-align: right;
} }
input[type="number"] { input[type="number"] {
width: 3em; width: 2em;
margin: 0 2px; margin: 0 2px;
} }
.appearance > div { .appearance > div {

View file

@ -0,0 +1,28 @@
export default {
drawText: 0x817f0238,
drawWater: {
GMSJ01: 0x80206a00,
GMSJ0A: 0x8012556c,
GMSE01: 0x801441e0,
GMSP01: 0x80138e1c,
},
fillRect: {
GMSJ01: 0x80201ea8,
GMSJ0A: 0x80121660,
GMSE01: 0x80140390,
GMSP01: 0x80134f0c,
},
setup2D: {
GMSJ01: 0x80035228,
GMSJ0A: 0x802caecc,
GMSE01: 0x802eb6bc,
GMSP01: 0x802e3864,
},
// r1 offset of J2DGrafContext in TGCConsole2::perform()
ctxSpOff: {
GMSJ01: 0xe90,
GMSJ0A: 0xbec,
GMSE01: 0xbd0,
GMSP01: 0xbe4,
},
};

View file

@ -173,16 +173,39 @@ export function liDX(rT, D) {
} }
} }
/** @param {string} s */ /**
export function strlen(s) { * @param {string} s
const fmtbuf = Encoding.convert(Encoding.stringToCode(s), 'SJIS'); * @param {string} version
return fmtbuf.length; // not NUL terminated */
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;
});
fmtbuf.push(0); // NUL terminated
return fmtbuf;
} }
/** @param {string} s */ /**
export function str2inst(s) { * @param {string} s
const fmtbuf = Encoding.convert(Encoding.stringToCode(s), 'SJIS'); * @param {string} version
fmtbuf.push(0); // NUL terminated */
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 fmtlen = fmtbuf.length;
const fmtlen3 = fmtlen & 3; const fmtlen3 = fmtlen & 3;
const pad = fmtlen3 ? 4 - fmtlen3 : 0; const pad = fmtlen3 ? 4 - fmtlen3 : 0;
@ -223,3 +246,30 @@ export function makeProgram(pc) {
/** @param {number} x */ /** @param {number} x */
export const inst2gecko = (x) => (x >>> 0).toString(16).toUpperCase().padStart(8, '0'); export const inst2gecko = (x) => (x >>> 0).toString(16).toUpperCase().padStart(8, '0');
/**
* @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,
];

View file

@ -6,7 +6,13 @@
"fgColorGrad": "グラデーション", "fgColorGrad": "グラデーション",
"fgColor1": "文字色(上)", "fgColor1": "文字色(上)",
"fgColor2": "文字色(下)", "fgColor2": "文字色(下)",
"alpha": "不透明度=" "alpha": "不透明度=",
"bgColor": "背景色:",
"bgOffset": "背景位置:",
"left": "左",
"right": "右",
"top": "上",
"bottom": "下"
}, },
"en-US": { "en-US": {
"location": "Location: ", "location": "Location: ",
@ -15,6 +21,22 @@
"fgColorGrad": "Gradient", "fgColorGrad": "Gradient",
"fgColor1": "Font color(Top): ", "fgColor1": "Font color(Top): ",
"fgColor2": "Font color(Bottom): ", "fgColor2": "Font color(Bottom): ",
"alpha": "Alpha=" "alpha": "Alpha=",
"bgColor": "Background color: ",
"bgOffset": "Background offset: ",
"left": "Left",
"right": "Right",
"top": "Top",
"bottom": "Bottom"
},
"fr-FR": {
"location": "Position : ",
"fontSize": "Taille de police : ",
"fgColor": "Couleur du texte : ",
"fgColorGrad": "Dégradé",
"fgColor1": "Couleur du texte (haut) : ",
"fgColor2": "Couleur du texte (bas) : ",
"bgColor": "Couleur de fond : ",
"alpha": "Alpha = "
} }
} }

View file

@ -1,10 +1,13 @@
import { parseJSON } from '../codegen.js'; import { parseJSON } from '../codegen.js';
import { getFillRectParams } from '../asm';
import { int2hex } from '../utils';
export const lskey = 'config/qft'; export const lskey = 'config/qft';
export const getPreviewText = () => '0:00.000';
export const defaultConfig = { export const defaultConfig = {
x: 16, x: 16,
y: 456, y: 456,
width: 112,
fontSize: 20, fontSize: 20,
fgRGB: 0xffffff, fgRGB: 0xffffff,
fgA: 0xff, fgA: 0xff,
@ -12,6 +15,10 @@ export const defaultConfig = {
fgA2: null, fgA2: null,
bgRGB: 0x000000, bgRGB: 0x000000,
bgA: 0x80, bgA: 0x80,
bgLeft: 0,
bgRight: 2,
bgTop: 2,
bgBot: 0,
freezeDuration: 30, freezeDuration: 30,
freeze: { freeze: {
yellowCoin: false, yellowCoin: false,
@ -35,6 +42,7 @@ export const defaultConfig = {
}, },
}; };
/** @returns {typeof defaultConfig} */
export function getConfig() { export function getConfig() {
const config = const config =
(typeof localStorage !== 'undefined' && parseJSON(localStorage.getItem(lskey))) || {}; (typeof localStorage !== 'undefined' && parseJSON(localStorage.getItem(lskey))) || {};
@ -45,6 +53,7 @@ export function getConfig() {
...defaultConfig.freeze, ...defaultConfig.freeze,
...config.freeze, ...config.freeze,
}, },
text: getPreviewText(),
}; };
} }
@ -57,8 +66,10 @@ 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'; import { measureText } from '../text.js';
export const statusKeys = Object.keys(statusDB); 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 ## save freeze frame, load and save QF
@ -100,7 +111,7 @@ export default function codegen(version, baseCode) {
const hook = freezeCodeHooks[key]; const hook = freezeCodeHooks[key];
if (hook) { if (hook) {
if (key === 'blueCoin') { if (key === 'blueCoin') {
const addr = hook; const addr = /**@type{number}*/ (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),
@ -234,25 +245,24 @@ export default function codegen(version, baseCode) {
// ui // ui
/* bounds */ /* bounds */
const { x, y, fontSize, width } = config; 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; const scale = fontSize / 20;
code += '077F0094 0000001D'; code += '077F0094 0000001D';
code += [ code += rect.map(int2gecko).join('');
x, // x1
y - fontSize - 2, // y1
x + width * scale, // x2
y, // y2
]
.map(int2gecko)
.join('');
code += '25753a253032752e2530337500000000'; // fmt code += '25753a253032752e2530337500000000'; // fmt
/* fontSize, fgColor, bgColor */ /**
code += '077F0110 00000010'; * 817F0110 drawTextOpt: {x, y, fontSize, colorTop, colorBot}
const bgColor = (config.bgRGB & 0xffffff) * 256 + (config.bgA & 0xff); * 817F0120 bgColor
*/
code += '077F0110 00000014';
const fgColor = (config.fgRGB & 0xffffff) * 256 + (config.fgA & 0xff); const fgColor = (config.fgRGB & 0xffffff) * 256 + (config.fgA & 0xff);
const fgColor2 = const fgColor2 =
((config.fgRGB2 ?? config.fgRGB) & 0xffffff) * 256 + ((config.fgA2 ?? config.fgA) & 0xff); ((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 += [fontSize, fgColor, fgColor2, bgColor].map(int2gecko).join('');
code += '00000000';
return code.replace(/\s/g, ''); return code.replace(/\s/g, '');
} }

View file

@ -2,17 +2,7 @@
<div id="config"> <div id="config">
<section class="appearance"> <section class="appearance">
<h3>{{ l.h3 }}</h3> <h3>{{ l.h3 }}</h3>
<div> <TextConfig v-model="textConfig" />
<TextConfig v-model="textConfig" />
</div>
<div>
<span>{{ l.bgColor }}</span
><input type="color" :value="rgbI2S(bgRGB)" @change="bgRGB = rgbS2I($event.target.value)" />
<span>{{ l.alpha }}</span
><input type="number" min="0" max="255" v-model.number="bgA" /><span
>/255={{ (bgA / 2.55).toFixed(1) }}%</span
>
</div>
</section> </section>
<section class="freeze"> <section class="freeze">
<h3>{{ l.freeze.h3 }}</h3> <h3>{{ l.freeze.h3 }}</h3>
@ -35,31 +25,12 @@
</template> </template>
<script> <script>
import { getConfig, lskey, codes, statusKeys } from './codegen.js'; import { defaultConfig, lskey, getConfig, getPreviewText, codes, statusKeys } from './codegen.js';
import { rgbI2S, rgbS2I, rgbaI2S } from '../utils'; import { makeUpdateConfig, 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() { const updateConfig = makeUpdateConfig(lskey, defaultConfig, getPreviewText);
const { x, y, fontSize, width, fgRGB, fgA, fgRGB2, fgA2, bgRGB, bgA, freeze, freezeDuration } = this;
const config = {
x,
y,
fontSize,
width,
fgRGB,
fgA,
fgRGB2,
fgA2,
bgRGB,
bgA,
freeze,
freezeDuration,
};
localStorage.setItem(lskey, JSON.stringify(config));
this.$emit('config', config);
}
export default { export default {
components: { components: {
TextConfig, TextConfig,
@ -87,21 +58,9 @@ export default {
rgbaI2S, rgbaI2S,
}, },
data() { data() {
const { x, y, fontSize, width, fgRGB, fgA, fgRGB2, fgA2, bgRGB, bgA, freeze, freezeDuration } = const config = getConfig();
getConfig();
return { return {
x, ...config,
y,
fontSize,
fgRGB,
fgA,
fgRGB2,
fgA2,
width,
bgRGB,
bgA,
freeze,
freezeDuration,
// const // const
freezeKeys: [ freezeKeys: [
...Object.keys(codes[this.version]?.freezeCodeHooks ?? {}), ...Object.keys(codes[this.version]?.freezeCodeHooks ?? {}),
@ -115,8 +74,7 @@ export default {
}, },
textConfig: { textConfig: {
get() { get() {
const { x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2 } = this; return this;
return { x, y, fontSize, fgRGB, fgA, fgRGB2, fgA2 };
}, },
set(value) { set(value) {
Object.assign(this, value); Object.assign(this, value);
@ -126,8 +84,6 @@ export default {
}, },
watch: { watch: {
width: updateConfig, width: updateConfig,
bgRGB: updateConfig,
bgA: updateConfig,
freezeDuration: updateConfig, freezeDuration: updateConfig,
}, },
}; };

View file

@ -1,14 +1,6 @@
{ {
"ja-JP": { "ja-JP": {
"h3": "見た目", "h3": "見た目",
"location": "位置:",
"fontSize": "文字サイズ:",
"fgColor": "文字色:",
"fgColorGrad": "グラデーション",
"fgColor1": "文字色(上)",
"fgColor2": "文字色(下)",
"bgColor": "背景色:",
"alpha": "不透明度=",
"preview": "プレビュー", "preview": "プレビュー",
"previewNote": "※ x座標の範囲は0~600、y座標の範囲は16~464です", "previewNote": "※ x座標の範囲は0~600、y座標の範囲は16~464です",
"freeze": { "freeze": {
@ -40,14 +32,6 @@
}, },
"en-US": { "en-US": {
"h3": "Appearance", "h3": "Appearance",
"location": "Location: ",
"fontSize": "Font size: ",
"fgColor": "Font color: ",
"fgColorGrad": "Gradient",
"fgColor1": "Font color (top): ",
"fgColor2": "Font color (bottom): ",
"bgColor": "Background color: ",
"alpha": "Alpha=",
"preview": "Preview", "preview": "Preview",
"previewNote": "※ x ranges from 0 to 600, and y ranges from 16 to 464.", "previewNote": "※ x ranges from 0 to 600, and y ranges from 16 to 464.",
"freeze": { "freeze": {
@ -79,14 +63,6 @@
}, },
"fr-FR": { "fr-FR": {
"h3": "Apparence", "h3": "Apparence",
"location": "Position : ",
"fontSize": "Taille de police : ",
"fgColor": "Couleur du texte : ",
"fgColorGrad": "Dégradé",
"fgColor1": "Couleur du texte (haut) : ",
"fgColor2": "Couleur du texte (bas) : ",
"bgColor": "Couleur de fond : ",
"alpha": "Alpha = ",
"preview": "Aperçu", "preview": "Aperçu",
"previewNote": "※ x doit être entre 0 et 600, et y entre 16 et 464.", "previewNote": "※ x doit être entre 0 et 600, et y entre 16 et 464.",
"freeze": { "freeze": {

View file

@ -0,0 +1,57 @@
/** @typedef {{index: number, kerning: number, width: number, code: number}} CharInfo */
import charInfoJP from '../../data/charInfo-JP.json';
import charInfoEU from '../../data/charInfo-EU.json';
/**
* @param {string} version
*/
const getFontInfo = (version) =>
['GMSJ01', 'GMSJ0A'].includes(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,
};
/**
* @param {string} text
* @param {string} version
*/
export function measureText(text, version) {
const { charInfo, rowSize, multibyte } = getFontInfo(version);
/** @type {{x: number, y: number, u: number, v: number}[]} */
const chars = [];
let x = 0;
let y = 0;
let w = 0;
let useKerning = false;
text.split('').forEach((c) => {
const { index, kerning, width } =
charInfo[c] ?? (multibyte && c.charCodeAt(0) >= 0x80 ? charInfo['?'] : charInfo[' ']);
if (c === '\n') {
useKerning = false;
x = 0;
y += 20;
return;
}
if (useKerning) x -= kerning;
useKerning = true;
// uv
const [u, v] = [(index % rowSize) * 20, ((index / rowSize) | 0) * 20];
chars.push({ x, y, u, v });
// next
x += width + kerning;
// update width
if (x > w) w = x;
});
return { chars, width: w, height: y + 20 };
}

View file

@ -1,3 +1,38 @@
/**
* @template T extends {Record<string, any>|Record<string, any>[]}
* @param {string} lskey
* @param {T} defaultConfig
* @param {(config: T)=>string} [makeText]
*/
export function makeUpdateConfig(lskey, defaultConfig, makeText) {
const configKeys = Object.keys(defaultConfig);
/** @type {(o: any)=>T} */
const makeConfig =
defaultConfig instanceof Array
? (o) => o.config
: (o) => Object.fromEntries(configKeys.map((k) => [k, o[k]]));
/** @this {any} */
return function updateConfig() {
// save config to localStorage
const config = makeConfig(this);
localStorage.setItem(lskey, JSON.stringify(config));
// emit `config` event to parent
this.$emit('config', makeText ? { ...config, text: makeText(config) } : config);
};
}
/**
* @param {number} x -- number to convert
* @param {number} size -- byte count
*/
export const int2hex = (x, size) =>
(x >>> 0)
.toString(16)
.toUpperCase()
.padStart(size << 1, '0')
.slice(-(size << 1));
/** @param {number} rgb */ /** @param {number} rgb */
export const rgbI2S = (rgb) => '#' + rgb.toString(16).padStart(6, '0'); export const rgbI2S = (rgb) => '#' + rgb.toString(16).padStart(6, '0');
/** @param {string} s */ /** @param {string} s */
@ -9,7 +44,18 @@ export const rgbS2I = (s) => parseInt(s.slice(1), 16);
export const rgbaI2S = (rgb, a) => export const rgbaI2S = (rgb, a) =>
'#' + rgb.toString(16).padStart(6, '0') + a.toString(16).padStart(2, '0'); '#' + rgb.toString(16).padStart(6, '0') + a.toString(16).padStart(2, '0');
export const fg2Style = (rgb, a, rgb2, a2) => { /** @type {(labels: Record<string, any>, locale: string, fallbackLocale?: string) => (key: string) => string} */
const c = rgbaI2S(rgb, a); export const makeGetLabel =
return rgb2 == null || a2 == null ? c : `linear-gradient(180deg, ${c}, ${rgbaI2S(rgb2, a2)})`; (labels, locale, fallbackLocale = 'en-US') =>
}; (key) => {
const segs = key.split('.');
for (const localeTry of [locale, fallbackLocale]) {
let o = labels[localeTry];
for (const seg of segs) {
if (o == null) break;
o = o[seg];
}
if (o != null) return o;
}
return null;
};

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB