baseline: OpenMU upstream b5a0961 (fresh source)

This commit is contained in:
Acentech Dev
2026-07-14 19:00:35 +03:00
parent 450402e47d
commit 36fc125d5c
11968 changed files with 748705 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
// <copyright file="ClientColorDepth.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
/// <summary>
/// The available color depths for the mu online client.
/// </summary>
public enum ClientColorDepth
{
/// <summary>
/// The 16 bit color depth.
/// </summary>
Bit16 = 0,
/// <summary>
/// The 32 bit color depth.
/// </summary>
Bit32 = 1,
}

View File

@@ -0,0 +1,27 @@
// <copyright file="ClientLanguage.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
/// <summary>
/// The available mu online client languages.
/// Only applicable for the global (english) client.
/// </summary>
public enum ClientLanguage
{
/// <summary>
/// The english language.
/// </summary>
English = 0,
/// <summary>
/// The portuguese language.
/// </summary>
Portuguese = 1,
/// <summary>
/// The spanish language.
/// </summary>
Spanish = 2,
}

View File

@@ -0,0 +1,37 @@
// <copyright file="ClientLanguageExtensions.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
/// <summary>
/// Extensions for <see cref="ClientLanguage"/>.
/// </summary>
public static class ClientLanguageExtensions
{
/// <summary>
/// The language keys used in the registry.
/// </summary>
private static readonly string[] LanguageKeys = { "Eng", "Por", "Spn" };
/// <summary>
/// Gets the string which should be set in the registry for the given <see cref="ClientLanguage"/>.
/// </summary>
/// <param name="clientLanguage">The client language.</param>
/// <returns>The string which should be set in the registry for the given <see cref="ClientLanguage"/>.</returns>
public static string? GetString(this ClientLanguage clientLanguage)
{
var index = (int)clientLanguage;
return index < LanguageKeys.Length ? LanguageKeys[index] : null;
}
/// <summary>
/// Gets the <see cref="ClientLanguage"/> based on the given key, which is set in the registry.
/// </summary>
/// <param name="languageKey">The language key.</param>
/// <returns>The <see cref="ClientLanguage"/> based on the given key, which is set in the registry.</returns>
public static ClientLanguage GetLanguage(this string languageKey)
{
return (ClientLanguage)Array.IndexOf(LanguageKeys, languageKey);
}
}

View File

@@ -0,0 +1,41 @@
// <copyright file="ClientResolution.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
/// <summary>
/// The available mu online client screen resolutions.
/// </summary>
public class ClientResolution
{
/// <summary>
/// Initializes a new instance of the <see cref="ClientResolution"/> class.
/// </summary>
public ClientResolution()
{
this.Index = 0;
this.Caption = string.Empty;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClientResolution"/> class.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="caption">The caption.</param>
public ClientResolution(int index, string caption)
{
this.Index = index;
this.Caption = caption;
}
/// <summary>
/// Gets the index.
/// </summary>
public int Index { get; init; }
/// <summary>
/// Gets the caption.
/// </summary>
public string Caption { get; init; }
}

View File

@@ -0,0 +1,94 @@
// <copyright file="ClientSettings.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
using System.Runtime.Versioning;
using Microsoft.Win32;
/// <summary>
/// This class allows to read and write the game client settings in the registry.
/// </summary>
internal class ClientSettings
{
private const string DefaultLanguage = "Eng";
/// <summary>
/// Gets or sets the client color depth.
/// </summary>
public ClientColorDepth ClientColorDepth { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is music enabled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is music enabled; otherwise, <c>false</c>.
/// </value>
public bool IsMusicEnabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is sound enabled.
/// </summary>
/// <value>
/// <c>true</c> if this instance is sound enabled; otherwise, <c>false</c>.
/// </value>
public bool IsSoundEnabled { get; set; }
/// <summary>
/// Gets or sets the volume level.
/// </summary>
public int VolumeLevel { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is window mode active.
/// </summary>
/// <value>
/// <c>true</c> if this instance is window mode active; otherwise, <c>false</c>.
/// </value>
public bool IsWindowModeActive { get; set; }
/// <summary>
/// Gets or sets the resolution.
/// </summary>
public int ResolutionIndex { get; set; }
/// <summary>
/// Gets or sets the language selection.
/// </summary>
public ClientLanguage LangSelection { get; set; }
/// <summary>
/// Loads the settings from the windows registry.
/// </summary>
[SupportedOSPlatform("windows")]
public void Load()
{
using var currentUserKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
using var key = currentUserKey.CreateSubKey(@"SOFTWARE\WebZen\Mu\Config");
this.ClientColorDepth = (ClientColorDepth)(key.GetValue("ColorDepth") ?? 0);
this.IsMusicEnabled = (int)(key.GetValue("MusicOnOff") ?? 0) == 1;
this.IsSoundEnabled = (int)(key.GetValue("SoundOnOff") ?? 0) == 1;
this.VolumeLevel = (int)(key.GetValue("VolumeLevel") ?? 0);
this.IsWindowModeActive = (int)(key.GetValue("WindowMode") ?? 0) == 1;
this.ResolutionIndex = (int)(key.GetValue("Resolution") ?? 0);
this.LangSelection = ((string?)key.GetValue("LangSelection") ?? DefaultLanguage).GetLanguage();
}
/// <summary>
/// Saves the settings at the windows registry.
/// </summary>
[SupportedOSPlatform("windows")]
public void Save()
{
using var currentUserKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32);
using var key = currentUserKey.CreateSubKey(@"SOFTWARE\WebZen\Mu\Config");
key.SetValue("ColorDepth", this.ClientColorDepth, RegistryValueKind.DWord);
key.SetValue("MusicOnOff", this.IsMusicEnabled, RegistryValueKind.DWord);
key.SetValue("SoundOnOff", this.IsSoundEnabled, RegistryValueKind.DWord);
key.SetValue("VolumeLevel", this.VolumeLevel, RegistryValueKind.DWord);
key.SetValue("WindowMode", this.IsWindowModeActive, RegistryValueKind.DWord);
key.SetValue("Resolution", this.ResolutionIndex, RegistryValueKind.DWord);
key.SetValue("LangSelection", this.LangSelection.GetString() ?? DefaultLanguage, RegistryValueKind.String);
}
}

View File

@@ -0,0 +1,248 @@
namespace MUnique.OpenMU.ClientLauncher
{
partial class ClientSettingsDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
windowModeCheckBox = new System.Windows.Forms.CheckBox();
musicActiveCheckBox = new System.Windows.Forms.CheckBox();
soundActiveCheckBox = new System.Windows.Forms.CheckBox();
groupBox3 = new System.Windows.Forms.GroupBox();
soundVolumeTrackBar = new System.Windows.Forms.TrackBar();
groupBox4 = new System.Windows.Forms.GroupBox();
clientLanguageComboBox = new System.Windows.Forms.ComboBox();
saveButton = new System.Windows.Forms.Button();
closeButton = new System.Windows.Forms.Button();
groupBox5 = new System.Windows.Forms.GroupBox();
clientResolutionComboBox = new System.Windows.Forms.ComboBox();
groupBox1 = new System.Windows.Forms.GroupBox();
colorDepthComboBox = new System.Windows.Forms.ComboBox();
groupBox3.SuspendLayout();
((System.ComponentModel.ISupportInitialize)soundVolumeTrackBar).BeginInit();
groupBox4.SuspendLayout();
groupBox5.SuspendLayout();
groupBox1.SuspendLayout();
SuspendLayout();
//
// windowModeCheckBox
//
windowModeCheckBox.AutoSize = true;
windowModeCheckBox.Location = new System.Drawing.Point(22, 286);
windowModeCheckBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
windowModeCheckBox.Name = "windowModeCheckBox";
windowModeCheckBox.Size = new System.Drawing.Size(104, 19);
windowModeCheckBox.TabIndex = 0;
windowModeCheckBox.Text = "Window Mode";
windowModeCheckBox.UseVisualStyleBackColor = true;
//
// musicActiveCheckBox
//
musicActiveCheckBox.AutoSize = true;
musicActiveCheckBox.Location = new System.Drawing.Point(136, 22);
musicActiveCheckBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
musicActiveCheckBox.Name = "musicActiveCheckBox";
musicActiveCheckBox.Size = new System.Drawing.Size(125, 19);
musicActiveCheckBox.TabIndex = 3;
musicActiveCheckBox.Text = "Background Music";
musicActiveCheckBox.UseVisualStyleBackColor = true;
//
// soundActiveCheckBox
//
soundActiveCheckBox.AutoSize = true;
soundActiveCheckBox.Location = new System.Drawing.Point(10, 22);
soundActiveCheckBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
soundActiveCheckBox.Name = "soundActiveCheckBox";
soundActiveCheckBox.Size = new System.Drawing.Size(98, 19);
soundActiveCheckBox.TabIndex = 4;
soundActiveCheckBox.Text = "Sound Effects";
soundActiveCheckBox.UseVisualStyleBackColor = true;
//
// groupBox3
//
groupBox3.Controls.Add(soundVolumeTrackBar);
groupBox3.Controls.Add(musicActiveCheckBox);
groupBox3.Controls.Add(soundActiveCheckBox);
groupBox3.Location = new System.Drawing.Point(19, 115);
groupBox3.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox3.Name = "groupBox3";
groupBox3.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox3.Size = new System.Drawing.Size(315, 111);
groupBox3.TabIndex = 7;
groupBox3.TabStop = false;
groupBox3.Text = "Sound";
//
// soundVolumeTrackBar
//
soundVolumeTrackBar.Dock = System.Windows.Forms.DockStyle.Bottom;
soundVolumeTrackBar.Location = new System.Drawing.Point(4, 63);
soundVolumeTrackBar.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
soundVolumeTrackBar.Name = "soundVolumeTrackBar";
soundVolumeTrackBar.Size = new System.Drawing.Size(307, 45);
soundVolumeTrackBar.TabIndex = 5;
//
// groupBox4
//
groupBox4.Controls.Add(clientLanguageComboBox);
groupBox4.Location = new System.Drawing.Point(19, 233);
groupBox4.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox4.Name = "groupBox4";
groupBox4.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox4.Size = new System.Drawing.Size(315, 46);
groupBox4.TabIndex = 8;
groupBox4.TabStop = false;
groupBox4.Text = "Language";
//
// clientLanguageComboBox
//
clientLanguageComboBox.Dock = System.Windows.Forms.DockStyle.Bottom;
clientLanguageComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
clientLanguageComboBox.FormattingEnabled = true;
clientLanguageComboBox.Items.AddRange(new object[] { "English", "Portuguese", "Spanish" });
clientLanguageComboBox.Location = new System.Drawing.Point(4, 20);
clientLanguageComboBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
clientLanguageComboBox.Name = "clientLanguageComboBox";
clientLanguageComboBox.Size = new System.Drawing.Size(307, 23);
clientLanguageComboBox.TabIndex = 0;
//
// saveButton
//
saveButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
saveButton.DialogResult = System.Windows.Forms.DialogResult.OK;
saveButton.Location = new System.Drawing.Point(153, 321);
saveButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
saveButton.Name = "saveButton";
saveButton.Size = new System.Drawing.Size(88, 27);
saveButton.TabIndex = 9;
saveButton.Text = "OK";
saveButton.UseVisualStyleBackColor = true;
saveButton.Click += SaveButtonClick;
//
// closeButton
//
closeButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
closeButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
closeButton.Location = new System.Drawing.Point(247, 321);
closeButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
closeButton.Name = "closeButton";
closeButton.Size = new System.Drawing.Size(88, 27);
closeButton.TabIndex = 10;
closeButton.Text = "Cancel";
closeButton.UseVisualStyleBackColor = true;
//
// groupBox5
//
groupBox5.Controls.Add(clientResolutionComboBox);
groupBox5.Location = new System.Drawing.Point(19, 13);
groupBox5.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox5.Name = "groupBox5";
groupBox5.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox5.Size = new System.Drawing.Size(315, 45);
groupBox5.TabIndex = 11;
groupBox5.TabStop = false;
groupBox5.Text = "Screen Resolution";
//
// clientResolutionComboBox
//
clientResolutionComboBox.Dock = System.Windows.Forms.DockStyle.Bottom;
clientResolutionComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
clientResolutionComboBox.FormattingEnabled = true;
clientResolutionComboBox.Location = new System.Drawing.Point(4, 19);
clientResolutionComboBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
clientResolutionComboBox.Name = "clientResolutionComboBox";
clientResolutionComboBox.Size = new System.Drawing.Size(307, 23);
clientResolutionComboBox.TabIndex = 1;
//
// groupBox1
//
groupBox1.Controls.Add(colorDepthComboBox);
groupBox1.Location = new System.Drawing.Point(19, 65);
groupBox1.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox1.Name = "groupBox1";
groupBox1.Padding = new System.Windows.Forms.Padding(4, 3, 4, 3);
groupBox1.Size = new System.Drawing.Size(316, 44);
groupBox1.TabIndex = 12;
groupBox1.TabStop = false;
groupBox1.Text = "Color Depth";
//
// colorDepthComboBox
//
colorDepthComboBox.Dock = System.Windows.Forms.DockStyle.Bottom;
colorDepthComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
colorDepthComboBox.FormattingEnabled = true;
colorDepthComboBox.Items.AddRange(new object[] { "Min Color (16 bit)", "Max Color (32 bit)" });
colorDepthComboBox.Location = new System.Drawing.Point(4, 18);
colorDepthComboBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
colorDepthComboBox.Name = "colorDepthComboBox";
colorDepthComboBox.Size = new System.Drawing.Size(308, 23);
colorDepthComboBox.TabIndex = 2;
//
// ClientSettingsDialog
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
CancelButton = closeButton;
ClientSize = new System.Drawing.Size(349, 361);
Controls.Add(groupBox1);
Controls.Add(groupBox5);
Controls.Add(closeButton);
Controls.Add(saveButton);
Controls.Add(groupBox4);
Controls.Add(groupBox3);
Controls.Add(windowModeCheckBox);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
Name = "ClientSettingsDialog";
StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
Text = "Change Client Settings";
groupBox3.ResumeLayout(false);
groupBox3.PerformLayout();
((System.ComponentModel.ISupportInitialize)soundVolumeTrackBar).EndInit();
groupBox4.ResumeLayout(false);
groupBox5.ResumeLayout(false);
groupBox1.ResumeLayout(false);
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.CheckBox windowModeCheckBox;
private System.Windows.Forms.CheckBox musicActiveCheckBox;
private System.Windows.Forms.CheckBox soundActiveCheckBox;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.GroupBox groupBox4;
private System.Windows.Forms.ComboBox clientLanguageComboBox;
private System.Windows.Forms.Button saveButton;
private System.Windows.Forms.TrackBar soundVolumeTrackBar;
private System.Windows.Forms.Button closeButton;
private System.Windows.Forms.GroupBox groupBox5;
private System.Windows.Forms.ComboBox clientResolutionComboBox;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.ComboBox colorDepthComboBox;
}
}

View File

@@ -0,0 +1,83 @@
// <copyright file="ClientSettingsDialog.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.Versioning;
using System.Windows.Forms;
/// <summary>
/// <see cref="ClientSettingsDialog"/>.
/// </summary>
[SupportedOSPlatform("windows")]
internal partial class ClientSettingsDialog : Form
{
/// <summary>
/// Initializes a new instance of the <see cref="ClientSettingsDialog"/> class.
/// </summary>
public ClientSettingsDialog()
{
this.InitializeComponent();
this.clientResolutionComboBox.DisplayMember = nameof(ClientResolution.Caption);
this.clientResolutionComboBox.ValueMember = nameof(ClientResolution.Index);
this.clientResolutionComboBox.DataSource = LauncherSettings.DefaultResolutions;
this.Icon = Icon.FromHandle(Properties.Resources.Settings_16x.GetHicon());
var config = new ClientSettings();
config.Load();
this.ReadConfig(config);
}
/// <summary>
/// Gets or sets the available client resolutions.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public BindingList<ClientResolution>? Resolutions
{
get => this.clientResolutionComboBox.DataSource as BindingList<ClientResolution>;
set
{
if (value?.Count > 0)
{
var index = this.clientResolutionComboBox.SelectedIndex;
this.clientResolutionComboBox.DataSource = value;
if (value?.Count > index)
{
this.clientResolutionComboBox.SelectedIndex = index;
}
}
}
}
private void SaveButtonClick(object sender, EventArgs e)
{
var config = new ClientSettings();
this.SetConfig(config);
config.Save();
}
private void ReadConfig(ClientSettings clientSettings)
{
this.colorDepthComboBox.SelectedIndex = clientSettings.ClientColorDepth == 0 ? 0 : 1;
this.musicActiveCheckBox.Checked = clientSettings.IsMusicEnabled;
this.soundActiveCheckBox.Checked = clientSettings.IsSoundEnabled;
this.soundVolumeTrackBar.Value = clientSettings.VolumeLevel;
this.windowModeCheckBox.Checked = clientSettings.IsWindowModeActive;
this.clientResolutionComboBox.SelectedIndex = clientSettings.ResolutionIndex;
this.clientLanguageComboBox.SelectedIndex = (int)clientSettings.LangSelection;
}
private void SetConfig(ClientSettings clientSettings)
{
clientSettings.ClientColorDepth = (ClientColorDepth)this.colorDepthComboBox.SelectedIndex;
clientSettings.IsMusicEnabled = this.musicActiveCheckBox.Checked;
clientSettings.IsSoundEnabled = this.soundActiveCheckBox.Checked;
clientSettings.VolumeLevel = this.soundVolumeTrackBar.Value;
clientSettings.IsWindowModeActive = this.windowModeCheckBox.Checked;
clientSettings.ResolutionIndex = this.clientResolutionComboBox.SelectedIndex;
clientSettings.LangSelection = (ClientLanguage)this.clientLanguageComboBox.SelectedIndex;
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,142 @@
namespace MUnique.OpenMU.ClientLauncher;
partial class HostConfigurationDialog
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
_serverPortControl = new System.Windows.Forms.NumericUpDown();
_serverAddressTextBox = new System.Windows.Forms.TextBox();
label2 = new System.Windows.Forms.Label();
label1 = new System.Windows.Forms.Label();
_descriptionTextBox = new System.Windows.Forms.TextBox();
button1 = new System.Windows.Forms.Button();
_cancelButton = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)_serverPortControl).BeginInit();
SuspendLayout();
//
// _serverPortControl
//
_serverPortControl.Location = new System.Drawing.Point(273, 36);
_serverPortControl.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
_serverPortControl.Maximum = new decimal(new int[] { 65535, 0, 0, 0 });
_serverPortControl.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
_serverPortControl.Name = "_serverPortControl";
_serverPortControl.Size = new System.Drawing.Size(68, 23);
_serverPortControl.TabIndex = 10;
_serverPortControl.Value = new decimal(new int[] { 44405, 0, 0, 0 });
//
// _serverAddressTextBox
//
_serverAddressTextBox.Location = new System.Drawing.Point(113, 35);
_serverAddressTextBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
_serverAddressTextBox.Name = "_serverAddressTextBox";
_serverAddressTextBox.Size = new System.Drawing.Size(152, 23);
_serverAddressTextBox.TabIndex = 9;
_serverAddressTextBox.Text = "127.127.127.127";
//
// label2
//
label2.AutoSize = true;
label2.Location = new System.Drawing.Point(13, 38);
label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label2.Name = "label2";
label2.Size = new System.Drawing.Size(89, 15);
label2.TabIndex = 8;
label2.Text = "Server-Address:";
//
// label1
//
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(13, 9);
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new System.Drawing.Size(70, 15);
label1.TabIndex = 11;
label1.Text = "Description:";
//
// _descriptionTextBox
//
_descriptionTextBox.Location = new System.Drawing.Point(113, 6);
_descriptionTextBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
_descriptionTextBox.Name = "_descriptionTextBox";
_descriptionTextBox.PlaceholderText = "<Enter a description here>";
_descriptionTextBox.Size = new System.Drawing.Size(228, 23);
_descriptionTextBox.TabIndex = 12;
//
// button1
//
button1.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
button1.DialogResult = System.Windows.Forms.DialogResult.OK;
button1.Location = new System.Drawing.Point(194, 83);
button1.Name = "button1";
button1.Size = new System.Drawing.Size(75, 23);
button1.TabIndex = 13;
button1.Text = "OK";
button1.UseVisualStyleBackColor = true;
//
// _cancelButton
//
_cancelButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right;
_cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel;
_cancelButton.Location = new System.Drawing.Point(113, 83);
_cancelButton.Name = "_cancelButton";
_cancelButton.Size = new System.Drawing.Size(75, 23);
_cancelButton.TabIndex = 14;
_cancelButton.Text = "Cancel";
_cancelButton.UseVisualStyleBackColor = true;
//
// HostConfiguration
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(362, 118);
Controls.Add(_cancelButton);
Controls.Add(button1);
Controls.Add(_descriptionTextBox);
Controls.Add(label1);
Controls.Add(_serverPortControl);
Controls.Add(_serverAddressTextBox);
Controls.Add(label2);
FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
MinimizeBox = false;
Name = "HostConfiguration";
Text = "Configure Connection";
((System.ComponentModel.ISupportInitialize)_serverPortControl).EndInit();
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.NumericUpDown _serverPortControl;
private System.Windows.Forms.TextBox _serverAddressTextBox;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.TextBox _descriptionTextBox;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button _cancelButton;
}

View File

@@ -0,0 +1,46 @@
// <copyright file="HostConfigurationDialog.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
using System.ComponentModel;
using System.Windows.Forms;
/// <summary>
/// Dialog for connection settings of a server.
/// </summary>
public partial class HostConfigurationDialog : Form
{
/// <summary>
/// Initializes a new instance of the <see cref="HostConfigurationDialog"/> class.
/// </summary>
public HostConfigurationDialog()
{
this.InitializeComponent();
}
/// <summary>
/// Gets or sets the settings.
/// </summary>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ServerHostSettings Settings
{
get
{
return new ServerHostSettings
{
Description = this._descriptionTextBox.Text,
Address = this._serverAddressTextBox.Text,
Port = (int)this._serverPortControl.Value,
};
}
set
{
this._descriptionTextBox.Text = value.Description;
this._serverAddressTextBox.Text = value.Address;
this._serverPortControl.Value = value.Port;
}
}
}

View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,31 @@
// <copyright file="ILauncher.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
/// <summary>
/// The interface of a launcher.
/// </summary>
internal interface ILauncher
{
/// <summary>
/// Gets or sets the host ip.
/// </summary>
string? HostAddress { get; set; }
/// <summary>
/// Gets or sets the host port.
/// </summary>
int HostPort { get; set; }
/// <summary>
/// Gets or sets the main executable path.
/// </summary>
string? MainExePath { get; set; }
/// <summary>
/// Launches the MU Online client with the specified settings.
/// </summary>
void LaunchClient();
}

View File

@@ -0,0 +1,183 @@
// <copyright file="Launcher.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Windows.Forms;
using Microsoft.Win32;
/// <summary>
/// The default launcher, which writes
/// * host and port into the registry (official way until season 6 at the global server)
/// * adds parameters /u and /p (works in some other versions of the game client)
/// before starting the main.exe.
/// </summary>
public class Launcher : ILauncher
{
/// <summary>
/// Gets or sets the host ip.
/// </summary>
public string? HostAddress { get; set; }
/// <summary>
/// Gets or sets the host port.
/// </summary>
public int HostPort { get; set; }
/// <summary>
/// Gets or sets the main executable path.
/// </summary>
public string? MainExePath { get; set; }
/// <summary>
/// Launches Mu with the set configuration.
/// </summary>
public void LaunchClient()
{
if (string.IsNullOrWhiteSpace(this.HostAddress))
{
MessageBox.Show("Host address is not set.", "Error");
return;
}
if (string.IsNullOrWhiteSpace(this.MainExePath))
{
MessageBox.Show("The path to the main.exe is not set.", "Error");
return;
}
if (this.ResolveHost() is not { } ipAddress)
{
MessageBox.Show($"Address '{this.HostAddress} could not be resolved to an IPv4 address.", "Error");
return;
}
if (OperatingSystem.IsWindows())
{
using var localMachineKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
using var key = localMachineKey.CreateSubKey(@"SOFTWARE\WebZen\Mu\Connection");
key.SetValue("Key", Environment.TickCount, RegistryValueKind.DWord);
key.SetValue("ParameterA", this.HostEncode(ipAddress), RegistryValueKind.String);
key.SetValue("ParameterB", this.PortEncode(ipAddress), RegistryValueKind.DWord);
}
else
{
if (MessageBox.Show(
"IP and Port couldn't be set, because the operating system is not windows. Try to launch the game client anyway?",
string.Empty,
MessageBoxButtons.YesNo) != DialogResult.Yes)
{
return;
}
}
var info = new DirectoryInfo(this.MainExePath!);
var startInfo = new ProcessStartInfo(this.MainExePath, ["connect", $"/u{ipAddress}", $"/p{this.HostPort}"])
{
WorkingDirectory = info.Parent!.FullName,
UseShellExecute = true,
Verb = "open",
};
if (OperatingSystem.IsWindows())
{
startInfo.LoadUserProfile = true;
}
Process.Start(startInfo);
}
/// <summary>
/// Encodes the Port, so that the main.exe can read it.
/// </summary>
/// <returns>Encoded port value which will be put into ParameterB.</returns>
private int PortEncode(string ipAddress)
{
var port = this.HostPort;
switch (ipAddress.Length % 4)
{
case 0:
port += 12 - (((port / 4) % 4) * 8);
return port;
case 1:
port += 7 - ((port % 8) * 2);
return port;
case 2:
port += 3 - ((port % 4) * 2);
return port;
case 3:
port += (0x13 - ((port % 4) * 2)) - (((port / 0x10) % 2) * 0x20);
return port;
default:
// we'll hopefully never run into this one
return port;
}
}
private string? ResolveHost()
{
if (IPAddress.TryParse(this.HostAddress, out _))
{
return this.HostAddress;
}
var entry = Dns.GetHostEntry(this.HostAddress!);
if (entry.AddressList.FirstOrDefault(a => a.AddressFamily == AddressFamily.InterNetwork) is { } ipAddress)
{
var address = ipAddress.ToString();
// The game client blocks connections to 127.0.0.1 (probably to prevent cheating).
return address == "127.0.0.1" ? "127.127.127.127" : address;
}
return null;
}
/// <summary>
/// Encodes the IP Address of the server, so that the main.exe can read it.
/// </summary>
/// <returns>Encoded string value of the ip address which to put into ParameterA.</returns>
private string HostEncode(string ipAddress)
{
var result = new StringBuilder();
var counter = 0;
foreach (var ch in ipAddress)
{
var encodedCharacter = '\0';
counter++;
switch (counter)
{
case 1:
encodedCharacter = (char)(ch + '\f');
break;
case 2:
encodedCharacter = (char)(ch + '\a');
break;
case 3:
encodedCharacter = (char)(ch + '\x0003');
break;
case 4:
encodedCharacter = (char)(ch + '\x0013');
counter = 0;
break;
default:
// we should not run into this case, since it's always 1 to 4.
break;
}
result.Append(encodedCharacter);
}
return result.ToString();
}
}

View File

@@ -0,0 +1,44 @@
// <copyright file="LauncherSettings.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
/// <summary>
/// Settings of the launcher.
/// </summary>
public class LauncherSettings
{
/// <summary>
/// Gets the default resolutions, which are based on the open source MuMain.
/// </summary>
public static ClientResolution[] DefaultResolutions =>
[
new(0, "640x480"),
new(1, "800x600"),
new(2, "1024x768"),
new(3, "1280x1024"),
new(4, "1600x1200"),
new(5, "1864x1400"),
new(6, "1600x900"),
new(7, "1600x1280"),
new(8, "1680x1050"),
new(9, "1920x1080"),
new(10, "2560x1440"),
];
/// <summary>
/// Gets or sets the main executable path.
/// </summary>
public string? MainExePath { get; set; }
/// <summary>
/// Gets or sets the configured hosts.
/// </summary>
public List<ServerHostSettings> Hosts { get; set; } = [];
/// <summary>
/// Gets or sets the resolutions.
/// </summary>
public List<ClientResolution> AvailableResolutions { get; set; } = [];
}

View File

@@ -0,0 +1,69 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net10.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable;CS4014;VSTHRD110;VSTHRD100</WarningsAsErrors>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<OutputPath>bin\Debug\</OutputPath>
<DocumentationFile>bin\Debug\MUnique.OpenMU.ClientLauncher.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<OutputPath>bin\Release\</OutputPath>
<DocumentationFile>bin\Release\MUnique.OpenMU.ClientLauncher.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<Content Include="Rocket_16x.ico" />
</ItemGroup>
<PropertyGroup>
<ApplicationManifest>MUnique.OpenMU.ClientLauncher.exe.manifest</ApplicationManifest>
<ApplicationIcon>Rocket_16x.ico</ApplicationIcon>
<StartupObject>MUnique.OpenMU.ClientLauncher.Program</StartupObject>
</PropertyGroup>
<ItemGroup>
<None Update="MUnique.OpenMU.ClientLauncher.exe.manifest">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Compile Update="MainForm.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="ClientSettingsDialog.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
</Compile>
<Compile Update="MainForm.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Update="ClientSettingsDialog.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>ClientSettingsDialog.cs</DependentUpon>
</Compile>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="ClientSettingsDialog.resx">
<DependentUpon>ClientSettingsDialog.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Update="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on and is
is designed to work with. Uncomment the appropriate elements and Windows will
automatically selected the most compatible environment. -->
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
</windowsSettings>
</application>
-->
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>

207
src/ClientLauncher/MainForm.Designer.cs generated Normal file
View File

@@ -0,0 +1,207 @@
namespace MUnique.OpenMU.ClientLauncher
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
_launchButton = new System.Windows.Forms.Button();
label1 = new System.Windows.Forms.Label();
openFileDialog = new System.Windows.Forms.OpenFileDialog();
MainExePathTextBox = new System.Windows.Forms.TextBox();
SearchMainExeButton = new System.Windows.Forms.Button();
label2 = new System.Windows.Forms.Label();
configurationDialogButton = new System.Windows.Forms.Button();
_editHostButton = new System.Windows.Forms.Button();
_addHostButton = new System.Windows.Forms.Button();
_serversComboBox = new System.Windows.Forms.ComboBox();
_removeHostButton = new System.Windows.Forms.Button();
SuspendLayout();
//
// _launchButton
//
_launchButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
_launchButton.Location = new System.Drawing.Point(505, 45);
_launchButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
_launchButton.Name = "_launchButton";
_launchButton.Size = new System.Drawing.Size(147, 27);
_launchButton.TabIndex = 0;
_launchButton.Text = "Launch Client";
_launchButton.UseVisualStyleBackColor = true;
_launchButton.Click += LaunchClick;
//
// label1
//
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(14, 17);
label1.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label1.Name = "label1";
label1.Size = new System.Drawing.Size(85, 15);
label1.TabIndex = 1;
label1.Text = "main.exe Path:";
//
// openFileDialog
//
openFileDialog.FileName = "main.exe";
openFileDialog.Filter = "Executeables|*.exe";
//
// MainExePathTextBox
//
MainExePathTextBox.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
MainExePathTextBox.Location = new System.Drawing.Point(107, 14);
MainExePathTextBox.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
MainExePathTextBox.Name = "MainExePathTextBox";
MainExePathTextBox.Size = new System.Drawing.Size(467, 23);
MainExePathTextBox.TabIndex = 2;
MainExePathTextBox.Text = "main.exe";
//
// SearchMainExeButton
//
SearchMainExeButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
SearchMainExeButton.Location = new System.Drawing.Point(582, 12);
SearchMainExeButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
SearchMainExeButton.Name = "SearchMainExeButton";
SearchMainExeButton.Size = new System.Drawing.Size(35, 27);
SearchMainExeButton.TabIndex = 3;
SearchMainExeButton.Text = "...";
SearchMainExeButton.UseVisualStyleBackColor = true;
SearchMainExeButton.Click += SearchMainExeButtonClick;
//
// label2
//
label2.AutoSize = true;
label2.Location = new System.Drawing.Point(57, 54);
label2.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
label2.Name = "label2";
label2.Size = new System.Drawing.Size(42, 15);
label2.TabIndex = 4;
label2.Text = "Server:";
//
// configurationDialogButton
//
configurationDialogButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
configurationDialogButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
configurationDialogButton.Image = Properties.Resources.Settings_16x;
configurationDialogButton.Location = new System.Drawing.Point(625, 12);
configurationDialogButton.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
configurationDialogButton.Name = "configurationDialogButton";
configurationDialogButton.Size = new System.Drawing.Size(27, 27);
configurationDialogButton.TabIndex = 4;
configurationDialogButton.UseVisualStyleBackColor = true;
configurationDialogButton.Click += ConfigurationDialogButtonClick;
//
// _editHostButton
//
_editHostButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
_editHostButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
_editHostButton.Image = Properties.Resources.Edit_16x;
_editHostButton.Location = new System.Drawing.Point(446, 47);
_editHostButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
_editHostButton.Name = "_editHostButton";
_editHostButton.Size = new System.Drawing.Size(23, 23);
_editHostButton.TabIndex = 7;
_editHostButton.UseVisualStyleBackColor = true;
_editHostButton.Click += OnEditHostButtonClick;
//
// _addHostButton
//
_addHostButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
_addHostButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
_addHostButton.Image = Properties.Resources.Add_16x;
_addHostButton.Location = new System.Drawing.Point(419, 47);
_addHostButton.Margin = new System.Windows.Forms.Padding(2, 3, 2, 3);
_addHostButton.Name = "_addHostButton";
_addHostButton.Size = new System.Drawing.Size(23, 23);
_addHostButton.TabIndex = 6;
_addHostButton.UseVisualStyleBackColor = true;
_addHostButton.Click += OnAddHostButtonClick;
//
// _serversComboBox
//
_serversComboBox.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right;
_serversComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
_serversComboBox.FormattingEnabled = true;
_serversComboBox.Location = new System.Drawing.Point(107, 48);
_serversComboBox.Name = "_serversComboBox";
_serversComboBox.Size = new System.Drawing.Size(307, 23);
_serversComboBox.TabIndex = 5;
_serversComboBox.SelectedValueChanged += OnServersComboBoxSelectedIndexChanged;
//
// _removeHostButton
//
_removeHostButton.Anchor = System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right;
_removeHostButton.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
_removeHostButton.Image = Properties.Resources.Remove_16x;
_removeHostButton.Location = new System.Drawing.Point(473, 46);
_removeHostButton.Margin = new System.Windows.Forms.Padding(2, 3, 5, 3);
_removeHostButton.Name = "_removeHostButton";
_removeHostButton.Size = new System.Drawing.Size(23, 23);
_removeHostButton.TabIndex = 8;
_removeHostButton.UseVisualStyleBackColor = true;
_removeHostButton.Click += OnRemoveHostButtonClick;
//
// MainForm
//
AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
ClientSize = new System.Drawing.Size(665, 82);
Controls.Add(_removeHostButton);
Controls.Add(_serversComboBox);
Controls.Add(_addHostButton);
Controls.Add(_editHostButton);
Controls.Add(configurationDialogButton);
Controls.Add(label2);
Controls.Add(SearchMainExeButton);
Controls.Add(MainExePathTextBox);
Controls.Add(label1);
Controls.Add(_launchButton);
Icon = (System.Drawing.Icon)resources.GetObject("$this.Icon");
Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
MaximizeBox = false;
MaximumSize = new System.Drawing.Size(1257, 121);
Name = "MainForm";
Text = "MU Game Client Launcher";
ResumeLayout(false);
PerformLayout();
}
#endregion
private System.Windows.Forms.Button _launchButton;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.OpenFileDialog openFileDialog;
private System.Windows.Forms.TextBox MainExePathTextBox;
private System.Windows.Forms.Button SearchMainExeButton;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Button configurationDialogButton;
private System.Windows.Forms.Button _editHostButton;
private System.Windows.Forms.Button _addHostButton;
private System.Windows.Forms.ComboBox _serversComboBox;
private System.Windows.Forms.Button _removeHostButton;
}
}

View File

@@ -0,0 +1,197 @@
// <copyright file="MainForm.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
#pragma warning disable CA1416 // This project is compiled for windows
namespace MUnique.OpenMU.ClientLauncher;
using System.ComponentModel;
using System.IO;
using System.Windows.Forms;
using System.Xml.Serialization;
/// <summary>
/// The main form of the launcher.
/// </summary>
public partial class MainForm : Form
{
private const string ConfigFileName = "launcher.config";
/// <summary>
/// Gets or sets the binding list for the configured hosts.
/// </summary>
private BindingList<ServerHostSettings> _hostsBindingList = new();
/// <summary>
/// Initializes a new instance of the <see cref="MainForm"/> class.
/// </summary>
public MainForm()
{
this.InitializeComponent();
this.LoadOptions();
this.UpdateButtonStates();
}
private BindingList<ServerHostSettings> Hosts
{
get => this._hostsBindingList;
set
{
this._hostsBindingList = value;
this._serversComboBox.DataSource = value;
}
}
private BindingList<ClientResolution> Resolutions { get; set; } = new(LauncherSettings.DefaultResolutions);
/// <summary>
/// Launches the MU Online client (main.exe) to connect to the configured address.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
private void LaunchClick(object sender, EventArgs e)
{
try
{
var selectedHost = (ServerHostSettings)this._serversComboBox.SelectedItem!;
var launcher = new Launcher
{
HostAddress = selectedHost.Address,
HostPort = selectedHost.Port,
MainExePath = this.MainExePathTextBox.Text,
};
launcher.LaunchClient();
this.SaveCurrentOptions();
}
catch (UnauthorizedAccessException)
{
MessageBox.Show("Can't access Windows Registry. To use the launcher, run it as Administrator.");
}
catch (Exception ex)
{
MessageBox.Show("Error Starting MU. Path correct?" + Environment.NewLine + ex.Message);
}
}
private void LoadOptions()
{
this._serversComboBox.DataSource = this.Hosts;
if (!File.Exists(ConfigFileName))
{
return;
}
try
{
var reader = new XmlSerializer(typeof(LauncherSettings));
using var file = new StreamReader(ConfigFileName);
if (reader.Deserialize(file) is LauncherSettings launcherSettings)
{
this.MainExePathTextBox.Text = launcherSettings.MainExePath;
this.Hosts = new BindingList<ServerHostSettings>(launcherSettings.Hosts);
if (launcherSettings.AvailableResolutions?.Any() is true)
{
this.Resolutions = new(launcherSettings.AvailableResolutions);
}
else
{
this.Resolutions = new(LauncherSettings.DefaultResolutions);
}
}
file.Close();
}
catch
{
this.Hosts.Clear();
this.Hosts.Add(new ServerHostSettings { Description = "Local ConnectServer", Address = "localhost", Port = 44405 });
this.Hosts.Add(new ServerHostSettings { Description = "Local GameServer 1", Address = "localhost", Port = 55901 });
}
}
private void SaveCurrentOptions()
{
var settings = new LauncherSettings
{
Hosts = this._hostsBindingList.ToList(),
MainExePath = this.MainExePathTextBox.Text,
AvailableResolutions = this.Resolutions.ToList(),
};
var writer = new XmlSerializer(typeof(LauncherSettings));
using var file = File.Create(ConfigFileName);
writer.Serialize(file, settings);
file.Close();
}
private void SearchMainExeButtonClick(object sender, EventArgs e)
{
var dialogResult = this.openFileDialog.ShowDialog(this);
if (dialogResult == DialogResult.OK)
{
this.MainExePathTextBox.Text = this.openFileDialog.FileName;
}
}
private void ConfigurationDialogButtonClick(object sender, EventArgs e)
{
if (OperatingSystem.IsWindows())
{
using var configDialog = new ClientSettingsDialog();
configDialog.Resolutions = this.Resolutions;
configDialog.ShowDialog(this);
}
else
{
MessageBox.Show("Changing the configuration of the MU game client is only supported on windows.");
}
}
private void OnAddHostButtonClick(object sender, EventArgs e)
{
using var dialog = new HostConfigurationDialog();
if (dialog.ShowDialog(this) == DialogResult.OK)
{
var settings = dialog.Settings;
this._hostsBindingList.Add(settings);
this.SaveCurrentOptions();
this.UpdateButtonStates();
}
}
private void OnEditHostButtonClick(object sender, EventArgs e)
{
var selectedConfiguration = (ServerHostSettings)this._serversComboBox.SelectedItem!;
using var dialog = new HostConfigurationDialog();
dialog.Settings = selectedConfiguration;
if (dialog.ShowDialog(this) == DialogResult.OK)
{
var editedConfiguration = dialog.Settings;
selectedConfiguration.Port = editedConfiguration.Port;
selectedConfiguration.Address = editedConfiguration.Address;
selectedConfiguration.Description = editedConfiguration.Description;
this.SaveCurrentOptions();
}
}
private void OnRemoveHostButtonClick(object sender, EventArgs e)
{
this._hostsBindingList.RemoveAt(this._serversComboBox.SelectedIndex);
this.SaveCurrentOptions();
}
private void OnServersComboBoxSelectedIndexChanged(object sender, EventArgs e)
{
this.UpdateButtonStates();
}
private void UpdateButtonStates()
{
var isServerSelected = this._serversComboBox.SelectedItem is ServerHostSettings;
this._editHostButton.Enabled = isServerSelected;
this._removeHostButton.Enabled = isServerSelected;
this._launchButton.Enabled = isServerSelected;
}
}

View File

@@ -0,0 +1,303 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="openFileDialog.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="$this.Icon" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
AAABAAEAMjIAAAEAIADIKAAAFgAAACgAAAAyAAAAZAAAAAEAIAAAAAAAECcAACMuAAAjLgAAAAAAAAAA
AAD29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/19fWfAAAAAAAAAAAAAAAAAAAAAPHx8RL29vZ09vb23Pb29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9/f3mv///wEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//X19Z8AAAAAAAAAAAAAAAD39/ce9vb23fb29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9fX1nP///wIAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9fX1nwAAAAAAAAAA9/f3Hvb2
9t329vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9fX1nP///wIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD29vb/9vb2//b29v9ubm7/Wlpa/1paWv9aWlr/Wlpa/1paWv9aWlr/Wlpa/1paWv+oqKj/9vb2//b2
9v/19fWfAAAAAPf39yD29vbf9vb2//b29v/19fX/paWl/2BgYP9aWlr/Wlpa/1paWv9aWlr/Wlpa/1pa
Wv9aWlr/Wlpa/11dXf/MzMz/9vb2//b29v/29vb/9vb2bAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2/1lZWf9CQkL/QkJC/0JCQv9CQkL/QkJC/0JC
Qv9CQkL/QkJC/5ycnP/29vb/9vb2//X19Z/4+Pgl9vb24vb29v/29vb/9fX1/42Njf9CQkL/QkJC/0JC
Qv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0tLS//U1NT/9vb2//b29v/19fWAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2//b29v/29vb/WVlZ/0JC
Qv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/nJyc//b29v/29vb/9vb2wPb29uT29vb/9vb2//X1
9f+Pj4//QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/7Ky
sv/29vb/9vb2//X19YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD29vb/9vb2//b29v9ZWVn/QkJC/0JCQv+lpaX/xsbG/8bGxv/Gxsb/xsbG/8bGxv/e3t7/9vb2//b2
9v/29vb/9vb2//b29v/19fX/jY2N/0JCQv9CQkL/QkJC/2xsbP/Gxsb/xsbG/8bGxv/Gxsb/xsbG/8bG
xv/Gxsb/Y2Nj/0JCQv9CQkL/srKy//b29v/29vb/9fX1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2/1lZWf9CQkL/QkJC/8nJyf/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9fX1/4iIiP9CQkL/QkJC/0JCQv9MTEz/5+fn//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v9vb2//QkJC/0JCQv+ysrL/9vb2//b29v/19fWAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2//b29v/29vb/WVlZ/0JC
Qv9CQkL/ycnJ//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//X19f+NjY3/QkJC/0JC
Qv9CQkL/QkJC/0JCQv9gYGD/2NjY//b29v/29vb/9vb2//b29v/29vb/9vb2/29vb/9CQkL/QkJC/7Ky
sv/29vb/9vb2//X19YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD29vb/9vb2//b29v9ZWVn/QkJC/0JCQv/Jycn/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/19fX/iIiI/0JCQv9CQkL/QkJC/0lJSf9CQkL/QkJC/0JCQv9MTEz/ubm5//b29v/29vb/9vb2//b2
9v/29vb/b29v/0JCQv9CQkL/srKy//b29v/29vb/9fX1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2/1lZWf9CQkL/QkJC/8nJyf/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9fX1/4iIiP9CQkL/QkJC/0JCQv9tbW3/6enp/5GRkf9DQ0P/QkJC/0JC
Qv9CQkL/jo6O//Ly8v/29vb/9vb2//b29v9vb2//QkJC/0JCQv+ysrL/9vb2//b29v/19fWAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2//b29v/29vb/WVlZ/0JC
Qv9CQkL/ycnJ//b29v/29vb/9vb2//b29v/29vb/9vb2//X19f+IiIj/QkJC/0JCQv9CQkL/bW1t/+3t
7f/29vb/9vb2/8HBwf9OTk7/QkJC/0JCQv9CQkL/b29v/+vr6//29vb/9vb2/29vb/9CQkL/QkJC/7Ky
sv/29vb/9vb2//X19YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD29vb/9vb2//b29v+tra3/oqKi/6Kiov/h4eH/9vb2//b29v/29vb/9vb2//b29v/19fX/iIiI/0JC
Qv9CQkL/QkJC/21tbf/t7e3/9vb2//b29v/29vb/9vb2/9zc3P9hYWH/QkJC/0JCQv9CQkL/YGBg/+Hh
4f/29vb/b29v/0JCQv9CQkL/srKy//b29v/29vb/9fX1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9fX1/4iIiP9CQkL/QkJC/0JCQv9tbW3/7e3t//b29v/29vb/9vb2//b29v/29vb/9vb2/+3t
7f99fX3/QkJC/0JCQv9CQkL/VFRU/9XV1f9vb2//QkJC/0JCQv+ysrL/9vb2//b29v/19fWAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//X19f+IiIj/QkJC/0JCQv9CQkL/bW1t/+3t7f/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//X19f+UlJT/QkJC/0JCQv9CQkL/TExM/1VVVf9CQkL/QkJC/7Ky
sv/29vb/9vb2//X19YAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD39/eZ9/f3mff395n39/eZ9/f3mfX19bv29vb/9vb2//b29v/19fX/jY2N/0JCQv9CQkL/QkJC/2xs
bP/s7Oz/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v+oqKj/Q0ND/0JC
Qv9CQkL/QkJC/0JCQv9CQkL/srKy//b29v/29vb/9fX1gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4+Pgk9vb24/b29v/29vb/9fX1/42N
jf9CQkL/QkJC/0JCQv9sbGz/7Ozs//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v+2trb/RkZG/0JCQv9CQkL/QkJC/0JCQv+ysrL/9vb2//b29v/19fWfAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9/f3Ifb2
9uP29vb/9vb2//X19f+IiIj/QkJC/0JCQv9CQkL/bm5u/+3t7f/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/AwMD/RkZG/0JCQv9CQkL/QkJC/6ur
q//29vb/9vb2//b29v/19fVqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAPf39x729vbd9vb2//b29v/19fX/jY2N/0JCQv9CQkL/QkJC/2xsbP/s7Oz/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v+8vLz/RUVF/0JCQv9CQkL/TExM/9fX1//29vb/9vb2//b29v329vZSAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD39/cg9vb23/b29v/29vb/9fX1/42Njf9CQkL/QkJC/0JC
Qv9ubm7/7e3t//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v+xsbH/Q0ND/0JCQv9CQkL/WVlZ/+bm5v/29vb/9vb2//b2
9vb29vY2AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8fHxEvb29uD29vb/9vb2//X1
9f+Ojo7/QkJC/0JCQv9CQkL/TExM/+zs7P/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v+pqan/QkJC/0JC
Qv9CQkL/aGho//Hx8f/29vb/9vb2//b29t7v7+8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD29vZ09vb2//b29v/19fX/jo6O/0JCQv9CQkL/QkJC/0JCQv9CQkL/m5ub//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v+cnJz/QkJC/0JCQv9CQkL/ioqK//b29v/29vb/9vb2//b29qwAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29tz29vb/9vb2/6Wlpf9CQkL/QkJC/0JCQv9KSkr/QkJC/0JC
Qv9ERET/ysrK//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//X19f99fX3/QkJC/0JCQv9CQkL/s7Oz//b2
9v/29vb/9vb2//X19WgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2//b29v/29vb/YGBg/0JC
Qv9CQkL/ampq/+Pj4/9fX1//QkJC/0JCQv9TU1P/4+Pj//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2/+7u
7v9jY2P/QkJC/0JCQv9JSUn/2dnZ//b29v/29vb/9vb28vb29hsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAD29vb/9vb2//b29v9ZWVn/QkJC/0JCQv/IyMj/9vb2/9fX1/9JSUn/QkJC/0JCQv9oaGj/8PDw//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2/9ra2v9LS0v/QkJC/0JCQv9qamr/9PT0//b29v/29vb/9vb2pgAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2/1lZWf9CQkL/QkJC/8nJyf/29vb/9vb2/7Oz
s/9CQkL/QkJC/0JCQv+Ghob/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/8/Pz/6Wlpf9kZGT/RkZG/1ZWVv+QkJD/5OTk//b29v/29vb/9vb2/7Ozs/9CQkL/QkJC/0JC
Qv+jo6P/9vb2//b29v/29vb/9/f3QgAAAAAAAAAAAAAAAAAAAAAAAAAA9vb2//b29v/29vb/WVlZ/0JC
Qv9CQkL/ycnJ//b29v/29vb/9vb2/4iIiP9CQkL/QkJC/0JCQv+dnZ3/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//Pz8/95eXn/QkJC/0JCQv9CQkL/QkJC/0JCQv9WVlb/2tra//b2
9v/29vb/9vb2/319ff9CQkL/QkJC/0hISP/c3Nz/9vb2//b29v/29vbQ////AQAAAAAAAAAAAAAAAAAA
AAD29vb/9vb2//b29v9ZWVn/QkJC/0JCQv/Jycn/9vb2//b29v/29vb/8PDw/2xsbP9CQkL/QkJC/0JC
Qv+vr6//9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/o6Oj/0JCQv9CQkL/QkJC/0JC
Qv9CQkL/QkJC/0JCQv9ra2v/9fX1//b29v/29vb/5eXl/05OTv9CQkL/QkJC/3x8fP/29vb/9vb2//b2
9v/19fVPAAAAAAAAAAAAAAAAAAAAAPb29v/29vb/9vb2/1lZWf9CQkL/QkJC/8nJyf/29vb/9vb2//b2
9v/29vb/6enp/19fX/9CQkL/QkJC/0RERP++vr7/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v9ZWVn/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv/T09P/9vb2//b29v/29vb/sLCw/0JC
Qv9CQkL/QkJC/8vLy//29vb/9vb2//b29s7///8BAAAAAAAAAAAAAAAA9vb2//b29v/29vb/WVlZ/0JC
Qv9CQkL/ycnJ//b29v/29vb/9vb2//b29v/29vb/39/f/1RUVP9CQkL/QkJC/0hISP/IyMj/9vb2//b2
9v/29vb/9vb2//b29v/29vb/8PDw/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/7e3
t//29vb/9vb2//b29v/19fX/ZGRk/0JCQv9CQkL/Z2dn//X19f/29vb/9vb2//X19UoAAAAAAAAAAAAA
AAD29vb/9vb2//b29v9ZWVn/QkJC/0JCQv/Jycn/9vb2//b29v/29vb/9vb2//b29v/29vb/1NTU/0xM
TP9CQkL/QkJC/0lJSf/BwcH/9vb2//b29v/29vb/9vb2//b29v/19fX/R0dH/0JCQv9CQkL/QkJC/0JC
Qv9CQkL/QkJC/0JCQv9CQkL/wsLC//b29v/29vb/9vb2//b29v/FxcX/QkJC/0JCQv9CQkL/xMTE//b2
9v/29vb/9vb2sQAAAAAAAAAAAAAAAPb29v/29vb/9vb2/1lZWf9CQkL/QkJC/2ZmZv9ycnL/cnJy/3Jy
cv9ycnL/cnJy/3Jycv9ycnL/VlZW/0JCQv9CQkL/QkJC/0ZGRv+5ubn/9vb2//b29v/29vb/9vb2//b2
9v94eHj/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/01NTf/u7u7/9vb2//b29v/29vb/9vb2//b2
9v9ycnL/QkJC/0JCQv90dHT/9vb2//b29v/29vb79PT0GAAAAAAAAAAA9vb2//b29v/29vb/XFxc/0JC
Qv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0RE
RP+wsLD/9vb2//b29v/29vb/9vb2/9fX1/9KSkr/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/rq6u//b2
9v/29vb/9vb2//b29v/29vb/9vb2/8TExP9CQkL/QkJC/0NDQ//Y2Nj/9vb2//b29v/29vZzAAAAAAAA
AAD29vb/9vb2//b29v/MzMz/S0tL/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JC
Qv9CQkL/QkJC/0JCQv9CQkL/QkJC/0NDQ/+goKD/9vb2//b29v/29vb/9vb2/8zMzP9YWFj/QkJC/0JC
Qv9CQkL/S0tL/6urq//29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9fX1/2BgYP9CQkL/QkJC/42N
jf/29vb/9vb2//b29tAAAAAAAAAAAPf395n29vb/9vb2//b29v/T09P/rq6u/66urv+urq7/rq6u/66u
rv+urq7/rq6u/66urv+urq7/rq6u/66urv+urq7/p6en/01NTf9CQkL/QkJC/0JCQv+Dg4P/8PDw//b2
9v/29vb/9vb2//Ly8v/ExMT/rq6u/7u7u//q6ur/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/q6ur/0JCQv9CQkL/U1NT//X19f/29vb/9vb2/vb29hsAAAAA////Afb29pX29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/2NjY/1dX
V/9CQkL/QkJC/0JCQv9nZ2f/39/f//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/n5+f/RUVF/0JCQv9CQkL/zMzM//b29v/29vb/9/f3YQAA
AAAAAAAA////Afb29pX29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/5OTk/2VlZf9CQkL/QkJC/0JCQv9MTEz/ubm5//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v90dHT/QkJC/0JC
Qv+RkZH/9vb2//b29v/29vaoAAAAAAAAAAAAAAAA////AfX19Wb29vZ39vb2d/b29nf29vZ39vb2d/b2
9nf29vZ39vb2d/b29nf29vZ39vb2d/b29nf19fWc9vb2//b29v/29vb/7+/v/4eHh/9CQkL/QkJC/0JC
Qv9CQkL/goKC/+jo6P/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2/66urv9CQkL/QkJC/19fX//29vb/9vb2//b29uoAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD29vZs9vb2/fb2
9v/29vb/9vb2/7CwsP9ISEj/QkJC/0JCQv9CQkL/UVFR/7a2tv/19fX/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/2dnZ/0JCQv9CQkL/Q0ND/+zs7P/29vb/9vb2//X1
9RoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD29vZR9vb29vb29v/29vb/9vb2/9jY2P9nZ2f/QkJC/0JCQv9CQkL/QkJC/2dn
Z//Jycn/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/19fX/Tk5O/0JC
Qv9CQkL/zMzM//b29v/29vb/9PT0SAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD29vY59vb24fb29v/29vb/9vb2//Pz
8/+fn5//RkZG/0JCQv9CQkL/QkJC/0JCQv91dXX/yMjI//b29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29v90dHT/QkJC/0JCQv+oqKj/9vb2//b29v/29vZ3AAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AADx8fES9vb2sPb29v/29vb/9vb2//b29v/Z2dn/d3d3/0JCQv9CQkL/QkJC/0JCQv9CQkL/ZWVl/7Gx
sf/r6+v/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2/42Njf9CQkL/QkJC/42Njf/29vb/9vb2//X1
9Z8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B9vb2bfb29vT29vb/9vb2//b29v/29vb/x8fH/2Rk
ZP9CQkL/QkJC/0JCQv9CQkL/QkJC/0dHR/96enr/s7Oz/97e3v/29vb/9vb2//b29v/29vb/oqKi/0JC
Qv9CQkL/gICA//b29v/29vb/9/f3twAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA9fX1Gvb2
9qL29vb/9vb2//b29v/29vb/9fX1/8HBwf9wcHD/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/1RU
VP97e3v/kZGR/6ioqP+NjY3/QkJC/0JCQv90dHT/9vb2//b29v/29vbNAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAPT09Ej19fXW9vb2//b29v/29vb/9vb2//b29v/U1NT/iIiI/05O
Tv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/2hoaP/29vb/9vb2//b2
9uUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP///wL29vZV9vb20fb2
9v/29vb/9vb2//b29v/29vb/8/Pz/8XFxf+Kior/WVlZ/0JCQv9CQkL/QkJC/0JCQv9CQkL/QkJC/0JC
Qv9CQkL/ioqK//b29v/29vb/9vb29gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAD///8B9fX1Tvf397b29vb89vb2//b29v/29vb/9vb2//b29v/29vb/6enp/8TE
xP+goKD/hYWF/3p6ev9ubm7/YWFh/4iIiP/09PT/9vb2//b29v/29vbkAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPb29hz39/d69vb22Pb2
9v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb2//b29v/29vb/9vb26Pn5
+SoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAA+Pj4IvX19Wn29vaw9fX18fb29v/29vb/9vb2//b29v/29vb/9vb2//b2
9v/29vb/9vb2//b29uf5+fkpAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8B+Pj4Ivb2
9lL19fWA9vb2qPb29sH19fXW9vb27fb29v/29vbn+fn5KQAAAAAAAAAAAADwAA//wAAAAOAAB//AAAAA
wAAD/8AAAACAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/
wAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wAAAAAAAA//AAAAAAAAD/8AAAAAAAAP/wADwAAAAA//AAOAA
AAAB/8AAwAAAAAD/wACAAAAAAH/AAAAAAAAAP8AAAAAAAAA/wAAAAAAAAB/AAAAAAAAAD8AAAAAAAAAP
wAAAAAAAAAfAAAAAAAAAA8AAAAAAAAADwAAAAAAAAAHAAAAAAAAAAcAAAAAAAAABwAAAAAAAAADAAAAA
AAAAAMAAAAAAAAAAwAAAAAAAAABAAAAAAAAAAEAAgAAAAAAAQADAAAAAAABAAP//gAAAAAAA///AAAAA
AAD//+AAAAAAAP//8AAAAAAA///4AAAAAAD///4AAAAAAP///4AAAAAA////wAAAAAD////wAAAAAP//
//4AAAAA/////8AAQAD/////+ADAAA==
</value>
</data>
</root>

View File

@@ -0,0 +1,24 @@
// <copyright file="Program.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
using System.Windows.Forms;
/// <summary>
/// The static main program.
/// </summary>
internal static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
internal static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
}

View File

@@ -0,0 +1,10 @@
// <copyright file="AssemblyInfo.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
using System.Reflection;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MUnique.OpenMU.ClientLauncher")]

View File

@@ -0,0 +1,103 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace MUnique.OpenMU.ClientLauncher.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MUnique.OpenMU.ClientLauncher.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Add_16x {
get {
object obj = ResourceManager.GetObject("Add_16x", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Edit_16x {
get {
object obj = ResourceManager.GetObject("Edit_16x", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Remove_16x {
get {
object obj = ResourceManager.GetObject("Remove_16x", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap Settings_16x {
get {
object obj = ResourceManager.GetObject("Settings_16x", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
}
}

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="Add_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Add.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Edit_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Edit.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Remove_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Remove.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="Settings_16x" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\Settings_16x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

Binary file not shown.

After

Width:  |  Height:  |  Size: 301 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 567 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,90 @@
// <copyright file="ServerHostSettings.cs" company="MUnique">
// Licensed under the MIT License. See LICENSE file in the project root for full license information.
// </copyright>
namespace MUnique.OpenMU.ClientLauncher;
using System.ComponentModel;
using System.Runtime.CompilerServices;
/// <summary>
/// Settings for one server.
/// </summary>
public class ServerHostSettings : INotifyPropertyChanged
{
private string? _description;
private string? _address;
private int _port;
/// <inheritdoc />
public event PropertyChangedEventHandler? PropertyChanged;
/// <summary>
/// Gets or sets the name of the configuration.
/// </summary>
public string? Description
{
get => this._description;
set
{
if (value == this._description)
{
return;
}
this._description = value;
this.RaisePropertyChanged();
}
}
/// <summary>
/// Gets or sets the host ip.
/// </summary>
public string? Address
{
get => this._address;
set
{
if (value == this._address)
{
return;
}
this._address = value;
this.RaisePropertyChanged();
}
}
/// <summary>
/// Gets or sets the host port.
/// </summary>
public int Port
{
get => this._port;
set
{
if (value == this._port)
{
return;
}
this._port = value;
this.RaisePropertyChanged();
}
}
/// <inheritdoc />
public override string ToString()
{
return $"{this.Description} ({this.Address}:{this.Port})";
}
/// <summary>
/// Raises the <see cref="PropertyChanged"/> event for the specified property.
/// </summary>
/// <param name="propertyName">The name of the property.</param>
protected virtual void RaisePropertyChanged([CallerMemberName] string? propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}