feat: partial support for patches

This commit is contained in:
2025-04-11 15:27:13 +00:00
parent b9a40d44a8
commit 1a68eda8c1
21 changed files with 1218 additions and 583 deletions

View File

@ -3,4 +3,4 @@ pub mod misc;
pub mod rainy;
pub mod profile;
pub mod config;
pub mod segatools_base;
pub mod patch;

155
rust/src/model/patch.rs Normal file
View File

@ -0,0 +1,155 @@
use std::collections::BTreeMap;
use serde::{de, Deserialize, Deserializer, Serialize};
use serde::ser::{Serializer, SerializeStruct};
use serde_json::Value;
use anyhow::Result;
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct PatchSelection(pub BTreeMap<String, PatchSelectionData>);
#[derive(Deserialize, Serialize, Clone, Debug)]
pub enum PatchSelectionData {
Enabled,
Number(i8),
Hex(Vec<u8>)
}
#[derive(Default)]
pub struct PatchFileVec(pub Vec<PatchFile>);
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct PatchFile(pub Vec<PatchList>);
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct PatchList {
pub filename: String,
pub version: String,
pub sha256: String,
pub patches: Vec<Patch>
}
#[derive(Clone, Debug)]
pub struct Patch {
pub id: String,
pub name: String,
pub tooltip: Option<String>,
pub data: PatchData
}
#[derive(Deserialize, Serialize, Clone, Debug)]
#[serde(untagged)]
pub enum PatchData {
Normal(NormalPatch),
Number(NumberPatch),
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct NormalPatch {
pub patches: Vec<NormalPatchField>
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct NormalPatchField {
pub offset: u64,
pub off: Vec<u8>,
pub on: Vec<u8>
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct NumberPatch {
pub offset: u64,
pub size: i64,
pub min: i32,
pub max: i32
}
impl Serialize for Patch {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: Serializer {
let mut state = serializer.serialize_struct("Patch", 7)?;
state.serialize_field("id", &self.id)?;
state.serialize_field("name", &self.name)?;
state.serialize_field("tooltip", &self.tooltip)?;
match &self.data {
PatchData::Normal(patch) => {
state.serialize_field("patches", &patch.patches)?;
}
PatchData::Number(patch) => {
state.serialize_field("type", "number")?;
state.serialize_field("offset", &patch.offset)?;
state.serialize_field("size", &patch.size)?;
state.serialize_field("min", &patch.min)?;
state.serialize_field("max", &patch.max)?;
}
}
state.end()
}
}
impl<'de> serde::Deserialize<'de> for Patch {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let value = Value::deserialize(d)?;
let data = Ok(match value.get("type").and_then(Value::as_str) {
Some("number") => PatchData::Number(NumberPatch {
offset: value.get("offset")
.and_then(Value::as_u64)
.ok_or_else(|| de::Error::missing_field("offset"))?,
size: value.get("size")
.and_then(Value::as_i64)
.ok_or_else(|| de::Error::missing_field("size"))?,
min: i32::try_from(value.get("min")
.and_then(Value::as_i64)
.ok_or_else(|| de::Error::missing_field("min"))?
).map_err(|_| de::Error::missing_field("min"))?,
max: i32::try_from(value.get("min")
.and_then(Value::as_i64)
.ok_or_else(|| de::Error::missing_field("min"))?
).map_err(|_| de::Error::missing_field("min"))?
}),
None => {
let mut patches = vec![];
for patch in value.get("patches").and_then(Value::as_array).unwrap() {
let mut off_list: Vec<u8> = Vec::new();
let mut on_list: Vec<u8> = Vec::new();
for off in patch.get("off").and_then(Value::as_array).unwrap() {
off_list.push(u8::try_from(
off.as_u64().ok_or_else(|| de::Error::missing_field("off"))?
).map_err(|_| de::Error::missing_field("off"))?);
}
for on in patch.get("on").and_then(Value::as_array).unwrap() {
on_list.push(u8::try_from(
on.as_u64().ok_or_else(|| de::Error::missing_field("on"))?
).map_err(|_| de::Error::missing_field("on"))?);
}
patches.push(NormalPatchField {
offset: patch.get("offset")
.and_then(Value::as_u64)
.ok_or_else(|| de::Error::missing_field("offset"))?,
off: off_list,
on: on_list
})
}
PatchData::Normal(NormalPatch {
patches
})
},
Some(&_) => return Err(de::Error::custom("unsupported type"))
});
Ok(Patch {
id: value.get("id")
.and_then(Value::as_str)
.ok_or_else(|| de::Error::missing_field("id"))?
.to_owned(),
name: value.get("name")
.and_then(Value::as_str)
.ok_or_else(|| de::Error::missing_field("name"))?
.to_owned(),
tooltip: value.get("tooltip")
.and_then(Value::as_str)
.and_then(|s| Some(s.to_owned())),
data: data?
})
}
}

View File

@ -1,142 +0,0 @@
use super::misc::Game;
pub fn segatools_base(game: Game) -> String {
match game {
Game::Ongeki =>
"[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.
enable=1
[system]
; Enable ALLS system settings.
enable=1
; Enable freeplay mode. This will disable the coin slot and set the game to
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
; allow you to start a game in freeplay mode.
freeplay=0
; LAN Install: Set this to 1 on all machines.
dipsw1=1
[gfx]
; Enables the graphics hook.
enable=1
[led15093]
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
; RGBs and the rear LED panel (billboard) on the cabinet.
enable=1
[led]
; Output billboard LED strip data to a named pipe called \"\\\\.\\pipe\\ongeki_led\"
cabLedOutputPipe=1
; Output billboard LED strip data to serial
cabLedOutputSerial=0
; Output slider LED data to the named pipe
controllerLedOutputPipe=1
; Output slider LED data to the serial port
controllerLedOutputSerial=0".to_owned(),
Game::Chunithm => "
[vfd]
; Enable VFD emulation. Disable to use a real VFD
; GP1232A02A FUTABA assembly.
enable=1
[system]
; Enable ALLS system settings.
enable=1
; Enable freeplay mode. This will disable the coin slot and set the game to
; freeplay. Keep in mind that some game modes (e.g. Freedom/Time Modes) will not
; allow you to start a game in freeplay mode.
freeplay=0
; LAN Install: If multiple machines are present on the same LAN then set
; this to 1 on exactly one machine and set this to 0 on all others.
dipsw1=1
; Monitor type: 0 = 120FPS, 1 = 60FPS
dipsw2=1
; Cab type: 0 = SP, 1 = CVT. SP will enable VFD and eMoney. This setting will switch
; the LED 837-15093-06 COM port and the AiMe reder hardware generation as well.
dipsw3=1
; -----------------------------------------------------------------------------
; Misc. hooks settings
; -----------------------------------------------------------------------------
[gfx]
; Enables the graphics hook.
enable=1
; Force the game to run windowed.
windowed=1
; Add a frame to the game window if running windowed.
framed=0
; Select the monitor to run the game on. (Fullscreen only, 0 =primary screen)
monitor=0
; -----------------------------------------------------------------------------
; LED settings
; -----------------------------------------------------------------------------
[led15093]
; Enable emulation of the 15093-06 controlled lights, which handle the air tower
; RGBs and the rear LED panel (billboard) on the cabinet.
enable=1
[led]
; Output billboard LED strip data to a named pipe called \"\\\\.\\pipe\\chuni_led\"
cabLedOutputPipe=1
; Output billboard LED strip data to serial
cabLedOutputSerial=0
; Output slider LED data to the named pipe
controllerLedOutputPipe=1
; Output slider LED data to the serial port
controllerLedOutputSerial=0
; Use the OpeNITHM protocol for serial LED output
controllerLedOutputOpeNITHM=0
; Serial port to send data to if using serial output. Default is COM5.
;serialPort=COM5
; Baud rate for serial data (set to 115200 if using OpeNITHM)
;serialBaud=921600
; Data output a sequence of bytes, with JVS-like framing.
; Each \"packet\" starts with 0xE0 as a sync. To avoid E0 appearing elsewhere,
; 0xD0 is used as an escape character -- if you receive D0 in the output, ignore
; it and use the next sent byte plus one instead.
;
; After the sync is one byte for the board number that was updated, followed by
; the red, green and blue values for each LED.
;
; Board 0 has 53 LEDs:
; [0]-[49]: snakes through left half of billboard (first column starts at top)
; [50]-[52]: left side partition LEDs
;
; Board 1 has 63 LEDs:
; [0]-[59]: right half of billboard (first column starts at bottom)
; [60]-[62]: right side partition LEDs
;
; Board 2 is the slider and has 31 LEDs:
; [0]-[31]: slider LEDs right to left BRG, alternating between keys and dividers
; -----------------------------------------------------------------------------
; Custom IO settings
; -----------------------------------------------------------------------------
[chuniio]
; Uncomment this if you have custom chuniio implementation comprised of a single 32bit DLL.
; (will use chu2to3 engine internally)
;path=
; Uncomment both of these if you have custom chuniio implementation comprised of two DLLs.
; x86 chuniio to path32, x64 to path64. Both are necessary.
;path32=
;path64=
".to_owned()
}
}