using System; using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using SlamCross.Models; namespace SlamCross.Services; public static class CfgService { public static void WriteCfgFiles(SourceGame game, string steamAppsPath, AppSettings settings) { var sep = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '\\' : '/'; var gameDir = Path.Combine(steamAppsPath, game.Directory.Replace('\\', sep)); var cfgFolder = Path.Combine(gameDir, game.ToCfg.Replace('\\', sep)); if (!Directory.Exists(cfgFolder)) throw new Exception($"CS2 cfg folder not found: {cfgFolder}\nCheck your Steam/CS2 installation path."); var lines = new List(); lines.Add("alias slam_play \"slam_updatecfg;slam_play_on;\""); if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { lines.Add("alias slam_play_on \"alias slam_play slam_play_off; slam_nonce; slam_updatecfg; voice_loopback 1; +voicerecord\""); lines.Add("alias slam_play_off \"alias slam_play slam_play_on; -voicerecord; voice_loopback 0;\""); } else { var virtualMicOut = string.IsNullOrEmpty(settings.VirtualMicOutDeviceName) ? "CABLE Output (VB-Audio Virtual Cable)" : settings.VirtualMicOutDeviceName.Trim(); var regularMic = string.IsNullOrEmpty(settings.RegularMicDeviceName) ? "Microphone" : settings.RegularMicDeviceName.Trim(); lines.Add($"alias slam_play_on \"alias slam_play slam_play_off; slam_nonce; slam_updatecfg; voice_loopback 1; voice_device_override {virtualMicOut}; +voicerecord\""); lines.Add($"alias slam_play_off \"alias slam_play slam_play_on; -voicerecord; voice_loopback 0; voice_device_override {regularMic};\""); } lines.Add("alias slam_updatecfg \"host_writeconfig\""); // the relay bind flips between _a/_b on each play press, so the app can tell a real // press apart from CS2 rewriting the vcfg on its own (round start, buy menu) lines.Add("alias slam_nonce slam_nonce_a"); lines.Add("alias slam_nonce_a \"alias slam_nonce slam_nonce_b; slam_bind_a\""); lines.Add("alias slam_nonce_b \"alias slam_nonce slam_nonce_a; slam_bind_b\""); lines.Add("alias slam_bind_a \"\""); lines.Add("alias slam_bind_b \"\""); lines.Add("alias slam_none \"\""); lines.Add($"bind {settings.RelayKey} slam_none"); if (settings.HoldToPlay) { lines.Add("alias +slam_hold_play slam_play_on"); lines.Add("alias -slam_hold_play slam_play_off"); lines.Add($"bind {settings.PlayKey} +slam_hold_play"); } else { lines.Add($"bind {settings.PlayKey} slam_play"); } lines.Add("alias slam_listtracks \"exec slam_tracklist.cfg\""); lines.Add("alias slam_curtrack \"exec slam_curtrack.cfg\""); lines.Add("alias slam_saycurtrack \"exec slam_saycurtrack.cfg\""); lines.Add("alias slam_sayteamcurtrack \"exec slam_sayteamcurtrack.cfg\""); for (int i = 0; i < game.Tracks.Count; i++) { var n = i + 1; var track = game.Tracks[i]; lines.Add($"alias slam_bindcmd_{n}_a \"bind {settings.RelayKey} slam_r_{n}_a\""); lines.Add($"alias slam_bindcmd_{n}_b \"bind {settings.RelayKey} slam_r_{n}_b\""); lines.Add($"alias slam_r_{n}_a \"\""); lines.Add($"alias slam_r_{n}_b \"\""); var select = $"alias slam_bind_a slam_bindcmd_{n}_a; alias slam_bind_b slam_bindcmd_{n}_b"; lines.Add($"alias {n} \"{select}\""); if (settings.WriteTags) foreach (var tag in track.Tags) lines.Add($"alias {tag} \"{select}\""); if (!string.IsNullOrEmpty(track.Hotkey)) lines.Add($"bind {track.Hotkey} \"{select}; slam_play_on;\""); } var footer = "voice_modenable 1; con_enable 1"; if (game.VoiceFadeOut) footer += "; voice_fadeouttime 0.0"; footer += "; host_writeconfig"; lines.Add(footer); // CS2 limits exec files to 200 lines; chain into slam2, slam3, etc. if needed. const int chunkSize = 199; int fileIdx = 1; for (int written = 0; written < lines.Count; written += chunkSize) { var fileName = fileIdx == 1 ? "slam.cfg" : $"slam{fileIdx}.cfg"; var chunk = lines.GetRange(written, Math.Min(chunkSize, lines.Count - written)); if (written + chunkSize < lines.Count) chunk.Add($"exec slam{fileIdx + 1}"); File.WriteAllLines(Path.Combine(cfgFolder, fileName), chunk); fileIdx++; } using var trackList = new StreamWriter(Path.Combine(cfgFolder, "slam_tracklist.cfg")); trackList.WriteLine("echo \"You can select tracks by tag or number.\""); trackList.WriteLine("echo \"--------------------Tracks--------------------\""); for (int i = 0; i < game.Tracks.Count; i++) { var t = game.Tracks[i]; var tagStr = settings.WriteTags && t.Tags.Count > 0 ? $" ['{string.Join("', '", t.Tags)}']" : ""; trackList.WriteLine($"echo \"{i + 1}. {t.Name}{tagStr}\""); } trackList.WriteLine("echo \"----------------------------------------------\""); } public static void DeleteCfgFiles(SourceGame game, string steamAppsPath) { var sep = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '\\' : '/'; var cfgFolder = Path.Combine(steamAppsPath, game.Directory.Replace('\\', sep), game.ToCfg.Replace('\\', sep)); string[] files = ["slam.cfg", "slam_tracklist.cfg", "slam_relay.cfg", "slam_curtrack.cfg", "slam_saycurtrack.cfg", "slam_sayteamcurtrack.cfg"]; foreach (var f in files) { var path = Path.Combine(cfgFolder, f); if (File.Exists(path)) try { File.Delete(path); } catch { } } } public static void WriteTrackCfgs(SourceGame game, string steamAppsPath, Track track) { var sep = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '\\' : '/'; var cfgFolder = Path.Combine(steamAppsPath, game.Directory.Replace('\\', sep), game.ToCfg.Replace('\\', sep)); File.WriteAllText(Path.Combine(cfgFolder, "slam_curtrack.cfg"), $"echo \"[SLAM] Track name: {track.Name}\"\n"); File.WriteAllText(Path.Combine(cfgFolder, "slam_saycurtrack.cfg"), $"say \"[SLAM] Track name: {track.Name}\"\n"); File.WriteAllText(Path.Combine(cfgFolder, "slam_sayteamcurtrack.cfg"), $"say_team \"[SLAM] Track name: {track.Name}\"\n"); } }