using System; using System.Collections.Generic; using System.Text.Json; using System.Diagnostics; using System.Linq; using RainOpsMini.Helpers; namespace RainOpsMini.Plugins { public class TicketQueryConfig { /// /// 开启此插件 /// public bool Enabled { get; set; } = false; /// /// 基础偏移量 /// public int BaseOffset { get; set; } = 0x0BE67B48; /// /// 阵营1基址偏移 (十进制) /// public List Team1Offsets { get; set; } = new List { 0x158, 0x408, 0x8, 0x2C0, 0x2B0 }; /// /// 阵营2基址偏移 (十进制) /// public List Team2Offsets { get; set; } = new List { 0x158, 0x408, 0x10, 0x2C0, 0x2B0 }; /// /// 游戏主程序路径 /// public string GameExePath { get; set; } = "./SquadGame/Binaries/Win64/SquadGameServer.exe"; /// /// 自动发现的候选路径列表 /// public List CandidateGameExePaths { get; set; } = new List(); } public class TicketQueryPlugin : IPlugin, IDisposable { public string Name => "阵营票数查询"; public string Description => "读取阵营票数,并支持动态修改内存偏移量配置"; public PluginCategory Category => PluginCategory.BasicFunction; public bool IsEnabled => _config.Enabled; private TicketQueryConfig _config = new TicketQueryConfig(); private Timer? _timer; private int _cachedTeam1Tickets; private int _cachedTeam2Tickets; public TicketQueryPlugin() { ApplyConfig(); } public object GetConfig() => _config; public void UpdateConfig(JsonElement config) { var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; _config = JsonSerializer.Deserialize(config.GetRawText(), options) ?? new TicketQueryConfig(); ApplyConfig(); } public void ResetConfig() { _config = new TicketQueryConfig(); ApplyConfig(); } public void Dispose() { _timer?.Dispose(); } private void ApplyConfig() { // 自动发现路径逻辑 var candidates = DetectGameProcesses(); _config.CandidateGameExePaths = candidates; // 如果当前配置为空,尝试自动填充 if (string.IsNullOrEmpty(_config.GameExePath)) { if (candidates.Count == 1) { _config.GameExePath = candidates[0]; } // 如果有多个,保留为空让用户选择(通过CandidateGameExePaths查看) } //检测是否相对路径 if (_config.GameExePath.StartsWith(".")) { _config.GameExePath = Path.GetFullPath(_config.GameExePath, Environment.CurrentDirectory); } MemoryExtraction.path = _config.GameExePath; if (_config.Team1Offsets != null && _config.Team2Offsets != null) { MemoryExtraction.UpdateOffsets(_config.BaseOffset, _config.Team1Offsets, _config.Team2Offsets); } // Manage timer based on Enabled state if (_config.Enabled) { StartTimer(); } else { StopTimer(); } } private void StartTimer() { if (_timer == null) { // Execute immediately, then every 10 seconds _timer = new Timer(OnTimerTick, null, 0, 10000); } else { _timer.Change(0, 10000); } } private void StopTimer() { _timer?.Change(Timeout.Infinite, Timeout.Infinite); } private void OnTimerTick(object? state) { try { if (!IsEnabled) return; // Read tickets and cache _cachedTeam1Tickets = MemoryExtraction.TickChange(1); _cachedTeam2Tickets = MemoryExtraction.TickChange(2); } catch (Exception ex) { RainOpsLog.Log($"[TicketQuery] 自动读取票数失败: {ex.Message}"); } } private List DetectGameProcesses() { var list = new List(); try { var currentDir = Environment.CurrentDirectory; // Normalize current directory path (ensure it ends with separator for proper check) if (!currentDir.EndsWith(Path.DirectorySeparatorChar.ToString()) && !currentDir.EndsWith(Path.AltDirectorySeparatorChar.ToString())) { currentDir += Path.DirectorySeparatorChar; } var processes = Process.GetProcesses(); foreach (var p in processes) { try { if (p.MainModule != null && !string.IsNullOrEmpty(p.MainModule.FileName) && p.MainModule.FileName.StartsWith(currentDir, StringComparison.OrdinalIgnoreCase) && p.MainModule.FileName.IndexOf("SquadGame\\Binaries\\Win64", StringComparison.OrdinalIgnoreCase) >= 0) { list.Add(p.MainModule.FileName); } } catch { } } } catch { } return list.Distinct().ToList(); } public int GetTickets(int teamId) { if (!IsEnabled) return 0; // Return cached value instead of reading from memory every time return teamId == 1 ? _cachedTeam1Tickets : _cachedTeam2Tickets; } public int SetTickets(int teamId, int tickets) { if (!IsEnabled) return 0; int result = MemoryExtraction.TickChange(teamId, tickets); // Update cache immediately if (teamId == 1) _cachedTeam1Tickets = result; if (teamId == 2) _cachedTeam2Tickets = result; return result; } } }