From 393a9fcd2fc5af20c24c1e6a2dd83d707bcd18ef Mon Sep 17 00:00:00 2001 From: sup39 Date: Mon, 24 Jul 2023 22:58:57 +0900 Subject: [PATCH] [v0.1.0-beta.2] Added static variables; improved UI - Added api.getVersion() - Added static variables - Improved UI - Added buttons to show/hide UI elements - Added button to reload managers --- CHANGELOG.md | 6 + Cargo.lock | 2 +- Cargo.toml | 2 +- res/ObjectParameters/J3DFrameCtrl.json | 47 +++++ res/ObjectParameters/JDrama TActor.json | 3 +- res/ObjectParameters/MActor.json | 31 +++ res/ObjectParameters/MActorAnmBck.json | 19 ++ res/ObjectParameters/TApplication.json | 59 ++++++ res/ObjectParameters/TBiancoGateKeeper.json | 55 ++++++ res/ObjectParameters/THitActor.json | 7 +- res/ObjectParameters/TLiveActor.json | 53 +++--- .../TMarDirector TDemoInfo.json | 62 ++++++ res/ObjectParameters/TMarDirector.json | 166 ++++++++++++++++ res/ObjectParameters/TMarDirector@QF.json | 19 ++ res/ObjectParameters/TMario.json | 131 +++++++++++++ src/server/api.rs | 5 + src/sms/mod.rs | 7 +- www/api.js | 20 +- www/index.css | 38 +++- www/index.html | 36 ++-- www/index.js | 179 ++++++++++++++---- 21 files changed, 846 insertions(+), 101 deletions(-) create mode 100644 res/ObjectParameters/J3DFrameCtrl.json create mode 100644 res/ObjectParameters/MActor.json create mode 100644 res/ObjectParameters/MActorAnmBck.json create mode 100644 res/ObjectParameters/TApplication.json create mode 100644 res/ObjectParameters/TBiancoGateKeeper.json create mode 100644 res/ObjectParameters/TMarDirector TDemoInfo.json create mode 100644 res/ObjectParameters/TMarDirector.json create mode 100644 res/ObjectParameters/TMarDirector@QF.json create mode 100644 res/ObjectParameters/TMario.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d2d3cb..9efa9fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,10 @@ # Changelog +## v0.1.0-beta.2 (2023/07/24) +- Added api.getVersion() +- Added static variables +- Improved UI + - Added buttons to show/hide UI elements + - Added button to reload managers ## v0.1.0-beta.1 (2023/07/23) - Implemented ObjectViewer - load/reload `ObjectParameters/*.json` diff --git a/Cargo.lock b/Cargo.lock index c3837b6..fa901c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -854,7 +854,7 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "sup-smsac" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" dependencies = [ "clap", "encoding_rs", diff --git a/Cargo.toml b/Cargo.toml index 34bb0d4..41b602b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sup-smsac" -version = "0.1.0-beta.1" +version = "0.1.0-beta.2" edition = "2021" license = "MIT OR Apache-2.0" authors = ["sup39 "] diff --git a/res/ObjectParameters/J3DFrameCtrl.json b/res/ObjectParameters/J3DFrameCtrl.json new file mode 100644 index 0000000..17d0eb9 --- /dev/null +++ b/res/ObjectParameters/J3DFrameCtrl.json @@ -0,0 +1,47 @@ +{ + "J3DFrameCtrl": { + "size": 20, + "offsets": [ + { + "offset": "0", + "type": "void*", + "name": "vtable", + "notes": "", + "hidden": true + }, + { + "offset": "4", + "type": "u8", + "name": "field_0x4", + "notes": "", + "hidden": true + }, + { + "offset": "5", + "type": "u8", + "format": "hex", + "name": "Flag", + "notes": "", + "hidden": false + }, + { + "offset": "8", + "type": "s16", + "name": "Length", + "notes": "" + }, + { + "offset": "c", + "type": "float", + "name": "Step", + "notes": "" + }, + { + "offset": "10", + "type": "float", + "name": "Counter", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/JDrama TActor.json b/res/ObjectParameters/JDrama TActor.json index 222a14b..052ebbd 100644 --- a/res/ObjectParameters/JDrama TActor.json +++ b/res/ObjectParameters/JDrama TActor.json @@ -12,7 +12,8 @@ "offset": "20", "type": "JStage::TActor", "name": "Inherited fields", - "notes": "" + "notes": "", + "hidden": true }, { "offset": "24", diff --git a/res/ObjectParameters/MActor.json b/res/ObjectParameters/MActor.json new file mode 100644 index 0000000..1d0be6e --- /dev/null +++ b/res/ObjectParameters/MActor.json @@ -0,0 +1,31 @@ +{ + "MActor": { + "size": 72, + "offsets": [ + { + "offset": "0", + "type": "MActorAnmData*", + "name": "", + "notes": "" + }, + { + "offset": "4", + "type": "J3DModel*", + "name": "", + "notes": "" + }, + { + "offset": "c", + "type": "MActorAnmBck*", + "name": "Current Animation", + "notes": "" + }, + { + "offset": "28", + "type": "MActorAnmBck**", + "name": "Animations?", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/MActorAnmBck.json b/res/ObjectParameters/MActorAnmBck.json new file mode 100644 index 0000000..2a23922 --- /dev/null +++ b/res/ObjectParameters/MActorAnmBck.json @@ -0,0 +1,19 @@ +{ + "MActorAnmBck": { + "size": 60, + "offsets": [ + { + "offset": "0", + "type": "s32", + "name": "id", + "notes": "" + }, + { + "offset": "4", + "type": "J3DFrameCtrl", + "name": "Frame *", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/TApplication.json b/res/ObjectParameters/TApplication.json new file mode 100644 index 0000000..bfcfa4a --- /dev/null +++ b/res/ObjectParameters/TApplication.json @@ -0,0 +1,59 @@ +{ + "TApplication": { + "size": 80, + "offsets": [ + { + "offset": "0", + "type": "TApplication*", + "name": "Pointer to itself", + "notes": "", + "hidden": true + }, + { + "offset": "4", + "type": "TDirector*", + "name": "Current director", + "notes": "" + }, + { + "offset": "8", + "type": "u8", + "name": "Director type", + "notes": "Director type\n2: [null] setup for 3? (after boot)\n3: [TGCLogoDir] Show Nintendo logo\n4: [TMovieDirector] Title screen FMV\n5: [TMarDirector/TMovieDirector] Regular game play (including most FMV)\n6: [TMovieDirector] other independent FMV (e.g. FLUDD tutorial, BH2 warp zone)\n7: Error? \n8: [TSelectDir] shine select\n9: [TMenuDirector] level select (for debug purpose?)" + }, + { + "offset": "a", + "type": "u16", + "format": "hex", + "name": "Previous area", + "notes": "" + }, + { + "offset": "e", + "type": "u16", + "format": "hex", + "name": "Current area", + "notes": "" + }, + { + "offset": "12", + "type": "u16", + "format": "hex", + "name": "Next area", + "notes": "" + }, + { + "offset": "18", + "type": "u16", + "name": "Current FMV", + "notes": "" + }, + { + "offset": "38", + "type": "s32", + "name": "Current save file", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/TBiancoGateKeeper.json b/res/ObjectParameters/TBiancoGateKeeper.json new file mode 100644 index 0000000..0eb5390 --- /dev/null +++ b/res/ObjectParameters/TBiancoGateKeeper.json @@ -0,0 +1,55 @@ +{ + "TBiancoGateKeeper": { + "size": 672, + "offsets": [ + { + "offset": "0", + "type": "TSpineEnemy", + "name": "Inherited fields", + "notes": "" + }, + { + "offset": "13c", + "type": "u8", + "name": "HP", + "notes": "" + }, + { + "offset": "154", + "type": "u32", + "name": "Received water count", + "notes": "Increased in TGateKeeperBase::receiveMessage()\nReset in TGateKeeperBase::perform() every frame" + }, + { + "offset": "17c", + "type": "s16", + "name": "Total water damage (~255)", + "notes": "" + }, + { + "offset": "17e", + "type": "s16", + "name": "Water damage timer (QF)", + "notes": "" + }, + { + "offset": "292", + "type": "u8", + "name": "GateKeeper type", + "notes": "[0] in AP\n[1] in BH [2] BH unlock\n[3] RH unlock\n[4] GB unlock" + }, + { + "offset": "296", + "type": "s16", + "name": "Death count", + "notes": "" + }, + { + "offset": "298", + "type": "s16", + "name": "Timer to launch Goro (BH only)", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/THitActor.json b/res/ObjectParameters/THitActor.json index bf04512..4256c24 100644 --- a/res/ObjectParameters/THitActor.json +++ b/res/ObjectParameters/THitActor.json @@ -61,14 +61,15 @@ "offset": "60", "type": "float", "name": "Entry radius", - "notes": "" + "notes": "", + "hidden": true }, { "offset": "64", "type": "u32", "format": "hex", - "name": "Collision flag\n (0) active / (1) no collision", - "notes": "" + "name": "Collision flag", + "notes": "(0) active / (1) no collision" } ] } diff --git a/res/ObjectParameters/TLiveActor.json b/res/ObjectParameters/TLiveActor.json index 8b0acd1..3dd4121 100644 --- a/res/ObjectParameters/TLiveActor.json +++ b/res/ObjectParameters/TLiveActor.json @@ -18,32 +18,22 @@ { "offset": "74", "type": "MActor*", - "name": "", - "notes": "" + "name": "MActor", + "notes": "", + "hidden": true }, { "offset": ["74", "c", "0"], - "type": "s32", - "name": "Animation id", + "type": "MActorAnmBck", + "name": "Current Animation *", "notes": "" }, { - "offset": ["74", "28", "0", "14"], - "type": "float", - "name": "Animation frame counter", - "notes": "" - }, - { - "offset": ["74", "28", "0", "c"], - "type": "s16", - "name": "Animation frame length", - "notes": "" - }, - { - "offset": ["74", "28", "0", "10"], - "type": "float", - "name": "Animation frame rate", - "notes": "" + "offset": ["74", "28", "0", "0"], + "type": "MActorAnmBck", + "name": "Animations[0] *", + "notes": "", + "hidden": true }, { "offset": "7c", @@ -70,7 +60,8 @@ "offset": "8c", "type": "TSpineBase*", "name": "AI", - "notes": "" + "notes": "", + "hidden": true }, { "offset": ["8c", "1c", "0"], @@ -94,37 +85,43 @@ "offset": "94", "type": "JGeometry::TVec3", "name": "* Movement (unit/step)", - "notes": "" + "notes": "", + "hidden": true }, { "offset": "ac", "type": "JGeometry::TVec3", "name": "* Speed (unit/step)", - "notes": "" + "notes": "", + "hidden": true }, { "offset": "bc", "type": "float", "name": "Wall hitbox width", - "notes": "" + "notes": "", + "hidden": true }, { "offset": "c4", "type": "TBGCheckData*", - "name": "", - "notes": "" + "name": "Ground", + "notes": "", + "hidden": true }, { "offset": "c8", "type": "float", "name": "Ground Height", - "notes": "" + "notes": "", + "hidden": true }, { "offset": "cc", "type": "float", "name": "Gravity (unit/step²)", - "notes": "" + "notes": "", + "hidden": true }, { "offset": "f0", diff --git a/res/ObjectParameters/TMarDirector TDemoInfo.json b/res/ObjectParameters/TMarDirector TDemoInfo.json new file mode 100644 index 0000000..48b6890 --- /dev/null +++ b/res/ObjectParameters/TMarDirector TDemoInfo.json @@ -0,0 +1,62 @@ +{ + "TMarDirector::TDemoInfo": { + "size": 34, + "offsets": [ + { + "offset": "0", + "type": "string", + "name": "BCK name", + "notes": "" + }, + { + "offset": ["4", "0"], + "type": "JGeometry::TVec3", + "name": "location.*", + "notes": "" + }, + { + "offset": "8", + "type": "s32", + "name": "", + "notes": "" + }, + { + "offset": "c", + "type": "float", + "name": "", + "notes": "" + }, + { + "offset": "10", + "type": "u8", + "name": "", + "notes": "bool" + }, + { + "offset": "14", + "type": "void*", + "name": "Callback", + "notes": "" + }, + { + "offset": "18", + "type": "u32", + "format": "hex", + "name": "Callback parameter", + "notes": "" + }, + { + "offset": "1c", + "type": "TActor*", + "name": "TActor*", + "notes": "" + }, + { + "offset": "20", + "type": "s16", + "name": "flag", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/TMarDirector.json b/res/ObjectParameters/TMarDirector.json new file mode 100644 index 0000000..2319528 --- /dev/null +++ b/res/ObjectParameters/TMarDirector.json @@ -0,0 +1,166 @@ +{ + "TMarDirector": { + "size": 616, + "offsets": [ + { + "offset": "4c", + "type": "u16", + "format": "hex", + "name": "Game mode flags", + "notes": "" + }, + { + "offset": "4e", + "type": "u16", + "format": "hex", + "name": "", + "notes": "" + }, + { + "offset": "50", + "type": "u16", + "format": "hex", + "name": "", + "notes": "" + }, + { + "offset": "54", + "type": "u32", + "name": "QF synchronizer", + "notes": "" + }, + { + "offset": "58", + "type": "u32", + "name": "Game QF", + "notes": "" + }, + { + "offset": "5c", + "type": "u32", + "name": "Global QF", + "notes": "" + }, + { + "offset": "60", + "type": "u32", + "name": "Started QF of cutscene", + "notes": "" + }, + { + "offset": "64", + "type": "u8", + "name": "Game state", + "notes": "" + }, + { + "offset": "7c", + "type": "s8", + "name": "Current stage", + "notes": "" + }, + { + "offset": "7d", + "type": "s8", + "name": "Current episode", + "notes": "" + }, + { + "offset": "b4", + "type": "u8", + "name": "Next director type", + "notes": "" + }, + { + "offset": "124", + "type": "u8", + "name": "Current game mode state", + "notes": "" + }, + { + "offset": "125", + "type": "u8", + "name": "Previous game mode state", + "notes": "" + }, + { + "offset": "126", + "type": "u8", + "name": "Next game mode state", + "notes": "" + }, + { + "offset": "12c", + "type": "TMarDirector::TDemoInfo", + "name": "slot0 *", + "notes": "" + }, + { + "offset": "150", + "type": "TMarDirector::TDemoInfo", + "name": "slot1 *", + "notes": "" + }, + { + "offset": "174", + "type": "TMarDirector::TDemoInfo", + "name": "slot2 *", + "notes": "" + }, + { + "offset": "198", + "type": "TMarDirector::TDemoInfo", + "name": "slot3 *", + "notes": "" + }, + { + "offset": "1bc", + "type": "TMarDirector::TDemoInfo", + "name": "slot4 *", + "notes": "" + }, + { + "offset": "1e0", + "type": "TMarDirector::TDemoInfo", + "name": "slot5 *", + "notes": "" + }, + { + "offset": "204", + "type": "TMarDirector::TDemoInfo", + "name": "slot6 *", + "notes": "" + }, + { + "offset": "228", + "type": "TMarDirector::TDemoInfo", + "name": "slot7 *", + "notes": "" + }, + { + "offset": "24c", + "type": "u8", + "name": "Next cutscene index to assign", + "notes": "" + }, + { + "offset": "24d", + "type": "u8", + "name": "Next cutscene index to play", + "notes": "" + }, + { + "offset": "260", + "type": "u8", + "name": "Stage load done", + "notes": "" + }, + { + "offset": "261", + "type": "s8", + "name": "Prompt id", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/TMarDirector@QF.json b/res/ObjectParameters/TMarDirector@QF.json new file mode 100644 index 0000000..50f4246 --- /dev/null +++ b/res/ObjectParameters/TMarDirector@QF.json @@ -0,0 +1,19 @@ +{ + "TMarDirector@QF": { + "size": 616, + "offsets": [ + { + "offset": "58", + "type": "u32", + "name": "Game QF", + "notes": "" + }, + { + "offset": "5c", + "type": "u32", + "name": "Global QF", + "notes": "" + } + ] + } +} diff --git a/res/ObjectParameters/TMario.json b/res/ObjectParameters/TMario.json new file mode 100644 index 0000000..0c56dd5 --- /dev/null +++ b/res/ObjectParameters/TMario.json @@ -0,0 +1,131 @@ +{ + "TMario": { + "size": 17040, + "offsets": [ + { + "offset": "0", + "type": "TTakeActor", + "name": "Inherited fields", + "notes": "" + }, + { + "offset": "7c", + "type": "u32", + "format": "hex", + "name": "Current state", + "notes": "" + }, + { + "offset": "80", + "type": "u32", + "format": "hex", + "name": "Previous state", + "notes": "" + }, + { + "offset": "84", + "type": "u16", + "name": "Substate", + "notes": "" + }, + { + "offset": "86", + "type": "u16", + "name": "Substate timer", + "notes": "" + }, + { + "offset": "8c", + "type": "float", + "name": "Base acceleration (0-32)", + "notes": "" + }, + { + "offset": "90", + "type": "s16", + "name": "Acceleration direction", + "notes": "" + }, + { + "offset": "94", + "type": "s16", + "name": "X angle", + "notes": "" + }, + { + "offset": "96", + "type": "s16", + "name": "Y angle (yaw)", + "notes": "" + }, + { + "offset": "98", + "type": "s16", + "name": "Z angle", + "notes": "" + }, + { + "offset": "a4", + "type": "JGeometry::TVec3", + "name": "* Speed", + "notes": "" + }, + { + "offset": "b0", + "type": "float", + "name": "Forward speed", + "notes": "" + }, + { + "offset": "e0", + "type": "TBGCheckData*", + "name": "Floor triangle under Mario", + "notes": "" + }, + { + "offset": "e8", + "type": "float", + "name": "Height of the ceiling above Mario", + "notes": "9,999,999 if none" + }, + { + "offset": "ec", + "type": "float", + "name": "Height of the floor below Mario", + "notes": "-32,767 if none" + }, + { + "offset": "f0", + "type": "float", + "name": "Height of the water surface at Mario’s position", + "notes": "Y position if none" + }, + { + "offset": "118", + "type": "u32", + "name": "Mario flags", + "format": "hex", + "notes": "& 0x00000001: \n& 0x00000002: Above a sewer floor?\n& 0x00000004: Is visible?\n& 0x00000008: Talking to NPC?\n& 0x00000010: Left water recently?\n& 0x00000020: Is in shadow\n& 0x00000040: Is in goop\n& 0x00000080: Filling FLUDD\n& 0x00000100: Is NOT on wire (starts off?)\n& 0x00000200: \n& 0x00000400: Is \"TOO BAD!\"?\n& 0x00000800: Is Ground Pound sit up?\n& 0x00001000: Has helmet?\n& 0x00002000: \n& 0x00004000: Is turbo boosting\n& 0x00008000: Has FLUDD?\n& 0x00010000: Is standing in water?\n& 0x00020000: Is in water?\n& 0x00040000: Is above sand?\n& 0x00080000: \n& 0x00100000: \n& 0x00200000: \n& 0x00400000: \n& 0x00800000: \n& 0x01000000: \n& 0x02000000: \n& 0x04000000: \n& 0x08000000: \n& 0x10000000: \n& 0x20000000: \n& 0x40000000: \n& 0x80000000: \n" + }, + { + "offset": "11C", + "type": "u32", + "format": "hex", + "name": "Previous Mario flags", + "notes": "" + }, + { + "offset": "120", + "type": "s16", + "name": "HP", + "notes": "" + }, + { + "offset": "12c", + "type": "float", + "name": "Underwater HP", + "notes": "" + } + ] + } +} diff --git a/src/server/api.rs b/src/server/api.rs index 88f52ca..ac8b775 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -263,6 +263,11 @@ pub async fn handle_command( }) }, + "getVersion" => { + let_dolphin!(d); + Ok(json!(d.ver().to_string())) + }, + "reload" => { let mut lock_obj_params = env.obj_params_result.lock().await; load_obj_params(&env.obj_params_dir) diff --git a/src/sms/mod.rs b/src/sms/mod.rs index 692879e..abcb842 100644 --- a/src/sms/mod.rs +++ b/src/sms/mod.rs @@ -5,7 +5,11 @@ pub enum SMSVersion { GMSJ01, GMSE01, GMSP01, GMSJ0A, } -pub mod vt; +impl std::fmt::Display for SMSVersion { + fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + std::fmt::Debug::fmt(self, fmt) + } +} use crate::addr::Addr; use crate::dolphin::{DolphinMemory, Dolphin}; @@ -20,6 +24,7 @@ impl Dolphin for SMSDolphin { } } +pub mod vt; impl SMSDolphin { #[inline] pub fn pid(&self) -> usize { diff --git a/www/api.js b/www/api.js index 7aab75e..6c27c20 100644 --- a/www/api.js +++ b/www/api.js @@ -4,6 +4,7 @@ // @ts-check /** * @typedef {number|number[]} ReqAddr + * @typedef {'GMSJ01'|'GMSE01'|'GMSP01'|'GMSJ0A'} SMSVersion */ /** @param {string} s */ @@ -27,10 +28,10 @@ function Client({onClose = null}={}) { /** * @template T * @param {string} action - * @param {any} payload + * @param {any} [payload] * @returns {Promise} */ - const request = (action, payload) => new Promise((rsv, rjt) => { + const request = (action, payload=null) => new Promise((rsv, rjt) => { if (ws == null) throw Error('Client is not connected to server. Use `client.connect()` first.'); const id = nextId++; reqs.set(id, {rsv, rjt}); @@ -62,7 +63,7 @@ function Client({onClose = null}={}) { /** * @returns {Promise} */ - init: () => request('init', null), + init: () => request('init'), /** * @param {ReqAddr} addr @@ -123,16 +124,19 @@ function Client({onClose = null}={}) { */ getFields: type => request('getFields', type), - getManagers: () => request('getManagers', 0) - .then((/**@type{[addr: number, cls: string, name: string, count: number][]}*/rows) => - rows.map(row => ({addr: row[0], cls: row[1], name: row[2], count: row[3]}))), + getManagers: () => request('getManagers') + .then((/**@type{[addr: number, type: string, name: string, count: number][]|null}*/rows) => + rows?.map(row => ({addr: row[0], type: row[1], name: row[2], count: row[3]})) ?? []), /** * @param {ReqAddr} addr */ getManagees: addr => request('getManagees', addr) - .then((/**@type{[addr: number, cls: string, name: string][]}*/rows) => - rows.map(row => ({addr: row[0], cls: row[1], name: row[2]}))), + .then((/**@type{[addr: number, type: string, name: string][]|null}*/rows) => + rows?.map(row => ({addr: row[0], type: row[1], name: row[2]})) ?? []), + + /** @returns {Promise} */ + getVersion: () => request('getVersion'), reload: () => request('reload', null), }, diff --git a/www/index.css b/www/index.css index 2e1741d..6938537 100644 --- a/www/index.css +++ b/www/index.css @@ -36,18 +36,28 @@ table.list td { padding: 1px 0.3em; white-space: pre-line; } -#managers td:nth-child(1), -#managers td:nth-child(5) { +#managerList td:nth-child(1), +#managerList td:nth-child(5) { padding: 0 2px } -#managers tr.managee > td:nth-child(2) { +#managerList tr.managee > td:nth-child(2) { padding-left: 1em } -#fields-viewer td:nth-child(1), -#fields-viewer td:nth-child(3) { +#fieldsViewer h3 { + margin-block-start: 0; + margin-block-end: 0.5em; +} +#fieldsViewer td:nth-child(1), +#fieldsViewer td:nth-child(3) { text-align: right; } +#fieldsViewer td:nth-child(4) { + display: none; +} +#fieldsViewer.showNotes td:nth-child(4) { + display: unset; +} .flex { display: flex; @@ -55,11 +65,23 @@ table.list td { } .flex > * { margin-right: 4px; + margin-block-end: 1em; } +body.wrap .flex { + flex-wrap: wrap; +} + .hidden { display: none; } +main { + visibility: hidden; +} +body.ready main { + visibility: visible; +} + body { background: var(--bg); color: var(--fg); @@ -68,7 +90,7 @@ body { font-size: 14px; padding: 4px; } -body.disconnected #msg { +body.error #msg { background: var(--bg-red); } h1 { @@ -83,6 +105,10 @@ header { margin-block-end: 0.75em; padding-left: 4px; } +section { + margin-block-end: 0.5em; +} + details { border: solid 1px var(--fg); padding: 0.5em 1em; diff --git a/www/index.html b/www/index.html index 2167b3e..fe4f8fd 100644 --- a/www/index.html +++ b/www/index.html @@ -1,14 +1,14 @@ - SMS Web Object Viewer (v0.1.0-beta.1) + SMS Web Object Viewer (v0.1.0-beta.2) -

SMS Web Object Viewer

+

SMS Web Object Viewer (v0.1.0-beta.2)

@@ -20,13 +20,27 @@
-
- -
-
-
-
-
-
-
+
+
+ + + + + + + + +
+
+
+
+
+
+ +
+
+
diff --git a/www/index.js b/www/index.js index 2b7840d..2579c1e 100644 --- a/www/index.js +++ b/www/index.js @@ -3,8 +3,8 @@ // @ts-check /** - * @typedef {{addr: number, cls: string, name: string, count: number}} Manager - * @typedef {{addr: number, cls: string, name: string}} Managee + * @typedef {{addr: number, type: string, name: string, count: number}} Manager + * @typedef {{addr: number, type: string, name: string}} Managee * @typedef {(td: HTMLTableCellElement) => void} CellFactory * @typedef {CellFactory[]} RowFactory * @typedef {{name: string, notes: string, offset: string|string[], type: string}} Field @@ -16,12 +16,6 @@ const fmt = { /** @param {number} x */ hex: x => x.toString(16).toUpperCase(), - /** @type {(x: number) => string} */ - float: (LOG_10_2 => x => { - const u = Math.floor(LOG_10_2*(Math.log2(Math.abs(x))-23)); - return x === 0 ? '0.0' : u > 0 || u < -8 ? x.toExponential(7) : - x.toFixed(-u); - })(Math.log10(2)), }; /** @@ -29,38 +23,113 @@ const fmt = { * @param {((td: HTMLTableCellElement)=>void)[][]} gTable */ function initTable(table, gTable) { - const nRow = gTable.length; - for (let r=table.rows.length; rnRow; r--) table.deleteRow(-1); - gTable.forEach((gRow, r) => { - const row = table.rows[r]; - const nCol = gRow.length; - for (let c=row.cells.length; cnCol; c--) row.deleteCell(-1); - gRow.forEach((g, c) => g(row.cells[c])); + const nRow0 = table.rows.length; + for (let r=0; r { + const row = table.insertRow(); + gRow.forEach(g => g(row.insertCell())); }); } document.addEventListener('DOMContentLoaded', async () => { const elmMsg = /**@type {HTMLDivElement}*/(document.getElementById('msg')); - const btnReload = /**@type {HTMLButtonElement}*/(document.getElementById('btn-reload')); - const elmManagers = /**@type {HTMLTableElement}*/(document.getElementById('managers')); - const elmFieldsViewer = /**@type {HTMLTableElement}*/(document.getElementById('fields-viewer')); + const btnReloadObjParams = /**@type {HTMLButtonElement}*/(document.getElementById('btnReloadObjParams')); + const btnReloadManagers = /**@type {HTMLButtonElement}*/(document.getElementById('btnReloadManagers')); + const cbShowManagers = /**@type {HTMLInputElement}*/(document.getElementById('cbShowManagers')); + const cbShowObjParamsNotes = /**@type {HTMLInputElement}*/(document.getElementById('cbShowObjParamsNotes')); + const cbWrapFlex = /**@type {HTMLInputElement}*/(document.getElementById('cbWrapFlex')); + /** @param {string} msg */ + function showError(msg) { + elmMsg.textContent = msg; + document.body.classList.add('error'); + } const client = Client({ - onClose() { - elmMsg.textContent = `Disconnected from server. Please reload the page.`; - document.body.classList.add('disconnected'); - }, + onClose: () => showError(`Disconnected from server. Please reload the page.`), }); const {api} = client; Object.assign(window, {client, api}); // TODO /**************** UI definition ****************/ /** - * @param {HTMLTableElement} elm + * @param {HTMLElement|null} elm + */ + function ManagerList(elm) { + if (elm == null) throw new Error('ManagerList not found'); + const elmTable = (() => { + const e = elm.querySelector('table'); + if (e == null) throw new Error('table should present in ManagerList'); + return e; + })(); + // TODO put in a json file + const staticVariables = [ + { + name: 'gpApplication', + addrs: [0x803E6000, 0x803E9700, 0x803E10C0, 0x803DA8E0], + type: 'TApplication', + }, + { + name: 'gpMarDirector', + addrs: [0x8040A2A8, 0x8040E178, 0x80405840, 0x803FF018], + type: 'TMarDirector*', + }, + { + name: 'QF', + addrs: [0x8040A2A8, 0x8040E178, 0x80405840, 0x803FF018], + type: 'TMarDirector@QF*', + }, + { + name: 'マリオ', + addrs: [0x8040A378, 0x8040E0E8, 0x804057B0, 0x803FEF88], + type: 'TMario*', + }, + ]; + return { + get classList() { + return elm.classList; + }, + async reload() { + const [vars, managers] = await Promise.all([ + api.getVersion().then(async ver => { + const iver = ['GMSJ01', 'GMSE01', 'GMSP01', 'GMSJ0A'].indexOf(ver); + return await Promise.all(staticVariables.map(async o => { + const ptrlv = o.type.match(/\*+$/)?.[0].length ?? 0; + const type = o.type.substring(0, o.type.length-ptrlv); + const addr0 = o.addrs[iver]; + const addr = ptrlv === 0 ? addr0 : await api.readBytes( + [addr0].concat(...Array(ptrlv-1).fill(0)), 4, + ).then(dv => dv?.getUint32(0) ?? 0); + return {name: o.name, addr, type}; + })); + }), + api.getManagers(), + ]); + fieldsViewer.reset(); + initTable(elmTable, [ + ...vars.map(o => makeManageesRowFactory(o)), + ...managers.map(makeManagersRowFactory), + ]); + }, + }; + } + const managerList = ManagerList(document.getElementById('managerList')); + + /** + * @param {HTMLElement|null} elm */ function FieldsViewer(elm) { + if (elm == null) throw new Error('FieldsViewer not found'); + const elmTitle = (() => { + const e = elm.querySelector('h3'); + if (e == null) throw new Error('h3 should present in FieldsViewer'); + return e; + })(); + const elmTable = (() => { + const e = elm.querySelector('table'); + if (e == null) throw new Error('table should present in FieldsViewer'); + return e; + })(); + // states const tdidxVal = 2; let hAnm = NaN; let t0 = 0; @@ -68,19 +137,22 @@ document.addEventListener('DOMContentLoaded', async () => { let target = null; async function readValues() { if (target == null) return []; - const values = await api.read([target.addr], target.cls); + const values = await api.read([target.addr], target.type); return values instanceof Array ? values : [values]; } /** @param {DOMHighResTimeStamp} t */ async function render(t) { if (t-t0 >= 33) { // TODO configurable fps (await readValues()) - .forEach((s, i) => elm.rows[i].cells[tdidxVal].textContent = s); + .forEach((s, i) => elmTable.rows[i].cells[tdidxVal].textContent = s); t0 = t; } hAnm = requestAnimationFrame(render); } const methods = { + get classList() { + return elm.classList; + }, reload() { api.reload().then(() => { target != null && methods.view(target); @@ -88,34 +160,55 @@ document.addEventListener('DOMContentLoaded', async () => { elmMsg.textContent = err; }); }, + reset() { + elm.classList.add('hidden'); + }, /** @param {Manager|Managee} o */ async view(o) { - btnReload.classList.remove('hidden'); cancelAnimationFrame(hAnm); target = o; - const fields = await api.getFields(o.cls); + elmTitle.textContent = `${o.name} (${o.type}) [${fmt.hex(o.addr)}]`; + const fields = await api.getFields(o.type); const values = readValues(); - initTable(elm, fields.map((r, i) => [ + initTable(elmTable, fields.map((r, i) => [ td => td.textContent = r[0], td => td.textContent = r[1], td => td.textContent = values[i], - // td => td.textContent = r[2], // TODO + td => td.textContent = r[2], td => td.textContent = r[3], td => td.textContent = r[4], ])); + elm.classList.remove('hidden'); if (fields.length) hAnm = requestAnimationFrame(render); }, } return methods; } - const fieldsViewer = FieldsViewer(elmFieldsViewer); - btnReload.addEventListener('click', () => { + const fieldsViewer = FieldsViewer(document.getElementById('fieldsViewer')); + btnReloadObjParams.addEventListener('click', () => { fieldsViewer.reload(); }); + btnReloadManagers.addEventListener('click', async () => { + managerList.reload(); + }); + cbShowManagers.addEventListener('change', function () { + managerList.classList[this.checked ? 'remove' : 'add']('hidden'); + }); + cbShowObjParamsNotes.addEventListener('change', function () { + fieldsViewer.classList[this.checked ? 'add' : 'remove']('showNotes'); + }); + cbWrapFlex.addEventListener('change', function () { + // TODO + document.body.classList[this.checked ? 'add' : 'remove']('wrap'); + }); /** @type {(o: Manager) => RowFactory} */ const makeManagersRowFactory = o => [ td => { + // remove old button + const btn0 = td.querySelector('button'); + if (btn0) td.removeChild(btn0); + // create new button const btn = document.createElement('button'); let open = false; /** @type {HTMLTableRowElement[] | null} */ @@ -146,22 +239,26 @@ document.addEventListener('DOMContentLoaded', async () => { td.appendChild(btn); }, td => td.textContent = `${o.name} (${o.count})`, - td => td.textContent = `${o.cls}`, + td => td.textContent = `${o.type}`, td => td.textContent = `${fmt.hex(o.addr)}`, makeViewerNavigatorFactory(o), ]; - /** @type {(o: Managee, i: number) => ((td: HTMLTableCellElement) => void)[]} */ + /** @type {(o: Managee, i?: number) => ((td: HTMLTableCellElement) => void)[]} */ const makeManageesRowFactory = (o, i) => [ _ => {}, - td => td.textContent = `${i}: (${o.name})`, - td => td.textContent = `${o.cls}`, + td => td.textContent = i == null ? o.name : `${i}: ${o.name}`, + td => td.textContent = `${o.type}`, td => td.textContent = `${fmt.hex(o.addr)}`, makeViewerNavigatorFactory(o), ]; /** @type {(o: Manager|Managee) => CellFactory} */ const makeViewerNavigatorFactory = o => td => { + // remove old button + const btn0 = td.querySelector('button'); + if (btn0) td.removeChild(btn0); + // create new button const btn = document.createElement('button'); btn.textContent = '>'; btn.addEventListener('click', () => { @@ -175,11 +272,11 @@ document.addEventListener('DOMContentLoaded', async () => { await client.connect(); // TODO url const pid = await api.init(); console.log('pid:', pid); + document.body.classList.add('ready'); - const managers = await api.getManagers(); - initTable(elmManagers, managers.map(makeManagersRowFactory)); + await managerList.reload(); } catch(e) { - elmMsg.textContent = e; - return; // TODO + console.log(e); + showError(e); } });