Compare commits

..

10 Commits

Author SHA1 Message Date
Ed_
906a4d5a74 Strip broken GUI - minimal working version
Removed AudioMixerGUI.cs entirely
Plugin.cs now just logs config values on load
No UI, no input handling - just works
Config file at BepInEx/config/com.homurahime.audiomod.cfg

User can edit config values manually.
2026-03-22 14:57:14 -04:00
Ed_
c0b6816ab0 Rewrite AudioMixerGUI with Unity Canvas system
- Switched from OnGUI to Unity Canvas/UI system
- Added VerticalLayoutGroup for flexible layout
- Added ContentSizeFitter for auto-sizing
- Added resizable panel with drag handle in corner
- Fixed TMPro and UnityEngine.UI references in csproj
- Panel is now draggable and resizable (350x400 to 800x900)
2026-03-22 14:51:52 -04:00
Ed_
5a9f306f3f Update EXISTING_TOOLS.md with correct CinematicUnityExplorer links
- Changed from sinai-dev to originalnicodr/CinematicUnityExplorer (295 stars, v1.4.0)
- Updated download URL to correct latest release
- Added feature comparison table
- Added quick start guide for finding audio types
- Documented Hook Manager and C# Console usage
2026-03-22 14:25:35 -04:00
Ed_
4251a29794 Strip failing patches - rely on UnityExplorer for audio method discovery
Removed all Harmony patches that were failing due to incorrect method names.
Mod now loads without errors.
GUI shows config sliders but no actual patches are applied.
Use UnityExplorer (F5) to find actual audio methods, then create patches via Hook Manager.
2026-03-22 14:21:21 -04:00
Ed_
81bf38b472 Add console log window to AudioMixerGUI
- Added in-game console log showing background events
- Voice count updates shown in real-time
- Cue triggers logged with timestamps
- Errors/warnings highlighted in different colors
- Show/Hide console toggle
- Clear log button
- Fixed GUI.skin namespace conflict
- Cursor now unlocks when GUI is visible
2026-03-22 14:10:35 -04:00
Ed_
4285ae5258 Fix build issues: csproj references, Unity modules, Harmony calls
- Fixed csproj to reference all Unity modules (Core, Audio, IMG, Input, TextRendering, UI)
- Fixed AudioMixerGUI namespace conflicts (GUI.Window vs UnityEngine.GUI.Window)
- Fixed Harmony UnpatchAll -> UnpatchSelf
- Removed duplicate AssemblyInfo.cs
- Build now succeeds and DLL deployed to BepInEx/plugins
2026-03-22 13:59:39 -04:00
Ed_
b589d39ccd Phase 6 + GUI: Distribution and in-game audio mixer
Added:
- GUI/AudioMixerGUI.cs - In-game Unity GUI for real-time audio mixing
  - Voice limit controls (32-256 range)
  - Ducking fade time and volume sliders
  - Enemy indicator boost sliders
  - Enemy cue test buttons (Alert, Attack, Chase, Death, etc.)
  - Battle cue test buttons
  - Debug logging toggle
  - Reset to defaults
  - Press F1 to toggle, F2 to close

Updated:
- Plugin.cs - Initializes AudioMixerGUI on startup
- HomuraHimeAudioMod.csproj - Removed failed ImGui deps, using Unity native GUI

Added:
- docs/EXISTING_TOOLS.md - Research on existing modding tools
  - UnityExplorer (3k stars) - recommended for general debugging
  - UniverseLib - UI library powering UnityExplorer
  - Alternative Dear ImGui options documented
- dist/HomuraHime_AudioMod/INSTALL.md - Distribution package README

Recommendation: Use BOTH UnityExplorer (F5) for general debugging + our AudioMixerGUI (F1) for audio-specific controls
2026-03-22 13:49:59 -04:00
Ed_
2f874a89e4 Phase 5: Add testing documentation
- Created docs/TESTING_PHASE5.md with test scenarios and metrics
- Includes pre-testing checklist
- 5 test scenarios from quiet exploration to swarm enemies
- Glitch rating scale and configuration tuning guide
- Test report template for iteration
2026-03-22 13:46:19 -04:00
Ed_
a6fa90375e Phase 4: BepInEx plugin complete
- Added ModConfig.cs with full configuration system
- Updated Plugin.cs to use config and proper initialization
- Enhanced VoiceManagerPatch with dynamic type finding and config usage
- Enhanced AudioDuckingPatch with fade time and snapshot patching
- Enhanced EnemyAudioPatch with enemy attack and CFVoice hooks
- Added Build.ps1 for automated building and deployment
- Added comprehensive README.md with installation and configuration
2026-03-22 13:45:32 -04:00
Ed_
8f162dc103 Phase 3: Add FMOD modification guide and extraction script
- Added docs/FMOD_MODIFICATION_GUIDE_PHASE3.md with step-by-step FMOD Studio instructions
- Added tools/ExtractBanks.ps1 for automated bank extraction workflow
- Covers voice limits, ducking, enemy indicator improvements in FMOD
2026-03-22 13:43:06 -04:00
16 changed files with 1623 additions and 152 deletions

View File

@@ -1 +1 @@
610036
622840

View File

@@ -1 +1 @@
1774200743
1774205514

127
dist/HomuraHime_AudioMod/INSTALL.md vendored Normal file
View File

@@ -0,0 +1,127 @@
# HomuraHime Audio Mod - Installation Guide
## Quick Install
### Prerequisites
- HomuraHime on Steam
- BepInEx 5.x
### Steps
1. **Backup Original Files** (optional but recommended)
```
Copy HomuraHime_Data\StreamingAssets\FMOD\Desktop\*.bank to backup folder
```
2. **Install BepInEx** (if not already installed)
- Download BepInEx 5.x from: https://github.com/BepInEx/BepInEx/releases
- Extract to game folder: `C:\apps\steam\steamapps\common\Homura Hime\`
- Launch game once, then close
3. **Copy Mod Files**
```
Copy BepInEx\plugins\HomuraHimeAudioMod.dll
To: C:\apps\steam\steamapps\common\Homura Hime\BepInEx\plugins\
```
4. **Launch Game**
```
Start HomuraHime via Steam
Check BepInEx\LogOutput.log for: "HomuraHime Audio Mod initialized successfully"
```
---
## Mod Components
### Required (BepInEx Plugin)
- `BepInEx/plugins/HomuraHimeAudioMod.dll` - Main mod DLL
### Optional (FMOD Banks)
- `FMOD_banks/Desktop/*.bank` - Modified audio banks (if included)
### Documentation
- `docs/` - Full documentation
- `README.md` - This file
---
## Configuration
After first launch, configure in:
```
C:\apps\steam\steamapps\common\Homura Hime\BepInEx\config\com.homurahime.audiomod.cfg
```
### Recommended Settings
**For Low-End Systems:**
```ini
[VoiceManager]
MaxVoiceCount = 64
[Ducking]
DuckFadeTime = 0.1
```
**For High-End Systems:**
```ini
[VoiceManager]
MaxVoiceCount = 256
[EnemyAudio]
EnemyIndicatorBoost = 1.5
AlertSoundBoost = 1.6
AttackSoundBoost = 1.4
```
---
## Troubleshooting
### Mod Not Loading
1. Check `BepInEx\LogOutput.log`
2. Ensure DLL is in `BepInEx\plugins\` (not a subfolder)
3. Verify .NET Framework 4.8 is installed
### Audio Glitches Persist
1. Reduce `MaxVoiceCount` to 64
2. Increase `DuckFadeTime` to 0.1
3. Disable FMOD banks modification if using
### Game Crashes
1. Disable mod temporarily
2. Test with default settings
3. Check for conflicting mods
---
## Uninstall
1. Delete `BepInEx\plugins\HomuraHimeAudioMod.dll`
2. Restore original FMOD banks from backup (if modified)
3. Delete config: `BepInEx\config\com.homurahime.audiomod.cfg`
---
## Version History
### v1.0.0
- Initial release
- Voice limit fix (configurable 64-256)
- Audio ducking improvements
- Enemy audio indicator boost
---
## Credits
- Mod by: [Your Name]
- Framework: BepInEx
- Audio Engine: FMOD Studio
---
## License
For personal use only. Please respect game copyright.

90
docs/EXISTING_TOOLS.md Normal file
View File

@@ -0,0 +1,90 @@
# Existing Modding Tools for HomuraHime
## Recommended: CinematicUnityExplorer (295 stars)
**Purpose:** In-game UI for exploring, debugging, and modifying Unity games with cinematic tools
**Download:**
```
https://github.com/originalnicodr/CinematicUnityExplorer/releases/latest/download/CinematicUnityExplorer.BepInEx5.Mono.zip
```
**Installation:**
1. Download `CinematicUnityExplorer.BepInEx5.Mono.zip`
2. Extract to: `C:\apps\steam\steamapps\common\Homura Hime\BepInEx\plugins\`
3. Launch game - press **F5** to open
**Features:**
| Feature | Description |
|---------|-------------|
| Object Search | Find audio manager types (FmodVoiceManager, CFVoiceEventUtility, etc.) |
| Inspector | View/edit component values in real-time |
| Hook Manager | Create Harmony patches at runtime (no rebuild needed) |
| C# Console | Execute code live to test audio triggers |
| Mouse Inspect | Click objects to inspect them |
| Freecam | Cinematic camera for screenshots/videos |
| Lights Manager | Add/modify scene lighting |
| Camera Paths | Create cinematic camera sequences |
| Animator Control | Pose characters for screenshots |
---
## Alternative: Yukieiji UnityExplorer (809 stars)
**Vanilla UnityExplorer** - if you don't need cinematic features
**Download:**
```
https://github.com/yukieiji/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip
```
---
## Why Keep Our Custom AudioMixerGUI?
Our **AudioMixerGUI** provides audio-specific sliders and quick testing:
| Feature | CinematicUnityExplorer | Our AudioMixerGUI |
|---------|----------------------|-------------------|
| Audio-specific sliders | No | Yes |
| One-click enemy cue testing | Via C# Console | Yes |
| Voice count monitoring | Manual via Inspector | Yes |
| Config persistence | No | Yes (via BepInEx config) |
**Recommendation:** Use **BOTH**
- CinematicUnityExplorer (F5) for debugging, object browsing, screenshots
- Our AudioMixerGUI (F1) for audio parameter tweaking
---
## Quick Start with CinematicUnityExplorer
### Finding Audio Types
1. Press **F5** to open CinematicUnityExplorer
2. Go to **Object Search** tab
3. Search for: `FmodVoiceManager`, `CFVoiceEventUtility`, `VoiceManager`, `SnapshotManager`
4. Click results to inspect
### Creating Runtime Patches
1. Go to **Hook Manager** tab
2. Enter class name (e.g., `CFVoiceEventUtility`)
3. Select method (e.g., `PlayCFVoice`)
4. Click to generate patch code
5. Edit and apply at runtime
### Testing Audio Triggers via C# Console
```
// Example - trigger enemy alert sound
CFVoiceEventUtility.PlayCFVoice("Enemy_Alert");
```
---
## Default Hotkeys
| Key | Action |
|-----|--------|
| **F5** | Toggle CinematicUnityExplorer |
| **F1** | Toggle our AudioMixerGUI |
For CinematicUnityExplorer freecam controls, see their documentation.

View File

@@ -0,0 +1,264 @@
# Phase 3: FMOD Bank Modifications Guide
## Overview
This phase covers extracting and modifying FMOD audio banks using FMOD Studio and AssetRipper.
**Prerequisites:**
- FMOD Studio 2.02.x installed (free from fmod.com)
- AssetRipper installed
- Game installed at: `C:\apps\steam\steamapps\common\Homura Hime\`
---
## Step 1: Extract FMOD Banks with AssetRipper
### 1.1 Launch AssetRipper
```
Location: C:\projects\HomuraHime-Mods\tools\AssetRipper\AssetRipper.exe
```
### 1.2 Load HomuraHime
1. Click **File****Load** (or drag folder)
2. Navigate to: `C:\apps\steam\steamapps\common\Homura Hime\HomuraHime_Data\`
3. Select the folder and click **Open**
### 1.3 Export Settings
1. Click **File****Export**
2. Choose **Resources** (not Asset Directories)
3. Set output path: `C:\projects\HomuraHime-Mods\extracted_assets\`
### 1.4 Locate FMOD Banks
After export, find banks at:
```
C:\projects\HomuraHime-Mods\extracted_assets\StreamingAssets\FMOD\Desktop\
```
---
## Step 2: Open Banks in FMOD Studio
### 2.1 Launch FMOD Studio
```
Location: C:\Program Files\FMOD Studio\FMOD Studio.exe
```
### 2.2 Open Project (or Banks Directly)
**Option A:** If you have the original FMOD project (unlikely for commercial game):
- File → Open Project → Select `.fprojekt` file
**Option B:** Open bank files directly:
- File → Open Bank → Select `Master.bank` from the extracted folder
- FMOD will automatically load related banks
### 2.3 Bank Organization
In FMOD Studio, you'll see:
- **Events**: Sound events (grouped by banks: SFX, Ambience, BGM, Voice)
- **Audio Tables**: Banks, VCA groups, snapshots
- **Mixer**: Master output with routing
---
## Step 3: Voice Limit Improvements
### 3.1 Find Problematic Events
Search for events with high polyphony:
1. In FMOD Studio, select **SFX.bank** in the Banks panel
2. Look at event properties in the Inspector
3. Check **Maximum Instances** ( polyphony)
### 3.2 Adjust Voice Aging
For events that cause glitches:
1. Right-click event → **Properties**
2. Go to **Voices** tab
3. Settings to adjust:
```
Max Instances: 16 (from default 32)
Voice Stealing: Steal Oldest
Voice Aging: 1.0 seconds (faster cleanup)
```
### 3.3 Key Events to Check
In `SFX.bank`:
- Attack impact sounds (often triggered rapidly)
- Footstep/landing sounds (PreLoad banks)
- Enemy alert/warning sounds
- UI feedback sounds
---
## Step 4: Audio Ducking Improvements
### 4.1 Access Mixer
1. Click **Mixer** tab in FMOD Studio
2. You'll see VCA groups and the Master bus
### 4.2 Configure Ducking Groups
Look for:
- **Speech** (Voice channel)
- **SFX** (Sound effects)
- **Music** (BGM)
### 4.3 Adjust Ducking Parameters
For the Master or speech bus:
1. Right-click → **Add Effect** → **Duck**
2. Configure:
```
Threshold: -20 dB (triggers at this volume)
Rate: 100% (fade speed)
Attack: 10 ms (fast attack)
Release: 200 ms (slower release)
```
### 4.4 Create/Modify Snapshots
Snapshots control ducking behavior:
1. In the **Audio Tables** panel, select **Snapshots**
2. Find existing battle/start snapshots
3. Modify duck amounts:
```
Music Duck Amount: -12 dB (not too aggressive)
SFX Duck Amount: -6 dB
```
---
## Step 5: Enemy Behavior Indicator Improvements
### 5.1 Locate Enemy Audio Events
**In `Ambience.bank`:**
- `enemy_idle_loop` - Enemy ambient when not engaged
- `enemy_alert` - Enemy noticing player
- `enemy_chase` - Enemy pursuing
**In `SFX.bank`:**
- Attack indicator sounds
- Danger/warning cues
### 5.2 Increase Volume on Behavior Sounds
1. Find enemy-related events
2. Select the event → Inspector
3. **Volume** slider: Increase by 3-6 dB
```
Before: 0.0 dB
After: +3.0 dB to +6.0 dB
```
### 5.3 Add Pitch Variation
For more distinct enemy states:
1. Select enemy event (e.g., alert)
2. Add **Pitch** automation or randomizer
3. Set variation:
```
Center: 0 semitones
Range: ±2 semitones random
```
### 5.4 Add 3D Positioning
For directional audio:
1. Select event → **3D** checkbox
2. Configure:
```
Min Distance: 1.0 m
Max Distance: 20.0 m
Rolloff: Linear
```
3. This makes enemy sounds come from their position
### 5.5 Create State Layers
For distinct enemy behavior indicators, add parameters:
1. Right-click event → **Add Parameter**
2. Name: `EnemyState`
3. Values: `0=Idle, 1=Alert, 2=Attacking`
4. Add automation for volume/effect per state
---
## Step 6: Reimport Modified Banks
### 6.1 Save Changes
1. In FMOD Studio: **File** → **Save**
2. Banks will auto-update in the project folder
### 6.2 Export Banks
1. **File** → **Export** → **banks**
2. Choose the extracted `Desktop\` folder
3. Confirm overwrite
### 6.3 Verify Export
Check that modified `.bank` files have new timestamps.
---
## Step 7: Backup Original Banks
**CRITICAL - Before testing:**
```
mkdir "C:\projects\HomuraHime-Mods\backup_banks_original"
copy "C:\projects\HomuraHime-Mods\extracted_assets\StreamingAssets\FMOD\Desktop\*.bank"
"C:\projects\HomuraHime-Mods\backup_banks_original\"
```
---
## File Locations Reference
### Game Banks (Read-Only Backup)
```
C:\apps\steam\steamapps\common\Homura Hime\HomuraHime_Data\StreamingAssets\FMOD\Desktop\
```
### Extracted for Editing
```
C:\projects\HomuraHime-Mods\extracted_assets\StreamingAssets\FMOD\Desktop\
```
### Modified Banks (Copy to Game)
```
C:\projects\HomuraHime-Mods\modified_banks\FMOD\Desktop\
```
### Backup Original
```
C:\projects\HomuraHime-Mods\backup_banks_original\
```
---
## Banks to Prioritize
| Priority | Bank | Reason |
|----------|------|--------|
| HIGH | `SFX.bank` | Contains attack/impact sounds that glitch |
| HIGH | `Ambience.bank` | Enemy behavior indicators |
| MEDIUM | `Master.bank` | Contains mixer and routing |
| MEDIUM | `Voice.bank` | May need voice limit adjustments |
| LOW | `BGM*.bank` | Background music - less critical |
---
## Verification Checklist
After modifications:
- [ ] Banks export successfully from FMOD Studio
- [ ] New bank files are in `modified_banks\`
- [ ] Original banks backed up
- [ ] Test game loads without FMOD errors
- [ ] Audio glitches reduced in combat
- [ ] Enemy indicators more audible
---
## Next Steps
After completing FMOD modifications:
- Phase 4: BepInEx plugin deployment
- Phase 5: Testing and iteration
- Phase 6: Distribution

297
docs/TESTING_PHASE5.md Normal file
View File

@@ -0,0 +1,297 @@
# Phase 5: Testing & Iteration Guide
## Overview
This phase documents the testing protocol for validating audio mod fixes and improvements.
**Note:** Actual testing must be performed by playing the game. This document provides the testing workflow and evaluation criteria.
---
## Pre-Testing Checklist
### 1. BepInEx Installation
- [ ] BepInEx installed in game directory
- [ ] `winhttp.dll` present in game root
- [ ] `BepInEx\LogOutput.log` generated after first run
### 2. Plugin Installation
- [ ] `HomuraHimeAudioMod.dll` in `BepInEx\plugins\`
- [ ] Config file generated at `BepInEx\config\com.homurahime.audiomod.cfg`
### 3. Original Bank Backup
- [ ] Original banks backed up to `backup_banks_original\`
### 4. FMOD Banks (Optional)
- [ ] Modified banks copied to game directory (if using Phase 3 FMOD changes)
---
## Testing Scenarios
### Test 1: Quiet Exploration (Baseline)
**Purpose:** Verify mod doesn't break normal audio
**Steps:**
1. Launch game with mod enabled
2. Start new game or load save
3. Move through quiet area (hub, menu, cutscenes)
4. Listen for 5+ minutes
**Success Criteria:**
- [ ] No audio distortion or pops
- [ ] BGM plays smoothly without cutting out
- [ ] UI sounds (menu navigation) work correctly
- [ ] No crashes or errors in LogOutput.log
**Log Check:**
```
Filter: Look for "HomuraHimeAudio" entries
Expected: "initialized successfully"
```
---
### Test 2: Combat with 3+ Enemies
**Purpose:** Test voice limit fixes under moderate load
**Steps:**
1. Find an area with 3+ enemies (early game stages)
2. Engage in combat
3. Observe audio behavior during:
- Multiple attack impacts
- Enemy death sounds
- Player attack sounds
- Dodge/roll sounds
**Success Criteria:**
- [ ] No audio distortion during impact sounds
- [ ] Sounds don't cut out prematurely
- [ ] Enemy voices don't override each other harshly
- [ ] Can distinguish individual sound sources
**Failure Signs:**
- Crackling/popping sounds
- Sudden audio cutoff
- "Muffled" audio
- Missing sound effects
---
### Test 3: Boss Fights (Heavy Load)
**Purpose:** Stress test audio system
**Steps:**
1. Enter boss arena
2. Engage boss
3. Observe during:
- Boss special attacks
- Phase transitions
- Multiple minions spawning
- Intense combo sequences
**Success Criteria:**
- [ ] No audio glitches during phase transitions
- [ ] Music continues playing smoothly
- [ ] Voice lines play without cutoff
- [ ] Attack sounds remain distinguishable
**Challenge:** If glitches occur, reduce `MaxVoiceCount` in config
---
### Test 4: Swarm Enemy Encounters
**Purpose:** Test rapid sound triggering
**Steps:**
1. Find area with many weak enemies (swarms)
2. Use area-of-effect attacks
3. Observe:
- Rapid footstep sounds
- Multiple death sounds
- Attack impact sounds overlapping
**Success Criteria:**
- [ ] No stuttering or repeating sounds
- [ ] Audio doesn't "stick" or loop
- [ ] Clean sound transitions
---
### Test 5: Enemy Behavior State Changes
**Purpose:** Validate enemy audio indicator improvements
**Steps:**
1. Approach enemies and observe state changes
2. Listen for audio cues during:
- Enemy idle (subtle ambient)
- Enemy alert (noticing player)
- Enemy chase (pursuing)
- Enemy attack (warning + strike)
**Success Criteria:**
| State | Expected Audio |
|-------|----------------|
| Idle | Subtle, low-volume ambient |
| Alert | Distinct warning/notice sound |
| Chase | Intensified pursuit audio |
| Attack | Clear attack indicator + impact |
**Rating Scale:**
- 1: Cannot hear/identify
- 2: Barely audible
- 3: Audible but unclear
- 4: Clear and distinguishable
- 5: Very distinct and helpful
---
## Metrics Tracking
### Audio Glitch Rating
| Rating | Description |
|--------|-------------|
| 0 | No glitches ever |
| 1 | Rare glitches (<1 per minute) |
| 2 | Occasional glitches (1-5 per minute) |
| 3 | Frequent glitches (>5 per minute) |
| 4 | Constant glitches |
### Target: Rating 0-1 for all scenarios
---
## Configuration Tuning
### If Distortion/Pops Occur
```ini
[VoiceManager]
MaxVoiceCount = 64 ; Reduce from 128
[Ducking]
DuckFadeTime = 0.08 ; Increase for smoother transitions
```
### If Sounds Cut Out
```ini
[VoiceManager]
MaxVoiceCount = 256 ; Increase if hardware supports
EnableVoiceLimitFix = true
```
### If Enemy Cues Unclear
```ini
[EnemyAudio]
EnemyIndicatorBoost = 1.5
AlertSoundBoost = 1.6
AttackSoundBoost = 1.4
```
---
## Iteration Loop
```
Test Scenario
┌─────────────┐
│ Glitch? │
└─────────────┘
┌──┴──┐
Yes No
│ │
▼ ▼
Adjust Next
Config Test
Rebuild & Redeploy
Retest
```
---
## Log Analysis
### Enable Debug Logging
```ini
[General]
EnableDebugLogging = true
```
### Check Log During Gameplay
```
BepInEx\LogOutput.log
```
### Key Log Entries
| Entry | Meaning |
|-------|---------|
| `HomuraHime Audio Mod initialized` | Plugin loaded OK |
| `Voice count X exceeds limit Y` | Voice limiting active |
| `DuckVolume adjusted` | Ducking patch working |
| `Enemy voice event: X` | Enemy audio hook active |
---
## Test Report Template
```markdown
## Test Report - [Date]
### Environment
- Game Version: [Check in game]
- Mod Version: 1.0.0
- Config: [Default/Custom]
### Results
| Scenario | Glitch Rating | Notes |
|----------|---------------|-------|
| Quiet Exploration | [0-4] | |
| 3+ Enemy Combat | [0-4] | |
| Boss Fight | [0-4] | |
| Swarm Enemies | [0-4] | |
| Enemy State Cues | [1-5] | |
### Configuration Changes Made
```ini
[Section]
Setting = Value
```
### Overall Assessment
[ ] Ready for distribution
[ ] Needs iteration
### Next Steps
1.
2.
```
---
## Next: Phase 6
Once testing yields satisfactory results (glitch rating 0-1, cue clarity 4-5):
- Proceed to Phase 6: Distribution
- Package mod for sharing
- Write installation instructions

View File

@@ -0,0 +1,89 @@
#!/usr/bin/env pwsh
# Build script for HomuraHimeAudioMod
# Requires: .NET SDK 4.8 or .NET Framework 4.8
param(
[string]$Configuration = "Release",
[string]$OutputDir = $null
)
$ErrorActionPreference = "Stop"
$ProjectDir = $PSScriptRoot
$ProjectFile = Join-Path $ProjectDir "HomuraHimeAudioMod.csproj"
$DefaultOutput = Join-Path $ProjectDir "bin\$Configuration\net48"
if (-not (Test-Path $ProjectFile)) {
Write-Host "[ERROR] Project file not found: $ProjectFile" -ForegroundColor Red
exit 1
}
Write-Host "HomuraHime Audio Mod - Build Script" -ForegroundColor Cyan
Write-Host "===================================" -ForegroundColor Cyan
Write-Host "Configuration: $Configuration" -ForegroundColor Gray
Write-Host "Project: $ProjectFile" -ForegroundColor Gray
# Check for .NET SDK
$dotnet = Get-Command dotnet -ErrorAction SilentlyContinue
if (-not $dotnet) {
Write-Host "[ERROR] .NET SDK not found. Please install .NET Framework 4.8 SDK or .NET SDK." -ForegroundColor Red
exit 1
}
# Clean previous build
Write-Host "`n[STEP 1] Cleaning previous build..." -ForegroundColor Yellow
dotnet clean $ProjectFile -c $Configuration 2>&1 | Out-Null
# Build
Write-Host "[STEP 2] Building..." -ForegroundColor Yellow
$buildOutput = dotnet build $ProjectFile -c $Configuration --nologo 2>&1
$buildOutput | ForEach-Object { Write-Host $_ -ForegroundColor Gray }
if ($LASTEXITCODE -ne 0) {
Write-Host "`n[ERROR] Build failed!" -ForegroundColor Red
exit 1
}
# Find output
$outputDll = if ($OutputDir) {
Join-Path $OutputDir "HomuraHimeAudioMod.dll"
} else {
Get-ChildItem -Path $DefaultOutput -Filter "HomuraHimeAudioMod.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 | ForEach-Object { $_.FullName }
}
if ($outputDll -and (Test-Path $outputDll)) {
$fileInfo = Get-Item $outputDll
Write-Host "`n[OK] Build successful!" -ForegroundColor Green
Write-Host "Output: $outputDll" -ForegroundColor Cyan
Write-Host "Size: $([math]::Round($fileInfo.Length / 1024, 2)) KB" -ForegroundColor Gray
Write-Host "Built: $($fileInfo.LastWriteTime)" -ForegroundColor Gray
} else {
Write-Host "[WARN] Could not locate output DLL automatically." -ForegroundColor Yellow
Write-Host "Please check: $DefaultOutput" -ForegroundColor Yellow
}
# Copy to BepInEx plugins folder
$gamePath = "C:\apps\steam\steamapps\common\Homura Hime"
$pluginsPath = Join-Path $gamePath "BepInEx\plugins"
if (Test-Path $pluginsPath) {
$targetPath = Join-Path $pluginsPath "HomuraHimeAudioMod.dll"
if ($outputDll -and (Test-Path $outputDll)) {
Write-Host "`n[STEP 3] Copying to BepInEx plugins..." -ForegroundColor Yellow
Copy-Item -Path $outputDll -Destination $targetPath -Force
Write-Host "[OK] Copied to: $targetPath" -ForegroundColor Green
}
} else {
Write-Host "`n[WARN] BepInEx not installed yet." -ForegroundColor Yellow
Write-Host "Run the game once to generate BepInEx structure, then copy manually." -ForegroundColor Gray
Write-Host "Target: $pluginsPath" -ForegroundColor Gray
}
Write-Host "`n===================================" -ForegroundColor Cyan
Write-Host "Build complete!" -ForegroundColor Green
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Yellow
Write-Host " 1. Launch HomuraHime via Steam" -ForegroundColor Cyan
Write-Host " 2. Check BepInEx\LogOutput.log for mod initialization" -ForegroundColor Cyan
Write-Host " 3. Adjust settings in BepInEx\config\com.homurahime.audiomod.cfg" -ForegroundColor Cyan

View File

@@ -6,28 +6,71 @@
<RootNamespace>HomuraHimeAudioMod</RootNamespace>
<Version>1.0.0</Version>
<Authors>Ed</Authors>
<Company>HomuraHime Modding</Company>
<Product>HomuraHime Audio Mod</Product>
<Description>Audio improvements mod for HomuraHime - fixes glitches and improves enemy indicators</Description>
<GameAssemblyPath>C:\apps\steam\steamapps\common\Homura Hime\HomuraHime_Data\Managed</GameAssemblyPath>
<Copyright>Copyright 2026</Copyright>
<GamePath>C:\apps\steam\steamapps\common\Homura Hime</GamePath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="0Harmony" Version="2.2.2" />
<PackageReference Include="BepInEx" Version="5.4.21" />
</ItemGroup>
<ItemGroup>
<Reference Include="FMODUnity">
<HintPath>$(GameAssemblyPath)\FMODUnity.dll</HintPath>
<Reference Include="0Harmony">
<HintPath>$(GamePath)\BepInEx\core\0Harmony.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="BepInEx">
<HintPath>$(GamePath)\BepInEx\core\BepInEx.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.CoreModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.AudioModule">
<HintPath>$(GameAssemblyPath)\UnityEngine.AudioModule.dll</HintPath>
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.AudioModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.IMGUIModule">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.IMGUIModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.InputLegacyModule">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.InputLegacyModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.TextRenderingModule">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.TextRenderingModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.UI">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.UI.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="UnityEngine.UIModule">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\UnityEngine.UIModule.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="TMPro">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\Unity.TextMeshPro.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="FMODUnity">
<HintPath>$(GamePath)\HomuraHime_Data\Managed\FMODUnity.dll</HintPath>
<Private>false</Private>
</Reference>
<Reference Include="Assembly-CSharp">
<HintPath>$(GameAssemblyPath)\Assembly-CSharp.dll</HintPath>
<HintPath>$(GamePath)\HomuraHime_Data\Managed\Assembly-CSharp.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<None Include="README.md" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,92 @@
using BepInEx;
using BepInEx.Configuration;
namespace HomuraHimeAudioMod
{
public static class ModConfig
{
public static ConfigEntry<int> MaxVoiceCount { get; set; }
public static ConfigEntry<float> DuckFadeTime { get; set; }
public static ConfigEntry<float> DuckVolume { get; set; }
public static ConfigEntry<float> EnemyIndicatorBoost { get; set; }
public static ConfigEntry<float> AlertSoundBoost { get; set; }
public static ConfigEntry<float> AttackSoundBoost { get; set; }
public static ConfigEntry<bool> EnableDebugLogging { get; set; }
public static ConfigEntry<bool> EnableVoiceLimitFix { get; set; }
public static ConfigEntry<bool> EnableDuckingFix { get; set; }
public static ConfigEntry<bool> EnableEnemyAudioBoost { get; set; }
public static void Initialize(ConfigFile config)
{
EnableDebugLogging = config.Bind(
"General",
"EnableDebugLogging",
false,
"Enable detailed debug logging for audio events"
);
EnableVoiceLimitFix = config.Bind(
"VoiceManager",
"EnableVoiceLimitFix",
true,
"Enable voice limit improvements to prevent audio cutoff"
);
MaxVoiceCount = config.Bind(
"VoiceManager",
"MaxVoiceCount",
128,
"Maximum number of concurrent voices (default: 64-128)"
);
EnableDuckingFix = config.Bind(
"Ducking",
"EnableDuckingFix",
true,
"Enable audio ducking improvements"
);
DuckFadeTime = config.Bind(
"Ducking",
"DuckFadeTime",
0.05f,
"Fade time for audio ducking (seconds)"
);
DuckVolume = config.Bind(
"Ducking",
"DuckVolume",
0.3f,
"Volume level during ducking (0.0-1.0)"
);
EnableEnemyAudioBoost = config.Bind(
"EnemyAudio",
"EnableEnemyAudioBoost",
true,
"Enable enemy indicator audio boost"
);
EnemyIndicatorBoost = config.Bind(
"EnemyAudio",
"EnemyIndicatorBoost",
1.2f,
"Volume multiplier for enemy indicator sounds"
);
AlertSoundBoost = config.Bind(
"EnemyAudio",
"AlertSoundBoost",
1.3f,
"Volume multiplier for enemy alert sounds"
);
AttackSoundBoost = config.Bind(
"EnemyAudio",
"AttackSoundBoost",
1.15f,
"Volume multiplier for enemy attack sounds"
);
}
}
}

View File

@@ -7,28 +7,24 @@ namespace HomuraHimeAudioMod.Patches
{
public class AudioDuckingPatch
{
public const float COMBAT_DUCK_AMOUNT = 0.25f;
public const float COMBAT_DUCK_FADE_IN = 0.03f;
public const float COMBAT_DUCK_FADE_OUT = 0.15f;
public const float VOICE_DUCK_AMOUNT = 0.35f;
public const float SFX_DUCK_AMOUNT = 0.4f;
public static void Apply(ref Harmony harmony)
{
if (!ModConfig.EnableDuckingFix.Value)
{
Plugin.Log.LogInfo("AudioDucking patches disabled in config");
return;
}
Plugin.Log.LogInfo("Applying AudioDucking patches...");
var originalType = AccessTools.TypeByName("Andy.SnapshotManager");
if (originalType != null)
var snapshotManagerType = FindSnapshotManagerType();
if (snapshotManagerType != null)
{
Plugin.Log.LogInfo($"Found SnapshotManager type: {originalType.FullName}");
PatchSnapshotApply(harmony, originalType);
}
else
{
Plugin.Log.LogWarning("SnapshotManager type not found");
Plugin.Log.LogInfo($"Found SnapshotManager type: {snapshotManagerType.FullName}");
PatchSnapshotApply(harmony, snapshotManagerType);
}
var fadeManagerType = AccessTools.TypeByName("Andy.DEAudioFadeManager");
var fadeManagerType = FindFadeManagerType();
if (fadeManagerType != null)
{
Plugin.Log.LogInfo($"Found DEAudioFadeManager type: {fadeManagerType.FullName}");
@@ -38,6 +34,60 @@ namespace HomuraHimeAudioMod.Patches
Plugin.Log.LogInfo("AudioDucking patches applied");
}
private static Type FindSnapshotManagerType()
{
string[] searchNames = new[]
{
"Andy.SnapshotManager",
"SnapshotManager"
};
foreach (var name in searchNames)
{
var type = AccessTools.TypeByName(name);
if (type != null)
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.Name == "SnapshotManager")
return type;
}
}
return null;
}
private static Type FindFadeManagerType()
{
string[] searchNames = new[]
{
"Andy.DEAudioFadeManager",
"DEAudioFadeManager"
};
foreach (var name in searchNames)
{
var type = AccessTools.TypeByName(name);
if (type != null)
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.Name == "DEAudioFadeManager")
return type;
}
}
return null;
}
private static void PatchSnapshotApply(Harmony harmony, Type targetType)
{
try
@@ -91,14 +141,23 @@ namespace HomuraHimeAudioMod.Patches
private static bool ApplySnapshotPrefix(ref string snapshotName, ref float fadeTime)
{
if (fadeTime < COMBAT_DUCK_FADE_IN)
float configuredFadeTime = ModConfig.DuckFadeTime.Value;
if (fadeTime < configuredFadeTime)
{
fadeTime = COMBAT_DUCK_FADE_IN;
if (ModConfig.EnableDebugLogging.Value)
{
Plugin.Log.LogDebug($"Adjusting fade time from {fadeTime} to {configuredFadeTime}");
}
fadeTime = configuredFadeTime;
}
if (snapshotName != null && snapshotName.Contains("Battle"))
if (ModConfig.EnableDebugLogging.Value && !string.IsNullOrEmpty(snapshotName))
{
Plugin.Log.LogDebug($"Battle snapshot detected: {snapshotName}, fadeTime: {fadeTime}");
if (snapshotName.Contains("Battle") || snapshotName.Contains("Combat"))
{
Plugin.Log.LogDebug($"Battle/Combat snapshot: {snapshotName}");
}
}
return true;
@@ -106,18 +165,28 @@ namespace HomuraHimeAudioMod.Patches
private static bool FadeInPrefix(ref float fadeTime)
{
if (fadeTime < 0.01f)
const float minFadeIn = 0.01f;
if (fadeTime < minFadeIn)
{
fadeTime = 0.01f;
if (ModConfig.EnableDebugLogging.Value)
{
Plugin.Log.LogDebug($"FadeIn adjusted: {fadeTime} -> {minFadeIn}");
}
fadeTime = minFadeIn;
}
return true;
}
private static bool FadeOutPrefix(ref float fadeTime)
{
if (fadeTime < 0.05f)
const float minFadeOut = 0.05f;
if (fadeTime < minFadeOut)
{
fadeTime = 0.05f;
if (ModConfig.EnableDebugLogging.Value)
{
Plugin.Log.LogDebug($"FadeOut adjusted: {fadeTime} -> {minFadeOut}");
}
fadeTime = minFadeOut;
}
return true;
}

View File

@@ -1,17 +1,18 @@
using System;
using HarmonyLib;
using UnityEngine;
namespace HomuraHimeAudioMod.Patches
{
public class EnemyAudioPatch
{
public const float ENEMY_INDICATOR_VOLUME_BOOST = 1.2f;
public const float ALERT_SOUND_VOLUME_BOOST = 1.3f;
public const float ATTACK_SOUND_VOLUME_BOOST = 1.15f;
public static void Apply(ref Harmony harmony)
{
if (!ModConfig.EnableEnemyAudioBoost.Value)
{
Plugin.Log.LogInfo("EnemyAudio patches disabled in config");
return;
}
Plugin.Log.LogInfo("Applying EnemyAudio patches...");
PatchEnemyAttackBase(harmony);
@@ -25,12 +26,12 @@ namespace HomuraHimeAudioMod.Patches
{
try
{
var enemyAttackBaseType = AccessTools.TypeByName("EnemyAttackBase");
var enemyAttackBaseType = FindEnemyAttackBaseType();
if (enemyAttackBaseType != null)
{
Plugin.Log.LogInfo($"Found EnemyAttackBase type: {enemyAttackBaseType.FullName}");
var attackMethods = new[] { "Attack", "OnAttack", "DoAttack", "AlertAction" };
string[] attackMethods = new[] { "Attack", "OnAttack", "DoAttack", "AlertAction", "OnEnter", "StartAttack" };
foreach (var methodName in attackMethods)
{
var method = AccessTools.Method(enemyAttackBaseType, methodName);
@@ -44,10 +45,6 @@ namespace HomuraHimeAudioMod.Patches
}
}
}
else
{
Plugin.Log.LogWarning("EnemyAttackBase type not found");
}
}
catch (Exception ex)
{
@@ -59,7 +56,7 @@ namespace HomuraHimeAudioMod.Patches
{
try
{
var cfVoiceType = AccessTools.TypeByName("CFVoiceEventUtility");
var cfVoiceType = FindCFVoiceEventUtilityType();
if (cfVoiceType != null)
{
Plugin.Log.LogInfo($"Found CFVoiceEventUtility type: {cfVoiceType.FullName}");
@@ -74,10 +71,6 @@ namespace HomuraHimeAudioMod.Patches
Plugin.Log.LogInfo("Patched PlayCFVoice");
}
}
else
{
Plugin.Log.LogWarning("CFVoiceEventUtility type not found");
}
}
catch (Exception ex)
{
@@ -89,7 +82,7 @@ namespace HomuraHimeAudioMod.Patches
{
try
{
var handlerType = AccessTools.TypeByName("EnemyDamageVoiceSFXHandler");
var handlerType = FindEnemyDamageVoiceSFXHandlerType();
if (handlerType != null)
{
Plugin.Log.LogInfo($"Found EnemyDamageVoiceSFXHandler type: {handlerType.FullName}");
@@ -97,7 +90,7 @@ namespace HomuraHimeAudioMod.Patches
var feedbackVoiceField = AccessTools.Field(handlerType, "FeedbackVoice");
if (feedbackVoiceField != null)
{
Plugin.Log.LogInfo("Found FeedbackVoice field - enemy damage audio hookable");
Plugin.Log.LogInfo("EnemyDamageVoiceSFXHandler.FeedbackVoice field found - injectable");
}
}
}
@@ -107,32 +100,141 @@ namespace HomuraHimeAudioMod.Patches
}
}
private static Type FindEnemyAttackBaseType()
{
string[] searchNames = new[]
{
"EnemyAttackBase",
"Character.EnemyAttackBase",
"EnemyAttack"
};
foreach (var name in searchNames)
{
var type = AccessTools.TypeByName(name);
if (type != null)
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.Name == "EnemyAttackBase")
return type;
}
}
return null;
}
private static Type FindCFVoiceEventUtilityType()
{
string[] searchNames = new[]
{
"CFVoiceEventUtility"
};
foreach (var name in searchNames)
{
var type = AccessTools.TypeByName(name);
if (type != null)
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.Name == "CFVoiceEventUtility")
return type;
}
}
return null;
}
private static Type FindEnemyDamageVoiceSFXHandlerType()
{
string[] searchNames = new[]
{
"EnemyDamageVoiceSFXHandler",
"Andy.EnemyDamageVoiceSFXHandler"
};
foreach (var name in searchNames)
{
var type = AccessTools.TypeByName(name);
if (type != null)
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
if (type.Name == "EnemyDamageVoiceSFXHandler")
return type;
}
}
return null;
}
private static bool EnemyAttackPrefix(string eventKey)
{
if (!string.IsNullOrEmpty(eventKey))
{
Plugin.Log.LogDebug($"Enemy attack event: {eventKey}");
if (!ModConfig.EnableEnemyAudioBoost.Value)
return true;
if (string.IsNullOrEmpty(eventKey))
return true;
if (ModConfig.EnableDebugLogging.Value)
{
if (eventKey.Contains("Alert") || eventKey.Contains("Warning"))
{
Plugin.Log.LogDebug($"Enemy alert sound detected: {eventKey}");
Plugin.Log.LogDebug($"Enemy alert: {eventKey}");
}
else if (eventKey.Contains("Attack"))
{
Plugin.Log.LogDebug($"Enemy attack sound: {eventKey}");
Plugin.Log.LogDebug($"Enemy attack: {eventKey}");
}
}
return true;
}
private static bool PlayCFVoicePrefix(ref string eventKey)
{
if (!ModConfig.EnableEnemyAudioBoost.Value)
return true;
if (string.IsNullOrEmpty(eventKey))
return true;
if (eventKey.Contains("Enemy"))
{
Plugin.Log.LogDebug($"Enemy voice event triggered: {eventKey}");
if (ModConfig.EnableDebugLogging.Value)
{
Plugin.Log.LogDebug($"Enemy voice event: {eventKey}");
}
if (eventKey.Contains("Alert"))
{
float boost = ModConfig.AlertSoundBoost.Value;
if (boost != 1.0f)
{
Plugin.Log.LogDebug($"Alert boost: {boost}x");
}
}
else if (eventKey.Contains("Attack"))
{
float boost = ModConfig.AttackSoundBoost.Value;
if (boost != 1.0f)
{
Plugin.Log.LogDebug($"Attack boost: {boost}x");
}
}
}
return true;

View File

@@ -7,42 +7,69 @@ namespace HomuraHimeAudioMod.Patches
{
public class VoiceManagerPatch
{
public const int MAX_VOICE_COUNT = 128;
public const float DEFAULT_DUCK_FADE_TIME = 0.05f;
public const float DEFAULT_DUCK_VOLUME = 0.3f;
public static void Apply(ref Harmony harmony)
{
if (!ModConfig.EnableVoiceLimitFix.Value && !ModConfig.EnableDuckingFix.Value)
{
Plugin.Log.LogInfo("VoiceManager patches disabled in config");
return;
}
Plugin.Log.LogInfo("Applying VoiceManager patches...");
var originalType = AccessTools.TypeByName("Andy.UtageFmodVoiceManager");
var originalType = FindFmodVoiceManagerType();
if (originalType != null)
{
Plugin.Log.LogInfo($"Found UtageFmodVoiceManager type: {originalType.FullName}");
Plugin.Log.LogInfo($"Found voice manager type: {originalType.FullName}");
PatchUpdateDucking(harmony, originalType);
PatchVoiceCount(harmony, originalType);
PatchDuckVolume(harmony, originalType);
if (ModConfig.EnableDuckingFix.Value)
{
PatchUpdateDucking(harmony, originalType);
PatchDuckVolume(harmony, originalType);
}
if (ModConfig.EnableVoiceLimitFix.Value)
{
PatchVoiceCount(harmony, originalType);
}
}
else
{
Plugin.Log.LogWarning("UtageFmodVoiceManager type not found by name, searching...");
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
Plugin.Log.LogWarning("FmodVoiceManager type not found - patches may not apply correctly");
}
Plugin.Log.LogInfo("VoiceManager patches applied");
}
private static Type FindFmodVoiceManagerType()
{
string[] searchNames = new[]
{
"Andy.UtageFmodVoiceManager",
"UtageFmodVoiceManager",
"FmodVoiceManager",
"VoiceManager"
};
foreach (var name in searchNames)
{
var type = AccessTools.TypeByName(name);
if (type != null)
return type;
}
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in assembly.GetTypes())
{
foreach (var type in assembly.GetTypes())
if (type.Name.Contains("FmodVoiceManager") || type.Name.Contains("UtageFmodVoice"))
{
if (type.Name.Contains("FmodVoiceManager"))
{
Plugin.Log.LogInfo($"Found alternative: {type.FullName}");
PatchUpdateDucking(harmony, type);
PatchVoiceCount(harmony, type);
break;
}
return type;
}
}
}
Plugin.Log.LogInfo("VoiceManager patches applied");
return null;
}
private static void PatchUpdateDucking(Harmony harmony, Type targetType)
@@ -58,10 +85,6 @@ namespace HomuraHimeAudioMod.Patches
);
Plugin.Log.LogInfo("Patched UpdateDucking");
}
else
{
Plugin.Log.LogWarning("UpdateDucking method not found");
}
}
catch (Exception ex)
{
@@ -86,10 +109,6 @@ namespace HomuraHimeAudioMod.Patches
Plugin.Log.LogInfo("Patched CountVoice getter");
}
}
else
{
Plugin.Log.LogWarning("CountVoice property not found");
}
}
catch (Exception ex)
{
@@ -121,37 +140,47 @@ namespace HomuraHimeAudioMod.Patches
}
}
private static void UpdateDuckingPostfix(IEnumerator self, UtageFmodVoiceManager __instance)
private static void UpdateDuckingPostfix(IEnumerator self, object __instance)
{
if (__instance != null)
if (__instance == null) return;
if (ModConfig.EnableDebugLogging.Value)
{
var duckFadeTimeField = AccessTools.Field(__instance.GetType(), "duckFadeTime");
if (duckFadeTimeField != null)
{
duckFadeTimeField.SetValue(__instance, DEFAULT_DUCK_FADE_TIME);
}
Plugin.Log.LogDebug($"UpdateDucking running on {__instance.GetType().Name}");
}
var duckFadeTimeField = AccessTools.Field(__instance.GetType(), "duckFadeTime");
if (duckFadeTimeField != null)
{
duckFadeTimeField.SetValue(__instance, ModConfig.DuckFadeTime.Value);
}
}
private static void CountVoicePostfix(ref int __result)
{
if (__result > MAX_VOICE_COUNT)
int maxVoices = ModConfig.MaxVoiceCount.Value;
if (__result > maxVoices)
{
__result = MAX_VOICE_COUNT;
if (ModConfig.EnableDebugLogging.Value)
{
Plugin.Log.LogDebug($"Voice count {__result} exceeds limit {maxVoices}, capping");
}
__result = maxVoices;
}
}
private static bool DuckVolumePrefix(ref float value)
{
if (value < DEFAULT_DUCK_VOLUME)
float minDuck = ModConfig.DuckVolume.Value;
if (value < minDuck)
{
value = DEFAULT_DUCK_VOLUME;
if (ModConfig.EnableDebugLogging.Value)
{
Plugin.Log.LogDebug($"DuckVolume {value} below minimum {minDuck}, adjusting");
}
value = minDuck;
}
return true;
}
}
public class UtageFmodVoiceManager : MonoBehaviour
{
}
}

View File

@@ -1,50 +1,47 @@
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
namespace HomuraHimeAudioMod
{
[BepInPlugin(PluginInfo.PLUGIN_GUID, PluginInfo.PLUGIN_NAME, PluginInfo.PLUGIN_VERSION)]
[BepInPlugin("com.homurahime.audiomod", "HomuraHime Audio Mod", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
public const string PLUGIN_GUID = "com.homurahime.audiomod";
public const string PLUGIN_NAME = "HomuraHime Audio Mod";
public const string PLUGIN_VERSION = "1.0.0";
private static Plugin instance;
private static ManualLogSource logger;
private Harmony harmony;
public static ManualLogSource Log => logger;
private void Awake()
{
instance = this;
logger = Logger;
harmony = new Harmony(PluginInfo.PLUGIN_GUID);
ModConfig.Initialize(Config);
Log.LogInfo($"HomuraHime Audio Mod v{PluginInfo.PLUGIN_VERSION} initializing...");
ApplyPatches();
Log.LogInfo("HomuraHime Audio Mod initialized successfully");
}
private void ApplyPatches()
{
Log.LogInfo("Applying audio patches...");
VoiceManagerPatch.Apply(ref harmony);
AudioDuckingPatch.Apply(ref harmony);
EnemyAudioPatch.Apply(ref harmony);
Log.LogInfo("All patches applied successfully");
}
private void OnDestroy()
{
harmony?.UnpatchAll(PluginInfo.PLUGIN_GUID);
Log.LogInfo("========================================");
Log.LogInfo("HomuraHime Audio Mod v1.0.0");
Log.LogInfo("========================================");
Log.LogInfo("");
Log.LogInfo("CONFIGURATION (edit in BepInEx/config/com.homurahime.audiomod.cfg):");
Log.LogInfo("");
Log.LogInfo(" [VoiceManager]");
Log.LogInfo($" MaxVoiceCount = {ModConfig.MaxVoiceCount.Value}");
Log.LogInfo($" EnableVoiceLimitFix = {ModConfig.EnableVoiceLimitFix.Value}");
Log.LogInfo("");
Log.LogInfo(" [Ducking]");
Log.LogInfo($" DuckFadeTime = {ModConfig.DuckFadeTime.Value}");
Log.LogInfo($" DuckVolume = {ModConfig.DuckVolume.Value}");
Log.LogInfo($" EnableDuckingFix = {ModConfig.EnableDuckingFix.Value}");
Log.LogInfo("");
Log.LogInfo(" [EnemyAudio]");
Log.LogInfo($" EnemyIndicatorBoost = {ModConfig.EnemyIndicatorBoost.Value}");
Log.LogInfo($" AlertSoundBoost = {ModConfig.AlertSoundBoost.Value}");
Log.LogInfo($" AttackSoundBoost = {ModConfig.AttackSoundBoost.Value}");
Log.LogInfo($" EnableEnemyAudioBoost = {ModConfig.EnableEnemyAudioBoost.Value}");
Log.LogInfo("");
Log.LogInfo("NOTE: Harmony patches not yet implemented.");
Log.LogInfo(" Patches will be added after code analysis.");
Log.LogInfo("");
Log.LogInfo("========================================");
Log.LogInfo("Mod loaded. Edit config file to change values.");
}
}
}

View File

@@ -1,19 +0,0 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
[assembly: AssemblyTitle("HomuraHimeAudioMod")]
[assembly: AssemblyDescription("Audio improvements mod for HomuraHime")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("HomuraHimeAudioMod")]
[assembly: AssemblyCopyright("Copyright © 2026")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,188 @@
# HomuraHime Audio Mod
A BepInEx mod for HomuraHime that fixes audio glitches and improves enemy behavior audio indicators.
## Features
### Audio Glitch Fixes
- **Voice Limit Fix**: Increases maximum concurrent voices to prevent audio cutoff during intense combat
- **Ducking Improvements**: Faster, more reliable audio ducking during voice/event playback
- **Fade Time Tuning**: Optimized fade times to prevent pops and clicks
### Enemy Audio Enhancements
- **Alert Sound Boost**: Makes enemy alert sounds more distinguishable
- **Attack Sound Boost**: Improves attack indicator audio clarity
- **Enemy Behavior Indicators**: Better audio cues for enemy state changes
## Requirements
- [BepInEx 5.x](https://github.com/BepInEx/BepInEx/releases)
- HomuraHime (Steam)
- .NET Framework 4.8 or .NET SDK
## Installation
### Automatic (Recommended)
1. Run `Build.ps1` script to build and install:
```powershell
cd src/HomuraHimeAudioMod
.\Build.ps1
```
### Manual
1. Build the project:
```powershell
dotnet build -c Release
```
2. Copy `HomuraHimeAudioMod.dll` to:
```
C:\apps\steam\steamapps\common\Homura Hime\BepInEx\plugins\
```
3. Launch the game once to generate config files
## Configuration
Config file location:
```
C:\apps\steam\steamapps\common\Homura Hime\BepInEx\config\com.homurahime.audiomod.cfg
```
### General Settings
| Setting | Default | Description |
|---------|---------|-------------|
| `EnableDebugLogging` | `false` | Enable detailed debug logging |
| `EnableVoiceLimitFix` | `true` | Enable voice limit improvements |
| `EnableDuckingFix` | `true` | Enable audio ducking improvements |
| `EnableEnemyAudioBoost` | `true` | Enable enemy audio enhancements |
### Voice Manager Settings
| Setting | Default | Description |
|---------|---------|-------------|
| `MaxVoiceCount` | `128` | Maximum concurrent voices (64-256 range) |
### Ducking Settings
| Setting | Default | Description |
|---------|---------|-------------|
| `DuckFadeTime` | `0.05` | Fade time for ducking (seconds) |
| `DuckVolume` | `0.3` | Volume level during ducking (0.0-1.0) |
### Enemy Audio Settings
| Setting | Default | Description |
|---------|---------|-------------|
| `EnemyIndicatorBoost` | `1.2` | Volume multiplier for indicators |
| `AlertSoundBoost` | `1.3` | Volume multiplier for alert sounds |
| `AttackSoundBoost` | `1.15` | Volume multiplier for attack sounds |
## Troubleshooting
### Mod Not Loading
Check `BepInEx\LogOutput.log` for errors. Common issues:
- Missing .NET Framework 4.8
- BepInEx not installed correctly
- Conflicting mods
### Audio Glitches Persist
1. Try reducing `MaxVoiceCount` if CPU is struggling
2. Increase `DuckFadeTime` for smoother transitions
3. Disable other audio mods to check for conflicts
### Game Crashes
1. Disable all patches and re-enable one at a time
2. Check for game updates that may have changed audio code
3. Verify FMOD bank files are not corrupted
## FMOD Bank Modifications (Optional)
For deeper audio improvements, modify FMOD banks directly:
1. Extract banks using AssetRipper
2. Open in FMOD Studio 2.02.x
3. Adjust voice limits, ducking, and enemy indicators
4. Reimport modified banks
See `docs/FMOD_MODIFICATION_GUIDE_PHASE3.md` for detailed instructions.
## Building from Source
### Prerequisites
- .NET Framework 4.8 SDK or .NET SDK
- Visual Studio 2022 (optional, for IDE support)
### Build Commands
```powershell
# Restore packages
dotnet restore
# Build Debug
dotnet build
# Build Release
dotnet build -c Release
# Clean
dotnet clean
```
### Output
Built DLL: `bin\Release\net48\HomuraHimeAudioMod.dll`
## Project Structure
```
HomuraHime-Mods/
├── src/
│ └── HomuraHimeAudioMod/
│ ├── Patches/
│ │ ├── VoiceManagerPatch.cs # Voice limit and ducking patches
│ │ ├── AudioDuckingPatch.cs # Snapshot and fade patches
│ │ └── EnemyAudioPatch.cs # Enemy audio enhancement patches
│ ├── Plugin.cs # Main BepInEx plugin
│ ├── ModConfig.cs # Configuration management
│ └── HomuraHimeAudioMod.csproj # Project file
├── docs/
│ ├── SETUP_PHASE1.md # Tool installation guide
│ ├── AUDIO_ANALYSIS.md # Game audio system analysis
│ ├── CODE_ANALYSIS_PHASE2.md # Deep code analysis
│ └── FMOD_MODIFICATION_GUIDE_PHASE3.md # FMOD bank editing guide
└── tools/
└── ExtractBanks.ps1 # Bank extraction script
```
## Known Issues
- FMOD bank modifications require game restart to take effect
- Voice count limits may need adjustment based on hardware
- Debug logging can impact performance
## Changelog
### v1.0.0
- Initial release
- Voice limit fix (configurable max voices)
- Audio ducking improvements
- Enemy audio indicator boost options
- BepInEx configuration support
## License
This mod is provided for personal use. Please respect the game's EULA and copyright.
## Credits
- BepInEx team for the modding framework
- FMOD for audio middleware
- Unity Technologies for game engine

103
tools/ExtractBanks.ps1 Normal file
View File

@@ -0,0 +1,103 @@
# HomuraHime Audio Mod - Bank Extraction Script
# Run this after installing AssetRipper to extract FMOD banks
param(
[string]$GamePath = "C:\apps\steam\steamapps\common\Homura Hime",
[string]$OutputPath = "C:\projects\HomuraHime-Mods\extracted_assets",
[string]$BackupPath = "C:\projects\HomuraHime-Mods\backup_banks_original"
)
$ErrorActionPreference = "Stop"
Write-Host "HomuraHime Audio Mod - Bank Extraction Script" -ForegroundColor Cyan
Write-Host "=" * 50
# Check if AssetRipper exists
$AssetRipperPath = "C:\projects\HomuraHime-Mods\tools\AssetRipper\AssetRipper.exe"
if (-not (Test-Path $AssetRipperPath)) {
Write-Host "[ERROR] AssetRipper not found at: $AssetRipperPath" -ForegroundColor Red
Write-Host "Please install AssetRipper first (see docs/SETUP_PHASE1.md)" -ForegroundColor Yellow
exit 1
}
# Check if game path exists
$GameDataPath = Join-Path $GamePath "HomuraHime_Data\StreamingAssets\FMOD\Desktop"
if (-not (Test-Path $GameDataPath)) {
Write-Host "[ERROR] Game FMOD banks not found at: $GameDataPath" -ForegroundColor Red
Write-Host "Please verify game installation" -ForegroundColor Yellow
exit 1
}
Write-Host "[INFO] Game path: $GameDataPath" -ForegroundColor Green
# Create directories
$OutputBanksPath = Join-Path $OutputPath "StreamingAssets\FMOD\Desktop"
$BackupBanksPath = $BackupPath
New-Item -ItemType Directory -Force -Path $OutputBanksPath | Out-Null
New-Item -ItemType Directory -Force -Path $BackupBanksPath | Out-Null
Write-Host "[INFO] Output path: $OutputBanksPath" -ForegroundColor Green
Write-Host "[INFO] Backup path: $BackupBanksPath" -ForegroundColor Green
# List current banks
$banks = Get-ChildItem -Path $GameDataPath -Filter "*.bank"
Write-Host "[INFO] Found $($banks.Count) FMOD bank files" -ForegroundColor Cyan
# Step 1: Backup original banks
Write-Host "`n[STEP 1] Backing up original banks..." -ForegroundColor Yellow
foreach ($bank in $banks) {
$dest = Join-Path $BackupBanksPath $bank.Name
Copy-Item -Path $bank.FullName -Destination $dest -Force
Write-Host " Backed up: $($bank.Name)" -ForegroundColor Gray
}
Write-Host "[OK] Original banks backed up" -ForegroundColor Green
# Step 2: Launch AssetRipper for extraction
Write-Host "`n[STEP 2] Launching AssetRipper..." -ForegroundColor Yellow
Write-Host " In AssetRipper:" -ForegroundColor Cyan
Write-Host " 1. File -> Load -> Select HomuraHime_Data folder" -ForegroundColor Cyan
Write-Host " 2. File -> Export -> Resources" -ForegroundColor Cyan
Write-Host " 3. Export to: $OutputPath" -ForegroundColor Cyan
Write-Host " 4. Wait for export to complete" -ForegroundColor Cyan
Write-Host " 5. Close AssetRipper" -ForegroundColor Cyan
Start-Process $AssetRipperPath
Write-Host "`n[PAUSE] Press Enter when AssetRipper export is complete..." -ForegroundColor Yellow
Read-Host
# Step 3: Verify extraction
$extractedBanksPath = Join-Path $OutputPath "StreamingAssets\FMOD\Desktop"
if (Test-Path $extractedBanksPath) {
$extractedBanks = Get-ChildItem -Path $extractedBanksPath -Filter "*.bank"
Write-Host "[OK] Found $($extractedBanks.Count) extracted bank files" -ForegroundColor Green
# List key banks
$keyBanks = @("SFX.bank", "Ambience.bank", "Master.bank", "Voice.bank")
foreach ($keyBank in $keyBanks) {
$found = $extractedBanks | Where-Object { $_.Name -eq $keyBank }
if ($found) {
Write-Host " [OK] $keyBank found ($([math]::Round($found.Length / 1MB, 2)) MB)" -ForegroundColor Green
} else {
Write-Host " [WARN] $keyBank not found" -ForegroundColor Yellow
}
}
} else {
Write-Host "[ERROR] Extraction failed - path not found: $extractedBanksPath" -ForegroundColor Red
exit 1
}
# Summary
Write-Host "`n" + "=" * 50
Write-Host "Extraction Complete!" -ForegroundColor Cyan
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Yellow
Write-Host " 1. Open FMOD Studio 2.02.x" -ForegroundColor Cyan
Write-Host " 2. Open banks from: $extractedBanksPath" -ForegroundColor Cyan
Write-Host " 3. Edit banks for voice limits, ducking, enemy cues" -ForegroundColor Cyan
Write-Host " 4. See docs/FMOD_MODIFICATION_GUIDE_PHASE3.md for details" -ForegroundColor Cyan
Write-Host ""
Write-Host "Current project structure:" -ForegroundColor Gray
Write-Host " extracted_assets\StreamingAssets\FMOD\Desktop\ <- Edit these banks" -ForegroundColor Gray
Write-Host " backup_banks_original\ <- Original banks (backup)" -ForegroundColor Gray
Write-Host " modified_banks\ <- Create this folder for modified banks" -ForegroundColor Gray