# Record Daemon A high-performance League of Legends recording daemon built with Rust and libobs. ## Features - **Automatic Recording**: Detects when League of Legends starts and automatically records matches - **Game Event Capture**: Captures game events (kills, deaths, objectives) via the League Client API (LQP) - **Event Timeline**: Maps game events to video timestamps for easy navigation - **Hardware Encoding**: Supports NVIDIA NVENC, AMD AMF, and software encoding (x264) - **IPC Interface**: Unix sockets (Linux) / Named pipes (Windows) for configuration and control from a Tauri app - **Configurable Presets**: Quality presets from 720p30 to 1440p60 - **Cross-Platform**: Supports Windows and Linux ## Architecture ``` record-daemon/ ├── src/ │ ├── main.rs # Entry point, daemon setup │ ├── lib.rs # Library exports │ ├── config/ # Configuration management │ │ ├── mod.rs │ │ ├── settings.rs # User settings │ │ ├── presets.rs # Encoder presets │ │ └── persistence.rs # Config file I/O │ ├── lqp/ # League Client API │ │ ├── mod.rs │ │ ├── auth.rs # Lockfile authentication │ │ ├── client.rs # WebSocket/REST client │ │ └── events.rs # Game event types │ ├── recording/ # libobs recording │ │ ├── mod.rs │ │ ├── obs_context.rs # OBS initialization │ │ ├── encoder.rs # Video encoding │ │ ├── capture.rs # Game capture │ │ └── output.rs # File output │ ├── ipc/ # IPC server │ │ ├── mod.rs │ │ ├── server.rs # Platform-specific server │ │ ├── protocol.rs # Message protocol │ │ └── handlers.rs # Command handlers │ ├── timeline/ # Event timeline │ │ ├── mod.rs │ │ ├── store.rs # Storage backend │ │ └── mapper.rs # Event-to-video mapping │ ├── state/ # State machine │ │ ├── mod.rs │ │ └── machine.rs # State transitions │ └── error.rs # Error types ``` ## Platform Support | Feature | Linux | Windows | |---------|-------|---------| | Game Detection | ✅ Wine/Proton paths | ✅ Native paths | | IPC Transport | Unix sockets | Named pipes | | Lockfile Detection | ✅ | ✅ | | Hardware Encoding | NVENC | NVENC, AMF | ## Building ### Linux ```bash cd record-daemon cargo build --release ``` ### Windows ```powershell cd record-daemon cargo build --release ``` **Prerequisites for Windows:** - Visual Studio Build Tools (MSVC) - OBS Studio installed (for libobs DLLs) ## Usage ### Running the Daemon ```bash # Run with default settings ./record-daemon # Run with custom config ./record-daemon --config /path/to/config.toml # Run with debug logging ./record-daemon --log-level debug # Run in foreground ./record-daemon --foreground ``` ### Command Line Options | Option | Description | |--------|-------------| | `-c, --config ` | Path to configuration file | | `-l, --log-level ` | Log level (trace, debug, info, warn, error) | | `-f, --foreground` | Run in foreground (don't daemonize) | | `-s, --socket ` | Socket path for IPC | ## Configuration The configuration file is stored at: - Linux: `~/.config/record-daemon/config.toml` - Windows: `%APPDATA%\record-daemon\config.toml` ### Example Configuration ```toml [video] encoder_preset = { type = "nvenc", bitrate = 8000, cq_level = 20, two_pass = true } quality = "high" frame_rate = 60 hardware_acceleration = true [output] path = "C:\\Users\\User\\Videos\\LeagueRecordings" # Windows # path = "/home/user/Videos/LeagueRecordings" # Linux naming_pattern = "{date}_{time}_{champion}" container = "mp4" [audio] enabled = true bitrate = 192 sample_rate = 48000 channels = 2 capture_game = true capture_mic = false [daemon] auto_record = true monitor_client = true poll_interval_ms = 1000 log_level = "info" save_timeline = true ``` ### Encoder Presets #### NVIDIA NVENC ```toml [video.encoder_preset] type = "nvenc" bitrate = 8000 cq_level = 20 two_pass = true ``` #### AMD AMF (Windows only) ```toml [video.encoder_preset] type = "amf" bitrate = 8000 quality = "balanced" ``` #### x264 (Software) ```toml [video.encoder_preset] type = "x264" preset = "veryfast" bitrate = 6000 crf = 23 ``` ### Quality Levels | Level | Resolution | FPS | Bitrate | |-------|------------|-----|---------| | Low | 720p | 30 | 4500 kbps | | Medium | 1080p | 30 | 6000 kbps | | High | 1080p | 60 | 8000 kbps | | Ultra | 1440p | 60 | 12000 kbps | ## IPC Protocol The daemon exposes an IPC server for communication with the Tauri app. ### Socket Location - Linux: `$XDG_RUNTIME_DIR/record-daemon.sock` (or `/tmp/record-daemon.sock`) - Windows: `\\.\pipe\record-daemon` (named pipe) ### Message Format All messages are JSON-encoded: ```json { "type": "request", "id": "uuid-v4", "command": "GetSettings", "payload": null } ``` ### Available Commands | Command | Description | |---------|-------------| | `GetSettings` | Get current settings | | `UpdateSettings` | Update settings | | `ResetSettings` | Reset to defaults | | `GetStatus` | Get daemon status | | `GetEncoders` | Get available encoders | | `StartRecording` | Start recording manually | | `StopRecording` | Stop recording | | `GetRecordings` | List all recordings | | `GetTimeline` | Get event timeline for a recording | | `Shutdown` | Shutdown the daemon | ### Example IPC Session (Linux) ```bash # Connect to socket nc -U /tmp/record-daemon.sock # Get status {"type":"request","id":"00000000-0000-0000-0000-000000000001","command":"GetStatus"} # Response {"request_id":"00000000-0000-0000-0000-000000000001","success":true,"data":{"status":"idle","isRecording":false,"clientConnected":false}} ``` ### Example IPC Session (Windows) ```powershell # Connect to named pipe using PowerShell $client = New-Object System.IO.Pipes.NamedPipeClientStream(".", "record-daemon", [System.IO.Pipes.PipeDirection]::InOut) $client.Connect() $reader = New-Object System.IO.StreamReader($client) $writer = New-Object System.IO.StreamWriter($client) # Send command $writer.WriteLine('{"type":"request","id":"00000000-0000-0000-0000-000000000001","command":"GetStatus"}') $writer.Flush() # Read response $response = $reader.ReadLine() Write-Host $response $client.Close() ``` ## Event Timeline Each recording has an associated timeline that maps game events to video timestamps: ```json { "recording_id": "uuid", "start_time": "2024-01-15T10:30:00Z", "end_time": "2024-01-15T11:15:00Z", "duration_secs": 2700, "events": [ { "video_timestamp": 120, "game_timestamp": 115, "event_type": "kill", "description": "Player1 killed Player2", "timestamp": "2024-01-15T10:32:00Z" } ] } ``` --- ## Windows Test Plan This section provides a comprehensive test plan for validating the daemon on Windows with League of Legends. ### Prerequisites 1. **System Requirements** - Windows 10/11 64-bit - League of Legends installed - OBS Studio installed (for libobs) - NVIDIA GPU (for NVENC) or AMD GPU (for AMF) - optional but recommended 2. **Build Requirements** - Rust toolchain (stable) - Visual Studio Build Tools 2022 - Windows SDK ### Test Environment Setup ```powershell # 1. Install Rust if not already installed winget install Rustlang.Rustup # 2. Verify Rust installation rustc --version cargo --version # 3. Build the daemon cd record-daemon cargo build --release ``` ### Test 1: Daemon Startup **Objective:** Verify daemon starts correctly on Windows. **Steps:** ```powershell # Run daemon in foreground with debug logging .\target\release\record-daemon.exe --foreground --log-level debug ``` **Expected Results:** - Daemon starts without errors - Named pipe `\\.\pipe\record-daemon` is created - Log shows "IPC server started successfully" - Config file created at `%APPDATA%\record-daemon\config.toml` **Verification:** ```powershell # Check if named pipe exists [System.IO.Directory]::GetFiles("\\.\\pipe\\") | Where-Object { $_ -like "*record-daemon*" } ``` ### Test 2: League Client Detection **Objective:** Verify daemon detects League Client startup. **Steps:** 1. Start the daemon with debug logging 2. Launch League of Legends Client 3. Wait for client to fully load (login screen) **Expected Results:** - Log shows "League Client detected (PID: XXXX, Port: XXXX)" - Lockfile found at `C:\Riot Games\League of Legends\lockfile` - Daemon transitions to Monitoring state **Verification:** ```powershell # Check lockfile exists Get-Content "C:\Riot Games\League of Legends\lockfile" # Expected format: LeagueClient:PID:PORT:PASSWORD:PROTOCOL # Example: LeagueClient:12345:52432:abc123:https ``` ### Test 3: Game Start Detection **Objective:** Verify daemon detects game start and begins recording. **Steps:** 1. Start daemon 2. Launch League Client 3. Start a game (Practice Tool, ARAM, or Summoner's Rift) 4. Wait for game to load **Expected Results:** - Log shows "Starting recording for game XXXXX" - Recording file created in output directory - Daemon transitions to Recording state **Verification:** ```powershell # Check for recording files Get-ChildItem -Path "C:\Users\$env:USERNAME\Videos\LeagueRecordings" -Recurse ``` ### Test 4: Event Capture **Objective:** Verify game events are captured and mapped to timeline. **Steps:** 1. Start daemon 2. Play a game (Practice Tool recommended for controlled testing) 3. Perform actions: kills, deaths, take objectives 4. End the game **Expected Results:** - Events logged in debug output - Timeline JSON file created after game ends - Events contain correct timestamps **Verification:** ```powershell # Check timeline files Get-ChildItem -Path "$env:APPDATA\record-daemon\timelines" -Recurse # View timeline content Get-Content "$env:APPDATA\record-daemon\timelines\*.json" | ConvertFrom-Json ``` ### Test 5: Game End Detection **Objective:** Verify daemon stops recording when game ends. **Steps:** 1. Start daemon 2. Play a game 3. End the game (win, lose, or surrender) **Expected Results:** - Log shows "Stopping recording" - Recording file finalized - Timeline saved - Daemon transitions back to Monitoring state ### Test 6: IPC Communication **Objective:** Verify IPC commands work on Windows named pipes. **Steps:** ```powershell # Create test script $pipe = New-Object System.IO.Pipes.NamedPipeClientStream(".", "record-daemon", [System.IO.Pipes.PipeDirection]::InOut) $pipe.Connect(5000) $reader = New-Object System.IO.StreamReader($pipe) $writer = New-Object System.IO.StreamWriter($pipe) # Test GetStatus $writer.WriteLine('{"type":"request","id":"00000000-0000-0000-0000-000000000001","command":"GetStatus"}') $writer.Flush() $response = $reader.ReadLine() Write-Host "Status: $response" # Test GetEncoders $writer.WriteLine('{"type":"request","id":"00000000-0000-0000-0000-000000000002","command":"GetEncoders"}') $writer.Flush() $response = $reader.ReadLine() Write-Host "Encoders: $response" $pipe.Close() ``` **Expected Results:** - GetStatus returns current daemon state - GetEncoders returns available hardware encoders ### Test 7: Client Disconnect/Reconnect **Objective:** Verify daemon handles client restarts correctly. **Steps:** 1. Start daemon 2. Launch League Client 3. Close League Client 4. Reopen League Client **Expected Results:** - Log shows "League Client stopped" when closed - Log shows "League Client detected" when reopened - Daemon state transitions correctly ### Test 8: Multiple Game Sessions **Objective:** Verify daemon handles multiple consecutive games. **Steps:** 1. Start daemon 2. Play 3 consecutive games 3. Check all recordings **Expected Results:** - Each game creates separate recording - Each recording has correct timeline - No memory leaks or performance degradation ### Test 9: Hardware Encoding **Objective:** Verify hardware encoding works correctly. **Steps:** 1. Configure NVENC or AMF in config 2. Start daemon 3. Play a game 4. Check recording quality **NVIDIA GPU Verification:** ```powershell # Check NVENC is being used nvidia-smi dmon -s u -d pwr,enc # Should show encoder utilization during recording ``` **Expected Results:** - Recording uses hardware encoder - GPU encoder utilization shown in monitoring tools - CPU usage remains low during recording ### Test 10: Error Recovery **Objective:** Verify daemon recovers from errors gracefully. **Steps:** 1. Start daemon 2. Start recording 3. Simulate error scenarios: - Kill game process - Delete output directory - Fill disk space (careful!) **Expected Results:** - Daemon logs error appropriately - Daemon recovers and continues operation - No crash or hang ### Test 11: Configuration Persistence **Objective:** Verify settings are saved and loaded correctly. **Steps:** ```powershell # Edit config $configPath = "$env:APPDATA\record-daemon\config.toml" notepad $configPath # Restart daemon .\target\release\record-daemon.exe --foreground ``` **Expected Results:** - Config changes persist across restarts - Invalid config shows appropriate error - Default config created if missing ### Test 12: Shutdown Handling **Objective:** Verify clean shutdown. **Steps:** 1. Start daemon 2. Start recording 3. Send Ctrl+C or shutdown signal **Expected Results:** - Recording stops cleanly - Timeline saved - Named pipe cleaned up - No orphaned processes --- ## Development ### Running Tests ```bash cargo test ``` ### Building Documentation ```bash cargo doc --open ``` ### Code Structure The daemon follows a state machine pattern: ``` Idle -> Monitoring -> Recording -> Monitoring -> Idle | | | v v v Error <-------- Error -------- Error | v Idle (recovery) ``` ## Dependencies - **tokio**: Async runtime - **tokio-tungstenite**: WebSocket for LQP - **reqwest**: HTTP client for LQP REST API - **serde/serde_json**: Serialization - **tracing**: Logging - **parking_lot**: High-performance locks - **chrono**: Date/time handling - **uuid**: Unique identifiers ## Troubleshooting ### Windows: Named Pipe Connection Failed ``` Error: Cannot connect to named pipe ``` **Solution:** Ensure daemon is running and pipe exists: ```powershell # Check pipe exists [System.IO.Directory]::GetFiles("\\.\\pipe\\") | Where-Object { $_ -like "*record-daemon*" } ``` ### Windows: Lockfile Not Found ``` Error: League Client lockfile not found ``` **Solution:** Check League Client installation path: ```powershell # Common paths Test-Path "C:\Riot Games\League of Legends\lockfile" Test-Path "D:\Riot Games\League of Legends\lockfile" ``` ### Windows: NVENC Not Available ``` Error: NVENC encoder not available ``` **Solution:** 1. Ensure NVIDIA GPU is present 2. Update NVIDIA drivers 3. Check NVENC support: `nvidia-smi` ## License MIT