diff --git a/makefile b/makefile index df03f3bd27..5651e19664 100644 --- a/makefile +++ b/makefile @@ -268,7 +268,7 @@ create_resource_tmp = $(eval $(call resource_rule_impl, $(firstword $(subst ^, , # OpenBve # ########### -OPEN_BVE_FOLDERS := . Audio Game Graphics Graphics/Renderer Interface OldCode OldCode/NewCode Parsers Properties OldParsers OldParsers/BveRouteParser Simulation/TrainManager Simulation/TrainManager/Train Simulation/World System System/Functions System/Input System/Logging System/Program System/Translations UserInterface +OPEN_BVE_FOLDERS := . Audio Game Graphics Graphics/Renderer Interface OldCode OldCode/NewCode Parsers Properties OldParsers OldParsers/BveRouteParser Simulation/TrainManager Simulation/TrainManager/Train Simulation/TrainPlugins Simulation/World System System/Functions System/Input System/Logging System/Program System/Translations UserInterface OPEN_BVE_FOLDERS := $(addprefix $(OPEN_BVE_ROOT)/, $(OPEN_BVE_FOLDERS)) OPEN_BVE_SRC := $(filter-out "$(OPEN_BVE_ROOT)/Properties/AssemblyInfo.cs",$(patsubst %, "%", $(foreach sdir, $(OPEN_BVE_FOLDERS), $(wildcard $(sdir)/*.cs)))) OPEN_BVE_DOC := $(addprefix /doc:, $(foreach sdir, $(OPEN_BVE_FOLDERS), $(wildcard $(sdir)/*.xml))) diff --git a/source/OpenBVE/Game/Messages.cs b/source/OpenBVE/Game/Messages.cs index d4ab3bc363..6f5b3a3e72 100644 --- a/source/OpenBVE/Game/Messages.cs +++ b/source/OpenBVE/Game/Messages.cs @@ -36,6 +36,17 @@ internal struct Message internal Vector2 RendererPosition; /// The level of alpha used by the renderer whilst fading out the message internal double RendererAlpha; + + internal Message(string Text, MessageDependency Depencency, MessageColor Color, double Timeout) + { + this.InternalText = Text; + this.Depencency = Depencency; + this.Color = Color; + this.Timeout = Timeout; + DisplayText = ""; + RendererPosition = new Vector2(0.0, 0.0); + RendererAlpha = 0.0; + } } /// The current in-game messages @@ -71,11 +82,22 @@ internal static void AddMessage(string Text, MessageDependency Depencency, Inter Messages[n].RendererAlpha = 0.0; } } + + internal static void AddMessage(Message Message) + { + int n = Messages.Length; + Array.Resize(ref Messages, n + 1); + Messages[n] = Message; + Messages[n].Timeout += Game.SecondsSinceMidnight; + } + internal static void AddDebugMessage(string text, double duration) { Game.AddMessage(text, Game.MessageDependency.None, Interface.GameMode.Expert, MessageColor.Magenta, Game.SecondsSinceMidnight + duration); } + + /// The number 1km/h must be multiplied by to produce your desired speed units, or 0.0 to disable this internal static double SpeedConversionFactor = 0.0; /// The unit of speed displayed in in-game messages diff --git a/source/OpenBVE/OldCode/Loading.cs b/source/OpenBVE/OldCode/Loading.cs index 225ca30bcd..5b4c0a9307 100644 --- a/source/OpenBVE/OldCode/Loading.cs +++ b/source/OpenBVE/OldCode/Loading.cs @@ -1,7 +1,9 @@ using System; +using System.Collections.Generic; using System.Text; using System.Windows.Forms; using System.Threading; +using OpenBveApi.Colors; using OpenBveApi.Math; namespace OpenBve { @@ -29,8 +31,10 @@ internal static class Loading { private static Encoding CurrentTrainEncoding; internal static double TrainProgressCurrentSum; internal static double TrainProgressCurrentWeight; - /// Stores the plugin error message string, or a null reference if no error encountered - internal static string PluginError; + /// The queue of messages to be displayed once the game has loaded + internal static List MessageQueue = new List(); + /// The color to be used for the plugin message + internal static MessageColor PluginMessageColor = MessageColor.Red; // load /// Initializes loading the route and train asynchronously. Set the Loading.Cancel member to cancel loading. Check the Loading.Complete member to see when loading has finished. diff --git a/source/OpenBVE/OldCode/NewCode/GameWindow.cs b/source/OpenBVE/OldCode/NewCode/GameWindow.cs index 295787a211..010757cc93 100644 --- a/source/OpenBVE/OldCode/NewCode/GameWindow.cs +++ b/source/OpenBVE/OldCode/NewCode/GameWindow.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading; using System.Windows.Forms; +using OpenBveApi.Colors; using OpenTK; using OpenTK.Graphics; using GL = OpenTK.Graphics.OpenGL.GL; @@ -665,10 +666,12 @@ private void SetupSimulation() Game.RouteInformation.ErrorsAndWarnings = Messages; //Print the plugin error encountered (If any) for 10s //This must be done after the simulation has init, as otherwise the timeout doesn't work - if (Loading.PluginError != null) + if (Loading.MessageQueue.Count > 0) { - Game.AddMessage(Loading.PluginError, Game.MessageDependency.None, Interface.GameMode.Expert, OpenBveApi.Colors.MessageColor.Red, Game.SecondsSinceMidnight + 5.0); - Game.AddMessage(Interface.GetInterfaceString("errors_plugin_failure2"), Game.MessageDependency.None, Interface.GameMode.Expert, OpenBveApi.Colors.MessageColor.Red, Game.SecondsSinceMidnight + 5.0); + foreach (var message in Loading.MessageQueue) + { + Game.AddMessage(message); + } } } loadComplete = true; diff --git a/source/OpenBVE/OldCode/PluginManager.cs b/source/OpenBVE/OldCode/PluginManager.cs deleted file mode 100644 index 8eb8f61d47..0000000000 --- a/source/OpenBVE/OldCode/PluginManager.cs +++ /dev/null @@ -1,653 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using OpenBveApi.Colors; -using OpenBveApi.Runtime; - -namespace OpenBve { - internal static partial class PluginManager { - - /// Represents an abstract plugin. - internal abstract class Plugin { - - // --- members --- - /// The file title of the plugin, including the file extension. - internal string PluginTitle; - /// Whether the plugin is the default ATS/ATC plugin. - internal bool IsDefault; - /// Whether the plugin returned valid information in the last Elapse call. - internal bool PluginValid; - /// The debug message the plugin returned in the last Elapse call. - internal string PluginMessage; - /// The train the plugin is attached to. - internal TrainManager.Train Train; - /// The array of panel variables. - internal int[] Panel; - /// Whether the plugin supports the AI. - internal bool SupportsAI; - /// The last in-game time reported to the plugin. - internal double LastTime; - /// The last reverser reported to the plugin. - internal int LastReverser; - /// The last power notch reported to the plugin. - internal int LastPowerNotch; - /// The last brake notch reported to the plugin. - internal int LastBrakeNotch; - /// The last aspects per relative section reported to the plugin. Section 0 is the current section, section 1 the upcoming section, and so on. - internal int[] LastAspects; - /// The absolute section the train was last. - internal int LastSection; - /// The last exception the plugin raised. - internal Exception LastException; - //NEW: Whether this plugin can disable the time acceleration factor - /// Whether this plugin can disable time acceleration. - internal static bool DisableTimeAcceleration; - /// The current camera view mode - internal OpenBveApi.Runtime.CameraViewMode CurrentCameraViewMode; - - private List currentRouteStations; - internal bool StationsLoaded; - // --- functions --- - /// Called to load and initialize the plugin. - /// The train specifications. - /// The initialization mode of the train. - /// Whether loading the plugin was successful. - internal abstract bool Load(VehicleSpecs specs, InitializationModes mode); - /// Called to unload the plugin. - internal abstract void Unload(); - /// Called before the train jumps to a different location. - /// The initialization mode of the train. - internal abstract void BeginJump(InitializationModes mode); - /// Called when the train has finished jumping to a different location. - internal abstract void EndJump(); - /// Called every frame to update the plugin. - internal void UpdatePlugin() { - /* - * Prepare the vehicle state. - * */ - double location = this.Train.Cars[0].FrontAxle.Follower.TrackPosition - this.Train.Cars[0].FrontAxlePosition + 0.5 * this.Train.Cars[0].Length; - //Curve Radius, Cant and Pitch Added - double CurrentRadius = this.Train.Cars[0].FrontAxle.Follower.CurveRadius; - double CurrentCant = this.Train.Cars[0].FrontAxle.Follower.CurveCant; - double CurrentPitch = this.Train.Cars[0].FrontAxle.Follower.Pitch; - //If the list of stations has not been loaded, do so - if (!StationsLoaded) - { - currentRouteStations = new List(); - foreach (Game.Station selectedStation in Game.Stations) - { - Station i = new Station - { - Name = selectedStation.Name, - ArrivalTime = selectedStation.ArrivalTime, - DepartureTime = selectedStation.DepartureTime, - StopTime = selectedStation.StopTime, - OpenLeftDoors = selectedStation.OpenLeftDoors, - OpenRightDoors = selectedStation.OpenRightDoors, - ForceStopSignal = selectedStation.ForceStopSignal, - DefaultTrackPosition = selectedStation.DefaultTrackPosition - }; - currentRouteStations.Add(i); - } - StationsLoaded = true; - } - //End of additions - double speed = this.Train.Cars[this.Train.DriverCar].Specs.CurrentPerceivedSpeed; - double bcPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.BrakeCylinderCurrentPressure; - double mrPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.MainReservoirCurrentPressure; - double erPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.EqualizingReservoirCurrentPressure; - double bpPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.BrakePipeCurrentPressure; - double sapPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.StraightAirPipeCurrentPressure; - VehicleState vehicle = new VehicleState(location, new Speed(speed), bcPressure, mrPressure, erPressure, bpPressure, sapPressure, CurrentRadius, CurrentCant, CurrentPitch); - /* - * Prepare the preceding vehicle state. - * */ - double bestLocation = double.MaxValue; - double bestSpeed = 0.0; - for (int i = 0; i < TrainManager.Trains.Length; i++) { - if (TrainManager.Trains[i] != this.Train & TrainManager.Trains[i].State == TrainManager.TrainState.Available) { - int c = TrainManager.Trains[i].Cars.Length - 1; - double z = TrainManager.Trains[i].Cars[c].RearAxle.Follower.TrackPosition - TrainManager.Trains[i].Cars[c].RearAxlePosition - 0.5 * TrainManager.Trains[i].Cars[c].Length; - if (z >= location & z < bestLocation) { - bestLocation = z; - bestSpeed = TrainManager.Trains[i].Specs.CurrentAverageSpeed; - } - } - } - var precedingVehicle = bestLocation != double.MaxValue ? new PrecedingVehicleState(bestLocation, bestLocation - location, new Speed(bestSpeed)) : null; - /* - * Get the driver handles. - * */ - Handles handles = GetHandles(); - /* - * Update the plugin. - * */ - double totalTime = Game.SecondsSinceMidnight; - double elapsedTime = Game.SecondsSinceMidnight - LastTime; - /* - * Set the current camera view mode - * Could probably do away with the CurrentCameraViewMode and use a direct cast?? - * - */ - CurrentCameraViewMode = (OpenBveApi.Runtime.CameraViewMode)World.CameraMode; - ElapseData data = new ElapseData(vehicle, precedingVehicle, handles, new Time(totalTime), new Time(elapsedTime), currentRouteStations, CurrentCameraViewMode, Interface.CurrentLanguageCode); - LastTime = Game.SecondsSinceMidnight; - Elapse(data); - this.PluginMessage = data.DebugMessage; - DisableTimeAcceleration = data.DisableTimeAcceleration; - /* - * Set the virtual handles. - * */ - this.PluginValid = true; - SetHandles(data.Handles, true); - } - /// Gets the driver handles. - /// The driver handles. - private Handles GetHandles() { - int reverser = this.Train.Specs.CurrentReverser.Driver; - int powerNotch = this.Train.Specs.CurrentPowerNotch.Driver; - int brakeNotch; - if (this.Train.Cars[this.Train.DriverCar].Specs.BrakeType == TrainManager.CarBrakeType.AutomaticAirBrake) { - brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? 3 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Service ? 2 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Lap ? 1 : 0; - } else { - if (this.Train.Specs.HasHoldBrake) { - brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 2 : this.Train.Specs.CurrentBrakeNotch.Driver > 0 ? this.Train.Specs.CurrentBrakeNotch.Driver + 1 : this.Train.Specs.CurrentHoldBrake.Driver ? 1 : 0; - } else { - brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 1 : this.Train.Specs.CurrentBrakeNotch.Driver; - } - } - bool constSpeed = this.Train.Specs.CurrentConstSpeed; - return new Handles(reverser, powerNotch, brakeNotch, constSpeed); - } - /// Sets the driver handles or the virtual handles. - /// The handles. - /// Whether to set the virtual handles. - private void SetHandles(Handles handles, bool virtualHandles) { - /* - * Process the handles. - */ - if (this.Train.Specs.SingleHandle & handles.BrakeNotch != 0) { - handles.PowerNotch = 0; - } - /* - * Process the reverser. - */ - if (handles.Reverser >= -1 & handles.Reverser <= 1) { - if (virtualHandles) { - this.Train.Specs.CurrentReverser.Actual = handles.Reverser; - } else { - TrainManager.ApplyReverser(this.Train, handles.Reverser, false); - } - } else { - if (virtualHandles) { - this.Train.Specs.CurrentReverser.Actual = this.Train.Specs.CurrentReverser.Driver; - } - this.PluginValid = false; - } - /* - * Process the power. - * */ - if (handles.PowerNotch >= 0 & handles.PowerNotch <= this.Train.Specs.MaximumPowerNotch) { - if (virtualHandles) { - this.Train.Specs.CurrentPowerNotch.Safety = handles.PowerNotch; - } else { - TrainManager.ApplyNotch(this.Train, handles.PowerNotch, false, 0, true); - } - } else { - if (virtualHandles) { - this.Train.Specs.CurrentPowerNotch.Safety = this.Train.Specs.CurrentPowerNotch.Driver; - } - this.PluginValid = false; - } -// if (handles.BrakeNotch != 0) { -// if (virtualHandles) { -// this.Train.Specs.CurrentPowerNotch.Safety = 0; -// } -// } - /* - * Process the brakes. - * */ - if (virtualHandles) { - this.Train.Specs.CurrentEmergencyBrake.Safety = false; - this.Train.Specs.CurrentHoldBrake.Actual = false; - } - if (this.Train.Cars[this.Train.DriverCar].Specs.BrakeType == TrainManager.CarBrakeType.AutomaticAirBrake) { - if (handles.BrakeNotch == 0) { - if (virtualHandles) { - this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Release; - } else { - TrainManager.UnapplyEmergencyBrake(this.Train); - TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Release); - } - } else if (handles.BrakeNotch == 1) { - if (virtualHandles) { - this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Lap; - } else { - TrainManager.UnapplyEmergencyBrake(this.Train); - TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Lap); - } - } else if (handles.BrakeNotch == 2) { - if (virtualHandles) { - this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Service; - } else { - TrainManager.UnapplyEmergencyBrake(this.Train); - TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Release); - } - } else if (handles.BrakeNotch == 3) { - if (virtualHandles) { - this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Service; - this.Train.Specs.CurrentEmergencyBrake.Safety = true; - } else { - TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Service); - TrainManager.ApplyEmergencyBrake(this.Train); - } - } else { - this.PluginValid = false; - } - } else { - if (this.Train.Specs.HasHoldBrake) { - if (handles.BrakeNotch == this.Train.Specs.MaximumBrakeNotch + 2) { - if (virtualHandles) { - this.Train.Specs.CurrentEmergencyBrake.Safety = true; - this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.MaximumBrakeNotch; - } else { - TrainManager.ApplyHoldBrake(this.Train, false); - TrainManager.ApplyNotch(this.Train, 0, true, this.Train.Specs.MaximumBrakeNotch, false); - TrainManager.ApplyEmergencyBrake(this.Train); - } - } else if (handles.BrakeNotch >= 2 & handles.BrakeNotch <= this.Train.Specs.MaximumBrakeNotch + 1) { - if (virtualHandles) { - this.Train.Specs.CurrentBrakeNotch.Safety = handles.BrakeNotch - 1; - } else { - TrainManager.UnapplyEmergencyBrake(this.Train); - TrainManager.ApplyHoldBrake(this.Train, false); - TrainManager.ApplyNotch(this.Train, 0, true, handles.BrakeNotch - 1, false); - } - } else if (handles.BrakeNotch == 1) { - if (virtualHandles) { - this.Train.Specs.CurrentBrakeNotch.Safety = 0; - this.Train.Specs.CurrentHoldBrake.Actual = true; - } else { - TrainManager.UnapplyEmergencyBrake(this.Train); - TrainManager.ApplyNotch(this.Train, 0, true, 0, false); - TrainManager.ApplyHoldBrake(this.Train, true); - } - } else if (handles.BrakeNotch == 0) { - if (virtualHandles) { - this.Train.Specs.CurrentBrakeNotch.Safety = 0; - } else { - TrainManager.UnapplyEmergencyBrake(this.Train); - TrainManager.ApplyNotch(this.Train, 0, true, 0, false); - TrainManager.ApplyHoldBrake(this.Train, false); - } - } else { - if (virtualHandles) { - this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.CurrentBrakeNotch.Driver; - } - this.PluginValid = false; - } - } else { - if (handles.BrakeNotch == this.Train.Specs.MaximumBrakeNotch + 1) { - if (virtualHandles) { - this.Train.Specs.CurrentEmergencyBrake.Safety = true; - this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.MaximumBrakeNotch; - } else { - TrainManager.ApplyHoldBrake(this.Train, false); - TrainManager.ApplyEmergencyBrake(this.Train); - } - } else if (handles.BrakeNotch >= 0 & handles.BrakeNotch <= this.Train.Specs.MaximumBrakeNotch | this.Train.Specs.CurrentBrakeNotch.DelayedChanges.Length == 0) { - if (virtualHandles) { - this.Train.Specs.CurrentBrakeNotch.Safety = handles.BrakeNotch; - } else { - TrainManager.UnapplyEmergencyBrake(this.Train); - TrainManager.ApplyNotch(this.Train, 0, true, handles.BrakeNotch, false); - } - } else { - if (virtualHandles) { - this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.CurrentBrakeNotch.Driver; - } - this.PluginValid = false; - } - } - } - /* - * Process the const speed system. - * */ - this.Train.Specs.CurrentConstSpeed = handles.ConstSpeed & this.Train.Specs.HasConstSpeed; - } - /// Called every frame to update the plugin. - /// The data passed to the plugin on Elapse. - /// This function should not be called directly. Call UpdatePlugin instead. - internal abstract void Elapse(ElapseData data); - /// Called to update the reverser. This invokes a call to SetReverser only if a change actually occured. - internal void UpdateReverser() { - int reverser = this.Train.Specs.CurrentReverser.Driver; - if (reverser != this.LastReverser) { - this.LastReverser = reverser; - SetReverser(reverser); - } - } - /// Called to indicate a change of the reverser. - /// The reverser. - /// This function should not be called directly. Call UpdateReverser instead. - internal abstract void SetReverser(int reverser); - /// Called to update the power notch. This invokes a call to SetPower only if a change actually occured. - internal void UpdatePower() { - int powerNotch = this.Train.Specs.CurrentPowerNotch.Driver; - if (powerNotch != this.LastPowerNotch) { - this.LastPowerNotch = powerNotch; - SetPower(powerNotch); - } - } - /// Called to indicate a change of the power notch. - /// The power notch. - /// This function should not be called directly. Call UpdatePower instead. - internal abstract void SetPower(int powerNotch); - /// Called to update the brake notch. This invokes a call to SetBrake only if a change actually occured. - internal void UpdateBrake() { - int brakeNotch; - if (this.Train.Cars[this.Train.DriverCar].Specs.BrakeType == TrainManager.CarBrakeType.AutomaticAirBrake) { - if (this.Train.Specs.HasHoldBrake) { - brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? 4 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Service ? 3 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Lap ? 2 : this.Train.Specs.CurrentHoldBrake.Driver ? 1 : 0; - } else { - brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? 3 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Service ? 2 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Lap ? 1 : 0; - } - } else { - if (this.Train.Specs.HasHoldBrake) { - brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 2 : this.Train.Specs.CurrentBrakeNotch.Driver > 0 ? this.Train.Specs.CurrentBrakeNotch.Driver + 1 : this.Train.Specs.CurrentHoldBrake.Driver ? 1 : 0; - } else { - brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 1 : this.Train.Specs.CurrentBrakeNotch.Driver; - } - } - if (brakeNotch != this.LastBrakeNotch) { - this.LastBrakeNotch = brakeNotch; - SetBrake(brakeNotch); - } - } - /// Called to indicate a change of the brake notch. - /// The brake notch. - /// This function should not be called directly. Call UpdateBrake instead. - internal abstract void SetBrake(int brakeNotch); - /// Called when a virtual key is pressed. - internal abstract void KeyDown(VirtualKeys key); - /// Called when a virtual key is released. - internal abstract void KeyUp(VirtualKeys key); - /// Called when a horn is played or stopped. - internal abstract void HornBlow(HornTypes type); - /// Called when the state of the doors changes. - internal abstract void DoorChange(DoorStates oldState, DoorStates newState); - /// Called to update the aspects of the section. This invokes a call to SetSignal only if a change in aspect occured or when changing section boundaries. - /// The sections to submit to the plugin. - internal void UpdateSignals(SignalData[] data) { - if (data.Length != 0) { - bool update; - if (this.Train.CurrentSectionIndex != this.LastSection) { - update = true; - } else if (data.Length != this.LastAspects.Length) { - update = true; - } else { - update = false; - for (int i = 0; i < data.Length; i++) { - if (data[i].Aspect != this.LastAspects[i]) { - update = true; - break; - } - } - } - if (update) { - SetSignal(data); - this.LastAspects = new int[data.Length]; - for (int i = 0; i < data.Length; i++) { - this.LastAspects[i] = data[i].Aspect; - } - } - } - } - /// Is called when the aspect in the current or any of the upcoming sections changes. - /// Signal information per section. In the array, index 0 is the current section, index 1 the upcoming section, and so on. - /// This function should not be called directly. Call UpdateSignal instead. - internal abstract void SetSignal(SignalData[] signal); - /// Called when the train passes a beacon. - /// The beacon type. - /// The section the beacon is attached to, or -1 for the next red signal. - /// Optional data attached to the beacon. - internal void UpdateBeacon(int type, int sectionIndex, int optional) { - if (sectionIndex == -1) { - sectionIndex = this.Train.CurrentSectionIndex + 1; - SignalData signal = null; - while (sectionIndex < Game.Sections.Length) { - signal = Game.GetPluginSignal(this.Train, sectionIndex); - if (signal.Aspect == 0) break; - sectionIndex++; - } - if (sectionIndex < Game.Sections.Length) { - SetBeacon(new BeaconData(type, optional, signal)); - } else { - SetBeacon(new BeaconData(type, optional, new SignalData(-1, double.MaxValue))); - } - } - if (sectionIndex >= 0) { - SignalData signal; - if (sectionIndex < Game.Sections.Length) { - signal = Game.GetPluginSignal(this.Train, sectionIndex); - } else { - signal = new SignalData(0, double.MaxValue); - } - SetBeacon(new BeaconData(type, optional, signal)); - } else { - SetBeacon(new BeaconData(type, optional, new SignalData(-1, double.MaxValue))); - } - } - /// Called when the train passes a beacon. - /// The beacon data. - /// This function should not be called directly. Call UpdateBeacon instead. - internal abstract void SetBeacon(BeaconData beacon); - /// Updates the AI. - /// The AI response. - internal AIResponse UpdateAI() { - if (this.SupportsAI) { - AIData data = new AIData(GetHandles()); - this.PerformAI(data); - if (data.Response != AIResponse.None) { - SetHandles(data.Handles, false); - } - return data.Response; - } else { - return AIResponse.None; - } - } - /// Called when the AI should be performed. - /// The AI data. - /// This function should not be called directly. Call UpdateAI instead. - internal abstract void PerformAI(AIData data); - - } - - /// Loads a custom plugin for the specified train. - /// The train to attach the plugin to. - /// The absolute path to the train folder. - /// The encoding to be used. - /// Whether the plugin was loaded successfully. - internal static bool LoadCustomPlugin(TrainManager.Train train, string trainFolder, System.Text.Encoding encoding) { - string config = OpenBveApi.Path.CombineFile(trainFolder, "ats.cfg"); - if (!System.IO.File.Exists(config)) { - return false; - } - string[] lines = System.IO.File.ReadAllLines(config, encoding); - if (lines.Length == 0) { - return false; - } - string file = OpenBveApi.Path.CombineFile(trainFolder, lines[0]); - string title = System.IO.Path.GetFileName(file); - if (!System.IO.File.Exists(file)) { - Interface.AddMessage(Interface.MessageType.Error, true, "The train plugin " + title + " could not be found in " + config); - return false; - } - Program.AppendToLogFile("Loading train plugin: " + file); - bool success = LoadPlugin(train, file, trainFolder); - if (success == false) - { - Loading.PluginError = Interface.GetInterfaceString("errors_plugin_failure1").Replace("[plugin]", file); - } - else - { - Program.AppendToLogFile("Train plugin loaded successfully."); - } - return success; - } - - /// Loads the default plugin for the specified train. - /// The train to attach the plugin to. - /// The train folder. - /// Whether the plugin was loaded successfully. - internal static bool LoadDefaultPlugin(TrainManager.Train train, string trainFolder) { - string file = OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("Plugins"), "OpenBveAts.dll"); - bool success = LoadPlugin(train, file, trainFolder); - if (success) { - train.Plugin.IsDefault = true; - SoundCfgParser.LoadDefaultPluginSounds(train, trainFolder); - } - return success; - } - - /// Loads the specified plugin for the specified train. - /// The train to attach the plugin to. - /// The file to the plugin. - /// The train folder. - /// Whether the plugin was loaded successfully. - private static bool LoadPlugin(TrainManager.Train train, string pluginFile, string trainFolder) { - string pluginTitle = System.IO.Path.GetFileName(pluginFile); - if (!System.IO.File.Exists(pluginFile)) { - Interface.AddMessage(Interface.MessageType.Error, true, "The train plugin " + pluginTitle + " could not be found."); - return false; - } - /* - * Unload plugin if already loaded. - * */ - if (train.Plugin != null) { - UnloadPlugin(train); - } - /* - * Prepare initialization data for the plugin. - * */ - BrakeTypes brakeType = (BrakeTypes)train.Cars[train.DriverCar].Specs.BrakeType; - int brakeNotches; - int powerNotches; - bool hasHoldBrake; - if (brakeType == BrakeTypes.AutomaticAirBrake) { - brakeNotches = 2; - powerNotches = train.Specs.MaximumPowerNotch; - hasHoldBrake = false; - } else { - brakeNotches = train.Specs.MaximumBrakeNotch + (train.Specs.HasHoldBrake ? 1 : 0); - powerNotches = train.Specs.MaximumPowerNotch; - hasHoldBrake = train.Specs.HasHoldBrake; - } - int cars = train.Cars.Length; - VehicleSpecs specs = new VehicleSpecs(powerNotches, brakeType, brakeNotches, hasHoldBrake, cars); - InitializationModes mode = (InitializationModes)Game.TrainStart; - /* - * Check if the plugin is a .NET plugin. - * */ - Assembly assembly; - try { - assembly = Assembly.LoadFile(pluginFile); - } catch (BadImageFormatException) { - assembly = null; - } catch (Exception ex) { - Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " could not be loaded due to the following exception: " + ex.Message); - return false; - } - if (assembly != null) { - Type[] types; - try { - types = assembly.GetTypes(); - } catch (ReflectionTypeLoadException ex) { - foreach (Exception e in ex.LoaderExceptions) { - Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " raised an exception on loading: " + e.Message); - } - return false; - } - foreach (Type type in types) { - if (typeof(IRuntime).IsAssignableFrom(type)) { - IRuntime api = assembly.CreateInstance(type.FullName) as IRuntime; - train.Plugin = new NetPlugin(pluginFile, trainFolder, api, train); - if (train.Plugin.Load(specs, mode)) { - return true; - } else { - train.Plugin = null; - return false; - } - } - } - Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " does not export a train interface and therefore cannot be used with openBVE."); - return false; - } - /* - * Check if the plugin is a Win32 plugin. - * - */ - try { - if (!CheckWin32Header(pluginFile)) { - Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " is of an unsupported binary format and therefore cannot be used with openBVE."); - return false; - } - } catch (Exception ex) { - Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " could not be read due to the following reason: " + ex.Message); - return false; - } - if (!Program.CurrentlyRunningOnWindows | IntPtr.Size != 4) { - Interface.AddMessage(Interface.MessageType.Warning, false, "The train plugin " + pluginTitle + " can only be used on 32-bit Microsoft Windows or compatible."); - return false; - } - if (Program.CurrentlyRunningOnWindows && !System.IO.File.Exists(AppDomain.CurrentDomain.BaseDirectory + "\\AtsPluginProxy.dll")) - { - Interface.AddMessage(Interface.MessageType.Warning, false, "AtsPluginProxy.dll is missing or corrupt- Please reinstall."); - return false; - } - train.Plugin = new Win32Plugin(pluginFile, train); - if (train.Plugin.Load(specs, mode)) { - return true; - } else { - train.Plugin = null; - Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " does not export a train interface and therefore cannot be used with openBVE."); - return false; - } - } - - /// Checks whether a specified file is a valid Win32 plugin. - /// The file to check. - /// Whether the file is a valid Win32 plugin. - private static bool CheckWin32Header(string file) { - using (System.IO.FileStream stream = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read)) { - using (System.IO.BinaryReader reader = new System.IO.BinaryReader(stream)) { - if (reader.ReadUInt16() != 0x5A4D) { - /* Not MZ signature */ - return false; - } - stream.Position = 0x3C; - stream.Position = reader.ReadInt32(); - if (reader.ReadUInt32() != 0x00004550) { - /* Not PE signature */ - return false; - } - if (reader.ReadUInt16() != 0x014C) { - /* Not IMAGE_FILE_MACHINE_I386 */ - return false; - } - } - } - return true; - } - - /// Unloads the currently loaded plugin, if any. - /// Unloads the plugin for the specified train. - internal static void UnloadPlugin(TrainManager.Train train) { - if (train.Plugin != null) { - train.Plugin.Unload(); - train.Plugin = null; - } - } - - } -} \ No newline at end of file diff --git a/source/OpenBVE/OldCode/TrainManager.cs b/source/OpenBVE/OldCode/TrainManager.cs index 83fe05c951..58e278eeb7 100644 --- a/source/OpenBVE/OldCode/TrainManager.cs +++ b/source/OpenBVE/OldCode/TrainManager.cs @@ -3135,6 +3135,15 @@ private static void UpdateSpeeds(Train Train, double TimeElapsed) // update train physics and controls private static void UpdateTrainPhysicsAndControls(Train Train, double TimeElapsed) { + if (Train.Plugin == null) + { + //If the safety system has crashed, set it's handles to be that of the driver + Train.Specs.CurrentReverser.Actual = Train.Specs.CurrentReverser.Driver; + Train.Specs.CurrentEmergencyBrake.Safety = Train.Specs.CurrentEmergencyBrake.Driver; + Train.Specs.CurrentBrakeNotch.Safety = Train.Specs.CurrentBrakeNotch.Driver; + Train.Specs.AirBrake.Handle.Safety = Train.Specs.AirBrake.Handle.Driver; + Train.Specs.CurrentPowerNotch.Safety = Train.Specs.CurrentPowerNotch.Driver; + } if (TimeElapsed == 0.0) { return; diff --git a/source/OpenBVE/OpenBve.csproj b/source/OpenBVE/OpenBve.csproj index 85ebb16171..6836d74ffb 100644 --- a/source/OpenBVE/OpenBve.csproj +++ b/source/OpenBVE/OpenBve.csproj @@ -143,6 +143,8 @@ + + @@ -169,9 +171,6 @@ formAbout.cs - - Form - @@ -249,22 +248,26 @@ formMain.cs Form + + + formMain.cs + Form - + PluginManager.cs - + PluginManager.cs - + diff --git a/source/OpenBVE/Simulation/TrainPlugins/Blacklist.cs b/source/OpenBVE/Simulation/TrainPlugins/Blacklist.cs new file mode 100644 index 0000000000..6301b1602c --- /dev/null +++ b/source/OpenBVE/Simulation/TrainPlugins/Blacklist.cs @@ -0,0 +1,292 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.Remoting.Messaging; +using System.Security.Cryptography; +using System.Xml; + + +#pragma warning disable 660,661 + +namespace OpenBve +{ + internal static partial class PluginManager + { + internal struct BlackListEntry + { + /// The file length of the blacklisted plugin + internal double FileLength; + /// The file name of the blacklisted plugin + internal string FileName; + /// The MD5 sum of the plugin file + internal string MD5; + /// The train-name if this plugin is blacklisted for one train only + internal string Train; + /// A textual string describing the reason this plugin is blacklisted + internal string Reason; + /// Whether all possible versions of this plugin are blacklisted (Overrides MD5 and length) + internal bool AllVersions; + + public static bool operator ==(BlackListEntry a, BlackListEntry b) + { + if (a.FileLength != b.FileLength) return false; + if (a.FileName != b.FileName) return false; + if (a.MD5 != b.MD5) return false; + if ((a.Train ?? "") != (b.Train ?? "")) return false; + if ((a.Reason ?? "") != (b.Reason ?? "")) return false; + if (a.AllVersions != b.AllVersions) return false; + return true; + } + + public static bool operator !=(BlackListEntry a, BlackListEntry b) + { + if (a.FileLength != b.FileLength) return true; + if (a.FileName != b.FileName) return true; + if (a.MD5 != b.MD5) return true; + if ((a.Train ?? "") != (b.Train ?? "")) return true; + if ((a.Reason ?? "") != (b.Reason ?? "")) return true; + if (a.AllVersions != b.AllVersions) return true; + return false; + } + } + + /// The currently blacklisted plugins + internal static List BlackListedPlugins; + + /// Checks whether the specified plugin is blacklisted + /// True if blacklisted, false otherwise + internal static bool CheckBlackList(string filePath, string trainFolder) + { + string pluginTitle = System.IO.Path.GetFileName(filePath); + if (BlackListedPlugins == null || BlackListedPlugins.Count == 0) + { + return false; + } + var n = System.IO.Path.GetFileName(filePath); + for (int i = 0; i < BlackListedPlugins.Count; i++) + { + if (BlackListedPlugins[i].FileName == n.ToLowerInvariant()) + { + if (BlackListedPlugins[i].AllVersions == true) + { + Interface.AddMessage(Interface.MessageType.Error, true, "The train plugin " + pluginTitle + " is blacklisted for the following reason:"); + if (BlackListedPlugins[i].Reason == String.Empty) + { + Interface.AddMessage(Interface.MessageType.Error, true, "No reason specified."); + } + else + { + Interface.AddMessage(Interface.MessageType.Error, true, BlackListedPlugins[i].Reason); + } + return true; + } + var fi = new FileInfo(filePath); + if (fi.Length == BlackListedPlugins[i].FileLength && (BlackListedPlugins[i].Train == null || trainFolder.ToLowerInvariant() == BlackListedPlugins[i].Train.ToLowerInvariant())) + { + var md5 = MD5.Create(); + using (var stream = File.OpenRead(filePath)) + { + md5.ComputeHash(stream); + } + //String.Empty is required, as otherwise it adds a null character..... + string s = BitConverter.ToString(md5.Hash).Replace("-", String.Empty).ToUpper(); + if (s == BlackListedPlugins[i].MD5) + { + Interface.AddMessage(Interface.MessageType.Error, true, "The train plugin " + pluginTitle + " is blacklisted for the following reason:"); + if (BlackListedPlugins[i].Reason == String.Empty) + { + Interface.AddMessage(Interface.MessageType.Error, true, "No reason specified."); + } + else + { + Interface.AddMessage(Interface.MessageType.Error, true, BlackListedPlugins[i].Reason); + } + return true; + } + } + + + } + } + return false; + } + + /// Checks whether the specified plugin entry is in the blacklist database + /// The plugin entry + internal static bool CheckBlackList(BlackListEntry plugin) + { + if (BlackListedPlugins == null || BlackListedPlugins.Count == 0) + { + return false; + } + for (int i = 0; i < BlackListedPlugins.Count; i++) + { + if (plugin == BlackListedPlugins[i]) + { + return true; + } + } + return false; + } + + /// Loads the database of blacklisted plugins from disk (Called once on startup) + /// The database path + internal static void LoadBlackListDatabase(string databasePath) + { + if (!System.IO.File.Exists(databasePath)) + { + return; + } + BlackListedPlugins = new List(); + XmlDocument currentXML = new XmlDocument(); + //Load the XML file + try + { + currentXML.Load(databasePath); + } + catch + { + return; + } + LoadBlackListDatabase(currentXML); + } + + /// Adds blacklisted plugins to the list from an XML document + /// The XML document to load + internal static void LoadBlackListDatabase(XmlDocument currentXML) + { + if (BlackListedPlugins == null) + { + BlackListedPlugins = new List(); + } + if (currentXML.DocumentElement != null) + { + XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/openBVE/TrainPlugins/Blacklist"); + //Check this file actually contains an openBVE plugin blacklist + if (DocumentNodes != null) + { + for(int i = 0; i < DocumentNodes.Count; i++) + { + BlackListEntry entry = ParseBlackListEntry(DocumentNodes[i]); + if (BlackListedPlugins.Count == 0) + { + BlackListedPlugins.Add(entry); + } + else + { + for (int j = 0; j < BlackListedPlugins.Count; j++) + { + if (BlackListedPlugins[j] == entry) + { + break; + } + if (j == BlackListedPlugins.Count -1) + { + BlackListedPlugins.Add(entry); + } + } + } + } + + } + } + } + + internal static BlackListEntry ParseBlackListEntry(XmlNode node) + { + if (node.HasChildNodes) + { + BlackListEntry p = new BlackListEntry(); + bool ch = false; + foreach (XmlNode c in node.ChildNodes) + { + switch (c.Name.ToLowerInvariant()) + { + case "filelength": + Double.TryParse(c.InnerText, out p.FileLength); + ch = true; + break; + case "filename": + p.FileName = c.InnerText.ToLowerInvariant(); + ch = true; + break; + case "md5": + p.MD5 = c.InnerText.ToUpper().Trim(); + ch = true; + break; + case "train": + p.Train = c.InnerText; + ch = true; + break; + case "reason": + p.Reason = c.InnerText; + ch = true; + break; + case "allversions": + string s = c.InnerText.ToLowerInvariant(); + if (s == "true" || s == "1") + { + p.AllVersions = true; + } + break; + } + } + if (ch && (p.MD5 != string.Empty || p.AllVersions == true)) + { + return p; + } + } + return new BlackListEntry(); + } + + internal static bool RemoveBlackListEntry(BlackListEntry entry) + { + if (BlackListedPlugins == null) + { + return false; + } + for (int i = BlackListedPlugins.Count -1; i >= 0; i--) + { + if (BlackListedPlugins[i] == entry) + { + BlackListedPlugins.RemoveAt(i); + return true; + } + } + return false; + } + + /// Saves the list of blacklisted plugins to disk + internal static void WriteBlackListDatabase() + { + if (BlackListedPlugins == null || BlackListedPlugins.Count == 0) + { + try + { + File.Delete(OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("PluginDatabase"), "blacklist.xml")); + } + catch {} + return; + } + //This isn't a public class, hence building the XML manually for write out + XmlDocument currentXML = new XmlDocument(); + XmlElement rootElement = (XmlElement)currentXML.AppendChild(currentXML.CreateElement("openBVE")); + XmlElement firstElement = (XmlElement)rootElement.AppendChild(currentXML.CreateElement("TrainPlugins")); + for (int i = 0; i < BlackListedPlugins.Count; i++) + { + XmlElement entry = (XmlElement)firstElement.AppendChild(currentXML.CreateElement("BlackList")); + entry.AppendChild(currentXML.CreateElement("FileLength")).InnerText = BlackListedPlugins[i].FileLength.ToString(CultureInfo.InvariantCulture); + entry.AppendChild(currentXML.CreateElement("FileName")).InnerText = BlackListedPlugins[i].FileName; + entry.AppendChild(currentXML.CreateElement("MD5")).InnerText = BlackListedPlugins[i].MD5; + entry.AppendChild(currentXML.CreateElement("Train")).InnerText = BlackListedPlugins[i].Train; + entry.AppendChild(currentXML.CreateElement("Reason")).InnerText = BlackListedPlugins[i].Reason; + entry.AppendChild(currentXML.CreateElement("AllVersions")).InnerText = BlackListedPlugins[i].AllVersions ? "true" : "false"; + } + using (StreamWriter sw = new StreamWriter(OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("PluginDatabase"), "blacklist.xml"))) + { + currentXML.Save(sw); + } + } + } +} diff --git a/source/OpenBVE/Simulation/TrainPlugins/CompatibleReplacement.cs b/source/OpenBVE/Simulation/TrainPlugins/CompatibleReplacement.cs new file mode 100644 index 0000000000..25e9f743fe --- /dev/null +++ b/source/OpenBVE/Simulation/TrainPlugins/CompatibleReplacement.cs @@ -0,0 +1,381 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Threading; +using System.Windows.Forms; +using System.Xml; +using OpenBveApi.Colors; + +namespace OpenBve +{ + internal static partial class PluginManager + { + internal struct ReplacementPlugin + { + /// The original plugin this is to replace + internal BlackListEntry OriginalPlugin; + + /// The name of the new plugin + internal string PluginName; + + /// The path of the compatible plugin to use + internal string PluginPath; + + /// The train-name if this replacement is to be used for only one train, or null for all + internal string[] Trains; + + /// The message displayed to the user when the compatible plugin is used for the first time + /// eg. Control changes + internal string Message; + + /// The required configuration file + /// If applicable + internal string ConfigurationFile; + + public static bool operator !=(ReplacementPlugin a, ReplacementPlugin b) + { + if ((a.Message ?? "") != (b.Message ?? "")) return true; + if (a.OriginalPlugin != b.OriginalPlugin) return true; + if (a.PluginPath.Trim() != b.PluginPath.Trim()) return true; + if (a.Trains != null && b.Trains != null) + { + if (a.Trains.Length == b.Trains.Length) + { + if (a.Trains.Length < 0) + { + for (int i = 0; i < a.Trains.Length; i++) + { + if (a.Trains[i] != b.Trains[i]) + { + return true; + } + } + } + } + else + { + return true; + } + } + if ((a.ConfigurationFile ?? "") != (b.ConfigurationFile ?? "")) return true; + return false; + } + + public static bool operator ==(ReplacementPlugin a, ReplacementPlugin b) + { + if ((a.Message ?? "") != (b.Message ?? "")) return false; + if (a.OriginalPlugin != b.OriginalPlugin) return false; + if (a.PluginPath.Trim() != b.PluginPath.Trim()) return false; + if (a.Trains != null && b.Trains != null) + { + if (a.Trains.Length != b.Trains.Length) return false; + if (a.Trains.Length < 0) + { + for (int i = 0; i < a.Trains.Length; i++) + { + if (a.Trains[i] != b.Trains[i]) + { + return false; + } + } + } + } + if ((a.ConfigurationFile ?? "") != (b.ConfigurationFile ?? "")) return false; + return true; + } + } + + /// Holds the list of available replacement plugins + internal static List AvailableReplacementPlugins; + + internal static bool FindReplacementPlugin(ref string PluginPath) + { + if (AvailableReplacementPlugins.Count == 0) + { + PluginPath = null; + return false; + } + var f = System.IO.Path.GetDirectoryName(PluginPath); + var fl = System.IO.Path.GetFileName(PluginPath); + if (fl == "OpenBveAts.dll") + { + return false; + } + + for (int i = 0; i < AvailableReplacementPlugins.Count; i++) + { + if (AvailableReplacementPlugins[i].Trains == null || AvailableReplacementPlugins[i].Trains.Contains(f.Split(Path.DirectorySeparatorChar).Last())) + { + if (CheckBlackList(AvailableReplacementPlugins[i].OriginalPlugin)) + { + //The original plugin is contained within the blacklist + Interface.AddMessage(Interface.MessageType.Warning, true, "The blacklisted train plugin " + fl + " has been replaced with the following compatible alternative: " + + AvailableReplacementPlugins[i].PluginName); + string s = "The blacklisted train plugin " + fl + " has been replaced with the following compatible alternative: " + AvailableReplacementPlugins[i].PluginName; + Loading.MessageQueue.Add(new Game.Message(s, Game.MessageDependency.None, MessageColor.Green, 10.0)); + Loading.PluginMessageColor = MessageColor.Green; + PluginPath = OpenBveApi.Path.CombineFile(Program.FileSystem.DataFolder, AvailableReplacementPlugins[i].PluginPath); + if (!String.IsNullOrEmpty(AvailableReplacementPlugins[i].Message)) + { + MessageBox.Show(AvailableReplacementPlugins[i].Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + return true; + } + + //Not in the blacklist, so check plugin length etc. + var fi = new FileInfo(PluginPath); + if (fi.Length == AvailableReplacementPlugins[i].OriginalPlugin.FileLength) + { + var md5 = MD5.Create(); + using (var stream = File.OpenRead(PluginPath)) + { + md5.ComputeHash(stream); + } + //String.Empty is required, as otherwise it adds a null character..... + string s = BitConverter.ToString(md5.Hash).Replace("-", String.Empty).ToUpper(); + if (s.ToLowerInvariant() == AvailableReplacementPlugins[i].OriginalPlugin.MD5.ToLowerInvariant()) + { + if (AvailableReplacementPlugins[i].ConfigurationFile != null) + { + try + { + switch (AvailableReplacementPlugins[i].PluginName.ToLowerInvariant()) + { + case "uktrainsys": + //Exact case of configuration file required on Linux + //TODO: Fix UKTrainsys to use API path resolution + System.IO.File.Copy(AvailableReplacementPlugins[i].ConfigurationFile, OpenBveApi.Path.CombineFile(f, "UkTrainSys.cfg")); + break; + case "bvec_ats": + System.IO.File.Copy(AvailableReplacementPlugins[i].ConfigurationFile, OpenBveApi.Path.CombineFile(f, "bvec_ats.cfg")); + break; + } + + } + catch + { + Interface.AddMessage(Interface.MessageType.Error, true, "A compatible alternative for train plugin " + fl + " was found, but an error occured whilst attempting to copy a required configuration file."); + return false; + } + } + Interface.AddMessage(Interface.MessageType.Warning, true, "The train plugin " + fl + " has been replaced with the following compatible alternative: " + + AvailableReplacementPlugins[i].PluginName); + string t = "The train plugin " + fl + " has been replaced with the following compatible alternative: " + AvailableReplacementPlugins[i].PluginName; + Loading.MessageQueue.Add(new Game.Message(t, Game.MessageDependency.None, MessageColor.Green, 10.0)); + Loading.PluginMessageColor = MessageColor.Green; + PluginPath = OpenBveApi.Path.CombineFile(Program.FileSystem.DataFolder, AvailableReplacementPlugins[i].PluginPath); + + if (!String.IsNullOrEmpty(AvailableReplacementPlugins[i].Message)) + { + MessageBox.Show(AvailableReplacementPlugins[i].Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Information); + } + return true; + } + } + } + } + return false; + + } + + /// Loads the database of compatible replacement plugins from disk + /// The database path + internal static void LoadReplacementDatabase(string databasePath) + { + if (!System.IO.File.Exists(databasePath)) + { + return; + } + AvailableReplacementPlugins = new List(); + XmlDocument currentXML = new XmlDocument(); + //Load the XML file + try + { + currentXML.Load(databasePath); + } + catch + { + return; + } + LoadReplacementDatabase(currentXML); + } + + /// Adds compatible replacement plugins to the list from an XML document + /// The XML document to load + internal static void LoadReplacementDatabase(XmlDocument currentXML) + { + if (AvailableReplacementPlugins == null) + { + AvailableReplacementPlugins = new List(); + } + if (currentXML.DocumentElement != null) + { + XmlNodeList DocumentNodes = currentXML.DocumentElement.SelectNodes("/openBVE/TrainPlugins/Replacements"); + //Check this file actually contains an openBVE plugin blacklist + if (DocumentNodes != null) + { + foreach (XmlNode n in DocumentNodes) + { + ReplacementPlugin plugin = ParseReplacementPlugin(n); + if (AvailableReplacementPlugins.Count == 0) + { + AvailableReplacementPlugins.Add(plugin); + } + else + { + for (int i = 0; i < AvailableReplacementPlugins.Count; i++) + { + //List.Contains() doesn't use the custom equality operator + if (AvailableReplacementPlugins[i] == plugin) + { + break; + } + if (i == AvailableReplacementPlugins.Count -1) + { + AvailableReplacementPlugins.Add(plugin); + } + } + } + } + } + } + } + + internal static ReplacementPlugin ParseReplacementPlugin(XmlNode node) + { + if (node.HasChildNodes) + { + ReplacementPlugin r = new ReplacementPlugin(); + bool ch1 = false; + foreach (XmlNode c in node.ChildNodes) + { + switch (c.Name.ToLowerInvariant()) + { + case "plugin": + if (c.HasChildNodes) + { + BlackListEntry p = new BlackListEntry(); + bool ch = false; + foreach (XmlNode cn in c.ChildNodes) + { + switch (cn.Name.ToLowerInvariant()) + { + case "filelength": + Double.TryParse(cn.InnerText, out p.FileLength); + ch = true; + break; + case "filename": + p.FileName = cn.InnerText; + ch = true; + break; + case "md5": + p.MD5 = cn.InnerText; + ch = true; + break; + case "train": + p.Train = cn.InnerText; + ch = true; + break; + case "reason": + p.Reason = cn.InnerText; + ch = true; + break; + } + } + if (ch && p.MD5 != string.Empty) + { + r.OriginalPlugin = p; + } + } + break; + case "name": + r.PluginName = c.InnerText; + ch1 = true; + break; + case "path": + r.PluginPath = c.InnerText; + ch1 = true; + break; + case "train": + r.Trains = c.InnerText.Split(';'); + ch1 = true; + break; + case "configuration": + r.ConfigurationFile = c.InnerText; + break; + case "message": + r.Message = c.InnerText; + ch1 = true; + break; + } + } + if (ch1 && r.OriginalPlugin != null) + { + return r; + } + } + return new ReplacementPlugin(); + } + + internal static bool RemoveReplacementPlugin(ReplacementPlugin plugin) + { + if (AvailableReplacementPlugins == null) + { + return false; + } + int t = AvailableReplacementPlugins.Count -1; + for (int i = 0; i < AvailableReplacementPlugins.Count; i++) + { + + if (plugin == AvailableReplacementPlugins[i]) + { + AvailableReplacementPlugins.RemoveAt(i); + return true; + } + } + return false; + } + + /// Saves the list of available replacement plugins to disk + internal static void WriteReplacementDatabase() + { + if (AvailableReplacementPlugins == null || AvailableReplacementPlugins.Count == 0) + { + try + { + File.Delete(OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("PluginDatabase"), + "compatiblereplacements.xml")); + } + catch {} + return; + } + //This isn't a public class, hence building the XML manually for write out + XmlDocument currentXML = new XmlDocument(); + XmlElement rootElement = (XmlElement)currentXML.AppendChild(currentXML.CreateElement("openBVE")); + XmlElement firstElement = (XmlElement)rootElement.AppendChild(currentXML.CreateElement("TrainPlugins")); + for (int i = 0; i < AvailableReplacementPlugins.Count; i++) + { + XmlElement entry = (XmlElement)firstElement.AppendChild(currentXML.CreateElement("Replacements")); + XmlElement plugin = (XmlElement)entry.AppendChild(currentXML.CreateElement("Plugin")); + plugin.AppendChild(currentXML.CreateElement("FileLength")).InnerText = AvailableReplacementPlugins[i].OriginalPlugin.FileLength.ToString(CultureInfo.InvariantCulture); + plugin.AppendChild(currentXML.CreateElement("FileName")).InnerText = AvailableReplacementPlugins[i].OriginalPlugin.FileName; + plugin.AppendChild(currentXML.CreateElement("MD5")).InnerText = AvailableReplacementPlugins[i].OriginalPlugin.MD5; + plugin.AppendChild(currentXML.CreateElement("Train")).InnerText = AvailableReplacementPlugins[i].OriginalPlugin.Train; + plugin.AppendChild(currentXML.CreateElement("Reason")).InnerText = AvailableReplacementPlugins[i].OriginalPlugin.Reason; + plugin.AppendChild(currentXML.CreateElement("AllVersions")).InnerText = AvailableReplacementPlugins[i].OriginalPlugin.AllVersions ? "true" : "false"; + entry.AppendChild(currentXML.CreateElement("Name")).InnerText = AvailableReplacementPlugins[i].PluginName; + entry.AppendChild(currentXML.CreateElement("Path")).InnerText = AvailableReplacementPlugins[i].PluginPath; + entry.AppendChild(currentXML.CreateElement("Train")).InnerText = AvailableReplacementPlugins[i].Trains == null ? String.Empty : string.Join(",", AvailableReplacementPlugins[i].Trains); + entry.AppendChild(currentXML.CreateElement("Configuration")).InnerText = AvailableReplacementPlugins[i].ConfigurationFile; + entry.AppendChild(currentXML.CreateElement("Message")).InnerText = AvailableReplacementPlugins[i].Message; + } + using (StreamWriter sw = new StreamWriter(OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("PluginDatabase"), "compatiblereplacements.xml"))) + { + currentXML.Save(sw); + } + } + } +} diff --git a/source/OpenBVE/OldCode/LegacyPlugin.cs b/source/OpenBVE/Simulation/TrainPlugins/LegacyPlugin.cs similarity index 100% rename from source/OpenBVE/OldCode/LegacyPlugin.cs rename to source/OpenBVE/Simulation/TrainPlugins/LegacyPlugin.cs diff --git a/source/OpenBVE/OldCode/NetPlugin.cs b/source/OpenBVE/Simulation/TrainPlugins/NetPlugin.cs similarity index 90% rename from source/OpenBVE/OldCode/NetPlugin.cs rename to source/OpenBVE/Simulation/TrainPlugins/NetPlugin.cs index bbebf768e4..1a17a23601 100644 --- a/source/OpenBVE/OldCode/NetPlugin.cs +++ b/source/OpenBVE/Simulation/TrainPlugins/NetPlugin.cs @@ -58,7 +58,7 @@ internal NetPlugin(string pluginFile, string trainFolder, IRuntime api, TrainMan // --- functions --- internal override bool Load(VehicleSpecs specs, InitializationModes mode) { LoadProperties properties = new LoadProperties(this.PluginFolder, this.TrainFolder, this.PlaySound, this.AddInterfaceMessage); - bool success; + bool success = false; #if !DEBUG try { #endif @@ -66,8 +66,7 @@ internal override bool Load(VehicleSpecs specs, InitializationModes mode) { base.SupportsAI = properties.AISupport == AISupport.Basic; #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif if (success) { @@ -79,8 +78,7 @@ internal override bool Load(VehicleSpecs specs, InitializationModes mode) { Api.Initialize(mode); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif UpdatePower(); @@ -101,9 +99,8 @@ internal override void Unload() { #endif this.Api.Unload(); #if !DEBUG - } catch (Exception ex) { - base.LastException = ex; - throw; + } catch { + //Not too worried about exceptions raised when unloading the plugin really... } #endif } @@ -114,8 +111,7 @@ internal override void BeginJump(InitializationModes mode) { this.Api.Initialize(mode); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -126,6 +122,10 @@ internal override void Elapse(ElapseData data) { #endif this.Api.Elapse(data); for (int i = 0; i < this.SoundHandlesCount; i++) { + if (this.SoundHandles[i] == null) + { + continue; + } if (this.SoundHandles[i].Stopped | this.SoundHandles[i].Source.State == Sounds.SoundSourceState.Stopped) { this.SoundHandles[i].Stop(); this.SoundHandles[i].Source.Stop(); @@ -139,8 +139,7 @@ internal override void Elapse(ElapseData data) { } #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -151,8 +150,7 @@ internal override void SetReverser(int reverser) { this.Api.SetReverser(reverser); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -163,8 +161,7 @@ internal override void SetPower(int powerNotch) { this.Api.SetPower(powerNotch); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -175,8 +172,7 @@ internal override void SetBrake(int brakeNotch) { this.Api.SetBrake(brakeNotch); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -187,8 +183,7 @@ internal override void KeyDown(VirtualKeys key) { this.Api.KeyDown(key); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -199,8 +194,7 @@ internal override void KeyUp(VirtualKeys key) { this.Api.KeyUp(key); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -211,8 +205,7 @@ internal override void HornBlow(HornTypes type) { this.Api.HornBlow(type); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -223,8 +216,7 @@ internal override void DoorChange(DoorStates oldState, DoorStates newState) { this.Api.DoorChange(oldState, newState); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -232,31 +224,21 @@ internal override void SetSignal(SignalData[] signal) { #if !DEBUG try { #endif -// if (this.Train == TrainManager.PlayerTrain) { -// for (int i = 0; i < signal.Length; i++) { -// Game.AddDebugMessage(i.ToString() + " - " + signal[i].Aspect.ToString(), 3.0); -// } -// } this.Api.SetSignal(signal); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } internal override void SetBeacon(BeaconData beacon) { -// if (this.Train == TrainManager.PlayerTrain) { -// Game.AddDebugMessage("Beacon, type=" + beacon.Type.ToString() + ", aspect=" + beacon.Signal.Aspect.ToString() + ", data=" + beacon.Optional.ToString(), 3.0); -// } #if !DEBUG try { #endif this.Api.SetBeacon(beacon); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } @@ -267,12 +249,33 @@ internal override void PerformAI(AIData data) { this.Api.PerformAI(data); #if !DEBUG } catch (Exception ex) { - base.LastException = ex; - throw; + Crash(ex); } #endif } + /// Is called when a .Net based plugin crashes during the game + /// The exception raised + internal void Crash(Exception ex) + { + try + { + base.LastException = ex; + CrashHandler.PluginCrash(ex.ToString()); + Unload(); + for (int i = 0; i < TrainManager.Trains.Length; i++) + { + if (TrainManager.Trains[i].Plugin.PluginTitle == base.PluginTitle) + { + TrainManager.Trains[i].Plugin = null; + } + } + } + catch + { + } + } + /// May be called from a .Net plugin, in order to add a message to the in-game display /// The message to display /// The color in which to display the message diff --git a/source/OpenBVE/Simulation/TrainPlugins/PluginManager.cs b/source/OpenBVE/Simulation/TrainPlugins/PluginManager.cs new file mode 100644 index 0000000000..3336b9315a --- /dev/null +++ b/source/OpenBVE/Simulation/TrainPlugins/PluginManager.cs @@ -0,0 +1,833 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenBveApi.Colors; +using OpenBveApi.Runtime; + +namespace OpenBve +{ + internal static partial class PluginManager + { + /// Represents an abstract plugin. + internal abstract class Plugin + { + // --- members --- + /// The file title of the plugin, including the file extension. + internal string PluginTitle; + /// Whether the plugin is the default ATS/ATC plugin. + internal bool IsDefault; + /// Whether the plugin returned valid information in the last Elapse call. + internal bool PluginValid; + /// The debug message the plugin returned in the last Elapse call. + internal string PluginMessage; + /// The train the plugin is attached to. + internal TrainManager.Train Train; + /// The array of panel variables. + internal int[] Panel; + /// Whether the plugin supports the AI. + internal bool SupportsAI; + /// The last in-game time reported to the plugin. + internal double LastTime; + /// The last reverser reported to the plugin. + internal int LastReverser; + /// The last power notch reported to the plugin. + internal int LastPowerNotch; + /// The last brake notch reported to the plugin. + internal int LastBrakeNotch; + /// The last aspects per relative section reported to the plugin. Section 0 is the current section, section 1 the upcoming section, and so on. + internal int[] LastAspects; + /// The absolute section the train was last. + internal int LastSection; + /// The last exception the plugin raised. + internal Exception LastException; + //NEW: Whether this plugin can disable the time acceleration factor + /// Whether this plugin can disable time acceleration. + internal static bool DisableTimeAcceleration; + /// The current camera view mode + internal OpenBveApi.Runtime.CameraViewMode CurrentCameraViewMode; + + private List currentRouteStations; + internal bool StationsLoaded; + // --- functions --- + /// Called to load and initialize the plugin. + /// The train specifications. + /// The initialization mode of the train. + /// Whether loading the plugin was successful. + internal abstract bool Load(VehicleSpecs specs, InitializationModes mode); + /// Called to unload the plugin. + internal abstract void Unload(); + /// Called before the train jumps to a different location. + /// The initialization mode of the train. + internal abstract void BeginJump(InitializationModes mode); + /// Called when the train has finished jumping to a different location. + internal abstract void EndJump(); + /// Called every frame to update the plugin. + internal void UpdatePlugin() + { + /* + * Prepare the vehicle state. + * */ + double location = this.Train.Cars[0].FrontAxle.Follower.TrackPosition - this.Train.Cars[0].FrontAxlePosition + 0.5 * this.Train.Cars[0].Length; + //Curve Radius, Cant and Pitch Added + double CurrentRadius = this.Train.Cars[0].FrontAxle.Follower.CurveRadius; + double CurrentCant = this.Train.Cars[0].FrontAxle.Follower.CurveCant; + double CurrentPitch = this.Train.Cars[0].FrontAxle.Follower.Pitch; + //If the list of stations has not been loaded, do so + if (!StationsLoaded) + { + currentRouteStations = new List(); + foreach (Game.Station selectedStation in Game.Stations) + { + Station i = new Station + { + Name = selectedStation.Name, + ArrivalTime = selectedStation.ArrivalTime, + DepartureTime = selectedStation.DepartureTime, + StopTime = selectedStation.StopTime, + OpenLeftDoors = selectedStation.OpenLeftDoors, + OpenRightDoors = selectedStation.OpenRightDoors, + ForceStopSignal = selectedStation.ForceStopSignal, + DefaultTrackPosition = selectedStation.DefaultTrackPosition + }; + currentRouteStations.Add(i); + } + StationsLoaded = true; + } + //End of additions + double speed = this.Train.Cars[this.Train.DriverCar].Specs.CurrentPerceivedSpeed; + double bcPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.BrakeCylinderCurrentPressure; + double mrPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.MainReservoirCurrentPressure; + double erPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.EqualizingReservoirCurrentPressure; + double bpPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.BrakePipeCurrentPressure; + double sapPressure = this.Train.Cars[this.Train.DriverCar].Specs.AirBrake.StraightAirPipeCurrentPressure; + VehicleState vehicle = new VehicleState(location, new Speed(speed), bcPressure, mrPressure, erPressure, bpPressure, sapPressure, CurrentRadius, CurrentCant, CurrentPitch); + /* + * Prepare the preceding vehicle state. + * */ + double bestLocation = double.MaxValue; + double bestSpeed = 0.0; + + for (int i = 0; i < TrainManager.Trains.Length; i++) + { + if (TrainManager.Trains[i] != this.Train & TrainManager.Trains[i].State == TrainManager.TrainState.Available) + { + int c = TrainManager.Trains[i].Cars.Length - 1; + double z = TrainManager.Trains[i].Cars[c].RearAxle.Follower.TrackPosition - TrainManager.Trains[i].Cars[c].RearAxlePosition - 0.5 * TrainManager.Trains[i].Cars[c].Length; + + if (z >= location & z < bestLocation) + { + bestLocation = z; + bestSpeed = TrainManager.Trains[i].Specs.CurrentAverageSpeed; + } + } + } + + var precedingVehicle = bestLocation != double.MaxValue ? new PrecedingVehicleState(bestLocation, bestLocation - location, new Speed(bestSpeed)) : null; + /* + * Get the driver handles. + * */ + Handles handles = GetHandles(); + /* + * Update the plugin. + * */ + double totalTime = Game.SecondsSinceMidnight; + double elapsedTime = Game.SecondsSinceMidnight - LastTime; + /* + * Set the current camera view mode + * Could probably do away with the CurrentCameraViewMode and use a direct cast?? + * + */ + CurrentCameraViewMode = (OpenBveApi.Runtime.CameraViewMode)World.CameraMode; + ElapseData data = new ElapseData(vehicle, precedingVehicle, handles, new Time(totalTime), new Time(elapsedTime), currentRouteStations, CurrentCameraViewMode, Interface.CurrentLanguageCode); + LastTime = Game.SecondsSinceMidnight; + Elapse(data); + this.PluginMessage = data.DebugMessage; + DisableTimeAcceleration = data.DisableTimeAcceleration; + /* + * Set the virtual handles. + * */ + this.PluginValid = true; + SetHandles(data.Handles, true); + } + /// Gets the driver handles. + /// The driver handles. + private Handles GetHandles() + { + int reverser = this.Train.Specs.CurrentReverser.Driver; + int powerNotch = this.Train.Specs.CurrentPowerNotch.Driver; + int brakeNotch; + + if (this.Train.Cars[this.Train.DriverCar].Specs.BrakeType == TrainManager.CarBrakeType.AutomaticAirBrake) + { + brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? 3 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Service ? 2 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Lap ? 1 : 0; + } + else { + if (this.Train.Specs.HasHoldBrake) + { + brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 2 : this.Train.Specs.CurrentBrakeNotch.Driver > 0 ? this.Train.Specs.CurrentBrakeNotch.Driver + 1 : this.Train.Specs.CurrentHoldBrake.Driver ? 1 : 0; + } + else { + brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 1 : this.Train.Specs.CurrentBrakeNotch.Driver; + } + } + + bool constSpeed = this.Train.Specs.CurrentConstSpeed; + return new Handles(reverser, powerNotch, brakeNotch, constSpeed); + } + /// Sets the driver handles or the virtual handles. + /// The handles. + /// Whether to set the virtual handles. + private void SetHandles(Handles handles, bool virtualHandles) + { + /* + * Process the handles. + */ + if (this.Train.Specs.SingleHandle & handles.BrakeNotch != 0) + { + handles.PowerNotch = 0; + } + /* + * Process the reverser. + */ + if (handles.Reverser >= -1 & handles.Reverser <= 1) + { + if (virtualHandles) + { + this.Train.Specs.CurrentReverser.Actual = handles.Reverser; + } + else { + TrainManager.ApplyReverser(this.Train, handles.Reverser, false); + } + } + else { + if (virtualHandles) + { + this.Train.Specs.CurrentReverser.Actual = this.Train.Specs.CurrentReverser.Driver; + } + this.PluginValid = false; + } + /* + * Process the power. + * */ + if (handles.PowerNotch >= 0 & handles.PowerNotch <= this.Train.Specs.MaximumPowerNotch) + { + if (virtualHandles) + { + this.Train.Specs.CurrentPowerNotch.Safety = handles.PowerNotch; + } + else { + TrainManager.ApplyNotch(this.Train, handles.PowerNotch, false, 0, true); + } + } + else { + if (virtualHandles) + { + this.Train.Specs.CurrentPowerNotch.Safety = this.Train.Specs.CurrentPowerNotch.Driver; + } + this.PluginValid = false; + } + + if (virtualHandles) + { + this.Train.Specs.CurrentEmergencyBrake.Safety = false; + this.Train.Specs.CurrentHoldBrake.Actual = false; + } + if (this.Train.Cars[this.Train.DriverCar].Specs.BrakeType == TrainManager.CarBrakeType.AutomaticAirBrake) + { + if (handles.BrakeNotch == 0) + { + if (virtualHandles) + { + this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Release; + } + else { + TrainManager.UnapplyEmergencyBrake(this.Train); + TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Release); + } + } + else if (handles.BrakeNotch == 1) + { + if (virtualHandles) + { + this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Lap; + } + else { + TrainManager.UnapplyEmergencyBrake(this.Train); + TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Lap); + } + } + else if (handles.BrakeNotch == 2) + { + if (virtualHandles) + { + this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Service; + } + else { + TrainManager.UnapplyEmergencyBrake(this.Train); + TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Release); + } + } + else if (handles.BrakeNotch == 3) + { + if (virtualHandles) + { + this.Train.Specs.AirBrake.Handle.Safety = TrainManager.AirBrakeHandleState.Service; + this.Train.Specs.CurrentEmergencyBrake.Safety = true; + } + else { + TrainManager.ApplyAirBrakeHandle(this.Train, TrainManager.AirBrakeHandleState.Service); + TrainManager.ApplyEmergencyBrake(this.Train); + } + } + else { + this.PluginValid = false; + } + } + else { + if (this.Train.Specs.HasHoldBrake) + { + if (handles.BrakeNotch == this.Train.Specs.MaximumBrakeNotch + 2) + { + if (virtualHandles) + { + this.Train.Specs.CurrentEmergencyBrake.Safety = true; + this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.MaximumBrakeNotch; + } + else { + TrainManager.ApplyHoldBrake(this.Train, false); + TrainManager.ApplyNotch(this.Train, 0, true, this.Train.Specs.MaximumBrakeNotch, false); + TrainManager.ApplyEmergencyBrake(this.Train); + } + } + else if (handles.BrakeNotch >= 2 & handles.BrakeNotch <= this.Train.Specs.MaximumBrakeNotch + 1) + { + if (virtualHandles) + { + this.Train.Specs.CurrentBrakeNotch.Safety = handles.BrakeNotch - 1; + } + else { + TrainManager.UnapplyEmergencyBrake(this.Train); + TrainManager.ApplyHoldBrake(this.Train, false); + TrainManager.ApplyNotch(this.Train, 0, true, handles.BrakeNotch - 1, false); + } + } + else if (handles.BrakeNotch == 1) + { + if (virtualHandles) + { + this.Train.Specs.CurrentBrakeNotch.Safety = 0; + this.Train.Specs.CurrentHoldBrake.Actual = true; + } + else { + TrainManager.UnapplyEmergencyBrake(this.Train); + TrainManager.ApplyNotch(this.Train, 0, true, 0, false); + TrainManager.ApplyHoldBrake(this.Train, true); + } + } + else if (handles.BrakeNotch == 0) + { + if (virtualHandles) + { + this.Train.Specs.CurrentBrakeNotch.Safety = 0; + } + else { + TrainManager.UnapplyEmergencyBrake(this.Train); + TrainManager.ApplyNotch(this.Train, 0, true, 0, false); + TrainManager.ApplyHoldBrake(this.Train, false); + } + } + else { + if (virtualHandles) + { + this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.CurrentBrakeNotch.Driver; + } + this.PluginValid = false; + } + } + else { + if (handles.BrakeNotch == this.Train.Specs.MaximumBrakeNotch + 1) + { + if (virtualHandles) + { + this.Train.Specs.CurrentEmergencyBrake.Safety = true; + this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.MaximumBrakeNotch; + } + else { + TrainManager.ApplyHoldBrake(this.Train, false); + TrainManager.ApplyEmergencyBrake(this.Train); + } + } + else if (handles.BrakeNotch >= 0 & handles.BrakeNotch <= this.Train.Specs.MaximumBrakeNotch | this.Train.Specs.CurrentBrakeNotch.DelayedChanges.Length == 0) + { + if (virtualHandles) + { + this.Train.Specs.CurrentBrakeNotch.Safety = handles.BrakeNotch; + } + else { + TrainManager.UnapplyEmergencyBrake(this.Train); + TrainManager.ApplyNotch(this.Train, 0, true, handles.BrakeNotch, false); + } + } + else { + if (virtualHandles) + { + this.Train.Specs.CurrentBrakeNotch.Safety = this.Train.Specs.CurrentBrakeNotch.Driver; + } + this.PluginValid = false; + } + } + } + /* + * Process the const speed system. + * */ + this.Train.Specs.CurrentConstSpeed = handles.ConstSpeed & this.Train.Specs.HasConstSpeed; + } + /// Called every frame to update the plugin. + /// The data passed to the plugin on Elapse. + /// This function should not be called directly. Call UpdatePlugin instead. + internal abstract void Elapse(ElapseData data); + /// Called to update the reverser. This invokes a call to SetReverser only if a change actually occured. + internal void UpdateReverser() + { + int reverser = this.Train.Specs.CurrentReverser.Driver; + + if (reverser != this.LastReverser) + { + this.LastReverser = reverser; + SetReverser(reverser); + } + } + /// Called to indicate a change of the reverser. + /// The reverser. + /// This function should not be called directly. Call UpdateReverser instead. + internal abstract void SetReverser(int reverser); + /// Called to update the power notch. This invokes a call to SetPower only if a change actually occured. + internal void UpdatePower() + { + int powerNotch = this.Train.Specs.CurrentPowerNotch.Driver; + + if (powerNotch != this.LastPowerNotch) + { + this.LastPowerNotch = powerNotch; + SetPower(powerNotch); + } + } + /// Called to indicate a change of the power notch. + /// The power notch. + /// This function should not be called directly. Call UpdatePower instead. + internal abstract void SetPower(int powerNotch); + /// Called to update the brake notch. This invokes a call to SetBrake only if a change actually occured. + internal void UpdateBrake() + { + int brakeNotch; + + if (this.Train.Cars[this.Train.DriverCar].Specs.BrakeType == TrainManager.CarBrakeType.AutomaticAirBrake) + { + if (this.Train.Specs.HasHoldBrake) + { + brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? 4 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Service ? 3 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Lap ? 2 : this.Train.Specs.CurrentHoldBrake.Driver ? 1 : 0; + } + else { + brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? 3 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Service ? 2 : this.Train.Specs.AirBrake.Handle.Driver == TrainManager.AirBrakeHandleState.Lap ? 1 : 0; + } + } + else { + if (this.Train.Specs.HasHoldBrake) + { + brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 2 : this.Train.Specs.CurrentBrakeNotch.Driver > 0 ? this.Train.Specs.CurrentBrakeNotch.Driver + 1 : this.Train.Specs.CurrentHoldBrake.Driver ? 1 : 0; + } + else { + brakeNotch = this.Train.Specs.CurrentEmergencyBrake.Driver ? this.Train.Specs.MaximumBrakeNotch + 1 : this.Train.Specs.CurrentBrakeNotch.Driver; + } + } + if (brakeNotch != this.LastBrakeNotch) + { + this.LastBrakeNotch = brakeNotch; + SetBrake(brakeNotch); + } + } + /// Called to indicate a change of the brake notch. + /// The brake notch. + /// This function should not be called directly. Call UpdateBrake instead. + internal abstract void SetBrake(int brakeNotch); + /// Called when a virtual key is pressed. + internal abstract void KeyDown(VirtualKeys key); + /// Called when a virtual key is released. + internal abstract void KeyUp(VirtualKeys key); + /// Called when a horn is played or stopped. + internal abstract void HornBlow(HornTypes type); + /// Called when the state of the doors changes. + internal abstract void DoorChange(DoorStates oldState, DoorStates newState); + /// Called to update the aspects of the section. This invokes a call to SetSignal only if a change in aspect occured or when changing section boundaries. + /// The sections to submit to the plugin. + internal void UpdateSignals(SignalData[] data) + { + if (data.Length != 0) + { + bool update; + + if (this.Train.CurrentSectionIndex != this.LastSection) + { + update = true; + } + else if (data.Length != this.LastAspects.Length) + { + update = true; + } + else { + update = false; + for (int i = 0; i < data.Length; i++) + { + if (data[i].Aspect != this.LastAspects[i]) + { + update = true; + break; + } + } + } + if (update) + { + SetSignal(data); + this.LastAspects = new int[data.Length]; + for (int i = 0; i < data.Length; i++) + { + this.LastAspects[i] = data[i].Aspect; + } + } + } + } + /// Is called when the aspect in the current or any of the upcoming sections changes. + /// Signal information per section. In the array, index 0 is the current section, index 1 the upcoming section, and so on. + /// This function should not be called directly. Call UpdateSignal instead. + internal abstract void SetSignal(SignalData[] signal); + /// Called when the train passes a beacon. + /// The beacon type. + /// The section the beacon is attached to, or -1 for the next red signal. + /// Optional data attached to the beacon. + internal void UpdateBeacon(int type, int sectionIndex, int optional) + { + if (sectionIndex == -1) + { + sectionIndex = this.Train.CurrentSectionIndex + 1; + + SignalData signal = null; + + while (sectionIndex < Game.Sections.Length) + { + signal = Game.GetPluginSignal(this.Train, sectionIndex); + if (signal.Aspect == 0) break; + sectionIndex++; + } + if (sectionIndex < Game.Sections.Length) + { + SetBeacon(new BeaconData(type, optional, signal)); + } + else { + SetBeacon(new BeaconData(type, optional, new SignalData(-1, double.MaxValue))); + } + } + if (sectionIndex >= 0) + { + SignalData signal; + + if (sectionIndex < Game.Sections.Length) + { + signal = Game.GetPluginSignal(this.Train, sectionIndex); + } + else { + signal = new SignalData(0, double.MaxValue); + } + SetBeacon(new BeaconData(type, optional, signal)); + } + else { + SetBeacon(new BeaconData(type, optional, new SignalData(-1, double.MaxValue))); + } + } + /// Called when the train passes a beacon. + /// The beacon data. + /// This function should not be called directly. Call UpdateBeacon instead. + internal abstract void SetBeacon(BeaconData beacon); + /// Updates the AI. + /// The AI response. + internal AIResponse UpdateAI() + { + if (this.SupportsAI) + { + AIData data = new AIData(GetHandles()); + this.PerformAI(data); + if (data.Response != AIResponse.None) + { + SetHandles(data.Handles, false); + } + return data.Response; + } + else { + return AIResponse.None; + } + } + /// Called when the AI should be performed. + /// The AI data. + /// This function should not be called directly. Call UpdateAI instead. + internal abstract void PerformAI(AIData data); + } + + /// Loads a custom plugin for the specified train. + /// The train to attach the plugin to. + /// The absolute path to the train folder. + /// The encoding to be used. + /// Whether the plugin was loaded successfully. + internal static bool LoadCustomPlugin(TrainManager.Train train, string trainFolder, System.Text.Encoding encoding) + { + string config = OpenBveApi.Path.CombineFile(trainFolder, "ats.cfg"); + + if (!System.IO.File.Exists(config)) + { + return false; + } + + string[] lines = System.IO.File.ReadAllLines(config, encoding); + + if (lines.Length == 0) + { + return false; + } + + string file = OpenBveApi.Path.CombineFile(trainFolder, lines[0]); + string title = System.IO.Path.GetFileName(file); + + if (!System.IO.File.Exists(file)) + { + Interface.AddMessage(Interface.MessageType.Error, true, "The train plugin " + title + " could not be found in " + config); + return false; + } + Program.AppendToLogFile("Loading train plugin: " + file); + + bool success = LoadPlugin(train, file, trainFolder); + + if (success == false) + { + Loading.MessageQueue.Add(new Game.Message((Interface.GetInterfaceString("errors_plugin_failure1").Replace("[plugin]", file)), Game.MessageDependency.None, MessageColor.Red, 5.0)); + } + else + { + Program.AppendToLogFile("Train plugin loaded successfully."); + } + return success; + } + + /// Loads the default plugin for the specified train. + /// The train to attach the plugin to. + /// The train folder. + /// Whether the plugin was loaded successfully. + internal static bool LoadDefaultPlugin(TrainManager.Train train, string trainFolder) + { + string file = OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("Plugins"), "OpenBveAts.dll"); + bool success = LoadPlugin(train, file, trainFolder); + + if (success) + { + train.Plugin.IsDefault = true; + SoundCfgParser.LoadDefaultPluginSounds(train, trainFolder); + } + return success; + } + + /// Loads the specified plugin for the specified train. + /// The train to attach the plugin to. + /// The file to the plugin. + /// The train folder. + /// Whether the plugin was loaded successfully. + private static bool LoadPlugin(TrainManager.Train train, string pluginFile, string trainFolder) + { + string pluginTitle = System.IO.Path.GetFileName(pluginFile); + + if (!System.IO.File.Exists(pluginFile)) + { + Interface.AddMessage(Interface.MessageType.Error, true, "The train plugin " + pluginTitle + " could not be found."); + return false; + } + if (PluginManager.CheckBlackList(pluginFile, trainFolder)) + { + //Check for a compatible replacement for this blacklisted plugin + if (!PluginManager.FindReplacementPlugin(ref pluginFile)) + { + return false; + } + } + + /* + * Unload plugin if already loaded. + * */ + if (train.Plugin != null) + { + UnloadPlugin(train); + } + /* + * Prepare initialization data for the plugin. + * */ + BrakeTypes brakeType = (BrakeTypes)train.Cars[train.DriverCar].Specs.BrakeType; + int brakeNotches; + int powerNotches; + bool hasHoldBrake; + + if (brakeType == BrakeTypes.AutomaticAirBrake) + { + brakeNotches = 2; + powerNotches = train.Specs.MaximumPowerNotch; + hasHoldBrake = false; + } + else { + brakeNotches = train.Specs.MaximumBrakeNotch + (train.Specs.HasHoldBrake ? 1 : 0); + powerNotches = train.Specs.MaximumPowerNotch; + hasHoldBrake = train.Specs.HasHoldBrake; + } + + int cars = train.Cars.Length; + VehicleSpecs specs = new VehicleSpecs(powerNotches, brakeType, brakeNotches, hasHoldBrake, cars); + InitializationModes mode = (InitializationModes)Game.TrainStart; + /* + * Check if the plugin is a .NET plugin. + * */ + Assembly assembly; + + try + { + assembly = Assembly.LoadFile(pluginFile); + } + catch (BadImageFormatException) + { + assembly = null; + } + catch (Exception ex) + { + Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " could not be loaded due to the following exception: " + ex.Message); + return false; + } + if (assembly != null) + { + Type[] types; + + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + foreach (Exception e in ex.LoaderExceptions) + { + Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " raised an exception on loading: " + e.Message); + } + return false; + } + foreach (Type type in types) + { + if (typeof(IRuntime).IsAssignableFrom(type)) + { + IRuntime api = assembly.CreateInstance(type.FullName) as IRuntime; + train.Plugin = new NetPlugin(pluginFile, trainFolder, api, train); + if (train.Plugin.Load(specs, mode)) + { + return true; + } + else { + train.Plugin = null; + return false; + } + } + } + Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " does not export a train interface and therefore cannot be used with openBVE."); + return false; + } + /* + * Check if the plugin is a Win32 plugin. + * + */ + try + { + if (!CheckWin32Header(pluginFile)) + { + Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " is of an unsupported binary format and therefore cannot be used with openBVE."); + return false; + } + } + catch (Exception ex) + { + Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " could not be read due to the following reason: " + ex.Message); + return false; + } + if (!Program.CurrentlyRunningOnWindows | IntPtr.Size != 4) + { + Interface.AddMessage(Interface.MessageType.Warning, false, "The train plugin " + pluginTitle + " can only be used on 32-bit Microsoft Windows or compatible."); + if (Interface.CurrentOptions.UseCompatiblePlugins) + { + if (!PluginManager.FindReplacementPlugin(ref pluginFile) && pluginTitle != "OpenBveAts.dll") + { + Interface.AddMessage(Interface.MessageType.Warning, false, "No compatible replacement was found."); + } + else + { + return LoadPlugin(train, pluginFile, trainFolder); + } + } + return false; + } + if (Program.CurrentlyRunningOnWindows && !System.IO.File.Exists(AppDomain.CurrentDomain.BaseDirectory + "\\AtsPluginProxy.dll")) + { + Interface.AddMessage(Interface.MessageType.Warning, false, "AtsPluginProxy.dll is missing or corrupt- Please reinstall."); + return false; + } + train.Plugin = new Win32Plugin(pluginFile, train); + if (train.Plugin.Load(specs, mode)) + { + return true; + } + else { + train.Plugin = null; + Interface.AddMessage(Interface.MessageType.Error, false, "The train plugin " + pluginTitle + " does not export a train interface and therefore cannot be used with openBVE."); + return false; + } + } + + /// Checks whether a specified file is a valid Win32 plugin. + /// The file to check. + /// Whether the file is a valid Win32 plugin. + private static bool CheckWin32Header(string file) + { + using (System.IO.FileStream stream = new System.IO.FileStream(file, System.IO.FileMode.Open, System.IO.FileAccess.Read)) + { + using (System.IO.BinaryReader reader = new System.IO.BinaryReader(stream)) + { + if (reader.ReadUInt16() != 0x5A4D) + { + /* Not MZ signature */ + return false; + } + stream.Position = 0x3C; + stream.Position = reader.ReadInt32(); + if (reader.ReadUInt32() != 0x00004550) + { + /* Not PE signature */ + return false; + } + if (reader.ReadUInt16() != 0x014C) + { + /* Not IMAGE_FILE_MACHINE_I386 */ + return false; + } + } + } + return true; + } + + /// Unloads the currently loaded plugin, if any. + /// Unloads the plugin for the specified train. + internal static void UnloadPlugin(TrainManager.Train train) + { + if (train.Plugin != null) + { + train.Plugin.Unload(); + train.Plugin = null; + } + } + } +} \ No newline at end of file diff --git a/source/OpenBVE/System/Functions/CrashHandler.cs b/source/OpenBVE/System/Functions/CrashHandler.cs index 5b880b62d6..eefe37c139 100644 --- a/source/OpenBVE/System/Functions/CrashHandler.cs +++ b/source/OpenBVE/System/Functions/CrashHandler.cs @@ -6,221 +6,297 @@ namespace OpenBve { - /// Provides functions for handling crashes, and producing an appropriate error log - class CrashHandler - { - static readonly System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; - static readonly string CrashLog = OpenBveApi.Path.CombineFile(Program.FileSystem.SettingsFolder,"OpenBVE Crash- " + DateTime.Now.ToString("yyyy.M.dd[HH.mm]") + ".log"); - /// Catches all unhandled exceptions within the current appdomain - internal static void CurrentDomain_UnhandledException(Object sender, UnhandledExceptionEventArgs e) - { - try - { - Exception ex = (Exception)e.ExceptionObject; - if (ex is ArgumentOutOfRangeException && ex.Message == "Specified argument was out of the range of valid values.\r\nParameter name: button") - { - //If a joystick with an excessive number of axis or buttons is connected, at the least show a nice error message, rather than simply dissapearing - MessageBox.Show("An unsupported joystick is connected: \n \n Too many buttons. \n \n Please unplug all USB joysticks & gamepads and try again."); - Environment.Exit(0); - } - if (ex is ArgumentOutOfRangeException && ex.Message == "Specified argument was out of the range of valid values.\r\nParameter name: axis") - { - //If a joystick with an excessive number of axis or buttons is connected, at the least show a nice error message, rather than simply dissapearing - MessageBox.Show("An unsupported joystick is connected: \n \n Too many axis. \n \n Please unplug all USB joysticks & gamepads and try again."); - Environment.Exit(0); - } - MessageBox.Show("Unhandled exception:\n\n" + ex.Message); - LogCrash(ex + Environment.StackTrace); + /// Provides functions for handling crashes, and producing an appropriate error log + class CrashHandler + { + static readonly System.Globalization.CultureInfo Culture = System.Globalization.CultureInfo.InvariantCulture; + static readonly string CrashLog = OpenBveApi.Path.CombineFile(Program.FileSystem.SettingsFolder,"OpenBVE Crash- " + DateTime.Now.ToString("yyyy.M.dd[HH.mm]") + ".log"); + /// Catches all unhandled exceptions within the current appdomain + internal static void CurrentDomain_UnhandledException(Object sender, UnhandledExceptionEventArgs e) + { + try + { + Exception ex = (Exception)e.ExceptionObject; + if (ex is ArgumentOutOfRangeException && ex.Message == "Specified argument was out of the range of valid values.\r\nParameter name: button") + { + //If a joystick with an excessive number of axis or buttons is connected, at the least show a nice error message, rather than simply dissapearing + MessageBox.Show("An unsupported joystick is connected: \n \n Too many buttons. \n \n Please unplug all USB joysticks & gamepads and try again."); + Environment.Exit(0); + } + if (ex is ArgumentOutOfRangeException && ex.Message == "Specified argument was out of the range of valid values.\r\nParameter name: axis") + { + //If a joystick with an excessive number of axis or buttons is connected, at the least show a nice error message, rather than simply dissapearing + MessageBox.Show("An unsupported joystick is connected: \n \n Too many axis. \n \n Please unplug all USB joysticks & gamepads and try again."); + Environment.Exit(0); + } + MessageBox.Show("Unhandled exception:\n\n" + ex.Message); + LogCrash(ex + Environment.StackTrace); - } - catch (Exception exc) - { - try - { - MessageBox.Show("A fatal exception occured inside the UnhandledExceptionHandler: \n\n" - + exc.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Stop); - LogCrash(exc + Environment.StackTrace); - } - finally - { - Environment.Exit(0); - } - } - } + } + catch (Exception exc) + { + try + { + MessageBox.Show("A fatal exception occured inside the UnhandledExceptionHandler: \n\n" + + exc.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Stop); + LogCrash(exc + Environment.StackTrace); + } + finally + { + Environment.Exit(0); + } + } + } - /// Catches all unhandled exceptions within the current UI thread - internal static void UIThreadException(object sender, ThreadExceptionEventArgs t) - { - try - { - MessageBox.Show("Unhandled Windows Forms Exception"); - LogCrash(t + Environment.StackTrace); - } - catch (Exception exc) - { - try - { - MessageBox.Show("A fatal exception occured inside the UIThreadException handler", - "Fatal Windows Forms Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Stop); - LogCrash(exc + Environment.StackTrace); - } - finally - { - Environment.Exit(0); - } - } - Environment.Exit(0); - } + /// Catches all unhandled exceptions within the current UI thread + internal static void UIThreadException(object sender, ThreadExceptionEventArgs t) + { + try + { + MessageBox.Show("Unhandled Windows Forms Exception"); + LogCrash(t + Environment.StackTrace); + } + catch (Exception exc) + { + try + { + MessageBox.Show("A fatal exception occured inside the UIThreadException handler", + "Fatal Windows Forms Error", MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Stop); + LogCrash(exc + Environment.StackTrace); + } + finally + { + Environment.Exit(0); + } + } + Environment.Exit(0); + } - /// This function logs an unhandled crash to disk - internal static void LogCrash(string ExceptionText) - { + /// This function logs an unhandled crash to disk + internal static void LogCrash(string ExceptionText) + { Program.AppendToLogFile("WARNING: Program crashing. Creating CrashLog file: " + CrashLog); using (StreamWriter outputFile = new StreamWriter(CrashLog)) - { - //Basic information - outputFile.WriteLine(DateTime.Now); - outputFile.WriteLine("OpenBVE " + Application.ProductVersion + " Crash Log"); - var Platform = "Unknown"; - if (OpenTK.Configuration.RunningOnWindows) - { - Platform = "Windows"; - } - else if (OpenTK.Configuration.RunningOnLinux) - { - Platform = "Linux"; - } - else if (OpenTK.Configuration.RunningOnMacOS) - { - Platform = "MacOS"; - } - else if (OpenTK.Configuration.RunningOnSdl2) - { - Platform = "SDL2"; - } - outputFile.WriteLine("Program is running on the " + Platform + " backend"); - if (Interface.CurrentOptions.FullscreenMode) - { - outputFile.WriteLine("Current screen resolution is: Full-screen " + Interface.CurrentOptions.FullscreenWidth + "px x " + Interface.CurrentOptions.FullscreenHeight + "px " + Interface.CurrentOptions.FullscreenBits + "bit color-mode"); - } - else - { - outputFile.WriteLine("Current screen resolution is: Windowed " + Interface.CurrentOptions.WindowWidth + "px x " + Interface.CurrentOptions.WindowHeight + "px "); - } - //Route and train - if (Game.RouteInformation.RouteFile != null) - { - outputFile.WriteLine("Current routefile is: " + Game.RouteInformation.RouteFile); - } - if (Game.RouteInformation.TrainFolder != null) - { - outputFile.WriteLine("Current train is: " + Game.RouteInformation.TrainFolder); - } - if (TrainManager.PlayerTrain != null) - { - outputFile.WriteLine("Current train plugin is: " + TrainManager.PlayerTrain.Plugin.PluginTitle); - } - //Errors and Warnings - if (Game.RouteInformation.FilesNotFound != null) - { - outputFile.WriteLine(Game.RouteInformation.FilesNotFound); - } - if (Game.RouteInformation.ErrorsAndWarnings != null) - { - outputFile.WriteLine(Game.RouteInformation.ErrorsAndWarnings); - } - //Track position and viewing distance - outputFile.WriteLine("Current track position is: " + World.CameraTrackFollower.TrackPosition.ToString("0.00", Culture) + " m"); - outputFile.WriteLine("Current viewing distance is: " + Interface.CurrentOptions.ViewingDistance); - outputFile.WriteLine("The exception caught was as follows: "); - outputFile.WriteLine(ExceptionText); - double MemoryUsed; - using (Process proc = Process.GetCurrentProcess()) - { - MemoryUsed = proc.PrivateMemorySize64; - } - outputFile.WriteLine("Current program memory usage: " + Math.Round((MemoryUsed / 1024 / 1024), 2) + "mb"); - var freeRamCounter = new PerformanceCounter("Memory", "Available MBytes"); - outputFile.WriteLine("System memory free: " + freeRamCounter.NextValue() + "mb"); - } + { + //Basic information + outputFile.WriteLine(DateTime.Now); + outputFile.WriteLine("OpenBVE " + Application.ProductVersion + " Crash Log"); + var Platform = "Unknown"; + if (OpenTK.Configuration.RunningOnWindows) + { + Platform = "Windows"; + } + else if (OpenTK.Configuration.RunningOnLinux) + { + Platform = "Linux"; + } + else if (OpenTK.Configuration.RunningOnMacOS) + { + Platform = "MacOS"; + } + else if (OpenTK.Configuration.RunningOnSdl2) + { + Platform = "SDL2"; + } + outputFile.WriteLine("Program is running on the " + Platform + " backend"); + if (Interface.CurrentOptions.FullscreenMode) + { + outputFile.WriteLine("Current screen resolution is: Full-screen " + Interface.CurrentOptions.FullscreenWidth + "px x " + Interface.CurrentOptions.FullscreenHeight + "px " + Interface.CurrentOptions.FullscreenBits + "bit color-mode"); + } + else + { + outputFile.WriteLine("Current screen resolution is: Windowed " + Interface.CurrentOptions.WindowWidth + "px x " + Interface.CurrentOptions.WindowHeight + "px "); + } + //Route and train + if (Game.RouteInformation.RouteFile != null) + { + outputFile.WriteLine("Current routefile is: " + Game.RouteInformation.RouteFile); + } + if (Game.RouteInformation.TrainFolder != null) + { + outputFile.WriteLine("Current train is: " + Game.RouteInformation.TrainFolder); + } + if (TrainManager.PlayerTrain != null) + { + outputFile.WriteLine("Current train plugin is: " + TrainManager.PlayerTrain.Plugin.PluginTitle); + } + //Errors and Warnings + if (Game.RouteInformation.FilesNotFound != null) + { + outputFile.WriteLine(Game.RouteInformation.FilesNotFound); + } + if (Game.RouteInformation.ErrorsAndWarnings != null) + { + outputFile.WriteLine(Game.RouteInformation.ErrorsAndWarnings); + } + //Track position and viewing distance + outputFile.WriteLine("Current track position is: " + World.CameraTrackFollower.TrackPosition.ToString("0.00", Culture) + " m"); + outputFile.WriteLine("Current viewing distance is: " + Interface.CurrentOptions.ViewingDistance); + outputFile.WriteLine("The exception caught was as follows: "); + outputFile.WriteLine(ExceptionText); + double MemoryUsed; + using (Process proc = Process.GetCurrentProcess()) + { + MemoryUsed = proc.PrivateMemorySize64; + } + outputFile.WriteLine("Current program memory usage: " + Math.Round((MemoryUsed / 1024 / 1024), 2) + "mb"); + var freeRamCounter = new PerformanceCounter("Memory", "Available MBytes"); + outputFile.WriteLine("System memory free: " + freeRamCounter.NextValue() + "mb"); + } - } + } - /// This function logs an exception caught whilst loading a route/ train to disk - internal static void LoadingCrash(string ExceptionText, bool Train) - { + /// This function logs an unhandled plugin crash to disk + internal static void PluginCrash(string ExceptionText) + { + + Program.AppendToLogFile("WARNING: Train plugin " + TrainManager.PlayerTrain.Plugin.PluginTitle + " crashed. Creating CrashLog file: " + CrashLog); + MessageBox.Show("Train plugin " + TrainManager.PlayerTrain.Plugin.PluginTitle + " crashed and has been unloaded. \r\n Some train features may no longer work.", Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Hand); + using (StreamWriter outputFile = new StreamWriter(CrashLog)) + { + //Basic information + outputFile.WriteLine(DateTime.Now); + outputFile.WriteLine("OpenBVE " + Application.ProductVersion + " Crash Log"); + var Platform = "Unknown"; + if (OpenTK.Configuration.RunningOnWindows) + { + Platform = "Windows"; + } + else if (OpenTK.Configuration.RunningOnLinux) + { + Platform = "Linux"; + } + else if (OpenTK.Configuration.RunningOnMacOS) + { + Platform = "MacOS"; + } + else if (OpenTK.Configuration.RunningOnSdl2) + { + Platform = "SDL2"; + } + outputFile.WriteLine("Program is running on the " + Platform + " backend"); + if (Interface.CurrentOptions.FullscreenMode) + { + outputFile.WriteLine("Current screen resolution is: Full-screen " + Interface.CurrentOptions.FullscreenWidth + "px x " + Interface.CurrentOptions.FullscreenHeight + "px " + Interface.CurrentOptions.FullscreenBits + "bit color-mode"); + } + else + { + outputFile.WriteLine("Current screen resolution is: Windowed " + Interface.CurrentOptions.WindowWidth + "px x " + Interface.CurrentOptions.WindowHeight + "px "); + } + //Route and train + if (Game.RouteInformation.RouteFile != null) + { + outputFile.WriteLine("Current routefile is: " + Game.RouteInformation.RouteFile); + } + if (Game.RouteInformation.TrainFolder != null) + { + outputFile.WriteLine("Current train is: " + Game.RouteInformation.TrainFolder); + } + if (TrainManager.PlayerTrain != null) + { + outputFile.WriteLine("Current train plugin is: " + TrainManager.PlayerTrain.Plugin.PluginTitle); + } + //Errors and Warnings + if (Game.RouteInformation.FilesNotFound != null) + { + outputFile.WriteLine(Game.RouteInformation.FilesNotFound); + } + if (Game.RouteInformation.ErrorsAndWarnings != null) + { + outputFile.WriteLine(Game.RouteInformation.ErrorsAndWarnings); + } + //Track position and viewing distance + outputFile.WriteLine("Current track position is: " + World.CameraTrackFollower.TrackPosition.ToString("0.00", Culture) + " m"); + outputFile.WriteLine("Current viewing distance is: " + Interface.CurrentOptions.ViewingDistance); + outputFile.WriteLine("The train plugin raised the following exception, and was unloaded: "); + outputFile.WriteLine(ExceptionText); + double MemoryUsed; + using (Process proc = Process.GetCurrentProcess()) + { + MemoryUsed = proc.PrivateMemorySize64; + } + outputFile.WriteLine("Current program memory usage: " + Math.Round((MemoryUsed / 1024 / 1024), 2) + "mb"); + var freeRamCounter = new PerformanceCounter("Memory", "Available MBytes"); + outputFile.WriteLine("System memory free: " + freeRamCounter.NextValue() + "mb"); + } + + } + + /// This function logs an exception caught whilst loading a route/ train to disk + internal static void LoadingCrash(string ExceptionText, bool Train) + { Program.AppendToLogFile("WARNING: Program crashing. Creating CrashLog file: " + CrashLog); using (StreamWriter outputFile = new StreamWriter(CrashLog)) - { - //Basic information - outputFile.WriteLine(DateTime.Now); - outputFile.WriteLine("OpenBVE " + Application.ProductVersion + " Crash Log"); - var Platform = "Unknown"; - if (OpenTK.Configuration.RunningOnWindows) - { - Platform = "Windows"; - } - else if (OpenTK.Configuration.RunningOnLinux) - { - Platform = "Linux"; - } - else if (OpenTK.Configuration.RunningOnMacOS) - { - Platform = "MacOS"; - } - else if (OpenTK.Configuration.RunningOnSdl2) - { - Platform = "SDL2"; - } - outputFile.WriteLine("Program is running on the " + Platform + " backend"); - if (Interface.CurrentOptions.FullscreenMode) - { - outputFile.WriteLine("Current screen resolution is: Full-screen " + Interface.CurrentOptions.FullscreenWidth + "px X " + Interface.CurrentOptions.FullscreenHeight + "px " + Interface.CurrentOptions.FullscreenBits + "bit color-mode"); - } - else - { - outputFile.WriteLine("Current screen resolution is: Windowed " + Interface.CurrentOptions.WindowWidth + "px X " + Interface.CurrentOptions.WindowHeight + "px "); - } - //Route and train information - try - { - //We need the try/ catch block in order to catch errors which may have occured before initing the current route, train or plugin - //These may occur if we feed dud data to the sim - outputFile.WriteLine("Current routefile is: " + Game.RouteInformation.RouteFile); - outputFile.WriteLine("Current train is: " + Game.RouteInformation.TrainFolder); - outputFile.WriteLine("Current train plugin is: " + TrainManager.PlayerTrain.Plugin.PluginTitle); - } - catch - { - } - //Errors and Warnings - if (Game.RouteInformation.FilesNotFound != null) - { - outputFile.WriteLine(Game.RouteInformation.FilesNotFound); - } - if (Game.RouteInformation.ErrorsAndWarnings != null) - { - outputFile.WriteLine(Game.RouteInformation.ErrorsAndWarnings); - } - if (Train) - { - outputFile.WriteLine("The current train plugin caused the following exception: "); - } - else - { - outputFile.WriteLine("The current routefile caused the following exception: "); - } - outputFile.WriteLine(ExceptionText); - double MemoryUsed; - using (Process proc = Process.GetCurrentProcess()) - { - MemoryUsed = proc.PrivateMemorySize64; - } - outputFile.WriteLine("Current program memory usage: " + Math.Round((MemoryUsed / 1024 / 1024),2) + "mb"); - var freeRamCounter = new PerformanceCounter("Memory", "Available MBytes"); - outputFile.WriteLine("System memory free: " + freeRamCounter.NextValue() + "mb"); - } + { + //Basic information + outputFile.WriteLine(DateTime.Now); + outputFile.WriteLine("OpenBVE " + Application.ProductVersion + " Crash Log"); + var Platform = "Unknown"; + if (OpenTK.Configuration.RunningOnWindows) + { + Platform = "Windows"; + } + else if (OpenTK.Configuration.RunningOnLinux) + { + Platform = "Linux"; + } + else if (OpenTK.Configuration.RunningOnMacOS) + { + Platform = "MacOS"; + } + else if (OpenTK.Configuration.RunningOnSdl2) + { + Platform = "SDL2"; + } + outputFile.WriteLine("Program is running on the " + Platform + " backend"); + if (Interface.CurrentOptions.FullscreenMode) + { + outputFile.WriteLine("Current screen resolution is: Full-screen " + Interface.CurrentOptions.FullscreenWidth + "px X " + Interface.CurrentOptions.FullscreenHeight + "px " + Interface.CurrentOptions.FullscreenBits + "bit color-mode"); + } + else + { + outputFile.WriteLine("Current screen resolution is: Windowed " + Interface.CurrentOptions.WindowWidth + "px X " + Interface.CurrentOptions.WindowHeight + "px "); + } + //Route and train information + try + { + //We need the try/ catch block in order to catch errors which may have occured before initing the current route, train or plugin + //These may occur if we feed dud data to the sim + outputFile.WriteLine("Current routefile is: " + Game.RouteInformation.RouteFile); + outputFile.WriteLine("Current train is: " + Game.RouteInformation.TrainFolder); + outputFile.WriteLine("Current train plugin is: " + TrainManager.PlayerTrain.Plugin.PluginTitle); + } + catch + { + } + //Errors and Warnings + if (Game.RouteInformation.FilesNotFound != null) + { + outputFile.WriteLine(Game.RouteInformation.FilesNotFound); + } + if (Game.RouteInformation.ErrorsAndWarnings != null) + { + outputFile.WriteLine(Game.RouteInformation.ErrorsAndWarnings); + } + if (Train) + { + outputFile.WriteLine("The current train plugin caused the following exception: "); + } + else + { + outputFile.WriteLine("The current routefile caused the following exception: "); + } + outputFile.WriteLine(ExceptionText); + double MemoryUsed; + using (Process proc = Process.GetCurrentProcess()) + { + MemoryUsed = proc.PrivateMemorySize64; + } + outputFile.WriteLine("Current program memory usage: " + Math.Round((MemoryUsed / 1024 / 1024),2) + "mb"); + var freeRamCounter = new PerformanceCounter("Memory", "Available MBytes"); + outputFile.WriteLine("System memory free: " + freeRamCounter.NextValue() + "mb"); + } - } - } + } + } } diff --git a/source/OpenBVE/System/Options.cs b/source/OpenBVE/System/Options.cs index 4f8582d28b..9be4e9a35c 100644 --- a/source/OpenBVE/System/Options.cs +++ b/source/OpenBVE/System/Options.cs @@ -156,6 +156,8 @@ internal class Options internal bool AllowAxisEB; /// Whether to prefer the native OpenTK operating system backend internal bool PreferNativeBackend = true; + /// Whether to use compatible plugins on non Win32 systems + internal bool UseCompatiblePlugins; internal TimeTableMode TimeTableStyle; @@ -229,6 +231,7 @@ internal Options() this.ProxyPassword = string.Empty; this.TimeAccelerationFactor = 5; this.AllowAxisEB = true; + this.UseCompatiblePlugins = true; this.TimeTableStyle = TimeTableMode.Default; this.packageCompressionType = CompressionType.Zip; } @@ -502,6 +505,9 @@ internal static void LoadOptions() } Interface.CurrentOptions.TimeAccelerationFactor = tf; break; + case "usecompatibleplugins": + Interface.CurrentOptions.UseCompatiblePlugins = string.Compare(Value, "false", StringComparison.OrdinalIgnoreCase) != 0; + break; } break; case "controls": switch (Key) @@ -770,6 +776,7 @@ internal static void SaveOptions() default: Builder.AppendLine("normal"); break; } Builder.Append("acceleratedtimefactor = " + CurrentOptions.TimeAccelerationFactor); + Builder.AppendLine("usecompatibleplugins = " + (CurrentOptions.UseCompatiblePlugins ? "true" : "false")); Builder.AppendLine(); Builder.AppendLine("[verbosity]"); Builder.AppendLine("showWarningMessages = " + (CurrentOptions.ShowWarningMessages ? "true" : "false")); diff --git a/source/OpenBVE/System/Program.cs b/source/OpenBVE/System/Program.cs index a31d3c1b37..e236b41873 100644 --- a/source/OpenBVE/System/Program.cs +++ b/source/OpenBVE/System/Program.cs @@ -151,6 +151,9 @@ private static void Main(string[] args) { //Environment.Exit(0); } } + //Load lists of blacklisted and compatible replacement plugins + PluginManager.LoadBlackListDatabase(OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("PluginDatabase"), "blacklist.xml")); + PluginManager.LoadReplacementDatabase(OpenBveApi.Path.CombineFile(Program.FileSystem.GetDataFolder("PluginDatabase"), "compatiblereplacements.xml")); Interface.LoadControls(null, out Interface.CurrentControls); { string folder = Program.FileSystem.GetDataFolder("Controls"); diff --git a/source/OpenBVE/UserInterface/formMain.Designer.cs b/source/OpenBVE/UserInterface/formMain.Designer.cs index 7e0a6884bc..06abf93e1b 100644 --- a/source/OpenBVE/UserInterface/formMain.Designer.cs +++ b/source/OpenBVE/UserInterface/formMain.Designer.cs @@ -408,6 +408,8 @@ private void InitializeComponent() { this.buttonCreatePackage = new System.Windows.Forms.Button(); this.openPackageFileDialog = new System.Windows.Forms.OpenFileDialog(); this.savePackageDialog = new System.Windows.Forms.SaveFileDialog(); + this.groupBoxCompatability = new System.Windows.Forms.GroupBox(); + this.checkBoxCompatiblePlugins = new System.Windows.Forms.CheckBox(); ((System.ComponentModel.ISupportInitialize)(this.pictureboxLogo)).BeginInit(); this.panelStart.SuspendLayout(); this.groupboxTrainSelection.SuspendLayout(); @@ -504,6 +506,7 @@ private void InitializeComponent() { this.splitContainerDependancies.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.dataGridViewPackages2)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.dataGridViewPackages3)).BeginInit(); + this.groupBoxCompatability.SuspendLayout(); this.SuspendLayout(); // // labelFillerOne @@ -1341,6 +1344,7 @@ private void InitializeComponent() { this.panelOptions.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(243)))), ((int)(((byte)(255)))), ((int)(((byte)(243))))); this.panelOptions.Controls.Add(this.buttonOptionsPrevious); this.panelOptions.Controls.Add(this.buttonOptionsNext); + this.panelOptions.Controls.Add(this.panelOptionsPage2); this.panelOptions.Controls.Add(this.panelOptionsRight); this.panelOptions.Controls.Add(this.panelOptionsLeft); this.panelOptions.Controls.Add(this.pictureboxLanguage); @@ -1348,7 +1352,6 @@ private void InitializeComponent() { this.panelOptions.Controls.Add(this.labelOptionsTitleSeparator); this.panelOptions.Controls.Add(this.labelOptionsTitle); this.panelOptions.Controls.Add(this.labelOptionsTitleBackground); - this.panelOptions.Controls.Add(this.panelOptionsPage2); this.panelOptions.Location = new System.Drawing.Point(160, 0); this.panelOptions.Name = "panelOptions"; this.panelOptions.Size = new System.Drawing.Size(659, 606); @@ -2269,6 +2272,7 @@ private void InitializeComponent() { this.panelOptionsPage2.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); + this.panelOptionsPage2.Controls.Add(this.groupBoxCompatability); this.panelOptionsPage2.Controls.Add(this.groupBoxPackageOptions); this.panelOptionsPage2.Location = new System.Drawing.Point(8, 72); this.panelOptionsPage2.Name = "panelOptionsPage2"; @@ -5167,14 +5171,36 @@ private void InitializeComponent() { // this.openPackageFileDialog.FileName = "openFileDialog1"; // + // groupBoxCompatability + // + this.groupBoxCompatability.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.groupBoxCompatability.Controls.Add(this.checkBoxCompatiblePlugins); + this.groupBoxCompatability.Location = new System.Drawing.Point(0, 158); + this.groupBoxCompatability.Name = "groupBoxCompatability"; + this.groupBoxCompatability.Size = new System.Drawing.Size(640, 100); + this.groupBoxCompatability.TabIndex = 20; + this.groupBoxCompatability.TabStop = false; + this.groupBoxCompatability.Text = "Compatablilty"; + // + // checkBoxCompatiblePlugins + // + this.checkBoxCompatiblePlugins.AutoSize = true; + this.checkBoxCompatiblePlugins.Location = new System.Drawing.Point(12, 19); + this.checkBoxCompatiblePlugins.Name = "checkBoxCompatiblePlugins"; + this.checkBoxCompatiblePlugins.Size = new System.Drawing.Size(344, 17); + this.checkBoxCompatiblePlugins.TabIndex = 0; + this.checkBoxCompatiblePlugins.Text = "Use compatible plugins on non-Windows systems (Where available)"; + this.checkBoxCompatiblePlugins.UseVisualStyleBackColor = true; + // // formMain // this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi; this.BackColor = System.Drawing.Color.White; this.ClientSize = new System.Drawing.Size(819, 606); - this.Controls.Add(this.panelControls); this.Controls.Add(this.panelOptions); + this.Controls.Add(this.panelControls); this.Controls.Add(this.labelVerticalSeparator); this.Controls.Add(this.panelInfo); this.Controls.Add(this.panelPanels); @@ -5326,6 +5352,8 @@ private void InitializeComponent() { this.splitContainerDependancies.ResumeLayout(false); ((System.ComponentModel.ISupportInitialize)(this.dataGridViewPackages2)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.dataGridViewPackages3)).EndInit(); + this.groupBoxCompatability.ResumeLayout(false); + this.groupBoxCompatability.PerformLayout(); this.ResumeLayout(false); } @@ -5716,5 +5744,7 @@ private void InitializeComponent() { private System.Windows.Forms.Button buttonCancel; private System.Windows.Forms.Button buttonCancel2; private System.Windows.Forms.Button buttonAbort; + private System.Windows.Forms.GroupBox groupBoxCompatability; + private System.Windows.Forms.CheckBox checkBoxCompatiblePlugins; } } \ No newline at end of file diff --git a/source/OpenBVE/UserInterface/formMain.Options.cs b/source/OpenBVE/UserInterface/formMain.Options.cs index 893ac51433..1d9e2767fb 100644 --- a/source/OpenBVE/UserInterface/formMain.Options.cs +++ b/source/OpenBVE/UserInterface/formMain.Options.cs @@ -74,8 +74,6 @@ private void trackBarTimeAccelerationFactor_ValueChanged(object sender, EventArg private void checkboxJoysticksUsed_CheckedChanged(object sender, EventArgs e) { groupboxJoysticks.Enabled = checkboxJoysticksUsed.Checked; } - - } } \ No newline at end of file diff --git a/source/OpenBVE/UserInterface/formMain.Packages.cs b/source/OpenBVE/UserInterface/formMain.Packages.cs index 9d26bec0b0..8983a5e563 100644 --- a/source/OpenBVE/UserInterface/formMain.Packages.cs +++ b/source/OpenBVE/UserInterface/formMain.Packages.cs @@ -9,6 +9,7 @@ using System.Windows.Forms; using OpenBveApi.Packages; using System.Text; +using System.Xml; namespace OpenBve { @@ -265,6 +266,9 @@ private void Extract(Package packageToReplace = null) case PackageType.Train: ExtractionDirectory = Program.FileSystem.TrainInstallationDirectory; break; + case PackageType.CompatibilityData: + ExtractionDirectory = Program.FileSystem.DataFolder; + break; default: ExtractionDirectory = Program.FileSystem.OtherInstallationDirectory; break; @@ -324,6 +328,34 @@ private void Extract(Package packageToReplace = null) } } Database.currentDatabase.InstalledOther.Add(currentPackage); + if (currentPackage.PackageType == PackageType.CompatibilityData) + { + //Compatibility data is not installed in a user selected folder, but should show in the 'Other' list + //We now need to add our new compatibility XML file to the compatibility database + if (!string.IsNullOrEmpty(currentPackage.CompatabilityXml)) + { + //TODO: Handle objects + string xmlFile = OpenBveApi.Path.CombineFile(System.IO.Path.GetTempPath(), currentPackage.CompatabilityXml); + if (!System.IO.File.Exists(xmlFile)) + { + break; + } + XmlDocument currentXML = new XmlDocument(); + currentXML.Load(xmlFile); + XmlNodeList nodes = currentXML.SelectNodes("/openBVE/TrainPlugins/Replacements"); + if (nodes != null && nodes.Count > 0) + { + PluginManager.LoadReplacementDatabase(currentXML); + PluginManager.WriteReplacementDatabase(); + } + nodes = currentXML.SelectNodes("/openBVE/TrainPlugins/Blacklist"); + if(nodes != null && nodes.Count > 0) + { + PluginManager.LoadBlackListDatabase(currentXML); + PluginManager.WriteBlackListDatabase(); + } + } + } break; } labelInstallSuccess1.Text = Interface.GetInterfaceString("packages_install_success"); @@ -485,7 +517,17 @@ internal void UninstallPackage(Package packageToUninstall, ref List Pac HidePanels(); panelDependancyError.Show(); } - if (Manipulation.UninstallPackage(packageToUninstall, currentDatabaseFolder, ref uninstallResults)) + bool uninstallOK; + XmlDocument xmlToUninstall = null; + if (packageToUninstall.PackageType == PackageType.CompatibilityData) + { + uninstallOK = Manipulation.UninstallPackage(packageToUninstall, currentDatabaseFolder, out uninstallResults, out xmlToUninstall); + } + else + { + uninstallOK = Manipulation.UninstallPackage(packageToUninstall, currentDatabaseFolder, ref uninstallResults); + } + if (uninstallOK) { Packages.Remove(packageToUninstall); switch (packageToUninstall.PackageType) @@ -499,6 +541,30 @@ internal void UninstallPackage(Package packageToUninstall, ref List Pac case PackageType.Train: DatabaseFunctions.cleanDirectory(Program.FileSystem.TrainInstallationDirectory, ref uninstallResults); break; + case PackageType.CompatibilityData: + DatabaseFunctions.cleanDirectory(Program.FileSystem.DataFolder, ref uninstallResults); + if (xmlToUninstall != null) + { + XmlNodeList nodes = xmlToUninstall.SelectNodes("/openBVE/TrainPlugins/Blacklist"); + if (nodes != null && nodes.Count > 0) + { + for (int i = 0; i < nodes.Count; i++) + { + PluginManager.RemoveBlackListEntry(PluginManager.ParseBlackListEntry(nodes[i])); + } + } + PluginManager.WriteBlackListDatabase(); + nodes = xmlToUninstall.SelectNodes("/openBVE/TrainPlugins/Replacements"); + if (nodes != null && nodes.Count > 0) + { + for (int i = 0; i < nodes.Count; i++) + { + PluginManager.RemoveReplacementPlugin(PluginManager.ParseReplacementPlugin(nodes[i])); + } + } + PluginManager.WriteReplacementDatabase(); + } + break; } labelUninstallSuccess.Text = Interface.GetInterfaceString("packages_uninstall_success"); labelUninstallSuccessHeader.Text = Interface.GetInterfaceString("packages_uninstall_success_header"); @@ -588,6 +654,7 @@ private void buttonUninstallPackage_Click(object sender, EventArgs e) case PackageType.Train: UninstallPackage(currentPackage, ref Database.currentDatabase.InstalledTrains); break; + case PackageType.CompatibilityData: case PackageType.Other: UninstallPackage(currentPackage, ref Database.currentDatabase.InstalledOther); break; @@ -616,6 +683,7 @@ private void buttonUninstallFinish_Click(object sender, EventArgs e) { switch (currentPackage.PackageType) { + case PackageType.CompatibilityData: case PackageType.Other: Database.currentDatabase.InstalledOther.Remove(currentPackage); break; @@ -1424,51 +1492,46 @@ private void comboBoxDependancyType_SelectedIndexChanged(object sender, EventArg private void buttonProceedAnyway1_Click(object sender, EventArgs e) { HidePanels(); - if (radioButtonOverwrite.Checked) - { - //Plain overwrite - Extract(); - } - else if (radioButtonReplace.Checked) + string result = String.Empty; + if (radioButtonReplace.Checked) { //Reinstall - string result = String.Empty; Manipulation.UninstallPackage(currentPackage, currentDatabaseFolder, ref result); - switch (currentPackage.PackageType) - { - case PackageType.Route: - for (int i = Database.currentDatabase.InstalledRoutes.Count -1; i >= 0; i--) + } + switch (currentPackage.PackageType) + { + case PackageType.Route: + for (int i = Database.currentDatabase.InstalledRoutes.Count - 1; i >= 0; i--) + { + if (Database.currentDatabase.InstalledRoutes[i].GUID == currentPackage.GUID) { - if (Database.currentDatabase.InstalledRoutes[i].GUID == currentPackage.GUID) - { - Database.currentDatabase.InstalledRoutes.RemoveAt(i); - } + Database.currentDatabase.InstalledRoutes.RemoveAt(i); } - DatabaseFunctions.cleanDirectory(Program.FileSystem.RouteInstallationDirectory, ref result); - break; - case PackageType.Train: - for (int i = Database.currentDatabase.InstalledTrains.Count - 1; i >= 0; i--) + } + DatabaseFunctions.cleanDirectory(Program.FileSystem.RouteInstallationDirectory, ref result); + break; + case PackageType.Train: + for (int i = Database.currentDatabase.InstalledTrains.Count - 1; i >= 0; i--) + { + if (Database.currentDatabase.InstalledTrains[i].GUID == currentPackage.GUID) { - if (Database.currentDatabase.InstalledTrains[i].GUID == currentPackage.GUID) - { - Database.currentDatabase.InstalledTrains.RemoveAt(i); - } + Database.currentDatabase.InstalledTrains.RemoveAt(i); } - DatabaseFunctions.cleanDirectory(Program.FileSystem.TrainInstallationDirectory, ref result); - break; - case PackageType.Other: - for (int i = Database.currentDatabase.InstalledOther.Count - 1; i >= 0; i--) + } + DatabaseFunctions.cleanDirectory(Program.FileSystem.TrainInstallationDirectory, ref result); + break; + case PackageType.CompatibilityData: + case PackageType.Other: + for (int i = Database.currentDatabase.InstalledOther.Count - 1; i >= 0; i--) + { + if (Database.currentDatabase.InstalledOther[i].GUID == currentPackage.GUID) { - if (Database.currentDatabase.InstalledOther[i].GUID == currentPackage.GUID) - { - Database.currentDatabase.InstalledOther.RemoveAt(i); - } + Database.currentDatabase.InstalledOther.RemoveAt(i); } - break; - } - Extract(); + } + break; } - + Extract(); } private void dataGridViewDependancies_CellContentClick(object sender, DataGridViewCellEventArgs e) diff --git a/source/OpenBVE/UserInterface/formMain.cs b/source/OpenBVE/UserInterface/formMain.cs index f2acb840e4..f2c949d4a4 100644 --- a/source/OpenBVE/UserInterface/formMain.cs +++ b/source/OpenBVE/UserInterface/formMain.cs @@ -876,6 +876,7 @@ private void formMain_FormClosing(object sender, FormClosingEventArgs e) Interface.CurrentOptions.TrainFolder = textboxTrainFolder.Text; Interface.CurrentOptions.MainMenuWidth = this.WindowState == FormWindowState.Maximized ? -1 : this.Size.Width; Interface.CurrentOptions.MainMenuHeight = this.WindowState == FormWindowState.Maximized ? -1 : this.Size.Height; + Interface.CurrentOptions.UseCompatiblePlugins = checkBoxCompatiblePlugins.Checked; if (Result.Start) { // recently used routes diff --git a/source/OpenBveApi/Packages.cs b/source/OpenBveApi/Packages.cs index 54c3f190e5..cacd1b0ad5 100644 --- a/source/OpenBveApi/Packages.cs +++ b/source/OpenBveApi/Packages.cs @@ -63,6 +63,9 @@ public string Version /// The image for this package [XmlIgnore] public Image PackageImage; + /// The compatibility XML file + /// (If a CompatibilityData package) + public string CompatabilityXml; /// The list of dependancies for this package public List Dependancies; /// The list of packages that this package reccomends you also install @@ -170,6 +173,8 @@ public enum PackageType Train = 2, /// The package is a route, utility etc. Other = 3, + /// The package contains compatibility data. + CompatibilityData = 5 } /// Holds the properties of a file, used during creation of a package. @@ -206,6 +211,7 @@ public static void ExtractPackage(Package currentPackage, string extractionDirec int i = 0; int j = 0; string fp = String.Empty; + XmlDocument uninstallXML = new XmlDocument(); try { using (Stream stream = File.OpenRead(currentPackage.PackageFile)) @@ -226,6 +232,18 @@ public static void ExtractPackage(Package currentPackage, string extractionDirec { //Skip zero-byte files } + else if(archiveEntry.Key.ToLowerInvariant() == currentPackage.CompatabilityXml) + { + archiveEntry.WriteToDirectory(System.IO.Path.GetTempPath()); + try + { + //Load the compatibility data for uninstall purposes + uninstallXML.Load(Path.CombineFile(System.IO.Path.GetTempPath(), currentPackage.CompatabilityXml)); + } + catch + { + } + } else { //Extract everything else, preserving directory structure @@ -246,7 +264,7 @@ public static void ExtractPackage(Package currentPackage, string extractionDirec Text += FileName + "\r\n"; } packageFiles = Text; - //Write out the package file list + //Write out the uninstall XML file var fileListDirectory = OpenBveApi.Path.CombineDirectory(databaseFolder, "Installed"); if (!Directory.Exists(fileListDirectory)) { @@ -255,8 +273,30 @@ public static void ExtractPackage(Package currentPackage, string extractionDirec var fileList = OpenBveApi.Path.CombineFile(fileListDirectory, currentPackage.GUID.ToUpper() + ".xml"); using (StreamWriter sw = new StreamWriter(fileList)) { - XmlSerializer listWriter = new XmlSerializer(typeof(List)); - listWriter.Serialize(sw, PackageFiles); + var currentXML = new XmlDocument(); + XmlElement rootElement = (XmlElement)currentXML.AppendChild(currentXML.CreateElement("openBVE")); + XmlElement firstElement = (XmlElement)rootElement.AppendChild(currentXML.CreateElement("UninstallData")); + XmlElement secondElement = (XmlElement)firstElement.AppendChild(currentXML.CreateElement("FilesInstalled")); + for (int k = 0; k < PackageFiles.Count; k++) + { + secondElement.AppendChild(currentXML.CreateElement("FileName")).InnerText = PackageFiles[k]; + } + try + { + XmlNodeList nodes = uninstallXML.SelectNodes("/openBVE/TrainPlugins"); + if (nodes != null) + { + for (int k = 0; k < nodes.Count; k++) + { + firstElement.AppendChild(firstElement.OwnerDocument.ImportNode(nodes[k], true)); + } + } + } + catch + { + } + + currentXML.Save(sw); } } @@ -366,11 +406,123 @@ public static bool UninstallPackage(Package currentPackage, string databaseFolde //The list of files installed by this package is missing return false; } - XmlSerializer listReader = new XmlSerializer(typeof(List)); List filesToDelete; using (FileStream readFileStream = new FileStream(fileList, FileMode.Open, FileAccess.Read, FileShare.Read)) { - filesToDelete = (List)listReader.Deserialize(readFileStream); + + XmlDocument uninstallXml = new XmlDocument(); + uninstallXml.Load(readFileStream); + //Original format, just a serialized array.... + XmlNodeList nodes = uninstallXml.SelectNodes("/ArrayOfString"); + filesToDelete = new List(); + if (nodes != null) + { + for (int i = 0; i < nodes.Count; i++) + { + filesToDelete.Add(nodes[i].InnerText); + } + } + //Format V2, better formed XML and allows us to uninstall compatibility items + nodes = uninstallXml.SelectNodes("/openBVE/UninstallData/FilesInstalled/FileName"); + if (nodes != null) + { + for (int i = 0; i < nodes.Count; i++) + { + filesToDelete.Add(nodes[i].InnerText); + } + } + } + File.Delete(fileList); + bool noErrors = true; + int errorCount = 0; + int deletionCount = 0; + string Result = ""; + foreach (var String in filesToDelete) + { + try + { + File.Delete(String); + Result += String + " deleted successfully. \r\n "; + deletionCount++; + } + catch (Exception ex) + { + //We have caught an error.... + //Set the return type to false, and add the exception to the results string + noErrors = false; + Result += String + "\r\n"; + Result += ex.Message + "\r\n"; + errorCount++; + } + } + //Set the final results string to display + PackageFiles = deletionCount + " files deleted successfully. \r\n" + errorCount + " errors were encountered. \r\n \r\n \r\n" + Result; + return noErrors; + } + + /// Uninstalls a package + /// The package to uninstall + /// The package database folder + /// Returns via 'ref' a list of files uninstalled + /// A list of nodes to be uninstalled from the compatibility database + /// True if uninstall succeeded with no errors, false otherwise + public static bool UninstallPackage(Package currentPackage, string databaseFolder, out string PackageFiles, out XmlDocument uninstallNodes) + { + var fileList = OpenBveApi.Path.CombineFile(OpenBveApi.Path.CombineDirectory(databaseFolder, "Installed"), currentPackage.GUID.ToUpper() + ".xml"); + if (!File.Exists(fileList)) + { + PackageFiles = null; + uninstallNodes = null; + //The list of files installed by this package is missing + return false; + } + List filesToDelete; + uninstallNodes = null; + using (FileStream readFileStream = new FileStream(fileList, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + + XmlDocument uninstallXml = new XmlDocument(); + uninstallXml.Load(readFileStream); + //Original format, just a serialized array.... + XmlNodeList nodes = uninstallXml.SelectNodes("/ArrayOfString"); + filesToDelete = new List(); + if (nodes != null) + { + for (int i = 0; i < nodes.Count; i++) + { + filesToDelete.Add(nodes[i].InnerText); + } + } + //Format V2, better formed XML and allows us to uninstall compatibility items + nodes = uninstallXml.SelectNodes("/openBVE/UninstallData/FilesInstalled/FileName"); + if (nodes != null) + { + for (int i = 0; i < nodes.Count; i++) + { + filesToDelete.Add(nodes[i].InnerText); + } + } + //Now select any train plugin data + nodes = uninstallXml.SelectNodes("/openBVE/UninstallData/TrainPlugins"); + uninstallNodes = new XmlDocument(); + XmlElement rootElement = (XmlElement)uninstallNodes.AppendChild(uninstallNodes.CreateElement("openBVE")); + //XmlElement firstElement = (XmlElement)rootElement.AppendChild(uninstallNodes.CreateElement("TrainPlugins")); + if (nodes != null) + { + for (int i = 0; i < nodes.Count; i++) + { + rootElement.AppendChild(rootElement.OwnerDocument.ImportNode(nodes[i], true)); + } + } + //Finally, compatbility object data + nodes = uninstallXml.SelectNodes("/openBVE/UninstallData/Compatibility"); + if (nodes != null) + { + for (int i = 0; i < nodes.Count; i++) + { + rootElement.AppendChild(rootElement.OwnerDocument.ImportNode(nodes[i], true)); + } + } } File.Delete(fileList); bool noErrors = true; @@ -556,7 +708,7 @@ public static VersionInformation CheckVersion(Package currentPackage, List