fix: TDWCM join failure — optional channels + install on both sides
All checks were successful
Build & Release Pack / build (push) Successful in 1m51s
All checks were successful
Build & Release Pack / build (push) Successful in 1m51s
tdwcm 0.2.1 registered ~31 payloads via playBidirectional without .optional() AND registers real blocks/items/entities, so a one-sided install fails login twice (required-channel mismatch, then registry sync). Ship ASM-patched jar (tools/patches/build_patched_tdwcm.sh, TdwcmPatcher.java) that inserts registrar.optional(), and add it to server_pack so negotiation and registry sync pass on both sides. Verified: dedicated server boots clean (Done ~27s), tdwcm:tardis_interior dimension comes up under Sable physics + IPSable portal storage. Audit: no other client-only mod in the pack uses NeoForge's payload API.
This commit is contained in:
parent
ae0ede6c28
commit
fde71b7f86
11 changed files with 163 additions and 9 deletions
|
|
@ -862,7 +862,7 @@ metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "mods/the-doctor-who-client-mod.pw.toml"
|
file = "mods/the-doctor-who-client-mod.pw.toml"
|
||||||
hash = "133badb6d33d880f47ad59a60dd3916231168ba07701c957aee1f781d5d54a5a"
|
hash = "dd77144a6fe93e69509999bc5676017580649157602c8a61653344a3b6853879"
|
||||||
metafile = true
|
metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
name = "THE Doctor Who Client Mod"
|
name = "THE Doctor Who Client Mod (patched - optional network channels)"
|
||||||
filename = "tdwcm-0.2.1.jar"
|
filename = "tdwcm-0.2.1-patched.jar"
|
||||||
side = "client"
|
side = "both"
|
||||||
|
|
||||||
[download]
|
[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-format = "sha512"
|
||||||
hash = "68b3de21b7d3f6a5bc8d8e386a8a80e56828eb8c7335be9994875533120052c0c689d524ec140fec850cf2c13c40c6daac3f6f3cd3bff0df7c0ad2ab1888a292"
|
hash = "4169de97214ab71178791299d5164fd04b99ed2429cb2799b8e177849979406d98158954564b33149a0d505cfe23d56005b8462dc22390c0b77ee7edaa8f43a4"
|
||||||
|
|
||||||
[update]
|
[update]
|
||||||
[update.modrinth]
|
[update.modrinth]
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0"
|
||||||
[index]
|
[index]
|
||||||
file = "index.toml"
|
file = "index.toml"
|
||||||
hash-format = "sha256"
|
hash-format = "sha256"
|
||||||
hash = "e00761ce338bdd5fe09338b04cb68660f8ccbe6d3c646dda8ea96e751f4d5b5a"
|
hash = "c655e3a03467912fbbc79a344d303e811df05ddea4889c414155e209a1e95937"
|
||||||
|
|
||||||
[versions]
|
[versions]
|
||||||
minecraft = "1.21.1"
|
minecraft = "1.21.1"
|
||||||
|
|
|
||||||
14
docs/mods.md
14
docs/mods.md
|
|
@ -21,9 +21,14 @@
|
||||||
`dynamic-fps`, `3dskinlayers`, `visuality-forge`, `midnightlib`,
|
`dynamic-fps`, `3dskinlayers`, `visuality-forge`, `midnightlib`,
|
||||||
`entityculling`, `immediatelyfast`, `badoptimizations`, `athena-ctm`,
|
`entityculling`, `immediatelyfast`, `badoptimizations`, `athena-ctm`,
|
||||||
`journeymap-integration`, `stellarview`, `stellar-view-mixin-renderer`,
|
`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`.
|
`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)
|
## Dimensional physics stack (both packs)
|
||||||
|
|
||||||
Physics / portals run on **Sable** + **Create Aeronautics** + **IPSable**
|
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
|
set in the @Mod constructor while KubeJS runs plugin callbacks during its own
|
||||||
construction → nondeterministic boot crash). The patch removes COE's guarded
|
construction → nondeterministic boot crash). The patch removes COE's guarded
|
||||||
registration; kubejs-create remains the wrapper provider.
|
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
|
- **Create addon libs** (all present): RPL (Big Cannons), Kotlin for Forge
|
||||||
(Slice & Dice, Jetpack), Mechanicals Lib (Mechanical Extruder), Architectury
|
(Slice & Dice, Jetpack), Mechanicals Lib (Mechanical Extruder), Architectury
|
||||||
(Goggles). Flywheel is bundled inside Create — do **not** add a standalone
|
(Goggles). Flywheel is bundled inside Create — do **not** add a standalone
|
||||||
|
|
|
||||||
|
|
@ -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 |
|
| `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` |
|
| `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'
|
After changing either: update the hash in **both** packs'
|
||||||
`mods/<name>.pw.toml`, run `packwiz refresh`, then
|
`mods/<name>.pw.toml`, run `packwiz refresh`, then
|
||||||
|
|
|
||||||
|
|
@ -711,6 +711,11 @@ file = "mods/terralith.pw.toml"
|
||||||
hash = "81c5c05f9ed4c07b9f027ff6c421939bb04e476075091f135d34aaa05af1f950"
|
hash = "81c5c05f9ed4c07b9f027ff6c421939bb04e476075091f135d34aaa05af1f950"
|
||||||
metafile = true
|
metafile = true
|
||||||
|
|
||||||
|
[[files]]
|
||||||
|
file = "mods/the-doctor-who-client-mod.pw.toml"
|
||||||
|
hash = "70b16676553132b386a3cf5e082e7f86d9d2aa488efe047d74096481269e9793"
|
||||||
|
metafile = true
|
||||||
|
|
||||||
[[files]]
|
[[files]]
|
||||||
file = "mods/thevoiddimension.pw.toml"
|
file = "mods/thevoiddimension.pw.toml"
|
||||||
hash = "41d3d7c6960b26b3f26eb532a3499c71ae976d2e434732d88e0ca56f7fd3b376"
|
hash = "41d3d7c6960b26b3f26eb532a3499c71ae976d2e434732d88e0ca56f7fd3b376"
|
||||||
|
|
|
||||||
13
server_pack/mods/the-doctor-who-client-mod.pw.toml
Normal file
13
server_pack/mods/the-doctor-who-client-mod.pw.toml
Normal file
|
|
@ -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"
|
||||||
|
|
@ -6,7 +6,7 @@ pack-format = "packwiz:1.1.0"
|
||||||
[index]
|
[index]
|
||||||
file = "index.toml"
|
file = "index.toml"
|
||||||
hash-format = "sha256"
|
hash-format = "sha256"
|
||||||
hash = "e71c2f015f60a250c965cb6592bc1c5e00ef657b54e2cbe70afc2bbce62cec98"
|
hash = "359a0f49c4ae92325c416b54888b43f8221a7569c4466a8e4537bfc07026ddad"
|
||||||
|
|
||||||
[versions]
|
[versions]
|
||||||
minecraft = "1.21.1"
|
minecraft = "1.21.1"
|
||||||
|
|
|
||||||
68
tools/patches/TdwcmPatcher.java
Normal file
68
tools/patches/TdwcmPatcher.java
Normal file
|
|
@ -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 <NetworkRegistry.class-in> <class-out>");
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
55
tools/patches/build_patched_tdwcm.sh
Normal file
55
tools/patches/build_patched_tdwcm.sh
Normal file
|
|
@ -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"
|
||||||
BIN
tools/patches/tdwcm-0.2.1-patched.jar
Normal file
BIN
tools/patches/tdwcm-0.2.1-patched.jar
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue