Skip to content

Commit 9e1f540

Browse files
committed
chore: various fix
Signed-off-by: lxl66566 <lxl66566@gmail.com>
1 parent 4842b23 commit 9e1f540

File tree

7 files changed

+109
-96
lines changed

7 files changed

+109
-96
lines changed

.taplo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
exclude = ["target/**"]
1+
exclude = ["target"]
22
include = ["**/*.toml"]
33

44
[[rule]]

Cargo.toml

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
[package]
2-
edition = "2024"
3-
name = "AudioSpeedHack"
4-
version = "0.1.0"
2+
authors = ["lxl66566 <lxl66566@gmail.com>"]
3+
categories = ["command-line-utilities", "games", "multimedia::audio"]
4+
description = "Accelerate audio in dsound.dll-based games."
5+
edition = "2024"
6+
keywords = ["audio", "speed", "speedup", "dsound"]
7+
license = "MIT"
8+
name = "AudioSpeedHack"
9+
readme = "README.md"
10+
repository = "https://github.com/lxl66566/AudioSpeedHack"
11+
version = "0.1.0"
512

613
[dependencies]
714
anyhow = "1.0.75"
@@ -15,3 +22,10 @@ ringbuf = "0.3"
1522
strum = "0.27"
1623
strum_macros = "0.27"
1724
terminal-menu = "3.1.0"
25+
26+
27+
[profile.release]
28+
lto = true
29+
opt-level = "z"
30+
panic = "abort"
31+
strip = true

README.md

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AudioSpeedHack
22

3-
一个实用小工具,**用于加速所有基于 dsound.dll 的游戏音频**
3+
一个实用工具,用于**加速所有基于 dsound.dll 的游戏音频**
44

55
## 用法
66

@@ -20,25 +20,36 @@
2020

2121
本项目的音频加速本质上是让程序加载修改后的 DLL,对音频**先加速升调,再降调**得到的。
2222

23-
1. 修改 [dsoal](https://github.com/lxl66566/dsoal) 项目代码,强制将 frequency 调整为 1.0 到 2.5 倍的自定义值,并为每个频率编译一个 dll,打包进此工具内。在执行“解压 DLL”选项后,工具将必要的 dll 解压到当前目录下。启动游戏,加载这些 dll 后,所有音频都会被加速 + 升调
24-
2. 然后,游戏音频会通过 VB-CABLE 输出到本工具的音频处理程序,该音频处理将升调后的音频还原到原始音高,并输出处理后的音频到播放设备上
23+
1. **音频拦截与加速**:本工具内置了 [dsoal](https://github.com/lxl66566/dsoal) 修改而来的 `dsound.dll` 文件。启动时,工具根据选择的加速倍率,将一些 DLL 释放到游戏根目录。当游戏运行时,它会加载自定义的 `dsound.dll` 而非系统默认 DLL。此 DLL 会强制加速音频缓冲区处理,从而提高音频播放速度,但副作用是音调也会随之升高
24+
2. **音高实时校正**:为了解决音调升高的问题,游戏的高音调音频会通过 [VB-CABLE Virtual Audio Device](https://vb-audio.com/Cable/) 输出。AudioSpeedHack 主程序会捕获来自虚拟声卡的音频流,对其进行实时的音高修正(降调),最后将正常音高、加速后的音频输出到播放设备上
2525

26-
## 排查问题
26+
## 问题排查
2727

2828
### 如何判断当前游戏是否使用 dsound.dll?
2929

30-
可以下载一个 [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon)
30+
可以使用微软官方的 [Process Monitor](https://learn.microsoft.com/en-us/sysinternals/downloads/procmon) 工具来检查
3131

32-
1. 运行 Procmon64.exe。
33-
2. 启动你的游戏,并且让其播放音频。
34-
3. 返回 Process Monitor,点击漏斗图标打开 Filter,筛选 Process Name 为你的游戏名称、Path contains `dsound.dll`
35-
4. 查看列表中是否有匹配的结果,Path 是否是游戏文件下的 `dsound.dll`
32+
1. 运行 `Procmon64.exe`
33+
2. 启动您的游戏,并确保游戏已经播放了一段音频。
34+
3. 切换到 Process Monitor 窗口,点击工具栏上的“漏斗”图标 (Filter) 打开筛选器。
35+
4. 添加筛选规则:
36+
- `Path` contains `dsound.dll`
37+
5. 查看结果列表。如果能找到匹配的条目,并且 Process Name 是游戏相关进程,其 Path 指向的是游戏目录下的 `dsound.dll`,则证明此工具适用。
3638

3739
## TODO
3840

39-
本工具还处于极为原始的阶段,欢迎 PR
41+
本工具还处于极为原始的阶段,欢迎任何形式的贡献(Issue/PR)
4042

4143
- [ ] **支持 xaudio2 与其他音频 API**
44+
- [ ] 音质改善
4245
- [ ] 傻瓜式判断游戏是否使用 dsound.dll
4346
- [ ] 注入而非预编译,或者减小 dll 体积
44-
- [ ] 更好的 TUI 界面或 GUI
47+
- [ ] 更好的 TUI 界面,或 GUI
48+
49+
## Tested On
50+
51+
Windows 11
52+
53+
- 春音 Alice*Gram
54+
- 白恋 Sakura*Gram
55+
- Deep One -ディープワン

src/asset.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ use std::{
33
path::{Path, PathBuf},
44
};
55

6-
use crate::utils;
76
use anyhow::Result;
87
use include_assets::{NamedArchive, include_dir};
8+
use log::debug;
9+
10+
use crate::utils;
911

1012
/// 提取选择的资源文件到指定目录
1113
///
@@ -39,10 +41,12 @@ pub fn extract_selected_assets(
3941
ret.push(aldrv_dest.clone());
4042
}
4143
fs::write(aldrv_dest, aldrv_bytes)?;
44+
debug!("Extracted dsoal-aldrv.dll");
4245

4346
if !dsound_dest.exists() {
4447
ret.push(dsound_dest.clone());
4548
}
4649
fs::write(dsound_dest, dsound_bytes)?;
50+
debug!("Extracted dsound.dll");
4751
Ok(ret)
4852
}

src/device.rs

Lines changed: 5 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use anyhow::Result;
55
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
66
use pitch_shift::PitchShifter;
77
use ringbuf::HeapRb;
8-
use terminal_menu::{button, label, menu, mut_menu, run};
98

109
use crate::utils::AudioExt;
1110

@@ -134,52 +133,6 @@ impl DeviceManager {
134133
Ok(())
135134
}
136135

137-
/// TUI 交互式选择输入输出设备
138-
pub fn select_device_tui(&mut self, device_type: DeviceType) -> Result<()> {
139-
let mut devices = self
140-
.host
141-
.devices()?
142-
.filter(|d| match device_type {
143-
DeviceType::Input => d.default_input_config().is_ok(),
144-
DeviceType::Output => d.default_output_config().is_ok(),
145-
})
146-
.collect::<Vec<_>>();
147-
148-
if devices.is_empty() {
149-
anyhow::bail!("未找到可用的{}设备。", device_type);
150-
}
151-
152-
// 创建菜单项
153-
let mut menu_items = vec![label(format!(
154-
"请选择{}设备,使用方向键选择, Enter确认:",
155-
device_type
156-
))];
157-
158-
// 将每个设备添加为按钮
159-
for device in &devices {
160-
let device_name = device.name().unwrap_or_else(|_| "Unknown".to_string());
161-
menu_items.push(button(device_name));
162-
}
163-
164-
let menu = menu(menu_items);
165-
run(&menu);
166-
167-
if mut_menu(&menu).canceled() {
168-
anyhow::bail!("用户取消了设备选择。");
169-
}
170-
171-
// 获取选择的设备名称并找到对应的设备
172-
let binding = mut_menu(&menu);
173-
let selected_name = binding.selected_item_name();
174-
let selected_index = devices
175-
.iter()
176-
.position(|d| d.name().unwrap_or_else(|_| "Unknown".to_string()) == selected_name)
177-
.unwrap(); // 在这个逻辑下,我们总是能找到它
178-
179-
self.set_device(device_type, devices.remove(selected_index));
180-
Ok(())
181-
}
182-
183136
pub fn run_process(self, speed: f32) -> anyhow::Result<()> {
184137
let input_device = self.input_device.expect("输入设备不存在");
185138
let output_device = self.output_device.expect("输出设备不存在");
@@ -314,7 +267,11 @@ impl DeviceManager {
314267
input_stream.play()?;
315268
output_stream.play()?;
316269

317-
info!("音频流已启动!正在将麦克风输入降低一个八度后播放。");
270+
info!(
271+
"音频流已启动!正在降低音高:speed {:.1}, pitch {:.4}",
272+
speed,
273+
speed.to_pitch()
274+
);
318275
info!("按 Enter 键退出程序。");
319276

320277
let mut _buffer = String::new();

src/main.rs

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,30 @@ pub mod utils;
77

88
use std::{env, fs, process};
99

10+
use ::log::{error, info};
1011
use clap::Parser;
1112

1213
use crate::{
1314
cli::{Cli, Commands},
1415
device::{DeviceManager, DeviceType},
1516
};
16-
use ::log::{debug, info};
17+
18+
struct PauseGuard<'a> {
19+
msg: &'a str,
20+
}
21+
22+
impl<'a> PauseGuard<'a> {
23+
fn new(msg: &'a str) -> Self {
24+
PauseGuard { msg }
25+
}
26+
}
27+
28+
impl<'a> Drop for PauseGuard<'a> {
29+
fn drop(&mut self) {
30+
println!("{}", self.msg);
31+
std::io::stdin().read_line(&mut String::new()).unwrap();
32+
}
33+
}
1734

1835
fn main() -> anyhow::Result<()> {
1936
log::log_init();
@@ -29,6 +46,7 @@ fn main() -> anyhow::Result<()> {
2946

3047
match cli.command {
3148
Commands::ListDevices => {
49+
let _pause_guard = PauseGuard::new("按任意键退出...");
3250
DeviceManager::default().list_all_devices()?;
3351
}
3452
Commands::UnpackDll(args) => {
@@ -38,6 +56,7 @@ fn main() -> anyhow::Result<()> {
3856
utils::System::Win32
3957
};
4058
asset::extract_selected_assets(system, args.speed, env::current_dir()?)?;
59+
info!("所有 dll 已解压到当前目录。");
4160
}
4261
Commands::Start(args) => {
4362
let mut device_manager = DeviceManager::default();
@@ -55,17 +74,23 @@ fn main() -> anyhow::Result<()> {
5574
args.speed,
5675
env::current_dir()?,
5776
)?;
58-
debug!("extracted files: {extracted:?}");
77+
info!("extracted files: {extracted:?}");
5978
let mut device_manager = DeviceManager::default();
6079
device_manager.select_device(DeviceType::Input, args.input_device)?;
6180
device_manager.select_device(DeviceType::Output, args.output_device)?;
6281
let _child;
6382
if let Some(exec) = args.exec {
64-
_child = process::Command::new(exec).spawn()?;
83+
_child = process::Command::new(exec)
84+
.env("DSOAL_LOGFILE", "dsoal_error.txt")
85+
.spawn()?;
6586
}
6687
device_manager.run_process(args.speed)?;
6788
for f in extracted {
68-
fs::remove_file(f)?;
89+
let _pause_guard;
90+
if let Err(e) = fs::remove_file(&f) {
91+
error!("Failed to remove file {f:?}: {e}");
92+
_pause_guard = PauseGuard::new("删除解压的 dll 失败,按任意键继续...");
93+
}
6994
}
7095
}
7196
}

src/tui.rs

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,14 @@
1+
use std::fs;
2+
13
use anyhow::{Context, Result};
24
use cpal::traits::{DeviceTrait, HostTrait};
3-
use std::fs;
45
use terminal_menu::{
56
TerminalMenuItem, back_button, button, label, list, menu, mut_menu, run, submenu,
67
};
78

8-
// 导入您在 main.rs 中定义的 pub clap 结构体
9-
use crate::cli::*;
10-
11-
/// 设备类型,用于区分输入和输出
12-
#[derive(Clone, Copy)]
13-
pub enum DeviceType {
14-
Input,
15-
Output,
16-
}
9+
use crate::{cli::*, device::DeviceType};
1710

18-
impl std::fmt::Display for DeviceType {
19-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20-
match self {
21-
DeviceType::Input => write!(f, "输入"),
22-
DeviceType::Output => write!(f, "输出"),
23-
}
24-
}
25-
}
11+
const NONE_EXEC_ITEM: &str = "None";
2612

2713
/// 非交互式地获取可用设备名称列表
2814
fn get_device_names(device_type: DeviceType) -> Result<Vec<String>> {
@@ -56,16 +42,16 @@ pub fn run_tui() -> Result<Cli> {
5642
// 2. 构建菜单
5743
let main_menu = menu(vec![
5844
label("请选择一个要执行的操作,按 q 退出:"),
59-
button("ListDevices (列出设备)"),
60-
submenu("UnpackDll (解压DLL)", unpack_dll_menu()),
61-
submenu(
62-
"Start (启动程序)",
63-
start_menu(&input_devices, &output_devices, &executable_options),
64-
),
6545
submenu(
6646
"UnpackAndStart (解压并启动)",
6747
unpack_and_start_menu(&input_devices, &output_devices, &executable_options),
6848
),
49+
submenu(
50+
"Start (启动程序)",
51+
start_menu(&input_devices, &output_devices, &executable_options),
52+
),
53+
submenu("UnpackDll (解压DLL)", unpack_dll_menu()),
54+
button("ListDevices (列出设备)"),
6955
button("Exit (退出)"),
7056
]);
7157

@@ -109,7 +95,7 @@ pub fn run_tui() -> Result<Cli> {
10995
.context("选择的输出设备无效")?;
11096

11197
let exec_selection = sub_menu.selection_value("执行程序 (可选)");
112-
let exec = if exec_selection == "None" {
98+
let exec = if exec_selection == NONE_EXEC_ITEM {
11399
None
114100
} else {
115101
Some(exec_selection.to_string())
@@ -141,7 +127,7 @@ pub fn run_tui() -> Result<Cli> {
141127
.context("选择的输出设备无效")?;
142128

143129
let exec_selection = sub_menu.selection_value("执行程序 (可选)");
144-
let exec = if exec_selection == "None" {
130+
let exec = if exec_selection == NONE_EXEC_ITEM {
145131
None
146132
} else {
147133
Some(exec_selection.to_string())
@@ -217,9 +203,25 @@ fn speed_options() -> Vec<&'static str> {
217203

218204
/// 获取当前目录下的文件和文件夹作为 `exec` 的选项
219205
fn exec_options() -> Vec<String> {
220-
let mut options = vec!["None".to_string()]; // 提供不选择的选项
206+
let mut options = vec![NONE_EXEC_ITEM.to_string()]; // 提供不选择的选项
221207
if let Ok(entries) = fs::read_dir(".") {
222-
for entry in entries.flatten() {
208+
for entry in entries
209+
.flatten()
210+
.filter(|e| e.file_type().map(|t| t.is_file()).unwrap_or(false))
211+
.filter(|e| {
212+
// 过滤掉自身
213+
if let Some(name) = e.file_name().to_str()
214+
&& name.contains(env!("CARGO_PKG_NAME"))
215+
{
216+
return false;
217+
}
218+
if let Some(ext) = e.path().extension().and_then(|e| e.to_str()) {
219+
return ["exe", "bat", "cmd", "ps1", "sh"]
220+
.contains(&ext.to_lowercase().as_str());
221+
}
222+
false
223+
})
224+
{
223225
if let Some(name) = entry.file_name().to_str() {
224226
options.push(name.to_string());
225227
}

0 commit comments

Comments
 (0)