Handle server stop-sound packets through the existing sound engine

Decode source/event selection flags, including 26.3 UI source 10, and route them to Audio.stop_sounds. Compare all 36 native Java codec selections and round-trip their bytes; reject invalid sources and truncated packets.

Co-Authored-By: Codex (gpt-6.1-sol) <[email protected]>
This commit is contained in:
RVZO6andCodex committed 2026-10-24 23:23:59 -04:00
1 parent f9e3244281
commit 07519df062
5 files changed
+136

No files matched your search

+3
View File
@@ -1790,6 +1790,9 @@ impl Game<'_> {
self.audio.play(&name, Some(at), p.volume, p.pitch);
}
}
C::StopSound(p) => self
.audio
.stop_sounds(p.sound.as_deref(), p.source.map(|s| s as usize)),
C::AddEntity(p) => {
let kind = self
.remote
@@ -0,0 +1,31 @@
use horizon_protocol::{
buf::{Codec, Reader, Writer},
packets::play::StopSound,
};
use std::{error::Error, io::Write};
fn main() -> Result<(), Box<dyn Error>> {
let args: Vec<_> = std::env::args().collect();
let mut out = std::fs::File::create(&args[2])?;
for line in std::fs::read_to_string(&args[1])?.lines() {
let input: serde_json::Value = serde_json::from_str(line)?;
let hex = input["wire"].as_str().unwrap();
let bytes: Vec<u8> = (0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16))
.collect::<Result<_, _>>()?;
let mut reader = Reader::new(&bytes);
let packet = StopSound::read(&mut reader)?;
assert_eq!(reader.remaining(), 0);
let mut writer = Writer::new();
StopSound::write(&packet, &mut writer);
assert_eq!(writer.data, bytes);
writeln!(
out,
"{}",
serde_json::json!({"source":packet.source,"sound":packet.sound})
)?;
}
assert!(StopSound::read(&mut Reader::new(&[1, 11])).is_err());
assert!(StopSound::read(&mut Reader::new(&[3])).is_err());
Ok(())
}
@@ -1118,6 +1118,42 @@ packet!(SoundEntity {
pitch: f32 => Float,
seed: i64 => Long,
});
/// ClientboundStopSoundPacket: bit 0 selects a source, bit 1 an event id.
#[derive(Clone, Debug, PartialEq)]
pub struct StopSound {
pub source: Option<i32>,
pub sound: Option<String>,
}
impl Codec<StopSound> for StopSound {
fn read(r: &mut Reader) -> crate::Result<Self> {
let flags = r.u8()?;
let source = if flags & 1 != 0 {
let value = r.varint()?;
if !(0..11).contains(&value) {
return Err(crate::Error::new("invalid sound source"));
}
Some(value)
} else {
None
};
let sound = if flags & 2 != 0 {
Some(r.string(32767)?)
} else {
None
};
Ok(Self { source, sound })
}
fn write(p: &Self, w: &mut Writer) {
w.u8(u8::from(p.source.is_some()) | (u8::from(p.sound.is_some()) << 1));
if let Some(source) = p.source {
w.varint(source);
}
if let Some(sound) = &p.sound {
w.string(sound);
}
}
}
/// One entry of ClientboundCommandsPacket: a literal or an argument node.
#[derive(Clone, Debug, PartialEq)]
pub struct CommandNode {
@@ -1678,6 +1714,7 @@ packet_enum!(Clientbound, crate::ids::play::clientbound {
SetTitlesAnimation(TitlesAnimation) = SET_TITLES_ANIMATION,
Sound(Sound) = SOUND,
SoundEntity(SoundEntity) = SOUND_ENTITY,
StopSound(StopSound) = STOP_SOUND,
StartConfiguration(Empty) = START_CONFIGURATION,
SwingAnimation(SwingAnimation) = SWING_ANIMATION,
SystemChat(SystemChat) = SYSTEM_CHAT,
@@ -0,0 +1,30 @@
package dev.horizonlab.parity;
import com.google.gson.*;
import io.netty.buffer.Unpooled;
import java.nio.file.*;
import java.util.*;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.protocol.game.ClientboundStopSoundPacket;
import net.minecraft.resources.Identifier;
import net.minecraft.sounds.SoundSource;
/** Every source/event selection combination through the native stream codec. */
public final class VanillaStopSoundWireOracle {
public static void main(String[] args) throws Exception {
var out = new ArrayList<String>();
for (int source = -1; source < SoundSource.values().length; source++) {
for (String sound : new String[]{null, "minecraft:entity.pig.ambient", "example:custom"}) {
var packet = new ClientboundStopSoundPacket(sound == null ? null : Identifier.parse(sound), source < 0 ? null : SoundSource.values()[source]);
var buf = new FriendlyByteBuf(Unpooled.buffer());
ClientboundStopSoundPacket.STREAM_CODEC.encode(buf, packet);
byte[] bytes = new byte[buf.readableBytes()]; buf.readBytes(bytes); buf.release();
var result = new JsonObject(); result.addProperty("wire", HexFormat.of().formatHex(bytes));
result.add("source", source < 0 ? JsonNull.INSTANCE : new JsonPrimitive(source));
result.add("sound", sound == null ? JsonNull.INSTANCE : new JsonPrimitive(sound));
out.add(new Gson().toJson(result));
}
}
Files.write(Path.of(args[0]), out);
}
}
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Stop-sound packet bytes and selections against the native Java codec."""
import json
import os
try:
from . import lab, test_rust_parity as base, oracle_harness as harness
except ImportError:
import lab
import test_rust_parity as base
import oracle_harness as harness
def main():
build=lab.LOCAL/'stop-sound-wire-parity'; build.mkdir(exist_ok=True)
(build/'report.json').unlink(missing_ok=True)
lock,jars,libraries=base.verify_artifacts()
cp=os.pathsep.join(map(str,[jars['server'],*libraries])); classes=build/'classes';classes.mkdir(exist_ok=True)
source=lab.ROOT/'mods/horizon/src/test/java/dev/horizonlab/parity/VanillaStopSoundWireOracle.java'
sources=[source,lab.ROOT/'crates/horizon-protocol/src/packets/play.rs',lab.ROOT/'crates/horizon-client/src/game/remote.rs']
tested=base.source_hashes(sources)
run=lambda cmd,name:harness.run_logged(cmd,build,name)
run(['javac','--release',lock['minecraft']['java_major'],'-proc:none','-cp',cp,'-d',classes,source],'compile-java')
run(['java','-cp',str(classes)+os.pathsep+cp,'dev.horizonlab.parity.VanillaStopSoundWireOracle',build/'java.jsonl'],'java')
run(['cargo','build','-p','horizon-protocol','--example','stop_sound_probe'],'compile-rust')
run([lab.cargo_target()/'debug/examples/stop_sound_probe',build/'java.jsonl',build/'rust.jsonl'],'rust')
java=[json.loads(x) for x in (build/'java.jsonl').read_text().splitlines()]
rust=[json.loads(x) for x in (build/'rust.jsonl').read_text().splitlines()]
assert len(java)==len(rust)==36
assert all({'source':a.get('source'),'sound':a.get('sound')}==b for a,b in zip(java,rust))
base.ensure_sources_unchanged(sources,tested)
lab.write_json(build/'report.json',{'pass':True,'cases':len(java),'sources_sha256':tested})
print(f'Stop sound: {len(java)} native wire selections match and re-encode byte for byte')
if __name__=='__main__':main()