Skip to content

Commit ccd1b48

Browse files
committed
Add SettingsExporter (contrib).
1 parent 5cb9f46 commit ccd1b48

File tree

3 files changed

+276
-0
lines changed

3 files changed

+276
-0
lines changed
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
// COPYRIGHT 2025 by the Open Rails project.
2+
//
3+
// This file is part of Open Rails.
4+
//
5+
// Open Rails is free software: you can redistribute it and/or modify
6+
// it under the terms of the GNU General Public License as published by
7+
// the Free Software Foundation, either version 3 of the License, or
8+
// (at your option) any later version.
9+
//
10+
// Open Rails is distributed in the hope that it will be useful,
11+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
// GNU General Public License for more details.
14+
//
15+
// You should have received a copy of the GNU General Public License
16+
// along with Open Rails. If not, see <http://www.gnu.org/licenses/>.
17+
18+
// Export the Settings in the Registry into an INI file, or vice versa.
19+
// In order to support multiple installations with different settings,
20+
// OpenRails supports using an INI file for the settings (instead of
21+
// the registry entries that are shared by all installations).
22+
// OpenRails creates a default INI file when the file exists, but has no
23+
// settings. This tool exports the current settings from the registry to
24+
// the INI file. It also allows the reverse.
25+
26+
// Important: UpdateSettings, part of UpdateManager has its own file,
27+
// Updater.ini.
28+
//
29+
// FUTURE: New settings classes that are not part of UserSettings need to
30+
// be added to the exporter. See FUTURE tag below.
31+
32+
using System;
33+
using System.Collections.Generic;
34+
using System.IO;
35+
using System.Linq;
36+
using Microsoft.Win32;
37+
using ORTS.Common;
38+
using ORTS.Settings;
39+
40+
namespace ORTS.SettingsExporter
41+
{
42+
class Program
43+
{
44+
static int Main(string[] args)
45+
{
46+
string fromArg = null; string toArg = null;
47+
48+
#region Parse Args
49+
// parse arguments
50+
foreach (string arg in args)
51+
{
52+
if (arg.Equals("/h") || arg.Equals("/help")) { ShowHelp(); Environment.Exit(1); }
53+
else if (arg.StartsWith("/")) { Console.WriteLine("ERROR: Invalid option {0}.", arg); ShowHelp(); Environment.Exit(1); }
54+
else if (fromArg == null) { fromArg = arg; }
55+
else if (toArg == null) { toArg = arg; }
56+
else { Console.WriteLine("ERROR: extraneous argument {0}.", arg); ShowHelp(); Environment.Exit(1); }
57+
}
58+
if (String.IsNullOrEmpty(fromArg) || String.IsNullOrEmpty(toArg))
59+
{ Console.WriteLine("ERROR: Missing from or to argument."); ShowHelp(); Environment.Exit(1); }
60+
else if (fromArg.Equals(toArg))
61+
{ Console.WriteLine("ERROR: From {0} and to {1} must not be the same.", fromArg, toArg); ShowHelp(); Environment.Exit(1); }
62+
#endregion
63+
64+
string loadFilePath = null; string loadRegistryKey = null;
65+
66+
#region Determine From
67+
// determine where to load from
68+
if (fromArg.Equals("INI")) { loadFilePath = SettingsBase.DefaultSettingsFileName; }
69+
else if (fromArg.Equals("REG")) { loadRegistryKey = SettingsBase.DefaultRegistryKey; }
70+
else if (fromArg.EndsWith(".ini")) { loadFilePath = fromArg; }
71+
else { loadRegistryKey = fromArg; }
72+
73+
// check that source exists
74+
if (!String.IsNullOrEmpty(loadFilePath))
75+
{
76+
var iniFilePath = Path.Combine(ApplicationInfo.ProcessDirectory, loadFilePath);
77+
if (!File.Exists(iniFilePath))
78+
{
79+
Console.WriteLine("ERROR: INI file {0} to export from does not exist.", iniFilePath);
80+
Environment.Exit(1);
81+
}
82+
}
83+
else if (!String.IsNullOrEmpty(loadRegistryKey))
84+
{
85+
using (var regKey = Registry.CurrentUser.OpenSubKey(loadRegistryKey))
86+
{
87+
if (regKey == null)
88+
{
89+
Console.WriteLine("ERROR: Reg key {0} to export from does not exist.", loadRegistryKey);
90+
Environment.Exit(1);
91+
}
92+
}
93+
}
94+
else
95+
{
96+
Console.WriteLine("ERROR: No source to export from found");
97+
Environment.Exit(1);
98+
}
99+
#endregion
100+
101+
// instantiate the static part of SettingsBase
102+
UserSettings userSettings;
103+
104+
// then override
105+
SettingsBase.OverrideSettingsLocations(loadFilePath, loadRegistryKey);
106+
107+
Console.WriteLine("Info: Loading from {0}.", SettingsBase.RegistryKey + SettingsBase.SettingsFilePath);
108+
109+
// load the user settings and its sub-settings from the default location
110+
IEnumerable<string> options = Enumerable.Empty<string>();
111+
userSettings = new UserSettings(options);
112+
var updateState = new UpdateState();
113+
// FUTURE: add here when a new settings class is added (that is not handled by UserSettings)
114+
115+
Console.WriteLine("Info: Successfully loaded settings from {0}.", userSettings.GetSettingsStoreName());
116+
117+
string saveFilePath = null; string saveRegistryKey = null;
118+
119+
#region Determine To
120+
// determine where to save to
121+
if (toArg.Equals("INI")) { saveFilePath = SettingsBase.DefaultSettingsFileName; }
122+
else if (toArg.Equals("REG")) { saveRegistryKey = SettingsBase.DefaultRegistryKey; }
123+
else if (toArg.Contains(".ini"))
124+
{
125+
// save to a custom ini file, do some basic verification
126+
saveFilePath = toArg;
127+
var dir = Path.GetDirectoryName(Path.Combine(ApplicationInfo.ProcessDirectory, saveFilePath));
128+
if (!Directory.Exists(dir)) { Console.WriteLine("ERROR: Directory {0} to save to does not exist.", dir); Environment.Exit(1); }
129+
}
130+
else
131+
{
132+
// load from a custom registry key
133+
saveRegistryKey = toArg;
134+
}
135+
#endregion
136+
137+
#region Backup
138+
if (!String.IsNullOrEmpty(saveFilePath))
139+
{
140+
// backup the file if it already exists
141+
string settingsFilePath = Path.Combine(ApplicationInfo.ProcessDirectory, saveFilePath);
142+
string backupFilePath = Path.Combine(ApplicationInfo.ProcessDirectory, saveFilePath + ".bak");
143+
if (File.Exists(settingsFilePath))
144+
{
145+
File.Delete(backupFilePath);
146+
File.Move(settingsFilePath, backupFilePath);
147+
Console.WriteLine("Info: Backed up existing INI file as {0}.", backupFilePath);
148+
}
149+
150+
// create an empty file (required by SettingsStore)
151+
using (File.Create(settingsFilePath)) { };
152+
153+
}
154+
else if (!String.IsNullOrEmpty(saveRegistryKey))
155+
{
156+
// backup the registry key if it already exists
157+
using (var regKey = Registry.CurrentUser.OpenSubKey(saveRegistryKey))
158+
{
159+
if (regKey != null)
160+
{
161+
using (var backupRegKey = Registry.CurrentUser.CreateSubKey(saveRegistryKey + "-Backup"))
162+
{
163+
CopyRegistryKey(regKey, backupRegKey);
164+
Console.WriteLine("Info: Backed up existing Registry key as {0}.", backupRegKey.Name);
165+
}
166+
}
167+
}
168+
}
169+
else
170+
{
171+
Console.WriteLine("ERROR: No destination to export to.");
172+
Environment.Exit(1);
173+
}
174+
#endregion
175+
176+
// change the settings store
177+
userSettings.ChangeSettingsStore(saveFilePath, saveRegistryKey, null); // section is defined in SettingsStoreLocalIni
178+
updateState.ChangeSettingsStore(saveFilePath, saveRegistryKey, UpdateState.SectionName);
179+
// FUTURE: add here when a new settings class is added (that is not handled by UserSettings)
180+
181+
Console.WriteLine("Info: Saving to {0}.", userSettings.GetSettingsStoreName());
182+
183+
// save the settings
184+
userSettings.Save();
185+
updateState.Save();
186+
// FUTURE: add here when a new settings class is added (that is not handled by UserSettings)
187+
188+
Console.WriteLine("Info: Successfully saved to {0}.", userSettings.GetSettingsStoreName());
189+
return 0;
190+
}
191+
192+
static void ShowHelp()
193+
{
194+
string cmd = Path.GetFileNameWithoutExtension(ApplicationInfo.ProcessFile);
195+
Console.WriteLine();
196+
Console.WriteLine("Usage: {0} <from> <to>", cmd);
197+
Console.WriteLine(" <from> Specify the source to load settings from. It may be:");
198+
Console.WriteLine(" INI : use the default INI file {0}.", SettingsBase.DefaultSettingsFileName);
199+
Console.WriteLine(" REG : use the default registry key {0}.", SettingsBase.DefaultRegistryKey);
200+
Console.WriteLine(" path : a specific INI file, relative to the OpenRails main folder. Must end with \".ini\".");
201+
Console.WriteLine(" key : a specific registry key, relative to the HKEY_CURRENT_USER key.");
202+
Console.WriteLine(" <to> Specify the destination to save the settings to. Similar to <from>.");
203+
Console.WriteLine(" /h, /help Show this help.");
204+
Console.WriteLine();
205+
Console.WriteLine("This utility reads the Settings (Options) from one location, and exports them to another location.");
206+
Console.WriteLine("It creates a backup of any settings that will be overwritten. Example:");
207+
Console.WriteLine(" <installfolder>\\OpenRails.ini.bak");
208+
Console.WriteLine(" HKEY_CURRENT_USER\\SOFTWARE\\OpenRails\\ORTS-Backup");
209+
Console.WriteLine("This utility is intended to:");
210+
Console.WriteLine("- Create an INI file that has the same settings as what is in the registry.");
211+
Console.WriteLine(" Example: {0} REG INI", cmd);
212+
Console.WriteLine("- Copy the settings from an INI file back into the registry, so that other installations");
213+
Console.WriteLine(" use the same settings.");
214+
Console.WriteLine(" Example: {0} INI REG", cmd);
215+
Console.WriteLine("- Backup the settings, before making temporary changes (and restore afterwards).");
216+
Console.WriteLine(" Example: {0} REG SOFTWARE\\OpenRails-Saved\\ORTS", cmd);
217+
Console.WriteLine();
218+
}
219+
220+
/// <summary>
221+
/// Recursively copy the values of a registry key to another registry key.
222+
/// </summary>
223+
/// <param name="fromKey">the key to copy from; must exist</param>
224+
/// <param name="toKey">the key to copy to; should not exist</param>
225+
static void CopyRegistryKey(RegistryKey fromKey, RegistryKey toKey)
226+
{
227+
// copy the values
228+
foreach (var name in fromKey.GetValueNames())
229+
{
230+
toKey.SetValue(name, fromKey.GetValue(name), fromKey.GetValueKind(name));
231+
}
232+
233+
// copy the subkeys
234+
foreach (var name in fromKey.GetSubKeyNames())
235+
{
236+
using (var fromSubKey = fromKey.OpenSubKey(name))
237+
{
238+
var toSubKey = toKey.CreateSubKey(name);
239+
CopyRegistryKey(fromSubKey, toSubKey);
240+
}
241+
}
242+
}
243+
}
244+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework Condition="'$(BuildDotNet)' == 'true'">net6-windows</TargetFramework>
4+
<TargetFramework Condition="'$(TargetFramework)' == ''">net472</TargetFramework>
5+
<OutputType>Exe</OutputType>
6+
<RootNamespace>Orts.SettingsExporter</RootNamespace>
7+
<AssemblyName>Contrib.SettingsExporter</AssemblyName>
8+
<ApplicationIcon>..\..\ORTS.ico</ApplicationIcon>
9+
<IsPublishable>False</IsPublishable>
10+
<AssemblyTitle>Open Rails Settings Exporter (Contributed)</AssemblyTitle>
11+
<Description>Export ORTS Settings from the Registry to the INI file, or vice versa.</Description>
12+
<Company>Open Rails</Company>
13+
<Product>Open Rails</Product>
14+
<Copyright>Copyright © 2009 - 2025</Copyright>
15+
<PackageLicenseExpression>GPL-3.0-or-later</PackageLicenseExpression>
16+
</PropertyGroup>
17+
<ItemGroup>
18+
<ProjectReference Include="..\..\ORTS.Common/ORTS.Common.csproj" />
19+
<ProjectReference Include="..\..\ORTS.Settings/ORTS.Settings.csproj" />
20+
</ItemGroup>
21+
<ItemGroup>
22+
<PackageReference Include="Microsoft.DotNet.UpgradeAssistant.Extensions.Default.Analyzers" Version="0.4.355802">
23+
<PrivateAssets>all</PrivateAssets>
24+
</PackageReference>
25+
</ItemGroup>
26+
</Project>

Source/ORTS.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ContentChecker", "ContentCh
5353
EndProject
5454
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimulatorTester", "Contrib\SimulatorTester\SimulatorTester.csproj", "{792B01A1-5C43-4FFA-A4C0-BBDC95A4A178}"
5555
EndProject
56+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SettingsExporter", "Contrib\SettingsExporter\SettingsExporter.csproj", "{FAD444CE-3EA7-47AC-A771-792E34BCE7DC}"
57+
EndProject
5658
Global
5759
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5860
Debug|Any CPU = Debug|Any CPU
@@ -159,6 +161,10 @@ Global
159161
{792B01A1-5C43-4FFA-A4C0-BBDC95A4A178}.Debug|Any CPU.Build.0 = Debug|Any CPU
160162
{792B01A1-5C43-4FFA-A4C0-BBDC95A4A178}.Release|Any CPU.ActiveCfg = Release|Any CPU
161163
{792B01A1-5C43-4FFA-A4C0-BBDC95A4A178}.Release|Any CPU.Build.0 = Release|Any CPU
164+
{FAD444CE-3EA7-47AC-A771-792E34BCE7DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
165+
{FAD444CE-3EA7-47AC-A771-792E34BCE7DC}.Debug|Any CPU.Build.0 = Debug|Any CPU
166+
{FAD444CE-3EA7-47AC-A771-792E34BCE7DC}.Release|Any CPU.ActiveCfg = Release|Any CPU
167+
{FAD444CE-3EA7-47AC-A771-792E34BCE7DC}.Release|Any CPU.Build.0 = Release|Any CPU
162168
EndGlobalSection
163169
GlobalSection(SolutionProperties) = preSolution
164170
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)