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