diff --git a/client_pack/index.toml b/client_pack/index.toml index e7caf03..4371f96 100644 --- a/client_pack/index.toml +++ b/client_pack/index.toml @@ -862,7 +862,7 @@ metafile = true [[files]] file = "mods/the-doctor-who-client-mod.pw.toml" -hash = "133badb6d33d880f47ad59a60dd3916231168ba07701c957aee1f781d5d54a5a" +hash = "dd77144a6fe93e69509999bc5676017580649157602c8a61653344a3b6853879" metafile = true [[files]] diff --git a/client_pack/mods/the-doctor-who-client-mod.pw.toml b/client_pack/mods/the-doctor-who-client-mod.pw.toml index e6b6c13..a40148e 100644 --- a/client_pack/mods/the-doctor-who-client-mod.pw.toml +++ b/client_pack/mods/the-doctor-who-client-mod.pw.toml @@ -1,11 +1,11 @@ -name = "THE Doctor Who Client Mod" -filename = "tdwcm-0.2.1.jar" -side = "client" +name = "THE Doctor Who Client Mod (patched - optional network channels)" +filename = "tdwcm-0.2.1-patched.jar" +side = "both" [download] -url = "https://cdn.modrinth.com/data/96oAiXlp/versions/xpTvgljr/tdwcm-0.2.1.jar" +url = "https://git.mithraic.cloud/ad3laid3/SciCraft/raw/branch/main/tools/patches/tdwcm-0.2.1-patched.jar" hash-format = "sha512" -hash = "68b3de21b7d3f6a5bc8d8e386a8a80e56828eb8c7335be9994875533120052c0c689d524ec140fec850cf2c13c40c6daac3f6f3cd3bff0df7c0ad2ab1888a292" +hash = "4169de97214ab71178791299d5164fd04b99ed2429cb2799b8e177849979406d98158954564b33149a0d505cfe23d56005b8462dc22390c0b77ee7edaa8f43a4" [update] [update.modrinth] diff --git a/client_pack/pack.toml b/client_pack/pack.toml index 513bfff..74fed20 100644 --- a/client_pack/pack.toml +++ b/client_pack/pack.toml @@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0" [index] file = "index.toml" hash-format = "sha256" -hash = "e00761ce338bdd5fe09338b04cb68660f8ccbe6d3c646dda8ea96e751f4d5b5a" +hash = "c655e3a03467912fbbc79a344d303e811df05ddea4889c414155e209a1e95937" [versions] minecraft = "1.21.1" diff --git a/docs/mods.md b/docs/mods.md index 37607a0..29e9858 100644 --- a/docs/mods.md +++ b/docs/mods.md @@ -21,9 +21,14 @@ `dynamic-fps`, `3dskinlayers`, `visuality-forge`, `midnightlib`, `entityculling`, `immediatelyfast`, `badoptimizations`, `athena-ctm`, `journeymap-integration`, `stellarview`, `stellar-view-mixin-renderer`, -`stellaris-stellarview-compatibility`, `the-doctor-who-client-mod`, +`stellaris-stellarview-compatibility`, `simply-tooltips`, `inventory-profiles-next` (with `libipn`), `just-enough-mekanism-multiblocks`. +> **Note:** `the-doctor-who-client-mod` (tdwcm) used to be listed here but +> CANNOT run one-sided: it registers blocks/items/entities (registry sync) +> plus ~31 required network channels. It ships patched on BOTH sides since +> v1.0.1 — see compatibility notes below. + ## Dimensional physics stack (both packs) Physics / portals run on **Sable** + **Create Aeronautics** + **IPSable** @@ -97,6 +102,13 @@ config needed there. set in the @Mod constructor while KubeJS runs plugin callbacks during its own construction → nondeterministic boot crash). The patch removes COE's guarded registration; kubejs-create remains the wrapper provider. +- **THE Doctor Who Client Mod runs on BOTH sides (patched)**: it registers + real blocks/items/entities via DeferredRegister and ~31 network payloads, so + a one-sided install fails login twice over (required-channel mismatch, then + registry sync). The patch marks all channels `.optional()`; with the mod on + both packs the negotiation passes regardless. Verified: dedicated server + boots clean with it (`Done` in ~10s). Audit 2026-08: no other client-only + mod in this pack uses NeoForge's payload API. - **Create addon libs** (all present): RPL (Big Cannons), Kotlin for Forge (Slice & Dice, Jetpack), Mechanicals Lib (Mechanical Extruder), Architectury (Goggles). Flywheel is bundled inside Create — do **not** add a standalone diff --git a/docs/server.md b/docs/server.md index 69f0697..7c6c284 100644 --- a/docs/server.md +++ b/docs/server.md @@ -79,6 +79,7 @@ for any fresh install to work**: |-----|-------|----------------| | `worldportal-neoforge-0.0.10+1.21-patched.jar` | Removes `EntityLMixin` (conflicts with Sable mixins) | hand-patched; see commit a88a0c2 | | `createoreexcavation-1.21-1.6.8-patched.jar` | Strips COE's guarded `ProcessingOutput` wrapper registration (race vs kubejs-create) | `bash tools/patches/build_patched_coe.sh` | +| `tdwcm-0.2.1-patched.jar` | Marks all TDWCM network channels `.optional()` (client-only mod registered ~31 required payloads → join rejection vs servers without it) | `bash tools/patches/build_patched_tdwcm.sh` | After changing either: update the hash in **both** packs' `mods/.pw.toml`, run `packwiz refresh`, then diff --git a/server_pack/index.toml b/server_pack/index.toml index e299d01..29a898b 100644 --- a/server_pack/index.toml +++ b/server_pack/index.toml @@ -711,6 +711,11 @@ file = "mods/terralith.pw.toml" hash = "81c5c05f9ed4c07b9f027ff6c421939bb04e476075091f135d34aaa05af1f950" metafile = true +[[files]] +file = "mods/the-doctor-who-client-mod.pw.toml" +hash = "70b16676553132b386a3cf5e082e7f86d9d2aa488efe047d74096481269e9793" +metafile = true + [[files]] file = "mods/thevoiddimension.pw.toml" hash = "41d3d7c6960b26b3f26eb532a3499c71ae976d2e434732d88e0ca56f7fd3b376" diff --git a/server_pack/mods/the-doctor-who-client-mod.pw.toml b/server_pack/mods/the-doctor-who-client-mod.pw.toml new file mode 100644 index 0000000..76fd829 --- /dev/null +++ b/server_pack/mods/the-doctor-who-client-mod.pw.toml @@ -0,0 +1,13 @@ +name = "THE Doctor Who Client Mod (patched - optional channels)" +filename = "tdwcm-0.2.1-patched.jar" +side = "both" + +[download] +url = "https://git.mithraic.cloud/ad3laid3/SciCraft/raw/branch/main/tools/patches/tdwcm-0.2.1-patched.jar" +hash-format = "sha512" +hash = "4169de97214ab71178791299d5164fd04b99ed2429cb2799b8e177849979406d98158954564b33149a0d505cfe23d56005b8462dc22390c0b77ee7edaa8f43a4" + +[update] +[update.modrinth] +mod-id = "96oAiXlp" +version = "xpTvgljr" diff --git a/server_pack/pack.toml b/server_pack/pack.toml index 7dbd164..07ec8b0 100644 --- a/server_pack/pack.toml +++ b/server_pack/pack.toml @@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0" [index] file = "index.toml" hash-format = "sha256" -hash = "e71c2f015f60a250c965cb6592bc1c5e00ef657b54e2cbe70afc2bbce62cec98" +hash = "359a0f49c4ae92325c416b54888b43f8221a7569c4466a8e4537bfc07026ddad" [versions] minecraft = "1.21.1" diff --git a/tools/patches/TdwcmPatcher.java b/tools/patches/TdwcmPatcher.java new file mode 100644 index 0000000..5b13631 --- /dev/null +++ b/tools/patches/TdwcmPatcher.java @@ -0,0 +1,68 @@ +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +import java.nio.file.Files; +import java.nio.file.Paths; + +/** + * Makes all of TDWCM's payload registrations optional. + * + * Upstream bug: net.dumonde.tdwcm.networking.NetworkRegistry.register() does + * registrar = event.registrar("1.0"); + * MESSAGES.forEach((t, m) -> registrar.playBidirectional(t, ...)); + * — every channel (entity_rotation_sync + ~30 more) is REQUIRED, so clients + * carrying the mod cannot join any server without it ("channel missing"). + * + * Fix: insert PayloadRegistrar.optional() right after registrar creation: + * registrar = event.registrar("1.0").optional(); + * Optional payloads are skipped during login negotiation when absent on the + * other side, and client→server messages are dropped server-side instead of + * failing handshake. Exactly what a client-side-only mod wants. + */ +public class TdwcmPatcher { + public static void main(String[] args) throws Exception { + if (args.length != 2) { + System.err.println("usage: TdwcmPatcher "); + System.exit(2); + } + byte[] bytes = Files.readAllBytes(Paths.get(args[0])); + ClassNode cn = new ClassNode(); + new ClassReader(bytes).accept(cn, 0); + + boolean patched = false; + for (MethodNode mn : cn.methods) { + if (!"register".equals(mn.name)) continue; + for (AbstractInsnNode n : mn.instructions) { + if (!(n instanceof MethodInsnNode mi)) continue; + if (!"registrar".equals(mi.name)) continue; + if (!(mi.getNext() instanceof VarInsnNode store) || store.getOpcode() != Opcodes.ASTORE) continue; + + InsnList ins = new InsnList(); + ins.add(new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, + "net/neoforged/neoforge/network/registration/PayloadRegistrar", + "optional", + "()Lnet/neoforged/neoforge/network/registration/PayloadRegistrar;", + false)); + mn.instructions.insert(mi, ins); + patched = true; + break; + } + } + if (!patched) throw new IllegalStateException("registrar(...) call site not found"); + + // COMPUTE_FRAMES: offsets shifted by the insertion; classpath must contain + // the original jar + neoforge universal jar for hierarchy resolution. + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cn.accept(cw); + Files.write(Paths.get(args[1]), cw.toByteArray()); + System.out.println("patched " + cn.name + ".register: registrar(...).optional() inserted"); + } +} diff --git a/tools/patches/build_patched_tdwcm.sh b/tools/patches/build_patched_tdwcm.sh new file mode 100644 index 0000000..b01ac2f --- /dev/null +++ b/tools/patches/build_patched_tdwcm.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash +# SCICRAFT — rebuild tdwcm with optional network channels. +# +# Why: THE Doctor Who Client Mod 0.2.1 registers all its payloads via +# PayloadRegistrar.playBidirectional WITHOUT .optional(), so clients carrying +# it are rejected by any server without the mod ("tdwcm:entity_rotation_sync + +# 30 more channels missing"). The mod is client-side-only in this pack, so we +# patch NetworkRegistry.register() to call registrar.optional() — absent +# payloads are then skipped in negotiation instead of failing login. +# +# Usage: tools/patches/build_patched_tdwcm.sh (from repo root) +set -euo pipefail +cd "$(dirname "$0")" + +TDWCM_URL="https://cdn.modrinth.com/data/96oAiXlp/versions/xpTvgljr/tdwcm-0.2.1.jar" +OUT_JAR="tdwcm-0.2.1-patched.jar" +WORK="$(mktemp -d)" +trap 'rm -rf "$WORK"' EXIT + +echo "[patch] downloading original..." +curl -fsSL -o "$WORK/tdwcm.jar" "$TDWCM_URL" + +if [ ! -f asm.jar ]; then + echo "[patch] fetching ASM..." + curl -fsSL -o asm.jar https://repo1.maven.org/maven2/org/ow2/asm/asm/9.7.1/asm-9.7.1.jar + curl -fsSL -o asm-tree.jar https://repo1.maven.org/maven2/org/ow2/asm/asm-tree/9.7.1/asm-tree-9.7.1.jar +fi + +# Neoforge universal jar for COMPUTE_FRAMES hierarchy resolution +NFG="${NEOFORGE_UNIVERSAL:-$WORK/neoforge-universal.jar}" +if [ ! -f "$NFG" ]; then + curl -fsSL -o "$WORK/neoforge-universal.jar" \ + https://maven.neoforged.net/releases/net/neoforged/neoforge/21.1.248/neoforge-21.1.248-universal.jar + NFG="$WORK/neoforge-universal.jar" +fi + +echo "[patch] compiling + running patcher..." +javac -cp asm.jar:asm-tree.jar TdwcmPatcher.java +unzip -q "$WORK/tdwcm.jar" "net/dumonde/tdwcm/networking/NetworkRegistry.class" -d "$WORK/x" +java -cp ".:asm.jar:asm-tree.jar:$WORK/tdwcm.jar:$NFG" TdwcmPatcher \ + "$WORK/x/net/dumonde/tdwcm/networking/NetworkRegistry.class" \ + "$WORK/NetworkRegistry.class" + +echo "[patch] rebuilding jar..." +cp "$WORK/tdwcm.jar" "$OUT_JAR" +mkdir -p "$WORK/stage/net/dumonde/tdwcm/networking" +cp "$WORK/NetworkRegistry.class" "$WORK/stage/net/dumonde/tdwcm/networking/" +(cd "$WORK/stage" && jar uf "$OLDPWD/$OUT_JAR" net) + +echo "[patch] verifying patched class inside jar..." +unzip -p "$OUT_JAR" net/dumonde/tdwcm/networking/NetworkRegistry.class > "$WORK/after.class" +cmp -s "$WORK/x/net/dumonde/tdwcm/networking/NetworkRegistry.class" "$WORK/after.class" \ + && { echo "[patch] ERROR: class unchanged"; exit 1; } +sha512sum "$OUT_JAR" +echo "[patch] done → $OUT_JAR" diff --git a/tools/patches/tdwcm-0.2.1-patched.jar b/tools/patches/tdwcm-0.2.1-patched.jar new file mode 100644 index 0000000..2ab0337 Binary files /dev/null and b/tools/patches/tdwcm-0.2.1-patched.jar differ