1 using System; 2 using System.Reflection; 3 using System.Runtime.InteropServices; 4 using System.Windows.Forms; 5 using OpenBve.Graphics; 6 using OpenBve.Input; 7 using OpenTK; 8 using OpenBveApi.FileSystem; 9 using OpenBveApi.Hosts; 10 using OpenBveApi.Interface; 11 using OpenBveApi.Math; 12 using RouteManager2; 13 using Control = OpenBveApi.Interface.Control; 14 15 namespace OpenBve { 16 /// <summary>Provides methods for starting the program, including the Main procedure.</summary> 17 internal static partial class Program { 18 #pragma warning disable IDE1006 // Suppress the VS2017 naming style rule, as this is an external syscall 19 /// <summary>Gets the UID of the current user if running on a Unix based system</summary> 20 /// <returns>The UID</returns> 21 /// <remarks>Used for checking if we are running as ROOT (don't!)</remarks> 22 [DllImport("libc")] getuid()23 private static extern uint getuid(); 24 25 /// <summary>Gets the UID of the current user if running on a Unix based system</summary> 26 /// <returns>The UID</returns> 27 /// <remarks>Used for checking if we are running as SUDO</remarks> 28 [DllImport("libc")] geteuid()29 private static extern uint geteuid(); 30 #pragma warning restore IDE1006 31 32 /// <summary>Stores the current CPU architecture</summary> 33 internal static ImageFileMachine CurrentCPUArchitecture; 34 35 /// <summary>The host API used by this program.</summary> 36 internal static Host CurrentHost = null; 37 38 /// <summary>Information about the file system organization.</summary> 39 internal static FileSystem FileSystem = null; 40 41 /// <summary>If the program is to be restarted, this contains the command-line arguments that should be passed to the process, or a null reference otherwise.</summary> 42 internal static string RestartArguments = null; 43 44 /// <summary>The random number generator used by this program.</summary> 45 internal static readonly Random RandomNumberGenerator = new Random(); 46 47 public static GameWindow currentGameWindow; 48 49 internal static JoystickManager Joysticks; 50 51 internal static NewRenderer Renderer; 52 53 internal static Sounds Sounds; 54 55 internal static CurrentRoute CurrentRoute; 56 57 internal static TrainManager TrainManager; 58 59 // --- functions --- 60 61 /// <summary>Is executed when the program starts.</summary> 62 /// <param name="args">The command-line arguments.</param> 63 [STAThread] Main(string[] args)64 private static void Main(string[] args) { 65 // --- load options and controls --- 66 try 67 { 68 FileSystem = FileSystem.FromCommandLineArgs(args, CurrentHost); 69 FileSystem.CreateFileSystem(); 70 Interface.LoadOptions(); 71 } 72 catch 73 { 74 // ignored 75 } 76 //Switch between SDL2 and native backends; use native backend by default 77 var options = new ToolkitOptions(); 78 CurrentHost = new Host(); 79 if (CurrentHost.Platform == HostPlatform.FreeBSD) 80 { 81 // The OpenTK X11 backend is broken on FreeBSD, so force SDL2 82 options.Backend = PlatformBackend.Default; 83 } 84 else if (Interface.CurrentOptions.PreferNativeBackend) 85 { 86 options.Backend = PlatformBackend.PreferNative; 87 } 88 Toolkit.Init(options); 89 90 // Add handler for UI thread exceptions 91 Application.ThreadException += (CrashHandler.UIThreadException); 92 93 // Force all WinForms errors to go through handler 94 Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); 95 96 // This handler is for catching non-UI thread exceptions 97 AppDomain.CurrentDomain.UnhandledException += (CrashHandler.CurrentDomain_UnhandledException); 98 99 100 //Determine the current CPU architecture- 101 //ARM will generally only support OpenGL-ES 102 PortableExecutableKinds peKind; 103 typeof(object).Module.GetPEKind(out peKind, out CurrentCPUArchitecture); 104 105 Application.EnableVisualStyles(); 106 Application.SetCompatibleTextRenderingDefault(false); 107 108 if (IntPtr.Size == 4) 109 { 110 Joysticks = new JoystickManager32(); 111 } 112 else 113 { 114 Joysticks = new JoystickManager64(); 115 } 116 117 if (CurrentHost.Platform == HostPlatform.FreeBSD) 118 { 119 // BSD seems to need this called at this point to avoid crashing 120 // https://github.com/leezer3/OpenBVE/issues/712 121 Joysticks.RefreshJoysticks(); 122 } 123 124 try { 125 FileSystem = FileSystem.FromCommandLineArgs(args, CurrentHost); 126 FileSystem.CreateFileSystem(); 127 } catch (Exception ex) { 128 MessageBox.Show(Translations.GetInterfaceString("errors_filesystem_invalid") + Environment.NewLine + Environment.NewLine + ex.Message, Translations.GetInterfaceString("program_title"), MessageBoxButtons.OK, MessageBoxIcon.Hand); 129 return; 130 } 131 132 Renderer = new NewRenderer(CurrentHost, Interface.CurrentOptions, FileSystem); 133 Sounds = new Sounds(); 134 CurrentRoute = new CurrentRoute(CurrentHost, Renderer); 135 136 //Platform specific startup checks 137 // --- Check if we're running as root, and prompt not to --- 138 if (CurrentHost.Platform == HostPlatform.GNULinux && (getuid() == 0 || geteuid() == 0)) 139 { 140 MessageBox.Show( 141 "You are currently running as the root user, or via the sudo command." + System.Environment.NewLine + 142 "This is a bad idea, please dont!", Translations.GetInterfaceString("program_title"), MessageBoxButtons.OK, MessageBoxIcon.Hand); 143 } 144 145 146 147 TrainManager = new TrainManager(CurrentHost, Renderer, Interface.CurrentOptions, FileSystem); 148 149 // --- load language --- 150 string folder = Program.FileSystem.GetDataFolder("Languages"); 151 Translations.LoadLanguageFiles(folder); 152 153 folder = Program.FileSystem.GetDataFolder("Cursors"); 154 Cursors.LoadCursorImages(folder); 155 156 Interface.LoadControls(null, out Interface.CurrentControls); 157 folder = Program.FileSystem.GetDataFolder("Controls"); 158 string file = OpenBveApi.Path.CombineFile(folder, "Default keyboard assignment.controls"); 159 Control[] controls; 160 Interface.LoadControls(file, out controls); 161 Interface.AddControls(ref Interface.CurrentControls, controls); 162 163 InputDevicePlugin.LoadPlugins(Program.FileSystem); 164 165 // --- check the command-line arguments for route and train --- 166 formMain.MainDialogResult result = new formMain.MainDialogResult(); 167 CommandLine.ParseArguments(args, ref result); 168 // --- check whether route and train exist --- 169 if (result.RouteFile != null) { 170 if (!System.IO.File.Exists(result.RouteFile)) 171 { 172 result.RouteFile = null; 173 } 174 } 175 if (result.TrainFolder != null) { 176 if (!System.IO.Directory.Exists(result.TrainFolder)) { 177 result.TrainFolder = null; 178 } 179 } 180 // --- if a route was provided but no train, try to use the route default --- 181 if (result.RouteFile != null & result.TrainFolder == null) 182 { 183 string error; 184 if (!CurrentHost.LoadPlugins(FileSystem, Interface.CurrentOptions, out error, TrainManager, Renderer)) 185 { 186 MessageBox.Show(error, @"OpenBVE", MessageBoxButtons.OK, MessageBoxIcon.Error); 187 throw new Exception("Unable to load the required plugins- Please reinstall OpenBVE"); 188 } 189 Game.Reset(false); 190 bool loaded = false; 191 for (int i = 0; i < Program.CurrentHost.Plugins.Length; i++) 192 { 193 if (Program.CurrentHost.Plugins[i].Route != null && Program.CurrentHost.Plugins[i].Route.CanLoadRoute(result.RouteFile)) 194 { 195 object Route = (object)Program.CurrentRoute; //must cast to allow us to use the ref keyword. 196 Program.CurrentHost.Plugins[i].Route.LoadRoute(result.RouteFile, result.RouteEncoding, null, null, null, true, ref Route); 197 Program.CurrentRoute = (CurrentRoute) Route; 198 Program.Renderer.Lighting.OptionAmbientColor = CurrentRoute.Atmosphere.AmbientLightColor; 199 Program.Renderer.Lighting.OptionDiffuseColor = CurrentRoute.Atmosphere.DiffuseLightColor; 200 Program.Renderer.Lighting.OptionLightPosition = CurrentRoute.Atmosphere.LightPosition; 201 loaded = true; 202 break; 203 } 204 } 205 206 if (!CurrentHost.UnloadPlugins(out error)) 207 { 208 MessageBox.Show(error, @"OpenBVE", MessageBoxButtons.OK, MessageBoxIcon.Error); 209 } 210 if (!loaded) 211 { 212 throw new Exception("No plugins capable of loading routefile " + result.RouteFile + " were found."); 213 } 214 if (!string.IsNullOrEmpty(Interface.CurrentOptions.TrainName)) { 215 folder = System.IO.Path.GetDirectoryName(result.RouteFile); 216 while (true) { 217 string trainFolder = OpenBveApi.Path.CombineDirectory(folder, "Train"); 218 if (System.IO.Directory.Exists(trainFolder)) { 219 try 220 { 221 folder = OpenBveApi.Path.CombineDirectory(trainFolder, Interface.CurrentOptions.TrainName); 222 } 223 catch (Exception ex) 224 { 225 if (ex is ArgumentException) 226 { 227 break; 228 } 229 } 230 if (System.IO.Directory.Exists(folder)) { 231 file = OpenBveApi.Path.CombineFile(folder, "train.dat"); 232 if (System.IO.File.Exists(file)) { 233 result.TrainFolder = folder; 234 result.TrainEncoding = System.Text.Encoding.UTF8; 235 for (int j = 0; j < Interface.CurrentOptions.TrainEncodings.Length; j++) { 236 if (string.Compare(Interface.CurrentOptions.TrainEncodings[j].Value, result.TrainFolder, StringComparison.InvariantCultureIgnoreCase) == 0) { 237 result.TrainEncoding = System.Text.Encoding.GetEncoding(Interface.CurrentOptions.TrainEncodings[j].Codepage); 238 break; 239 } 240 } 241 } 242 } break; 243 } 244 if (folder == null) continue; 245 System.IO.DirectoryInfo info = System.IO.Directory.GetParent(folder); 246 if (info != null) { 247 folder = info.FullName; 248 } else { 249 break; 250 } 251 } 252 } 253 Game.Reset(false); 254 } 255 256 // --- show the main menu if necessary --- 257 if (result.RouteFile == null | result.TrainFolder == null) { 258 Joysticks.RefreshJoysticks(); 259 260 if (CurrentHost.Platform == HostPlatform.AppleOSX && IntPtr.Size != 4) 261 { 262 //WinForms are not supported on 64-bit Apple, so show the experimental GL menu 263 result.ExperimentalGLMenu = true; 264 } 265 else 266 { 267 if (!result.ExperimentalGLMenu) 268 { 269 result = formMain.ShowMainDialog(result); 270 } 271 } 272 } else { 273 result.Start = true; 274 //Apply translations 275 Translations.SetInGameLanguage(Translations.CurrentLanguageCode); 276 } 277 278 if (result.ExperimentalGLMenu) 279 { 280 result.Start = true; 281 result.RouteFile = null; 282 result.TrainFolder = null; 283 } 284 285 // --- start the actual program --- 286 if (result.Start) { 287 if (Initialize()) { 288 #if !DEBUG 289 try { 290 #endif 291 MainLoop.StartLoopEx(result); 292 #if !DEBUG 293 } catch (Exception ex) { 294 bool found = false; 295 for (int i = 0; i < TrainManager.Trains.Length; i++) { 296 if (TrainManager.Trains[i] != null && TrainManager.Trains[i].Plugin != null) { 297 if (TrainManager.Trains[i].Plugin.LastException != null) { 298 CrashHandler.LoadingCrash(ex.Message, true); 299 MessageBox.Show("The train plugin " + TrainManager.Trains[i].Plugin.PluginTitle + " caused a runtime exception: " + TrainManager.Trains[i].Plugin.LastException.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Hand); 300 found = true; 301 RestartArguments = ""; 302 break; 303 } 304 } 305 } 306 if (!found) 307 { 308 if (ex is System.DllNotFoundException) 309 { 310 Interface.AddMessage(MessageType.Critical, false, "The required system library " + ex.Message + " was not found on the system."); 311 switch (ex.Message) 312 { 313 case "libopenal.so.1": 314 MessageBox.Show("openAL was not found on this system. \n Please install libopenal1 via your distribtion's package management system.", Translations.GetInterfaceString("program_title"), MessageBoxButtons.OK, MessageBoxIcon.Hand); 315 break; 316 default: 317 MessageBox.Show("The required system library " + ex.Message + " was not found on this system.", Translations.GetInterfaceString("program_title"), MessageBoxButtons.OK, MessageBoxIcon.Hand); 318 break; 319 } 320 } 321 else 322 { 323 Interface.AddMessage(MessageType.Critical, false, "The route and train loader encountered the following critical error: " + ex.Message); 324 CrashHandler.LoadingCrash(ex + Environment.StackTrace, false); 325 } 326 RestartArguments = ""; 327 } 328 } 329 #endif 330 } 331 Deinitialize(); 332 } 333 // --- restart the program if necessary --- 334 if (RestartArguments != null) { 335 string arguments; 336 if (FileSystem.RestartArguments.Length != 0 & RestartArguments.Length != 0) { 337 arguments = FileSystem.RestartArguments + " " + RestartArguments; 338 } else { 339 arguments = FileSystem.RestartArguments + RestartArguments; 340 } 341 try { 342 System.Diagnostics.Process.Start(System.IO.File.Exists(FileSystem.RestartProcess) ? FileSystem.RestartProcess : Application.ExecutablePath, arguments); 343 } catch (Exception ex) { 344 MessageBox.Show(ex.Message + "\n\nProcess = " + FileSystem.RestartProcess + "\nArguments = " + arguments, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error); 345 } 346 } 347 } 348 349 350 /// <summary>Initializes the program. A matching call to deinitialize must be made when the program is terminated.</summary> 351 /// <returns>Whether the initialization was successful.</returns> Initialize()352 private static bool Initialize() 353 { 354 string error; 355 if (!CurrentHost.LoadPlugins(FileSystem, Interface.CurrentOptions, out error, TrainManager, Renderer)) { 356 MessageBox.Show(error, @"OpenBVE", MessageBoxButtons.OK, MessageBoxIcon.Error); 357 return false; 358 } 359 360 Joysticks.RefreshJoysticks(); 361 // begin HACK // 362 Renderer.Camera.VerticalViewingAngle = 45.0.ToRadians(); 363 Renderer.Camera.HorizontalViewingAngle = 2.0 * Math.Atan(Math.Tan(0.5 * Renderer.Camera.VerticalViewingAngle) * Renderer.Screen.AspectRatio); 364 Renderer.Camera.OriginalVerticalViewingAngle = Renderer.Camera.VerticalViewingAngle; 365 Renderer.Camera.ExtraViewingDistance = 50.0; 366 Renderer.Camera.ForwardViewingDistance = (double)Interface.CurrentOptions.ViewingDistance; 367 Renderer.Camera.BackwardViewingDistance = 0.0; 368 Program.CurrentRoute.CurrentBackground.BackgroundImageDistance = (double)Interface.CurrentOptions.ViewingDistance; 369 // end HACK // 370 string programVersion = @"v" + Application.ProductVersion + OpenBve.Program.VersionSuffix; 371 FileSystem.ClearLogFile(programVersion); 372 return true; 373 } 374 375 /// <summary>Deinitializes the program.</summary> Deinitialize()376 private static void Deinitialize() 377 { 378 string error; 379 Program.CurrentHost.UnloadPlugins(out error); 380 Sounds.Deinitialize(); 381 if (currentGameWindow != null) 382 { 383 currentGameWindow.Dispose(); 384 } 385 } 386 387 } 388 } 389