This commit is contained in:
PVV Sanctuary
2025-09-20 23:23:56 +02:00
parent 90b6cb5506
commit 3dd82dc66f
2 changed files with 133 additions and 17 deletions

View File

@@ -7,3 +7,4 @@ edition = "2024"
clap = { version = "4.5.47", features = ["derive"] }
rand = "0.9.2"
rodio = "0.21.1"
windows-service = "0.8.0"

View File

@@ -1,32 +1,69 @@
#![windows_subsystem = "windows"]
use clap::Parser;
use rand::prelude::*;
use rodio::Decoder;
use std::ffi::OsString;
use std::{io::Cursor, path::Path, time::Duration};
use windows_service::{
Result, define_windows_service,
service::{ServiceAccess, ServiceErrorControl, ServiceInfo, ServiceStartType, ServiceType},
service::{ServiceControl, ServiceControlAccept, ServiceExitCode, ServiceState, ServiceStatus},
service_control_handler::{self, ServiceControlHandlerResult},
service_dispatcher,
service_manager::{ServiceManager, ServiceManagerAccess},
};
define_windows_service!(ffi_service_main, my_service_main);
const SERVICE_NAME: &str = "interruptsfx";
fn my_service_main(arguments: Vec<OsString>) {
if let Err(e) = run_service(arguments) {
eprintln!("Service failed: {:?}", e);
}
}
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
struct Args {
#[arg(short, long, default_value_t = false)]
dynamic_sounds: bool,
#[arg(long, default_value_t = 60 * 4)]
#[arg(long, default_value_t = 0.1)]
volume: f32,
#[arg(long, default_value_t = 60 * 30)]
min_time_interval_seconds: u64,
#[arg(long, default_value_t = 60 * 15)]
#[arg(long, default_value_t = 60 * 60 * 3)]
max_time_interval_seconds: u64,
#[arg(long, default_value_t = String::from("./custom_sounds"))]
#[arg(long, default_value_t = String::from(r"C:\Users\Pleb\AppData\Local\Larian Studios\Baldur's Gate 3\Sounds"))]
custom_sounds_directory: String,
}
fn main() {
let args = Args::parse();
play_audio(&args);
if std::env::args().any(|arg| arg == "install") {
install_service();
return;
}
if std::env::args().any(|arg| arg == "uninstall") {
uninstall_service();
return;
}
if let Err(e) = service_dispatcher::start(SERVICE_NAME, ffi_service_main) {
eprintln!("Failed to start service: {:?}", e);
}
}
fn play_audio(args: &Args) {
use rand::prelude::*;
let mut rng = rand::rng();
struct AudioData {
data: Vec<Cursor<Vec<u8>>>,
rng: ThreadRng,
sink: rodio::Sink,
}
fn setup_audio(args: &Args) -> AudioData {
let rng = rand::rng();
let stream_handle =
rodio::OutputStreamBuilder::open_default_stream().expect("open default audio stream");
let sink = rodio::Sink::connect_new(&stream_handle.mixer());
let sink = rodio::Sink::connect_new(stream_handle.mixer());
sink.set_volume(args.volume);
let mut data = vec![
Cursor::new(
include_bytes!("../sounds/35-roblox-death-sound-variations-in-60-seconds-pt.mp3")
@@ -69,7 +106,7 @@ fn play_audio(args: &Args) {
Cursor::new(include_bytes!("../sounds/whistle_RjClohy.mp3").to_vec()),
];
if args.dynamic_sounds {
if args.custom_sounds_directory.as_str() != "none" {
let path = Path::new(&args.custom_sounds_directory);
for item in path.read_dir().unwrap() {
let item = item.unwrap();
@@ -82,13 +119,91 @@ fn play_audio(args: &Args) {
}
}
AudioData { data, rng, sink }
}
fn play_audio(args: &Args, audio_data: &mut AudioData) {
loop {
let i = rng.random_range(0..data.len());
let source = Decoder::try_from(data[i].clone()).unwrap();
sink.append(source);
sink.sleep_until_end();
std::thread::sleep(Duration::from_secs(rng.random_range(
std::thread::sleep(Duration::from_secs(audio_data.rng.random_range(
args.min_time_interval_seconds..args.max_time_interval_seconds,
)));
let i = audio_data.rng.random_range(0..audio_data.data.len());
let source = Decoder::try_from(audio_data.data[i].clone()).unwrap();
audio_data.sink.append(source);
audio_data.sink.sleep_until_end();
}
}
pub fn install_service() {
println!("starting to install");
let manager =
ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CREATE_SERVICE).unwrap();
let service_info = ServiceInfo {
name: SERVICE_NAME.into(),
display_name: "interruptsfx".into(),
service_type: ServiceType::OWN_PROCESS,
start_type: ServiceStartType::AutoStart,
error_control: ServiceErrorControl::Normal,
executable_path: std::env::current_exe().unwrap(),
launch_arguments: vec![],
dependencies: vec![],
account_name: None, // Local system account
account_password: None,
};
manager
.create_service(&service_info, ServiceAccess::START)
.unwrap();
println!("Service installed successfully");
}
pub fn uninstall_service() {
println!("starting to uninstall");
let manager =
ServiceManager::local_computer(None::<&str>, ServiceManagerAccess::CONNECT).unwrap();
let service = manager
.open_service(SERVICE_NAME, ServiceAccess::DELETE)
.unwrap();
service.delete().unwrap();
println!("Service uninstalled successfully");
}
fn run_service(args: Vec<OsString>) -> Result<()> {
let (shutdown_tx, shutdown_rx) = std::sync::mpsc::channel();
let status_handle =
service_control_handler::register(
SERVICE_NAME,
move |control_event| match control_event {
ServiceControl::Stop => {
shutdown_tx.send(()).unwrap();
ServiceControlHandlerResult::NoError
}
_ => ServiceControlHandlerResult::NotImplemented,
},
)?;
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Running,
controls_accepted: ServiceControlAccept::STOP,
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(10),
process_id: None,
})?;
let args = Args::parse_from(args);
let mut audio_data = setup_audio(&args);
loop {
play_audio(&args, &mut audio_data);
if shutdown_rx.try_recv().is_ok() {
break;
}
}
status_handle.set_service_status(ServiceStatus {
service_type: ServiceType::OWN_PROCESS,
current_state: ServiceState::Stopped,
controls_accepted: ServiceControlAccept::empty(),
exit_code: ServiceExitCode::Win32(0),
checkpoint: 0,
wait_hint: Duration::from_secs(0),
process_id: None,
})?;
Ok(())
}