# HG changeset patch # User Link Mauve # Date 1762626573 -3600 # Node ID ee09657d3789c007eee801961aa3d539e058ab47 # Parent 816e1f01d650a03bf98b9d39df41c8a04f63f7c5 Replace the stage parser with the Rust one diff --git a/formats/src/th06/std.rs b/formats/src/th06/std.rs --- a/formats/src/th06/std.rs +++ b/formats/src/th06/std.rs @@ -218,7 +218,6 @@ // TODO: replace this assert with a custom error. assert_eq!(size, 12); let (i, instr) = parse_instruction_args(i, opcode)?; - println!("{} {:?}", time, instr); let call = Call { time, instr }; Ok((i, call)) } diff --git a/python/src/lib.rs b/python/src/lib.rs --- a/python/src/lib.rs +++ b/python/src/lib.rs @@ -1,7 +1,8 @@ -use pyo3::exceptions::PyIOError; +use pyo3::exceptions::{PyIOError, PyKeyError}; use pyo3::prelude::*; -use pyo3::types::PyBytes; +use pyo3::types::{PyBytes, PyTuple}; use touhou_formats::th06::pbg3; +use touhou_formats::th06::std as stage; use std::collections::HashMap; use std::fs::File; use std::io::BufReader; @@ -11,6 +12,69 @@ mod glide; #[pyclass(module = "libtouhou")] +struct PyModel { + inner: stage::Model, +} + +#[pymethods] +impl PyModel { + #[getter] + fn quads(&self) -> Vec<(u16, f32, f32, f32, f32, f32)> { + self.inner.quads.iter().map(|quad| (quad.anm_script, quad.pos.x, quad.pos.y, quad.pos.z, quad.size_override.width, quad.size_override.height)).collect() + } + + #[getter] + fn bounding_box(&self) -> [f32; 6] { + self.inner.bounding_box + } +} + +#[pyclass(module = "libtouhou")] +struct PyStage { + inner: stage::Stage, +} + +#[pymethods] +impl PyStage { + #[getter] + fn models(&self) -> Vec { + self.inner.models.clone().into_iter().map(|inner| PyModel { inner }).collect() + } + + #[getter] + fn object_instances(&self) -> Vec<(u16, f32, f32, f32)> { + self.inner.instances.iter().map(|instance| (instance.id, instance.pos.x, instance.pos.y, instance.pos.z)).collect() + } + + #[getter] + fn name(&self) -> &str { + &self.inner.name + } + + #[getter] + fn bgms(&self) -> Vec> { + self.inner.musics.clone() + } + + #[getter] + fn script(&self, py: Python) -> Vec<(u32, u16, Py)> { + fn call_to_python(py: Python, call: &stage::Call) -> PyResult<(u32, u16, Py)> { + let (opcode, args) = match call.instr { + stage::Instruction::SetViewpos(x, y, z) => (0, (x, y, z).into_pyobject(py)?.unbind()), + stage::Instruction::SetFog(r, g, b, _a, near, far) => (1, (r, g, b, near, far).into_pyobject(py)?.unbind()), + stage::Instruction::SetViewpos2(x, y, z) => (2, (x, y, z).into_pyobject(py)?.unbind()), + stage::Instruction::StartInterpolatingViewpos2(frame, _unused1, _unused2) => (3, (frame,).into_pyobject(py)?.unbind()), + stage::Instruction::StartInterpolatingFog(frame, _unused1, _unused2) => (4, (frame,).into_pyobject(py)?.unbind()), + stage::Instruction::Unknown(unused1, unused2, unused3) => (5, (unused1, unused2, unused3).into_pyobject(py)?.unbind()), + }; + Ok((call.time, opcode, args)) + } + + self.inner.script.iter().map(|call| call_to_python(py, call).unwrap()).collect() + } +} + +#[pyclass(module = "libtouhou")] struct PBG3 { filename: PathBuf, inner: pbg3::PBG3>, @@ -111,13 +175,24 @@ /// Return the given file as an io.BytesIO object. fn get_file(&self, py: Python, name: String) -> PyResult> { + if let Some(archive) = self.known_files.get(&name) { + let mut archive = archive.borrow_mut(py); + let bytes = archive.get_file(py, &name); + let io = py.import("io")?; + let bytesio_class = io.dict().get_item("BytesIO")?.unwrap(); + let file = bytesio_class.call1((bytes,))?; + Ok(file.unbind()) + } else { + Err(PyKeyError::new_err(format!("Unknown file {name:?}"))) + } + } + + fn get_stage(&self, py: Python, name: String) -> PyResult> { let archive = self.known_files.get(&name).unwrap(); let mut archive = archive.borrow_mut(py); - let bytes = archive.get_file(py, &name); - let io = py.import("io")?; - let bytesio_class = io.dict().get_item("BytesIO")?.unwrap(); - let file = bytesio_class.call1((bytes,))?; - Ok(file.unbind()) + let bytes = archive.get_file_internal(&name); + let (_, inner) = stage::Stage::from_slice(&bytes).unwrap(); + Ok(Py::new(py, PyStage { inner })?) } } diff --git a/pytouhou/formats/std.py b/pytouhou/formats/std.py deleted file mode 100644 --- a/pytouhou/formats/std.py +++ /dev/null @@ -1,198 +0,0 @@ -# -*- encoding: utf-8 -*- -## -## Copyright (C) 2011 Thibaut Girka -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 3 only. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## - -"""Stage Definition (STD) files handling. - -This module provides classes for handling the Stage Definition file format. -The STD file format is a format used in Touhou 6: EoSD to describe non-gameplay -aspects of a stage: its name, its music, 3D models composing its background, -and various scripted events such as camera movement. -""" - - -from struct import pack, unpack, calcsize -from pytouhou.utils.helpers import read_string, get_logger - -logger = get_logger(__name__) - - -class Model: - def __init__(self, unknown=0, bounding_box=None, quads=None): - self.unknown = 0 - self.bounding_box = bounding_box or (0., 0., 0., - 0., 0., 0.) - self.quads = quads or [] - - - -class Stage: - """Handle Touhou 6 Stage Definition files. - - Stage Definition files are structured files describing non-gameplay aspects - aspects of a stage. They are split in a header an 3 additional sections. - - The header contains the name of the stage, the background musics (BGM) used, - as well as the number of quads and objects composing the background. - The first section describes the models composing the background, whereas - the second section dictates how they are used. - The last section describes the changes to the camera, fog, and other things. - - Instance variables: - name -- name of the stage - bgms -- list of (name, path) describing the different background musics used - models -- list of Model objects - object_instances -- list of instances of the aforementioned models - script -- stage script (camera, fog, etc.) - """ - - _instructions = {0: ('fff', 'set_viewpos'), - 1: ('BBBxff', 'set_fog'), - 2: ('fff', 'set_viewpos2'), - 3: ('Ixxxxxxxx', 'start_interpolating_viewpos2'), - 4: ('Ixxxxxxxx', 'start_interpolating_fog')} - - def __init__(self): - self.name = '' - self.bgms = (('', ''), ('', ''), ('', ''), ('', '')) - self.models = [] - self.object_instances = [] - self.script = [] - - - @classmethod - def read(cls, file): - """Read a Stage Definition file. - - Raise an exception if the file is invalid. - Return a STD instance otherwise. - """ - - stage = Stage() - - nb_models, nb_faces = unpack('