From 4b600e248590e9e3fe54afc9d800530d7f2e496e Mon Sep 17 00:00:00 2001 From: "Crunch (Chaz9)" Date: Wed, 18 Dec 2024 21:43:24 +0000 Subject: [PATCH] ok --- Custom Client/CustomClient.csproj | 7 +- Custom Client/Engines/OxygenEngine.cs | 138 +++++--- Custom Client/Engines/RSDKEngine.cs | 179 +++++++--- Custom Client/Logger.cs | 115 ++++--- Custom Client/Program.cs | 455 ++++++++++---------------- Custom Client/Tools/RSDKAnalyzer.cs | 1 + build.ps1 | 110 +++++-- 7 files changed, 569 insertions(+), 436 deletions(-) diff --git a/Custom Client/CustomClient.csproj b/Custom Client/CustomClient.csproj index 9de851a2..e9a1121c 100644 --- a/Custom Client/CustomClient.csproj +++ b/Custom Client/CustomClient.csproj @@ -10,6 +10,7 @@ SonicHybrid icon.ico SonicHybridUltimate.CustomClient.Program + SonicHybridUltimate @@ -26,9 +27,9 @@ - - - + + + diff --git a/Custom Client/Engines/OxygenEngine.cs b/Custom Client/Engines/OxygenEngine.cs index 0b773a3f..25472b38 100644 --- a/Custom Client/Engines/OxygenEngine.cs +++ b/Custom Client/Engines/OxygenEngine.cs @@ -1,86 +1,132 @@ using System; using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; namespace SonicHybridUltimate.Engines { - public class OxygenEngine : IGameEngine + public class OxygenEngine : IDisposable { - [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] - private static extern int InitOxygenEngine(string scriptPath); + private readonly ILogger _logger; + private bool _isInitialized; + private string _currentScript = string.Empty; + private bool _isDisposed; - [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] - private static extern void UpdateOxygenEngine(); - - [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] - private static extern void CleanupOxygenEngine(); - - private bool isInitialized = false; - private string currentGame = ""; - private readonly ILogger logger; - - public OxygenEngine(ILogger logger) + public OxygenEngine(ILogger logger) { - this.logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public bool Initialize(string scriptPath) { + ThrowIfDisposed(); + try { - int result = InitOxygenEngine(scriptPath); - isInitialized = result == 0; - if (isInitialized) + _logger.LogInformation("Initializing Oxygen Engine with script: {ScriptPath}", scriptPath); + + if (_isInitialized) { - currentGame = scriptPath; - logger.Log($"Oxygen Engine initialized with script: {scriptPath}"); + _logger.LogWarning("Oxygen Engine is already initialized. Cleaning up first..."); + Cleanup(); + } + + var result = NativeMethods.InitOxygenEngine(scriptPath); + _isInitialized = (result == 1); + + if (_isInitialized) + { + _currentScript = scriptPath; + _logger.LogInformation("Oxygen Engine initialized successfully"); } else { - logger.LogError($"Failed to initialize Oxygen Engine with script: {scriptPath}"); + _logger.LogError("Failed to initialize Oxygen Engine"); } - return isInitialized; + + return _isInitialized; } catch (Exception ex) { - logger.LogError($"Error initializing Oxygen Engine: {ex.Message}"); + _logger.LogError(ex, "Error initializing Oxygen Engine"); return false; } } public void Update() { - if (isInitialized) + ThrowIfDisposed(); + + if (!_isInitialized) { - try - { - UpdateOxygenEngine(); - } - catch (Exception ex) - { - logger.LogError($"Error updating Oxygen Engine: {ex.Message}"); - } + return; + } + + try + { + NativeMethods.UpdateOxygenEngine(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating Oxygen Engine"); + throw; } } public void Cleanup() { - if (isInitialized) + if (!_isInitialized) { - try - { - CleanupOxygenEngine(); - isInitialized = false; - currentGame = ""; - logger.Log("Oxygen Engine cleaned up successfully"); - } - catch (Exception ex) - { - logger.LogError($"Error cleaning up Oxygen Engine: {ex.Message}"); - } + return; + } + + try + { + _logger.LogInformation("Cleaning up Oxygen Engine"); + NativeMethods.CleanupOxygenEngine(); + _isInitialized = false; + _currentScript = string.Empty; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error cleaning up Oxygen Engine"); + throw; } } - public bool IsRunning => isInitialized; - public string CurrentGame => currentGame; + private void ThrowIfDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(OxygenEngine)); + } + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + if (_isInitialized) + { + Cleanup(); + } + + _isDisposed = true; + GC.SuppressFinalize(this); + } + + private static class NativeMethods + { + [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] + public static extern int InitOxygenEngine(string scriptPath); + + [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] + public static extern void UpdateOxygenEngine(); + + [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] + public static extern void CleanupOxygenEngine(); + } } } diff --git a/Custom Client/Engines/RSDKEngine.cs b/Custom Client/Engines/RSDKEngine.cs index 46758d14..1dca8f49 100644 --- a/Custom Client/Engines/RSDKEngine.cs +++ b/Custom Client/Engines/RSDKEngine.cs @@ -1,86 +1,175 @@ using System; using System.Runtime.InteropServices; +using Microsoft.Extensions.Logging; namespace SonicHybridUltimate.Engines { - public class RSDKEngine : IGameEngine + public class RSDKEngine : IDisposable { - [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] - private static extern int InitRSDKv4(string dataPath); + private readonly ILogger _logger; + private bool _isInitialized; + private string _currentGame = string.Empty; + private bool _isDisposed; - [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] - private static extern void UpdateRSDKv4(); + private const int ADDR_CURRENT_ZONE = 0x203A40; + private const int ADDR_ROBOT_STATE = 0x203A44; + private const int ADDR_EXPLOSION_TIMER = 0x203A48; + private const byte DEATH_EGG_ZONE_ID = 0x0B; - [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] - private static extern void CleanupRSDKv4(); - - private bool isInitialized = false; - private string currentGame = ""; - private readonly ILogger logger; - - public RSDKEngine(ILogger logger) + public RSDKEngine(ILogger logger) { - this.logger = logger; + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public bool Initialize(string gamePath) { + ThrowIfDisposed(); + try { - int result = InitRSDKv4(gamePath); - isInitialized = result == 0; - if (isInitialized) + _logger.LogInformation("Initializing RSDK with game: {GamePath}", gamePath); + + if (_isInitialized) + { + _logger.LogWarning("RSDK is already initialized. Cleaning up first..."); + Cleanup(); + } + + var result = NativeMethods.InitRSDKv4(gamePath); + _isInitialized = (result == 1); + + if (_isInitialized) { - currentGame = gamePath; - logger.Log($"RSDK Engine initialized with game: {gamePath}"); + _currentGame = gamePath; + _logger.LogInformation("RSDK initialized successfully"); } else { - logger.LogError($"Failed to initialize RSDK Engine with game: {gamePath}"); + _logger.LogError("Failed to initialize RSDK"); } - return isInitialized; + + return _isInitialized; } catch (Exception ex) { - logger.LogError($"Error initializing RSDK Engine: {ex.Message}"); + _logger.LogError(ex, "Error initializing RSDK"); return false; } } public void Update() { - if (isInitialized) + ThrowIfDisposed(); + + if (!_isInitialized) { - try - { - UpdateRSDKv4(); - } - catch (Exception ex) - { - logger.LogError($"Error updating RSDK Engine: {ex.Message}"); - } + return; + } + + try + { + NativeMethods.UpdateRSDKv4(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error updating RSDK"); + throw; } } public void Cleanup() { - if (isInitialized) + if (!_isInitialized) { - try - { - CleanupRSDKv4(); - isInitialized = false; - currentGame = ""; - logger.Log("RSDK Engine cleaned up successfully"); - } - catch (Exception ex) - { - logger.LogError($"Error cleaning up RSDK Engine: {ex.Message}"); - } + return; + } + + try + { + _logger.LogInformation("Cleaning up RSDK"); + NativeMethods.CleanupRSDKv4(); + _isInitialized = false; + _currentGame = string.Empty; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error cleaning up RSDK"); + throw; + } + } + + public bool IsDeathEggDefeated() + { + ThrowIfDisposed(); + + if (!_isInitialized) + { + return false; + } + + try + { + return IsInDeathEggZone() && + GetDeathEggRobotState() == 1 && + GetDeathEggExplosionTimer() > 0; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking Death Egg state"); + return false; } } - public bool IsRunning => isInitialized; - public string CurrentGame => currentGame; + private bool IsInDeathEggZone() + { + var zoneId = Marshal.ReadByte(new IntPtr(ADDR_CURRENT_ZONE)); + return zoneId == DEATH_EGG_ZONE_ID; + } + + private int GetDeathEggRobotState() + { + return Marshal.ReadByte(new IntPtr(ADDR_ROBOT_STATE)); + } + + private int GetDeathEggExplosionTimer() + { + return Marshal.ReadByte(new IntPtr(ADDR_EXPLOSION_TIMER)); + } + + private void ThrowIfDisposed() + { + if (_isDisposed) + { + throw new ObjectDisposedException(nameof(RSDKEngine)); + } + } + + public void Dispose() + { + if (_isDisposed) + { + return; + } + + if (_isInitialized) + { + Cleanup(); + } + + _isDisposed = true; + GC.SuppressFinalize(this); + } + + private static class NativeMethods + { + [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] + public static extern int InitRSDKv4(string dataPath); + + [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] + public static extern void UpdateRSDKv4(); + + [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] + public static extern void CleanupRSDKv4(); + } } } diff --git a/Custom Client/Logger.cs b/Custom Client/Logger.cs index 82cc12cc..cfa9a0a0 100644 --- a/Custom Client/Logger.cs +++ b/Custom Client/Logger.cs @@ -1,76 +1,97 @@ +using System; +using System.IO; using System.Text; +using System.Diagnostics; namespace SonicHybridUltimate.CustomClient { - public static class Logger + public enum LogLevel { - private static readonly string LogFile = "hybrid_client.log"; - private static bool enableDebugLogging = false; - private static readonly object lockObj = new object(); + Debug, + Information, + Warning, + Error + } - public static void Initialize(bool enableDebug) + public interface ILogger + { + void Log(LogLevel level, string message); + void LogError(string message, Exception? exception = null); + } + + public class Logger : ILogger + { + private readonly string _name; + private readonly Action _logAction; + private readonly string _logFile; + private static readonly object _lock = new object(); + + public Logger(string name, Action logAction) { - enableDebugLogging = enableDebug; - // Clear log file on startup - if (File.Exists(LogFile)) - File.WriteAllText(LogFile, string.Empty); - - Log("Logger initialized"); + _name = name; + _logAction = logAction; + _logFile = "hybrid_client.log"; + + // Create or clear log file + File.WriteAllText(_logFile, string.Empty); } - public static void Log(string message, bool isDebug = false) + public void Log(LogLevel level, string message) { - if (isDebug && !enableDebugLogging) - return; + var logMessage = FormatMessage(level, message); + WriteLog(logMessage); + } - var logMessage = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {(isDebug ? "[DEBUG] " : "")}{message}"; + public void LogError(string message, Exception? exception = null) + { + var sb = new StringBuilder(); + sb.AppendLine(FormatMessage(LogLevel.Error, message)); - lock (lockObj) + if (exception != null) + { + sb.AppendLine($"Exception: {exception.GetType().Name}"); + sb.AppendLine($"Message: {exception.Message}"); + sb.AppendLine($"Stack Trace: {exception.StackTrace}"); + } + + WriteLog(sb.ToString()); + } + + private string FormatMessage(LogLevel level, string message) + { + return $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [{level}] [{_name}] {message}"; + } + + private void WriteLog(string message) + { + lock (_lock) { try { - File.AppendAllText(LogFile, logMessage + Environment.NewLine); - Debug.WriteLine(logMessage); + File.AppendAllText(_logFile, message + Environment.NewLine); + _logAction(message); + Trace.WriteLine(message); } catch (Exception ex) { - Debug.WriteLine($"Failed to write to log file: {ex.Message}"); + Trace.WriteLine($"Failed to write to log: {ex.Message}"); } } } + } - public static void LogError(string message, Exception? ex = null) - { - var sb = new StringBuilder(); - sb.AppendLine($"[ERROR] {message}"); - - if (ex != null) - { - sb.AppendLine($"Exception: {ex.GetType().Name}"); - sb.AppendLine($"Message: {ex.Message}"); - sb.AppendLine($"Stack Trace: {ex.StackTrace}"); - } + public class LoggerProvider + { + private readonly Action _logAction; - Log(sb.ToString()); + public LoggerProvider(Action logAction) + { + _logAction = logAction; } - public static string[] GetRecentLogs(int lines = 100) + public ILogger CreateLogger(string categoryName) { - try - { - if (!File.Exists(LogFile)) - return Array.Empty(); - - return File.ReadAllLines(LogFile) - .Reverse() - .Take(lines) - .Reverse() - .ToArray(); - } - catch - { - return Array.Empty(); - } + return new Logger(categoryName, _logAction); } } } diff --git a/Custom Client/Program.cs b/Custom Client/Program.cs index 1fe4609b..f17aa698 100644 --- a/Custom Client/Program.cs +++ b/Custom Client/Program.cs @@ -1,353 +1,260 @@ using System; -using System.Diagnostics; using System.Windows.Forms; -using System.Text.Json; -using System.IO; -using System.Threading.Tasks; -using System.Runtime.InteropServices; +using System.Drawing; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; +using SonicHybridUltimate.Engines; +using SonicHybridUltimate.Tools; namespace SonicHybridUltimate { - public class Program + public partial class MainForm : Form { - [STAThread] - static void Main() + private readonly ILogger _logger; + private readonly IServiceProvider _services; + private readonly RSDKEngine _rsdkEngine; + private readonly OxygenEngine _oxygenEngine; + private readonly RSDKAnalyzer _rsdkAnalyzer; + + private RichTextBox _logBox = null!; + private Label _statusLabel = null!; + private Button _loadSonic2Button = null!; + private Button _loadSonic3Button = null!; + private System.Windows.Forms.Timer _updateTimer = null!; + + private string _currentGame = string.Empty; + private bool _isTransitioning; + + public MainForm(IServiceProvider services) { - Application.SetHighDpiMode(HighDpiMode.SystemAware); - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new SonicHybridClient()); - } - } - - public class SonicHybridClient : Form - { - // Native methods for RSDK and Oxygen Engine - [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] - private static extern int InitRSDKv4(string dataPath); + _services = services ?? throw new ArgumentNullException(nameof(services)); + _logger = _services.GetRequiredService>(); + _rsdkEngine = _services.GetRequiredService(); + _oxygenEngine = _services.GetRequiredService(); + _rsdkAnalyzer = _services.GetRequiredService(); - [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] - private static extern void UpdateRSDKv4(); + InitializeComponents(); + InitializeTimer(); - [DllImport("RSDKv4", CallingConvention = CallingConvention.Cdecl)] - private static extern void CleanupRSDKv4(); - - [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] - private static extern int InitOxygenEngine(string scriptPath); - - [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] - private static extern void UpdateOxygenEngine(); - - [DllImport("OxygenEngine", CallingConvention = CallingConvention.Cdecl)] - private static extern void CleanupOxygenEngine(); - - private string currentGame = "None"; - private readonly string configPath = "config.json"; - private readonly string logPath = "hybrid.log"; - private RichTextBox logBox; - private Label statusLabel; - private Dictionary gameState; - private bool engineRunning = false; - - // Core paths - private readonly string baseRSDKPath; - private readonly string sonic3Path; - private readonly string dataPath; - private readonly string scriptPath; - - // Game-specific paths - private readonly string sonic1DataPath; - private readonly string sonic2DataPath; - private readonly string sonicCDDataPath; - - public SonicHybridClient() - { - // Initialize base paths - baseRSDKPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Hybrid-RSDK Main"); - sonic3Path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Sonic 3 AIR Main", "Oxygen", "sonic3air"); - dataPath = Path.Combine(baseRSDKPath, "Data"); - scriptPath = Path.Combine(sonic3Path, "scripts"); - - // Initialize game-specific paths - sonic1DataPath = Path.Combine(dataPath, "Sonic1"); - sonic2DataPath = Path.Combine(dataPath, "Sonic2"); - sonicCDDataPath = Path.Combine(dataPath, "SonicCD"); - - // Load or create game state - gameState = LoadGameState(); - - InitializeUI(); - ValidateEnvironment(); + _logger.LogInformation("MainForm initialized"); } - private Dictionary LoadGameState() + private void InitializeComponents() { - try - { - if (File.Exists(configPath)) - { - var json = File.ReadAllText(configPath); - return JsonSerializer.Deserialize>(json) ?? new(); - } - } - catch (Exception ex) - { - LogMessage($"Error loading game state: {ex.Message}"); - } - return new Dictionary(); - } + Text = "Sonic Hybrid Ultimate"; + Size = new Size(1024, 768); + StartPosition = FormStartPosition.CenterScreen; - private void SaveGameState() - { - try - { - var json = JsonSerializer.Serialize(gameState); - File.WriteAllText(configPath, json); - } - catch (Exception ex) + // Create main layout + var mainLayout = new TableLayoutPanel { - LogMessage($"Error saving game state: {ex.Message}"); - } - } + Dock = DockStyle.Fill, + RowCount = 3, + ColumnCount = 1 + }; - private void InitializeUI() - { - this.Text = "Sonic Hybrid Ultimate"; - this.Size = new Size(800, 600); - this.FormBorderStyle = FormBorderStyle.FixedSingle; - this.MaximizeBox = false; + mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 40f)); // Buttons + mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 100f)); // Log + mainLayout.RowStyles.Add(new RowStyle(SizeType.Absolute, 30f)); // Status + + // Create button panel + var buttonPanel = new FlowLayoutPanel + { + Dock = DockStyle.Fill, + FlowDirection = FlowDirection.LeftToRight, + Padding = new Padding(5) + }; - statusLabel = new Label + _loadSonic2Button = new Button { - Text = "Ready to Start", - Location = new Point(20, 20), + Text = "Load Sonic 2", AutoSize = true }; + _loadSonic2Button.Click += LoadSonic2_Click; - var startButton = new Button + _loadSonic3Button = new Button { - Text = "Start Game", - Location = new Point(350, 20), - Size = new Size(100, 30) + Text = "Load Sonic 3 & Knuckles", + AutoSize = true, + Enabled = false // Disabled until Sonic 2 is completed }; - startButton.Click += StartGame; + _loadSonic3Button.Click += LoadSonic3_Click; + + buttonPanel.Controls.AddRange(new Control[] { _loadSonic2Button, _loadSonic3Button }); - logBox = new RichTextBox + // Create log box + _logBox = new RichTextBox { - Location = new Point(20, 60), - Size = new Size(740, 480), + Dock = DockStyle.Fill, ReadOnly = true, BackColor = Color.Black, ForeColor = Color.LightGreen, - Font = new Font("Consolas", 10) + Font = new Font("Consolas", 10f) }; - this.Controls.AddRange(new Control[] { statusLabel, startButton, logBox }); - - // Set up game loop timer - var gameTimer = new Timer { Interval = 16 }; // ~60 FPS - gameTimer.Tick += GameLoop; - gameTimer.Start(); - } - - private void ValidateEnvironment() - { - LogMessage("Validating environment..."); - - // Check RSDK environment - if (!Directory.Exists(baseRSDKPath)) + // Create status label + _statusLabel = new Label { - LogMessage("ERROR: RSDK directory not found!"); - return; - } - - // Check Sonic 3 AIR environment - if (!Directory.Exists(sonic3Path)) - { - LogMessage("ERROR: Sonic 3 AIR source directory not found!"); - return; - } - - // Check game data directories - var requiredPaths = new Dictionary - { - { "Sonic 1", sonic1DataPath }, - { "Sonic 2", sonic2DataPath }, - { "Sonic CD", sonicCDDataPath } + Dock = DockStyle.Fill, + Text = "Ready", + TextAlign = ContentAlignment.MiddleLeft, + BorderStyle = BorderStyle.Fixed3D }; - foreach (var path in requiredPaths) - { - if (!Directory.Exists(path.Value)) - { - LogMessage($"ERROR: {path.Key} data directory not found at {path.Value}"); - return; - } - } + // Add controls to layout + mainLayout.Controls.Add(buttonPanel, 0, 0); + mainLayout.Controls.Add(_logBox, 0, 1); + mainLayout.Controls.Add(_statusLabel, 0, 2); - // Check script files - var mainLemonPath = Path.Combine(scriptPath, "main.lemon"); - if (!File.Exists(mainLemonPath)) - { - LogMessage("ERROR: Sonic 3 AIR main script not found!"); - return; - } + Controls.Add(mainLayout); - LogMessage("Environment validation complete"); + // Set up logging + var loggerProvider = new LoggerProvider(Log); + var factory = LoggerFactory.Create(builder => + { + builder.AddProvider(loggerProvider); + builder.AddDebug(); + builder.AddConsole(); + }); } - private void StartGame(object? sender, EventArgs e) + private void InitializeTimer() { - if (engineRunning) + _updateTimer = new System.Windows.Forms.Timer { - LogMessage("Game is already running!"); - return; - } + Interval = 16 // ~60 FPS + }; + _updateTimer.Tick += UpdateTimer_Tick; + _updateTimer.Start(); + } + private void LoadSonic2_Click(object sender, EventArgs e) + { try { - StartRSDK(); + _logger.LogInformation("Loading Sonic 2..."); + if (_rsdkEngine.Initialize("Hybrid-RSDK Main/Data/sonic2.rsdk")) + { + _currentGame = "sonic2"; + _statusLabel.Text = "Running: Sonic 2"; + _loadSonic2Button.Enabled = false; + _logger.LogInformation("Sonic 2 loaded successfully"); + } + else + { + _logger.LogError("Failed to load Sonic 2"); + MessageBox.Show("Failed to load Sonic 2. Please check the log for details.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } catch (Exception ex) { - LogMessage($"Error starting game: {ex.Message}"); - MessageBox.Show($"Failed to start game: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + _logger.LogError(ex, "Error loading Sonic 2"); + MessageBox.Show($"Error loading Sonic 2: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } - private void StartRSDK() + private void LoadSonic3_Click(object sender, EventArgs e) { - LogMessage("Starting RSDK..."); - currentGame = "RSDK"; - statusLabel.Text = "Running: RSDK"; - - // Initialize RSDK - var result = InitRSDKv4(dataPath); - if (result != 0) + try { - throw new Exception("Failed to initialize RSDK"); + _logger.LogInformation("Loading Sonic 3 & Knuckles..."); + if (_oxygenEngine.Initialize("Sonic 3 AIR Main/Oxygen/sonic3air.dll")) + { + _currentGame = "sonic3"; + _statusLabel.Text = "Running: Sonic 3 & Knuckles"; + _loadSonic3Button.Enabled = false; + _logger.LogInformation("Sonic 3 & Knuckles loaded successfully"); + } + else + { + _logger.LogError("Failed to load Sonic 3 & Knuckles"); + MessageBox.Show("Failed to load Sonic 3 & Knuckles. Please check the log for details.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } - - engineRunning = true; - } - - private void StartSonic3() - { - LogMessage("Starting Sonic 3 AIR..."); - currentGame = "SONIC3"; - statusLabel.Text = "Running: Sonic 3 AIR"; - - // Clean up RSDK first - CleanupRSDKv4(); - - // Initialize Oxygen Engine - var result = InitOxygenEngine(scriptPath); - if (result != 0) + catch (Exception ex) { - throw new Exception("Failed to initialize Oxygen Engine"); + _logger.LogError(ex, "Error loading Sonic 3 & Knuckles"); + MessageBox.Show($"Error loading Sonic 3 & Knuckles: {ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } - - // Set initial state - gameState["LAST_GAME"] = "SONIC2"; - SaveGameState(); - - engineRunning = true; - } - - private bool IsDeathEggDefeated() - { - // Check all required conditions for Death Egg completion - return IsInDeathEggZone() && - GetDeathEggRobotState() == 1 && - GetDeathEggExplosionTimer() > 0; - } - - private bool IsInDeathEggZone() - { - // Memory address for current zone ID - const int ADDR_CURRENT_ZONE = 0x203A40; - byte zoneId = Marshal.ReadByte(new IntPtr(ADDR_CURRENT_ZONE)); - return zoneId == 0x0B; // Death Egg Zone ID } - private int GetDeathEggRobotState() + private void UpdateTimer_Tick(object sender, EventArgs e) { - // Memory address for Death Egg Robot state - const int ADDR_ROBOT_STATE = 0x203A44; - return Marshal.ReadByte(new IntPtr(ADDR_ROBOT_STATE)); - } - - private int GetDeathEggExplosionTimer() - { - // Memory address for explosion timer - const int ADDR_EXPLOSION_TIMER = 0x203A48; - return Marshal.ReadByte(new IntPtr(ADDR_EXPLOSION_TIMER)); - } - - private void GameLoop(object? sender, EventArgs e) - { - if (!engineRunning) return; - try { - if (currentGame == "RSDK") + if (_isTransitioning) { - UpdateRSDKv4(); - - // Check for Sonic 2 completion - if (IsDeathEggDefeated()) - { - LogMessage("Death Egg Zone completed! Transitioning to Sonic 3..."); - SaveGameState(); - CleanupRSDKv4(); - StartSonic3(); - } + // Handle transition between games + return; } - else if (currentGame == "Sonic3") + + switch (_currentGame) { - UpdateOxygenEngine(); + case "sonic2": + _rsdkEngine.Update(); + CheckSonic2Completion(); + break; + case "sonic3": + _oxygenEngine.Update(); + break; } } catch (Exception ex) { - LogMessage($"Error in game loop: {ex.Message}"); - engineRunning = false; + _logger.LogError(ex, "Error in update loop"); } } - private void LogMessage(string message) + private void CheckSonic2Completion() { - var timestamp = DateTime.Now.ToString("HH:mm:ss"); - var logMessage = $"[{timestamp}] {message}"; + // TODO: Implement Death Egg zone completion check + // For now, just enable Sonic 3 button for testing + _loadSonic3Button.Enabled = true; + } - try - { - File.AppendAllText(logPath, logMessage + Environment.NewLine); - - this.Invoke(() => - { - logBox.AppendText(logMessage + Environment.NewLine); - logBox.ScrollToCaret(); - }); - } - catch (Exception ex) + private void Log(string message) + { + if (InvokeRequired) { - Debug.WriteLine($"Logging error: {ex.Message}"); + Invoke(new Action(Log), message); + return; } + + _logBox.AppendText(message + Environment.NewLine); + _logBox.ScrollToCaret(); } protected override void OnFormClosing(FormClosingEventArgs e) { - if (engineRunning) - { - LogMessage("Shutting down game engine..."); - if (currentGame == "RSDK") - CleanupRSDKv4(); - else if (currentGame == "SONIC3") - CleanupOxygenEngine(); - } - SaveGameState(); + _updateTimer.Stop(); + _rsdkEngine.Cleanup(); + _oxygenEngine.Cleanup(); base.OnFormClosing(e); } } + + public static class Program + { + [STAThread] + public static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + + var services = new ServiceCollection() + .AddLogging(builder => + { + builder.AddDebug(); + builder.AddConsole(); + }) + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); + + Application.Run(services.GetRequiredService()); + } + } } diff --git a/Custom Client/Tools/RSDKAnalyzer.cs b/Custom Client/Tools/RSDKAnalyzer.cs index d22d3ff1..c683bd2c 100644 --- a/Custom Client/Tools/RSDKAnalyzer.cs +++ b/Custom Client/Tools/RSDKAnalyzer.cs @@ -2,6 +2,7 @@ using System.IO; using System.Text; using System.Collections.Generic; +using Microsoft.Extensions.Logging; namespace SonicHybridUltimate.Tools { diff --git a/build.ps1 b/build.ps1 index 770b4182..af545f67 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,7 +1,8 @@ # Main build script for Sonic Hybrid Ultimate param( [switch]$Release = $false, - [switch]$Clean = $false + [switch]$Clean = $false, + [switch]$SkipNative = $false ) $ErrorActionPreference = "Stop" @@ -12,6 +13,55 @@ function Write-Header($text) { Write-Host "`n=== $text ===" -ForegroundColor Cyan } +function Initialize-VSEnvironment { + Write-Header "Initializing Visual Studio Environment" + + # Try multiple possible Visual Studio paths + $vsInstallPaths = @( + "${env:ProgramFiles}\Microsoft Visual Studio\2022\Community", + "${env:ProgramFiles}\Microsoft Visual Studio\2022\Professional", + "${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Community", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Professional", + "${env:ProgramFiles(x86)}\Microsoft Visual Studio\2022\Enterprise" + ) + + $vcvarsall = $null + foreach ($vsPath in $vsInstallPaths) { + $testPath = Join-Path $vsPath "VC\Auxiliary\Build\vcvarsall.bat" + if (Test-Path $testPath) { + $vcvarsall = $testPath + break + } + } + + if (-not $vcvarsall) { + Write-Host "Visual Studio 2022 with C++ support not found." -ForegroundColor Yellow + Write-Host "You can:" + Write-Host "1. Install Visual Studio 2022 with C++ workload from: https://visualstudio.microsoft.com/" + Write-Host "2. Run this script with -SkipNative to skip building native components" + if (-not $SkipNative) { + throw "Cannot build native components without Visual Studio. Install Visual Studio or use -SkipNative." + } + return $false + } + + Write-Host "Found Visual Studio at: $vcvarsall" + + # Import VS environment variables + $tempFile = [System.IO.Path]::GetTempFileName() + cmd /c "call `"$vcvarsall`" amd64 && set > `"$tempFile`"" + Get-Content $tempFile | ForEach-Object { + if ($_ -match "^(.*?)=(.*)$") { + $name = $matches[1] + $value = $matches[2] + [System.Environment]::SetEnvironmentVariable($name, $value, [System.EnvironmentVariableTarget]::Process) + } + } + Remove-Item $tempFile + return $true +} + function Verify-Dependencies { Write-Header "Checking Dependencies" @@ -22,19 +72,14 @@ function Verify-Dependencies { } Write-Host ".NET SDK Version: $dotnetVersion" - # Check CMake - $cmakeVersion = (cmake --version | Select-Object -First 1) - if (-not $?) { - throw "CMake not found. Please install CMake 3.15 or later." - } - Write-Host "CMake Version: $cmakeVersion" - - # Check Visual Studio - $vsPath = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath - if (-not $vsPath) { - throw "Visual Studio not found. Please install Visual Studio 2022 with C++ support." + # Check CMake if building native components + if (-not $SkipNative) { + $cmakeVersion = (cmake --version | Select-Object -First 1) + if (-not $?) { + throw "CMake not found. Please install CMake 3.15 or later." + } + Write-Host "CMake Version: $cmakeVersion" } - Write-Host "Visual Studio Path: $vsPath" } function Setup-Directories { @@ -43,11 +88,17 @@ function Setup-Directories { # Create necessary directories $dirs = @( "Hybrid-RSDK Main/Data", - "Hybrid-RSDK Main/build", - "Custom Client/bin", - "Sonic 3 AIR Main/Oxygen" + "Custom Client/bin" ) + # Add native build directories if not skipping + if (-not $SkipNative) { + $dirs += @( + "Hybrid-RSDK Main/build", + "Sonic 3 AIR Main/Oxygen" + ) + } + foreach ($dir in $dirs) { $path = Join-Path $rootDir $dir if (-not (Test-Path $path)) { @@ -66,10 +117,16 @@ function Build-CustomClient { } dotnet build -c $buildType - # Copy RSDK and Oxygen DLLs to output + # Copy RSDK and Oxygen DLLs to output if they exist $outputDir = "bin/$buildType/net6.0-windows" - Copy-Item -Path "$rootDir/Hybrid-RSDK Main/build/bin/*.dll" -Destination $outputDir -Force - Copy-Item -Path "$rootDir/Sonic 3 AIR Main/Oxygen/*.dll" -Destination $outputDir -Force + if (-not $SkipNative) { + if (Test-Path "$rootDir/Hybrid-RSDK Main/build/bin") { + Copy-Item -Path "$rootDir/Hybrid-RSDK Main/build/bin/*.dll" -Destination $outputDir -Force + } + if (Test-Path "$rootDir/Sonic 3 AIR Main/Oxygen") { + Copy-Item -Path "$rootDir/Sonic 3 AIR Main/Oxygen/*.dll" -Destination $outputDir -Force + } + } } finally { Pop-Location @@ -85,7 +142,10 @@ function Build-HybridRSDK { Remove-Item -Recurse -Force $buildDir } - cmake -B $buildDir -DCMAKE_BUILD_TYPE=$buildType + # Configure with Visual Studio generator + cmake -B $buildDir -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=$buildType + + # Build using CMake --build cmake --build $buildDir --config $buildType } finally { @@ -103,11 +163,16 @@ function Write-Instructions { Write-Host "2. Run the client:" Write-Host " cd 'Custom Client/bin/$buildType/net6.0-windows'" Write-Host " ./SonicHybridUltimate.exe`n" + + if ($SkipNative) { + Write-Host "Note: Native components were skipped. Install Visual Studio 2022 with C++ support to enable full functionality." -ForegroundColor Yellow + } } try { # Main build process Verify-Dependencies + $hasVS = Initialize-VSEnvironment Setup-Directories if ($Clean) { @@ -115,11 +180,14 @@ try { } # Build components - Build-HybridRSDK # Build RSDK first as Custom Client depends on it + if (-not $SkipNative -and $hasVS) { + Build-HybridRSDK # Build RSDK first as Custom Client depends on it + } Build-CustomClient Write-Instructions } catch { Write-Host "Build failed: $_" -ForegroundColor Red + Write-Host $_.ScriptStackTrace exit 1 }