├── AM2RLauncher ├── AM2RLauncher.Mac │ ├── Icon.icns │ ├── Info.plist │ ├── AM2RLauncher.Mac.csproj │ ├── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ └── Program.cs ├── AM2RLauncher.Gtk │ ├── icon64.ico │ ├── AM2RLauncher.Gtk.csproj │ ├── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ └── Program.cs ├── AM2RLauncher.Wpf │ ├── icon64.ico │ ├── log4net.config │ ├── app.config │ ├── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── AM2RLauncher.Wpf.csproj │ └── Program.cs ├── AM2RLauncher │ ├── Resources │ │ ├── discord.png │ │ ├── matrix.png │ │ ├── AM2RIcon.png │ │ ├── LauncherIcon.ico │ │ ├── bgCentered.png │ │ ├── github light x48.png │ │ ├── reddit_share_circle_48.png │ │ └── youtube_social_circle_red 48.png │ ├── MainForm │ │ ├── CustomControls │ │ │ ├── Spacer.cs │ │ │ ├── LauncherCheckbox.cs │ │ │ ├── BigColorButton.cs │ │ │ ├── SmallColorButton.cs │ │ │ ├── URLImageButton.cs │ │ │ ├── ColorButton.cs │ │ │ ├── ImageButton.cs │ │ │ └── CustomButton.cs │ │ ├── LauncherColors.cs │ │ ├── MainForm.Methods.cs │ │ └── MainForm.StateMachine.cs │ ├── Properties │ │ ├── Resources.Designer.cs │ │ └── Resources.resx │ ├── AM2RLauncher.csproj │ ├── XML │ │ └── LauncherConfigXML.cs │ └── LauncherUpdater.cs ├── distribution │ └── linux │ │ ├── AM2RLauncher.png │ │ ├── AM2RLauncher.desktop │ │ └── AM2RLauncher.appdata.xml ├── AM2RLauncherLib │ ├── AM2RLauncherLib.csproj │ ├── XML │ │ ├── Serializer.cs │ │ └── ProfileXML.cs │ ├── Core.cs │ ├── OS.cs │ ├── Splash.cs │ ├── HelperMethods.cs │ └── CrossPlatformOperations.cs ├── buildAll.bat └── AM2RLauncher.sln ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yaml └── workflows │ ├── build.yml │ └── build-and-publish.yml ├── README.md └── .gitignore /AM2RLauncher/AM2RLauncher.Mac/Icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher.Mac/Icon.icns -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Gtk/icon64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher.Gtk/icon64.ico -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Wpf/icon64.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher.Wpf/icon64.ico -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/discord.png -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/matrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/matrix.png -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/AM2RIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/AM2RIcon.png -------------------------------------------------------------------------------- /AM2RLauncher/distribution/linux/AM2RLauncher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/distribution/linux/AM2RLauncher.png -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/LauncherIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/LauncherIcon.ico -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/bgCentered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/bgCentered.png -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/github light x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/github light x48.png -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/reddit_share_circle_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/reddit_share_circle_48.png -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Resources/youtube_social_circle_red 48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AM2R-Community-Developers/AM2RLauncher/HEAD/AM2RLauncher/AM2RLauncher/Resources/youtube_social_circle_red 48.png -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/Spacer.cs: -------------------------------------------------------------------------------- 1 | using Eto.Forms; 2 | 3 | namespace AM2RLauncher; 4 | 5 | /// 6 | /// A custom control that acts as a spacer between other controls. 7 | /// 8 | public class Spacer : Label 9 | { 10 | /// 11 | /// Initialize a new . 12 | /// 13 | /// The height of the spacer in pixel. 14 | public Spacer(int height) 15 | { 16 | Height = height; 17 | } 18 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: AM2R-FAQ 4 | url: https://am2r-community-developers.github.io/DistributionCenter/faq.html 5 | about: Find frequently asked questions for AM2R. 6 | - name: AM2R Issues 7 | url: https://github.com/AM2R-Community-Developers/AM2R-Community-Updates/issues/new/choose 8 | about: Got an issue for AM2R itself? 9 | - name: AM2RLauncher Wiki 10 | url: https://github.com/AM2R-Community-Developers/AM2RLauncher/wiki 11 | about: A more in-depth documentation for features and usage of the AM2RLauncher. 12 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/AM2RLauncherLib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | default 6 | Debug;Release 7 | AnyCPU 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Wpf/log4net.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/LauncherCheckbox.cs: -------------------------------------------------------------------------------- 1 | using Eto.Forms; 2 | 3 | namespace AM2RLauncher; 4 | 5 | /// 6 | /// A custom AM2RLauncher-themed checkbox. 7 | /// 8 | public class LauncherCheckbox : CheckBox 9 | { 10 | /// 11 | /// Initializes a new . 12 | /// 13 | /// The text that should be displayed next to the checkbox. 14 | /// The checked state of the checkbox. 15 | public LauncherCheckbox(string text, bool? checkedState = false) 16 | { 17 | Text = text; 18 | Checked = checkedState; 19 | TextColor = LauncherColors.Green; 20 | } 21 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Mac/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleName 6 | AM2RLauncher 7 | CFBundleIdentifier 8 | com.example.AM2RLauncher 9 | CFBundleShortVersionString 10 | 1.0 11 | LSMinimumSystemVersion 12 | 10.15 13 | CFBundleDevelopmentRegion 14 | en 15 | NSHumanReadableCopyright 16 | 17 | CFBundleIconFile 18 | Icon.icns 19 | 20 | 21 | -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "nuget" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | labels: 13 | - "dependencies" 14 | 15 | - package-ecosystem: "github-actions" 16 | directory: "/" 17 | schedule: 18 | interval: "daily" 19 | labels: 20 | - "dependencies" 21 | -------------------------------------------------------------------------------- /AM2RLauncher/distribution/linux/AM2RLauncher.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Categories=Game 4 | Encoding=UTF-8 5 | Name=AM2RLauncher 6 | Comment=A front-end for dealing with AM2R updates and mods 7 | Comment[de]=Ein Programm für die Handhabung mit AM2R-Updates und Mods 8 | Comment[es]=Un front-end para organizar y controlar las actualizaciones y mods de AM2R 9 | Comment[fr]=Une interface pour intéragir avec les mods et MAJ de AM2R 10 | Comment[it]=Una front-end per gestire aggiornamenti e mod di AM2R 11 | Comment[pt]=Um front-end para gerenciar mods e atualizações do AM2R 12 | Comment[ja]=AM2Rの更新やMODを扱うためのフロントエンド 13 | Comment[ru]=Клиент для распоряжения обновлениями и модами для AM2R 14 | Comment[zh_CN]=一个负责管理 AM2R 的更新和 Mod 的前端 15 | Exec=AM2RLauncher 16 | Icon=AM2RLauncher 17 | Terminal=false 18 | SingleMainWindow=true 19 | -------------------------------------------------------------------------------- /AM2RLauncher/buildAll.bat: -------------------------------------------------------------------------------- 1 | :: dotnet publish AM2RLauncher.Wpf -c release -r win-x64 -o "builds\win64" 2 | :: ROBOCOPY "builds\win64" "builds\win64\lib\ " /XF *.exe *.config *.manifest /XD lib logs data /E /IS /MOVE 3 | dotnet publish AM2RLauncher.Wpf -c release -r win-x86 -o "builds\win86" 4 | ROBOCOPY "builds\win86" "builds\win86\lib\ " /XF *.exe *.config *.manifest /XD lib logs data /E /IS /MOVE 5 | move .\builds\win86\AM2RLauncher.Wpf.exe .\builds\win86\AM2RLauncher.exe 6 | move .\builds\win86\AM2RLauncher.Wpf.exe.config .\builds\win86\AM2RLauncher.exe.config 7 | dotnet publish AM2RLauncher.Gtk -p:PublishSingleFile=true -p:DebugType=embedded -c release -r linux-x64 --no-self-contained -o "builds\linux64" 8 | dotnet publish AM2RLauncher.Gtk -p:PublishSingleFile=true -p:DebugType=embedded -c release -r linux-x64 --self-contained -o "builds\linux64-selfContained" 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve. 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Expected behavior** 14 | A clear and concise description of what you expected to happen. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. 19 | 2. 20 | 3. 21 | 22 | **Screenshots** 23 | If applicable, add screenshots to help explain your problem. 24 | 25 | **Platform** 26 | - OS: [e.g. Windows 10] 27 | - AM2RLauncher Version: [e.g. 2.0.0] 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | 32 | **Log file** 33 | Please attach the `AM2RLauncher.log` log file. On Windows it's found in the `Logs` folder next to the executable, on Linux it's in the `Logs` folder in `~/.local/share/AM2RLauncher`. 34 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/BigColorButton.cs: -------------------------------------------------------------------------------- 1 | namespace AM2RLauncher; 2 | 3 | /// 4 | /// A custom button implementation for the AM2RLauncher. 5 | /// Generates a big with AM2R colors. 6 | /// 7 | public class BigColorButton : ColorButton 8 | { 9 | /// 10 | /// Initializes a 11 | /// 12 | /// The text that should be displayed on the button. 13 | public BigColorButton(string text) 14 | { 15 | Text = text; 16 | Height = 40; 17 | Width = 275; 18 | TextColor = LauncherColors.Green; 19 | TextColorDisabled = LauncherColors.Inactive; 20 | BackgroundColor = LauncherColors.BG; 21 | BackgroundColorHover = LauncherColors.BGHover; 22 | FrameColor = LauncherColors.Green; 23 | FrameColorDisabled = LauncherColors.Inactive; 24 | } 25 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/SmallColorButton.cs: -------------------------------------------------------------------------------- 1 | using Eto.Drawing; 2 | 3 | namespace AM2RLauncher; 4 | 5 | /// 6 | /// A custom button implementation for the AM2RLauncher. 7 | /// Generates a small with AM2R colors. 8 | /// 9 | public class SmallColorButton : ColorButton 10 | { 11 | private Font smallButtonFont = new Font(SystemFont.Default, 10); 12 | 13 | /// 14 | /// Initializes a new . 15 | /// 16 | /// The text that should be displayed on the button. 17 | public SmallColorButton(string text) 18 | { 19 | Font = smallButtonFont; 20 | Text = text; 21 | Height = 40; 22 | Width = 275; 23 | TextColor = LauncherColors.Green; 24 | TextColorDisabled = LauncherColors.Inactive; 25 | BackgroundColor = LauncherColors.BG; 26 | BackgroundColorHover = LauncherColors.BGHover; 27 | FrameColor = LauncherColors.Green; 28 | FrameColorDisabled = LauncherColors.Inactive; 29 | } 30 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/LauncherColors.cs: -------------------------------------------------------------------------------- 1 | using AM2RLauncherLib; 2 | using Eto.Drawing; 3 | 4 | namespace AM2RLauncher; 5 | 6 | public static class LauncherColors 7 | { 8 | // Colors 9 | /// The main green color. 10 | public static readonly Color Green = Color.FromArgb(142, 188, 35); 11 | /// The warning red color. 12 | public static readonly Color Red = Color.FromArgb(188, 10, 35); 13 | /// The main inactive color. 14 | public static readonly Color Inactive = Color.FromArgb(109, 109, 109); 15 | /// The black background color without alpha value. 16 | public static readonly Color BGNoAlpha = Color.FromArgb(10, 10, 10); 17 | /// The black background color. 18 | // XORG can't display alpha anyway, and Wayland breaks with it. 19 | // TODO: that sounds like an Eto issue. investigate, try to open eto issue. 20 | public static readonly Color BG = OS.IsLinux ? Color.FromArgb(10, 10, 10) : Color.FromArgb(10, 10, 10, 80); 21 | /// The lighter green color on hover. 22 | public static readonly Color BGHover = Color.FromArgb(17, 28, 13); 23 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Gtk/AM2RLauncher.Gtk.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net8.0 6 | icon64.ico 7 | LatestMajor 8 | latest 9 | Debug;Release 10 | AnyCPU 11 | 12 | 13 | 14 | TRACE 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | True 28 | True 29 | Resources.resx 30 | 31 | 32 | 33 | 34 | 35 | ResXFileCodeGenerator 36 | Resources.Designer.cs 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/URLImageButton.cs: -------------------------------------------------------------------------------- 1 | using AM2RLauncherLib; 2 | using Eto.Drawing; 3 | using Pablo.Controls; 4 | 5 | namespace AM2RLauncher; 6 | 7 | /// 8 | /// An , that when clicked opens a URL. 9 | /// 10 | public class URLImageButton : ImageButton 11 | { 12 | /// 13 | /// Initializes a new 14 | /// 15 | /// The image that should be drawn. 16 | /// The URL that should get opened. 17 | /// The tool tip for the control. 18 | public URLImageButton(Bitmap image, string url, string tooltip = "") 19 | { 20 | Image = image; 21 | ToolTip = tooltip; 22 | 23 | Click += (_, _) => CrossPlatformOperations.OpenURL(url); 24 | } 25 | 26 | /// 27 | /// Initializes a new 28 | /// 29 | /// The image as a byte array that should be drawn. 30 | /// The URL that should get opened. 31 | /// The tool tip for the control. 32 | public URLImageButton(byte[] image, string url, string tooltip = "") : this(new Bitmap(image), url, tooltip) 33 | { 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Wpf/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 8 | 10 | 12 | 14 | 16 | 18 | 20 | 22 | 24 | 26 | 28 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Mac/AM2RLauncher.Mac.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net8.0-macos 6 | osx-x64;osx-arm64 7 | 10.15 8 | LatestMajor 9 | Debug;Release 10 | AnyCPU 11 | 12 | 13 | 14 | 4 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | True 27 | True 28 | Resources.resx 29 | 30 | 31 | 32 | 33 | 34 | ResXFileCodeGenerator 35 | Resources.Designer.cs 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ main ] 7 | paths-ignore: 8 | - 'README.md' 9 | pull_request: 10 | branches: [ main ] 11 | paths-ignore: 12 | - 'README.md' 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ubuntu-latest, macos-latest, windows-latest] 20 | configuration: [Release] 21 | include: 22 | - os: ubuntu-latest 23 | COMMAND: AM2RLauncher.Gtk -p:PublishSingleFile=true -p:DebugType=embedded -r linux-x64 --no-self-contained 24 | ARTIFACT: AM2RLauncher/AM2RLauncher.Gtk/bin/Release/net8.0/linux-x64/publish/ 25 | - os: macos-latest 26 | COMMAND: AM2RLauncher.Mac -o builds/macOS-latest 27 | ARTIFACT: AM2RLauncher/builds/macOS-latest 28 | - os: windows-latest 29 | COMMAND: AM2RLauncher.Wpf -r win-x86 -o builds\win86 30 | ARTIFACT: AM2RLauncher\AM2RLauncher.Wpf\bin\Release\net48\win-x86\ 31 | runs-on: ${{ matrix.os }} 32 | 33 | 34 | steps: 35 | - uses: actions/checkout@v5 36 | - name: Setup .NET 37 | uses: actions/setup-dotnet@v5 38 | with: 39 | dotnet-version: 8.x.x 40 | - name: Install Mac workload 41 | working-directory: ./AM2RLauncher 42 | run: dotnet workload install macos && dotnet workload restore 43 | - name: Restore dependencies 44 | working-directory: ./AM2RLauncher 45 | run: dotnet restore 46 | - name: Build 47 | working-directory: ./AM2RLauncher 48 | run: dotnet publish ${{ matrix.COMMAND }} -c "${{ matrix.configuration }}" 49 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/XML/Serializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace AM2RLauncherLib.XML; 6 | 7 | /// 8 | /// The Serializer class, that serializes to and deserializes from XML files. 9 | /// 10 | public static class Serializer 11 | { 12 | /// 13 | /// Serializes as a to XML. 14 | /// 15 | /// The class to serialize to. 16 | /// The object that will be serialized. 17 | /// The serialized XML as a . 18 | public static string Serialize(object item) 19 | { 20 | Type t = typeof(T); 21 | MemoryStream memStream = new MemoryStream(); 22 | System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(t); 23 | 24 | serializer.Serialize(memStream, item); 25 | 26 | string xml = Encoding.UTF8.GetString(memStream.ToArray()); 27 | 28 | memStream.Flush(); 29 | memStream.Close(); 30 | memStream.Dispose(); 31 | memStream = null; 32 | 33 | return xml; 34 | } 35 | 36 | /// 37 | /// Deserialize into an object of class that can be assigned. 38 | /// 39 | /// The class that will be deserialized to. 40 | /// An XML that will be deserialized. 41 | /// A deserialized object of class from . 42 | public static T Deserialize(string xmlString) 43 | { 44 | Type t = typeof(T); 45 | using MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlString)); 46 | System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(t); 47 | return (T)serializer.Deserialize(memStream); 48 | } 49 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/Core.cs: -------------------------------------------------------------------------------- 1 | using log4net; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace AM2RLauncherLib; 6 | 7 | /// 8 | /// Class that has core stuff that doesn't fit anywhere else. 9 | /// 10 | public static class Core 11 | { 12 | /// 13 | /// Our log object, that handles logging the current execution to a file. 14 | /// 15 | public static readonly ILog Log = LogManager.GetLogger(typeof(Core)); 16 | 17 | /// The Version that identifies this current release. 18 | public const string Version = "2.3.0"; 19 | 20 | /// 21 | /// Indicates whether or not we have established an internet connection. 22 | /// 23 | public static readonly bool IsInternetThere = HelperMethods.IsConnectedToInternet(); 24 | 25 | /// 26 | /// Path where the Launcher's PatchData folder is located. 27 | /// 28 | public static readonly string PatchDataPath = CrossPlatformOperations.CurrentPath + "/PatchData"; 29 | 30 | /// 31 | /// Path where the AM2R_11.zip is located. 32 | /// 33 | public static readonly string AM2R11File = CrossPlatformOperations.CurrentPath + "/AM2R_11.zip"; 34 | 35 | /// 36 | /// Path where the Launcher's Profiles folder is located. 37 | /// 38 | public static readonly string ProfilesPath = CrossPlatformOperations.CurrentPath + "/Profiles"; 39 | 40 | /// 41 | /// Path where the Launcher's Mods folder is located. 42 | /// 43 | public static readonly string ModsPath = CrossPlatformOperations.CurrentPath + "/Mods"; 44 | 45 | /// 46 | /// This is used on Windows only. This sets a window to be in foreground. It's used i.e. to fix AM2R just being hidden. 47 | /// 48 | /// Pointer to the process you want to have in the foreground. 49 | /// 50 | [DllImport("user32.dll")] 51 | public static extern bool SetForegroundWindow(IntPtr hWnd); 52 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Gtk/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace AM2RLauncher.Gtk.Properties { 11 | using System; 12 | 13 | 14 | [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] 15 | [System.Diagnostics.DebuggerNonUserCodeAttribute()] 16 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 17 | internal class Resources { 18 | 19 | private static System.Resources.ResourceManager resourceMan; 20 | 21 | private static System.Globalization.CultureInfo resourceCulture; 22 | 23 | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 24 | internal Resources() { 25 | } 26 | 27 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 28 | internal static System.Resources.ResourceManager ResourceManager { 29 | get { 30 | if (object.Equals(null, resourceMan)) { 31 | System.Resources.ResourceManager temp = new System.Resources.ResourceManager("AM2RLauncher.Gtk.Properties.Resources", typeof(Resources).Assembly); 32 | resourceMan = temp; 33 | } 34 | return resourceMan; 35 | } 36 | } 37 | 38 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static System.Globalization.CultureInfo Culture { 40 | get { 41 | return resourceCulture; 42 | } 43 | set { 44 | resourceCulture = value; 45 | } 46 | } 47 | 48 | internal static string log4netContents { 49 | get { 50 | return ResourceManager.GetString("log4netContents", resourceCulture); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace AM2RLauncher.Mac.Properties { 11 | using System; 12 | 13 | 14 | [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 15 | [System.Diagnostics.DebuggerNonUserCodeAttribute()] 16 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 17 | internal class Resources { 18 | 19 | private static System.Resources.ResourceManager resourceMan; 20 | 21 | private static System.Globalization.CultureInfo resourceCulture; 22 | 23 | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 24 | internal Resources() { 25 | } 26 | 27 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 28 | internal static System.Resources.ResourceManager ResourceManager { 29 | get { 30 | if (object.Equals(null, resourceMan)) { 31 | System.Resources.ResourceManager temp = new System.Resources.ResourceManager("AM2RLauncher.Mac.Properties.Resources", typeof(Resources).Assembly); 32 | resourceMan = temp; 33 | } 34 | return resourceMan; 35 | } 36 | } 37 | 38 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static System.Globalization.CultureInfo Culture { 40 | get { 41 | return resourceCulture; 42 | } 43 | set { 44 | resourceCulture = value; 45 | } 46 | } 47 | 48 | internal static string log4netContents { 49 | get { 50 | return ResourceManager.GetString("log4netContents", resourceCulture); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Wpf/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace AM2RLauncher.Wpf.Properties { 11 | using System; 12 | 13 | 14 | [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 15 | [System.Diagnostics.DebuggerNonUserCodeAttribute()] 16 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 17 | internal class Resources { 18 | 19 | private static System.Resources.ResourceManager resourceMan; 20 | 21 | private static System.Globalization.CultureInfo resourceCulture; 22 | 23 | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 24 | internal Resources() { 25 | } 26 | 27 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 28 | internal static System.Resources.ResourceManager ResourceManager { 29 | get { 30 | if (object.Equals(null, resourceMan)) { 31 | System.Resources.ResourceManager temp = new System.Resources.ResourceManager("AM2RLauncher.Wpf.Properties.Resources", typeof(Resources).Assembly); 32 | resourceMan = temp; 33 | } 34 | return resourceMan; 35 | } 36 | } 37 | 38 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static System.Globalization.CultureInfo Culture { 40 | get { 41 | return resourceCulture; 42 | } 43 | set { 44 | resourceCulture = value; 45 | } 46 | } 47 | 48 | internal static string log4netContents { 49 | get { 50 | return ResourceManager.GetString("log4netContents", resourceCulture); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.github/workflows/build-and-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build And Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | os: [ubuntu-latest, macos-13, windows-latest] 12 | configuration: [Release] 13 | include: 14 | - os: ubuntu-latest 15 | COMMAND: AM2RLauncher.Gtk -p:PublishSingleFile=true -p:DebugType=embedded -r ubuntu.18.04-x64 --no-self-contained -o builds/ubuntu-latest 16 | ARTIFACT: AM2RLauncher/builds/ubuntu-latest 17 | POSTBUILD: echo "No post build to do!" 18 | - os: macos-13 19 | COMMAND: AM2RLauncher.Mac -o builds/macOS-latest 20 | ARTIFACT: AM2RLauncher/builds/macOS-latest 21 | POSTBUILD: rm -r AM2RLauncher/builds/macOS-latest/* && mv AM2RLauncher/AM2RLauncher.Mac/bin/Release/net8.0-macos/AM2RLauncher.Mac.app AM2RLauncher/builds/macOS-latest/AM2RLauncher.Mac.app 22 | - os: windows-latest 23 | COMMAND: AM2RLauncher.Wpf -r win-x86 24 | ARTIFACT: AM2RLauncher\AM2RLauncher.Wpf\bin\Release\net48\win-x86\ 25 | # This is very ugly, *please* tell me a better way to do this 26 | POSTBUILD: mv AM2RLauncher\AM2RLauncher.Wpf\bin\Release\net48\win-x86\AM2RLauncher.Wpf.exe AM2RLauncher\AM2RLauncher.Wpf\bin\Release\net48\win-x86\AM2RLauncher.exe && mv AM2RLauncher\AM2RLauncher.Wpf\bin\Release\net48\win-x86\AM2RLauncher.Wpf.exe.config AM2RLauncher\AM2RLauncher.Wpf\bin\Release\net48\win-x86\AM2RLauncher.exe.config && rmdir AM2RLauncher\AM2RLauncher.Wpf\bin\Release\net48\win-x86\publish -Recurse 27 | runs-on: ${{ matrix.os }} 28 | 29 | 30 | steps: 31 | - uses: actions/checkout@v5 32 | - name: Setup .NET 33 | uses: actions/setup-dotnet@v5 34 | with: 35 | dotnet-version: 8.x.x 36 | - name: Install Mac workload 37 | working-directory: ./AM2RLauncher 38 | run: dotnet workload install macos && dotnet workload restore 39 | - name: Restore dependencies 40 | working-directory: ./AM2RLauncher 41 | run: dotnet restore 42 | - name: Switch XCode 43 | run: sudo xcode-select -switch /Applications/Xcode_15.0.1.app/Contents/Developer 44 | if: matrix.os == 'macos-13' 45 | - name: Build 46 | working-directory: ./AM2RLauncher 47 | run: dotnet publish ${{ matrix.COMMAND }} -c "${{ matrix.configuration }}" 48 | - name: Post-Build 49 | run: | 50 | cp ./LICENSE ./${{ matrix.ARTIFACT }}/ 51 | ${{ matrix.POSTBUILD }} 52 | # Steps for uploading artifacts. 53 | - name: Zip to Archive 54 | run: 7z a -tzip ${{ matrix.os }}.zip ./${{ matrix.ARTIFACT }} 55 | - name: Upload Artifacts 56 | uses: actions/upload-artifact@v4.6.2 57 | with: 58 | name: ${{ matrix.os }} 59 | path: ${{ matrix.os }}.zip 60 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/ColorButton.cs: -------------------------------------------------------------------------------- 1 | using Eto.Drawing; 2 | using Eto.Forms; 3 | using Pablo.Controls; 4 | 5 | namespace AM2RLauncher; 6 | 7 | /// 8 | /// Extension of that allows for advanced color settings. 9 | /// 10 | public class ColorButton : CustomButton 11 | { 12 | /// The to draw the background with when is true. 13 | public Color BackgroundColorHover { get; set; } 14 | 15 | /// The to use for text drawing. 16 | public Font Font { get; set; } 17 | 18 | /// The with which to draw the frame. 19 | public Color FrameColor { get; set; } 20 | 21 | /// The with which to draw the frame when it is disabled. 22 | public Color FrameColorDisabled { get; set; } 23 | 24 | /// The text to be drawn. 25 | public string Text { get; set; } 26 | 27 | /// The with which to draw the . 28 | public Color TextColor { get; set; } 29 | 30 | /// The with which to draw the when it is disabled. 31 | public Color TextColorDisabled { get; set; } 32 | 33 | /// 34 | /// Creates a with a default set of attributes. 35 | /// 36 | public ColorButton() 37 | { 38 | BackgroundColorHover = Colors.White; 39 | Font = new Font(SystemFont.Default, 12); 40 | FrameColor = Colors.White; 41 | FrameColorDisabled = Colors.Gray; 42 | Text = ""; 43 | TextColor = Colors.Black; 44 | TextColorDisabled = Colors.Gray; 45 | } 46 | 47 | /// 48 | /// Event raised to draw this control. 49 | /// 50 | /// 51 | protected override void OnPaint(PaintEventArgs pe) 52 | { 53 | // Define draw bounds 54 | Rectangle drawBounds = new Rectangle(2, 2, Width - 5, Height - 5); 55 | 56 | // Draw background 57 | pe.Graphics.FillRectangle(Hover ? BackgroundColorHover : BackgroundColor, drawBounds); 58 | 59 | // Draw frame 60 | Pen framePen = new Pen(Enabled ? FrameColor : FrameColorDisabled, 1); 61 | pe.Graphics.DrawRectangle(framePen, drawBounds); 62 | 63 | SolidBrush brush = new SolidBrush(Enabled ? TextColor : TextColorDisabled); 64 | 65 | // Get text measurements 66 | SizeF stringSize = pe.Graphics.MeasureString(Font, Text); 67 | int textWidth = (int)stringSize.Width; 68 | int textHeight = (int)stringSize.Height; 69 | 70 | // Draw text 71 | pe.Graphics.DrawText(Font, brush, new PointF(Width / 2 - textWidth / 2, Height / 2 - textHeight / 2), Text); 72 | } 73 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Mac/Program.cs: -------------------------------------------------------------------------------- 1 | using Eto.Forms; 2 | using log4net; 3 | using log4net.Config; 4 | using System; 5 | using System.IO; 6 | using AM2RLauncherLib; 7 | using log4net.Repository.Hierarchy; 8 | 9 | // ReSharper disable LocalizableElement - we want hardcoded strings for console writes. 10 | 11 | namespace AM2RLauncher.Mac; 12 | 13 | /// 14 | /// The main class for the Mac project. 15 | /// 16 | internal static class MainClass 17 | { 18 | /// 19 | /// The logger for , used to write any caught exceptions. 20 | /// 21 | private static readonly ILog log = LogManager.GetLogger(typeof(MainForm)); 22 | 23 | /// 24 | /// The main method for the Mac project. 25 | /// 26 | [STAThread] 27 | public static void Main() 28 | { 29 | string launcherDataPath = CrossPlatformOperations.CurrentPath; 30 | 31 | // Make sure first, ~/.local/share/AM2RLauncher exists 32 | Directory.CreateDirectory(launcherDataPath); 33 | 34 | // Now, see if log4netConfig exists, if not write it again. 35 | if (!File.Exists($"{launcherDataPath}/log4net.config")) 36 | File.WriteAllText($"{launcherDataPath}/log4net.config", Properties.Resources.log4netContents.Replace("${DATADIR}", launcherDataPath)); 37 | 38 | // Configure logger 39 | XmlConfigurator.Configure(new FileInfo(launcherDataPath + "/log4net.config")); 40 | 41 | // if we're on debug, always set logLevel to debug 42 | #if DEBUG 43 | ((Logger)log.Logger).Level = log4net.Core.Level.Debug; 44 | #endif 45 | 46 | try 47 | { 48 | Application macLauncher = new Application(Eto.Platforms.macOS); 49 | LauncherUpdater.Main(); 50 | macLauncher.UnhandledException += MacLauncher_UnhandledException; 51 | macLauncher.Run(new MainForm()); 52 | } 53 | catch (Exception e) 54 | { 55 | log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace); 56 | Console.WriteLine($"{Language.Text.UnhandledException}\n{e.Message}\n*****Stack Trace*****\n\n{e.StackTrace}"); 57 | Console.WriteLine($"Check the logs at {launcherDataPath} for more info!"); 58 | } 59 | } 60 | 61 | /// 62 | /// This method gets fired when an unhandled exception occurs in . 63 | /// 64 | private static void MacLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e) 65 | { 66 | log.Error($"An unhandled exception has occurred: \n*****Stack Trace*****\n\n{e.ExceptionObject}"); 67 | Application.Instance.Invoke(() => 68 | { 69 | MessageBox.Show($"{Language.Text.UnhandledException}\n*****Stack Trace*****\n\n{e.ExceptionObject}", "Mac", MessageBoxType.Error); 70 | }); 71 | } 72 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Wpf/AM2RLauncher.Wpf.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WinExe 5 | net48 6 | icon64.ico 7 | AM2RLauncher.Wpf 8 | 9 | false 10 | AM2RLauncher-Windows 11 | Community Developers 12 | AM2RLauncher 13 | 2.0.0 14 | 2021 15 | en 16 | https://github.com/AM2R-Community-Developers/AM2RLauncherRewrite/ 17 | https://github.com/AM2R-Community-Developers/AM2RLauncherRewrite/ 18 | GNU General Public License v3.0 19 | A Launcher for AM2R that with mod capabilities. 20 | latest 21 | Debug;Release 22 | AnyCPU 23 | 24 | 25 | 26 | x86 27 | DEBUG;TRACE 28 | 29 | 30 | 31 | x86 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | True 50 | True 51 | Resources.resx 52 | 53 | 54 | 55 | 56 | 57 | ResXFileCodeGenerator 58 | Resources.Designer.cs 59 | 60 | 61 | 62 | 63 | 64 | Never 65 | 66 | 67 | Always 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/OS.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AM2RLauncherLib; 5 | 6 | /// 7 | /// Class that has information about the current running operating system. 8 | /// 9 | public static class OS 10 | { 11 | /// 12 | /// Determines if the current OS is Windows. 13 | /// 14 | public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); 15 | 16 | /// 17 | /// Determines if the current OS is Linux. 18 | /// 19 | public static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); 20 | 21 | /// 22 | /// Determines if the current OS is Mac. 23 | /// 24 | public static readonly bool IsMac = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); 25 | 26 | /// 27 | /// Determines if the current OS is a unix based system (Mac or Linux). 28 | /// 29 | public static readonly bool IsUnix = IsLinux || IsMac; 30 | 31 | /// 32 | /// Gets a string representation of the current OS. 33 | /// 34 | public static readonly string Name = DetermineOsName(); 35 | 36 | /// 37 | /// Generates a string representation of the current OS 38 | /// 39 | private static string DetermineOsName() 40 | { 41 | if (IsWindows) 42 | return "Windows"; 43 | if (IsLinux) 44 | return "Linux"; 45 | if (IsMac) 46 | return "Mac"; 47 | 48 | return "Unknown OS"; 49 | } 50 | 51 | /// 52 | /// Checks if this is run via WINE. 53 | /// 54 | public static readonly bool IsThisRunningFromWINE = CheckIfRunFromWINE(); 55 | 56 | /// 57 | /// Checks if this is run via Flatpak. 58 | /// 59 | public static readonly bool IsThisRunningFromFlatpak = CheckIfRunFromFlatpak(); 60 | 61 | /// 62 | /// Checks if the Launcher is ran from WINE. 63 | /// 64 | /// if run from WINE, if not. 65 | private static bool CheckIfRunFromWINE() 66 | { 67 | // We check for wine by seeing if a reg entry exists. 68 | // Not the best way, and could be removed from the future, but good enough for our purposes. 69 | if (IsWindows && (Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\Wine") != null)) 70 | return true; 71 | 72 | return false; 73 | } 74 | 75 | /// 76 | /// Checks if the Launcher is ran from a Flatpak. 77 | /// 78 | /// see langword="true"/> if run from a Flatpak, if not. 79 | private static bool CheckIfRunFromFlatpak() 80 | { 81 | if (!IsLinux) return false; 82 | 83 | // This file is present in all flatpaks 84 | if (File.Exists("/.flatpak-info")) 85 | return true; 86 | return false; 87 | } 88 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Wpf/Program.cs: -------------------------------------------------------------------------------- 1 | using Eto.Forms; 2 | using log4net; 3 | using log4net.Config; 4 | using System; 5 | using System.IO; 6 | using AM2RLauncherLib; 7 | using log4net.Repository.Hierarchy; 8 | 9 | // ReSharper disable LocalizableElement - we want hardcoded strings for console writes. 10 | 11 | namespace AM2RLauncher.Wpf; 12 | 13 | /// 14 | /// The main class for the WinForms project. 15 | /// 16 | internal static class MainClass 17 | { 18 | /// 19 | /// The logger for , used to write any caught exceptions. 20 | /// 21 | private static readonly ILog log = LogManager.GetLogger(typeof(MainForm)); 22 | /// 23 | /// The main method for the WinForms project. 24 | /// 25 | [STAThread] 26 | public static void Main() 27 | { 28 | string launcherDataPath = CrossPlatformOperations.CurrentPath; 29 | 30 | // Make sure first, that the path exists 31 | Directory.CreateDirectory(launcherDataPath); 32 | 33 | // Now, see if log4netConfig exists, if not write it again. 34 | if (!File.Exists($"{launcherDataPath}/log4net.config")) 35 | File.WriteAllText($"{launcherDataPath}/log4net.config", Properties.Resources.log4netContents.Replace("${DATADIR}", launcherDataPath)); 36 | 37 | // Configure logger 38 | XmlConfigurator.Configure(new FileInfo($"{launcherDataPath}/log4net.config")); 39 | 40 | // if we're on debug, always set log level to debug 41 | #if DEBUG 42 | ((Logger)log.Logger).Level = log4net.Core.Level.Debug; 43 | #endif 44 | 45 | //Log Wine 46 | if (OS.IsThisRunningFromWINE) log.Info("Currently running from WINE!"); 47 | 48 | // Try catch in case it ever crashes before actually getting to the Eto application 49 | try 50 | { 51 | Application winLauncher = new Application(Eto.Platforms.WinForms); 52 | LauncherUpdater.Main(); 53 | winLauncher.UnhandledException += WinLauncher_UnhandledException; 54 | winLauncher.Run(new MainForm()); 55 | } 56 | catch (Exception e) 57 | { 58 | log.Error($"An unhandled exception has occurred: \n*****Stack Trace*****\n\n{e.StackTrace}"); 59 | System.Windows.Forms.MessageBox.Show($"{Language.Text.UnhandledException}\n{e.Message}\n*****Stack Trace*****\n\n{e.StackTrace}", "Microsoft .NET Framework", 60 | System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); 61 | } 62 | } 63 | 64 | /// 65 | /// This method gets fired when an unhandled exception occurs in . 66 | /// 67 | private static void WinLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e) 68 | { 69 | log.Error($"An unhandled exception has occurred: \n*****Stack Trace*****\n\n{e.ExceptionObject}"); 70 | MessageBox.Show($"{Language.Text.UnhandledException}\n*****Stack Trace*****\n\n{e.ExceptionObject}", "Microsoft .NET Framework", MessageBoxType.Error); 71 | } 72 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31912.275 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AM2RLauncher", "AM2RLauncher\AM2RLauncher.csproj", "{9B67055C-5AEF-4F83-AFE0-38E600183C9F}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AM2RLauncherLib", "AM2RLauncherLib\AM2RLauncherLib.csproj", "{B3AAD6F3-41B9-4981-8CD4-FE683429AD95}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AM2RLauncher.Gtk", "AM2RLauncher.Gtk\AM2RLauncher.Gtk.csproj", "{0A619FA8-61B4-48C6-9461-21B3FE137D03}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AM2RLauncher.Wpf", "AM2RLauncher.Wpf\AM2RLauncher.Wpf.csproj", "{8A162932-BAA3-429A-84C4-B93A1C85DDE3}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AM2RLauncher.Mac", "AM2RLauncher.Mac\AM2RLauncher.Mac.csproj", "{6F240F19-144E-4704-A16A-8FDAC3867EC9}" 15 | EndProject 16 | Global 17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 18 | Debug|Any CPU = Debug|Any CPU 19 | Release|Any CPU = Release|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {9B67055C-5AEF-4F83-AFE0-38E600183C9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {9B67055C-5AEF-4F83-AFE0-38E600183C9F}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {9B67055C-5AEF-4F83-AFE0-38E600183C9F}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {9B67055C-5AEF-4F83-AFE0-38E600183C9F}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {B3AAD6F3-41B9-4981-8CD4-FE683429AD95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {B3AAD6F3-41B9-4981-8CD4-FE683429AD95}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {B3AAD6F3-41B9-4981-8CD4-FE683429AD95}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {B3AAD6F3-41B9-4981-8CD4-FE683429AD95}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {0A619FA8-61B4-48C6-9461-21B3FE137D03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {0A619FA8-61B4-48C6-9461-21B3FE137D03}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {0A619FA8-61B4-48C6-9461-21B3FE137D03}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {0A619FA8-61B4-48C6-9461-21B3FE137D03}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {8A162932-BAA3-429A-84C4-B93A1C85DDE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {8A162932-BAA3-429A-84C4-B93A1C85DDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {8A162932-BAA3-429A-84C4-B93A1C85DDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {8A162932-BAA3-429A-84C4-B93A1C85DDE3}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {6F240F19-144E-4704-A16A-8FDAC3867EC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {6F240F19-144E-4704-A16A-8FDAC3867EC9}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {6F240F19-144E-4704-A16A-8FDAC3867EC9}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {6F240F19-144E-4704-A16A-8FDAC3867EC9}.Release|Any CPU.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {F6A6677C-BAAB-43EC-A289-E7AB0A61E0C8} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // 5 | // Changes to this file may cause incorrect behavior and will be lost if 6 | // the code is regenerated. 7 | // 8 | //------------------------------------------------------------------------------ 9 | 10 | namespace AM2RLauncher.Properties { 11 | using System; 12 | 13 | 14 | [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] 15 | [System.Diagnostics.DebuggerNonUserCodeAttribute()] 16 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 17 | internal class Resources { 18 | 19 | private static System.Resources.ResourceManager resourceMan; 20 | 21 | private static System.Globalization.CultureInfo resourceCulture; 22 | 23 | [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 24 | internal Resources() { 25 | } 26 | 27 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 28 | internal static System.Resources.ResourceManager ResourceManager { 29 | get { 30 | if (object.Equals(null, resourceMan)) { 31 | System.Resources.ResourceManager temp = new System.Resources.ResourceManager("AM2RLauncher.Properties.Resources", typeof(Resources).Assembly); 32 | resourceMan = temp; 33 | } 34 | return resourceMan; 35 | } 36 | } 37 | 38 | [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static System.Globalization.CultureInfo Culture { 40 | get { 41 | return resourceCulture; 42 | } 43 | set { 44 | resourceCulture = value; 45 | } 46 | } 47 | 48 | internal static byte[] AM2RIcon { 49 | get { 50 | object obj = ResourceManager.GetObject("AM2RIcon", resourceCulture); 51 | return ((byte[])(obj)); 52 | } 53 | } 54 | 55 | internal static byte[] bgCentered { 56 | get { 57 | object obj = ResourceManager.GetObject("bgCentered", resourceCulture); 58 | return ((byte[])(obj)); 59 | } 60 | } 61 | 62 | internal static byte[] discordIcon48 { 63 | get { 64 | object obj = ResourceManager.GetObject("discordIcon48", resourceCulture); 65 | return ((byte[])(obj)); 66 | } 67 | } 68 | 69 | internal static byte[] githubIcon48 { 70 | get { 71 | object obj = ResourceManager.GetObject("githubIcon48", resourceCulture); 72 | return ((byte[])(obj)); 73 | } 74 | } 75 | 76 | internal static byte[] redditIcon48 { 77 | get { 78 | object obj = ResourceManager.GetObject("redditIcon48", resourceCulture); 79 | return ((byte[])(obj)); 80 | } 81 | } 82 | 83 | internal static byte[] youtubeIcon48 { 84 | get { 85 | object obj = ResourceManager.GetObject("youtubeIcon48", resourceCulture); 86 | return ((byte[])(obj)); 87 | } 88 | } 89 | 90 | internal static byte[] matrixIcon48 { 91 | get { 92 | object obj = ResourceManager.GetObject("matrixIcon48", resourceCulture); 93 | return ((byte[])(obj)); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/ImageButton.cs: -------------------------------------------------------------------------------- 1 | using Eto.Drawing; 2 | using System; 3 | 4 | namespace Pablo.Controls; 5 | 6 | /// 7 | /// Extension of that allows image assignment and drawing. 8 | /// Originally written by cwensley (https://gist.github.com/cwensley/95000998e37acd93e830), 9 | /// modified by Lojemiru and Miepee for use in the AM2RLauncher. 10 | /// 11 | public class ImageButton : CustomButton 12 | { 13 | /// 14 | /// Whether or not this control's size has been set. 15 | /// 16 | bool sizeSet; 17 | 18 | /// 19 | /// Gets or sets the to draw during . 20 | /// 21 | public Image Image { get; set; } 22 | 23 | /// 24 | /// Gets or sets the to draw during if this control is disabled. 25 | /// 26 | public Image DisabledImage { get; set; } 27 | 28 | /// 29 | /// Gets or sets the of this control. 30 | /// 31 | public override Size Size 32 | { 33 | get { return base.Size; } 34 | set { base.Size = value; sizeSet = true; } 35 | } 36 | 37 | /// 38 | /// Default constructor, assigns no special attributes. 39 | /// 40 | public ImageButton() { } 41 | 42 | /// 43 | /// Event raised when this control is loading. 44 | /// 45 | protected override void OnLoad(EventArgs e) 46 | { 47 | base.OnLoad(e); 48 | if (Image != null) 49 | { 50 | if (!sizeSet) 51 | this.Size = Image.Size + 4; 52 | } 53 | } 54 | 55 | /// 56 | /// Event raised when this control has finished loading. 57 | /// 58 | protected override void OnLoadComplete(EventArgs e) 59 | { 60 | base.OnLoadComplete(e); 61 | 62 | if (DisabledImage == null) 63 | { 64 | var image = Image; 65 | if (image != null) 66 | { 67 | var disabledImage = new Bitmap(image.Size.Width, image.Size.Height, PixelFormat.Format32bppRgba); 68 | using (var graphics = new Graphics(disabledImage)) 69 | { 70 | graphics.DrawImage(image, 0, 0); 71 | } 72 | using (var bd = disabledImage.Lock()) 73 | { 74 | unsafe 75 | { 76 | var data = (int*)bd.Data; 77 | for (int i = 0; i < bd.ScanWidth * disabledImage.Size.Height; i++) 78 | { 79 | var col = Color.FromArgb(bd.TranslateDataToArgb(*data)); 80 | var gray = (col.R + col.G + col.B) / 3; 81 | *data = bd.TranslateArgbToData(Color.FromGrayscale(gray, 0.8f).ToArgb()); 82 | } 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | /// 90 | /// Event raised to draw this control. 91 | /// 92 | protected override void OnPaint(Eto.Forms.PaintEventArgs pe) 93 | { 94 | var image = this.Enabled ? Image : DisabledImage; 95 | var size = image.Size.FitTo(this.Size - 2); 96 | var xoffset = (this.Size.Width - size.Width) / 2; 97 | var yoffset = (this.Size.Height - size.Height) / 2; 98 | pe.Graphics.DrawImage(image, xoffset, yoffset, size.Width, size.Height); 99 | if (Hover) 100 | // This can probably be done prettier, but can't be bothered to much with it to be honest. 101 | pe.Graphics.DrawRectangle(SystemColors.Highlight, xoffset, yoffset, size.Width, size.Height); 102 | } 103 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/AM2RLauncher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | latest 6 | Resources\LauncherIcon.ico 7 | Lojemiru, Miepee 8 | AM2R-Community-Developers 9 | This is a Launcher for AM2R and its Community Updates. 10 | https://github.com/AM2R-Community-Developers/AM2RLauncher/ 11 | Debug;Release 12 | AnyCPU 13 | true 14 | 15 | 16 | 17 | true 18 | 1701;1702;0090 19 | 20 | 21 | 22 | true 23 | none 24 | false 25 | 1701;1702;0090 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | True 47 | True 48 | Text.resx 49 | 50 | 51 | True 52 | True 53 | Resources.resx 54 | 55 | 56 | 57 | 58 | 59 | Never 60 | 61 | 62 | 63 | 64 | Never 65 | 66 | 67 | Never 68 | 69 | 70 | 71 | Never 72 | 73 | 74 | 75 | 76 | Never 77 | 78 | 79 | PublicResXFileCodeGenerator 80 | Never 81 | Text.Designer.cs 82 | 83 | 84 | Never 85 | 86 | 87 | 88 | ResXFileCodeGenerator 89 | Resources.Designer.cs 90 | 91 | 92 | 93 | 94 | 95 | PreserveNewest 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Gtk/Program.cs: -------------------------------------------------------------------------------- 1 | using Eto.Forms; 2 | using log4net; 3 | using log4net.Config; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text.RegularExpressions; 9 | using AM2RLauncherLib; 10 | using Application = Eto.Forms.Application; 11 | using FileInfo = System.IO.FileInfo; 12 | using log4net.Repository.Hierarchy; 13 | 14 | // ReSharper disable LocalizableElement - we want hardcoded strings for console writes. 15 | 16 | namespace AM2RLauncher.Gtk; 17 | 18 | /// 19 | /// The main class for the GTK project. 20 | /// 21 | internal static class MainClass 22 | { 23 | /// 24 | /// The logger for , used to write any caught exceptions. 25 | /// 26 | private static readonly ILog log = LogManager.GetLogger(typeof(MainForm)); 27 | /// 28 | /// The main method for the GTK project. 29 | /// 30 | [STAThread] 31 | public static void Main() 32 | { 33 | string launcherDataPath = CrossPlatformOperations.CurrentPath; 34 | 35 | // Make sure first, ~/.local/share/AM2RLauncher exists 36 | Directory.CreateDirectory(launcherDataPath); 37 | 38 | // Now, see if log4netConfig exists, if not write it again. 39 | if (!File.Exists($"{launcherDataPath}/log4net.config")) 40 | File.WriteAllText($"{launcherDataPath}/log4net.config", Properties.Resources.log4netContents.Replace("${DATADIR}", launcherDataPath)); 41 | 42 | // Configure logger 43 | XmlConfigurator.Configure(new FileInfo($"{launcherDataPath}/log4net.config")); 44 | 45 | // if we're on debug, always set log level to debug 46 | #if DEBUG 47 | ((Logger)log.Logger).Level = log4net.Core.Level.Debug; 48 | #endif 49 | 50 | // Log distro and version (if it exists) 51 | if (File.Exists("/etc/os-release")) 52 | { 53 | string osRelease = File.ReadAllText("/etc/os-release"); 54 | Regex lineRegex = new Regex(".*=.*"); 55 | List results = lineRegex.Matches(osRelease).ToList(); 56 | Match version = results.FirstOrDefault(x => x.Value.StartsWith("VERSION")); 57 | string distroName = results.FirstOrDefault(x => x.Value.StartsWith("NAME"))?.Value[5..].Replace("\"", ""); 58 | string versionName = version == null ? "" : version.Value[8..].Replace("\"", ""); 59 | log.Info($"Current Distro: {distroName} {versionName}"); 60 | } 61 | else 62 | log.Error("Couldn't determine the currently running distro!"); 63 | 64 | if (OS.IsThisRunningFromFlatpak) 65 | log.Info("Running from Flatpak!"); 66 | 67 | #if NOAPPIMAGE 68 | log.Info("On \"No AppImage\" configuration."); 69 | #else 70 | log.Info("On AppImage configuration."); 71 | #endif 72 | 73 | #if PATCHOPENSSL 74 | log.Info("On configuration for patching OpenSSL 1.0.0."); 75 | #endif 76 | 77 | try 78 | { 79 | Application gtkLauncher = new Application(Eto.Platforms.Gtk); 80 | LauncherUpdater.Main(); 81 | gtkLauncher.UnhandledException += GTKLauncher_UnhandledException; 82 | gtkLauncher.Run(new MainForm()); 83 | } 84 | catch (Exception e) 85 | { 86 | log.Error($"An unhandled exception has occurred: \n*****Stack Trace*****\n\n{e.StackTrace}"); 87 | Console.WriteLine($"{Language.Text.UnhandledException}\n{e.Message}\n*****Stack Trace*****\n\n{e.StackTrace}"); 88 | Console.WriteLine($"Check the logs at {launcherDataPath} for more info!"); 89 | } 90 | } 91 | 92 | /// 93 | /// This method gets fired when an unhandled exception occurs in . 94 | /// 95 | private static void GTKLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e) 96 | { 97 | log.Error($"An unhandled exception has occurred: \n*****Stack Trace*****\n\n{e.ExceptionObject}"); 98 | Application.Instance.Invoke(() => 99 | { 100 | MessageBox.Show($"{Language.Text.UnhandledException}\n*****Stack Trace*****\n\n{e.ExceptionObject}", "GTK", MessageBoxType.Error); 101 | }); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/XML/ProfileXML.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Xml.Serialization; 3 | 4 | namespace AM2RLauncherLib.XML; 5 | 6 | /// 7 | /// Class that handles how the mod settings are saved as XML. 8 | /// 9 | [Serializable] 10 | [XmlRoot("message")] 11 | public class ProfileXML 12 | { 13 | /// Indicates the Operating system the mod was made for. 14 | [XmlAttribute("OperatingSystem")] 15 | public string OperatingSystem 16 | { get; set; } 17 | /// Indicates the xml version the mod was made in. 18 | [XmlAttribute("XMLVersion")] 19 | public int XMLVersion 20 | { get; set; } 21 | /// Indicates the version of the mod. 22 | [XmlAttribute("Version")] 23 | public string Version 24 | { get; set; } 25 | /// Indicates the mod's name. 26 | [XmlAttribute("Name")] 27 | public string Name 28 | { get; set; } 29 | /// Indicates the mod's author. 30 | [XmlAttribute("Author")] 31 | public string Author 32 | { get; set; } 33 | /// Indicates whether or not the mod uses custom music. 34 | [XmlAttribute("UsesCustomMusic")] 35 | public bool UsesCustomMusic 36 | { get; set; } 37 | /// Indicates the save location of the mod. 38 | [XmlAttribute("SaveLocation")] 39 | public string SaveLocation 40 | { get; set; } 41 | /// Indicates whether or not the mod supports Android. 42 | [XmlAttribute("SupportsAndroid")] 43 | public bool SupportsAndroid 44 | { get; set; } 45 | /// Indicates whether or not the mod was compiled with YYC. 46 | [XmlAttribute("UsesYYC")] 47 | public bool UsesYYC 48 | { get; set; } 49 | /// Indicates if the mod is installable. This is only for archival community updates mods. 50 | [XmlAttribute("Installable")] 51 | public bool Installable 52 | { get; set; } 53 | /// Indicates any notes that the mod author deemed worthy to share about his mod. 54 | [XmlAttribute("ProfileNotes")] 55 | public string ProfileNotes 56 | { get; set; } 57 | /// This gets calculated at runtime, by the Launcher. Indicates where the install data for the mod is stored. 58 | [XmlIgnore] 59 | public string DataPath 60 | { get; set; } 61 | 62 | /// Creates a with a default set of attributes. 63 | public ProfileXML() 64 | { 65 | Installable = true; 66 | } 67 | 68 | /// 69 | /// Creates a with a custom set of attributes. 70 | /// 71 | /// The operating system the mod was made on. 72 | /// The xml version the mod was created with. 73 | /// The version of the mod. 74 | /// The mod name. 75 | /// The mod author. 76 | /// Whether or not the mod uses custom music. 77 | /// The save location of the mod. 78 | /// Whether or not the mod works for android. 79 | /// Whether or not the mod was made with YYC. 80 | /// Whether or not the mod is installable. 81 | /// The notes of the mod. 82 | public ProfileXML(string operatingSystem, int xmlVersion, string version, string name, string author, 83 | bool usesCustomMusic, string saveLocation, bool android, bool usesYYC, 84 | bool installable, string profileNotes) 85 | { 86 | OperatingSystem = operatingSystem; 87 | XMLVersion = xmlVersion; 88 | Version = version; 89 | Name = name; 90 | Author = author; 91 | UsesCustomMusic = usesCustomMusic; 92 | SaveLocation = saveLocation; 93 | SupportsAndroid = android; 94 | UsesYYC = usesYYC; 95 | Installable = installable; 96 | ProfileNotes = profileNotes; 97 | } 98 | 99 | public override string ToString() 100 | { 101 | return Name; 102 | } 103 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AM2RLauncher 2 | A front-end application that simplifies installing the latest AM2R-Community-Updates, creating APKs for Android use, as well as Mods for AM2R. It supports Windows (x86/x64) as well as Linux (x64). 3 | 4 | ## What is this? 5 | AM2R (Another Metroid 2 Remake) is a fan-made remake of *Metroid II: Return of Samus* in the style of *Metroid: Zero Mission*. It was released in 2016 by DoctorM64 and his team, and very soon after, has received a DMCA letter from Nintendo. 6 | A few years after, multiple members of the community managed to reverse engineer the game and published unofficial updates, dubbed the AM2R-Community-Updates. Those fixed bugs and added new features like widescreen, added content planned by the original devs before they were struck down, and ported it to other platforms. 7 | 8 | The AM2RLauncher lets you conveniently play those Community-Updates, automatically receive updates, install AM2R mods and create APKs to play on your phone. 9 | No copyrighted files are shipped, you need to provide your own copy of AM2R_11! 10 | 11 | For further questions regarding AM2R [see this FAQ](https://am2r-community-developers.github.io/DistributionCenter/faq.html). 12 | For further questions regarding the AM2RLauncher [see this Wiki](https://github.com/AM2R-Community-Developers/AM2RLauncher/wiki). 13 | For further communication, you can reach us on [Discord](https://discord.gg/nk7UYPbd5u), [Matrix](https://matrix.to/#/#am2r-space:matrix.org), or [GitHub issues](https://github.com/AM2R-Community-Developers/AM2RLauncher/issues). 14 | 15 | ## Downloads 16 | ![GitHub release (latest by date)](https://img.shields.io/github/v/release/AM2R-Community-Developers/AM2RLauncher?label=GitHub&logo=github&style=flat-square) ![Flathub](https://img.shields.io/flathub/v/io.github.am2r_community_developers.AM2RLauncher?label=FlatHub&logo=flathub&logoColor=white&style=flat-square) 17 | ![AUR version](https://img.shields.io/aur/version/am2rlauncher?label=AUR&style=flat-square) 18 | Downloads can be found at the [Release Page](https://github.com/AM2R-Community-Developers/AM2RLauncher/releases). 19 | 20 | For all Linux users, a [Flatpak](https://flathub.org/apps/details/io.github.am2r_community_developers.AM2RLauncher) is available and can be installed with all above dependencies bundled. This method is the recommended way for Steam Deck users. 21 | 22 | ### Arch Linux 23 | 24 | For Arch Linux users an [AUR Package](https://aur.archlinux.org/packages/am2rlauncher/) also exists. Install it with `makepkg -si` or use your favourite AUR helper. 25 | 26 | ### NixOS 27 | 28 | The launcher has also been packaged for NixOS. It can be run with `nix run nixpkgs#am2rlauncher`. 29 | 30 | ## Dependencies 31 | Windows needs the [.NET Framework 4.8 runtime](https://dotnet.microsoft.com/download/dotnet-framework/net48) installed. 32 | Linux needs the following dependencies installed: 33 | 34 | - [.NET Core 6.0 runtime](https://dotnet.microsoft.com/download/dotnet/6.0) or later. .NET Core 6.0 is preferred. 35 | - `xdelta3` 36 | - `gtk3` 37 | - `libappindicator3` 38 | - `webkitgtk` 39 | - `openssl` 40 | - `fuse2` 41 | 42 | As well as these dependencies to run AM2R: 43 | - 32-bit version of `libpulse` 44 | - 32-bit version of `openal` 45 | - 32-bit version of your graphics drivers 46 | 47 | Optionally, for APK creation any Java runtime is needed. 48 | 49 | For more detailed instructions check out the [installation process wiki page](https://github.com/AM2R-Community-Developers/AM2RLauncher/wiki/Installation-Process). 50 | 51 | ## Configuration and Data Files 52 | The AM2RLauncher stores its files in the following places: 53 | - On Windows, it stores the config file to the `AM2RLauncher.exe.config` next to the binary, and its data files in the same folder as the binary. 54 | - On Linux, it stores the config file to `$XDG_CONFIG_HOME/AM2RLauncher` and its data files to `$XDG_DATA_HOME/AM2RLauncher` (which are defaulting back to `~/.config` and `~/.local/share` respectively). 55 | 56 | The AM2RLauncher data can get quite big, so if you wish to change where it stores it, you can do so with the `AM2RLAUNCHERDATA` environment variable (i.e `$env:AM2RLAUNCHERDATA="D:\MyLauncherData"` or `AM2RLAUNCHERDATA="/mnt/bigDrive/launcherData"`). 57 | **Data files are different for each OS, you cannot mix and match them!** 58 | 59 | If you wish to redistribute the AM2RLauncher to some Linux distro, you can use the `NoAutoUpdate` configuration, in order to build the AM2RLauncher with the auto-updating features disabled. Further assets (.desktop file, icon, appdata) can also be found in this directory: `./AM2RLauncher/distribution/linux`. 60 | 61 | # Contributing 62 | Contributions of any kind are always welcome! Here's possible methods on how you can contribute: 63 | - Keep the [Wiki](https://github.com/AM2R-Community-Developers/AM2RLauncher/wiki) up-to date should any changes occur. Or notify us if it isn't. 64 | - Help us with translating the AM2RLauncher into more languages. You can find the language files [here](https://github.com/AM2R-Community-Developers/AM2RLauncher/tree/main/AM2RLauncher/AM2RLauncher/Language). 65 | - Test the development builds for any regressions, check any of the [`TODO`s](https://github.com/AM2R-Community-Developers/AM2RLauncher/search?q=todo) that need testing or help with experimental features (like macOS). 66 | - Submit Pull Requests to fix bugs or implement new features. 67 | 68 | # Compiling Instructions: 69 | ## Dependencies 70 | For compiling for Windows .Net Framework 4.8 SDK is needed. For Linux and Mac .Net Core 5.0 SDK or later is needed. 71 | 72 | ## Windows Instructions 73 | Open the solution with Visual Studio 2019. 74 | Alternatively, build via `dotnet build` / the `buildAll` batch file. 75 | 76 | ## Linux Instructions 77 | In order to build for linux, use `dotnet publish AM2RLauncher.Gtk -p:PublishSingleFile=true -p:DebugType=embedded -c release -r linux-x64 --no-self-contained`, MonoDevelop sadly doesn't work. 78 | 79 | ### Arch Linux 80 | An `am2rlauncher-git` [AUR Package](https://aur.archlinux.org/packages/am2rlauncher-git/) also exists. 81 | 82 | ### NixOS 83 | On NixOS, the `am2rlauncher` package can be used for installation. 84 | 85 | ## Mac Instructions 86 | You can open the solution with Visual Studio for Mac, but it likely will crash after compliation. Use `dotnet publish AM2RLauncher.Mac -c release` instead. 87 | Note that Mac is currently **unsupported**. We will try to answer questions, but cannot guarantee to fix issues with Mac. 88 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/Splash.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace AM2RLauncherLib; 5 | 6 | /// 7 | /// Class only for providing Splashes 8 | /// 9 | public static class Splash 10 | { 11 | /// 12 | /// Cross-Platform splash strings 13 | /// 14 | private static readonly string[] generalSplash = 15 | { 16 | "The real Ridley is the friends we made along the way.", 17 | "Now with 100% more Septoggs!", 18 | "Now with 100% more Blob Throwers!", 19 | "Speedrun THIS, I dare you.", 20 | "The broken pipe is a lie.", 21 | "I am altering the broken pipe. Pray I don't alter it any further.", 22 | "How dreadful, Yet Another Launcher to bloat my system.", 23 | "Over committing to April Fool's since 2018.", 24 | "Also try Metroid II: Return of Samus!", 25 | "Also try Metroid: Samus Returns!", 26 | "Also try Prime 2D!", 27 | "Also try Skippy the Bot!", 28 | "Trust me, it's an 'unintentional feature.'", 29 | "Coming soon to a PC near you!", 30 | "This ain't your parents' Metroid 2!", 31 | "Now setting boss pattern to S P E E N", 32 | "You S P E E N me right round, baby!", 33 | "Wait, I thought Metroid was a guy!", 34 | "jellyfish is helping yes", 35 | "Why can't Metroid crawl?", 36 | "When in doubt, blame GameMaker.", 37 | "It just works™", 38 | "Reject C++, return to ABSTRACTION", 39 | "C# is just C++++ with Windows support.", 40 | "Use of the Launcher has now been authorized!", 41 | "Oh? So you're patching me?", 42 | "I, Gawron Giovana, have a dream.", 43 | "Oooh weee ooo, I look just like an Omega", 44 | "AH THE MOLE", 45 | "S P I D E R B A L L", 46 | "Fun is infinite with Community-Developers Inc.", 47 | "You may only proceed if you wear baggy pants.", 48 | "Did you know that games take time to develop?", 49 | "Ask the wombat.", 50 | "This isn't complicated enough yet.", 51 | "The broken pipe's destination is left as an exercise for the player." 52 | }; 53 | 54 | /// 55 | /// Windows only splash strings 56 | /// 57 | private static readonly string[] windowsSplash = 58 | { 59 | ":(", 60 | "All your machine are belong to MS", 61 | "All your data are belong to us", 62 | "Go to Settings to activate AM2R.", 63 | "(Not Responding)" 64 | }; 65 | 66 | /// 67 | /// Linux only splash strings 68 | /// 69 | private static readonly string[] linuxSplash = 70 | { 71 | "Sorry this is ugly, but at least it works.", 72 | "GTK + QT = 💣", 73 | "I hope you use Arch, btw", 74 | "All your Ubuntu are belong to Arch btw.", 75 | "Help, how do I quit vim!?", 76 | "The bloat isn't our fault, YoYo Games forced 1GB dependencies!", 77 | "On second thought, maybe the bloat is our fault...", 78 | "The quieter you are, the more Metroids you can hear.", 79 | "What you are referring to as AM2R, is in fact, GNU/AM2R.", 80 | "GNOME be gone!", 81 | "Kurse you KDE!", 82 | "Go compile your own girlfriend. This one doesn't count, she's out of your league.", 83 | "Year 3115, NVIDIA still doesn't support Wayland.", 84 | "Was a mouse really that expensive, i3 users!?", 85 | "Imagine using non-free software.", 86 | "What if... we used a non-FOSS OS? Haha just kidding... Unless?", 87 | "🐧", 88 | "You are already patched 🐸🕶", 89 | "Yes, do as I say!", 90 | "Did you find the penguin yet?" 91 | }; 92 | 93 | /// 94 | /// Splashes additionally used in Flatpaks 95 | /// 96 | private static readonly string[] flatpakSplash = 97 | { 98 | "All hail Gaben", 99 | "Thanks to Flatpak, our bugs are now consistent!", 100 | "You're using SteamOS, aren't you?", 101 | "Now extra sandboxed, in case of GameMaker jank!", 102 | "The next remake? Another Metroid 4 Remake of course!", 103 | "What do you mean that there's a number after 2?", 104 | "We don't like Sand. It's course, rough, and it gets everywhere.", 105 | "This is a sandbox, not a sandy B.O.X.", 106 | "Now easily available on an App Store!", 107 | "How did you Discover this launcher?" 108 | }; 109 | 110 | /// 111 | /// Mac only splash strings 112 | /// 113 | private static readonly string[] macSplash = 114 | { 115 | "Does this even work?", 116 | "You weren't supposed to do that!", 117 | "Another Mac 2 Remake", 118 | "This shouldn't even be possible", 119 | "How long until this breaks?", 120 | "Have fun trying to convince modders to support this!", 121 | "It's a secret to everybody." 122 | }; 123 | 124 | /// 125 | /// Combined splash strings 126 | /// 127 | private static readonly string[] combinedSplash = CombineSplashes(); 128 | 129 | /// 130 | /// Get a random splash string, according to the current OS. 131 | /// 132 | /// The randomly chosen splash as a . 133 | public static string GetSplash() 134 | { 135 | Random rng = new Random(); 136 | 137 | string splashString = combinedSplash[rng.Next(0, combinedSplash.Length)]; 138 | return splashString; 139 | } 140 | 141 | /// 142 | /// Creates a string array which is concatenated with the splash array for the current OS 143 | /// 144 | /// A string array where and the splash array for the current OS have been concatenated. 145 | private static string[] CombineSplashes() 146 | { 147 | string[] totalSplashes; 148 | if (OS.IsWindows) 149 | totalSplashes = generalSplash.Concat(windowsSplash).ToArray(); 150 | else if (OS.IsLinux) 151 | totalSplashes = generalSplash.Concat(linuxSplash).ToArray(); 152 | else if (OS.IsMac) 153 | totalSplashes = generalSplash.Concat(macSplash).ToArray(); 154 | else 155 | totalSplashes = generalSplash; 156 | 157 | // This is seperate, as we want them to be combined with linux 158 | if (OS.IsThisRunningFromFlatpak) 159 | totalSplashes = totalSplashes.Concat(flatpakSplash).ToArray(); 160 | 161 | return totalSplashes; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Gtk/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | <log4net> 122 | <root> 123 | <level value="INFO" /> 124 | <appender-ref ref="file" /> 125 | </root> 126 | <appender name="file" type="log4net.Appender.RollingFileAppender"> 127 | <file value="${DATADIR}/Logs/AM2RLauncher.log" /> 128 | <appendToFile value="true" /> 129 | <rollingStyle value="Once" /> 130 | <maxSizeRollBackups value="7" /> 131 | <maximumFileSize value="3MB" /> 132 | <staticLogFileName value="true" /> 133 | <layout type="log4net.Layout.PatternLayout"> 134 | <conversionPattern value="%date [%thread] %level %logger - %message%newline" /> 135 | </layout> 136 | </appender> 137 | </log4net> 138 | 139 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | <log4net> 122 | <root> 123 | <level value="INFO" /> 124 | <appender-ref ref="file" /> 125 | </root> 126 | <appender name="file" type="log4net.Appender.RollingFileAppender"> 127 | <file value="${DATADIR}/Logs/AM2RLauncher.log" /> 128 | <appendToFile value="true" /> 129 | <rollingStyle value="Once" /> 130 | <maxSizeRollBackups value="7" /> 131 | <maximumFileSize value="3MB" /> 132 | <staticLogFileName value="true" /> 133 | <layout type="log4net.Layout.PatternLayout"> 134 | <conversionPattern value="%date [%thread] %level %logger - %message%newline" /> 135 | </layout> 136 | </appender> 137 | </log4net> 138 | 139 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher.Wpf/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | <log4net> 122 | <root> 123 | <level value="INFO" /> 124 | <appender-ref ref="file" /> 125 | </root> 126 | <appender name="file" type="log4net.Appender.RollingFileAppender"> 127 | <file value="${DATADIR}/Logs/AM2RLauncher.log" /> 128 | <appendToFile value="true" /> 129 | <rollingStyle value="Once" /> 130 | <maxSizeRollBackups value="7" /> 131 | <maximumFileSize value="3MB" /> 132 | <staticLogFileName value="true" /> 133 | <layout type="log4net.Layout.PatternLayout"> 134 | <conversionPattern value="%date [%thread] %level %logger - %message%newline" /> 135 | </layout> 136 | </appender> 137 | </log4net> 138 | 139 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/CustomControls/CustomButton.cs: -------------------------------------------------------------------------------- 1 | using Eto.Drawing; 2 | using Eto.Forms; 3 | using System; 4 | 5 | namespace Pablo.Controls; 6 | 7 | /// 8 | /// A custom button implementation for advanced drawing and events. 9 | /// Originally written by cwensley (https://gist.github.com/cwensley/95000998e37acd93e830), 10 | /// modified by Lojemiru and Miepee for use in the AM2RLauncher. 11 | /// 12 | public class CustomButton : Drawable 13 | { 14 | /// Whether or not this is being pressed. 15 | bool pressed; 16 | 17 | /// Whether or not the mouse is hovering over this . 18 | bool hover; 19 | 20 | /// Whether or not the mouse is pressed down over this . 21 | bool mouseDown; 22 | 23 | /// Gets or sets the disabled color of this control. 24 | public static Color DisabledColor = Color.FromGrayscale(0.4f, 0.3f); 25 | 26 | /// Gets or sets the color of this control. 27 | public static Color EnabledColor = Colors.Black; 28 | 29 | /// Gets or sets whether or not this control is enabled. 30 | public override bool Enabled 31 | { 32 | get { return base.Enabled; } 33 | set 34 | { 35 | if (base.Enabled == value) 36 | return; 37 | base.Enabled = value; 38 | if (Loaded) 39 | Invalidate(); 40 | } 41 | } 42 | 43 | /// Gets or sets . 44 | public bool Pressed 45 | { 46 | get { return pressed; } 47 | set 48 | { 49 | if (pressed != value) 50 | { 51 | pressed = value; 52 | mouseDown = false; 53 | if (Loaded) 54 | Invalidate(); 55 | } 56 | } 57 | } 58 | 59 | /// Gets . 60 | public bool Hover 61 | { 62 | get { return hover; } 63 | } 64 | 65 | /// Gets either the or dependent on . 66 | public Color DrawColor 67 | { 68 | get { return Enabled ? EnabledColor : DisabledColor; } 69 | } 70 | 71 | /// Gets or sets whether or not this control should act as a toggle. 72 | public bool Toggle { get; set; } 73 | 74 | /// Gets or sets whether or not this control is persistent. 75 | public bool Persistent { get; set; } 76 | 77 | public event EventHandler Click; 78 | 79 | /// 80 | /// Event raised when this control is clicked. 81 | /// 82 | protected virtual void OnClick(EventArgs e) 83 | { 84 | if (Click != null) 85 | Click(this, e); 86 | } 87 | 88 | /// Default constructor. Sets to true. 89 | public CustomButton() 90 | { 91 | Enabled = true; 92 | CanFocus = true; 93 | } 94 | 95 | /// Event raised when this control is resized. 96 | protected override void OnSizeChanged(EventArgs e) 97 | { 98 | base.OnSizeChanged(e); 99 | if (Loaded) 100 | Invalidate(); 101 | } 102 | 103 | /// 104 | /// Event raised when the mouse is pressed down over this control's bounding box. 105 | /// 106 | protected override void OnMouseDown(MouseEventArgs e) 107 | { 108 | base.OnMouseDown(e); 109 | if (!Enabled) 110 | return; 111 | mouseDown = true; 112 | Invalidate(); 113 | } 114 | 115 | /// 116 | /// Event raised when the mouse enters this control's bounding box. 117 | /// 118 | protected override void OnMouseEnter(MouseEventArgs e) 119 | { 120 | Cursor = new Cursor(CursorType.Pointer); 121 | base.OnMouseEnter(e); 122 | hover = true; 123 | Invalidate(); 124 | } 125 | 126 | /// 127 | /// Event raised when the mouse leaves this control's bounding box. 128 | /// 129 | protected override void OnMouseLeave(MouseEventArgs e) 130 | { 131 | Cursor = new Cursor(CursorType.Default); 132 | base.OnMouseLeave(e); 133 | hover = false; 134 | Invalidate(); 135 | } 136 | 137 | /// 138 | /// Event raised, when this control gets focused. 139 | /// 140 | protected override void OnGotFocus(EventArgs e) 141 | { 142 | hover = true; 143 | base.OnGotFocus(e); 144 | Invalidate(); 145 | } 146 | 147 | /// 148 | /// Event raised, when this control looses focus. 149 | /// 150 | protected override void OnLostFocus(EventArgs e) 151 | { 152 | hover = false; 153 | base.OnLostFocus(e); 154 | Invalidate(); 155 | } 156 | 157 | /// 158 | /// Event raised, when a key is pressed down. 159 | /// 160 | protected override void OnKeyDown(KeyEventArgs e) 161 | { 162 | base.OnKeyDown(e); 163 | if (e.KeyData == Keys.Enter && HasFocus) 164 | OnClick(null); 165 | } 166 | 167 | /// 168 | /// Event raised when the mouse is released over this control's bounding box. 169 | /// 170 | /// 171 | protected override void OnMouseUp(MouseEventArgs e) 172 | { 173 | base.OnMouseUp(e); 174 | var rect = new Rectangle(this.Size); 175 | if (mouseDown && rect.Contains((Point)e.Location)) 176 | { 177 | if (Toggle) 178 | pressed = !pressed; 179 | else if (Persistent) 180 | pressed = true; 181 | else 182 | pressed = false; 183 | mouseDown = false; 184 | 185 | this.Invalidate(); 186 | if (Enabled) 187 | OnClick(EventArgs.Empty); 188 | } 189 | else 190 | { 191 | mouseDown = false; 192 | this.Invalidate(); 193 | } 194 | } 195 | 196 | /// 197 | /// Event raised to draw this control. 198 | /// 199 | /// 200 | protected override void OnPaint(PaintEventArgs pe) 201 | { 202 | var rect = new Rectangle(this.Size); 203 | var col = Color.FromGrayscale(hover && Enabled ? 0.95f : 0.8f); 204 | if (Enabled && (pressed || mouseDown)) 205 | { 206 | pe.Graphics.FillRectangle(col, rect); 207 | pe.Graphics.DrawInsetRectangle(Colors.Gray, Colors.White, rect); 208 | } 209 | else if (hover && Enabled) 210 | { 211 | pe.Graphics.FillRectangle(col, rect); 212 | pe.Graphics.DrawInsetRectangle(Colors.White, Colors.Gray, rect); 213 | } 214 | 215 | base.OnPaint(pe); 216 | } 217 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | builds/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # StyleCop 66 | StyleCopReport.xml 67 | 68 | # Files built by Visual Studio 69 | *_i.c 70 | *_p.c 71 | *_h.h 72 | *.ilk 73 | *.meta 74 | *.obj 75 | *.iobj 76 | *.pch 77 | *.pdb 78 | *.ipdb 79 | *.pgc 80 | *.pgd 81 | *.rsp 82 | *.sbr 83 | *.tlb 84 | *.tli 85 | *.tlh 86 | *.tmp 87 | *.tmp_proj 88 | *_wpftmp.csproj 89 | *.log 90 | *.vspscc 91 | *.vssscc 92 | .builds 93 | *.pidb 94 | *.svclog 95 | *.scc 96 | 97 | # Chutzpah Test files 98 | _Chutzpah* 99 | 100 | # Visual C++ cache files 101 | ipch/ 102 | *.aps 103 | *.ncb 104 | *.opendb 105 | *.opensdf 106 | *.sdf 107 | *.cachefile 108 | *.VC.db 109 | *.VC.VC.opendb 110 | 111 | # Visual Studio profiler 112 | *.psess 113 | *.vsp 114 | *.vspx 115 | *.sap 116 | 117 | # Visual Studio Trace Files 118 | *.e2e 119 | 120 | # TFS 2012 Local Workspace 121 | $tf/ 122 | 123 | # Guidance Automation Toolkit 124 | *.gpState 125 | 126 | # ReSharper is a .NET coding add-in 127 | _ReSharper*/ 128 | *.[Rr]e[Ss]harper 129 | *.DotSettings.user 130 | 131 | # TeamCity is a build add-in 132 | _TeamCity* 133 | 134 | # DotCover is a Code Coverage Tool 135 | *.dotCover 136 | 137 | # AxoCover is a Code Coverage Tool 138 | .axoCover/* 139 | !.axoCover/settings.json 140 | 141 | # Visual Studio code coverage results 142 | *.coverage 143 | *.coveragexml 144 | 145 | # NCrunch 146 | _NCrunch_* 147 | .*crunch*.local.xml 148 | nCrunchTemp_* 149 | 150 | # MightyMoose 151 | *.mm.* 152 | AutoTest.Net/ 153 | 154 | # Web workbench (sass) 155 | .sass-cache/ 156 | 157 | # Installshield output folder 158 | [Ee]xpress/ 159 | 160 | # DocProject is a documentation generator add-in 161 | DocProject/buildhelp/ 162 | DocProject/Help/*.HxT 163 | DocProject/Help/*.HxC 164 | DocProject/Help/*.hhc 165 | DocProject/Help/*.hhk 166 | DocProject/Help/*.hhp 167 | DocProject/Help/Html2 168 | DocProject/Help/html 169 | 170 | # Click-Once directory 171 | publish/ 172 | 173 | # Publish Web Output 174 | *.[Pp]ublish.xml 175 | *.azurePubxml 176 | # Note: Comment the next line if you want to checkin your web deploy settings, 177 | # but database connection strings (with potential passwords) will be unencrypted 178 | *.pubxml 179 | *.publishproj 180 | 181 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 182 | # checkin your Azure Web App publish settings, but sensitive information contained 183 | # in these scripts will be unencrypted 184 | PublishScripts/ 185 | 186 | # NuGet Packages 187 | *.nupkg 188 | # NuGet Symbol Packages 189 | *.snupkg 190 | # The packages folder can be ignored because of Package Restore 191 | **/[Pp]ackages/* 192 | # except build/, which is used as an MSBuild target. 193 | !**/[Pp]ackages/build/ 194 | # Uncomment if necessary however generally it will be regenerated when needed 195 | #!**/[Pp]ackages/repositories.config 196 | # NuGet v3's project.json files produces more ignorable files 197 | *.nuget.props 198 | *.nuget.targets 199 | 200 | # Microsoft Azure Build Output 201 | csx/ 202 | *.build.csdef 203 | 204 | # Microsoft Azure Emulator 205 | ecf/ 206 | rcf/ 207 | 208 | # Windows Store app package directories and files 209 | AppPackages/ 210 | BundleArtifacts/ 211 | Package.StoreAssociation.xml 212 | _pkginfo.txt 213 | *.appx 214 | *.appxbundle 215 | *.appxupload 216 | 217 | # Visual Studio cache files 218 | # files ending in .cache can be ignored 219 | *.[Cc]ache 220 | # but keep track of directories ending in .cache 221 | !?*.[Cc]ache/ 222 | 223 | # Others 224 | ClientBin/ 225 | ~$* 226 | *~ 227 | *.dbmdl 228 | *.dbproj.schemaview 229 | *.jfm 230 | *.pfx 231 | *.publishsettings 232 | orleans.codegen.cs 233 | 234 | # Including strong name files can present a security risk 235 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 236 | #*.snk 237 | 238 | # Since there are multiple workflows, uncomment next line to ignore bower_components 239 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 240 | #bower_components/ 241 | 242 | # RIA/Silverlight projects 243 | Generated_Code/ 244 | 245 | # Backup & report files from converting an old project file 246 | # to a newer Visual Studio version. Backup files are not needed, 247 | # because we have git ;-) 248 | _UpgradeReport_Files/ 249 | Backup*/ 250 | UpgradeLog*.XML 251 | UpgradeLog*.htm 252 | ServiceFabricBackup/ 253 | *.rptproj.bak 254 | 255 | # SQL Server files 256 | *.mdf 257 | *.ldf 258 | *.ndf 259 | 260 | # Business Intelligence projects 261 | *.rdl.data 262 | *.bim.layout 263 | *.bim_*.settings 264 | *.rptproj.rsuser 265 | *- [Bb]ackup.rdl 266 | *- [Bb]ackup ([0-9]).rdl 267 | *- [Bb]ackup ([0-9][0-9]).rdl 268 | 269 | # Microsoft Fakes 270 | FakesAssemblies/ 271 | 272 | # GhostDoc plugin setting file 273 | *.GhostDoc.xml 274 | 275 | # Node.js Tools for Visual Studio 276 | .ntvs_analysis.dat 277 | node_modules/ 278 | 279 | # Visual Studio 6 build log 280 | *.plg 281 | 282 | # Visual Studio 6 workspace options file 283 | *.opt 284 | 285 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 286 | *.vbw 287 | 288 | # Visual Studio LightSwitch build output 289 | **/*.HTMLClient/GeneratedArtifacts 290 | **/*.DesktopClient/GeneratedArtifacts 291 | **/*.DesktopClient/ModelManifest.xml 292 | **/*.Server/GeneratedArtifacts 293 | **/*.Server/ModelManifest.xml 294 | _Pvt_Extensions 295 | 296 | # Paket dependency manager 297 | .paket/paket.exe 298 | paket-files/ 299 | 300 | # FAKE - F# Make 301 | .fake/ 302 | 303 | # CodeRush personal settings 304 | .cr/personal 305 | 306 | # Python Tools for Visual Studio (PTVS) 307 | __pycache__/ 308 | *.pyc 309 | 310 | # Cake - Uncomment if you are using it 311 | # tools/** 312 | # !tools/packages.config 313 | 314 | # Tabs Studio 315 | *.tss 316 | 317 | # Telerik's JustMock configuration file 318 | *.jmconfig 319 | 320 | # BizTalk build output 321 | *.btp.cs 322 | *.btm.cs 323 | *.odx.cs 324 | *.xsd.cs 325 | 326 | # OpenCover UI analysis results 327 | OpenCover/ 328 | 329 | # Azure Stream Analytics local run output 330 | ASALocalRun/ 331 | 332 | # MSBuild Binary and Structured Log 333 | *.binlog 334 | 335 | # NVidia Nsight GPU debugger configuration file 336 | *.nvuser 337 | 338 | # MFractors (Xamarin productivity tool) working folder 339 | .mfractor/ 340 | 341 | # Local History for Visual Studio 342 | .localhistory/ 343 | 344 | # BeatPulse healthcheck temp database 345 | healthchecksdb 346 | 347 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 348 | MigrationBackup/ 349 | 350 | # Ionide (cross platform F# VS Code tools) working folder 351 | .ionide/ 352 | 353 | # Visual Studio Code folder 354 | .vscode/ 355 | 356 | # Mac's DS_Stores 357 | .DS_Store 358 | 359 | # IDEA files and folders 360 | .idea/ 361 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\AM2RIcon.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 123 | 124 | 125 | ..\Resources\bgCentered.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 126 | 127 | 128 | ..\Resources\discord.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 129 | 130 | 131 | ..\Resources\github light x48.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 132 | 133 | 134 | ..\Resources\reddit_share_circle_48.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 135 | 136 | 137 | ..\Resources\youtube_social_circle_red 48.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 138 | 139 | 140 | ..\Resources\matrix.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 141 | 142 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/XML/LauncherConfigXML.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Xml.Serialization; 4 | 5 | namespace AM2RLauncher.XML; 6 | 7 | /// 8 | /// Class that handles how the Launcher settings are saved as XML. 9 | /// 10 | /// Only affects Linux and Mac! Windows settings can be found in the Wpf project in "app.config". 11 | [Serializable] 12 | [XmlRoot("settings")] 13 | public class LauncherConfigXML 14 | { 15 | /// Indicates whether or not to auto-update the Launcher. Used for 16 | [XmlAttribute("AutoUpdateAM2R")] 17 | public bool AutoUpdateAM2R 18 | { get; set; } 19 | /// Indicates whether or not to auto-update the Launcher. Used for 20 | [XmlAttribute("AutoUpdateLauncher")] 21 | public bool AutoUpdateLauncher 22 | { get; set; } 23 | /// Indicates the Language of the Launcher. Used for 24 | [XmlAttribute("Language")] 25 | public string Language 26 | { get; set; } 27 | /// Indicates whether or not to use High-quality music when patching to PC. Used for 28 | [XmlAttribute("MusicHQPC")] 29 | public bool MusicHQPC 30 | { get; set; } 31 | /// Indicates whether or not to use High-quality music when patching to Android. Used for 32 | [XmlAttribute("MusicHQAndroid")] 33 | public bool MusicHQAndroid 34 | { get; set; } 35 | /// Indicates the index for . 36 | [XmlAttribute("MirrorIndex")] 37 | public int MirrorIndex 38 | { get; set; } 39 | /// Indicates the index for . 40 | [XmlAttribute("ProfileIndex")] 41 | public string ProfileIndex 42 | { get; set; } 43 | /// Indicates whether or not to have custom mirrors enabled. Used for 44 | [XmlAttribute("CustomMirrorEnabled")] 45 | public bool CustomMirrorEnabled 46 | { get; set; } 47 | /// Indicates the custom mirror as a text. Used for 48 | [XmlAttribute("CustomMirrorText")] 49 | public string CustomMirrorText 50 | { get; set; } 51 | /// Indicates whether or not to create debug logs of profile. Used for 52 | [XmlAttribute("ProfileDebugLog")] 53 | public bool ProfileDebugLog 54 | { get; set; } 55 | /// Indicates the Width of the Launcher. 56 | [XmlAttribute("Width")] 57 | public int Width 58 | { get; set; } 59 | /// Indicates the Height of the Launcher. 60 | [XmlAttribute("Height")] 61 | public int Height 62 | { get; set; } 63 | /// Indicates whether or not the Launcher is maximized or not. 64 | [XmlAttribute("IsMaximized")] 65 | public bool IsMaximized 66 | { get; set; } 67 | 68 | // Huge help from James for all of this!!! 69 | // Here's a short explanation of this. Basically, what we do is create an indexer. This makes it possible for this class to be indexed, like this LauncherConfigXML[i] 70 | // The indexer here has a get and set "submethod". And both use Reflection to get the current class as something that can be used with lambda values 71 | // So for get we just lambda the properties and search for something that can be read and has the same name as the input property. After that, we just get the value and return it as a string 72 | // Set is basically the same, but instead of returning it, we set the property value, which would be followed after the `='. 73 | // So LauncherConfigXML[property] = hellWorld would set the value of `property` to `hellWorld` 74 | /// 75 | /// An Indexer for . Not to be used directly, use 76 | /// or instead! 77 | /// 78 | /// The property to get or set. 79 | /// The value of as an if used as a get, if used as a set. 80 | public object this[string property] 81 | { 82 | get 83 | { 84 | // This is gonna throw an exception, if the property can't be found. because of null.GetValue(this) 85 | return typeof(LauncherConfigXML).GetProperties().First(p => p.CanRead && p.Name == property).GetValue(this).ToString(); 86 | } 87 | set 88 | { 89 | typeof(LauncherConfigXML).GetProperties().First(p => p.CanWrite && p.Name == property).SetValue(this, value); 90 | } 91 | } 92 | 93 | /// 94 | /// Creates a with a default set of attributes. 95 | /// 96 | public LauncherConfigXML() 97 | { 98 | AutoUpdateAM2R = true; 99 | AutoUpdateLauncher = true; 100 | Language = "Default"; 101 | MusicHQPC = true; 102 | MusicHQAndroid = false; 103 | ProfileIndex = "null"; 104 | MirrorIndex = 0; 105 | CustomMirrorEnabled = false; 106 | CustomMirrorText = ""; 107 | ProfileDebugLog = true; 108 | Width = 600; 109 | Height = 600; 110 | IsMaximized = false; 111 | } 112 | 113 | /// 114 | /// Creates a with custom attributes. 115 | /// 116 | /// Parameter that indicates if is enabled or not. 117 | /// Parameter that indicates if is enabled or not. 118 | /// Parameter that indicates the language of the launcher. 119 | /// Parameter that indicates if is enabled or not. 120 | /// Parameter that indicates if is enabled or not. 121 | /// Parameter that saves the index of the selected profile of . 122 | /// Parameter that saves the index of the selected mirror in . 123 | /// Parameter that indicates if is enabled or not. 124 | /// Parameter that indicates if is enabled or not. 125 | /// Parameter that's used for . 126 | /// Parameter that indicates the width of . 127 | /// Parameter that indicates the height of . 128 | /// Parameter that indicates if has been set to fullscreen or not. 129 | public LauncherConfigXML(bool autoUpdateAM2R, bool autoUpdateLauncher, string language, bool musicHQPC, bool musicHQAndroid, 130 | string profileIndex, int mirrorIndex, bool profileDebugLog, bool customMirrorEnabled, 131 | string customMirrorText, int width, int height, bool isMaximized) 132 | { 133 | AutoUpdateAM2R = autoUpdateAM2R; 134 | AutoUpdateLauncher = autoUpdateLauncher; 135 | Language = language; 136 | MusicHQPC = musicHQPC; 137 | MusicHQAndroid = musicHQAndroid; 138 | ProfileIndex = profileIndex; 139 | MirrorIndex = mirrorIndex; 140 | CustomMirrorEnabled = customMirrorEnabled; 141 | CustomMirrorText = customMirrorText; 142 | ProfileDebugLog = profileDebugLog; 143 | Width = width; 144 | Height = height; 145 | IsMaximized = isMaximized; 146 | } 147 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/HelperMethods.cs: -------------------------------------------------------------------------------- 1 | using log4net; 2 | using System; 3 | using System.IO; 4 | using System.Net; 5 | using System.Security.Cryptography; 6 | 7 | namespace AM2RLauncherLib; 8 | 9 | /// 10 | /// Class that has various Helper functions. Basically anything that could be used outside the Launcher resides here. 11 | /// 12 | public static class HelperMethods 13 | { 14 | // Load reference to logger 15 | /// 16 | /// Our log object, that handles logging the current execution to a file. 17 | /// 18 | private static readonly ILog log = Core.Log; 19 | 20 | // Thank you, Microsoft docs: https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories 21 | // Slightly modified by adding overwriteFiles bool, as we need to replace readme, music, etc. 22 | /// 23 | /// This copies the contents of a specified Directory recursively to another Directory. 24 | /// 25 | /// Full Path to the Directory that will be recursively copied. 26 | /// Full Path to the Directory you want to copy to. If the Directory does not exist, it will be created. 27 | /// Specify if Files should be overwritten or not 28 | /// Specify if you want to copy Sub-Directories as well. 29 | public static void DirectoryCopy(string sourceDirName, string destDirName, bool overwriteFiles = true, bool copySubDirs = true) 30 | { 31 | // Get the subdirectories for the specified directory. 32 | DirectoryInfo dir = new DirectoryInfo(sourceDirName); 33 | 34 | if (!dir.Exists) 35 | { 36 | throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDirName}"); 37 | } 38 | 39 | DirectoryInfo[] dirs = dir.GetDirectories(); 40 | 41 | // If the destination directory doesn't exist, create it. 42 | Directory.CreateDirectory(destDirName); 43 | 44 | // Get the files in the directory and copy them to the new location. 45 | FileInfo[] files = dir.GetFiles(); 46 | foreach (FileInfo file in files) 47 | { 48 | string tempPath = Path.Combine(destDirName, file.Name); 49 | file.CopyTo(tempPath, overwriteFiles); 50 | } 51 | 52 | // If copying subdirectories, copy them and their contents to new location. 53 | if (!copySubDirs) 54 | return; 55 | 56 | foreach (DirectoryInfo subDir in dirs) 57 | { 58 | string tempPath = Path.Combine(destDirName, subDir.Name); 59 | DirectoryCopy(subDir.FullName, tempPath, overwriteFiles); 60 | } 61 | 62 | } 63 | 64 | /// 65 | /// This is a custom method, that deletes a Directory. The reason this is used, instead of , 66 | /// is because this one sets the attributes of all files to be deletable, while does not do that on it's own. 67 | /// It's needed, because sometimes there are read-only files being generated, that would normally need more code in order to reset the attributes.
68 | /// Note, that this method acts recursively. 69 | ///
70 | /// The directory to delete. 71 | public static void DeleteDirectory(string targetDir) 72 | { 73 | if (!Directory.Exists(targetDir)) return; 74 | 75 | File.SetAttributes(targetDir, FileAttributes.Normal); 76 | 77 | foreach (string file in Directory.GetFiles(targetDir)) 78 | { 79 | File.SetAttributes(file, FileAttributes.Normal); 80 | File.Delete(file); 81 | } 82 | 83 | foreach (string dir in Directory.GetDirectories(targetDir)) 84 | { 85 | DeleteDirectory(dir); 86 | } 87 | 88 | Directory.Delete(targetDir, false); 89 | } 90 | 91 | /// 92 | /// Recursively lowercases all files and folders from a specified directory. 93 | /// 94 | /// The path to the directory whose contents should be lowercased. 95 | public static void LowercaseFolder(string directory) 96 | { 97 | DirectoryInfo dir = new DirectoryInfo(directory); 98 | 99 | foreach(var file in dir.GetFiles()) 100 | { 101 | if (file.Name == file.Name.ToLower()) continue; 102 | // Windows is dumb, thus we need to move in two trips 103 | file.MoveTo(file.DirectoryName + "/" + file.Name.ToLower() + "_"); 104 | string newPath = file.FullName.Substring(0, file.FullName.Length - 1); 105 | File.Delete(newPath); 106 | file.MoveTo(newPath); 107 | } 108 | 109 | foreach(var subDir in dir.GetDirectories()) 110 | { 111 | if (subDir.Name == subDir.Name.ToLower()) continue; 112 | // ReSharper disable once PossibleNullReferenceException - since this is a subdirectory, it always has a parent 113 | // Windows is dumb, thus we need to move in two trips 114 | subDir.MoveTo(subDir.Parent.FullName + "/" + subDir.Name.ToLower() + "_"); 115 | // -2 because after a moving operation, DirInfo already appends a / 116 | subDir.MoveTo(subDir.FullName.Substring(0, subDir.FullName.Length-2)); 117 | LowercaseFolder(subDir.FullName); 118 | } 119 | } 120 | 121 | /// 122 | /// Calculates an MD5 hash from a given file. 123 | /// 124 | /// Full Path to the file whose MD5 hash is supposed to be calculated. 125 | /// The MD5 hash as a , empty string if file does not exist. 126 | /*TODO: in the future we should use sha256, as both md5 and sha1 are unsafe. 127 | This however needs to wait, until we somehow can find a way to publish the windows launcher as .net core...*/ 128 | public static string CalculateMD5(string filename) 129 | { 130 | // Check if File exists first 131 | if (!File.Exists(filename)) 132 | return ""; 133 | 134 | using FileStream stream = File.OpenRead(filename); 135 | using MD5 md5 = MD5.Create(); 136 | byte[] hash = md5.ComputeHash(stream); 137 | return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant(); 138 | } 139 | 140 | /// 141 | /// Performs recursive rollover on a set of log files. 142 | /// 143 | /// The log file to begin the rollover from. 144 | /// The maximum amount of log files to retain. Default is 9, as that's the highest digit. 145 | //TODO: double check this method 146 | public static void RecursiveRollover(string logFile, int max = 9) 147 | { 148 | int index = 1; 149 | char endChar = logFile[logFile.Length - 1]; 150 | string fileName; 151 | 152 | // If not the original file, set the new index and get the new fileName. 153 | if (Char.IsNumber(endChar)) 154 | { 155 | index = Int32.Parse(endChar.ToString()) + 1; 156 | fileName = logFile.Remove(logFile.Length - 1) + index; 157 | } 158 | else // Otherwise, if the original file, just set fileName to log.txt.1. 159 | fileName = logFile + ".1"; 160 | 161 | // If new name already exists, run the rollover algorithm on it! 162 | if (File.Exists(fileName)) 163 | RecursiveRollover(fileName, max); 164 | 165 | //TODO: this can fail if one doesn't have permissions to move or delete the file 166 | // maybe fixed? 167 | try 168 | { 169 | // If index is less than max, rename file. 170 | if (index < max) 171 | File.Move(logFile, fileName); 172 | else // Otherwise, delete the file. 173 | File.Delete(logFile); 174 | } 175 | catch 176 | { 177 | //ignore 178 | } 179 | } 180 | 181 | /// 182 | /// Checks if we currently have an internet connection, by pinging GitHub. 183 | /// 184 | /// if we have internet, if not. 185 | public static bool IsConnectedToInternet() 186 | { 187 | log.Info("Checking internet connection..."); 188 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://github.com/site-map"); 189 | try 190 | { 191 | request.GetResponse(); 192 | } 193 | catch (WebException) 194 | { 195 | log.Info("Internet connection failed."); 196 | return false; 197 | } 198 | log.Info("Internet connection established!"); 199 | return true; 200 | 201 | // TODO: For some reason, using the below approach creates zombie process when checking for Xdelta on Linux 202 | // I have no idea why, but I also can't be bothered to troubleshoot why that is the case right now. 203 | // Until someone figures out why that is the case, and makes the below approach not create zombie processes 204 | // it will stay commented out and the slower above approach will be used instead. 205 | /*log.Info("Checking internet connection..."); 206 | try 207 | { 208 | PingReply pingReply = new Ping().Send("github.com"); 209 | if (pingReply?.Status == IPStatus.Success) 210 | { 211 | log.Info("Internet connection established!"); 212 | return true; 213 | } 214 | } 215 | catch { /* ignoring exceptions */ /*} 216 | log.Info("Internet connection failed."); 217 | return false;*/ 218 | } 219 | 220 | /// 221 | /// Gets and replaces "$NAME" with . 222 | /// 223 | /// The text to get 224 | /// The text to replace "$NAME" with. 225 | /// 226 | public static string GetText(string languageText, string replacementText = "") 227 | { 228 | return languageText.Replace("$NAME", replacementText); 229 | } 230 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs: -------------------------------------------------------------------------------- 1 | using AM2RLauncherLib; 2 | using AM2RLauncherLib.XML; 3 | using AM2RLauncher.Language; 4 | using Eto.Forms; 5 | using LibGit2Sharp; 6 | using System; 7 | using System.Configuration; 8 | using System.IO; 9 | using System.Text.RegularExpressions; 10 | using Configuration = System.Configuration.Configuration; 11 | 12 | namespace AM2RLauncher; 13 | 14 | public partial class MainForm : Form 15 | { 16 | /// 17 | /// Method that updates . 18 | /// 19 | /// The value that should be set to. 20 | /// The min value that should be set to. 21 | /// The max value that should be set to. 22 | private void UpdateProgressBar(int value, int min, int max) 23 | { 24 | Application.Instance.Invoke(() => 25 | { 26 | progressBar.MinValue = min; 27 | progressBar.MaxValue = max; 28 | progressBar.Value = value; 29 | }); 30 | } 31 | 32 | 33 | /// 34 | /// Method that updates with a min value of 0 and max value of 100. 35 | /// 36 | /// The value that should be set to. 37 | private void UpdateProgressBar(int value) 38 | { 39 | UpdateProgressBar(value, 0, 100); 40 | } 41 | 42 | /// 43 | /// Safety check function before accessing . 44 | /// 45 | /// if it is valid, if not. 46 | private bool IsProfileIndexValid() 47 | { 48 | return profileIndex != null; 49 | } 50 | 51 | /// 52 | /// This is just a helper method for the git commands in order to have a progress bar display for them. 53 | /// 54 | private bool TransferProgressHandlerMethod(TransferProgress transferProgress) 55 | { 56 | // Thank you random issue on the gitlib2sharp repo!!!! 57 | // Also tldr; rtfm 58 | if (isGitProcessGettingCancelled) return false; 59 | 60 | // This needs to be in an Invoke, in order to access the variables from the main thread 61 | // Otherwise this will throw a runtime exception 62 | Application.Instance.Invoke(() => 63 | { 64 | progressBar.MinValue = 0; 65 | progressBar.MaxValue = transferProgress.TotalObjects; 66 | if (currentGitObject >= transferProgress.ReceivedObjects) 67 | return; 68 | progressLabel.Text = Text.ProgressbarProgress + " " + transferProgress.ReceivedObjects + " (" + ((int)transferProgress.ReceivedBytes / 1000000) + "MB) / " + transferProgress.TotalObjects + " objects"; 69 | currentGitObject = transferProgress.ReceivedObjects; 70 | progressBar.Value = transferProgress.ReceivedObjects; 71 | }); 72 | 73 | return true; 74 | } 75 | 76 | /// 77 | /// Creates a single-file, zip-filtered file dialog. 78 | /// 79 | /// The title of the file dialog. 80 | /// The created file dialog. 81 | private OpenFileDialog GetSingleZipDialog(string title = "") 82 | { 83 | OpenFileDialog fileDialog = new OpenFileDialog 84 | { 85 | Directory = new Uri(CrossPlatformOperations.CurrentPath), 86 | MultiSelect = false, 87 | Title = title 88 | }; 89 | fileDialog.Filters.Add(new FileFilter(Text.ZipArchiveText, ".zip")); 90 | return fileDialog; 91 | } 92 | 93 | private void DisableProgressBar() 94 | { 95 | progressBar.Visible = false; 96 | progressBar.Value = 0; 97 | } 98 | 99 | private void EnableProgressBar() 100 | { 101 | progressBar.Visible = true; 102 | progressBar.Value = 0; 103 | } 104 | 105 | private void DisableProgressBarAndProgressLabel() 106 | { 107 | DisableProgressBar(); 108 | progressLabel.Visible = false; 109 | progressLabel.Text = ""; 110 | } 111 | 112 | private void EnableProgressBarAndLabel() 113 | { 114 | EnableProgressBar(); 115 | progressLabel.Visible = true; 116 | progressLabel.Text = ""; 117 | } 118 | 119 | /// 120 | /// Reads the Launcher config file on the current Platform and returns the value for . 121 | /// 122 | /// The property to get the value from. 123 | /// The value from as a string 124 | public static string ReadFromConfig(string property) 125 | { 126 | if (OS.IsWindows) 127 | { 128 | // We use the configuration manager in order to read `property` from the app.config and then return it 129 | ConnectionStringSettings appConfig = ConfigurationManager.ConnectionStrings[property]; 130 | if (appConfig == null) throw new ArgumentException("The property " + property + " could not be found."); 131 | string value = appConfig.ConnectionString; 132 | log.Info($"Reading {property} from config with value {value}."); 133 | return value; 134 | } 135 | if (OS.IsUnix) 136 | { 137 | string launcherConfigFilePath = CrossPlatformOperations.NixLauncherConfigFilePath; 138 | string launcherConfigPath = Path.GetDirectoryName(launcherConfigFilePath); 139 | XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML(); 140 | 141 | // If folder doesn't exist, create it and the config file 142 | Directory.CreateDirectory(launcherConfigPath); 143 | if (!File.Exists(launcherConfigFilePath)) 144 | File.WriteAllText(launcherConfigFilePath, Serializer.Serialize(launcherConfig)); 145 | 146 | // Deserialize the config xml into launcherConfig 147 | launcherConfig = Serializer.Deserialize(File.ReadAllText(launcherConfigFilePath)); 148 | 149 | // This uses the indexer, which means, we can use the variable in order to get the property. Look at LauncherConfigXML for more info 150 | string value = launcherConfig[property]?.ToString(); 151 | log.Info($"Reading {property} from config with value {value}."); 152 | return value; 153 | } 154 | 155 | log.Error(OS.Name + " has no config to read from!"); 156 | return null; 157 | } 158 | 159 | /// 160 | /// Writes to in the Launcher Config file. 161 | /// 162 | /// The property whose value you want to change. 163 | /// The value that will be written. 164 | public static void WriteToConfig(string property, object value) 165 | { 166 | log.Info($"Writing {value} of type {value.GetType()} to {property} to config."); 167 | if (OS.IsWindows) 168 | { 169 | // We use the configuration manager in order to read from the app.config, change the value and save it 170 | Configuration appConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); 171 | if (appConfig == null) 172 | throw new NullReferenceException("Could not find the Config file! Please make sure it exists!"); 173 | ConnectionStringsSection connectionStringsSection = (ConnectionStringsSection)appConfig.GetSection("connectionStrings"); 174 | if (connectionStringsSection?.ConnectionStrings[property]?.ConnectionString == null) 175 | throw new ArgumentException("The property " + property + " could not be found."); 176 | connectionStringsSection.ConnectionStrings[property].ConnectionString = value.ToString(); 177 | appConfig.Save(); 178 | ConfigurationManager.RefreshSection("connectionStrings"); 179 | } 180 | else if (OS.IsUnix) 181 | { 182 | string launcherConfigFilePath = CrossPlatformOperations.NixLauncherConfigFilePath; 183 | string launcherConfigPath = Path.GetDirectoryName(launcherConfigFilePath); 184 | XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML(); 185 | 186 | // If folder doesn't exist, create it and the config file 187 | Directory.CreateDirectory(launcherConfigPath); 188 | if (!File.Exists(launcherConfigFilePath)) 189 | File.WriteAllText(launcherConfigFilePath, Serializer.Serialize(launcherConfig)); 190 | 191 | // Deserialize the config xml into launcherConfig 192 | launcherConfig = Serializer.Deserialize(File.ReadAllText(launcherConfigFilePath)); 193 | 194 | // Uses indexer. Look at LauncherConfigXML for more info 195 | launcherConfig[property] = value; 196 | 197 | // Serialize back into the file 198 | File.WriteAllText(launcherConfigFilePath, Serializer.Serialize(launcherConfig)); 199 | } 200 | else 201 | log.Error(OS.Name + " has no config to write to!"); 202 | } 203 | 204 | /// 205 | /// When a Launcher update occurs that introduces new config properties, this method ensures that the old user config is copied over as much as possible. 206 | /// 207 | public static void CopyOldConfigToNewConfig() 208 | { 209 | if (OS.IsWindows) 210 | { 211 | string oldConfigPath = CrossPlatformOperations.LauncherName + ".oldCfg"; 212 | string newConfigPath = CrossPlatformOperations.LauncherName + ".config"; 213 | string oldConfigText = File.ReadAllText(oldConfigPath); 214 | string newConfigText = File.ReadAllText(newConfigPath); 215 | 216 | Regex settingRegex = new Regex(""); 217 | 218 | MatchCollection oldMatch = settingRegex.Matches(oldConfigText); 219 | MatchCollection newMatch = settingRegex.Matches(newConfigText); 220 | 221 | for (int i = 0; i < oldMatch.Count; i++) 222 | newConfigText = newConfigText.Replace(newMatch[i].Value, oldMatch[i].Value); 223 | 224 | File.WriteAllText(newConfigPath, newConfigText); 225 | 226 | } 227 | else if (OS.IsUnix) 228 | { 229 | string launcherConfigFilePath = CrossPlatformOperations.NixLauncherConfigFilePath; 230 | 231 | // For some reason deserializing and saving back again works, not exactly sure why, but I'll take it 232 | XML.LauncherConfigXML launcherConfig = Serializer.Deserialize(File.ReadAllText(launcherConfigFilePath)); 233 | File.WriteAllText(launcherConfigFilePath, Serializer.Serialize(launcherConfig)); 234 | } 235 | else 236 | log.Error(OS.Name + " has no config to transfer over!"); 237 | } 238 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/LauncherUpdater.cs: -------------------------------------------------------------------------------- 1 | using AM2RLauncherLib; 2 | using Eto.Forms; 3 | using log4net; 4 | using System; 5 | using System.Diagnostics; 6 | using System.IO; 7 | using System.IO.Compression; 8 | using System.Net; 9 | using System.Reflection; 10 | 11 | namespace AM2RLauncher; 12 | 13 | /// 14 | /// Class that checks for Updates and then updates the Launcher. 15 | /// 16 | //TODO: Mac support for auto updater in general 17 | public static class LauncherUpdater 18 | { 19 | // How often this was broken count: 8 20 | // Auto updating is fun! 21 | 22 | /// The Version that identifies this current release. 23 | private const string VERSION = Core.Version; 24 | 25 | /// The Path of the oldConfig. Only gets used Windows-only 26 | private static readonly string oldConfigPath = updatePath + "/" + CrossPlatformOperations.LauncherName + ".oldCfg"; 27 | 28 | /// The actual Path where the executable is stored, only used for updating. 29 | private static string updatePath 30 | { 31 | get 32 | { 33 | if (OS.IsWindows) 34 | return Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); 35 | if (OS.IsLinux) 36 | return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); 37 | if (OS.IsMac) 38 | // TODO: double check on mac if this is correct 39 | return Path.GetDirectoryName($"{AppDomain.CurrentDomain.BaseDirectory}../../../"); 40 | throw new NotSupportedException($"{OS.Name} does not have an update path!"); 41 | } 42 | } 43 | 44 | /// 45 | /// Our log object, that handles logging the current execution to a file. 46 | /// 47 | private static readonly ILog log = LogManager.GetLogger(typeof(LauncherUpdater)); 48 | 49 | /// 50 | /// Performs the entire AM2RLauncher update procedure. 51 | /// 52 | public static void Main() 53 | { 54 | #if NOAUTOUPDATE 55 | log.Info("On \"No auto update\" configuration, skipping auto update."); 56 | return; 57 | #endif 58 | 59 | log.Info("Running update check..."); 60 | 61 | // Update section 62 | 63 | // Clean old files that have been left 64 | if (File.Exists(CrossPlatformOperations.CurrentPath + "/AM2RLauncher.bak")) 65 | { 66 | log.Info("AM2RLauncher.bak detected. Removing file."); 67 | File.Delete(CrossPlatformOperations.CurrentPath + "/AM2RLauncher.bak"); 68 | } 69 | if (OS.IsWindows && File.Exists(oldConfigPath)) 70 | { 71 | log.Info(CrossPlatformOperations.LauncherName + ".oldCfg detected. Removing file."); 72 | File.Delete(oldConfigPath); 73 | } 74 | if (OS.IsWindows && Directory.Exists(updatePath + "/oldLib")) 75 | { 76 | log.Info("Old lib folder detected, removing folder."); 77 | Directory.Delete(updatePath + "/oldLib", true); 78 | } 79 | 80 | // Clean up old update libs 81 | if (OS.IsWindows && Directory.Exists(updatePath + "/lib")) 82 | { 83 | foreach (FileInfo file in new DirectoryInfo(updatePath + "/lib").GetFiles()) 84 | { 85 | if (!file.Name.EndsWith(".bak")) 86 | continue; 87 | log.Info("Old bak file detected, deleting " + file.FullName); 88 | file.Delete(); 89 | } 90 | 91 | // Do the same for each subdirectory 92 | foreach (DirectoryInfo dir in new DirectoryInfo(updatePath + "/lib").GetDirectories()) 93 | { 94 | foreach (FileInfo file in dir.GetFiles()) 95 | { 96 | if (!file.Name.EndsWith(".bak")) 97 | continue; 98 | log.Info("Old bak file detected, deleting " + file.FullName); 99 | file.Delete(); 100 | } 101 | } 102 | } 103 | 104 | // Check settings if autoUpdateLauncher is set to true 105 | bool autoUpdate = Boolean.Parse(MainForm.ReadFromConfig("AutoUpdateLauncher")); 106 | 107 | if (!autoUpdate) 108 | { 109 | log.Info("AutoUpdate Launcher set to false. Exiting update check."); 110 | return; 111 | } 112 | 113 | log.Info("AutoUpdate Launcher set to true!"); 114 | 115 | // This is supposed to fix the updater throwing an exception on windows 7 and earlier(?) 116 | // See this for information: https://stackoverflow.com/q/2859790 and https://stackoverflow.com/a/50977774 117 | if (OS.IsWindows) 118 | { 119 | ServicePointManager.Expect100Continue = true; 120 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; 121 | } 122 | 123 | HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest"); 124 | HttpWebResponse response; 125 | try 126 | { 127 | response = (HttpWebResponse)request.GetResponse(); 128 | } 129 | catch (WebException) 130 | { 131 | log.Error("WebException caught during version request! Displaying MessageBox."); 132 | MessageBox.Show(Language.Text.NoInternetConnection); 133 | return; 134 | } 135 | 136 | // The URL from above redirects to the latest version, which we extract from the new url 137 | Uri realUri = response.ResponseUri; 138 | string onlineVersion = realUri.AbsoluteUri.Substring(realUri.AbsoluteUri.LastIndexOf('/') + 1); 139 | bool isCurrentVersionOutdated = false; 140 | 141 | string[] localVersionArray = VERSION.Split('.'); 142 | string[] onlineVersionArray = onlineVersion.Split('.'); 143 | 144 | // compare the remote version to our local version 145 | for (int i = 0; i < localVersionArray.Length; i++) 146 | { 147 | int onlineNum = Int32.Parse(onlineVersionArray[i]); 148 | int localNum = Int32.Parse(localVersionArray[i]); 149 | if (onlineNum > localNum) 150 | { 151 | isCurrentVersionOutdated = true; 152 | break; 153 | } 154 | if (localNum > onlineNum) 155 | break; 156 | } 157 | 158 | log.Info((isCurrentVersionOutdated ? "Updating" : "Not Updating") + " from " + VERSION + " to " + onlineVersion); 159 | 160 | // No new update, exiting 161 | if (!isCurrentVersionOutdated) 162 | return; 163 | 164 | // For mac, we just show a message box that a new version is available, because I don't want to support it yet. 165 | // hardcoded string, since also temporarily until it gets supported one day :tm:. 166 | if (OS.IsMac) 167 | { 168 | MessageBox.Show("Your current version is outdated! The newest version is " + onlineVersion + ". " + 169 | "Please recompile AM2RLauncher again or disable auto-updating"); 170 | return; 171 | } 172 | 173 | log.Info("Current version (" + VERSION + ") is outdated! Initiating update for version " + onlineVersion + "."); 174 | 175 | string tmpUpdatePath = Path.GetTempPath() + "/AM2RLauncherTmpUpdate/"; 176 | string zipPath = tmpUpdatePath + "/launcher.zip"; 177 | 178 | // Clean tmpupdate & zippath 179 | if (Directory.Exists(tmpUpdatePath)) 180 | Directory.Delete(tmpUpdatePath, true); 181 | Directory.CreateDirectory(tmpUpdatePath); 182 | 183 | if (File.Exists(zipPath)) 184 | File.Delete(zipPath); 185 | 186 | // Download the new remote version 187 | try 188 | { 189 | using WebClient client = new WebClient(); 190 | string platformSuffix = ""; 191 | if (OS.IsWindows) platformSuffix = "_win"; 192 | else if (OS.IsLinux) platformSuffix = "_lin"; 193 | 194 | log.Info("Downloading https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip to " + zipPath + "."); 195 | 196 | client.DownloadFile("https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip", zipPath); 197 | 198 | log.Info("File successfully downloaded."); 199 | } 200 | catch (UnauthorizedAccessException) 201 | { 202 | log.Error("UnauthorizedAccessException caught! Displaying MessageBox."); 203 | MessageBox.Show(Language.Text.UnauthorizedAccessMessage); 204 | return; 205 | } 206 | 207 | ZipFile.ExtractToDirectory(zipPath, tmpUpdatePath); 208 | log.Info("Updates successfully extracted to " + tmpUpdatePath); 209 | 210 | // Delete the zip, as we won't need it anymore 211 | File.Delete(zipPath); 212 | 213 | // Windows won't let us replace files directly, but it will let us rename files. So we start renaming every file with a .bak suffix 214 | File.Move(updatePath + "/" + CrossPlatformOperations.LauncherName, updatePath + "/AM2RLauncher.bak"); 215 | if (OS.IsWindows) File.Move(updatePath + "/" + CrossPlatformOperations.LauncherName + ".config", updatePath + "/" + CrossPlatformOperations.LauncherName + ".oldCfg"); 216 | 217 | // Move everything from root tmpupdate to root updatePath 218 | foreach (FileInfo file in new DirectoryInfo(tmpUpdatePath).GetFiles()) 219 | { 220 | log.Info("Moving " + file.FullName + " to " + CrossPlatformOperations.CurrentPath + "/" + file.Name); 221 | if (File.Exists(updatePath + "/" + file.Name)) 222 | File.Delete(updatePath + "/" + file.Name); 223 | File.Move(file.FullName, updatePath + "/" + file.Name); 224 | } 225 | // For windows, the actual application is in "AM2RLauncher.dll". Which means, we need to update the lib folder as well. 226 | if (OS.IsWindows && Directory.Exists(updatePath + "/lib")) 227 | { 228 | // So, because Windows behavior is dumb... 229 | 230 | // Rename all files in lib to *.bak 231 | foreach (FileInfo file in new DirectoryInfo(updatePath + "/lib").GetFiles()) 232 | { 233 | log.Info("Moving " + file.FullName + " to " + file.Directory + "/" + file.Name + ".bak"); 234 | file.MoveTo(file.Directory + "/" + file.Name + ".bak"); 235 | } 236 | 237 | // Do the same for each sub directory 238 | foreach (DirectoryInfo dir in new DirectoryInfo(updatePath + "/lib").GetDirectories()) 239 | { 240 | foreach (FileInfo file in dir.GetFiles()) 241 | { 242 | log.Info("Moving " + file.FullName + " to " + file.Directory + "/" + file.Name + ".bak"); 243 | file.MoveTo(file.Directory + "/" + file.Name + ".bak"); 244 | } 245 | } 246 | 247 | // Yes, the above calls could be recursive. No, I can't be bothered to make them as such. 248 | // Finally, we put the new lib folder into tmpupdate path 249 | if (Directory.Exists(tmpUpdatePath + "lib")) 250 | { 251 | log.Info("Moving lib directory from '" + tmpUpdatePath + "' to current path"); 252 | HelperMethods.DirectoryCopy(tmpUpdatePath + "/lib", updatePath + "/lib"); 253 | } 254 | } 255 | 256 | // We did everything with the new update, it can now be deleted 257 | Directory.Delete(tmpUpdatePath, true); 258 | log.Info("Deleted temporary update path: '" + tmpUpdatePath + "'"); 259 | 260 | // Transfer config files 261 | MainForm.CopyOldConfigToNewConfig(); 262 | 263 | log.Info("Files extracted. Preparing to restart executable..."); 264 | if (OS.IsLinux) Process.Start("chmod", "+x " + updatePath + "/" + CrossPlatformOperations.LauncherName); 265 | 266 | // And finally we restart, and boot into the new file 267 | Process.Start(updatePath + "/" + CrossPlatformOperations.LauncherName); 268 | Environment.Exit(0); 269 | } 270 | } -------------------------------------------------------------------------------- /AM2RLauncher/distribution/linux/AM2RLauncher.appdata.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | io.github.am2r_community_developers.AM2RLauncher 5 | CC0-1.0 6 | GPL-3.0 7 | AM2RLauncher 8 | AM2R Community Developers 9 | A front-end for dealing with AM2R updates and mods 10 | Ein Programm für die Handhabung mit AM2R-Updates und Mods 11 | Un front-end para organizar y controlar las actualizaciones y mods de AM2R 12 | Une interface pour intéragir avec les mods et MAJ de AM2R 13 | Una front-end per gestire aggiornamenti e mod di AM2R 14 | Um front-end para gerenciar mods e atualizações do AM2R 15 | AM2Rの更新やMODを扱うためのフロントエンド 16 | Клиент для распоряжения обновлениями и модами для AM2R 17 | 一个负责管理 AM2R 的更新和 Mod 的前端 18 | 19 | Game 20 | 21 | 22 | 23 |

24 | AM2R (Another Metroid 2 Remake) is a fan-made remake of Metroid II: Return of Samus in the style of Metroid: Zero Mission. 25 | It was released in 2016 by DoctorM64 and his team, and very soon after, has received a DMCA letter from Nintendo.
26 | A few years after, multiple members of the community managed to reverse engineer the game and published unofficial updates, 27 | dubbed the AM2R-Community-Updates. Those fixed bugs and added new features like widescreen, added content planned by the original devs before 28 | they were struck down, and ported it to other platforms. 29 |

30 |

31 | AM2R (Another Metroid 2 Remake, Englisch für ein weiteres Metroid 2 Remake) ist ein von Fans entwickeltes Remake von 32 | Metroid II: Return of Samus im Stil von Metroid: Zero Mission. Es wurde 2016 von DoctorM64 und seinem Team veröffentlicht, 33 | erhielten aber kurz darauf rechtliche Klagen von Nintendo.
34 | Ein paar Jahre später gelang es mehreren Mitgliedern der Gemeinde, das Spiel zu rekonstruieren und inoffizielle Updates zu veröffentlichen, 35 | die AM2R-Community-Updates genannt werden. Diese behoben Fehler und fügten neue Funktionen wie Breitbild hinzu, 36 | Inhalte die von den ursprünglichen Entwicklern geplant waren, und portierten das Spiel auf andere Betriebsysteme. 37 |

38 |

39 | AM2R (Another Metroid 2 Remake, que significa Otro Remake de Metroid 2) es un remake del juego para la Game Boy 40 | Metroid II: Return of Samus hecho por fans, utilizando un estilo similar al de Metroid: Zero Mission para Game Boy Advance. 41 | Lanzado originalmente en 2016 por DoctorM64 y su equipo, al poco tiempo recibió un aviso por parte de Nintendo, indicando que infringía 42 | la Ley de Derechos de Autor de la Era Digital.
43 | Unos años más tarde, un grupo de miembros pertenecientes a la comunidad lograron reproducir el funcionamiento del juego mediante ingeniería inversa 44 | y consiguieron publicar actualizaciones no oficiales, apodadas AM2R-Community-Updates (Actualizaciones de la Comunidad de AM2R). 45 | Estas actualizaciones arreglaron algunos bugs y añadieron características nuevas como la posibilidad de jugar en pantalla ancha, 46 | así como otros contenidos extra que los desarrolladores originales tenían planeado lanzar antes de que les cerraran el proyecto. 47 | También han conseguido portear el juego a otros sistemas. 48 |

49 |

50 | AM2R (Another Metroid 2 Remake) est un remake fait par des fans de Metroid II: Return of Samus dans le style de Metroid: Zero Mission. 51 | Le jeu fut publié en 2016 par DoctorM64 et son équipe, et peu de temps après, reçut une lettre de DMCA de la part de Nintendo.
52 | Quelques années après, plusieurs membre de la communauté réussirent à faire une rétro-ingénierie du jeu et publières des MAJ non-officielles, 53 | appelées AM2R-Community-Updates. Elles corrigent des bugs et ajoutent de nouvelles fonctionalités comme un écran large, 54 | et ajoutent du contenu prévu par les développeurs originaux avant la termination, et incluent des ports pour de nouvelles plateformes. 55 |

56 |

57 | AM2R (Another Metroid 2 Remake) è un remake creato dai fan di Metroid II: Return of Samus nello stile di Metroid: Zero Mission. 58 | È stato rilasciato nel 2016 da DoctorM64 e il suo team, e poco dopo, ha ricevuto una lettera di DMCA da Nintendo.
59 | Qualche anno dopo, molteplici membri della comunità sono riusciti ad estrapolare il gioco e pubblicare aggiornamenti non ufficiali, 60 | rinominati Aggiornamenti della Comunità di AM2R. Questi hanno sistemato i bug e aggiunto funzionalità come il widescreen, 61 | aggiunto contenuti pianificati dagli sviluppatori originali prima di essere stati fermati, e portarli ad altre piattaforme. 62 |

63 |

64 | AM2R (Another Metroid 2 Remake) é um remake de Metroid II: Return of Samus no estilo de Metroid: Zero Mission criado por fans. 65 | Foi lançado em 2016 por DoctorM64 e sua equipe, porém foi retirado do ar após receber um aviso legal da Nintendo.
66 | Alguns anos depois, múltiplos membros da comunidade conseguiram realizar a engenharia reversa do projeto e publicaram atualizações não oficiais, 67 | chamadas de "Atualizações da Comunidade do AM2R". Essas atualizações consertaram bugs, adicionaram novas funcionalidades como Widescreen, 68 | adicionaram conteúdo planejado pelos desenvolvedores originais e portaram o jogo para outras plataformas. 69 |

70 |

71 | AM2R (Another Metroid 2 Remake) は『Metroid II: Return of Samus』をメトロイドゼロミッションのスタイルでリメイクしたファンメイドの作品です。
72 | DoctorM64と彼のチームによって2016年にリリースされたものの、すぐに任天堂から著作権侵害の警告を受けました。
73 | 数年後、コミュニティの複数のメンバーがゲームをリバースエンジニアリングすることに成功し、AM2R-Community-Updatesという非公式アップデートを公開しました。
74 | その内容は、バグの修正、ワイドスクリーンのような新しい機能の追加、元の開発者が手を引く前に計画したコンテンツの追加、そして他のプラットフォームへの移植です。 75 |

76 |

77 | AM2R(Another Metroid 2 Remake,意为另一个密特罗德2重制版)是以《密特罗德 零点任务》的游戏风格制作的《密特罗德II 萨姆斯的回归》的同人重制版。 78 | AM2R 最初由 DoctorM64 及其团队成员发布于 2016 年,并且在发布后不久收到了来自任天堂的 DMCA 邮件。
79 | 数年后,多位社区成员成功逆向工程了该游戏,并发布非官方更新版本,称之为“AM2R社区更新”(AM2R-Community-Updates)。 80 | 社区更新在修复 Bug 的同时也添加了诸如宽屏显示的新功能,并且添加了原开发者在 AM2R 被叫停前所计划实现的内容,还将 AM2R 移植到了其他平台上。 81 |

82 | 83 | 84 | 85 | 86 | 87 |

88 | The AM2RLauncher lets you conveniently play those Community-Updates, automatically receive updates, install AM2R mods and create 89 | APKs to play on your phone.
90 | No copyrighted files are shipped, you need to provide your own copy of AM2R_11! 91 |

92 |

93 | Mit dem AM2RLauncher ist es möglich diese Community-Updates, als auch AM2R-Mods, bequem zu installieren, automatisch Updates zu erhalten, 94 | und APKs für das Speielen auf dem Handy zu erstellen.
95 | Es werden keine urheberrechtlich geschützten Dateien mitgeliefert, es muss eine eigene Kopie von AM2R_11 bereitgestellt werden! 96 |

97 |

98 | El programa AM2RLauncher te permite jugar de forma fácil a esas Actualizaciones de la Comunidad, recibir actualizaciones automáticas, 99 | instalar mods para AM2R y crear archivos APK para poder jugar en tu móvil.
100 | ¡No contiene ningún archivo protegido con copyright, deberás proporcionar tu propia copia de AM2R_11! 101 |

102 |

103 | Le AM2RLauncher vous permet de jouer facilement à ces MAJ communautaires, recevoir les MAJ automatiquement, 104 | installer les mods d'AM2R et créer des APK pour jouer sur téléphone.
105 | Aucun fichier sous copyright n'est inclus, vous devez avoir votre propre archive de AM2R_11! 106 |

107 |

108 | L'AM2RLauncher ti permette convenientemente di giocare a questi Aggiornamenti della Comunità, ricevere aggiornamenti automaticamente, 109 | installare mod di AM2R e creare APK per giocare sul tuo cellulare.
110 | Nessun file con copyright viene distribuito, avrai bisogno di fornire la tua copia di AM2R_11! 111 |

112 |

113 | O AM2RLauncher permite que você use as Atualizações da Comunidade com facilidade, receba atualizações automaticamente, 114 | instale mods e crie APKs para jogar no seu celular.
115 | Nenhum arquivo sujeito a Copyright é incluído. Você precisa prover sua própria cópia do AM2R_11! 116 |

117 |

118 | AM2RLauncherは、これらのCommunity-Updateを便利に扱うためのもので、自動アップデート、AM2R用のMODのインストール、スマートフォンでプレイするためのAPKの作成、といった機能があります。
119 | なお著作権で保護されたファイルは含まれていないため、『AM2R_11.zip』ファイルを利用者自身が用意する必要があります。 120 |

121 |

122 | 使用 AM2R 启动器(AM2RLauncher)可以方便地游玩社区更新版本,自动获取更新,安装 AM2R 的 Mod,还可以创建 APK 以安装在手机上进行游玩。
123 | AM2R 启动器不会提供受版权保护的文件,需要用户自行提供 AM2R_11 的原始副本! 124 |

125 | 126 | 127 | 128 | 129 | 180 |
181 | io.github.am2r_community_developers.AM2RLauncher.desktop 182 | https://github.com/AM2R-Community-Developers/AM2RLauncher 183 | https://github.com/AM2R-Community-Developers/AM2RLauncher/issues 184 | https://github.com/AM2R-Community-Developers/AM2RLauncher/wiki/FAQ 185 | https://github.com/AM2R-Community-Developers/AM2RLauncher/wiki 186 | 187 | 188 | 189 | 190 | 191 | moderate 192 | moderate 193 | mild 194 | 195 | 196 | 197 | Initial screen of AM2RLauncher 198 | https://cdn.discordapp.com/attachments/509717926807601182/999676114492461056/unknown.png 199 | 200 | 201 |
202 | -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncher/MainForm/MainForm.StateMachine.cs: -------------------------------------------------------------------------------- 1 | using Eto.Drawing; 2 | using System; 3 | using System.Linq; 4 | using AM2RLauncherLib; 5 | using AM2RLauncherLib.XML; 6 | using AM2RLauncher.Language; 7 | 8 | namespace AM2RLauncher; 9 | 10 | /// 11 | /// Everything UI/Form state machine-related goes in here 12 | /// 13 | public partial class MainForm 14 | { 15 | 16 | /// 17 | /// An enum, that has possible states for the play button. 18 | /// 19 | public enum PlayButtonState 20 | { 21 | Download, 22 | Downloading, 23 | Select11, 24 | Install, 25 | Installing, 26 | Play, 27 | Playing 28 | } 29 | 30 | /// 31 | /// An enum, that has different states for . 32 | /// 33 | public enum ApkButtonState 34 | { 35 | Create, 36 | Creating 37 | } 38 | 39 | /// 40 | /// Updates , , , and according to the current conditions. 41 | /// 42 | private void UpdateStateMachine() 43 | { 44 | UpdatePlayState(); 45 | UpdateApkState(); 46 | UpdateProfileState(); 47 | UpdateModSettingsState(); 48 | } 49 | 50 | /// 51 | /// Determines current conditions and calls accordingly. 52 | /// 53 | private void UpdatePlayState() 54 | { 55 | // If we're downloading or installing, don't change anything 56 | if ((updateState == PlayButtonState.Downloading) || (updateState == PlayButtonState.Installing)) 57 | return; 58 | 59 | // If we're currently creating an APK, we disable the play button 60 | if (apkButtonState == ApkButtonState.Creating) 61 | { 62 | playButton.Enabled = false; 63 | return; 64 | } 65 | 66 | playButton.Enabled = true; 67 | // If PatchData isn't cloned, we still need to download 68 | if (!Profile.IsPatchDataCloned()) 69 | { 70 | SetPlayButtonState(PlayButtonState.Download); 71 | return; 72 | } 73 | 74 | // If 1.1 isn't installed, we still need to select it 75 | if (!Profile.Is11Installed()) 76 | { 77 | SetPlayButtonState(PlayButtonState.Select11); 78 | return; 79 | } 80 | 81 | var isProfileValid = IsProfileIndexValid(); 82 | // If current profile is installed, we're ready to play! 83 | if (isProfileValid && Profile.IsProfileInstalled(profileList[profileIndex.Value])) 84 | { 85 | SetPlayButtonState(PlayButtonState.Play); 86 | return; 87 | } 88 | // Otherwise, if profile is NOT installable, we delete the profile because we can't install it and therefore holds no value! 89 | if (isProfileValid && profileList[profileIndex.Value].Installable == false) 90 | { 91 | DeleteProfileAndAdjustLists(profileList[profileIndex.Value]); 92 | return; 93 | } 94 | 95 | // Otherwise, we still need to install. 96 | SetPlayButtonState(PlayButtonState.Install); 97 | } 98 | 99 | /// 100 | /// Determines current conditions and enables or disables accordingly. 101 | /// 102 | private void UpdateApkState() 103 | { 104 | // Safety check 105 | if (apkButton == null) 106 | return; 107 | 108 | // Our default values 109 | apkButton.Enabled = false; 110 | apkButton.ToolTip = Text.ApkButtonDisabledToolTip; 111 | 112 | // If profile supports Android and if we are NOT already creating an APK... 113 | if (!IsProfileIndexValid()) 114 | return; 115 | 116 | var profile = profileList[profileIndex.Value]; 117 | if (!profile.SupportsAndroid || !profile.Installable || apkButtonState != ApkButtonState.Create) 118 | return; 119 | 120 | // Switch status based on main button's state 121 | switch (updateState) 122 | { 123 | case PlayButtonState.Download: 124 | case PlayButtonState.Downloading: 125 | case PlayButtonState.Select11: 126 | case PlayButtonState.Installing: 127 | case PlayButtonState.Playing: return; 128 | 129 | case PlayButtonState.Install: 130 | case PlayButtonState.Play: apkButton.Enabled = true; apkButton.ToolTip = HelperMethods.GetText(Text.ApkButtonEnabledToolTip, profileDropDown?.SelectedKey ?? ""); break; 131 | } 132 | } 133 | 134 | /// 135 | /// Determines current conditions and enables or disables the and related controls accordingly. 136 | /// 137 | private void UpdateProfileState() 138 | { 139 | // Safety check 140 | if (profileDropDown == null) 141 | return; 142 | switch (updateState) 143 | { 144 | case PlayButtonState.Download: 145 | case PlayButtonState.Downloading: 146 | case PlayButtonState.Select11: 147 | case PlayButtonState.Installing: 148 | case PlayButtonState.Playing: profileDropDown.Enabled = false; break; 149 | 150 | case PlayButtonState.Install: 151 | case PlayButtonState.Play: profileDropDown.Enabled = true; break; 152 | 153 | } 154 | if (apkButtonState == ApkButtonState.Creating) profileDropDown.Enabled = false; 155 | 156 | Color col = profileDropDown.Enabled ? LauncherColors.Green : LauncherColors.Inactive; 157 | 158 | if (OS.IsWindows) 159 | profileDropDown.TextColor = col; 160 | profileAuthorLabel.TextColor = col; 161 | profileVersionLabel.TextColor = col; 162 | profileLabel.TextColor = col; 163 | } 164 | 165 | /// 166 | /// Determines current conditions and enables or disabled the mainPage controls accordingly. 167 | /// 168 | private void UpdateModSettingsState() 169 | { 170 | // Safety check 171 | if (modSettingsProfileDropDown == null || modSettingsProfileDropDown.DataStore.Any()) return; 172 | 173 | bool enabled = false; 174 | switch (updateState) 175 | { 176 | case PlayButtonState.Download: 177 | case PlayButtonState.Downloading: 178 | case PlayButtonState.Select11: 179 | case PlayButtonState.Installing: 180 | case PlayButtonState.Playing: enabled = false; break; 181 | 182 | case PlayButtonState.Install: 183 | case PlayButtonState.Play: enabled = true; break; 184 | } 185 | if (apkButtonState == ApkButtonState.Creating) enabled = false; 186 | 187 | string selectedProfileName = modSettingsProfileDropDown.SelectedKey; 188 | 189 | settingsProfileLabel.TextColor = LauncherColors.Green; 190 | modSettingsProfileDropDown.Enabled = enabled; 191 | desktopShortcutButton.Enabled = enabled; 192 | profileButton.Enabled = enabled; 193 | profileButton.ToolTip = HelperMethods.GetText(Text.OpenProfileFolderToolTip, selectedProfileName); 194 | saveButton.Enabled = enabled; 195 | saveButton.ToolTip = HelperMethods.GetText(Text.OpenSaveFolderToolTip, selectedProfileName); 196 | addModButton.Enabled = enabled; 197 | addModButton.ToolTip = Text.AddNewModToolTip; 198 | 199 | // Only enable these, when we're not on the community updates 200 | if (modSettingsProfileDropDown.SelectedIndex > 0) 201 | { 202 | updateModButton.Enabled = profileList[modSettingsProfileDropDown.SelectedIndex].Installable; 203 | updateModButton.ToolTip = HelperMethods.GetText(Text.UpdateModButtonToolTip, selectedProfileName); 204 | deleteModButton.Enabled = enabled; 205 | deleteModButton.ToolTip = HelperMethods.GetText(Text.DeleteModButtonToolTip, selectedProfileName); 206 | } 207 | 208 | 209 | Color col = enabled ? LauncherColors.Green : LauncherColors.Inactive; 210 | 211 | if (OS.IsWindows) 212 | modSettingsProfileDropDown.TextColor = col; 213 | 214 | settingsProfileLabel.TextColor = col; 215 | } 216 | 217 | /// 218 | /// Sets the global and then changes the state of accordingly. 219 | /// 220 | /// The state that should be set to. 221 | private void SetPlayButtonState(PlayButtonState state) 222 | { 223 | updateState = state; 224 | switch (updateState) 225 | { 226 | case PlayButtonState.Download: 227 | case PlayButtonState.Downloading: 228 | case PlayButtonState.Select11: 229 | case PlayButtonState.Install: 230 | case PlayButtonState.Play: playButton.Enabled = true; break; 231 | 232 | case PlayButtonState.Installing: 233 | case PlayButtonState.Playing: playButton.Enabled = false; break; 234 | } 235 | playButton.Text = GetPlayButtonText(); 236 | playButton.ToolTip = GetPlayButtonTooltip(); 237 | 238 | playButton.Invalidate(); 239 | 240 | UpdateModSettingsState(); 241 | } 242 | 243 | /// 244 | /// Sets the global and then changes the state of accordingly. 245 | /// 246 | /// The state that should be set to. 247 | private void SetApkButtonState(ApkButtonState state) 248 | { 249 | apkButtonState = state; 250 | switch (apkButtonState) 251 | { 252 | case ApkButtonState.Create: apkButton.Enabled = true; break; 253 | case ApkButtonState.Creating: apkButton.Enabled = false; break; 254 | } 255 | apkButton.Text = GetApkButtonText(); 256 | } 257 | 258 | /// 259 | /// This returns the text that should have depending on the global updateState. 260 | /// 261 | /// The text as a , or if the current State is invalid. 262 | private string GetPlayButtonText() 263 | { 264 | switch (updateState) 265 | { 266 | case PlayButtonState.Download: return Text.Download; 267 | case PlayButtonState.Downloading: return Text.Abort; 268 | case PlayButtonState.Select11: return Text.Select11; 269 | case PlayButtonState.Install: return Text.Install; 270 | case PlayButtonState.Installing: return Text.Installing; 271 | case PlayButtonState.Play: return Text.Play; 272 | case PlayButtonState.Playing: return Text.Playing; 273 | default: return null; 274 | } 275 | } 276 | 277 | /// 278 | /// This returns the tooltip that should have depending on the global updateState. 279 | /// 280 | /// The tooltip as a , or if the current State is invalid. 281 | private string GetPlayButtonTooltip() 282 | { 283 | string profileName = ((profileDropDown != null) && (profileDropDown.DataStore.Any())) ? profileDropDown.SelectedKey : ""; 284 | switch (updateState) 285 | { 286 | case PlayButtonState.Download: return Text.PlayButtonDownloadToolTip; 287 | case PlayButtonState.Downloading: return Text.PlayButtonDownloadToolTip; 288 | case PlayButtonState.Select11: return Text.PlayButtonSelect11ToolTip; 289 | case PlayButtonState.Install: return playButton.ToolTip = HelperMethods.GetText(Text.PlayButtonInstallToolTip, profileName); 290 | case PlayButtonState.Installing: return Text.PlayButtonInstallingToolTip; 291 | case PlayButtonState.Play: return HelperMethods.GetText(Text.PlayButtonPlayToolTip, profileName); 292 | case PlayButtonState.Playing: return Text.PlayButtonPlayingToolTip; 293 | default: return null; 294 | } 295 | } 296 | 297 | /// 298 | /// This returns the text that the apkButton should have depending on the global updateState. 299 | /// 300 | /// The text as a , or if the current State is invalid. 301 | private string GetApkButtonText() 302 | { 303 | switch (apkButtonState) 304 | { 305 | case ApkButtonState.Create: return Text.CreateAPK; 306 | case ApkButtonState.Creating: return Text.CreatingAPK; 307 | default: return null; 308 | } 309 | } 310 | 311 | /// 312 | /// Loads valid profile entries and reloads the necessary UI components. 313 | /// 314 | private void LoadProfilesAndAdjustLists() 315 | { 316 | // Reset loaded profiles 317 | profileList.Clear(); 318 | profileIndex = null; 319 | 320 | // Add profile names to the profileDropDown 321 | foreach (ProfileXML profile in Profile.LoadProfiles()) 322 | { 323 | // Archive version notes 324 | if (!profile.Installable) 325 | { 326 | if (profile.Name.Contains("Community Updates")) 327 | profile.ProfileNotes = Text.ArchiveNotesCommunityUpdates; 328 | else 329 | profile.ProfileNotes = Text.ArchiveNotesMods + "\n\n" + profile.ProfileNotes; 330 | } 331 | 332 | profileList.Add(profile); 333 | } 334 | 335 | // Read the value from the config 336 | string profIndexString = ReadFromConfig("ProfileIndex"); 337 | 338 | // Check if either no profile was found or the setting says that the last current profile didn't exist 339 | if (!profileDropDown.DataStore.Any()) 340 | profileIndex = null; 341 | else 342 | { 343 | // We know that profiles exist at this point, so we're going to point it to 0 instead so the following code doesn't fail 344 | if ((profIndexString == "null") || String.IsNullOrWhiteSpace(profIndexString)) 345 | profIndexString = "0"; 346 | 347 | // We parse from the settings, and check if profiles got deleted from the last time the launcher has been selected. if yes, we revert the last selection to 0; 348 | int intParseResult = Int32.Parse(profIndexString); 349 | profileIndex = intParseResult; 350 | if (profileIndex >= profileDropDown.DataStore.Count()) 351 | profileIndex = 0; 352 | profileDropDown.SelectedIndex = profileIndex.Value; 353 | } 354 | 355 | // Update stored profiles in the Profile Settings tab 356 | // TODO: For some reason this is needed, because if its removed, the selected index goes bonkers 357 | modSettingsProfileDropDown.SelectedIndex = profileDropDown.DataStore.Any() ? 0 : -1; 358 | 359 | log.Info("Reloading UI components after loading successful."); 360 | 361 | UpdateStateMachine(); 362 | } 363 | 364 | 365 | /// 366 | /// Deletes a profile and reloads the necessary UI components. 367 | /// 368 | /// The profile to delete. 369 | private void DeleteProfileAndAdjustLists(ProfileXML profile) 370 | { 371 | Profile.DeleteProfile(profile); 372 | LoadProfilesAndAdjustLists(); 373 | } 374 | 375 | private void ArchiveProfileAndAdjustLists(ProfileXML profile) 376 | { 377 | Profile.ArchiveProfile(profile); 378 | LoadProfilesAndAdjustLists(); 379 | } 380 | 381 | /// Enables and changes colors for and accordingly. 382 | private void EnableMirrorControlsAccordingly() 383 | { 384 | bool enabled = (bool)customMirrorCheck.Checked; 385 | customMirrorTextBox.Enabled = enabled; 386 | mirrorDropDown.Enabled = !enabled; 387 | // Not sure why the dropdown menu needs this hack, but the textBox does not. 388 | //TODO: eto feature request 389 | if (OS.IsWindows) 390 | mirrorDropDown.TextColor = mirrorDropDown.Enabled ? LauncherColors.Green : LauncherColors.Inactive; 391 | mirrorLabel.TextColor = !enabled ? LauncherColors.Green : LauncherColors.Inactive; 392 | } 393 | } -------------------------------------------------------------------------------- /AM2RLauncher/AM2RLauncherLib/CrossPlatformOperations.cs: -------------------------------------------------------------------------------- 1 | using log4net; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Reflection; 7 | 8 | namespace AM2RLauncherLib; 9 | 10 | /// 11 | /// Class that does operations that work cross-platform. 12 | /// 13 | public static class CrossPlatformOperations 14 | { 15 | /// 16 | /// The logger for , used to write any caught exceptions. 17 | /// 18 | private static readonly ILog log = LogManager.GetLogger(typeof(CrossPlatformOperations)); 19 | 20 | /// 21 | /// Name of the Launcher executable. 22 | /// 23 | public static readonly string LauncherName = AppDomain.CurrentDomain.FriendlyName; 24 | 25 | /// 26 | /// Path to the Home Folder. 27 | /// 28 | public static string Home => Environment.GetFolderPath(Environment.SpecialFolder.UserProfile, Environment.SpecialFolderOption.DoNotVerify); 29 | 30 | /// 31 | /// Config file path for *nix based systems.
32 | ///
33 | /// 34 | /// Linux: Will point to XDG_CONFIG_HOME/AM2RLauncher/config.xml
35 | /// Mac: Will point to ~/Library/Preferences/AM2RLauncher/config.xml.
36 | /// Anything else: 37 | ///
38 | public static string NixLauncherConfigFilePath 39 | { 40 | get 41 | { 42 | switch (OS.Name) 43 | { 44 | case "Linux": return $"{Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)}/AM2RLauncher/config.xml"; 45 | case "Mac": return $"{Home}/Library/Preferences/AM2RLauncher/config.xml"; 46 | default: return null; 47 | } 48 | } 49 | } 50 | 51 | /// 52 | /// Current Path where the Launcher Data is located. 53 | /// 54 | public static readonly string CurrentPath = GenerateCurrentPath(); 55 | 56 | /// 57 | /// Generates the mirror list, depending on the current Platform. 58 | /// 59 | /// A containing the mirror links. 60 | public static List GenerateMirrorList() 61 | { 62 | if (OS.IsWindows) 63 | { 64 | return new List 65 | { 66 | "https://github.com/AM2R-Community-Developers/AM2R-Autopatcher-Windows.git", 67 | "https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Windows.git" 68 | }; 69 | } 70 | if (OS.IsLinux) 71 | { 72 | return new List 73 | { 74 | "https://github.com/AM2R-Community-Developers/AM2R-Autopatcher-Linux.git", 75 | "https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Linux.git" 76 | }; 77 | } 78 | if (OS.IsMac) 79 | { 80 | return new List 81 | { 82 | "https://github.com/Miepee/AM2R-Autopatcher-Mac.git" 83 | //TODO: make mac official at some point:tm: and mirror it on gitlab 84 | }; 85 | 86 | } 87 | 88 | // Should never occur, but... 89 | log.Error($"{OS.Name} has no mirror lists!"); 90 | return new List(); 91 | } 92 | 93 | 94 | 95 | /// 96 | /// This open a website cross-platform. 97 | /// 98 | /// The URL of the website to be opened. 99 | public static void OpenURL(string url) 100 | { 101 | if (OS.IsWindows) Process.Start(url); 102 | else if (OS.IsLinux) Process.Start("xdg-open", url); 103 | else if (OS.IsMac) Process.Start("open", url); 104 | else log.Error($"{OS.Name} can't open URLs!"); 105 | } 106 | 107 | /// 108 | /// Opens in a file explorer. Creates the directory if it doesn't exist. 109 | /// 110 | /// Path to open. 111 | public static void OpenFolder(string path) 112 | { 113 | // We have to replace forward slashes with backslashes here on windows because explorer.exe is picky... 114 | // And on Nix systems, we want to replace ~ with its corresponding env var 115 | string realPath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\") 116 | : path.Replace("~", Home); 117 | 118 | log.Info($"Creating {realPath} if it did not exist before"); 119 | Directory.CreateDirectory(realPath); 120 | 121 | // Needs quotes otherwise paths with space wont open 122 | if (OS.IsWindows) 123 | // And we're using explorer.exe to prevent people from stuffing system commands in here wholesale. That would be bad. 124 | Process.Start("explorer.exe", $"\"{realPath}\""); 125 | else if (OS.IsLinux) 126 | Process.Start("xdg-open", $"\"{realPath}\""); 127 | else if (OS.IsMac) 128 | Process.Start("open", $"\"{realPath}\""); 129 | else 130 | log.Error($"{OS.Name} can't open folders!"); 131 | } 132 | 133 | /// 134 | /// Opens and selects it in a file explorer. 135 | /// Only selects on Windows and Mac, on Linux it just opens the folder. Does nothing if file doesn't exist. 136 | /// 137 | /// Path to open. 138 | public static void OpenFolderAndSelectFile(string path) 139 | { 140 | // We have to replace forward slashes with backslashes here on windows because explorer.exe is picky... 141 | // And on nix systems, we want to replace ~ with its corresponding env var 142 | string realPath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\") 143 | : path.Replace("~", Home); 144 | if (!File.Exists(realPath)) 145 | { 146 | log.Error($"{realPath}did not exist, operation to open its folder and select it was cancelled!"); 147 | return; 148 | } 149 | 150 | // Needs quotes otherwise paths with spaces wont open 151 | if (OS.IsWindows) 152 | // And we're using explorer.exe to prevent people from stuffing system commands in here wholesale. That would be bad. 153 | Process.Start("explorer.exe", $"/select, \"{realPath}\""); 154 | else if (OS.IsLinux) 155 | // Linux only opens the directory because opening and selecting a file requires some dbus stuff I don't want to bother for now 156 | // If anyone wants to do a PR, feel free to! 157 | Process.Start("xdg-open", $"\"{Path.GetDirectoryName(realPath)}\""); 158 | else if (OS.IsMac) 159 | Process.Start("open", $"-R \"{realPath}\""); 160 | else 161 | log.Error($"{OS.Name} can't open select files in file explorer!"); 162 | } 163 | 164 | /// 165 | /// Checks if command-line Java is installed and located in PATH. 166 | /// 167 | /// if it is installed, if not. 168 | public static bool IsJavaInstalled() 169 | { 170 | string process = ""; 171 | string arguments = ""; 172 | 173 | if (OS.IsWindows) 174 | { 175 | process = "cmd.exe"; 176 | arguments = "/C java -version"; 177 | } 178 | else if (OS.IsUnix) 179 | { 180 | process = "java"; 181 | arguments = "-version"; 182 | } 183 | else 184 | log.Error($"{OS.Name} has no java process/arguments"); 185 | 186 | ProcessStartInfo javaStart = new ProcessStartInfo 187 | { 188 | FileName = process, 189 | Arguments = arguments, 190 | UseShellExecute = false, 191 | CreateNoWindow = true 192 | }; 193 | 194 | 195 | Process java = new Process { StartInfo = javaStart }; 196 | 197 | // This is primarily for unix, but could be happening on windows as well 198 | // This gets triggered, if "java" cannot be found. 199 | try 200 | { 201 | java.Start(); 202 | java.WaitForExit(); 203 | } 204 | catch (System.ComponentModel.Win32Exception) 205 | { 206 | return false; 207 | } 208 | 209 | return java.ExitCode == 0; 210 | } 211 | 212 | /// 213 | /// Checks if command-line xdelta is installed and located in PATH. 214 | /// 215 | /// if it is installed, if not. 216 | public static bool CheckIfXdeltaIsInstalled() 217 | { 218 | const string process = "xdelta3"; 219 | const string arguments = "-V"; 220 | // TODO: for mac, we need to embed two xdelta binaries, for x64 and arm 221 | 222 | ProcessStartInfo xdeltaStart = new ProcessStartInfo 223 | { 224 | FileName = process, 225 | Arguments = arguments, 226 | UseShellExecute = false, 227 | CreateNoWindow = true 228 | }; 229 | 230 | 231 | Process xdelta = new Process { StartInfo = xdeltaStart }; 232 | 233 | try 234 | { 235 | xdelta.Start(); 236 | xdelta.WaitForExit(); 237 | } 238 | catch (System.ComponentModel.Win32Exception) 239 | { 240 | return false; 241 | } 242 | 243 | return xdelta.ExitCode == 0; 244 | } 245 | 246 | /// 247 | /// This applies an Xdelta Patch cross-platform. 248 | /// 249 | /// Full Path to the original file. 250 | /// Full Path to the Xdelta patch to apply. 251 | /// Full Path to the output file. 252 | /// This method assumes that Xdelta is already installed and located in PATH, except 253 | /// for Windows, where it uses the provided one. 254 | public static void ApplyXdeltaPatch(string originalFile, string patchFile, string outputFile) 255 | { 256 | // For *whatever reason* **sometimes** xdelta patching doesn't work, if outputFile = originalFile. So I'm fixing that here. 257 | string originalOutput = outputFile; 258 | if (originalFile == outputFile) 259 | outputFile += "_"; 260 | 261 | // The reason why currentPath is taken out of all paths, is because xdelta (windows?) breaks if it has non-ascii characters 262 | // So for users who have a russian username for example, this would just throw. 263 | // By replacing the currentPath and setting the working directory to where we want it to be, we ensure to only have our ascii characters. 264 | string arguments = $"-f -d -s \"{originalFile.Replace($"{CurrentPath}/", "")}\" \"{patchFile.Replace($"{CurrentPath}/", "")}\" \"{outputFile.Replace($"{CurrentPath}/", "")}\""; 265 | 266 | ProcessStartInfo parameters = new ProcessStartInfo 267 | { 268 | FileName = OS.IsWindows ? $"{CurrentPath}/PatchData/utilities/xdelta/xdelta3.exe" : "xdelta3", 269 | WorkingDirectory = $"{CurrentPath}", 270 | UseShellExecute = false, 271 | CreateNoWindow = true, 272 | Arguments = arguments 273 | }; 274 | 275 | using Process proc = new Process { StartInfo = parameters }; 276 | proc.Start(); 277 | proc.WaitForExit(); 278 | 279 | 280 | if ((originalOutput == outputFile) || !File.Exists(outputFile)) 281 | return; 282 | 283 | File.Delete(originalOutput); 284 | File.Move(outputFile, originalOutput); 285 | } 286 | 287 | /// 288 | /// Runs a Java jar file cross-platform. 289 | /// 290 | /// The arguments for the jar file. 291 | /// The working directory for the jar process. 292 | /// If then it will fallback to the users' Home directory 293 | /// This assumes that Java is installed and located in PATH. 294 | public static void RunJavaJar(string arguments = null, string workingDirectory = null) 295 | { 296 | workingDirectory ??= Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); 297 | string proc = "", 298 | javaArgs = ""; 299 | 300 | if (OS.IsWindows) 301 | { 302 | proc = "cmd"; 303 | javaArgs = "/C java -jar"; 304 | } 305 | else if (OS.IsUnix) 306 | { 307 | proc = "java"; 308 | javaArgs = "-jar"; 309 | } 310 | else 311 | log.Error($"{OS.Name} has no java process!"); 312 | 313 | ProcessStartInfo jarStart = new ProcessStartInfo 314 | { 315 | FileName = proc, 316 | Arguments = $"{javaArgs} {arguments}", 317 | WorkingDirectory = workingDirectory, 318 | UseShellExecute = false, 319 | CreateNoWindow = true 320 | }; 321 | 322 | Process jarProcess = new Process 323 | { 324 | StartInfo = jarStart 325 | }; 326 | 327 | jarProcess.Start(); 328 | jarProcess.WaitForExit(); 329 | } 330 | 331 | /// 332 | /// Figures out what the AM2RLauncher's should be.
333 | ///
334 | /// 335 | /// Determination is as follows: 336 | /// 337 | /// $AM2RLAUNCHERDATA environment variable is read and folders are recursively generated. 338 | /// The current OS is checked. For Windows, the path where the executable is located will be returned.
339 | /// For Linux, $XDG_DATA_HOME/AM2RLauncher will be returned. 340 | /// Should $XDG_DATA_HOME be empty, it will default to $HOME/.local/share.
341 | /// For Mac, HOME/Library/Application Support/AM2RLauncher" will be returned.
342 | /// The path where the executable is located will be returned. 343 | ///
344 | /// Should any errors occur, it falls down to the next step.
345 | /// The path where the AM2RLauncher can store its data. 346 | private static string GenerateCurrentPath() 347 | { 348 | // First, we check if the user has a custom AM2RLAUNCHERDATA env var 349 | string am2rLauncherDataEnvVar = Environment.GetEnvironmentVariable("AM2RLAUNCHERDATA"); 350 | if (!String.IsNullOrWhiteSpace(am2rLauncherDataEnvVar)) 351 | { 352 | try 353 | { 354 | // This will create the directories recursively if they don't exist 355 | Directory.CreateDirectory(am2rLauncherDataEnvVar); 356 | 357 | // Our env var is now set and directories exist 358 | log.Info($"CurrentPath is set to {am2rLauncherDataEnvVar}"); 359 | return am2rLauncherDataEnvVar; 360 | } 361 | catch (Exception ex) 362 | { 363 | log.Error($"There was an error with '{am2rLauncherDataEnvVar}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults."); 364 | } 365 | } 366 | 367 | if (OS.IsWindows) 368 | { 369 | log.Info("Using default Windows CurrentPath."); 370 | // Windows has the path where the exe is located as default 371 | return Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location); 372 | } 373 | else if (OS.IsLinux) 374 | { 375 | 376 | // Linux has the Path at XDG_DATA_HOME/AM2RLauncher 377 | string linuxPath = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData, Environment.SpecialFolderOption.DoNotVerify)}/AM2RLauncher"; 378 | 379 | try 380 | { 381 | Directory.CreateDirectory(linuxPath); 382 | log.Info($"CurrentPath is set to {linuxPath}"); 383 | return linuxPath; 384 | } 385 | catch (Exception ex) 386 | { 387 | log.Error($"There was an error with '{linuxPath}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults."); 388 | } 389 | } 390 | else if (OS.IsMac) 391 | { 392 | // Cannot use SpecialFolders here, as the current .NET version returns them wrongly. 393 | // Mac has the Path at HOME/Application Support/Library/AM2RLauncher 394 | string macPath = $"{Home}/Library/Application Support/AM2RLauncher"; 395 | try 396 | { 397 | Directory.CreateDirectory(macPath); 398 | log.Info("Using default Mac CurrentPath."); 399 | return macPath; 400 | } 401 | catch (Exception ex) 402 | { 403 | log.Error($"There was an error with '{macPath}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults."); 404 | } 405 | } 406 | else 407 | log.Error($"{OS.Name} has no current path!"); 408 | 409 | log.Info("Something went wrong, falling back to the default CurrentPath."); 410 | return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); 411 | } 412 | } --------------------------------------------------------------------------------