From 9ec7e6801e790d144f513a7dea78c9b6ca02a00e Mon Sep 17 00:00:00 2001 From: Mansive <33560917+Mansive@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:08:19 -0500 Subject: [PATCH 1/7] Add 428: Shibuya Scramble --- PC_Steam_428_Shibuya_Scramble.js | 114 +++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 PC_Steam_428_Shibuya_Scramble.js diff --git a/PC_Steam_428_Shibuya_Scramble.js b/PC_Steam_428_Shibuya_Scramble.js new file mode 100644 index 0000000..15a3eb7 --- /dev/null +++ b/PC_Steam_428_Shibuya_Scramble.js @@ -0,0 +1,114 @@ +// ==UserScript== +// @name 428: Shibuya Scramble (428 〜封鎖された渋谷で〜) +// @version 1.0.0 +// @author Mansive +// @description Steam +// * Spike Chunsoft Co., Ltd. +// * Abstraction Games +// +// https://store.steampowered.com/app/648590/428_Shibuya_Scramble/ +// ==/UserScript== + +const __e = Process.enumerateModules()[0]; + +console.warn("For the Japanese version of the game, not the English one."); + +const handler1 = trans.send((s) => s, "600++"); +const handler2 = trans.send((s) => s, "800+"); // appear after + +const encoder = new TextEncoder("utf-8"); +const decoder = new TextDecoder("utf-8"); + +// attach("Dialogue1", "8A 0F 83 E8 00 74 2B 83 E8 02 BB 02 00 00 00 80", "edi", handler); +attach("Dialogue2", "0F B6 08 8D 50 01 8B C1 89 57 14 83 E8 00 74 ED", "eax", handler1, perChar); +attach("Guide", "8B CA 8D 79 01 8A 01 41 84 C0 75 F9 2B CF 8D 44 24", "edx", handler2, once); + +function getPatternAddress(name, pattern) { + const results = Memory.scanSync(__e.base, __e.size, pattern); + if (results.length === 0) { + throw new Error(`[${name}] Hook not found!`); + } + + let address = results[0].address; + console.log(`\x1b[32m[${name}] Found hook ${address}\x1b[0m`); + if (results.length > 1) { + console.warn(`${name} has ${results.length} results`); + } + + return address; +} + +function once(address, name, register, handler) { + Interceptor.attach(address, { + onEnter() { + const text = this.context[register].readUtf8String(); + handler(text); + }, + }); +} + +function perChar(address, name, register, handler) { + let previousAddress = NULL; + Interceptor.attach(address, function (args) { + // console.log("onEnter:", name); + // 02 1b 00 1E 00 01 | new line? + // 02 1B 00 1E 00 2D | text remaining cursor on new line + // 02 1B 00 1E 00 01 | text remaining cursor on new line + // 02 1b 00 01 | new line? text remaining? + // 02 1f 00 | end of line + // 02 1b 00 | text remaining... + // 02 1e 00 01 | text remaining, inline + // 02 2D 04 | midline pause + + // dots + // E3 80 8C 02 43 02 01 00 43 02 01 00 0F 01 08 01 E7 AC B9 + // E3 81 A8 02 43 02 01 00 43 02 01 00 0F 01 08 2D 04 00 00 00 1E 01 EF BC 91 + // 02 43 02 01 00 43 02 01 00 0F 01 08 1F 00 25 00 + + /** @type {NativePointer} */ + const address = this.context[register]; + if (address.equals(previousAddress)) { + return; + } + previousAddress = address; + + const byte1 = address.readU8(); + const byte2 = address.add(1).readU8(); + const byte3 = address.add(2).readU8(); + + if ((byte1 === 0x02 && byte2 === 0x1b) || (byte1 === 0x02 && byte2 === 0x25)) { + handler("\n"); + return; + } else if (byte1 === 0x02 && byte2 === 0x43 /* byte3 === 0x02 */) { + // add elipses? + handler("……"); + return; + } else if (byte1 === 0x02 && byte2 === 0x2b && byte3 === 0x01) { + handler("——"); + return; + } + + const byte4 = address.add(3).readU8(); + + const char = decoder.decode(Uint8Array.from([byte1, byte2, byte3, byte4]))[0]; + // console.warn(ptr(byte1), ptr(byte2), ptr(byte3), ptr(byte4), char); + + // actually a full-width whitespace + // E4 BB 9D + if (char === "仝") { + handler(" "); + return; + } + + handler(char); + }); +} + +function attach(name, pattern, register, handler, strategy) { + const address = getPatternAddress(name, pattern); + strategy(address, name, register, handler); +} + +trans.replace((s) => { + return s.trim(); +}); From 44d4ac5df725e2435b6579a21f98ceb160561206 Mon Sep 17 00:00:00 2001 From: Mansive <33560917+Mansive@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:14:55 -0500 Subject: [PATCH 2/7] Add known issue message --- PC_Steam_428_Shibuya_Scramble.js | 1 + 1 file changed, 1 insertion(+) diff --git a/PC_Steam_428_Shibuya_Scramble.js b/PC_Steam_428_Shibuya_Scramble.js index 15a3eb7..dc41f64 100644 --- a/PC_Steam_428_Shibuya_Scramble.js +++ b/PC_Steam_428_Shibuya_Scramble.js @@ -12,6 +12,7 @@ const __e = Process.enumerateModules()[0]; console.warn("For the Japanese version of the game, not the English one."); +console.warn("Known issue: Guide hook prints all the text at once"); const handler1 = trans.send((s) => s, "600++"); const handler2 = trans.send((s) => s, "800+"); // appear after From a3b6f987a5c5e97008248ad55b106b6fba4014b9 Mon Sep 17 00:00:00 2001 From: Mansive <33560917+Mansive@users.noreply.github.com> Date: Wed, 1 Apr 2026 14:17:01 -0500 Subject: [PATCH 3/7] Remove comment --- PC_Steam_428_Shibuya_Scramble.js | 1 - 1 file changed, 1 deletion(-) diff --git a/PC_Steam_428_Shibuya_Scramble.js b/PC_Steam_428_Shibuya_Scramble.js index dc41f64..58e3883 100644 --- a/PC_Steam_428_Shibuya_Scramble.js +++ b/PC_Steam_428_Shibuya_Scramble.js @@ -81,7 +81,6 @@ function perChar(address, name, register, handler) { handler("\n"); return; } else if (byte1 === 0x02 && byte2 === 0x43 /* byte3 === 0x02 */) { - // add elipses? handler("……"); return; } else if (byte1 === 0x02 && byte2 === 0x2b && byte3 === 0x01) { From 7cab9ccbcb3fe8d98361c9af6b84739b0706e35f Mon Sep 17 00:00:00 2001 From: AuroraWright Date: Thu, 2 Apr 2026 16:07:35 +0200 Subject: [PATCH 4/7] Add newline type and choice parsing --- PC_Steam_428_Shibuya_Scramble.js | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/PC_Steam_428_Shibuya_Scramble.js b/PC_Steam_428_Shibuya_Scramble.js index 58e3883..79f92f9 100644 --- a/PC_Steam_428_Shibuya_Scramble.js +++ b/PC_Steam_428_Shibuya_Scramble.js @@ -50,6 +50,7 @@ function once(address, name, register, handler) { function perChar(address, name, register, handler) { let previousAddress = NULL; + let skipNewLine = false; Interceptor.attach(address, function (args) { // console.log("onEnter:", name); // 02 1b 00 1E 00 01 | new line? @@ -60,6 +61,13 @@ function perChar(address, name, register, handler) { // 02 1b 00 | text remaining... // 02 1e 00 01 | text remaining, inline // 02 2D 04 | midline pause + // 02 06 00 10 | new line? + + // choice sequences (the engine seems to add a newline *if not already present in the previous sentence*): + // e3 80 80 02 + // 02 0e 01 00 + // e3 80 80 02 + // 02 2d 04 00 // dots // E3 80 8C 02 43 02 01 00 43 02 01 00 0F 01 08 01 E7 AC B9 @@ -76,22 +84,36 @@ function perChar(address, name, register, handler) { const byte1 = address.readU8(); const byte2 = address.add(1).readU8(); const byte3 = address.add(2).readU8(); + const byte4 = address.add(3).readU8(); - if ((byte1 === 0x02 && byte2 === 0x1b) || (byte1 === 0x02 && byte2 === 0x25)) { + if (byte1 === 0x02 && (byte2 === 0x06 || byte2 === 0x1b || byte2 === 0x25)) { handler("\n"); + skipNewLine = true; return; } else if (byte1 === 0x02 && byte2 === 0x43 /* byte3 === 0x02 */) { handler("……"); + skipNewLine = false; return; } else if (byte1 === 0x02 && byte2 === 0x2b && byte3 === 0x01) { handler("——"); + skipNewLine = false; + return; + } else if (byte1 === 0x02 && byte2 === 0xe && byte3 === 0x01) { + if (skipNewLine) { + handler("(?) "); + } + else { + handler("\n(?) "); + } + skipNewLine = false; + return; + } else if (byte1 === 0xe3 && byte2 === 0x80 && byte3 === 0x80 && byte4 === 0x02) { return; } - const byte4 = address.add(3).readU8(); - const char = decoder.decode(Uint8Array.from([byte1, byte2, byte3, byte4]))[0]; - // console.warn(ptr(byte1), ptr(byte2), ptr(byte3), ptr(byte4), char); + skipNewLine = false; + //console.warn(ptr(byte1), ptr(byte2), ptr(byte3), ptr(byte4), char); // actually a full-width whitespace // E4 BB 9D From da98feb44515ecb6cb6ce2cf933bac7f8b9aed2b Mon Sep 17 00:00:00 2001 From: Mansive <33560917+Mansive@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:21:41 -0500 Subject: [PATCH 5/7] Add AuroraWright as author --- PC_Steam_428_Shibuya_Scramble.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PC_Steam_428_Shibuya_Scramble.js b/PC_Steam_428_Shibuya_Scramble.js index 79f92f9..b21922f 100644 --- a/PC_Steam_428_Shibuya_Scramble.js +++ b/PC_Steam_428_Shibuya_Scramble.js @@ -1,7 +1,7 @@ // ==UserScript== // @name 428: Shibuya Scramble (428 〜封鎖された渋谷で〜) // @version 1.0.0 -// @author Mansive +// @author Mansive, AuroraWright // @description Steam // * Spike Chunsoft Co., Ltd. // * Abstraction Games From 3a73a3903d594d4d7ac4f4ddcad8de849ecb424d Mon Sep 17 00:00:00 2001 From: Mansive <33560917+Mansive@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:23:40 -0500 Subject: [PATCH 6/7] Detect bytes for full-width whitespace instead --- PC_Steam_428_Shibuya_Scramble.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/PC_Steam_428_Shibuya_Scramble.js b/PC_Steam_428_Shibuya_Scramble.js index b21922f..8d8fb5e 100644 --- a/PC_Steam_428_Shibuya_Scramble.js +++ b/PC_Steam_428_Shibuya_Scramble.js @@ -17,7 +17,6 @@ console.warn("Known issue: Guide hook prints all the text at once"); const handler1 = trans.send((s) => s, "600++"); const handler2 = trans.send((s) => s, "800+"); // appear after -const encoder = new TextEncoder("utf-8"); const decoder = new TextDecoder("utf-8"); // attach("Dialogue1", "8A 0F 83 E8 00 74 2B 83 E8 02 BB 02 00 00 00 80", "edi", handler); @@ -101,27 +100,23 @@ function perChar(address, name, register, handler) { } else if (byte1 === 0x02 && byte2 === 0xe && byte3 === 0x01) { if (skipNewLine) { handler("(?) "); - } - else { + } else { handler("\n(?) "); } skipNewLine = false; return; } else if (byte1 === 0xe3 && byte2 === 0x80 && byte3 === 0x80 && byte4 === 0x02) { return; + } else if (byte1 === 0xe4 && byte2 === 0xbb && byte3 === 0x9d) { + // "E4 BB 9D" is "仝" but gets rendered as full-width whitespace ingame + handler(" "); + return; } const char = decoder.decode(Uint8Array.from([byte1, byte2, byte3, byte4]))[0]; skipNewLine = false; //console.warn(ptr(byte1), ptr(byte2), ptr(byte3), ptr(byte4), char); - // actually a full-width whitespace - // E4 BB 9D - if (char === "仝") { - handler(" "); - return; - } - handler(char); }); } From 8f29e67601dec7433381f3c10a0b816e6dc13350 Mon Sep 17 00:00:00 2001 From: Mansive <33560917+Mansive@users.noreply.github.com> Date: Thu, 2 Apr 2026 09:25:44 -0500 Subject: [PATCH 7/7] Change skipNewLine to false in whitespace check --- PC_Steam_428_Shibuya_Scramble.js | 1 + 1 file changed, 1 insertion(+) diff --git a/PC_Steam_428_Shibuya_Scramble.js b/PC_Steam_428_Shibuya_Scramble.js index 8d8fb5e..5769571 100644 --- a/PC_Steam_428_Shibuya_Scramble.js +++ b/PC_Steam_428_Shibuya_Scramble.js @@ -110,6 +110,7 @@ function perChar(address, name, register, handler) { } else if (byte1 === 0xe4 && byte2 === 0xbb && byte3 === 0x9d) { // "E4 BB 9D" is "仝" but gets rendered as full-width whitespace ingame handler(" "); + skipNewLine = false; return; }