mirror of
https://github.com/horizon-rust/horizon.git
synced 2026-11-07 11:06:11 -10:00
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:
1 parent
f9e3244281
commit
07519df062
5 files changed
+136
No files matched your search
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user