diff --git a/.github/workflows/DeployMkDocs.yml b/.github/workflows/DeployMkDocs.yml
index 8c16852c..1e364674 100644
--- a/.github/workflows/DeployMkDocs.yml
+++ b/.github/workflows/DeployMkDocs.yml
@@ -1,33 +1,29 @@
-name: DeployMkDocs
+name: MkDocs Build and Deploy
-# Controls when the action will run.
on:
- # Triggers the workflow on push on the master branch
- push:
- branches: [ master ]
-
- # Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
+ push:
+ branches: [ "main", "master" ]
+ paths:
+ - "mkdocs.yml"
+ - "docs/**"
+ pull_request:
+ branches: [ "main", "master" ]
+ paths:
+ - "mkdocs.yml"
+ - "docs/**"
-# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
- # This workflow contains a single job called "build"
build:
- # The type of runner that the job will run on
runs-on: ubuntu-latest
-
- # Steps represent a sequence of tasks that will be executed as part of the job
+ permissions:
+ contents: read
+ pages: write
+ id-token: write
steps:
-
- # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
- - name: Checkout Branch
- uses: actions/checkout@v2
-
- # Deploy MkDocs
- name: Deploy MkDocs
- # You may pin to the exact commit or the version.
- # uses: mhausenblas/mkdocs-deploy-gh-pages@66340182cb2a1a63f8a3783e3e2146b7d151a0bb
- uses: mhausenblas/mkdocs-deploy-gh-pages@master
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- REQUIREMENTS: ./docs/requirements.txt
+ uses: Reloaded-Project/devops-mkdocs@v1
+ with:
+ requirements: ./docs/requirements.txt
+ publish-to-pages: ${{ github.event_name == 'push' }}
+ checkout-current-repo: true
\ No newline at end of file
diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml
index 68c03e95..50721107 100644
--- a/.github/workflows/build-and-publish.yml
+++ b/.github/workflows/build-and-publish.yml
@@ -14,6 +14,7 @@ env:
TOOLS_PATH: ./source/Publish/Tools.zip
PUBLISH_CHOCO_PATH: ./source/Publish/Chocolatey
PUBLISH_INSTALLER_PATH: ./source/Publish/Installer/Setup.exe
+ PUBLISH_INSTALLER_STATIC_PATH: ./source/Publish/Installer-Static/Setup-Linux.exe
PUBLISH_CHANGELOG_PATH: ./source/Publish/Changelog.md
PUBLISH_PACKAGES_PATH: ./source/Publish/Packages
PUBLISH_RELEASE_FOLDER: ./source/Publish/Release
@@ -30,7 +31,7 @@ jobs:
shell: pwsh
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: 'recursive'
@@ -44,7 +45,7 @@ jobs:
# Required for C#10 features.
- name: Setup Node.js
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v4
with:
node-version: '14'
@@ -58,26 +59,28 @@ jobs:
run: npm install -g auto-changelog
- name: Setup Dotnet SDK (5.0)
- uses: actions/setup-dotnet@v2
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: '5.0.x'
- include-prerelease: true
- name: Setup Dotnet SDK (7.0)
- uses: actions/setup-dotnet@v2
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: '7.0.x'
- include-prerelease: true
- name: Setup Dotnet SDK (8.0)
- uses: actions/setup-dotnet@v2
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- include-prerelease: true
+
+ - name: Setup Dotnet SDK (9.0)
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '9.0.x'
- name: Get Dotnet Info
run: dotnet --info
-
+
- name: Publish
run: |
if ($env:IS_RELEASE -eq 'true')
@@ -94,9 +97,6 @@ jobs:
run: |
echo "$env:RELEASE_TAG" > "version.txt"
Compress-Archive -update "version.txt" "$env:PUBLISH_RELEASE_PATH"
-
- - name: Test
- run: dotnet test -c Release ./source/Reloaded.Mod.Loader.Tests/Reloaded.Mod.Loader.Tests.csproj
- name: Create NuGet Package Artifacts
run: |
@@ -121,7 +121,7 @@ jobs:
}
- name: Upload Chocolatey Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: Chocolatey Package
@@ -130,7 +130,7 @@ jobs:
retention-days: 0
- name: Upload Reloaded Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: Loader Build
@@ -139,16 +139,18 @@ jobs:
retention-days: 0
- name: Upload Installer Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: Installer
# A file, directory or wildcard pattern that describes what to upload
- path: ${{ env.PUBLISH_INSTALLER_PATH }}
+ path: |
+ ${{ env.PUBLISH_INSTALLER_PATH }}
+ ${{ env.PUBLISH_INSTALLER_STATIC_PATH }}
retention-days: 0
- name: Upload NuGet Artifacts
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: NuGet Packages
@@ -157,7 +159,7 @@ jobs:
retention-days: 0
- name: Upload Changelog Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: Changelog
@@ -166,16 +168,19 @@ jobs:
retention-days: 0
- name: Upload Tools Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: Tools
# A file, directory or wildcard pattern that describes what to upload
path: ${{ env.TOOLS_PATH }}
retention-days: 0
-
+
+ - name: Test
+ run: dotnet test -c Release ./source/Reloaded.Mod.Loader.Tests/Reloaded.Mod.Loader.Tests.csproj
+
- name: Upload to GitHub Releases
- uses: softprops/action-gh-release@v0.1.14
+ uses: softprops/action-gh-release@v2
if: env.IS_RELEASE == 'true'
with:
# Path to load note-worthy description of changes in release from
@@ -185,6 +190,7 @@ jobs:
${{ env.PUBLISH_RELEASE_FOLDER }}/*
${{ env.TOOLS_PATH }}
${{ env.PUBLISH_INSTALLER_PATH }}
+ ${{ env.PUBLISH_INSTALLER_STATIC_PATH }}
- name: Upload to NuGet (on Tag)
env:
diff --git a/.github/workflows/reloaded-utils-server.yml b/.github/workflows/reloaded-utils-server.yml
index 8acf8b27..18329188 100644
--- a/.github/workflows/reloaded-utils-server.yml
+++ b/.github/workflows/reloaded-utils-server.yml
@@ -46,24 +46,23 @@ jobs:
shell: pwsh
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: 'recursive'
- name: Setup .NET Core SDK (5.0)
- uses: actions/setup-dotnet@v1.8.2
+ uses: actions/setup-dotnet@v4
with:
dotnet-version: 5.0.x
- - name: Setup .NET Core SDK (7.0)
- uses: actions/setup-dotnet@v1.8.2
+ - name: Setup .NET Core SDK (8.0)
+ uses: actions/setup-dotnet@v4
with:
- dotnet-version: 7.0.x
- include-prerelease: true
+ dotnet-version: 8.0.x
- name: Setup Node.js
- uses: actions/setup-node@v2
+ uses: actions/setup-node@v4
with:
node-version: '14'
@@ -84,7 +83,7 @@ jobs:
run: ./source/Mods/Reloaded.Utils.Server/Publish.ps1 -ChangelogPath "$env:PUBLISH_CHANGELOG_PATH" -BuildR2R true
- name: Upload GitHub Release Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: GitHub Release
@@ -93,7 +92,7 @@ jobs:
${{ env.PUBLISH_GITHUB_PATH }}/*
- name: Upload GameBanana Release Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: GameBanana Release
@@ -102,7 +101,7 @@ jobs:
${{ env.PUBLISH_GAMEBANANA_PATH }}/*
- name: Upload NuGet Release Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: NuGet Release
@@ -111,7 +110,7 @@ jobs:
${{ env.PUBLISH_NUGET_PATH }}/*
- name: Upload Changelog Artifact
- uses: actions/upload-artifact@v2.2.4
+ uses: actions/upload-artifact@v4
with:
# Artifact name
name: Changelog
diff --git a/README.md b/README.md
index 715e5f93..0a30708e 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,8 @@ Made from the ground up **proudly** using the C# programming language.
For more information, please visit [the Reloaded-II website.](https://reloaded-project.github.io/Reloaded-II/)
+For installation instructions, see [Quick Start Guide.](https://reloaded-project.github.io/Reloaded-II/QuickStart)
+
## Contributions
Contributions to this project are **highly encouraged**.
diff --git a/changelog-template.hbs b/changelog-template.hbs
index 00f783c6..176885da 100644
--- a/changelog-template.hbs
+++ b/changelog-template.hbs
@@ -1,67 +1,46 @@
-# Changelog
+If you are updating from version less than 1.28, install the following first
-All notable changes to this project will be documented in this file.
+- [.NET 9 x64 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.0-windows-x64-installer)
+- [.NET 9 x86 Desktop Runtime](https://dotnet.microsoft.com/en-us/download/dotnet/thank-you/runtime-desktop-9.0.0-windows-x86-installer)
-The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
-and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+---------
-## Release (1.24) Highlights
+[Read and Discuss in a Browser](https://github.com/Reloaded-Project/Reloaded-II/discussions/473).
+[Previous Changelog](https://github.com/Reloaded-Project/Reloaded-II/releases/tag/1.28.3).
-This is a very small feature update mostly consisting of community PRs.
+# 1.28.6: Path/Location Warnings & Minor Theme Adjustment
-### Custom Controls for Mod Configurations
+- @dreamsyntax added [warnings for when Reloaded and/or the `Mods` folder are placed in OneDrive](https://github.com/Reloaded-Project/Reloaded-II/pull/518), or are placed in a special path.
-@jroweboy added the ability additional controls developers can use in their mods ![image](https://user-images.githubusercontent.com/952515/227748620-6b25b471-12c9-461b-9c8b-d387a716d54f.png);
+![image](https://github.com/user-attachments/assets/7ad44f76-8565-4684-9d03-33cb6fd1dde3)
-The 3 new custom controls are:
-```
-Slider: Horizontal Slider with an optional textbox to display the value
-FilePicker: Textbox with a button to open a `OpenFileDialog`.
-FolderPicker: Textbox with a button to open a custom `FolderSelectDialog`.
-```
+![image](https://github.com/user-attachments/assets/f9d3460c-1f6e-4bad-bf9f-a98040fbc8d5)
-Example usage:
+OneDrive you want to avoid for performance reasons.
+Special paths you want to avoid because some games don't support them well. Some games may fail to load custom assets out of the mod folders; Reloaded now warns about this indiscriminately.
-```csharp
-[DisplayName("Int Slider")]
-[Description("This is a int that uses a slider control similar to a volume control slider.")]
-[DefaultValue(100)]
-[SliderControlParams(/* Settings here */)]
-public int IntSlider { get; set; } = 100;
+- @dreamsyntax added [a warning for when a game is places in a path with a special folder](https://github.com/Reloaded-Project/Reloaded-II/pull/516).
-[DisplayName("Double Slider")]
-[Description("This is a double that uses a slider control without any frills.")]
-[DefaultValue(0.5)]
-[SliderControlParams(minimum: 0.0, maximum: 1.0)]
-public double DoubleSlider { get; set; } = 0.5;
+![image](https://github.com/user-attachments/assets/a3c0ff36-1231-4149-92f2-e96564ff7417)
-[DisplayName("File Picker")]
-[Description("This is a sample file picker.")]
-[DefaultValue("")]
-[FilePickerParams(title:"Choose a File to load from")]
-public string File { get; set; } = "";
+Same as above. Sometimes people download old games through 'mysterious ways', place them in a special location and try to mod them before trying even an unmodded game. This way they'll hopefully know that the path may be a problem.
-[DisplayName("Folder Picker")]
-[Description("Opens a file picker but locked to only allow folder selections.")]
-[DefaultValue("")]
-[FolderPickerParams(/* Settings here */)]
-public string Folder { get; set; } = "";
-```
+- @dreamsyntax made [a small adjustment on the UI colours](https://github.com/Reloaded-Project/Reloaded-II/issues/500).
-### Added Localization for Traditional Chinese (TW)
+Before:
-@EditorKos contributed a localization for `zh-TW` locale.
+![image](https://github.com/user-attachments/assets/753085f5-ad64-4a6b-81d5-d7e9990eca3c)
-### Fixes
+After:
-- Mod Packs can now use custom `ReleaseMetadataFileName(s)`. (thanks @jroweboy)
-- Mods from GameBanana which specify custom additional contributors/authors no longer breaks mod search.
- - GameBanana (probably unintentionally) made a change to how one of the fields is returned.
- - And that broke things... so I added a workaround.
- - I also need to update the cache server; please give it a moment after the update goes live.
-- Log files no longer incorrectly produce newlines for `Write()` (thanks @gurrenm3)
+![image](https://github.com/user-attachments/assets/378730d5-f92c-4525-a2b3-738db59060b1)
-## Complete Changes
+In the interest of accessibility, and people using monitors with interesting contrast ratios; the following adjustment above was made.
+The Reloaded theme was originally made on a cheap TN panel; however for some IPS displays and beyond, this change makes a lot of sense.
+
+------------------------------------
+
+## Complete Changes (Autogenerated)
{{#each releases}}
{{#if href}}
@@ -100,4 +79,17 @@ public string Folder { get; set; } = "";
{{#unless options.hideCredit}}
Reloaded changelogs are generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog) 😇.
-{{/unless}}
\ No newline at end of file
+{{/unless}}
+
+====
+
+## Available Downloads
+
+(Below this text, on GitHub)
+
+`Setup.exe`: This is a 1 click installer for Windows.
+`Setup-Linux.exe`: This is a version of `Setup.exe` for easier use in WINE / Proton. Use [Linux Setup Guide](https://reloaded-project.github.io/Reloaded-II/LinuxSetupGuideNew/).
+`Release.zip`: For people who prefer to install manually without an installer.
+`Tools.zip`: Tools for mod authors and developers.
+
+Other files are related to updates, you can ignore them.
\ No newline at end of file
diff --git a/docs/CreatingRelease.md b/docs/CreatingRelease.md
index 03bde073..8d287d6b 100644
--- a/docs/CreatingRelease.md
+++ b/docs/CreatingRelease.md
@@ -6,7 +6,9 @@
*even if you don't plan to ship updates*. Doing so will allow your mod to be included in [Mod Packs](./InstallingModPacks.md) [(How to Create Them)](./CreatingModPacks.md).
Before uploading a mod, you should first create a `Release`.
+
A `Release` consists of 2 files:
+
- Compressed version of your mod.
- JSON text file containing update information.
diff --git a/docs/FAQ.md b/docs/FAQ.md
index 1a5013f0..e1c4210d 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -82,7 +82,7 @@ Anything labelled as `accepted` is up for grabs by anyone, unless assigned to a
Just make sure to follow the coding style in the existing projects and try to write good code.
If the code isn't up to scruff, you might be asked to make changes.
-Instructions for building Reloaded, and some of the details of its internal workings are also [available as part of the documentation](./index.md#reloaded-for-potential-contributors)
+Instructions for building Reloaded, and some of the details of its internal workings are also [available as part of the documentation](./BuildingReloaded.md)
**Documentation, especially one that benefits the end user is just as valuable as any code.**
@@ -97,3 +97,11 @@ If your existing mod is tied with a specific mod loader (e.g. using some kind of
Please note that config files may be dropped in game directory for mods originally meant for ASI loaders, unless the mod explicitly checks DLL directory; you might need to make minor changes to your mods to account for that.
You will still need to generate a mod configuration as per the guide.
+
+## How can I Install Mods Manually?
+
+To install mods manually, simply extract a downloaded `zip` or `7z` file to the `Mods` folder.
+
+![Install Mod](./Images/InstallMod.gif)
+
+If there is no single folder inside the downloaded mod, create one yourself.
\ No newline at end of file
diff --git a/docs/Images/AddGameInReloaded-OnWine.png b/docs/Images/AddGameInReloaded-OnWine.png
new file mode 100644
index 00000000..d410f0f8
Binary files /dev/null and b/docs/Images/AddGameInReloaded-OnWine.png differ
diff --git a/docs/Images/DisableLaunchFromWine.png b/docs/Images/DisableLaunchFromWine.png
new file mode 100644
index 00000000..a6437838
Binary files /dev/null and b/docs/Images/DisableLaunchFromWine.png differ
diff --git a/docs/Images/FlatPak-Discover.png b/docs/Images/FlatPak-Discover.png
new file mode 100644
index 00000000..c07d49de
Binary files /dev/null and b/docs/Images/FlatPak-Discover.png differ
diff --git a/docs/Images/InstallModNew.gif b/docs/Images/InstallModNew.gif
new file mode 100644
index 00000000..2037b48c
Binary files /dev/null and b/docs/Images/InstallModNew.gif differ
diff --git a/docs/Images/Launch-Flatseal-3.png b/docs/Images/Launch-Flatseal-3.png
new file mode 100644
index 00000000..d8213757
Binary files /dev/null and b/docs/Images/Launch-Flatseal-3.png differ
diff --git a/docs/Images/Launch-Flatseal.jpg b/docs/Images/Launch-Flatseal.jpg
new file mode 100644
index 00000000..bd98755a
Binary files /dev/null and b/docs/Images/Launch-Flatseal.jpg differ
diff --git a/docs/Images/OnWine-InstallsToDesktop.png b/docs/Images/OnWine-InstallsToDesktop.png
new file mode 100644
index 00000000..01cfd793
Binary files /dev/null and b/docs/Images/OnWine-InstallsToDesktop.png differ
diff --git a/docs/Images/OpenWithProtontricks.png b/docs/Images/OpenWithProtontricks.png
new file mode 100644
index 00000000..2e645809
Binary files /dev/null and b/docs/Images/OpenWithProtontricks.png differ
diff --git a/docs/Images/ProtontricksLaunchGui.png b/docs/Images/ProtontricksLaunchGui.png
new file mode 100644
index 00000000..609a087b
Binary files /dev/null and b/docs/Images/ProtontricksLaunchGui.png differ
diff --git a/docs/LinuxSetupGuide.md b/docs/LinuxSetupGuide.md
index aad9c776..7c198e47 100644
--- a/docs/LinuxSetupGuide.md
+++ b/docs/LinuxSetupGuide.md
@@ -1,4 +1,11 @@
-# Linux Setup Guide
+# Linux Setup Guide (Legacy)
+
+!!! info "This is the *legacy* setup guide."
+
+ This shows you how to run Reloaded in a setup that involves running the launcher via Wine
+ and games via Wine/Proton. This is a bit more flexible but can be a hassle to set up.
+
+ For a more streamlined guide, see [Linux Setup Guide (New)](LinuxSetupGuideNew.md).
!!! help "Help Needed"
@@ -35,7 +42,7 @@ You can then download the Reloaded Installer (`Setup.exe`) [from the downloads p
If the installer has issues, you can try running it without GUI `wine Setup.exe --nogui`.
-!!! note
+!!! note
If you have the native version of .NET installed on your machine, it is possible that in some
cases the native version might be executed by Wine as opposed to the installed Windows version.
@@ -135,7 +142,7 @@ export WINEPREFIX="/home//.local/share/Steam/steamapps/compatdata
wine Setup.exe --dependenciesOnly
## Create Symbolic Link for Mod Loader Settings.
-ln -s "/home//.wine/drive_c/users//AppData/Roaming/Reloaded-Mod-Loader-II/" "/home//.local/share/Steam/steamapps/compatdata//pfx/drive_c/users/steamuser/AppData/Roaming/Reloaded-Mod-Loader-II/"
+ln -s "/home//.wine/drive_c/users//AppData/Roaming/Reloaded-Mod-Loader-II" "/home//.local/share/Steam/steamapps/compatdata//pfx/drive_c/users/steamuser/AppData/Roaming/Reloaded-Mod-Loader-II"
```
Once you are done, launch the Reloaded launcher and [Deploy ASI Loader](#using-asi-loader).
diff --git a/docs/LinuxSetupGuideNew.md b/docs/LinuxSetupGuideNew.md
new file mode 100644
index 00000000..0df55e45
--- /dev/null
+++ b/docs/LinuxSetupGuideNew.md
@@ -0,0 +1,93 @@
+# Linux Setup Guide
+
+!!! info "This is a streamlined guide for setting up a single game via Steam."
+
+ This will help you install 1 instance of Reloaded per game.
+ The steps try to be as minimal as possible.
+
+ This is a generic guide for any game. For more specific info, consider looking up
+ a ***game specific guide***.
+
+## Install and Run your game via Steam
+
+If you have a Steam game, install it and run it at least once.
+
+If you have a non-Steam game, add it to the Steam launcher and launch it from Steam first.
+
+!!! info "This is required for [Protontricks] to discover the game."
+
+## Install Protontricks
+
+If you are on a ***Steam Deck*** or have ***Flatpak*** pre-installed on your Linux distribution,
+use the [Installing Protontricks via FlatPak][protontricks-flatpak] section.
+
+Otherwise refer to the [Protontricks] documentation for installation info.
+
+## Install Reloaded-II via Protontricks
+
+Download [Setup-Linux.exe] (Direct Link) from Reloaded's releases section.
+
+Then start it via Protontricks. ***Right-click*** the EXE in your file browser and select `Open with Protontricks Launcher`
+from the context menu.
+
+![OpenWithProtontricks](./Images/OpenWithProtontricks.png)
+
+!!! note "You may need to look into the `Open With` menu."
+
+And select your game from the list:
+
+![OpenWithProtontricks](./Images/ProtontricksLaunchGui.png)
+
+This will install to your desktop:
+
+![OnWine-InstallsToDesktop](./Images/OnWine-InstallsToDesktop.png)
+
+You can then start the game via the shortcut on your desktop, or from the start menu.
+
+!!! note "The Linux installer does not have a GUI"
+
+ Installation may take a minute, please be patient.
+
+ As an approximation, the installer will download 140MB of files, install 4 runtimes and Reloaded.
+ This usually takes 10-30 seconds and is mostly dependent on internet speed.
+
+## Add a Game
+
+!!! info "Add your game to Steam"
+
+- For most Steam installs this will be `Z:\home\\.local\share\Steam\steamapps\common\`.
+- For ***Steam Deck*** users with game installed in SDCard this may be `E:\steamapps\common\`.
+
+![AddGameInReloaded](./Images/AddGameInReloaded-OnWine.png)
+
+## Launching Reloaded-II
+
+Installing via `Protontricks` should have created a shortcut on your desktop.
+Provided you don't move the game folder or Reloaded folder, this shortcut should 'just work'.
+
+You are done. See [Quick Start] for further 'Getting Started' steps.
+
+## [Optional] Starting Reloaded with your Game via Steam
+
+If you wish to auto-inject Reloaded while starting your game via Steam without
+having to go through the launcher, try using the [Using ASI Loader](./LinuxSetupGuide.md#using-asi-loader) step of the legacy setup guide.
+
+-----------------------------
+
+## Notes
+
+!!! note "You can install Reloaded-II via regular `Wine`"
+
+ However it's recommended you install via [Protontricks] if you plan to run your
+ game via Proton. It will make your life much easier; as installing in Wine and running
+ via Proton involves additional steps, outlined in the [Legacy Install Guide](./LinuxSetupGuide.md).
+
+## Credits
+
+- `Deck Screenshots`: [rudiger]
+
+[rudiger]: https://x.com/rudiger__tw
+[Protontricks]: https://github.com/Matoking/protontricks
+[Setup-Linux.exe]: https://github.com/Reloaded-Project/Reloaded-II/releases/latest/download/Setup-Linux.exe
+[protontricks-flatpak]: ./LinuxSetupGuideNewExtra.md#installing-protontricks-via-flatpak
+[Quick Start]: ./QuickStart.md
diff --git a/docs/LinuxSetupGuideNewExtra.md b/docs/LinuxSetupGuideNewExtra.md
new file mode 100644
index 00000000..5b541605
--- /dev/null
+++ b/docs/LinuxSetupGuideNewExtra.md
@@ -0,0 +1,56 @@
+# Extra
+
+!!! info "This guide contains extra tips for setting up Reloaded on Linux."
+
+## Installing Protontricks via FlatPak
+
+!!! info "Includes any distribution where FlatPak is available."
+
+To insall Protontricks via FlatPak, we will need [Flatseal] and [Protontricks].
+
+- [Flatseal] will allow the sandboxed [Protontricks] to see alternative Steam library locations.
+- [Protontricks] will let you run Reloaded inside the game's sandbox.
+
+These can be installed via a FlatPak supporting Package Manager such as [Discover]:
+
+![Discover](./Images/FlatPak-Discover.png)
+
+After installing, first launch [Flatseal]:
+
+![Flatseal](./Images/Launch-Flatseal.jpg)
+
+Scroll through the left column of apps in [Flatseal] until you're able to find and click on "[Protontricks]". This will display a configuration list in the right pane:
+
+![Flatseal](./Images/Launch-Flatseal-3.png)
+
+Scroll down to the "Filesystem" section:
+
+1. Enable `All system files`.
+
+Close Flatseal.
+
+Lastly add an alias so `protontricks` can be executed from the terminal:
+
+```bash
+echo "alias protontricks='flatpak run com.github.Matoking.protontricks'" >> ~/.bashrc
+echo "alias protontricks-launch='flatpak run --command=protontricks-launch com.github.Matoking.protontricks'" >> ~/.bashrc
+```
+
+Paste these lines into a `terminal` window, and press `enter`.
+
+!!! note "We enabled full filesystem access for simplicity"
+
+ You can be more granular if you know what you're doing, but for simplicity
+ we've simply enabled full filesystem access.
+
+ The installer requires access to `/home` and protontricks needs access to Steam library folders.
+
+## Credits
+
+- `Deck Screenshots`: [rudiger]
+
+[rudiger]: https://x.com/rudiger__tw
+[Discover]: https://apps.kde.org/en-gb/discover/
+[Flatseal]: https://github.com/tchx84/Flatseal
+[Protontricks]: https://github.com/Matoking/protontricks
+[Setup-Linux.exe]: https://github.com/Reloaded-Project/Reloaded-II/releases/latest/download/Setup-Linux.exe
diff --git a/docs/ModTemplate.md b/docs/ModTemplate.md
index 2ccb2d3b..d41a3f8a 100644
--- a/docs/ModTemplate.md
+++ b/docs/ModTemplate.md
@@ -36,7 +36,7 @@ A delta update is an update that only requires the user to download the code and
./Publish.ps1 -MakeDelta true -UseNuGetDelta true -NuGetPackageId reloaded.sharedlib.hooks -NuGetFeedUrl http://packages.sewer56.moe:5000/v3/index.json
```
-See [Delta Updates](./PublishingMods/#delta-updates) on more information about the topic.
+See [Delta Updates](./CreatingRelease.md#add-delta-update) on more information about the topic.
### Publishing as ReadyToRun
@@ -71,6 +71,10 @@ You can read more about R2R in the following web resources:
Incorrect use of trimming *can* and *will* break your mods. When using trimming you should test your mods thoroughly.
+!!! warning
+
+ You may need to add [Microsoft.NET.ILLink.Tasks](https://www.nuget.org/packages/Microsoft.NET.ILLink.Tasks) for this to work when running .NET SDK 8 or newer.
+
*Assembly trimming* allows you to remove unused code from your mods (and their dependencies), often significantly shrinking the size of the generated DLLs. This in turn improves load times, download size and runtime memory use. At the time of writing, the Reloaded Loader itself and most official & creator made mods use trimming.
### Testing Trimming
@@ -305,4 +309,4 @@ public override void Migrate(string oldDirectory, string newDirectory)
}
```
-This process can also be used to handle migration for other config modifications such as when `TryRunCustomConfiguration() == true`.
\ No newline at end of file
+This process can also be used to handle migration for other config modifications such as when `TryRunCustomConfiguration() == true`.
diff --git a/docs/QuickStart.md b/docs/QuickStart.md
index a5472289..b8c6e982 100644
--- a/docs/QuickStart.md
+++ b/docs/QuickStart.md
@@ -1,6 +1,18 @@
# Quick Start
+## Download Reloaded
+
+First you need to download Reloaded itself:
+
+- ***Windows***: [Run the Installer][windows-installer].
+
+- ***Linux***: Use the [Linux Setup Guide][linux-setup-guide].
+ - Come back once you're booted into Reloaded launcher.
+
+The program will install into your desktop by default.
+
## Add an Application
+
First step to getting started with Reloaded is to add an Application you'll be modifying.
This can be found on the bottom left corner of the launcher, with the `+` button.
@@ -10,12 +22,13 @@ This can be found on the bottom left corner of the launcher, with the `+` button
Make sure to add the App and not the app's launcher.
-## Extract Mods
-To install mods, simply extract a downloaded `zip` or `7z` file to the `Mods` folder.
+## Install a Mod
+
+Mods can be installed by dragging them over the Reloaded window.
-![Install Mod](./Images/InstallMod.gif)
+![Add An Application 2](./Images/InstallModNew.gif)
-If there is no single folder inside the downloaded mod, create one yourself.
+If this doesn't work for you, you can also [install them manually](./FAQ.md#how-can-i-install-mods-manually).
## Configure Mods
@@ -41,3 +54,7 @@ Reloaded uses `.exe` name to determine which mods should automatically be assign
If a mod does not show in the application, click the 3 gear button (`Manage Mods`).
From there, select the mod that you have just extracted from the list and check your game on the list below.
+
+
+[windows-installer]: https://github.com/Reloaded-Project/Reloaded-II/releases/latest/download/Setup.exe
+[linux-setup-guide]: ./LinuxSetupGuideNew.md
\ No newline at end of file
diff --git a/docs/index.md b/docs/index.md
index a03a7509..6cfc24fe 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -22,6 +22,8 @@ hide:
It is an ***extensible*** and ***modular*** framework that allows you to create your own mods for any game.
+For installation instructions, see [Quick Start Guide](./QuickStart.md).
+
## Mod Loader
@@ -179,7 +181,7 @@ It is an ***extensible*** and ***modular*** framework that allows you to create
- Install Reloaded & Runtimes with a single click.
+ Install Reloaded & Runtimes with a single click.
Very fast, completes in under 30 seconds.
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 3d336bd0..354af684 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1 +1,3 @@
-mkdocs-redirects
\ No newline at end of file
+mkdocs-redirects
+mkdocs-exclude-unused-files
+mkdocs-exclude
\ No newline at end of file
diff --git a/mkdocs.yml b/mkdocs.yml
index 6ae55744..bdddf27f 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -16,7 +16,11 @@ markdown_extensions:
- tables
- pymdownx.details
- pymdownx.highlight
- - pymdownx.superfences
+ - pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tasklist
- def_list
- meta
@@ -26,8 +30,15 @@ markdown_extensions:
- pymdownx.tabbed:
alternate_style: true
- pymdownx.emoji:
- emoji_index: !!python/name:materialx.emoji.twemoji
- emoji_generator: !!python/name:materialx.emoji.to_svg
+ emoji_index: !!python/name:material.extensions.emoji.twemoji
+ emoji_generator: !!python/name:material.extensions.emoji.to_svg
+
+theme:
+ name: material
+ palette:
+ scheme: reloaded-slate
+ features:
+ - navigation.instant
extra_css:
- Custom/Stylesheets/extra.css
@@ -41,6 +52,18 @@ plugins:
'PublishingMods.md': 'EnablingUpdateSupport.md'
'InterModCommunication.md': 'DependencyInjection_HowItWork.md'
'DependencyInjection.md': 'DependencyInjection_HowItWork.md'
+ - exclude-unused-files:
+ file_types_to_check: [ "psd", "7z", "kra" ]
+ file_types_override_mode: append
+ enabled: true
+ - exclude:
+ # Exclude the Theme's own files.
+ glob:
+ - Reloaded/Pages/*
+ - Reloaded/docs/*
+ - Reloaded/Readme.md
+ - Reloaded/LICENSE
+ - Reloaded/mkdocs.yml
theme:
name: material
@@ -56,7 +79,10 @@ nav:
- For Users:
- Quick Start: QuickStart.md
- Frequently Asked Questions: FAQ.md
- - Linux Setup Guide: LinuxSetupGuide.md
+ - Linux Guides:
+ - Linux Setup Guide: LinuxSetupGuideNew.md
+ - Linux Setup Guide (Extra): LinuxSetupGuideNewExtra.md
+ - Linux Setup Guide (Legacy): LinuxSetupGuide.md
- Installing Mod Packs: InstallingModPacks.md
- Advanced:
- NuGet Sources: NuGetSources.md
diff --git a/source/Mods/Reloaded.Utils.Server/Reloaded.Utils.Server.csproj b/source/Mods/Reloaded.Utils.Server/Reloaded.Utils.Server.csproj
index 51698964..2ab9abfa 100644
--- a/source/Mods/Reloaded.Utils.Server/Reloaded.Utils.Server.csproj
+++ b/source/Mods/Reloaded.Utils.Server/Reloaded.Utils.Server.csproj
@@ -1,10 +1,10 @@
- net7.0-windows
+ net8.0-windows
false
true
- 11.0
+ 12.0
enable
True
$(RELOADEDIIMODS)/Reloaded.Utils.Server
@@ -51,6 +51,10 @@
+
+
+
+
diff --git a/source/Packages/dll_syringe.Net.Sys.0.16.0.nupkg b/source/Packages/dll_syringe.Net.Sys.0.16.0.nupkg
new file mode 100644
index 00000000..bfe83f4c
Binary files /dev/null and b/source/Packages/dll_syringe.Net.Sys.0.16.0.nupkg differ
diff --git a/source/Publish-Settings/Include-Regexes.txt b/source/Publish-Settings/Include-Regexes.txt
index 22c47fec..e240b3fa 100644
--- a/source/Publish-Settings/Include-Regexes.txt
+++ b/source/Publish-Settings/Include-Regexes.txt
@@ -1,11 +1,5 @@
^Theme[\\\/]Default*
^Theme[\\\/]Halogen*
^Theme[\\\/]Helpers*
-^Assets[\\\/]Languages[\\\/]en-GB\.xaml
-^Assets[\\\/]Languages[\\\/]es-ES\.xaml
-^Assets[\\\/]Languages[\\\/]nl-NL\.xaml
-^Assets[\\\/]Languages[\\\/]pirate\.xaml
-^Assets[\\\/]Languages[\\\/]pt-PT\.xaml
-^Assets[\\\/]Languages[\\\/]ru-RU\.xaml
-^Assets[\\\/]Languages[\\\/]uwu\.xaml
+^Assets[\\\/]Languages[\\\/].*\.xaml
^version\.txt
\ No newline at end of file
diff --git a/source/Publish.ps1 b/source/Publish.ps1
index 03d7c7d4..5fb3a56d 100644
--- a/source/Publish.ps1
+++ b/source/Publish.ps1
@@ -35,9 +35,9 @@ $chocoToolsPath = "$chocoPath/tools"
# Project Paths
$bootstrapperPath = "Reloaded.Mod.Loader.Bootstrapper/Reloaded.Mod.Bootstrapper.vcxproj"
$installerProjectPath = "./Reloaded.Mod.Installer/Reloaded.Mod.Installer.csproj"
+$installerCliProjectPath = "./Reloaded.Mod.Installer.Cli/Reloaded.Mod.Installer.Cli.csproj"
$launcherProjectPath = "Reloaded.Mod.Launcher/Reloaded.Mod.Launcher.csproj"
$loaderProjectPath = "Reloaded.Mod.Loader/Reloaded.Mod.Loader.csproj"
-$addressDumperProjectPath = "Reloaded.Mod.Launcher.Kernel32AddressDumper/Reloaded.Mod.Launcher.Kernel32AddressDumper.csproj"
$templateProjectPath = "Reloaded.Mod.Template/Reloaded.Mod.Template.NuGet.csproj"
$communityProjectPath = "Tools/Reloaded.Community.Tool/Reloaded.Community.Tool.csproj"
@@ -48,6 +48,7 @@ $publisherProjectPath = "Tools/Reloaded.Publisher/Reloaded.Publisher.csproj"
$publishDirectory = "Publish"
$chocoPublishDirectory = "$publishDirectory/Chocolatey"
$installerPublishDirectory = "$publishDirectory/Installer"
+$installerStaticPublishDirectory = "$publishDirectory/Installer-Static"
$templatePublishDirectory = "$publishDirectory/ModTemplate"
$releaseFolder = "/Release"
$toolsReleaseFileName = "/Tools.zip"
@@ -62,7 +63,6 @@ Get-ChildItem "$chocoPath" -Include *.nupkg -Recurse -ErrorAction SilentlyContin
# Build using Visual Studio
msbuild $bootstrapperPath /p:Configuration=Release /p:Platform=x64 /p:OutDir="$loaderOutputPath"
-dotnet publish "$addressDumperProjectPath" -c Release -r win-x86 --self-contained false -o "$loaderOutputPath"
# Build AnyCPU, and then copy 32-bit AppHost.
dotnet publish "$launcherProjectPath" -c Release --self-contained false -o "$outputPath"
@@ -78,7 +78,10 @@ dotnet publish "$publisherProjectPath" -c Release -r win-x64 --self-contained fa
dotnet publish "$communityProjectPath" -c Release -r win-x64 --self-contained false -o "$toolsPath" /p:PublishSingleFile=true
# Build Installer
-dotnet publish "$installerProjectPath" -o "$installerPublishDirectory"
+dotnet publish "$installerProjectPath" -f net472 -o "$installerPublishDirectory"
+
+# Build Installer (Static/Linux)
+dotnet publish "$installerCliProjectPath" -f net8.0-windows -r win-x64 -o "$installerStaticPublishDirectory"
# Build Templates
dotnet pack "$templateProjectPath" -o "$templatePublishDirectory"
diff --git a/source/Reloaded-II.sln b/source/Reloaded-II.sln
index a2ce903d..4e7e783e 100644
--- a/source/Reloaded-II.sln
+++ b/source/Reloaded-II.sln
@@ -4,7 +4,6 @@ VisualStudioVersion = 17.1.31911.260
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.Mod.Launcher", "Reloaded.Mod.Launcher\Reloaded.Mod.Launcher.csproj", "{159EF9A3-1B08-45F8-97CA-8DD72BC91936}"
ProjectSection(ProjectDependencies) = postProject
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E} = {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}
{CF3E2DC7-04F8-4878-97F4-1BCF950AC97D} = {CF3E2DC7-04F8-4878-97F4-1BCF950AC97D}
EndProjectSection
EndProject
@@ -39,8 +38,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestAppA", "Testing\Apps\Te
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestInterfaces", "Testing\Other\TestInterfaces\TestInterfaces.csproj", "{D0363CCD-17A6-4700-8C94-D4E3327C6A07}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.Mod.Launcher.Kernel32AddressDumper", "Reloaded.Mod.Launcher.Kernel32AddressDumper\Reloaded.Mod.Launcher.Kernel32AddressDumper.csproj", "{B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestModD", "Testing\Mods\TestModD\TestModD.csproj", "{E036F18E-312A-4C78-A8E9-5E37E5EBA9B9}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestModE", "Testing\Mods\TestModE\TestModE.csproj", "{0C911851-C2F3-4E84-BF0D-413DAD7887A7}"
@@ -85,6 +82,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reloaded.AutoIndexBuilder",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestModControlParams", "Testing\Mods\TestModControlParams\TestModControlParams.csproj", "{17147B47-9D02-4D39-8B6A-D3C9514333C9}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reloaded.Mod.Installer.Lib", "Reloaded.Mod.Installer.Lib\Reloaded.Mod.Installer.Lib.csproj", "{1C4D1CDB-6B7D-4591-9254-84DBF89AA110}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Reloaded.Mod.Installer.Cli", "Reloaded.Mod.Installer.Cli\Reloaded.Mod.Installer.Cli.csproj", "{A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -277,22 +278,6 @@ Global
{D0363CCD-17A6-4700-8C94-D4E3327C6A07}.Release-ModLoaderOnly|Any CPU.ActiveCfg = Release|Any CPU
{D0363CCD-17A6-4700-8C94-D4E3327C6A07}.Release-ModLoaderOnly|Any CPU.Build.0 = Release|Any CPU
{D0363CCD-17A6-4700-8C94-D4E3327C6A07}.Release-ModLoaderOnly|X64+X86.ActiveCfg = Release|Any CPU
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug|Any CPU.ActiveCfg = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug|Any CPU.Build.0 = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug|X64+X86.ActiveCfg = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug|X64+X86.Build.0 = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug-ModLoaderOnly|Any CPU.ActiveCfg = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug-ModLoaderOnly|Any CPU.Build.0 = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug-ModLoaderOnly|X64+X86.ActiveCfg = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Debug-ModLoaderOnly|X64+X86.Build.0 = Debug|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release|Any CPU.ActiveCfg = Release|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release|Any CPU.Build.0 = Release|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release|X64+X86.ActiveCfg = Release|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release|X64+X86.Build.0 = Release|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release-ModLoaderOnly|Any CPU.ActiveCfg = Release|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release-ModLoaderOnly|Any CPU.Build.0 = Release|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release-ModLoaderOnly|X64+X86.ActiveCfg = Release|x86
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}.Release-ModLoaderOnly|X64+X86.Build.0 = Release|x86
{E036F18E-312A-4C78-A8E9-5E37E5EBA9B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E036F18E-312A-4C78-A8E9-5E37E5EBA9B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E036F18E-312A-4C78-A8E9-5E37E5EBA9B9}.Debug|X64+X86.ActiveCfg = Debug|Any CPU
@@ -529,6 +514,38 @@ Global
{17147B47-9D02-4D39-8B6A-D3C9514333C9}.Release-ModLoaderOnly|Any CPU.Build.0 = Release|Any CPU
{17147B47-9D02-4D39-8B6A-D3C9514333C9}.Release-ModLoaderOnly|X64+X86.ActiveCfg = Release|Any CPU
{17147B47-9D02-4D39-8B6A-D3C9514333C9}.Release-ModLoaderOnly|X64+X86.Build.0 = Release|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug|X64+X86.ActiveCfg = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug|X64+X86.Build.0 = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug-ModLoaderOnly|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug-ModLoaderOnly|Any CPU.Build.0 = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug-ModLoaderOnly|X64+X86.ActiveCfg = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Debug-ModLoaderOnly|X64+X86.Build.0 = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release|X64+X86.ActiveCfg = Release|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release|X64+X86.Build.0 = Release|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release-ModLoaderOnly|Any CPU.ActiveCfg = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release-ModLoaderOnly|Any CPU.Build.0 = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release-ModLoaderOnly|X64+X86.ActiveCfg = Debug|Any CPU
+ {1C4D1CDB-6B7D-4591-9254-84DBF89AA110}.Release-ModLoaderOnly|X64+X86.Build.0 = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug|X64+X86.ActiveCfg = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug|X64+X86.Build.0 = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug-ModLoaderOnly|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug-ModLoaderOnly|Any CPU.Build.0 = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug-ModLoaderOnly|X64+X86.ActiveCfg = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Debug-ModLoaderOnly|X64+X86.Build.0 = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release|X64+X86.ActiveCfg = Release|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release|X64+X86.Build.0 = Release|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release-ModLoaderOnly|Any CPU.ActiveCfg = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release-ModLoaderOnly|Any CPU.Build.0 = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release-ModLoaderOnly|X64+X86.ActiveCfg = Debug|Any CPU
+ {A5C8607B-C5C6-4EF5-A86D-1E9B79B1E7E5}.Release-ModLoaderOnly|X64+X86.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -560,7 +577,6 @@ Global
Reloaded.Mod.Loader.IPC\Reloaded.Mod.Loader.IPC.projitems*{1050bfd3-eda7-4aa1-bff8-dd9db0c8ee79}*SharedItemsImports = 13
Reloaded.Mod.Shared\Reloaded.Mod.Shared.projitems*{4d10ed29-b435-4b9c-a75b-a8961f6d3b31}*SharedItemsImports = 5
Reloaded.Mod.Loader.IPC\Reloaded.Mod.Loader.IPC.projitems*{9d416472-b721-4df6-8218-63853a17d8f6}*SharedItemsImports = 5
- Reloaded.Mod.Shared\Reloaded.Mod.Shared.projitems*{b0bfee3e-4a56-4f9f-95a8-9229fc74d46e}*SharedItemsImports = 5
Reloaded.Mod.Loader.IPC\Reloaded.Mod.Loader.IPC.projitems*{cf3e2dc7-04f8-4878-97f4-1bcf950ac97d}*SharedItemsImports = 5
Reloaded.Mod.Shared\Reloaded.Mod.Shared.projitems*{cf3e2dc7-04f8-4878-97f4-1bcf950ac97d}*SharedItemsImports = 5
Reloaded.Mod.Shared\Reloaded.Mod.Shared.projitems*{d322e71a-f32e-42e9-8340-c747d3d9028c}*SharedItemsImports = 13
diff --git a/source/Reloaded.Mod.Installer.Cli/Cli.cs b/source/Reloaded.Mod.Installer.Cli/Cli.cs
new file mode 100644
index 00000000..f3536463
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Cli/Cli.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Threading.Tasks;
+using ConsoleProgressBar;
+using Reloaded.Mod.Installer.DependencyInstaller.IO;
+using Reloaded.Mod.Installer.Lib;
+
+namespace Reloaded.Mod.Installer.Cli;
+
+public static class Cli
+{
+ public static Settings Settings;
+
+ ///
+ /// True if application was executed in CLI. Else false, it should be ran in GUI.
+ public static bool TryRunCli(string[] args)
+ {
+ // Note: This code is kind of jank, mostly hackily put together. Sorry!
+ // I was in a rush.
+ Settings = Settings.GetSettings(args);
+
+ // Handle special case of dependency only install.
+ foreach (var arg in args)
+ {
+ if (arg.Equals("--dependenciesOnly", StringComparison.OrdinalIgnoreCase))
+ {
+ InstallDependenciesOnly();
+ return true;
+ }
+
+ if (arg.Equals("--nogui"))
+ {
+ InstallInCli();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private static void InstallDependenciesOnly()
+ {
+ var model = new MainWindowViewModel();
+ using var progressBar = SetupCliInstall("Installing (Dependencies Only)", model);
+ using var temporaryFolder = new TemporaryFolderAllocation();
+ Console.WriteLine($"Using Temporary Folder: {temporaryFolder.FolderPath}");
+ Settings.InstallLocation = temporaryFolder.FolderPath; // Ensure Legacy Behaviour
+ Settings.CreateShortcut = false;
+ Settings.StartReloaded = false;
+ Task.Run(() => model.InstallReloadedAsync(Settings)).Wait();
+ }
+
+ internal static void InstallInCli()
+ {
+ var model = new MainWindowViewModel();
+ using var progressBar = SetupCliInstall("Installing (No GUI)", model);
+ Task.Run(() => model.InstallReloadedAsync(Settings)).Wait();
+ }
+
+ private static ProgressBar SetupCliInstall(string progressText, MainWindowViewModel model)
+ {
+ var progressBar = new ProgressBar();
+ var progress = progressBar.HierarchicalProgress;
+ model = new MainWindowViewModel();
+ model.PropertyChanged += (_, eventArgs) =>
+ {
+ if (eventArgs.PropertyName == nameof(model.Progress))
+ progress.Report(model.Progress / 100.0f, progressText);
+ };
+
+ return progressBar;
+ }
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Cli/Program.cs b/source/Reloaded.Mod.Installer.Cli/Program.cs
new file mode 100644
index 00000000..b702bfd8
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Cli/Program.cs
@@ -0,0 +1,20 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Reloaded.Mod.Installer.DependencyInstaller.IO;
+using ProgressBar = ConsoleProgressBar.ProgressBar;
+
+namespace Reloaded.Mod.Installer.Cli;
+
+public class Program
+{
+ public static void Main(string[] args)
+ {
+ Console.WriteLine("Reloaded-II CLI Installer\n" +
+ "Mode flags:\n" +
+ "--dependenciesOnly: Don't install Reloaded, just install runtimes.");
+
+ if (!Cli.TryRunCli(args))
+ Cli.InstallInCli();
+ }
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Cli/Reloaded.Mod.Installer.Cli.csproj b/source/Reloaded.Mod.Installer.Cli/Reloaded.Mod.Installer.Cli.csproj
new file mode 100644
index 00000000..aa3be7f1
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Cli/Reloaded.Mod.Installer.Cli.csproj
@@ -0,0 +1,33 @@
+
+
+
+ Exe
+ NET472;net8.0-windows
+ preview
+ app.manifest
+ Setup-Linux
+ enable
+ appicon.ico
+ portable
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/Reloaded.Mod.Installer.Cli/app.manifest b/source/Reloaded.Mod.Installer.Cli/app.manifest
new file mode 100644
index 00000000..04608b92
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Cli/app.manifest
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/Reloaded.Mod.Installer.Cli/appicon.ico b/source/Reloaded.Mod.Installer.Cli/appicon.ico
new file mode 100644
index 00000000..63f6d5bd
Binary files /dev/null and b/source/Reloaded.Mod.Installer.Cli/appicon.ico differ
diff --git a/source/Reloaded.Mod.Installer.DependencyInstaller/Reloaded.Mod.Installer.DependencyInstaller.csproj b/source/Reloaded.Mod.Installer.DependencyInstaller/Reloaded.Mod.Installer.DependencyInstaller.csproj
index ec4fded1..a4ca9cbd 100644
--- a/source/Reloaded.Mod.Installer.DependencyInstaller/Reloaded.Mod.Installer.DependencyInstaller.csproj
+++ b/source/Reloaded.Mod.Installer.DependencyInstaller/Reloaded.Mod.Installer.DependencyInstaller.csproj
@@ -3,10 +3,12 @@
netstandard2.0
preview
+ portable
-
+
+
diff --git a/source/Reloaded.Mod.Installer.Lib/MainWindowViewModel.cs b/source/Reloaded.Mod.Installer.Lib/MainWindowViewModel.cs
new file mode 100644
index 00000000..4256d399
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/MainWindowViewModel.cs
@@ -0,0 +1,419 @@
+using System.IO.Compression;
+using System.Linq;
+using System.Net.Http;
+using Windows.Win32.Security;
+using Windows.Win32.System.Threading;
+using Microsoft.Win32;
+using Reloaded.Mod.Installer.Lib.Utilities;
+using static Windows.Win32.PInvoke;
+using static Reloaded.Mod.Installer.DependencyInstaller.DependencyInstaller;
+using Path = System.IO.Path;
+
+namespace Reloaded.Mod.Installer.Lib;
+
+public class MainWindowViewModel : ObservableObject
+{
+ ///
+ /// The current step of the download process.
+ ///
+ public int CurrentStepNo { get; set; }
+
+ ///
+ /// Current production step.
+ ///
+ public string CurrentStepDescription { get; set; } = "";
+
+ ///
+ /// The current setup progress.
+ /// Range 0 - 100.0f.
+ ///
+ public double Progress { get; set; }
+
+ ///
+ /// A cancellation token allowing you to cancel the download operation.
+ ///
+ public CancellationTokenSource CancellationToken { get; } = new();
+
+ public async Task InstallReloadedAsync(Settings settings)
+ {
+ // Note: All of this code is terrible, I don't have the time to make it good.
+
+ // ReSharper disable InconsistentNaming
+ const uint MB_OK = 0x0;
+ const uint MB_ICONINFORMATION = 0x40;
+ // ReSharper restore InconsistentNaming
+
+ // Handle Proton specific customizations.
+ var protonTricksSuffix = GetProtontricksSuffix();
+ var isProton = !string.IsNullOrEmpty(protonTricksSuffix);
+ OverrideInstallLocationForProton(settings, protonTricksSuffix, out var nativeInstallFolder, out var userName);
+
+ // Check for existing installation
+ if (Directory.Exists(settings.InstallLocation) && Directory.GetFiles(settings.InstallLocation).Length > 0)
+ {
+ Native.MessageBox(IntPtr.Zero, $"An existing installation has been detected at:\n{settings.InstallLocation}\n\n" +
+ $"To prevent data loss, installation will be aborted.\n" +
+ $"If you wish to reinstall, delete or move the existing installation.", "Existing Installation", MB_OK | MB_ICONINFORMATION);
+ return;
+ }
+
+ // Step
+ Directory.CreateDirectory(settings.InstallLocation);
+
+ using var tempDownloadDir = new TemporaryFolderAllocation();
+ var progressSlicer = new ProgressSlicer(new Progress(d =>
+ {
+ Progress = d * 100.0;
+ }));
+
+ try
+ {
+ var downloadLocation = Path.Combine(tempDownloadDir.FolderPath, $"Reloaded-II.zip");
+
+ // 0.15
+ CurrentStepNo = 0;
+ await DownloadReloadedAsync(downloadLocation, progressSlicer.Slice(0.15));
+ if (CancellationToken.IsCancellationRequested)
+ throw new TaskCanceledException();
+
+ // 0.20
+ CurrentStepNo = 1;
+ ExtractReloaded(settings.InstallLocation, downloadLocation, progressSlicer.Slice(0.05));
+ if (CancellationToken.IsCancellationRequested)
+ throw new TaskCanceledException();
+
+ // 1.00
+ CurrentStepNo = 2;
+ await CheckAndInstallMissingRuntimesAsync(settings.InstallLocation, tempDownloadDir.FolderPath,
+ progressSlicer.Slice(0.8),
+ s => { CurrentStepDescription = s; }, CancellationToken.Token);
+
+ var executableName = IntPtr.Size == 8 ? "Reloaded-II.exe" : "Reloaded-II32.exe";
+ var executablePath = CombineWithForwardSlash(settings.InstallLocation, executableName);
+ var nativeExecutablePath = CombineWithForwardSlash(nativeInstallFolder, executableName);
+ var shortcutPath = CombineWithForwardSlash(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "Reloaded-II.lnk");
+
+ // For Proton/Wine, create a shortcut up one folder above install location.
+ if (!string.IsNullOrEmpty(protonTricksSuffix))
+ shortcutPath = CombineWithForwardSlash(Path.GetDirectoryName(settings.InstallLocation)!, "Reloaded-II - " + SanitizeFileName(protonTricksSuffix) + ".lnk");
+
+ if (settings.CreateShortcut)
+ {
+ CurrentStepDescription = "Creating Shortcut";
+ if (!isProton)
+ NativeShellLink.MakeShortcut(shortcutPath, executablePath);
+ else
+ MakeProtonShortcut(userName, protonTricksSuffix, shortcutPath, nativeExecutablePath);
+ }
+
+ CurrentStepDescription = "All Set";
+
+ // On WINE, overwrite environment variables that may be inherited
+ // from host permanently.
+ if (WineDetector.IsWine())
+ {
+ ShowDotFilesInWine();
+ SetEnvironmentVariable("DOTNET_ROOT", "%ProgramFiles%\\dotnet");
+ SetEnvironmentVariable("DOTNET_BUNDLE_EXTRACT_BASE_DIR", "%TEMP%\\.net");
+ }
+
+ if (!settings.HideNonErrorGuiMessages && isProton)
+ Native.MessageBox(IntPtr.Zero, $"Reloaded was installed via Proton to your Desktop.\nYou can find it at: {nativeInstallFolder}", "Installation Complete", MB_OK | MB_ICONINFORMATION);
+
+ if (settings.StartReloaded)
+ {
+ // We're in an admin process; but we want to de-escalate as Reloaded-II is not
+ // meant to be used in admin mode.
+ StartProcessWithReducedPrivileges(executablePath);
+ }
+ }
+ catch (TaskCanceledException)
+ {
+ IOEx.TryDeleteDirectory(settings.InstallLocation);
+ }
+ catch (Exception e)
+ {
+ IOEx.TryDeleteDirectory(settings.InstallLocation);
+ Native.MessageBox(IntPtr.Zero, "There was an error in installing Reloaded.\n" +
+ $"Feel free to open an issue on github.com/Reloaded-Project/Reloaded-II if you require support.\n" +
+ $"Exception: {e}\n" +
+ $"Message: {e.Message}\n" +
+ $"Inner Exception: {e.InnerException}\n" +
+ $"Stack Trace: {e.StackTrace}", "Error in Installing Reloaded", MB_OK);
+ }
+ }
+ private void MakeProtonShortcut(string? userName, string protonTricksSuffix, string shortcutPath, string nativeExecutablePath)
+ {
+ nativeExecutablePath = nativeExecutablePath.Replace('\\', '/');
+ var desktopFile =
+"""
+[Desktop Entry]
+Name=Reloaded-II ({SUFFIX})
+Exec=bash -ic 'protontricks-launch --appid {APPID} "{NATIVEPATH}"'
+Type=Application
+StartupNotify=true
+Comment=Reloaded II installation for {SUFFIX}
+Path={RELOADEDFOLDER}
+Icon={RELOADEDFOLDER}/Mods/reloaded.sharedlib.hooks/Preview.png
+StartupWMClass=reloaded-ii.exe
+""";
+ // reloaded.sharedlib.hooks is present in all Reloaded installs after boot, so we can use that... for now.
+ desktopFile = desktopFile.Replace("{USER}", userName);
+ desktopFile = desktopFile.Replace("{APPID}", Environment.GetEnvironmentVariable("STEAM_APPID"));
+ desktopFile = desktopFile.Replace("{SUFFIX}", protonTricksSuffix);
+ desktopFile = desktopFile.Replace("{RELOADEDFOLDER}", Path.GetDirectoryName(nativeExecutablePath)!);
+ desktopFile = desktopFile.Replace("{NATIVEPATH}", nativeExecutablePath);
+ desktopFile = desktopFile.Replace('\\', '/');
+ shortcutPath = shortcutPath.Replace('\\', '/');
+ shortcutPath = shortcutPath.Replace(".lnk", ".desktop");
+
+ try
+ {
+ File.WriteAllText(shortcutPath, desktopFile);
+
+ // Write `.desktop` file that integrates into shell.
+ var shellShortcutPath = $@"Z:/home/{userName}/.local/share/applications/{SanitizeFileName(Path.GetFileName(shortcutPath))}";
+ File.WriteAllText(shellShortcutPath, desktopFile);
+
+ // Mark as executable.
+ LinuxTryMarkAsExecutable(shortcutPath);
+ LinuxTryMarkAsExecutable(shellShortcutPath);
+ }
+ catch (FileNotFoundException e)
+ {
+ ThrowFailedToCreateShortcut(e);
+ }
+ catch (DirectoryNotFoundException e)
+ {
+ ThrowFailedToCreateShortcut(e);
+ }
+ void ThrowFailedToCreateShortcut(Exception e)
+ {
+ throw new Exception("Failed to create Reloaded shortcut.\n" +
+ "If you have `protontricks` installed via `flatpak`, you may need to give it FileSystem permission `flatpak override --user --filesystem=host com.github.Matoking.protontricks`", e);
+ }
+ }
+
+ private static void LinuxTryMarkAsExecutable(string windowsPath)
+ {
+ windowsPath = windowsPath.Replace('\\', '/');
+ windowsPath = windowsPath.Replace("Z:", "");
+ var processInfo = new ProcessStartInfo
+ {
+ FileName = "cmd.exe",
+ Arguments = $"/c start Z:/bin/chmod +x \"{windowsPath}\"",
+ UseShellExecute = true,
+ CreateNoWindow = true
+ };
+ try
+ {
+ Process.Start(processInfo);
+ }
+ catch (Exception)
+ {
+ // If the first attempt fails, try with the alternative path
+ processInfo.Arguments = $"/c start Z:/usr/bin/chmod +x \"{windowsPath}\"";
+ try
+ {
+ Process.Start(processInfo);
+ }
+ catch (Exception)
+ {
+ // Both attempts failed
+ }
+ }
+ }
+
+ private static void OverrideInstallLocationForProton(Settings settings, string protonTricksSuffix, out string nativeInstallFolder, out string? userName)
+ {
+ nativeInstallFolder = "";
+ userName = "";
+ if (settings.IsManuallyOverwrittenLocation) return;
+ if (string.IsNullOrEmpty(protonTricksSuffix)) return;
+
+ var desktopDir = GetHomeDesktopDirectoryOnProton(out nativeInstallFolder, out userName);
+ var folderName = $"Reloaded-II - {protonTricksSuffix}";
+
+ settings.InstallLocation = CombineWithForwardSlash(desktopDir, folderName);
+ nativeInstallFolder = CombineWithForwardSlash(nativeInstallFolder, folderName);
+ }
+
+ private static async Task DownloadReloadedAsync(string downloadLocation, IProgress downloadProgress)
+ {
+ using var client = new HttpClient();
+ using var response = await client.GetAsync("https://github.com/Reloaded-Project/Reloaded-II/releases/latest/download/Release.zip", HttpCompletionOption.ResponseHeadersRead);
+ response.EnsureSuccessStatusCode();
+
+ var totalBytes = response.Content.Headers.ContentLength ?? 0L;
+ var totalReadBytes = 0L;
+ int readBytes;
+ var buffer = new byte[128 * 1024];
+
+ using var contentStream = await response.Content.ReadAsStreamAsync();
+ using var fileStream = new FileStream(downloadLocation, FileMode.Create, FileAccess.Write, FileShare.None, buffer.Length, true);
+ while ((readBytes = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ {
+ await fileStream.WriteAsync(buffer, 0, readBytes);
+ totalReadBytes += readBytes;
+ downloadProgress?.Report(totalReadBytes * 1d / totalBytes);
+ }
+
+ if (!File.Exists(downloadLocation))
+ throw new Exception("Reloaded failed to download (no file was written to disk).");
+ }
+
+ private static void ExtractReloaded(string extractFolderPath, string downloadedPackagePath, IProgress slice)
+ {
+ ZipFile.ExtractToDirectory(downloadedPackagePath, extractFolderPath);
+ if (Directory.GetFiles(extractFolderPath).Length == 0)
+ throw new Exception($"Reloaded failed to download (downloaded archive was not properly extracted).");
+
+ File.Delete(downloadedPackagePath);
+ slice.Report(1);
+ }
+
+ private static unsafe void StartProcessWithReducedPrivileges(string executablePath)
+ {
+ SAFER_LEVEL_HANDLE saferHandle = default;
+ try
+ {
+ // 1. Create a new new access token
+ if (!SaferCreateLevel(SAFER_SCOPEID_USER, SAFER_LEVELID_NORMALUSER,
+ SAFER_LEVEL_OPEN, &saferHandle, null))
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ if (!SaferComputeTokenFromLevel(saferHandle, null, out var newAccessToken, 0, null))
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ // Set the token to medium integrity because SaferCreateLevel doesn't reduce the
+ // integrity level of the token and keep it as high.
+ if (!ConvertStringSidToSid("S-1-16-8192", out var psid))
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ TOKEN_MANDATORY_LABEL tml = default;
+ tml.Label.Attributes = SE_GROUP_INTEGRITY;
+ tml.Label.Sid = (PSID)psid.DangerousGetHandle();
+
+ var length = (uint)Marshal.SizeOf(tml);
+ if (!SetTokenInformation(newAccessToken, TOKEN_INFORMATION_CLASS.TokenIntegrityLevel, &tml, length))
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+
+ // 2. Start process using the new access token
+ // Cannot use Process.Start as there is no way to set the access token to use
+ fixed (char* commandLinePtr = executablePath)
+ {
+ STARTUPINFOW si = default;
+ Span span = new Span(commandLinePtr, executablePath.Length);
+ if (CreateProcessAsUser(newAccessToken, null, ref span, null, null, bInheritHandles: false, default,
+ null, null, in si, out var pi))
+ {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+ }
+ }
+ catch
+ {
+ // In case of WINE, or some other unexpected event.
+ Process.Start(executablePath);
+ }
+ finally
+ {
+ if (saferHandle != default)
+ {
+ SaferCloseLevel(saferHandle);
+ }
+ }
+ }
+
+ private static void SetEnvironmentVariable(string name, string value)
+ {
+ Environment.SetEnvironmentVariable(name, value, EnvironmentVariableTarget.Process);
+ using var key = Registry.CurrentUser.OpenSubKey("Environment", true);
+ if (key != null)
+ {
+ key.SetValue(name, value);
+ Console.WriteLine($"Environment variable '{name}' set to '{value}'");
+ }
+ else
+ {
+ Console.WriteLine("Failed to open Environment key in registry");
+ }
+ }
+
+ ///
+ /// This suffix is appended to shortcut name and install folder.
+ ///
+ private static string GetProtontricksSuffix()
+ {
+ try
+ {
+ // Note: Steam games are usually installed in a folder which is a friendly name
+ // for the game. If the user is running in Protontricks, there's a high
+ // chance that the folder will be named just right, e.g. 'Persona 5 Royal'.
+ return SanitizeFileName(Path.GetFileName(Environment.GetEnvironmentVariable("STEAM_APP_PATH")) ?? string.Empty);
+ }
+ catch (Exception)
+ {
+ return "";
+ }
+ }
+
+ ///
+ /// This suffix is appended to shortcut name and install folder.
+ ///
+ private static string GetHomeDesktopDirectoryOnProton(out string linuxPath, out string? userName)
+ {
+ userName = Environment.GetEnvironmentVariable("LOGNAME");
+ if (userName != null)
+ {
+ // TODO: This is a terrible hack.
+ linuxPath = $"/home/{userName}/Desktop";
+ return @$"Z:/home/{userName}/Desktop";
+ }
+
+ Native.MessageBox(IntPtr.Zero, "Cannot determine username for proton installation.\n" +
+ "Please make sure that 'LOGNAME' environment variable is set.",
+ "Error in Installing Reloaded", 0x0);
+ throw new Exception("Terminated because cannot find username.");
+ }
+
+ private static void ShowDotFilesInWine()
+ {
+ try
+ {
+ using RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Wine", true)!;
+ // Set the ShowDotFiles value to "Y"
+ key.SetValue("ShowDotFiles", "Y", RegistryValueKind.String);
+ Console.WriteLine("Successfully set ShowDotFiles to Y in the Wine registry.");
+ }
+ catch (Exception)
+ {
+ Native.MessageBox(IntPtr.Zero, "Failed to auto-unhide dot files in Wine.\n" +
+ "You'll need to enter `winecfg` and check `show dot files` manually yourself.",
+ "Error in Configuring WINE", 0x0);
+ }
+ }
+
+ ///
+ /// Sanitizes a file or path name to not contain invalid chars.
+ ///
+ /// Sanitized file name.
+ private static string SanitizeFileName(string fileName)
+ {
+ var invalidChars = GetInvalidFileNameChars();
+ return new string(fileName.Where(x => !invalidChars.Contains(x)).ToArray());
+ }
+
+ // Copied from Windows version, Unix version is a subset of this.
+ private static char[] GetInvalidFileNameChars() =>
+ [
+ '\"', '<', '>', '|', '\0',
+ (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10,
+ (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20,
+ (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30,
+ (char)31, ':', '*', '?', '\\', '/'
+ ];
+
+ private static string CombineWithForwardSlash(string a, string b) => Path.Combine(a, b).Replace('\\', '/');
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Lib/NativeMethods.txt b/source/Reloaded.Mod.Installer.Lib/NativeMethods.txt
new file mode 100644
index 00000000..c7f7ac1f
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/NativeMethods.txt
@@ -0,0 +1,12 @@
+SaferCreateLevel
+SaferComputeTokenFromLevel
+SaferCloseLevel
+SAFER_SCOPEID_*
+SAFER_LEVELID_*
+SAFER_LEVEL_*
+CreateProcessAsUser
+TOKEN_MANDATORY_LABEL
+SE_GROUP_INTEGRITY
+ConvertStringSidToSid
+SetTokenInformation
+LocalFree
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Lib/Reloaded.Mod.Installer.Lib.csproj b/source/Reloaded.Mod.Installer.Lib/Reloaded.Mod.Installer.Lib.csproj
new file mode 100644
index 00000000..afb50107
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/Reloaded.Mod.Installer.Lib.csproj
@@ -0,0 +1,48 @@
+
+
+
+ NET472;net8.0-windows
+ preview
+ enable
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/Reloaded.Mod.Installer.Lib/Settings.cs b/source/Reloaded.Mod.Installer.Lib/Settings.cs
new file mode 100644
index 00000000..8c253c2b
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/Settings.cs
@@ -0,0 +1,49 @@
+using System.Linq;
+namespace Reloaded.Mod.Installer.Lib;
+
+///
+/// Settings for the installer.
+///
+public class Settings
+{
+ public string InstallLocation { get; set; } = Path.Combine(GetSafeInstallPath(), "Reloaded-II");
+ public bool IsManuallyOverwrittenLocation { get; set; }
+ public bool CreateShortcut { get; set; } = true;
+ public bool HideNonErrorGuiMessages { get; set; } = false;
+ public bool StartReloaded { get; set; } = true;
+
+ public Settings() { }
+
+ public static Settings GetSettings(string[] args)
+ {
+ var settings = new Settings();
+ for (int x = 0; x < args.Length - 1; x++)
+ {
+ if (args[x] == "--installdir")
+ {
+ settings.InstallLocation = args[x + 1];
+ settings.IsManuallyOverwrittenLocation = true;
+ }
+ if (args[x] == "--nogui") settings.HideNonErrorGuiMessages = true;
+ if (args[x] == "--nocreateshortcut") settings.CreateShortcut = false;
+ if (args[x] == "--nostartreloaded") settings.StartReloaded = false;
+ }
+
+ return settings;
+ }
+
+ private static string GetSafeInstallPath()
+ {
+ var installPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
+ bool hasNonAsciiChars = installPath.Any(c => c > 127);
+ if (installPath.Contains("OneDrive") || hasNonAsciiChars)
+ {
+ var driveRoot = Path.GetPathRoot(Environment.SystemDirectory);
+ if (driveRoot == null)
+ // if for some reason we can't determine the root, fallback to Desktop
+ return installPath;
+ return driveRoot;
+ }
+ return installPath;
+ }
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Lib/Usings.cs b/source/Reloaded.Mod.Installer.Lib/Usings.cs
new file mode 100644
index 00000000..333de6b7
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/Usings.cs
@@ -0,0 +1,11 @@
+global using Reloaded.Mod.Installer.DependencyInstaller;
+global using Reloaded.Mod.Installer.DependencyInstaller.IO;
+global using System;
+global using System.ComponentModel;
+global using System.Diagnostics;
+global using System.IO;
+global using System.Runtime.InteropServices;
+global using System.Runtime.InteropServices.ComTypes;
+global using System.Text;
+global using System.Threading;
+global using System.Threading.Tasks;
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Lib/Utilities/IOEx.cs b/source/Reloaded.Mod.Installer.Lib/Utilities/IOEx.cs
new file mode 100644
index 00000000..c845b621
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/Utilities/IOEx.cs
@@ -0,0 +1,15 @@
+namespace Reloaded.Mod.Installer.Lib.Utilities
+{
+ // ReSharper disable once InconsistentNaming
+ public static class IOEx
+ {
+ ///
+ /// Tries to delete a directory, if possible.
+ ///
+ public static void TryDeleteDirectory(string path, bool recursive = true)
+ {
+ try { Directory.Delete(path, recursive); }
+ catch (Exception) { /* Ignored */ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Lib/Utilities/Native.cs b/source/Reloaded.Mod.Installer.Lib/Utilities/Native.cs
new file mode 100644
index 00000000..1a05091a
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/Utilities/Native.cs
@@ -0,0 +1,7 @@
+namespace Reloaded.Mod.Installer.Lib.Utilities;
+
+public static class Native
+{
+ [DllImport("user32.dll", CharSet = CharSet.Auto)]
+ public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer/Utilities/ObservableObject.cs b/source/Reloaded.Mod.Installer.Lib/Utilities/ObservableObject.cs
similarity index 51%
rename from source/Reloaded.Mod.Installer/Utilities/ObservableObject.cs
rename to source/Reloaded.Mod.Installer.Lib/Utilities/ObservableObject.cs
index 8a8de8a7..b1a0e9e2 100644
--- a/source/Reloaded.Mod.Installer/Utilities/ObservableObject.cs
+++ b/source/Reloaded.Mod.Installer.Lib/Utilities/ObservableObject.cs
@@ -1,4 +1,4 @@
-namespace Reloaded.Mod.Installer.Utilities;
+namespace Reloaded.Mod.Installer.Lib.Utilities;
///
/// An abstract class that implements the bare minimum of the INotifyPropertyChanged interface.
@@ -6,10 +6,4 @@ namespace Reloaded.Mod.Installer.Utilities;
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
-
- protected void RaisePropertyChangedEvent(string propertyName)
- {
- var handler = PropertyChanged;
- handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
- }
}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Lib/Utilities/ShellLink.cs b/source/Reloaded.Mod.Installer.Lib/Utilities/ShellLink.cs
new file mode 100644
index 00000000..8baa2aee
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/Utilities/ShellLink.cs
@@ -0,0 +1,118 @@
+
+using System.Diagnostics.CodeAnalysis;
+#if NET8_0_OR_GREATER
+using System.Runtime.InteropServices.Marshalling;
+#endif
+
+namespace Reloaded.Mod.Installer.Lib.Utilities;
+
+#if NET8_0_OR_GREATER
+[GeneratedComClass]
+#else
+[ComImport]
+#endif
+[Guid("00021401-0000-0000-C000-000000000046")]
+internal partial class ShellLink
+{
+}
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
+#else
+[ComImport]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("000214F9-0000-0000-C000-000000000046")]
+internal partial interface IShellLink
+{
+ void GetPath(IntPtr pszFile, int cchMaxPath, IntPtr pfd, int fFlags);
+ void GetIDList(out IntPtr ppidl);
+ void SetIDList(IntPtr pidl);
+ void GetDescription(IntPtr pszName, int cchMaxName);
+ void SetDescription(string pszName);
+ void GetWorkingDirectory(IntPtr pszDir, int cchMaxPath);
+ void SetWorkingDirectory(string pszDir);
+ void GetArguments(IntPtr pszArgs, int cchMaxPath);
+ void SetArguments(string pszArgs);
+ void GetHotkey(out short pwHotkey);
+ void SetHotkey(short wHotkey);
+ void GetShowCmd(out int piShowCmd);
+ void SetShowCmd(int iShowCmd);
+ void GetIconLocation(IntPtr pszIconPath, int cchIconPath, out int piIcon);
+ void SetIconLocation(string pszIconPath, int iIcon);
+ void SetRelativePath(string pszPathRel, int dwReserved);
+ void Resolve(IntPtr hwnd, int fFlags);
+ void SetPath(string pszFile);
+}
+
+#if NET8_0_OR_GREATER
+[GeneratedComInterface(StringMarshalling = StringMarshalling.Utf16)]
+#else
+[ComImport]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+#endif
+[Guid("0000010B-0000-0000-C000-000000000046")]
+internal partial interface IPersistFile
+{
+ void GetCurFile(out IntPtr ppszFileName);
+ void IsDirty();
+ void Load(string pszFileName, uint dwMode);
+ void Save(string pszFileName, [MarshalAs(UnmanagedType.I1)] bool fRemember);
+ void SaveCompleted(string pszFileName);
+}
+
+internal static class NativeShellLink
+{
+ [DllImport("ole32.dll")]
+ public static extern int CoCreateInstance(
+ [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
+ IntPtr pUnkOuter,
+ uint dwClsContext,
+ [MarshalAs(UnmanagedType.LPStruct)] Guid riid,
+ out IntPtr ppv);
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ private static IShellLink CreateShellLink(out nint pShellLink)
+ {
+ var CLSID_ShellLink = new Guid("00021401-0000-0000-C000-000000000046");
+ var IID_IShellLink = new Guid("000214F9-0000-0000-C000-000000000046");
+
+ const uint CLSCTX_INPROC_SERVER = 1;
+ var hResult = CoCreateInstance(CLSID_ShellLink, IntPtr.Zero, CLSCTX_INPROC_SERVER, IID_IShellLink, out pShellLink);
+ if (hResult != 0)
+ {
+ throw new COMException("Failed to create ShellLink instance", hResult);
+ }
+
+
+#if NET8_0_OR_GREATER
+ ComWrappers cw = new StrategyBasedComWrappers();
+ return (IShellLink)cw.GetOrCreateObjectForComInstance(pShellLink, CreateObjectFlags.None);
+#else
+return (IShellLink)Marshal.GetObjectForIUnknown(pShellLink);
+ #endif
+ }
+
+ [SuppressMessage("ReSharper", "InconsistentNaming")]
+ public static void MakeShortcut(string shortcutPath, string executablePath)
+ {
+ #if NET8_0_OR_GREATER
+ var shell = CreateShellLink(out var pShellLink);
+ #else
+ var shell = (IShellLink)new ShellLink();
+ #endif
+
+ shell.SetDescription($"Reloaded II");
+ shell.SetPath($"\"{executablePath}\"");
+ shell.SetWorkingDirectory(Path.GetDirectoryName(executablePath)!);
+
+ #if NET8_0_OR_GREATER
+ ComWrappers cw = new StrategyBasedComWrappers();
+ var file = (IPersistFile)cw.GetOrCreateObjectForComInstance(pShellLink, CreateObjectFlags.None);
+ file.Save(shortcutPath, false);
+ #else
+ var file = (IPersistFile)shell;
+ file.Save(shortcutPath, false);
+ #endif
+ }
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer.Lib/WineDetector.cs b/source/Reloaded.Mod.Installer.Lib/WineDetector.cs
new file mode 100644
index 00000000..08058aa4
--- /dev/null
+++ b/source/Reloaded.Mod.Installer.Lib/WineDetector.cs
@@ -0,0 +1,16 @@
+namespace Reloaded.Mod.Installer.Lib;
+
+internal class WineDetector
+{
+ internal static bool IsWine()
+ {
+ var ntdll = GetModuleHandle("ntdll.dll");
+ return GetProcAddress(ntdll, "wine_get_version") != IntPtr.Zero;
+ }
+
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
+
+ [DllImport("kernel32.dll")]
+ private static extern IntPtr GetModuleHandle(string lpModuleName);
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer/FodyWeavers.xml b/source/Reloaded.Mod.Installer/FodyWeavers.xml
deleted file mode 100644
index e0a55604..00000000
--- a/source/Reloaded.Mod.Installer/FodyWeavers.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer/MainWindow.xaml.cs b/source/Reloaded.Mod.Installer/MainWindow.xaml.cs
index 8d7ae283..d7fb481d 100644
--- a/source/Reloaded.Mod.Installer/MainWindow.xaml.cs
+++ b/source/Reloaded.Mod.Installer/MainWindow.xaml.cs
@@ -1,3 +1,5 @@
+using Reloaded.Mod.Installer.Lib;
+
namespace Reloaded.Mod.Installer;
///
@@ -17,9 +19,12 @@ public MainWindow()
private async void OnLoaded(object sender, RoutedEventArgs e)
{
- InstallTask = ViewModel.InstallReloadedAsync();
+ InstallTask = ViewModel.InstallReloadedAsync(Cli.Cli.Settings);
await InstallTask.ConfigureAwait(false);
- Application.Current.Shutdown(0);
+ Application.Current.Dispatcher.Invoke(() =>
+ {
+ Application.Current.Shutdown(0);
+ });
}
private async void OnClosing(object sender, CancelEventArgs e)
diff --git a/source/Reloaded.Mod.Installer/MainWindowViewModel.cs b/source/Reloaded.Mod.Installer/MainWindowViewModel.cs
deleted file mode 100644
index 5c892b48..00000000
--- a/source/Reloaded.Mod.Installer/MainWindowViewModel.cs
+++ /dev/null
@@ -1,128 +0,0 @@
-using static Reloaded.Mod.Installer.DependencyInstaller.DependencyInstaller;
-using MessageBox = System.Windows.MessageBox;
-using Path = System.IO.Path;
-
-namespace Reloaded.Mod.Installer;
-
-public class MainWindowViewModel : ObservableObject
-{
- ///
- /// The current step of the download process.
- ///
- public int CurrentStepNo { get; set; }
-
- ///
- /// Current production step.
- ///
- public string CurrentStepDescription { get; set; } = "";
-
- ///
- /// The current setup progress.
- /// Range 0 - 100.0f.
- ///
- public double Progress { get; set; }
-
- ///
- /// A cancellation token allowing you to cancel the download operation.
- ///
- public CancellationTokenSource CancellationToken { get; set; } = new CancellationTokenSource();
-
- public async Task InstallReloadedAsync(string? installFolder = null, bool createShortcut = true, bool startReloaded = true)
- {
- // Step
- installFolder ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "Reloaded-II");
- Directory.CreateDirectory(installFolder);
-
- using var tempDownloadDir = new TemporaryFolderAllocation();
- var progressSlicer = new ProgressSlicer(new Progress(d =>
- {
- Progress = d * 100.0;
- }));
-
- try
- {
- var downloadLocation = Path.Combine(tempDownloadDir.FolderPath, $"Reloaded-II.zip");
-
- // 0.15
- CurrentStepNo = 0;
- await DownloadReloadedAsync(downloadLocation, progressSlicer.Slice(0.15));
- if (CancellationToken.IsCancellationRequested)
- throw new TaskCanceledException();
-
- // 0.20
- CurrentStepNo = 1;
- await ExtractReloadedAsync(installFolder, downloadLocation, progressSlicer.Slice(0.05));
- if (CancellationToken.IsCancellationRequested)
- throw new TaskCanceledException();
-
- // 1.00
- CurrentStepNo = 2;
- await CheckAndInstallMissingRuntimesAsync(installFolder, tempDownloadDir.FolderPath,
- progressSlicer.Slice(0.8),
- s => { CurrentStepDescription = s; }, CancellationToken.Token);
-
- var executableName = IntPtr.Size == 8 ? "Reloaded-II.exe" : "Reloaded-II32.exe";
- var executablePath = Path.Combine(installFolder, executableName);
- var shortcutPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), "Reloaded-II.lnk");
-
- if (createShortcut)
- {
- CurrentStepDescription = "Creating Shortcut";
- MakeShortcut(shortcutPath, executablePath);
- }
-
- CurrentStepDescription = "All Set";
-
- if (startReloaded)
- Process.Start(executablePath);
- }
- catch (TaskCanceledException)
- {
- IOEx.TryDeleteDirectory(installFolder);
- }
- catch (Exception e)
- {
- IOEx.TryDeleteDirectory(installFolder);
- MessageBox.Show("There was an error in installing Reloaded.\n" +
- $"Feel free to open an issue on github.com/Reloaded-Project/Reloaded-II if you require support.\n" +
- $"Message: {e.Message}\n" +
- $"Stack Trace: {e.StackTrace}", "Error in Installing Reloaded");
- }
- }
-
- private void MakeShortcut(string shortcutPath, string executablePath)
- {
- var shell = (IShellLink)new ShellLink();
- shell.SetDescription($"Reloaded II");
- shell.SetPath($"\"{executablePath}\"");
- shell.SetWorkingDirectory(Path.GetDirectoryName(executablePath));
-
- // Save Shortcut
- var file = (IPersistFile)shell;
- file.Save(shortcutPath, false);
- }
-
- private async Task ExtractReloadedAsync(string extractFolderPath, string downloadedPackagePath, IProgress slice)
- {
- CurrentStepDescription = "Extracting Reloaded II";
- var extractor = new ZipPackageExtractor();
- await extractor.ExtractPackageAsync(downloadedPackagePath, extractFolderPath, slice, CancellationToken.Token);
-
- if (Native.IsDirectoryEmpty(extractFolderPath))
- throw new Exception($"Reloaded failed to download (downloaded archive was not properly extracted).");
-
- IOEx.TryDeleteFile(downloadedPackagePath);
- }
-
- private async Task DownloadReloadedAsync(string downloadLocation, IProgress downloadProgress)
- {
- CurrentStepDescription = "Downloading Reloaded II";
- var resolver = new GithubPackageResolver("Reloaded-Project", "Reloaded-II", "Release.zip");
- var versions = await resolver.GetPackageVersionsAsync();
- var latestVersion = versions.First();
- await resolver.DownloadPackageAsync(latestVersion, downloadLocation, downloadProgress, CancellationToken.Token);
-
- if (!File.Exists(downloadLocation))
- throw new Exception($"Reloaded failed to download (no file was written to disk).");
- }
-}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer/Program.cs b/source/Reloaded.Mod.Installer/Program.cs
index c127f7fe..768bc316 100644
--- a/source/Reloaded.Mod.Installer/Program.cs
+++ b/source/Reloaded.Mod.Installer/Program.cs
@@ -1,5 +1,3 @@
-using ProgressBar = ConsoleProgressBar.ProgressBar;
-
namespace Reloaded.Mod.Installer;
internal class Program
@@ -7,54 +5,11 @@ internal class Program
[STAThread]
public static void Main(string[] args)
{
- // Handle special case of dependency only install.
- foreach (var arg in args)
- {
- if (arg.Equals("--dependenciesOnly", StringComparison.OrdinalIgnoreCase))
- {
- InstallDependenciesOnly();
- return;
- }
-
- if (arg.Equals("--nogui"))
- {
- InstallNoGui();
- return;
- }
- }
+ if (Cli.Cli.TryRunCli(args))
+ return;
var application = new App();
application.InitializeComponent();
application.Run();
}
-
- private static void InstallDependenciesOnly()
- {
- var model = new MainWindowViewModel();
- using var progressBar = SetupCliInstall("Installing (Dependencies Only)", model);
- using var temporaryFolder = new TemporaryFolderAllocation();
- Console.WriteLine($"Using Temporary Folder: {temporaryFolder.FolderPath}");
- Task.Run(() => model.InstallReloadedAsync(temporaryFolder.FolderPath, false, false)).Wait();
- }
-
- private static void InstallNoGui()
- {
- var model = new MainWindowViewModel();
- using var progressBar = SetupCliInstall("Installing (No GUI)", model);
- Task.Run(() => model.InstallReloadedAsync()).Wait();
- }
-
- private static ProgressBar SetupCliInstall(string progressText, MainWindowViewModel model)
- {
- var progressBar = new ProgressBar();
- var progress = progressBar.HierarchicalProgress;
- model = new MainWindowViewModel();
- model.PropertyChanged += (sender, eventArgs) =>
- {
- if (eventArgs.PropertyName == nameof(model.Progress))
- progress.Report(model.Progress / 100.0f, progressText);
- };
-
- return progressBar;
- }
}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer/Reloaded.Mod.Installer.csproj b/source/Reloaded.Mod.Installer/Reloaded.Mod.Installer.csproj
index 6f52c0f2..ce7df3d9 100644
--- a/source/Reloaded.Mod.Installer/Reloaded.Mod.Installer.csproj
+++ b/source/Reloaded.Mod.Installer/Reloaded.Mod.Installer.csproj
@@ -1,8 +1,8 @@
-
+
Exe
- NET472
+ NET472;net8.0-windows
true
preview
app.manifest
@@ -11,27 +11,49 @@
appicon.ico
true
Reloaded.Mod.Installer.Program
+ portable
+ <_SuppressWpfTrimError>true
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
-
+
all
runtime; compile; build; native; contentfiles; analyzers; buildtransitive
-
-
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
all
+
+
-
+
+
diff --git a/source/Reloaded.Mod.Installer/Usings.cs b/source/Reloaded.Mod.Installer/Usings.cs
index c49548d9..31b1879a 100644
--- a/source/Reloaded.Mod.Installer/Usings.cs
+++ b/source/Reloaded.Mod.Installer/Usings.cs
@@ -1,13 +1,10 @@
global using HandyControl.Controls;
-global using Onova.Services;
global using Reloaded.Mod.Installer.DependencyInstaller;
global using Reloaded.Mod.Installer.DependencyInstaller.IO;
-global using Reloaded.Mod.Installer.Utilities;
global using System;
global using System.ComponentModel;
global using System.Diagnostics;
global using System.IO;
-global using System.Linq;
global using System.Runtime.InteropServices;
global using System.Runtime.InteropServices.ComTypes;
global using System.Text;
diff --git a/source/Reloaded.Mod.Installer/Utilities/IOEx.cs b/source/Reloaded.Mod.Installer/Utilities/IOEx.cs
deleted file mode 100644
index 500d023f..00000000
--- a/source/Reloaded.Mod.Installer/Utilities/IOEx.cs
+++ /dev/null
@@ -1,123 +0,0 @@
-using Path = System.IO.Path;
-
-namespace Reloaded.Mod.Installer.Utilities
-{
- // ReSharper disable once InconsistentNaming
- public static class IOEx
- {
- ///
- /// Moves a directory from a given source path to a target path, overwriting all files.
- ///
- /// The source path.
- /// The target path.
- public static void MoveDirectory(string source, string target)
- {
- MoveDirectory(source, target, (x, y) =>
- {
- File.Copy(x, y, true);
- File.Delete(x);
- });
- }
-
- ///
- /// Copies a directory from a given source path to a target path, overwriting all files.
- ///
- /// The source path.
- /// The target path.
- public static void CopyDirectory(string source, string target)
- {
- MoveDirectory(source, target, (x, y) => File.Copy(x, y, true));
- }
-
- private static void MoveDirectory(string source, string target, Action moveDirectoryAction)
- {
- Directory.CreateDirectory(target);
-
- // Get all files in source directory.
- var sourceFilePaths = Directory.EnumerateFiles(source);
-
- // Move them.
- foreach (var sourceFilePath in sourceFilePaths)
- {
- // Get destination file path
- var destFileName = Path.GetFileName(sourceFilePath);
- var destFilePath = Path.Combine(target, destFileName);
-
- while (File.Exists(destFilePath) && !CheckFileAccess(destFilePath, FileMode.Open, FileAccess.Write))
- Thread.Sleep(100);
-
- if (File.Exists(destFilePath))
- File.Delete(destFilePath);
-
- moveDirectoryAction(sourceFilePath, destFilePath);
- }
-
- // Get all subdirectories in source directory.
- var sourceSubDirPaths = Directory.EnumerateDirectories(source);
-
- // Recursively move them.
- foreach (var sourceSubDirPath in sourceSubDirPaths)
- {
- var destSubDirName = Path.GetFileName(sourceSubDirPath);
- var destSubDirPath = Path.Combine(target, destSubDirName);
- MoveDirectory(sourceSubDirPath, destSubDirPath, moveDirectoryAction);
- }
- }
-
- ///
- /// Tries to open a stream for a specified file.
- /// Returns null if it fails due to file lock.
- ///
- public static FileStream? TryOpenOrCreateFileStream(string filePath, FileMode mode = FileMode.OpenOrCreate, FileAccess access = FileAccess.ReadWrite)
- {
- try
- {
- return File.Open(filePath, mode, access);
- }
- catch (UnauthorizedAccessException)
- {
- return null;
- }
- catch (IOException)
- {
- return null;
- }
- }
-
- ///
- /// Checks whether a file with a specific path can be opened.
- ///
- public static bool CheckFileAccess(string filePath, FileMode mode = FileMode.Open, FileAccess access = FileAccess.ReadWrite)
- {
- using var stream = TryOpenOrCreateFileStream(filePath, mode, access);
- return stream != null;
- }
-
- ///
- /// Tries to delete a directory, if possible.
- ///
- public static void TryDeleteDirectory(string path, bool recursive = true)
- {
- try { Directory.Delete(path, recursive); }
- catch (Exception) { /* Ignored */ }
- }
-
- ///
- /// Tries to delete a directory, if possible.
- ///
- public static void TryDeleteFile(string path)
- {
- try { File.Delete(path); }
- catch (Exception) { /* Ignored */ }
- }
-
- ///
- /// Tries to empty a directory, if possible.
- ///
- public static void TryEmptyDirectory(string path)
- {
- TryDeleteDirectory(path, true);
- Directory.CreateDirectory(path);
- }
- }
-}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer/Utilities/Native.cs b/source/Reloaded.Mod.Installer/Utilities/Native.cs
deleted file mode 100644
index 32e84064..00000000
--- a/source/Reloaded.Mod.Installer/Utilities/Native.cs
+++ /dev/null
@@ -1,7 +0,0 @@
-namespace Reloaded.Mod.Installer.Utilities;
-
-public static class Native
-{
- [DllImport("Shlwapi.dll", EntryPoint = "PathIsDirectoryEmptyW", CharSet = CharSet.Unicode)]
- public static extern bool IsDirectoryEmpty(string directory);
-}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Installer/Utilities/ShellLink.cs b/source/Reloaded.Mod.Installer/Utilities/ShellLink.cs
deleted file mode 100644
index 3553e282..00000000
--- a/source/Reloaded.Mod.Installer/Utilities/ShellLink.cs
+++ /dev/null
@@ -1,32 +0,0 @@
-namespace Reloaded.Mod.Installer.Utilities;
-
-[ComImport]
-[Guid("00021401-0000-0000-C000-000000000046")]
-internal class ShellLink
-{
-}
-
-[ComImport]
-[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
-[Guid("000214F9-0000-0000-C000-000000000046")]
-internal interface IShellLink
-{
- void GetPath([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszFile, int cchMaxPath, out IntPtr pfd, int fFlags);
- void GetIDList(out IntPtr ppidl);
- void SetIDList(IntPtr pidl);
- void GetDescription([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszName, int cchMaxName);
- void SetDescription([MarshalAs(UnmanagedType.LPWStr)] string pszName);
- void GetWorkingDirectory([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszDir, int cchMaxPath);
- void SetWorkingDirectory([MarshalAs(UnmanagedType.LPWStr)] string pszDir);
- void GetArguments([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszArgs, int cchMaxPath);
- void SetArguments([MarshalAs(UnmanagedType.LPWStr)] string pszArgs);
- void GetHotkey(out short pwHotkey);
- void SetHotkey(short wHotkey);
- void GetShowCmd(out int piShowCmd);
- void SetShowCmd(int iShowCmd);
- void GetIconLocation([Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszIconPath, int cchIconPath, out int piIcon);
- void SetIconLocation([MarshalAs(UnmanagedType.LPWStr)] string pszIconPath, int iIcon);
- void SetRelativePath([MarshalAs(UnmanagedType.LPWStr)] string pszPathRel, int dwReserved);
- void Resolve(IntPtr hwnd, int fFlags);
- void SetPath([MarshalAs(UnmanagedType.LPWStr)] string pszFile);
-}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Program.cs b/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Program.cs
deleted file mode 100644
index 7a1bd7bb..00000000
--- a/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Program.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Reloaded.Mod.Launcher.Kernel32AddressDumper
-{
- static class Program
- {
- [STAThread]
- static void Main()
- {
- nuint loadLibraryAddress = GetLoadLibraryAddress();
- byte[] bytes = BitConverter.GetBytes((long) loadLibraryAddress);
-
- var file = MemoryMappedFile.OpenExisting(SharedConstants.Kernel32AddressDumperMemoryMappedFileName);
- var viewStream = file.CreateViewStream();
- viewStream.Write(bytes, 0, bytes.Length);
- }
-
- private static nuint GetLoadLibraryAddress()
- {
- var kernel32Handle = LoadLibraryW("kernel32");
- return GetProcAddress(kernel32Handle, "LoadLibraryW");
- }
-
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- private static extern nuint LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
- private static extern nuint GetProcAddress(nuint hModule, string procName);
- }
-}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Reloaded.Mod.Launcher.Kernel32AddressDumper.csproj b/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Reloaded.Mod.Launcher.Kernel32AddressDumper.csproj
deleted file mode 100644
index 37388245..00000000
--- a/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Reloaded.Mod.Launcher.Kernel32AddressDumper.csproj
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- {B0BFEE3E-4A56-4F9F-95A8-9229FC74D46E}
- WinExe
- preview
- Kernel32AddressDumper
- net7.0-windows
- false
- ..\Output\Launcher\Loader\
- Reloaded.Mod.Launcher.Kernel32AddressDumper
- Reloaded.Mod.Launcher.Kernel32AddressDumper
- Copyright © 2019
- x86
- true
-
-
-
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Usings.cs b/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Usings.cs
deleted file mode 100644
index 76029918..00000000
--- a/source/Reloaded.Mod.Launcher.Kernel32AddressDumper/Usings.cs
+++ /dev/null
@@ -1,3 +0,0 @@
-global using Reloaded.Mod.Shared;
-global using System;
-global using System.IO.MemoryMappedFiles;
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Lib/Assets/Loader/Asi/UltimateAsiLoader.7z b/source/Reloaded.Mod.Launcher.Lib/Assets/Loader/Asi/UltimateAsiLoader.7z
index ba9d99f3..a0c105ec 100644
Binary files a/source/Reloaded.Mod.Launcher.Lib/Assets/Loader/Asi/UltimateAsiLoader.7z and b/source/Reloaded.Mod.Launcher.Lib/Assets/Loader/Asi/UltimateAsiLoader.7z differ
diff --git a/source/Reloaded.Mod.Launcher.Lib/Assets/Microsoft/replace-files-with-itself.exe b/source/Reloaded.Mod.Launcher.Lib/Assets/Microsoft/replace-files-with-itself.exe
new file mode 100644
index 00000000..27832cd6
Binary files /dev/null and b/source/Reloaded.Mod.Launcher.Lib/Assets/Microsoft/replace-files-with-itself.exe differ
diff --git a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/AddApplicationCommand.cs b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/AddApplicationCommand.cs
index f366887f..da7a1041 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/AddApplicationCommand.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/AddApplicationCommand.cs
@@ -58,11 +58,30 @@ static string GetProductName(string exePath)
return Path.GetFileName(exePath);
}
}
-
+
try { exePath = SymlinkResolver.GetFinalPathName(exePath); }
catch (Exception e) { Errors.HandleException(e, Resources.ErrorAddApplicationCantReadSymlink.Get()); }
-
- var config = new ApplicationConfig(Path.GetFileName(exePath).ToLower(), GetProductName(exePath), exePath, Path.GetDirectoryName(exePath));
+
+ // Warn if OneDrive or NonAsciiChars detected in Game Path
+ bool hasNonAsciiChars = exePath.Any(c => c > 127);
+ if (exePath.Contains("OneDrive") || hasNonAsciiChars)
+ {
+ var confirmAddAnyway = Actions.DisplayMessagebox.Invoke(Resources.ProblematicPathTitle.Get(), Resources.ProblematicPathAppDescription.Get(), new Actions.DisplayMessageBoxParams()
+ {
+ StartupLocation = Actions.WindowStartupLocation.CenterScreen,
+ Type = Actions.MessageBoxType.OkCancel
+ });
+
+ if (!confirmAddAnyway)
+ {
+ param.ResultCreatedApplication = false;
+ return;
+ }
+ }
+
+ var isMsStore = TryUnprotectGamePassGame.TryIt(exePath);
+ var appId = ApplicationConfig.AliasAppId(Path.GetFileName(exePath).ToLower());
+ var config = new ApplicationConfig(appId, GetProductName(exePath), exePath, Path.GetDirectoryName(exePath));
// Set AppName if empty & Ensure no duplicate ID.
if (string.IsNullOrEmpty(config.AppName))
@@ -87,6 +106,9 @@ static string GetProductName(string exePath)
Console.WriteLine(e);
}
+ // Try to auto deploy ASI Loader.
+ HandleAddedMsStoreBinary(isMsStore, applicationConfigFile, config);
+
// Write file to disk.
Directory.CreateDirectory(applicationDirectory);
IConfig.ToPath(config, applicationConfigFile);
@@ -99,6 +121,27 @@ static string GetProductName(string exePath)
param.ResultCreatedApplication = true;
}
+ internal static void HandleAddedMsStoreBinary(bool isMsStore, string applicationConfigFile, ApplicationConfig config)
+ {
+ if (isMsStore)
+ {
+ var deployer = new AsiLoaderDeployer(new PathTuple(applicationConfigFile, config));
+ if (deployer.CanDeploy())
+ {
+ deployer.DeployAsiLoader(out var loaderPath, out var bootstrapperPath);
+ DeployAsiLoaderCommand.PrintDeployedAsiLoaderInfo(loaderPath!, bootstrapperPath);
+ config.DontInject = true;
+ }
+ else
+ {
+ // For GamePass, we can't dll inject, so we need to throw error to user screen.
+ Actions.DisplayMessagebox.Invoke(Resources.AsiLoaderDialogTitle.Get(), Resources.AsiLoaderGamePassAutoInstallFail.Get());
+ }
+ }
+
+ config.IsMsStore = isMsStore;
+ }
+
private void ApplicationsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
var newConfig = _configService.Items.FirstOrDefault(x => x.Config.AppId == _newConfig?.AppId);
diff --git a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/DeployAsiLoaderCommand.cs b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/DeployAsiLoaderCommand.cs
index f9594d0f..823be4dd 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/DeployAsiLoaderCommand.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/DeployAsiLoaderCommand.cs
@@ -31,7 +31,12 @@ public void Execute(object? parameter)
return;
_deployer.DeployAsiLoader(out string? loaderPath, out string bootstrapperPath);
- string deployedBootstrapper = $"{Resources.AsiLoaderDialogBootstrapperDeployed.Get()} {bootstrapperPath}";
+ PrintDeployedAsiLoaderInfo(bootstrapperPath, loaderPath);
+ }
+
+ internal static void PrintDeployedAsiLoaderInfo(string bootstrapperPath, string? loaderPath)
+ {
+ var deployedBootstrapper = $"{Resources.AsiLoaderDialogBootstrapperDeployed.Get()} {bootstrapperPath}";
if (loaderPath == null)
{
// Installed Bootstrapper but not loader.
@@ -39,7 +44,7 @@ public void Execute(object? parameter)
}
else
{
- string deployedLoader = $"{Resources.AsiLoaderDialogLoaderDeployed.Get()} {loaderPath}";
+ var deployedLoader = $"{Resources.AsiLoaderDialogLoaderDeployed.Get()} {loaderPath}";
Actions.DisplayMessagebox.Invoke(Resources.AsiLoaderDialogTitle.Get(), $"{deployedLoader}\n{deployedBootstrapper}");
}
}
diff --git a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/MakeShortcutCommand.cs b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/MakeShortcutCommand.cs
index ac1d1ccb..27661fa7 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/MakeShortcutCommand.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/MakeShortcutCommand.cs
@@ -62,7 +62,7 @@ public void Execute(object? parameter)
// Save the shortcut.
var file = (IPersistFile) shell;
- var link = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), $"{_config?.Config.AppName} (Reloaded).lnk");
+ var link = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory), $"{_config?.Config.AppName.SanitizeFileName()} (Reloaded).lnk");
file.Save(link, false);
Actions.DisplayMessagebox?.Invoke(Resources.AddAppShortcutCreatedTitle.Get(),
diff --git a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/QueryCommunityIndexCommand.cs b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/QueryCommunityIndexCommand.cs
index ae0e4246..65da3f7f 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Commands/Application/QueryCommunityIndexCommand.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Commands/Application/QueryCommunityIndexCommand.cs
@@ -106,6 +106,8 @@ public static void ApplyIndexEntry(AppItem indexApp, PathTuple
diff --git a/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/DeleteModCommand.cs b/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/DeleteModCommand.cs
index f30b3976..af3a85b2 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/DeleteModCommand.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/DeleteModCommand.cs
@@ -20,7 +20,15 @@ public DeleteModCommand(PathTuple? modTuple)
///
public void Execute(object? parameter)
{
- // Delete folder contents.
+ var deleteConfirm = Actions.DisplayMessagebox.Invoke(Resources.DeleteModDialogTitle.Get(), string.Format(Resources.DeleteModDialogDescription.Get(), _modTuple.Config.ModName), new Actions.DisplayMessageBoxParams()
+ {
+ StartupLocation = Actions.WindowStartupLocation.CenterScreen,
+ Type = Actions.MessageBoxType.OkCancel
+ });
+
+ if (!deleteConfirm)
+ return;
+
var directory = Path.GetDirectoryName(_modTuple!.Path) ?? throw new InvalidOperationException(Resources.ErrorFailedToGetDirectoryOfMod.Get());
Directory.Delete(directory, true);
}
diff --git a/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/PublishModCommand.cs b/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/PublishModCommand.cs
index 5c6cb7cf..b964a926 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/PublishModCommand.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/PublishModCommand.cs
@@ -21,6 +21,9 @@ public PublishModCommand(PathTuple? modTuple)
///
public void Execute(object? parameter)
{
+ if (Update.CheckMissingDependencies().AllAvailable)
+ Task.Run(async () => await DependencyMetadataWriterFactory.ExecuteAllAsync(IoC.Get())).Wait();
+
if (!NuGetVersion.TryParse(_modTuple!.Config.ModVersion, out var version))
{
Actions.DisplayMessagebox(Resources.ErrorInvalidModConfigTitle.Get(), Resources.ErrorInvalidModConfigDescription.Get());
diff --git a/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/SetModImageCommand.cs b/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/SetModImageCommand.cs
index 7961f7ae..2e6d3eab 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/SetModImageCommand.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Commands/Mod/SetModImageCommand.cs
@@ -32,7 +32,9 @@ public void Execute(object? parameter)
string iconPath = Path.Combine(modDirectory, iconFileName);
// Copy image and set config file path.
- File.Copy(imagePath, iconPath, true);
+ if (!imagePath.Equals(iconPath, StringComparison.OrdinalIgnoreCase))
+ File.Copy(imagePath, iconPath, true);
+
_modTuple.Config.ModIcon = iconFileName;
_modTuple.Save();
}
diff --git a/source/Reloaded.Mod.Launcher.Lib/Interop/IResourceFileSelector.cs b/source/Reloaded.Mod.Launcher.Lib/Interop/IResourceFileSelector.cs
index d6a0974c..38c76715 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Interop/IResourceFileSelector.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Interop/IResourceFileSelector.cs
@@ -19,4 +19,9 @@ public interface IResourceFileSelector : INotifyPropertyChanged
/// Executed after a new XAML file has been set as the source.
///
event Action NewFileSet;
+
+ ///
+ /// Selects an existing available XAML file with a matching file name.
+ ///
+ void SelectXamlFileByName(string fileName);
}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Lib/Misc/CompatibilityDialogs.cs b/source/Reloaded.Mod.Launcher.Lib/Misc/CompatibilityDialogs.cs
index fac103c6..56803195 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Misc/CompatibilityDialogs.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Misc/CompatibilityDialogs.cs
@@ -1,3 +1,4 @@
+using Environment = Reloaded.Mod.Shared.Environment;
namespace Reloaded.Mod.Launcher.Lib.Misc;
///
@@ -12,7 +13,7 @@ public static class CompatibilityDialogs
public static bool WineShowLaunchDialog()
{
var loaderSettings = IoC.Get();
- if (loaderSettings.SkipWineLaunchWarning)
+ if (loaderSettings.SkipWineLaunchWarning || !Environment.RequiresWineLaunchDialog)
return true;
return Actions.ShowRunAppViaWineDialog();
diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/Model/Dialog/ObservablePack.cs b/source/Reloaded.Mod.Launcher.Lib/Models/Model/Dialog/ObservablePack.cs
index 96bbd651..837178b0 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Models/Model/Dialog/ObservablePack.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Models/Model/Dialog/ObservablePack.cs
@@ -55,6 +55,7 @@ public ObservablePack(ReloadedPackReader packReader)
obItem.Name = item.Name;
obItem.Readme = item.Readme;
obItem.Summary = item.Summary;
+ obItem.ReleaseMetadataFileName = item.ReleaseMetadataFileName;
foreach (var image in item.ImageFiles)
{
var data = new MemoryStream(packReader.GetImage(image.Path));
diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/ConfigureModsViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/ConfigureModsViewModel.cs
index 1a6cd42a..68d9e226 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/ConfigureModsViewModel.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/ConfigureModsViewModel.cs
@@ -133,26 +133,53 @@ private void BuildModList()
///
private List GetInitialModSet(PathTuple[] modsForThisApp, PathTuple applicationTuple)
{
- // Note: Must put items in top to bottom load order.
- var enabledModIds = applicationTuple.Config.EnabledMods;
-
// Get dictionary of mods for this app by Mod ID
- var modDictionary = new Dictionary>();
+ var modDictionary = new Dictionary>();
foreach (var mod in modsForThisApp)
modDictionary[mod.Config.ModId] = mod;
- // Add enabled mods.
var totalModList = new List(modsForThisApp.Length);
- foreach (var enabledModId in enabledModIds)
+
+ if (applicationTuple.Config.PreserveDisabledModOrder)
{
- if (modDictionary.ContainsKey(enabledModId))
+ // Modern Behaviour: Mod Order is Preserved
+ var enabledModIds = applicationTuple.Config.EnabledMods.Where(modDictionary.ContainsKey).Distinct().ToArray();
+ var sortedModIds = applicationTuple.Config.SortedMods.Where(modDictionary.ContainsKey).Distinct().ToArray();
+
+ var enabledModIdSet = enabledModIds.ToHashSet();
+ var sortedModIdSet = sortedModIds.ToHashSet();
+
+ // Add sorted mods.
+ foreach (var sortedModId in sortedModIds)
+ totalModList.Add(MakeSaveSubscribedModEntry(enabledModIdSet.Contains(sortedModId), modDictionary[sortedModId]));
+
+ // Add enabled mods that were not in the sorted mod collection.
+ // This can happen in case of config upgrade from an older version.
+ foreach (var enabledModId in enabledModIds.Where(x => !sortedModIdSet.Contains(x)))
totalModList.Add(MakeSaveSubscribedModEntry(true, modDictionary[enabledModId]));
+
+ // Add the remaining mods on the bottom of the list as disabled.
+ var remainingMods = modsForThisApp.Where(x => !enabledModIdSet.Contains(x.Config.ModId) && !sortedModIdSet.Contains(x.Config.ModId)).OrderBy(x => x.Config.ModName);
+ totalModList.AddRange(remainingMods.Select(x => MakeSaveSubscribedModEntry(false, x)));
+ }
+ else
+ {
+ // Classic Behaviour: Disabled Mods are Alphabetical by Name
+ var enabledModIds = applicationTuple.Config.EnabledMods;
+
+ // Add enabled mods.
+ foreach (var enabledModId in enabledModIds)
+ {
+ if (modDictionary.ContainsKey(enabledModId))
+ totalModList.Add(MakeSaveSubscribedModEntry(true, modDictionary[enabledModId]));
+ }
+
+ // Add disabled mods.
+ var enabledModIdSet = enabledModIds.ToHashSet();
+ var disabledMods = modsForThisApp.Where(x => !enabledModIdSet.Contains(x.Config.ModId)).OrderBy(x => x.Config.ModName);
+ totalModList.AddRange(disabledMods.Select(x => MakeSaveSubscribedModEntry(false, x)));
}
- // Add disabled mods.
- var enabledModIdSet = applicationTuple.Config.EnabledMods.ToHashSet();
- var disabledMods = modsForThisApp.Where(x => !enabledModIdSet.Contains(x.Config.ModId)).OrderBy(x => x.Config.ModName);
- totalModList.AddRange(disabledMods.Select(x => MakeSaveSubscribedModEntry(false, x)));
return totalModList;
}
@@ -207,6 +234,11 @@ private async Task SaveApplication()
try
{
+ // Don't update this if user doesn't want to preserve their order, in
+ // case the user wants to backtrack and revert. 'e.g. I want to 'try' the other option'.
+ if (ApplicationTuple.Config.PreserveDisabledModOrder)
+ ApplicationTuple.Config.SortedMods = AllMods!.Select(x => x.Tuple.Config.ModId).ToArray();
+
ApplicationTuple.Config.EnabledMods = AllMods!.Where(x => x.Enabled == true).Select(x => x.Tuple.Config.ModId).ToArray();
await ApplicationTuple.SaveAsync(_saveToken.Token);
}
diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/EditAppViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/EditAppViewModel.cs
index 65f57eb1..6564f829 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/EditAppViewModel.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/Application/EditAppViewModel.cs
@@ -119,15 +119,45 @@ public void SetAppImage()
///
public void SetNewExecutablePath()
{
- var result = SelectEXEFile();
- if (string.IsNullOrEmpty(result))
+ var newFilePath = SelectEXEFile();
+ if (string.IsNullOrEmpty(newFilePath))
return;
- result = SymlinkResolver.GetFinalPathName(result);
- if (!Path.GetFileName(Application.Config.AppLocation).Equals(Path.GetFileName(result), StringComparison.OrdinalIgnoreCase))
+ var isMsStore = TryUnprotectGamePassGame.TryIt(newFilePath);
+ newFilePath = SymlinkResolver.GetFinalPathName(newFilePath);
+ if (!Path.GetFileName(Application.Config.AppLocation)
+ .Equals(Path.GetFileName(newFilePath), StringComparison.OrdinalIgnoreCase))
Actions.DisplayMessagebox(Resources.AddAppWarningTitle.Get(), Resources.AddAppWarning.Get());
- Application.Config.AppLocation = result;
+ // Update the AppLocation to the new executable path
+ Application.Config.AppLocation = newFilePath;
+ Application.Config.WorkingDirectory = CalculateNewRelativeDirectory(newFilePath);
+
+ // Handle MS store DRM and update.
+ AddApplicationCommand.HandleAddedMsStoreBinary(isMsStore, Application.Path, Application.Config);
+ Application.Save();
+ }
+
+ private string CalculateNewRelativeDirectory(string newFilePath)
+ {
+ // Store old configuration values
+ var oldWorkingDirectory = Application.Config.WorkingDirectory;
+ var oldAppLocation = Application.Config.AppLocation;
+ var exeParent = Path.GetDirectoryName(newFilePath)!;
+
+ try
+ {
+ var relativePath = Path.GetRelativePath(oldWorkingDirectory, oldAppLocation);
+ // +1 to remove final backslash.
+ var newWorkingDirectory = newFilePath.Substring(0, newFilePath.Length - (relativePath.Length + 1));
+ return Directory.Exists(newWorkingDirectory)
+ ? newWorkingDirectory
+ : exeParent;
+ }
+ catch (Exception)
+ {
+ return exeParent;
+ }
}
///
@@ -163,7 +193,10 @@ private void OnAppLocationChanged(object? sender, PropertyChangedEventArgs e)
private string SelectEXEFile()
{
- var dialog = new VistaOpenFileDialog();
+ // This is a Save dialog because Open dialog checks read privileges,
+ // and that will not work with read protected MS Store/Gamepass executables.
+ var dialog = new VistaSaveFileDialog();
+ dialog.OverwritePrompt = false;
dialog.Title = Resources.AddAppExecutableTitle.Get();
dialog.Filter = $"{Resources.AddAppExecutableFilter.Get()} (*.exe)|*.exe";
dialog.FileName = ApplicationConfig.GetAbsoluteAppLocation(Application);
diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/DownloadPackagesViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/DownloadPackagesViewModel.cs
index 174a4836..4d946b2e 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/DownloadPackagesViewModel.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/DownloadPackagesViewModel.cs
@@ -297,13 +297,16 @@ public void Execute(object? parameter)
_canExecute = false;
RaiseCanExecute(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
- ActionWrappers.ExecuteWithApplicationDispatcher(() =>
+ ActionWrappers.ExecuteWithApplicationDispatcher(async () =>
{
var viewModel = new DownloadPackageViewModel(_package!, IoC.Get());
- _ = viewModel.StartDownloadAsync(); // Fire and forget.
+ var dl = viewModel.StartDownloadAsync(); // Fire and forget.
Actions.ShowFetchPackageDialog(viewModel);
+
+ await dl;
+ await Update.ResolveMissingPackagesAsync();
});
-
+
_canExecute = true;
RaiseCanExecute(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
diff --git a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/SettingsPageViewModel.cs b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/SettingsPageViewModel.cs
index 1c1cfd8b..b497beba 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/SettingsPageViewModel.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Models/ViewModel/SettingsPageViewModel.cs
@@ -32,11 +32,6 @@ public class SettingsPageViewModel : ObservableObject
///
public string Copyright { get; set; }
- ///
- /// The .NET Runtime version the Launcher is running on.
- ///
- public string RuntimeVersion { get; set; }
-
///
/// Configuration for the mod loader.
///
@@ -73,13 +68,9 @@ public SettingsPageViewModel(ApplicationConfigService appConfigService, ModConfi
}
catch (Exception) { /* Non-critical, could happen on CIFS file share, we can ignore. */ }
- Copyright = Regex.Replace(copyRightStr, @"\|.*", $"| {Version.GetReleaseVersion()!.ToNormalizedString()}");
- RuntimeVersion = $"Core: {RuntimeInformation.FrameworkDescription}";
- ActionWrappers.ExecuteWithApplicationDispatcher(() =>
- {
- SelectCurrentLanguage();
- SelectCurrentTheme();
- });
+ copyRightStr = Regex.Replace(copyRightStr, @"\|.*", $"| {Version.GetReleaseVersion()!.ToNormalizedString()}");
+ copyRightStr += $" | {RuntimeInformation.FrameworkDescription}";
+ Copyright = copyRightStr;
}
///
@@ -111,22 +102,12 @@ public async Task SaveNewThemeAsync()
{
LoaderConfig.ThemeFile = ThemeSelector.File;
await SaveConfigAsync();
-
+
// TODO: This is a bug workaround for where the language ComboBox gets reset after a theme change.
- SelectCurrentLanguage();
+ LanguageSelector!.SelectXamlFileByName(Path.GetFileName(LoaderConfig.LanguageFile!));
}
}
- ///
- /// Auto selects the desired language file from a list based on path.
- ///
- public void SelectCurrentLanguage() => SelectXamlFile(LanguageSelector, LoaderConfig.LanguageFile);
-
- ///
- /// Auto selects the desired theme file from a list based on path.
- ///
- public void SelectCurrentTheme() => SelectXamlFile(ThemeSelector, LoaderConfig.ThemeFile);
-
///
/// Opens the location where the log files are stored.
///
@@ -137,20 +118,7 @@ public async Task SaveNewThemeAsync()
///
public void OpenConfigFile() => ProcessExtensions.OpenFileWithDefaultProgram(Paths.LoaderConfigPath);
- private void SelectXamlFile(IResourceFileSelector? selector, string fileName)
- {
- if (selector == null)
- return;
- foreach (var file in selector.Files)
- {
- if (file != fileName)
- continue;
-
- selector.File = file;
- break;
- }
- }
/* Functions */
private void UpdateTotalApplicationsInstalled() => TotalApplicationsInstalled = AppConfigService.Items.Count;
diff --git a/source/Reloaded.Mod.Launcher.Lib/Reloaded.Mod.Launcher.Lib.csproj b/source/Reloaded.Mod.Launcher.Lib/Reloaded.Mod.Launcher.Lib.csproj
index ae06535a..8f7b1f4e 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Reloaded.Mod.Launcher.Lib.csproj
+++ b/source/Reloaded.Mod.Launcher.Lib/Reloaded.Mod.Launcher.Lib.csproj
@@ -8,14 +8,17 @@
true
enable
enable
+ portable
-
+
+
+
-
+
all
@@ -34,6 +37,10 @@
Always
true
+
+ Always
+ true
+
diff --git a/source/Reloaded.Mod.Launcher.Lib/Setup.cs b/source/Reloaded.Mod.Launcher.Lib/Setup.cs
index cd1e5876..b109bf06 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Setup.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Setup.cs
@@ -125,7 +125,7 @@ public static bool TryGetIncompatibleMods(PathTuple applicati
/// Quickly checks for any missing dependencies amongst available mods
/// and opens a menu allowing to download if mods are unavailable.
///
- ///
+ /// True if there are missing deps, else false.
public static async Task CheckForMissingModDependenciesAsync()
{
var deps = Update.CheckMissingDependencies();
@@ -150,7 +150,16 @@ private static Task DoSanityTests()
// Needs to be ran after SetupViewModelsAsync
var apps = IoC.GetConstant().Items;
var mods = IoC.GetConstant().Items.ToArray();
-
+
+ // Unprotect all MS Store titles if needed.
+ foreach (var app in apps)
+ {
+ if (app.Config.IsMsStore)
+ {
+ TryUnprotectGamePassGame.TryIgnoringErrors(app.Config.AppLocation);
+ }
+ }
+
// Enforce compatibility non-async, since this is unlikely to do anything.
foreach (var app in apps)
EnforceModCompatibility(app, mods);
@@ -301,7 +310,12 @@ private static void SetLoaderPaths(LoaderConfig config, string launcherDirectory
///
private static async Task CheckForUpdatesAsync()
{
- await DependencyMetadataWriterFactory.ExecuteAllAsync(IoC.Get());
+ // The action below is destructive.
+ // It may remove update metadata for missing dependencies.
+ // Don't run this unless we have all the mods.
+ if (Update.CheckMissingDependencies().AllAvailable)
+ await DependencyMetadataWriterFactory.ExecuteAllAsync(IoC.Get());
+
await Update.CheckForLoaderUpdatesAsync();
await Task.Run(Update.CheckForModUpdatesAsync);
await CheckForMissingModDependenciesAsync();
diff --git a/source/Reloaded.Mod.Launcher.Lib/Startup.cs b/source/Reloaded.Mod.Launcher.Lib/Startup.cs
index b64c1796..091edd40 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Startup.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Startup.cs
@@ -10,7 +10,7 @@ namespace Reloaded.Mod.Launcher.Lib;
///
public static class Startup
{
- private static Dictionary _commandLineArguments = new Dictionary();
+ private static Dictionary _commandLineArguments = new();
///
/// Populates Command Lin
@@ -22,13 +22,19 @@ public static bool HandleCommandLineArgs()
PopulateCommandLineArgs();
// Check if Kill Process
+ bool forceInject = false;
if (_commandLineArguments.TryGetValue(Constants.ParameterKill, out string? processId))
+ {
KillProcessWithId(processId);
+ // for outdated bootstrappers, assume injection is required when kill specified.
+ // otherwise follow regular setting
+ forceInject = true;
+ }
// Check if Launch
if (_commandLineArguments.TryGetValue(Constants.ParameterLaunch, out string? applicationToLaunch))
{
- LaunchApplicationAndExit(applicationToLaunch);
+ LaunchApplicationAndExit(applicationToLaunch, forceInject);
result = true;
}
@@ -69,7 +75,7 @@ private static void KillProcessWithId(string processId)
}
[MethodImpl(MethodImplOptions.NoInlining)]
- private static void LaunchApplicationAndExit(string applicationToLaunch)
+ private static void LaunchApplicationAndExit(string applicationToLaunch, bool forceInject)
{
// Acquire arguments
var loaderConfig = IoC.Get();
@@ -85,17 +91,18 @@ private static void LaunchApplicationAndExit(string applicationToLaunch)
arguments = $"{arguments} {application.Config.AppArguments}";
_commandLineArguments.TryGetValue(Constants.ParameterWorkingDirectory, out var workingDirectory);
-
+ var inject = !application!.Config.DontInject | forceInject;
+
// Show warning for Wine users.
if (Shared.Environment.IsWine)
{
// Set up UI Resources, since they're needed for the dialog.
if (CompatibilityDialogs.WineShowLaunchDialog())
- StartGame(applicationToLaunch, arguments, workingDirectory);
+ StartGame(applicationToLaunch, arguments, workingDirectory, inject);
}
else
{
- StartGame(applicationToLaunch, arguments, workingDirectory);
+ StartGame(applicationToLaunch, arguments, workingDirectory, inject);
}
}
@@ -111,6 +118,7 @@ private static void DownloadModAndExit(string downloadUrl)
_ = viewModel.StartDownloadAsync();
Actions.ShowFetchPackageDialog(viewModel);
+ Update.ResolveMissingPackages();
Actions.DisplayMessagebox(Resources.PackageDownloaderDownloadCompleteTitle.Get(), Resources.PackageDownloaderDownloadCompleteDescription.Get(), new Actions.DisplayMessageBoxParams()
{
Type = Actions.MessageBoxType.Ok,
@@ -141,11 +149,11 @@ private static void OpenPackAndExit(string r2PackLocation)
private static void InitControllerSupport() => Actions.InitControllerSupport();
- private static void StartGame(string applicationToLaunch, string arguments, string? workingDirectory = null)
+ private static void StartGame(string applicationToLaunch, string arguments, string? workingDirectory, bool inject)
{
// Launch the application.
var launcher = ApplicationLauncher.FromLocationAndArguments(applicationToLaunch, arguments, workingDirectory);
- launcher.Start();
+ launcher.Start(inject);
}
private static void PopulateCommandLineArgs()
diff --git a/source/Reloaded.Mod.Launcher.Lib/Static/Errors.cs b/source/Reloaded.Mod.Launcher.Lib/Static/Errors.cs
index 0e7e2125..01b99b52 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Static/Errors.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Static/Errors.cs
@@ -1,3 +1,5 @@
+using System.Windows;
+
namespace Reloaded.Mod.Launcher.Lib.Static;
///
@@ -11,7 +13,23 @@ public static class Errors
public static void HandleException(Exception ex, string message = "")
{
if (!Debugger.IsAttached)
- Actions.SynchronizationContext.Send((x) => Actions.DisplayMessagebox?.Invoke(Resources.ErrorUnknown.Get(), $"{message}{ex.Message}\n{ex.StackTrace}"), null);
+ {
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ var isUiInitialized = Actions.SynchronizationContext != null && Actions.DisplayMessagebox != null;
+ if (isUiInitialized)
+ {
+ // Just in case of an error before proper UI init.
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
+ if (Actions.DisplayMessagebox != null)
+ {
+ Actions.DisplayMessagebox.Invoke(Resources.ErrorUnknown.Get(), $"{message}{ex.Message}\n{ex.StackTrace}");
+ }
+ else
+ {
+ MessageBox.Show($"{message}{ex.Message}\n{ex.StackTrace}", Resources.ErrorUnknown.Get());
+ }
+ }
+ }
else
Debugger.Break();
}
diff --git a/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs b/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs
index 5bde2241..fb849863 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Static/Resources.cs
@@ -197,4 +197,21 @@ public static void Init(IDictionaryResourceProvider provider)
public static IDictionaryResource SearchOptionSortViews { get; set; }
public static IDictionaryResource SearchOptionAscending { get; set; }
public static IDictionaryResource SearchOptionDescending { get; set; }
+
+ // Update 1.26.0: GamePass ASI Loader Auto Deploy
+ public static IDictionaryResource AsiLoaderGamePassAutoInstallFail { get; set; }
+
+ // Update 1.26.0: Drag & Drop Mods
+ public static IDictionaryResource DragDropInstalledModsTitle { get; set; }
+ public static IDictionaryResource DragDropInstalledModsDescription { get; set; }
+
+ // Update 1.28.4: Delete Mod Dialog
+ public static IDictionaryResource DeleteModDialogTitle { get; set; }
+ public static IDictionaryResource DeleteModDialogDescription { get; set; }
+
+ // Update 1.28.6: Problematic Path Warnings
+ public static IDictionaryResource ProblematicPathTitle { get; set; }
+ public static IDictionaryResource ProblematicPathAppDescription { get; set; }
+ public static IDictionaryResource ProblematicPathReloadedDescription { get; set; }
+ public static IDictionaryResource ProblematicPathModsDescription { get; set; }
}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Lib/Update.cs b/source/Reloaded.Mod.Launcher.Lib/Update.cs
index 972640ac..98a086ca 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Update.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Update.cs
@@ -1,4 +1,5 @@
using Reloaded.Mod.Loader.Update.Providers.GitHub;
+using Reloaded.Mod.Loader.Update.Providers.Index;
using Constants = Reloaded.Mod.Launcher.Lib.Misc.Constants;
using Version = Reloaded.Mod.Launcher.Lib.Utility.Version;
@@ -162,44 +163,127 @@ public static async Task ResolveMissingPackagesAsync(CancellationToken token = d
if (!HasInternetConnection)
return;
- ModDependencyResolveResult resolveResult = null!;
+ ModDependencyResolveResult? lastResolveResult = default;
+ ModDependencyResolveResult resolveResult;
do
{
- // Get missing dependencies for this update loop.
- var missingDeps = CheckMissingDependencies();
+ resolveResult = await GetMissingDependenciesToDownload(token);
+ if (resolveResult.FoundDependencies.Count <= 0)
+ break;
- // Get Dependencies
- var resolver = DependencyResolverFactory.GetInstance(IoC.Get());
-
- var results = new List>();
- foreach (var dependencyItem in missingDeps.Items)
- foreach (var dependency in dependencyItem.Dependencies)
- results.Add(resolver.ResolveAsync(dependency, dependencyItem.Mod.PluginData, token));
-
- await Task.WhenAll(results);
+ if (IsSameAsLast(resolveResult, lastResolveResult))
+ {
+ ShowStuckInDownloadLoopDialog(resolveResult);
+ break;
+ }
- // Merge Results
- resolveResult = ModDependencyResolveResult.Combine(results.Select(x => x.Result));
+ lastResolveResult = resolveResult;
DownloadPackages(resolveResult, token);
}
- while (resolveResult.FoundDependencies.Count > 0);
+ while (true);
if (resolveResult.NotFoundDependencies.Count > 0)
+ ShowMissingPackagesDialog(resolveResult);
+ }
+
+ ///
+ /// Resolves a list of missing packages.
+ ///
+ public static void ResolveMissingPackages()
+ {
+ if (!HasInternetConnection)
+ return;
+
+ ModDependencyResolveResult? lastResolveResult = default;
+ ModDependencyResolveResult resolveResult;
+
+ do
{
- ActionWrappers.ExecuteWithApplicationDispatcher(() =>
+ resolveResult = Task.Run(async () => await GetMissingDependenciesToDownload(default)).GetAwaiter().GetResult();
+ if (resolveResult.FoundDependencies.Count <= 0)
+ break;
+
+ if (IsSameAsLast(resolveResult, lastResolveResult))
{
- Actions.DisplayMessagebox(Resources.ErrorMissingDependency.Get(),
- $"{Resources.FetchNugetNotFoundMessage.Get()}\n\n" +
- $"{string.Join('\n', resolveResult.NotFoundDependencies)}\n\n" +
- $"{Resources.FetchNugetNotFoundAdvice.Get()}",
- new Actions.DisplayMessageBoxParams()
- {
- Type = Actions.MessageBoxType.Ok,
- StartupLocation = Actions.WindowStartupLocation.CenterScreen
- });
- });
+ ShowStuckInDownloadLoopDialog(resolveResult);
+ break;
+ }
+
+ DownloadPackages(resolveResult);
+ lastResolveResult = resolveResult;
+ }
+ while (true);
+
+ if (resolveResult.NotFoundDependencies.Count > 0)
+ ShowMissingPackagesDialog(resolveResult);
+ }
+
+ ///
+ /// Displays the dialog indicating missing packages/dependencies.
+ ///
+ public static void ShowMissingPackagesDialog(ModDependencyResolveResult resolveResult)
+ {
+ // Note: This is slow, but it's ok in this rare case.
+ var notFoundDeps = resolveResult.NotFoundDependencies;
+ var list = new List();
+ var modConfigService = IoC.Get();
+
+ foreach (var notFound in notFoundDeps)
+ foreach (var item in modConfigService.Items)
+ {
+ var conf = item.Config;
+ if (conf.ModDependencies.Contains(notFound))
+ list.Add($"{notFound} | Required by: {conf.ModId}");
}
+
+ ActionWrappers.ExecuteWithApplicationDispatcher(() =>
+ {
+ Actions.DisplayMessagebox(Resources.ErrorMissingDependency.Get(),
+ $"{Resources.FetchNugetNotFoundMessage.Get()}\n\n" +
+ $"{string.Join('\n', list)}\n\n" +
+ $"{Resources.FetchNugetNotFoundAdvice.Get()}",
+ new Actions.DisplayMessageBoxParams()
+ {
+ Type = Actions.MessageBoxType.Ok,
+ StartupLocation = Actions.WindowStartupLocation.CenterScreen
+ });
+ });
+ }
+
+ ///
+ /// Gets all missing dependencies to be downloaded.
+ ///
+ public static async Task GetMissingDependenciesToDownload(CancellationToken token)
+ {
+ // Get missing dependencies for this update loop.
+ var missingDeps = CheckMissingDependencies();
+ if (missingDeps.AllAvailable)
+ return ModDependencyResolveResult.Combine(Enumerable.Empty());
+
+ // Get Dependencies
+ var resolver = DependencyResolverFactory.GetInstance(IoC.Get());
+
+ var results = new List>();
+ foreach (var dependencyItem in missingDeps.Items)
+ foreach (var dependency in dependencyItem.Dependencies)
+ results.Add(resolver.ResolveAsync(dependency, dependencyItem.Mod.PluginData, token));
+
+ await Task.WhenAll(results);
+
+ // Merge Results
+ var result = ModDependencyResolveResult.Combine(results.Select(x => x.Result));;
+ if (result.NotFoundDependencies.Count <= 0)
+ return result;
+
+ // Fallback to using Index Resolver if we couldn't find the package otherwise.
+ var indexResolver = new IndexDependencyResolver();
+ var indexResults = new List();
+ foreach (var notFound in result.NotFoundDependencies)
+ indexResults.Add(await indexResolver.ResolveAsync(notFound, null, token));
+
+ indexResults.Add(result);
+ return ModDependencyResolveResult.Combine(indexResults);
}
///
@@ -270,4 +354,48 @@ public static bool CheckForInternetConnection()
return false;
}
+
+ // TODO: This is a temporary hack to get people unstuck.
+ private static bool IsSameAsLast(ModDependencyResolveResult thisItem, ModDependencyResolveResult? lastItem)
+ {
+ if (lastItem == null)
+ return false;
+
+ if (thisItem.FoundDependencies.Count != lastItem.FoundDependencies.Count)
+ return false;
+
+ // Assert whether they changed.
+ // We will always have ID as we resolve deps by ID.
+ var thisIds = new HashSet(thisItem.FoundDependencies.Select(x => x.Id)!);
+ var otherIds = new HashSet(lastItem.FoundDependencies.Select(x => x.Id)!);
+ return thisIds.SetEquals(otherIds);
+ }
+
+ private static void ShowStuckInDownloadLoopDialog(ModDependencyResolveResult result)
+ {
+ var message = new StringBuilder("We got stuck in a dependency download loop.\n" +
+ "This bug is tracked at:\n" +
+ "https://github.com/Reloaded-Project/Reloaded-II/issues/226\n\n" +
+ "Here's a list of mods that's stuck:\n");
+
+ foreach (var item in result.FoundDependencies)
+ {
+ message.AppendLine($"Id: {item.Id} | Name: {item.Name} | Version: {item.Version} | Source: {item.Source}");
+ }
+
+ message.AppendLine($"\nSometimes this can happen due to a mod incorrectly published/uploaded,\n" +
+ $"or a file being removed by a mod author of a dependency.\n\n" +
+ $"In some very rare cases, this can happen on any mod for completely unknown reasons.\n\n" +
+ $"Please report this issue to the link above if you encounter it.\n" +
+ $"In the meantime, download the required mods manually (you should " +
+ $"hopefully find it by ID or Name).\n\n" +
+ $"Sorry for the pain.");
+
+ ActionWrappers.ExecuteWithApplicationDispatcher(() =>
+ {
+ Actions.DisplayMessagebox.Invoke("Stuck in Download Loop", message.ToString(), new Actions.DisplayMessageBoxParams(){
+ StartupLocation = Actions.WindowStartupLocation.CenterScreen
+ });
+ });
+ }
}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Lib/Usings.cs b/source/Reloaded.Mod.Launcher.Lib/Usings.cs
index 47af0eac..4fbdbfdb 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Usings.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Usings.cs
@@ -8,7 +8,6 @@
global using Octokit;
global using Ookii.Dialogs.Wpf;
global using PropertyChanged;
-global using Reloaded.Memory.Sources;
global using Reloaded.Mod.Interfaces;
global using Reloaded.Mod.Launcher.Lib.Commands.Application;
global using Reloaded.Mod.Launcher.Lib.Commands.Download;
diff --git a/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationInjector.cs b/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationInjector.cs
index 24fb2c36..a86ddbd8 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationInjector.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationInjector.cs
@@ -1,26 +1,37 @@
+using dll_syringe.Net.Sys;
+
namespace Reloaded.Mod.Launcher.Lib.Utility;
///
/// Class that can be used to inject Reloaded into an active process.
///
-public class ApplicationInjector
+public unsafe class ApplicationInjector : IDisposable
{
private readonly int _modLoaderSetupTimeout;
private readonly int _modLoaderSetupSleepTime;
private Process _process;
- private BasicDllInjector _injector;
+ private CSyringe* _syringe;
///
public ApplicationInjector(Process process)
{
_process = process;
- _injector = new BasicDllInjector(process);
+ _syringe = NativeMethods.syringe_for_suspended_process((uint)_process.Id);
var loaderConfig = IoC.Get();
_modLoaderSetupTimeout = loaderConfig.LoaderSetupTimeout;
_modLoaderSetupSleepTime = loaderConfig.LoaderSetupSleeptime;
}
+
+ ~ApplicationInjector() => Dispose();
+
+ ///
+ public void Dispose()
+ {
+ NativeMethods.syringe_free(_syringe);
+ GC.SuppressFinalize(this);
+ }
///
/// Injects the Reloaded bootstrapper into an active process.
@@ -28,8 +39,20 @@ public ApplicationInjector(Process process)
/// DLL Injection failed, likely due to bad DLL or application.
public void Inject()
{
- long handle = _injector.Inject(GetBootstrapperPath(_process));
- if (handle == 0)
+ // TODO: This is slow and wasteful, change this when we change encoding in injector.
+ var bootstrapperPath = GetBootstrapperPath(_process);
+ var bootstrapperPathBytes = Encoding.UTF8.GetBytes(bootstrapperPath);
+ var bootstrapperPathWithNull = new byte[bootstrapperPathBytes.Length + 1];
+ Array.Copy(bootstrapperPathBytes, bootstrapperPathWithNull, bootstrapperPathBytes.Length);
+ bootstrapperPathWithNull[bootstrapperPathBytes.Length] = 0;
+
+ bool success;
+ fixed(byte* bootstrapperPathPtr = bootstrapperPathWithNull)
+ {
+ success = NativeMethods.syringe_inject(_syringe, bootstrapperPathPtr);
+ }
+
+ if (!success)
throw new ArgumentException(Resources.ErrorDllInjectionFailed.Get());
try
diff --git a/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationLauncher.cs b/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationLauncher.cs
index d17e1313..a4fa2ec3 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationLauncher.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Utility/ApplicationLauncher.cs
@@ -21,7 +21,7 @@ public static ApplicationLauncher FromLocationAndArguments(string location, stri
{
_arguments = arguments ?? "",
_location = location,
- _workingDirectory = workingDirectory ?? Path.GetDirectoryName(location)!
+ _workingDirectory = string.IsNullOrEmpty(workingDirectory) ? Path.GetDirectoryName(location)! : workingDirectory
};
if (!File.Exists(launcher._location))
@@ -41,7 +41,7 @@ public static ApplicationLauncher FromApplicationConfig(PathTuple
/// Starts the application, injecting Reloaded into it.
///
- public void Start()
+ public void Start(bool inject = true)
{
// Start up the process
Native.STARTUPINFO startupInfo = new Native.STARTUPINFO();
@@ -64,16 +64,19 @@ public void Start()
// DLL Injection
var process = Process.GetProcessById((int) processInformation.dwProcessId);
- var injector = new ApplicationInjector(process);
+ using var injector = new ApplicationInjector(process);
- try
+ if (inject)
{
- injector.Inject();
- }
- catch (Exception)
- {
- Native.ResumeThread(processInformation.hThread);
- throw;
+ try
+ {
+ injector.Inject();
+ }
+ catch (Exception)
+ {
+ Native.ResumeThread(processInformation.hThread);
+ throw;
+ }
}
Native.ResumeThread(processInformation.hThread);
diff --git a/source/Reloaded.Mod.Launcher.Lib/Utility/AsiLoaderDeployer.cs b/source/Reloaded.Mod.Launcher.Lib/Utility/AsiLoaderDeployer.cs
index aa3a952f..a5fb9ab1 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Utility/AsiLoaderDeployer.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Utility/AsiLoaderDeployer.cs
@@ -223,13 +223,14 @@ private void ExtractAsiLoader(string filePath, bool is64bit)
{
"winmm.dll",
"wininet.dll",
- "version.dll",
"dsound.dll",
- "dinput8.dll"
+ "dinput8.dll",
+ "version.dll"
};
private static readonly string[] AsiCommonDirectories =
{
+ "", // root folder
"scripts",
"plugins"
};
diff --git a/source/Reloaded.Mod.Launcher.Lib/Utility/AutoInjector.cs b/source/Reloaded.Mod.Launcher.Lib/Utility/AutoInjector.cs
index bdc33e36..a6676b85 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Utility/AutoInjector.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Utility/AutoInjector.cs
@@ -27,7 +27,7 @@ private void ProcessWatcherOnOnNewProcess(Process newProcess)
var config = _configService.Items.FirstOrDefault(x => string.Equals(ApplicationConfig.GetAbsoluteAppLocation(x), fullPath, StringComparison.OrdinalIgnoreCase));
if (config != null && config.Config.AutoInject)
{
- var appInjector = new ApplicationInjector(newProcess);
+ using var appInjector = new ApplicationInjector(newProcess);
appInjector.Inject();
}
}
diff --git a/source/Reloaded.Mod.Launcher.Lib/Utility/BasicDllInjector.cs b/source/Reloaded.Mod.Launcher.Lib/Utility/BasicDllInjector.cs
deleted file mode 100644
index 1f6ce3b5..00000000
--- a/source/Reloaded.Mod.Launcher.Lib/Utility/BasicDllInjector.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-namespace Reloaded.Mod.Launcher.Lib.Utility;
-
-///
-/// Provides the implementation of a very basic, primitive DLL Injector.
-/// This one is required to inject into an application in suspended state.
-///
-/// WARNING: USE FROM 64-bit process only!
-///
-public class BasicDllInjector
-{
- private static nuint _x64LoadLibraryAddress;
- private static nuint _x86LoadLibraryAddress;
- private static bool _initialized;
-
- private readonly Process _process;
- private readonly ExternalMemory _memory;
-
- ///
- /// Provides the implementation of a primitive DLL injector, supporting injection into
- /// suspended process.
- ///
- public BasicDllInjector(Process process)
- {
- // Set the Location and Handle to the Process to be Injected.
- _process = process;
- _memory = new ExternalMemory(process);
- }
-
- /// Full path to library to load.
- /// 0 if injection failed.
- public int Inject(string libraryPath)
- {
- if (!_initialized)
- PreloadAddresses();
-
- var loadLibraryAddress = _process.Is64Bit() ? _x64LoadLibraryAddress : _x86LoadLibraryAddress;
- var libraryNameMemoryAddress = WriteLoadLibraryParameter(libraryPath);
- int result = ExecuteFunction(loadLibraryAddress, libraryNameMemoryAddress);
- _memory.Free(libraryNameMemoryAddress);
- return result;
- }
-
- private nuint WriteLoadLibraryParameter(string libraryPath)
- {
- byte[] libraryNameBytes = Encoding.Unicode.GetBytes(libraryPath);
- var processPointer = _memory.Allocate(libraryNameBytes.Length);
- _memory.WriteRaw(processPointer, libraryNameBytes);
- return processPointer;
- }
-
- private int ExecuteFunction(nuint address, nuint parameterAddress)
- {
- IntPtr hThread = CreateRemoteThread(_process.Handle, IntPtr.Zero, IntPtr.Zero, address, parameterAddress, 0, out _);
- WaitForSingleObject(hThread, unchecked((uint)-1));
- GetExitCodeThread(hThread, out uint exitCode);
- return (int)exitCode;
- }
-
- /* Helper functions */
-
- private static nuint Getx64LoadLibraryAddress()
- {
- var kernel32Handle = LoadLibraryW("kernel32");
- return GetProcAddress(kernel32Handle, "LoadLibraryW");
- }
-
- private static nuint Getx86LoadLibraryAddress()
- {
- // Setup Memory Mapped File for transfer.
- var file = MemoryMappedFile.CreateOrOpen(SharedConstants.Kernel32AddressDumperMemoryMappedFileName, sizeof(long));
-
- // Load dummy 32bit process to get 32bit addresses.
- var kernelDumpProcess = StartKernelAddressDumper();
- kernelDumpProcess.WaitForExit();
-
- var viewStream = file.CreateViewStream();
- var reader = new BinaryReader(viewStream);
- var result = (nuint)reader.ReadInt64();
-
- if (result == 0)
- throw new Exception(Resources.ErrorGetProcAddress32Failed.Get());
-
- return result;
- }
-
- private static Process StartKernelAddressDumper()
- {
- string location = Paths.GetKernel32AddressDumperPath(AppDomain.CurrentDomain.BaseDirectory);
- return Process.Start(location);
- }
-
- private static void PreloadAddresses()
- {
- // Dummy. Static constructor only needed.
- if (_initialized)
- return;
-
- ActionWrappers.TryCatch(() => { _x64LoadLibraryAddress = Getx64LoadLibraryAddress(); });
- ActionWrappers.TryCatch(() => { _x86LoadLibraryAddress = Getx86LoadLibraryAddress(); });
- _initialized = true;
- }
-
- #region Native Imports
- [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
- private static extern nuint LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName);
-
- [DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
- private static extern nuint GetProcAddress(nuint hModule, string procName);
-
- [DllImport("kernel32.dll")]
- private static extern IntPtr CreateRemoteThread(IntPtr hProcess, IntPtr lpThreadAttributes, IntPtr dwStackSize,
- nuint lpStartAddress, nuint lpParameter, uint dwCreationFlags, out IntPtr lpThreadId);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
-
- [DllImport("kernel32.dll")]
- private static extern bool GetExitCodeThread(IntPtr hThread, out uint lpExitCode);
- #endregion
-}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher.Lib/Utility/SymlinkResolver.cs b/source/Reloaded.Mod.Launcher.Lib/Utility/SymlinkResolver.cs
index 16d9d390..a64f8f0c 100644
--- a/source/Reloaded.Mod.Launcher.Lib/Utility/SymlinkResolver.cs
+++ b/source/Reloaded.Mod.Launcher.Lib/Utility/SymlinkResolver.cs
@@ -17,7 +17,6 @@ public static class SymlinkResolver
private const short MaxPath = short.MaxValue; // Windows 10 with path extension.
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
-
private const uint FILE_READ_EA = 0x0008;
private const uint FILE_FLAG_BACKUP_SEMANTICS = 0x2000000;
@@ -42,25 +41,12 @@ static extern IntPtr CreateFile(
/// Resolves a symbolic link and normalizes the path.
///
/// The path to be resolved.
- /// Resolves UWP application paths.
- public static string GetFinalPathName(string path, bool allowUwp = true)
+ public static string GetFinalPathName(string path)
{
// Special Case for UWP/MSStore.
- if (allowUwp)
- {
- try
- {
- var folder = Path.GetDirectoryName(path)!;
- var manifest = Path.Combine(folder, "appxmanifest.xml");
- if (File.Exists(manifest))
- return TryGetFilePathFromUWPAppManifest(path, manifest);
- }
- catch (Exception) { }
- }
-
var h = CreateFile(path, FILE_READ_EA, FileShare.ReadWrite | FileShare.Delete, IntPtr.Zero, FileMode.Open, FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero);
if (h == INVALID_HANDLE_VALUE)
- throw new Win32Exception();
+ return path;
try
{
@@ -78,37 +64,6 @@ public static string GetFinalPathName(string path, bool allowUwp = true)
}
}
- private static string TryGetFilePathFromUWPAppManifest(string path, string manifest)
- {
- var document = new XmlDocument();
- document.Load(manifest);
-
- var tag = document.GetElementsByTagName("Identity")[0]!;
- var packageName = tag!.Attributes!["Name"]!.Value;
-
- // I wish I could use WinRT APIs but support is removed from runtime and the official way cuts off support for Win7/8.1
- var newFolder = GetPowershellPackageInstallLocation(packageName!);
- return Path.Combine(newFolder, Path.GetFileName(path));
- }
-
- private static string GetPowershellPackageInstallLocation(string packageName)
- {
- var processStartInfo = new ProcessStartInfo
- {
- FileName = @"powershell",
- Arguments = $"(Get-AppxPackage {packageName}).InstallLocation",
- RedirectStandardOutput = true,
- CreateNoWindow = true
- };
- var process = Process.Start(processStartInfo);
- process?.WaitForExit();
- var output = process?.StandardOutput.ReadToEnd().TrimEnd();
- if (output == null)
- throw new Exception("Failed to get Package Install location via PowerShell.");
-
- return output;
- }
-
private static string RemoveDevicePrefix(string path)
{
const string DevicePrefix = @"\\?\";
diff --git a/source/Reloaded.Mod.Launcher.Lib/Utility/TryUnprotectGamePassGame.cs b/source/Reloaded.Mod.Launcher.Lib/Utility/TryUnprotectGamePassGame.cs
new file mode 100644
index 00000000..62404411
--- /dev/null
+++ b/source/Reloaded.Mod.Launcher.Lib/Utility/TryUnprotectGamePassGame.cs
@@ -0,0 +1,250 @@
+using System.Security.Cryptography;
+using System.Xml;
+using Reloaded.Mod.Installer.DependencyInstaller.IO;
+using static Reloaded.Mod.Launcher.Lib.Utility.DesktopAppxActivateOptions;
+using FileMode = System.IO.FileMode;
+
+namespace Reloaded.Mod.Launcher.Lib.Utility;
+
+///
+/// GamePass games restrict our access to EXE files, making it difficult for Reloaded to do various operations:
+/// - Displaying Game Icon
+/// - Deploying ASI Loader
+/// etc.
+///
+public static class TryUnprotectGamePassGame
+{
+ ///
+ /// Path to the main game binary.
+ /// True if this was auto-unprotected.
+ public static bool TryIt(string exePath)
+ {
+ // Note: We assume this may be a GamePass game if we can't read the binary.
+ // This is a very naive approach, but as we're not parsing `.GamingRoot`,
+ // to 'know' that this is a library path, this is the best we can do for now.
+ var read = CanRead(exePath);
+ if (read)
+ return !read;
+
+ // If we can't read it, try finding an AppxManifest.xml file by going up directories.
+ if (!GetAppXManifestPath(exePath, out var manifestPath))
+ return false;
+
+ ExtractInfoFromUWPAppManifest(manifestPath!, out var appId, out var packageFamilyName);
+
+ var contentFolder = Path.GetDirectoryName(manifestPath);
+ var exeFiles = Directory.GetFiles(contentFolder!, "*.exe", SearchOption.AllDirectories);
+
+ // Load custom binary that does file copying to handle the decryption.
+ var libraryDirectory = Path.GetDirectoryName(Assembly.GetEntryAssembly()!.Location);
+ var compressedLoaderPath = $"{libraryDirectory}/Assets/replace-files-with-itself.exe";
+
+ // Append command to create 'terminate' file indicating script completion.
+ using var tempDir = new TemporaryFolderAllocation();
+ var scriptPath = Path.Combine(tempDir.FolderPath, "files.txt");
+ File.WriteAllLines(scriptPath, exeFiles, Encoding.UTF8);
+
+ // Execute the script in game context where we have perms to access the files.
+ // ReSharper disable once SuspiciousTypeConversion.Global
+ TryActivate(
+ packageFamilyName + "!" + appId,
+ compressedLoaderPath,
+ $"\"{scriptPath}\""
+ );
+
+ // Wait until 'replace-files-with-itself' is terminated.
+ var processName = "replace-files-with-itself";
+ while (Process.GetProcessesByName(processName).Length > 0)
+ Thread.Sleep(1);
+
+ return true;
+ }
+
+ [SuppressMessage("ReSharper", "SuspiciousTypeConversion.Global")]
+ private static void TryActivate(string packageFamilyName, string compressedLoaderPath, string scriptPath)
+ {
+ // Note: `Get-Command Invoke-CommandInDesktopPackage | Format-List *` in powershell
+ // and decompile with dnSpy, etc. to check current OS impl.
+ try
+ {
+ var act = (IDesktopAppxActivatorWin11)new DesktopAppxActivator();
+ act.ActivateWithOptions(
+ packageFamilyName,
+ compressedLoaderPath,
+ scriptPath,
+ (uint)(CentennialProcess | NonPackagedExeProcessTree),
+ 0,
+ out _);
+ return;
+ }
+ catch (Exception) { /* ignored */ }
+
+ try
+ {
+ var act = (IDesktopAppxActivatorWin10)new DesktopAppxActivator();
+ act.ActivateWithOptions(
+ packageFamilyName,
+ compressedLoaderPath,
+ scriptPath,
+ (uint)(CentennialProcess | NonPackagedExeProcessTree),
+ 0,
+ out _);
+ return;
+ }
+ catch (Exception) { /* ignored */ }
+
+ throw new Exception("Can't make use of DesktopAppxActivator. Your OS may be too recent. Please report this.");
+ }
+
+ ///
+ /// Path to the main game binary.
+ /// True if this was auto-unprotected.
+ public static bool TryIgnoringErrors(string exePath)
+ {
+ try
+ {
+ return TryIt(exePath);
+ }
+ catch (Exception)
+ {
+ return false;
+ }
+ }
+
+ private static bool GetAppXManifestPath(string exePath, [MaybeNullWhen(false)] out string appManifest)
+ {
+ var currentDirectory = Path.GetDirectoryName(exePath);
+ while (currentDirectory != null)
+ {
+ var appxManifestPath = Path.Combine(currentDirectory, "AppxManifest.xml");
+ if (File.Exists(appxManifestPath))
+ {
+ appManifest = appxManifestPath;
+ return true;
+ }
+
+ currentDirectory = Path.GetDirectoryName(currentDirectory);
+ }
+
+ appManifest = null;
+ return false;
+ }
+
+ private static void ExtractInfoFromUWPAppManifest(string manifest, out string appId, out string packageFamilyName)
+ {
+ var document = new XmlDocument();
+ document.Load(manifest);
+
+ var tag = document.GetElementsByTagName("Identity")[0]!;
+ var packageName = tag!.Attributes!["Name"]!.Value;
+ var publisherName = tag!.Attributes!["Publisher"]!.Value;
+ var applicationTag = document.GetElementsByTagName("Application")[0]!;
+
+ appId = applicationTag!.Attributes!["Id"]!.Value;
+ packageFamilyName = $"{packageName}_{GetPublisherHash(publisherName)}";
+ }
+
+ // Credits: https://gist.github.com/marcinotorowski/6a51023600160fcceef9ceea341bbc4a
+ private static string GetPublisherHash(string publisherId)
+ {
+ using var sha = SHA256.Create();
+ var encoded = sha.ComputeHash(Encoding.Unicode.GetBytes(publisherId));
+ var binaryString = string.Concat(encoded.Take(8).Select(c => Convert.ToString(c, 2).PadLeft(8, '0'))) + '0'; // representing 65-bits = 13 * 5
+ var encodedPublisherId = string.Concat(Enumerable.Range(0, binaryString.Length / 5).Select(i => "0123456789abcdefghjkmnpqrstvwxyz".Substring(Convert.ToInt32(binaryString.Substring(i * 5, 5), 2), 1)));
+ return encodedPublisherId;
+ }
+
+ private static bool CanRead(string exePath)
+ {
+ try
+ {
+ using var fs = new FileStream(exePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 524288);
+ fs.ReadByte();
+ return true;
+ }
+ catch
+ {
+ // We can't read the file.
+ return false;
+ }
+ }
+}
+
+// Enum for activation options
+[Flags]
+internal enum DesktopAppxActivateOptions
+{
+ None = 0,
+ Elevate = 1,
+ NonPackagedExe = 2,
+ NonPackagedExeProcessTree = 4,
+ NonPackagedExeFlags = 6,
+ NoErrorUI = 8,
+ CheckForAppInstallerUpdates = 16,
+ CentennialProcess = 32,
+ UniversalProcess = 64,
+ Win32AlaCarteProcess = 128,
+ RuntimeBehaviorFlags = 224,
+ PartialTrust = 256,
+ UniversalConsole = 512,
+ AppSilo = 1024,
+ TrustLevelFlags = 1280,
+}
+
+// COM interface for activating desktop applications
+[Guid("F158268A-D5A5-45CE-99CF-00D6C3F3FC0A")]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+internal interface IDesktopAppxActivatorWin11
+{
+ void Activate(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string applicationUserModelId,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string packageRelativeExecutable,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string arguments,
+ out IntPtr processHandle);
+
+ void ActivateWithOptions(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string applicationUserModelId,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string executable,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string arguments,
+ uint activationOptions,
+ uint parentProcessId,
+ out IntPtr processHandle);
+}
+
+[Guid("72e3a5b0-8fea-485c-9f8b-822b16dba17f")]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+internal interface IDesktopAppxActivatorWin10
+{
+ void Activate(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string applicationUserModelId,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string packageRelativeExecutable,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string arguments,
+ out IntPtr processHandle);
+
+ void ActivateWithOptions(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string applicationUserModelId,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string executable,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string arguments,
+ uint activationOptions,
+ uint parentProcessId,
+ out IntPtr processHandle);
+}
+
+[ComImport]
+[Guid("168EB462-775F-42AE-9111-D714B2306C2E")]
+class DesktopAppxActivator
+{
+
+}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher/App.xaml.cs b/source/Reloaded.Mod.Launcher/App.xaml.cs
index a01d50cb..3478489d 100644
--- a/source/Reloaded.Mod.Launcher/App.xaml.cs
+++ b/source/Reloaded.Mod.Launcher/App.xaml.cs
@@ -39,16 +39,58 @@ private void OnStartup(object sender, StartupEventArgs e)
StartProfileOptimization();
PrepareWebRequests();
+ // Need to construct MainWindow before invoking any dialog, otherwise Shutdown will be called on closing the dialog
var window = new MainWindow();
+
+ // Warn if OneDrive or NonAsciiChars detected in Reloaded-II directory
+ bool reloadedPathHasNonAsciiChars = AppContext.BaseDirectory.Any(c => c > 127);
+ if (AppContext.BaseDirectory.Contains("OneDrive") || reloadedPathHasNonAsciiChars)
+ {
+ Actions.DisplayMessagebox.Invoke(Lib.Static.Resources.ProblematicPathTitle.Get(), Lib.Static.Resources.ProblematicPathReloadedDescription.Get(), new Actions.DisplayMessageBoxParams()
+ {
+ StartupLocation = Actions.WindowStartupLocation.CenterScreen,
+ Type = Actions.MessageBoxType.Ok
+ });
+ }
+ else // We only do this check if the Reloaded-II directory check passed
+ {
+ // Warn if OneDrive or NonAsciiChars detected in Mods directory
+ var modsDirectory = Lib.IoC.Get().GetModConfigDirectory();
+ if (modsDirectory != null)
+ {
+ bool modsDirectoryPathHasNonAsciiChars = modsDirectory.Any(c => c > 127);
+ if (modsDirectory.Contains("OneDrive") || modsDirectoryPathHasNonAsciiChars)
+ {
+ Actions.DisplayMessagebox.Invoke(Lib.Static.Resources.ProblematicPathTitle.Get(), Lib.Static.Resources.ProblematicPathModsDescription.Get(), new Actions.DisplayMessageBoxParams()
+ {
+ StartupLocation = Actions.WindowStartupLocation.CenterScreen,
+ Type = Actions.MessageBoxType.Ok
+ });
+ }
+ }
+ }
+
window.ShowDialog();
}
private void SetupResources()
{
- var launcherFolder = AppContext.BaseDirectory;
+ var launcherFolder = AppContext.BaseDirectory;
var languageSelector = new XamlFileSelector($"{launcherFolder}\\Assets\\Languages");
var themeSelector = new XamlFileSelector($"{launcherFolder}\\Theme");
+ var conf = Lib.IoC.GetConstant();
+ if (conf.FirstLaunch)
+ {
+ // Default the language to user's system language.
+ // e.g. en-GB.xaml
+ var currentCulture = Thread.CurrentThread.CurrentUICulture + ".xaml";
+ conf.LanguageFile = languageSelector.Files.FirstOrDefault(x => Path.GetFileName(x) == currentCulture) ?? conf.LanguageFile;
+ }
+
+ themeSelector.SelectXamlFileByName(Path.GetFileName(conf.ThemeFile));
+ languageSelector.SelectXamlFileByName(Path.GetFileName(conf.LanguageFile));
+
LibraryBindings.Init(languageSelector, themeSelector);
// Ideally this should be in Setup, however the download dialogs should be localized.
@@ -56,6 +98,11 @@ private void SetupResources()
Resources.MergedDictionaries.Add(themeSelector);
themeSelector.NewFileSet += OnThemeChanged;
Resources.MergedDictionaries.Add(new ResourceDictionary() { Source = new Uri($"{launcherFolder}\\Theme\\Helpers\\BackwardsCompatibilityHelpers.xaml", UriKind.RelativeOrAbsolute) });
+
+ // Hack: Disable client side decoration (glow) on Linux
+ // This reduces flicker on XWayland.
+ if (Environment.IsWine)
+ new DictionaryResourceManipulator(Application.Current.Resources).Set("EnableGlow", false);
}
private void OnThemeChanged()
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/de-DE.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/de-DE.xaml
new file mode 100644
index 00000000..5b9627a0
--- /dev/null
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/de-DE.xaml
@@ -0,0 +1,694 @@
+
+
+
+
+
+
+ Anwendung hinzufügen
+ Anwendung bearbeiten
+ Mods verwalten
+ Mods herunterladen
+ Mod Lader-Einstellungen
+ Hauptseite
+ Anwendungen suchen/filtern
+ Reloaded II wird vorbereitet..
+
+
+ Standardkonfiguration wird erstellt
+ App-/Lader-/Mod-Konfigurationen bereinigen
+ Ressourcen einrichten
+ Vollständig geladen in:
+ Erstellen von Vorlagen
+ Nach Updates suchen
+ Durchführung von Sanitätsprüfungen
+
+
+ Klicke auf dieses Bild, um ein benutzerdefiniertes Logo zu importieren.
+ Wählen das Bild aus, das als Symbol verwendet werden soll.
+ Bild
+
+ Neu
+ Löschen
+
+ Anwendungsname
+ Speicherort der Ausführung
+ Kommandozeilenargumente
+
+ Erweiterte Werkzeuge & Optionen
+ Verknüpfung erstellen
+ Auto-Injektion
+ Hinweis: Auto-Injektion wird bereitgestellt, aber nicht empfohlen, da keine Garantie gegeben werden kann, dass Mods den Code vor der Anwendung ausführen.
Bitte verwende stattdessen reguläres "Anwendung starten" von Reloaded oder die "DLL Lader"-methode beschrieben in der GitHub-Dokumentation.
Bitte reiche keine Fehler für Mods ein, die nicht im Auto-Injektionsmodus funktionieren.
+
+ Wähle eine Anwendung aus, für die eine Konfiguration erstellt werden soll.
+ Ausführbare Anwendungsdatei
+
+ Verknüpfung erstellt
+ Verknüpfung erstellt bei:
+
+
+ Mods suchen
+ Apps suchen
+ Extra Optionen
+ Mod-Verzeichnis öffnen
+ In das NuGet-Format konvertieren
+ Abhängigkeiten verwalten
+ Wähle den Mod aus, dessen Details geändert werden soll.
+ Wähle die Anwendungen aus, die mit diesem Mod kompatibel sind.
+
+
+ Abhängigkeiten für Mod festlegen
+
+
+ Mod erstellen
+ Mod erstellen
+ Eindeutige ID
+ Name
+ Autor
+ Version
+ Kurze Beschreibung
+ Mod-Abhängigkeiten
+
+ Wähle ein benutzerdefiniertes Logo für den Mod.
+ Bild
+
+ Nicht eindeutige ID
+ Die ModID für den Mod, den du erstellst, ist nicht eindeutig.
Bitte mach die ModID eindeutig.
+
+
+ Bereinigungskonfigurationen
+ Der folgende Vorgang führt die Verwaltung verschiedener Konfigurationen durch (App, Mod etc.).
Zu den Verwaltungsfunktionen gehören Dinge wie das Entfernen nicht vorhandener Mods aus der Liste der Abhängigkeiten eines Mods.
Bitte beachte, dass fehlende Abhängigkeiten, unterstützte Anwendungen usw. aus den Konfigurationsdateien entfernt werden.
+ Bereinigung abgeschlossen
+
+ Installierte Anwendungen:
+ Mods installiert:
+ Kompiliert am:
+ Benutzerhandbuch + Dokumente.
+ Reloaded II ist freie Software, die unter der GNU GPL V3 lizenziert ist
+ Automatische Aktualisierungen
+ Konsole anzeigen
+ Asynchron laden
+ Reloaded II ist eine leistungsstarke Software, die die Ausführung beliebigen/benutzerdefinierten Codes ermöglicht. Die Nutzung erfolgt auf eigene Gefahr.
+
+
+ Anwendungs-Hub
+ Gesamtzahl der Mods
+ Andere Instanzen
+ Neu geladene Instanzen
+
+
+ Willkommen bei Reloaded II
+ Es ist dein erstes Mal, dass du Reloaded II verwendst.
+ Bevor du beginnst, solltest du einen Blick auf die folgenden Links werfen:
+ Dokumentation
+ Benutzerhandbuch
+ Möglicherweise findest du diese Links später in den [Mod Lader-Einstellungen].
+
+
+ OK
+ Abbrechen
+
+
+ Nicht neu geladener Prozess
+ Reloaded verwaltet nicht diesen speziellen Prozess.
+ Du kannst Reloaded laden, indem du auf die folgende Schaltfläche unten klickst.
+ Injizieren
+ Hinweis: Möglicherweise funktionieren nicht alle Mods wie erwartet, wenn sie geladen werden, während die Anwendung ausgeführt und nicht angehalten wird. Bitte reiche keine Fehler für Mods ein, die mit dieser Funktion nicht funktionieren.
+
+
+ Reloaded Prozess
+
+
+ Mods konfigurieren
+ Anwendung starten
+
+ Ziehe Elemente per Ziehen-und-Ablegen, um die Ladereihenfolge von Mods zu ändern. Mods werden geladen, beginnend mit dem oberen Mod zuerst und dem unteren Mod zuletzt.
+ Verwendet Reloaded um dir mitzuteilen, dass du dir einige Mods besorgen sollst. Reloaded-II ist cool! Niedlichkeit ist Gerechtigkeit, Es ist das Gesetz!.
+ Mod konfigurieren
+ Ordner öffnen
+
+ Mod-Set laden
+ Mod-Set speichern
+ Wähle die Datei aus, aus der die Liste der Mods geladen werden soll.
+ Wähle den Pfad aus, in dem die Liste der Mods gespeichert werden soll.
+
+
+ Kompatibilitätswarnung
+ Du hast Mods aktiviert, die als "nicht mit deiner Anwendung kompatibel" markiert sind:
+ Durch Drücken von „OK“ werden die Mods für deine Anwendung aktiviert. Durch Drücken von „Abbrechen“ werden die Mods deaktiviert.
+
+
+ Wieder aufnehmen
+ Aussetzen
+ Entladen
+ Aktualisieren
+ Mod laden
+ Hinweis: Dieses Menü ist in erster Linie für die Verwendung durch Entwickler gedacht.
+
+
+ Mod-Updates verfügbar
+ Herunterladen
+ Mod Id
+ Größe (MB)
+ Alte Version
+ Neue Version
+
+
+ Anwendungen werden ausgeführt
+ Du hast derzeit Anwendungen, die Reloaded ausführen.
Updates werden heruntergeladen, jedoch erst angewendet, wenn alle Anwendungen geschlossen sind.
+
+
+ Update verfügbar
+ Aktuelle Version
+ Neue Version
+
+ Kein Änderungsprotokoll verfügbar
+ Änderungsprotokoll auf GitHub ansehen
+ Update
+
+ Anwendungen werden ausgeführt
+ Du hast derzeit Anwendungen, die Reloaded ausführen.
Das Update kann nicht durchgeführt werden. Bitte schließe alle Anwendungen.
+ Prozessliste
+
+
+ Herunterladen
+ Wird heruntergeladen
+ Bereits heruntergeladen
+
+ Webseite besuchen
+ Auf Updates prüfen & Abhängigkeiten
+
+
+ Hurra!
+ Hier gibt es nichts zu tun. Alle Mods in Ordnung!
+
+
+ Pakete herunterladen
+ Die folgenden Pakete werden installiertd:
+ Die folgenden Pakete wurden nicht gefunden:
+ Du musst sie selbst finden :/
+
+
+ Oof!
+ Beim Versuch, Reloaded-II zu laden, ist ein Fehler aufgetreten.
+
+
+ Mod-Archiv herunterladen
+ Dateiname
+
+
+ Du musst diese Anwendung als Administrator ausführen.
Um Anwendungsstart-/-beendigungsereignisse von Windows Management Instrumentation (WMI) zu empfangen, sind Administratorrechte erforderlich. Entwickler: Führe deine Lieblings-IDE, z. B. Visual Studio, als Administrator aus.
+
+
+ Mod konfigurieren
+ Speichern
+
+
+ Fehler
+ Unbekannter Fehler
+ Der Pfad zu dieser Anwendung ist entweder null oder leer. Bitte korrigiere den Pfad.
+ Das Verzeichnis des zu löschenden Mods konnte nicht abgerufen werden.
+ Das Verzeichnis der zu löschenden Anwendung konnte nicht abgerufen werden.
+ wurde nicht gefunden. Dies kann dadurch verursacht werden, dass Antivirensoftware Änderungen im GitHub-Repository löscht und/oder unterbricht.
+ Der Vorgang konnte nicht gestartet werden. Dies geschieht im Allgemeinen aufgrund eines der drei Probleme:
- Der Pfad zur EXE-Datei ist falsch (Hast du die Anwendung verschoben?)
- Das Programm ist für die Ausführung als Administrator konfiguriert (Rechtsklick -> Eigenschaften) aber Reloaded läuft im Benutzermodus.
- Störungen z.B durch Antivirensoftware. Überprüfe deine Protokolle.
Hier ist der von Windows zurückgegebene Fehler:
+ Die DLL konnte nicht in den Anwendungsprozess eingefügt werden.
+ Reloaded kann die EXE-Datei für dieses Programm nicht finden.
+Erwäge eine Aktualisierung unter `Anwendung bearbeiten` -> `Update`.
+Normalerweise kann dies passieren, weil:
+- Du hast deinen Anwendungsordner verschoben.
+- App-Ordner wurde nach einem Update verschoben (kann bei GamePass/UWP passieren).
+ Die Suche nach Updates ist fehlgeschlagen. Entweder unterliegen wir einer Ratenbegrenzung durch GitHub oder sind nicht mit dem Internet verbunden.
+ Warnung: Wahrscheinlich fehlt dir die x86-Version von .NET Core, 32-Bit-Anwendungen funktionieren nicht.
Bitte überprüfe noch einmal die auf der Download-Seite aufgeführten Anforderungen.
Tatsächlicher Fehler: Adresse von GetProcAddress für x86 konnte nicht abgerufen werden.
+
+
+ ASI Lader bereitstellen
+ ASI Lader Bereitstellung
+ Der Ultimate ASI Loader (DLL-Loader eines Drittanbieters) von ThirteenAG kann bei Bedarf für Reloaded bereitgestellt werden
automatisch gestartet werden, ohne einen Launcher oder eine Verknüpfung zu verwenden. Möchtest du diesen Loader bereitstellen?
+ ASI Lader erfolgreich bereitgestellt:
+ Reloaded Bootstrapper bereitgestellt für:
+ Kein kompatibler DLL-Name zum Hinzufügen von Ultimate ASI Lader gefunden.
+
+
+ Fehlende Anforderungen
+ Es scheint, dass bestimmte Voraussetzungen für die Ausführung von Reloaded auf Ihrem System fehlen. Der Launcher hat die folgenden fehlenden Anforderungen gefunden:
+ Bitte lade die oben angegebenen Anforderungen herunter und schließe das Fenster.
+
+
+ Haupt
+ Aktionen
+ Prozesse
+ Zusammenfassung
+
+
+ Tweete mir
+ Sponser mich auf GitHub
+ Tritt dem Discord Server bei
+
+
+ NuGet-Quellen konfigurieren
+ Quellen konfigurieren
+
+ Name (Erforderlich)
+ URL (Erforderlich)
+ Beispiel: http://167.71.128.50:5000/v3/index.json
+ Beschreibung
+
+
+ Wine Kompatibilitätshinweis
+ Es scheint, dass du versuchst, eine Anwendung mit dem Launcher in Wine auszuführen.
Dieser Vorgang schlägt wahrscheinlich fehl, da die Zielanwendung spezielle Tricks zur Ausführung erfordert oder isoliert ist (wie zum Beispiel unterschiedliche Wine-präfixe) die die Dll Injezierung verhindern.
Unter Wine laufen zu lassen, Es wird empfohlen, den ASI Lader bereitzustellen (Siehe: Menü „Anwendung bearbeiten“.) und führe das Spiel außerhalb des Launchers aus.
Bitte beachte, dass Reloaded nicht häufig unter Wine getestet wird.
+ Trotzdem versuchen
+
+
+ Reloaded Einstellungen
+ Sprache
+ Thema
+ Protokolldateien anzeigen
+ Konfigurationsdatei öffnen
+
+
+ Entweder ist die Anwendung abgestürzt, Reloaded konnte nicht geladen werden oder Reloaded wurde nicht innerhalb des Auszeit-Limits initialisiert.
Die folgenden Vorgänge könnten hilfreich sein:
- Schließe diesen Fehler, warte einige Sekunden und versuche es erneut.
- Schließe andere Kopien der Anwendung, wenn diese bereits ausgeführt werden. (Task-Manager verwenden)
- Starte deinen Computer neu.
Dies ist eine Warnung. Wenn deine Anwendung wie erwartet funktioniert, kannst du diese Meldung ignorieren.
Tatsächlicher Fehler:
Der Port für die im Prozess ausgeführte Reloaded-Instanz konnte nicht abgerufen werden.
+
+
+ Bootstrapper aktualisiert
+ Reloaded Bootstrapper wurde für die folgenden Programme automatisch aktualisiert:
{0}
+
+
+ Das Verzeichnis zum Bereitstellen des Bootstrappers konnte nicht erstellt werden. Erwäge Reloaded als Administrator auszuführen.
Tatsächlicher Fehler: {0}
+
+
+ Reloaded II Tutorial
+
+ Anwendung hinzufügen
+ Mods extrahieren
+ Mods konfigurieren
+ Mods aktivieren
+ Fertig
+
+ Anwendung hinzufügen
+ Das Tutorial überspringen
+ Diesen Schritt überspringen
+ Der erste Schritt zum Einstieg in Reloaded besteht darin, eine Anwendung hinzuzufügen, die du modifizieren möchtest.
(Normalerweise auf der Schaltfläche „+“ unten links im Launcher zu finden)
+ Stelle sicher, dass du die App und nicht den Launcher hinzufügst, wie oben gezeigt.
+
+ Um Mods zu installieren, Extrahiere einfach eine heruntergeladene Zip-Datei in den Ordner 'Mods'..
+ Wenn im heruntergeladenen Mod kein einzelner Ordner vorhanden ist, erstelle selbst einen.
+ Vorherige
+ Nächste
+
+ Einige Mods unterstützen möglicherweise zusätzliche Konfigurationen, sodass du Dinge optimieren kannst.
+ Wenn „Mod konfigurieren“ rot ist, wenn der Mod markiert ist, kann er konfiguriert werden.
+
+ Um Mods zu aktivieren, drück einfach das quadratische Kontrollkästchen.
+ Ein Mod ist aktiviert, wenn das Kontrollkästchen rot ist, deaktiviert, wenn es grau ist.
+
+ Vielen Dank, dass du Reloaded II ausprobierst.
+ Möglicherweise findest du auch die folgenden Links hilfreich:
+ Du findest diese Links auch in [Mod Lader-Einstellungen].
+
+
+ Veröffentlichen
+ Bearbeiten
+ Mod bearbeiten
+ Benutzerkonfiguration
+ Ordner öffnen
+ Konfigurieren
+
+
+ Mod bearbeiten
+
+
+ Fehler: Reloaded II-Version konnte nicht ermittelt werden
+ Mod(s) im Einsatz
+ Du hast derzeit Anwendungen, die Reloaded ausführen.
Updates werden heruntergeladen, jedoch erst angewendet, wenn alle Anwendungen geschlossen sind.
+ Fehlende Abhängigkeiten
+ Ungültige Versionsnummer
+ Die vom Mod verwendete Versionsnummer entspricht nicht dem Standard `Semantische Versionierung` (SemVer) und kann keine Updates erhalten.
Bitte aktualisiere die Versionsnummer deines Mods, um SemVer zu verwenden.
Beispiele: `1.0.0`, `1.0`, `2.0-alpha`.
+
+
+ Beschreibung
+
+ Allgemeine Details
+ Mod-Abhängigkeiten
+ Update-Unterstützung
+ Fertigstellung
+
+ Wenn dein Mod die Anwesenheit eines anderen Mods erfordert, wähle ihn hier aus.
+
+ Universeller Mod
+ Wenn aktiviert, ist der Mod standardmäßig in jeder Anwendung sichtbar.
+
+ Bibliothek
+ Wenn aktiviert, kann der Mod nicht explizit vom Benutzer aktiviert werden. Kann nur als Abhängigkeit aktiviert werden.
+
+ Speichern
+ Wähle Anwendungen aus, für die der Mod verfügbar sein soll.
+
+ Name der JSON-Metadatendatei.
Ändere dies nur, wenn du mehrere Mods auf derselben Seite veröffentlichst.
+
+
+ Rechtsklick auf den Mod, um weitere Optionen anzuzeigen
+
+
+ Eindeutige Mod-ID
+ Bitte leg eine eindeutige Mod-ID im Format "game.type.name" fest.. Zum Beispiel: `sonicheroes.skins.seasidehillmidnight`. Sobald die ID festgelegt ist, kann sie nicht mehr geändert werden.
+
+
+ Anwendungs-ID
+ Update
+
+ Ausführbarer Name Warnung
+ Der ausführbare Name der hinzugefügten Anwendung unterscheidet sich vom Original.
Wenn du eine weitere Anwendung hinzufügst, solltest du diese als neue Anwendung hinzufügen.
+
+
+ Mod veröffentlichen
+ Ziel veröffentlichen
+ Verwende die Standardeinstellung, es sei denn, es gibt eine Option für die Orte, an denen du veröffentlichst.
Aktiviert/deaktiviert bestimmte Verhaltensweisen.
+
+ Dateien ausschließen
+ Dateien einschließen
+ Vorherige Version(en)
+ Die Bereitstellung früherer Versionen deines Mods ermöglicht die Verwendung der Delta-Komprimierung, eine Möglichkeit, kleinere Downloadgrößen zu ermöglichen, wenn du von einer früheren Version auf eine neuere Version eines Mods aktualisierst.
+ Veröffentlichen
+
+ Wähle eine Mod-Konfiguration aus (ModConfig.json).
+ Konfiguration
+
+ Ungültige Mod-Konfiguration
+ Die bereitgestellte Datei stellt keine gültige Mod-Konfiguration dar.
+
+ Ungültige Mod-Version
+ Die bereitgestellte Mod-Version ist neuer oder entspricht bereits der zu veröffentlichenden Version.
+
+ Ungültige Mod-ID
+ Die angegebene Mod-ID stimmt nicht mit der ID des zu veröffentlichenden Mods überein.
+
+ Dateieinschlüsse & Ausschlüsse
+ Regulärer Ausdruck
+ Regulären Ausdrücke testen
+ Ausgeschlossene Dateien
+
+ Komprimierungseinstellungen
+ Komprimierungsstufe
+ Komprimierungsmethode
+
+ Ausgabeordner
+ Ausgabeordner festlegen
+
+ Automatisches Delta
+ Erstellt automatisch ein Delta-Paket aus der letzten Version.
Vergiss nicht, den Ausgabepfad auf den einer vorhandenen Version festzulegen.
+
+ Automatische Delta-Warnung
+ Die Release-Metadatendatei wurde im Ausgabeordner nicht gefunden. Erwäge ggf. eine Umbenennung der Datei.
Erwarteter Dateiname: {0}.
+
+ Name der generierten .7z-Paketdatei
+
+ Publishing Tutorial
+ Update Support Warnung
+ Für diesen Mod sind keine Update-Einstellungen konfiguriert, was bedeutet, dass er keine automatischen Updates erhalten kann.
Lies vor der Veröffentlichung bitte das „Tutorial zum Veröffentlichen“, um herauszufinden, wie du dies einrichten kannst.
+
+
+ Benutzerkonfiguration bearbeiten
+ Betaversionen erlauben
+ Ermöglicht das Herunterladen früherer Versionen des Mods.
+
+
+ Download abgeschlossen
+ Download abgeschlossen.
Dieses Fenster wird in wenigen Sekunden automatisch geschlossen.
+ Abhängigkeiten werden herunterladen
+
+
+ Quelle
+ Alle Quellen
+
+
+ Mod-Download-Quellen
+
+
+ Falsche ausführbare Datei
+ Nicht übereinstimmender Hash
+ Warnung
+
+ Die ausführbare Hauptdatei (EXE) der Anwendung ist geändert oder hat eine andere Version als erwartet.
+ Bitte beachte, dass sich möglicherweise nicht alle Mods wie erwartet verhalten. Erwäge die in der obigen Nachricht enthaltenen Anweisungen (falls vorhanden) zu befolgen.
+
+ Ein schneller Scan des Spieleverzeichnisses hat die folgenden Warnungen ergeben:
+ Bitte beachte, dass sich möglicherweise nicht alle Mods wie erwartet verhalten. Befolge gegebenenfalls die Anweisungen in den obigen Warnungen (falls vorhanden)..
+
+ Spiel auswählen
+ Bitte wähle dein gewünschtes Spiel aus der folgenden Liste aus und drücke OK.
+ Wenn dein Spiel nicht in der Liste enthalten ist, wähle bitte „Anderes Spiel“ aus.
+ Anderes Spiel
+
+ Community Konfiguration testen
+ Ermöglicht dir, die Anwendungskonfiguration zu testen, bevor du sie an den Reloaded Community-Index sendest.
+
+ JSON-Datei auswählen
+ Community Konfiguration
+
+
+ Speichert eine Verknüpfung, die zum Starten der modifizierten App ohne Reloaded Launcher verwendet werden kann.
+ Lädt eine Liste der aktivierten Mods.
+ Speichert die aktuelle Liste der aktivierten Mods.
+ Du kannst mich verwenden, um Reloaded in dieses Programm zu laden.
+ Du kannst mich verwenden, um aktuell geladene Mods zu verwalten.
+
+
+ Zurücksetzen
+ Setzt die Konfigurationswerte auf ihre Standardwerte zurück.
[Hinweis: Erfordert Mod-Unterstützung, kontaktiere den Autor, wenn es nicht funktioniert]
+
+
+ Controller-Konfiguration
+ Keine
+ Tut nichts.
+
+ Hoch
+ Navigiert im Menü nach oben.
+
+ Runter
+ Navigiert im Menü nach unten.
+
+ Links
+ Navigiert im Menü nach links.
+
+ Rechts
+ Navigiert im Menü nach rechts.
+
+ Akzeptieren
+ Klickt auf eine Schaltfläche.
+
+ Rückgang
+ Schließt das aktuelle Nicht-Hauptfenster.
+
+ Nächste Seite
+ Navigiert in Fenstern mit mehreren Seiten zur nächsten Seite.
+
+ Letzte Seite
+ Navigiert in Fenstern mit mehreren Seiten zur vorherigen Seite.
+
+ Inkrement
+ Addiert +1 zum aktuellen Wert eines UI-Elements.
+
+ Verringern
+ Entfernt 1 vom aktuellen Wert eines UI-Elements.
+
+ Modifikator
+ Ermöglicht alternatives Verhalten für einige UI-Elemente.
Beispielsweise kann Mod+Auf verwendet werden, um die Mod-Reihenfolge neu zu ordnen.
+
+ Menü links/rechts [Stick]
+ Navigiert im Menü nach links/rechts
+
+ Menü auf/ab [Stick]
+ Navigiert im Menü nach oben/unten
+
+ Hauptkonfiguration
+ Trigger
+ Verwende dies bei Zuordnung zum Controller-Trigger
+ Reloaded Controller Konfiguration
+
+ Neu
+ Linksklick weist eine neue Bindung zu.
Mittelklick kehrt den Wert um.
Rechtsklick löscht die Zuordnung!
! zeigt an, dass der Wert invertiert ist.
+ Auszeit
+
+
+ Erfordert die Aktivierung des Mods „Reloaded II Server“ mit dem LiteNetLib-Host.
Lade es über das Menü „Mods herunterladen“ herunter.
+ Status
+
+
+ Von Wine starten
+ Wenn du Reloaded in Wine ausführst, beachte bitte Folgendes:
+
+ - Das Spiel wird mit derselben Wine/Proton-Konfiguration wie dieser Launcher gestartet.
+
+ - Wenn dies nicht gewünscht ist
+ ASI Lader bereitstellen
+ und starte dein Spiel von außerhalb des Launchers.
+
+ - Das Ausführen mit Proton erfordert einige zusätzliche Schritte,
+ Bitte beachte das Wiki.
+
+ Für weitere Hilfe, bitte
+ sieh im Wiki nach
+ oder
+ spreche ein Problem auf GitHub an.
+
+ Anwendung starten
+
+
+ Liesmich & Änderungsprotokoll
+ Liesmich-Datei festlegen
+ Ermöglicht dir die Angabe einer Liesmich-Datei.
Für die Beschreibung im Menü „Mods herunterladen“ werden Liesmich-Dateien verwendet.
Verwendet Markdown. Weitere Informationen findest du im Wiki.
+ Änderungsprotokoll-Datei festlegen
+ Ermöglicht dir die Angabe einer Änderungsprotokolldatei.
Änderungsprotokolle werden im Menü „Mods herunterladen“ und im Update-Dialog verwendet.
Verwendet Markdown. Weitere Informationen findest du im Wiki.
+
+ Markdown-Datei auswählen (*.md).
+ Markdown Datei
+
+
+ NuGet
+
+ Projektseite
+ Schließen
+
+ Mod von:
+ Mod Autor
+ Gefällt mir Anzahl
+ Download Anzahl
+ Ansichten
+ Letzte Aktualisierung
+
+
+ Laufzeit Update Erforderlich
+ Für die neue Version von reloaded ist ein .NET-Laufzeit-Update erforderlich.
Der Download beginnt, nachdem dieses Feld geschlossen wurde.
Klicke bitte auf „Ja“, wenn Dialogfelder zur Benutzerkontensteuerung angezeigt werden.
+
+
+ Mod-Name
+ Update?
+
+
+ Webseite
+ Vollständiger HTTP(s)-Link zu Ihrer Website.
+ Webseite besuchen
+
+
+ Mod-Pack erstellen
+
+ Es werden nur berechtigte Mods angezeigt.
+ Mods müssen Updates unterstützen, um berechtigt zu sein.
+
+
+ Mod-Pack bearbeiten
+
+ Hinweis: Die Bilder in diesem Karussell werden während der Installation in der richtigen Größe angezeigt.
Du kannst alle Aspektprobleme ignorieren.
+ Bildbeschreibung
+
+ Zum Bearbeiten anklicken
+ Mod hinzufügen
+ Fügt diesem Paket einen Mod hinzu.
Hinweis: Dies kann eine Weile dauern, wenn viele Bilder vorhanden sind.
+ Mod entfernen
+ Entfernt den zuletzt ausgewählten Mod aus diesem Paket.
+ Liesmich speichern
+ Liesmich festlegen
+ Bild hinzufügen
+ Bild entfernen
+ Vorhandenes Paket laden
+ Paket speichern
+ Paket testen
+
+ Name des Pakets, wie er während der Installation angezeigt wird.
+ Name des Mods, wie er während der Installation angezeigt wird.
+ Fügt dem Pack/Mod ein neues Bild hinzu.
+ Entfernt das aktuell sichtbare Bild.
+ Kurze Beschreibung des Mods erste Zeile. Der Benutzer kann während der Installation erweitern, um die vollständige Liesmich-Datei anzuzeigen.
+ Aktualisiert die vollständige Liesmich-Datei des Mods/Pakets. Die Liesmich-Datei verwendet das Markdown-Format (.md)..
+ Speichert die Liesmich-Datei zur Bearbeitung in einem externen Editor auf der Festplatte.
+
+ Mod Pack auswählen (*.r2pack).
+ R2 Mod Pack
+ Mod Pack konnte nicht gespeichert werden
+
+
+ Mod Pack installieren
+
+ Herunterladen?
+ Einige Mods konnten nicht heruntergeladen werden
+
+ Mods zum herunterladen bestätigen
+ Nachdem du noch einmal überprüft hast was du möchtest, starte den Download.
+
+ wird heruntergeladen {0}...
+ Start
+ Zurückgehen
+ Herunterladen starten
+ Diesen Mod installieren
+ Diesen Mod überspringen
+
+
+ Bild auswählen.
+ Bild
+
+
+ Installierte ausblenden
+ Versteckt nach Möglichkeit bereits installierte Mods.
(Unterstützt NuGet & 'Veröffentliche' exportierte Mods.)
+
+
+ Der endgültige Pfad der Datei konnte nicht aufgelöst werden (Symlink-Erkennung).
+Aufgrund von DRM oder seltsamen Dateisystem-Spielereien könnte ein Fehler auftreten. Kopiere in diesem Fall den EXE-Pfad aus dem Loader-Protokoll in das Menü „Anwendung bearbeiten“..
+
+ Datei-Metadaten/Versionsinformationen konnten nicht von der Festplatte abgerufen werden. Wir werden unser Bestes geben, um etwas zu erraten.
+(Berichten zufolge kann dies bei einigen exotischen Netzwerkkonfigurationen [z.B CIFS] passieren. Wenn keine weiteren Probleme auftreten, sollte alles in Ordnung sein.).
+
+ Beim Hinzufügen der Anwendung sind Fehler aufgetreten: Mögliche Ursachen sind:
+- Ungewöhnliches Dateisystem-Setup (Anwendung auf Netzwerkfreigabe usw.)
+- Du hast nicht das Recht, auf die Datei zuzugreifen.
+- Microsoft DRM (MS Store/GamePass).
+
+ Die EXE-Datei konnte nicht gelesen werden. Dies kann verschiedene Gründe haben.
+- DRM (Microsoft Store/GamePass).
+- Du hast nicht das Recht, auf die EXE-Datei zuzugreifen.
+- Die Datei wurde von ihrem ursprünglichen Speicherort verschoben.
+
+Wenn du mit MS Store/GamePass arbeitest, musst du den ASI LOADER aufgrund von DRM (Verschlüsselung!!) höchstwahrscheinlich manuell bereitstellen.
+und starte dann das Programm direkt per EXE. Frag deine Community, wenn du weitere Hilfe benötigst.
+
+DRM: 'Du wirst nichts besitzen und glücklich sein.'
+
+
+ Die EXE-Datei konnte nicht gelesen werden, wenn ASI Loader bereitgestellt werden kann. Dies kann verschiedene Gründe haben.
+- DRM (Microsoft Store/GamePass).
+- Du hast nicht das Recht, auf die EXE-Datei zuzugreifen.
+- Die Datei wurde von ihrem ursprünglichen Speicherort verschoben.
+
+
+ Alle Mods installieren
+
+
+ Keine
+ Zuletzt bearbeitet
+ Downloads
+ Gefällt mir
+ Ansichten
+
+ Aufsteigend
+ Absteigend
+
+ Bitte beachte, dass einige Downloadquellen das Sortieren/Filtern möglicherweise nicht unterstützen.
+
+
+ Lade ALLE Dateien so hoch, wie sie vom Herausgeber erstellt wurden. Benenne keine davon um.
+Weitere Informationen findest du im Tutorial. Vergiss nicht, das richtige Veröffentlichungsziel festzulegen.
+
+
+
+
+ Etikett hinzufügen
+ Ein voreingestelltes Etikett auswählen
+ Etikettname hier (oder Voreinstellung auswählen 👉)
+ Nach Etikett filtern
+
+
+
+ Arbeitsverzeichnis
+
+
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml
index 4656cc5a..d2dc9542 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/en-GB.xaml
@@ -459,7 +459,7 @@ Usually this can happen because:
Resets the configuration values to their defaults.
[Note: Requires mod support, contact author if doesn't work]
- Controller Configuration [Beta]
+ Controller Configuration
None
Does nothing.
@@ -505,7 +505,7 @@ Usually this can happen because:
Main Config
Trigger
Use This if Mapping to Controller Trigger
- Reloaded Controller Configuration [Beta]
+ Reloaded Controller Configuration
New
Left click assigns a new binding.
Middle click inverts the value.
Right click clears the mapping!
! indicates the value is inverted.
@@ -658,7 +658,8 @@ DRM: 'You will own nothing and you will be happy.'
Failed to read EXE file for if can deploy ASI Loader. This might happen for a variety of reasons.
- DRM (Microsoft Store/GamePass).
- You don't have the right to access the EXE file.
-- The file has moved from its original location.
+- The file has moved from its original location.
+
Install All Mods
@@ -686,9 +687,43 @@ For more info, refer to the tutorial. Don't forget to set correct Publish target
Select a Preset Tag
Tag Name Here (or select preset 👉)
Filter By Tag
-
-
+
Working Directory
+
+ Preserve Mod Order Across Restarts
+ When ENABLED, order of all mods is preserved across App restarts.
When DISABLED, 'enabled' mods are at the top in load order and 'disabled' mods at bottom, in alphabetical order.
(Default is 'ENABLED' to gauge user feedback, but may change in future.)
+
+
+ This GamePass title cannot be Auto Setup, sorry. You'll need a manual install of ASI Loader or other shim.
+ Don't Inject Loader
+ When ENABLED, doesn't inject loader. Use this when you are loading Reloaded via an external shim like ASI Loader.
When DISABLED, injects loader as usual.
+
+
+ Installed Mods via Drag & Drop
+ Installed {0} Mod(s)
+
+
+
+ Install Mods
+ Mods can be installed by dragging them over the Reloaded window.
+ Alternatively, you can
+ Install Mods Manually
+
+
+ Search Settings
+ Previous
+ Next
+
+
+ Delete Mod
+ {0}
will be deleted.
+
+
+ Potentially Problematic Path Detected
+ The application you selected is in a folder that is managed by OneDrive or contains non-ASCII (special) characters.
It is recommended to move the application to a different path, such as "C:\Games\".
Using the application in an unsupported path may result in mods not working.
Press OK to add the application anyway.
+ Reloaded-II has detected it is installed in a folder that is managed by OneDrive or contains non-ASCII (special) characters.
It is recommended to move your Reloaded-II folder to a different path, such as "C:\Reloaded-II\".
Keeping Reloaded-II in an unsupported path may result in mods not working.
+ Reloaded-II has detected your mods folder is managed by OneDrive or contains non-ASCII (special) characters.
It is recommended to move your mods folder to a different path.
Keeping mods in an unsupported path may result in problems running mods.
+
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/es-419.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/es-419.xaml
index 068b210f..620c15a6 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/es-419.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/es-419.xaml
@@ -456,7 +456,7 @@
Reinicia la configuración a sus valores predeterminados.
[Nota: no funciona si el mod no es compatible con esta función, contacta con el autor si es así]
- Configuración de controladores [Beta]
+ Configuración de controladores
Nada
No hace nada.
@@ -502,7 +502,7 @@
Configuración principal
Gatillo
Usa esta opción para asignar el gatillo de un controlador
- Configuración de controladores para Reloaded [Beta]
+ Configuración de controladores para Reloaded
Nueva
Haz clic izquierdo para crear una asignación nueva.
Haz clic central para invertir el valor.
Haz clic derecho para eliminar la asignación.
«!» indica que el valor está invertido.
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/es-ES.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/es-ES.xaml
index 5c64c3e4..baf3018d 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/es-ES.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/es-ES.xaml
@@ -456,7 +456,7 @@
Reinicia la configuración a sus valores predeterminados.
[Nota: no funciona si el mod no es compatible con esta función, contacta con el autor si es así]
- Configuración de mandos [Beta]
+ Configuración de mandos
Nada
No hace nada.
@@ -502,7 +502,7 @@
Configuración principal
Gatillo
Usa esta opción para asignar el gatillo de un mando
- Configuración de mandos para Reloaded [Beta]
+ Configuración de mandos para Reloaded
Nueva
Haz clic izquierdo para crear una asignación nueva.
Haz clic central para invertir el valor.
Haz clic derecho para eliminar la asignación.
«!» indica que el valor está invertido.
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/nl-NL.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/nl-NL.xaml
index 7aef5662..74ecdd88 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/nl-NL.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/nl-NL.xaml
@@ -1,6 +1,7 @@
+ xmlns:sys="clr-namespace:System;assembly=mscorlib"
+ xml:space="preserve">
@@ -213,7 +214,11 @@
niet gevonden. Dit zou kunnen worden veroorzaakt doordat de Antivirus software veranderingen in de GitHub repository verwijderd of beschadigd.
Gefaald om het process te starten. In het algemeen wordt dit veroorzaakt door één van de volgende 3 problemen:
- Het pad naar de EXE klopt niet (heeft u de applicatie verplaatst?)
- De applicatie is ingesteld om als een admin uitgevoerd te worden (rechtermuisknop -> eigenschappen) maar Reloaded wordt uitgevoerd in gebruiker mode.
- Storing van bv. Antivirus software. Controleer uw logs.
Hier is de error terug gegeven door Windows:
Gefaald om DLL inject uittevoeren in de applicatie process.
- Het pad naar de applicatie die uitgevoerd moet worden klop niet. Controleer alstublieft de applicatie configuratie.
+ Reloaded kan het EXE bestand voor dit programma niet vinden.
+Overweeg om dit aan te passen in `Bewerk Applicatie` -> `Update`.
+Dit kan voorkomen omdat:
+- Je de applicatie folder hebt verplaatst.
+- De App folder was verplaatst na een update (dit kan gebeuren met GamePass/UWP).
Gefaald om te controleren voor updates. We worden momenteel begrenst door GitHub or zijn niet verbonden met het internet.
Waarschuwing: Je mist waarschijnlijk de x86 versie van .NET Core, 32-bit applicatie zullen niet werken.
Bekijk opnieuw de benodigdheden op de download pagina.
Echte Error: Gefaald om het adres van GetProcAddress for x86 te verkrijgen.
@@ -454,7 +459,7 @@
Reset de huidige configuratie waarden naar de standaard waarden.
[Let op: Dit benodigd mod ondersteuning, zoek contact op met de auteur als dit niet werkt]
- Controller Configuratie [Beta]
+ Controller Configuratie
Geen
Doet niks.
@@ -500,7 +505,7 @@
Hoofd Configuratie
Trigger
Gebruik Dit om de Controller Trigger in te stellen
- Reloaded Controller Configuratie [Beta]
+ Reloaded Controller Configuratie
Nieuw
Links klikken wijst een nieuwe verbinding toe.
Middel klik inverteert het getal.
Rechts klik verwijdert de toewijzing!
! geeft aan dat het getal is geinverteert.
@@ -681,4 +686,12 @@ Voor meer informatie, bekijk de tutorial. Vergeet niet om de correcte Publish do
Selecteer een Preset Tag
Tag Naam Hier (of selecteer preset 👉)
Filter Met Tag
+
+
+ Werk Folder
+
+
+ Hou Mod Volgorde Instant Ongeacht Opnieuw Opstarten
+ Als dit AAN staat, dan wordt de volgorde van de mods in stand gehouden, ongeacht herstarten.
Als het UIT staat, staan 'geactiveerde' mods aan de top met de laad volgorde en 'uitgezette' mods onderaan, in alfabetische volgorde.
(Standaard staat dit 'AAN' om gebruikersfeedback te ontvangen, maar dit kan veranderen in de toekomst.)
+
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/pirate.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/pirate.xaml
index 7bd863b6..35afb9e8 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/pirate.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/pirate.xaml
@@ -503,7 +503,7 @@ This window will automatically close in a few seconds.
[note: Requires mod support, contact author if doesn't work]
- Controller configuration [beta]
+ Controller configuration
None
Does nothing.
@@ -550,7 +550,7 @@ E.g. Mod+up can be used to re-order th' mod order.
Main config
Trigger
Use this if mapping to controller trigger
- Reloaded controller configuration [beta]
+ Reloaded controller configuration
New
Left click assigns a new binding.
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/pt-BR.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/pt-BR.xaml
index 9af4cd32..d18e3165 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/pt-BR.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/pt-BR.xaml
@@ -292,7 +292,7 @@
Resetar
- Configuração de Controle [Beta]
+ Configuração de Controle
Nenhum
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/uwu.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/uwu.xaml
index b3bcfdbb..56cd85a5 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/uwu.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/uwu.xaml
@@ -503,7 +503,7 @@ This >_< w-window wiww automaticawwy c-cwose in a f-few seconds.
- >w< C-Contwowwew Configuwation [Beta]
+ >w< C-Contwowwew Configuwation
N-Nyonye
D-Does >w< n-nyothing.
@@ -550,7 +550,7 @@ This >_< w-window wiww automaticawwy c-cwose in a f-few seconds.Main Config
Twiggew
Use This if Mapping to >w< C-Contwowwew Twiggew
- Wewoaded >w< C-Contwowwew Configuwation [Beta]
+ Wewoaded >w< C-Contwowwew Configuwation
Nyew
Weft cwick a-assigns a nyew binding.
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/zh-CN.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/zh-CN.xaml
new file mode 100644
index 00000000..d391c8e6
--- /dev/null
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/zh-CN.xaml
@@ -0,0 +1,694 @@
+
+
+
+
+
+
+ 添加应用程序
+ 编辑应用程序
+ 管理 Mods
+ 下载 Mods
+ Mod 加载器设置
+ 主页面
+ 搜索/筛选应用程序
+ 准备 Reloaded II..
+
+
+ 正在创建默认配置
+ 正在清除应用程序/加载器/Mod 配置
+ 正在配置资源
+ 加载完成于:
+ 正在创建模板
+ 正在检查更新
+ 正在进行检查
+
+
+ 点击此图片来导入一个自定义的 LOGO。
+ 选择要用作图标的图片。
+ 图片
+
+ 新增
+ 删除
+
+ 应用程序名称
+ 可执行文件位置
+ 命令行参数
+
+ 高级工具与选项
+ 创建快捷方式
+ 自动注入
+ 注意:虽然提供了自动注入功能,但不推荐使用,因为无法保证 Mod 会在应用程序之前执行代码。
请改用 Reloaded 的常规「启动应用程序」功能或 GitHub 文档中描述的「DLL 加载器」方法。
如果使用自动注入模式,Mod 不工作的相关错误报告请勿提交。
+
+ 选择一个应用程序来创建配置。
+ 应用程序可执行文件
+
+ 已创建快捷方式
+ 快捷方式已创建于:
+
+
+ 搜索 Mods
+ 搜索应用程序
+ 额外选项
+ 打开 Mod 目录
+ 转换为 NuGet 格式
+ 管理依赖项
+ 选择要修改详情的 Mod。
+ 选择与此 Mod 兼容的应用程序。
+
+
+ 设置 Mod 依赖项
+
+
+ 创建 Mod
+ 创建 Mod
+ 唯一 Mod ID
+ 名称
+ 作者
+ 版本
+ 简短描述
+ Mod 依赖项
+
+ 为 Mod 选择一个自定义的 Logo
+ 图片
+
+ 非唯一 Mod ID
+ 您正在创建的 Mod 的 ModId 不是唯一的。
请确保 ModId 的唯一性。
+
+
+ 清理配置
+ 此操作对各种配置(应用、Mod 等)进行整理。
整理功能包括从 Mod 的依赖项列表中移除不存在的 Mod 等。
请注意,如果缺少任何依赖项、支持的应用程序等,它们将从配置文件中移除。
+ 清理完成
+
+ 已安装的应用程序:
+ 已安装的 Mods:
+ 编译于:
+ 用户指南 + 文档。
+ Reloaded II 是一款根据 GNU GPL V3 许可证授权的自由软件
+ 自动更新
+ 显示控制台
+ 异步加载
+ Reloaded 是一款功能强大的软件,支持执行任意/自定义代码。请自行承担使用风险。
+
+
+ 应用中心
+ Mods 总数
+ 其他实例
+ Reloaded 实例
+
+
+ 欢迎使用 Reloaded II
+ 这是您首次使用 Reloaded II。
+ 在开始之前,您可以考虑查看以下链接:
+ 文档
+ 用户指南
+ 您之后可以在 [Mod 加载器设置] 中找到这些链接。
+
+
+ 确定
+ 取消
+
+
+ 非 Reloaded 进程
+ Reloaded 不管理这一特定进程。
+ 您可以点击下面的按钮加载 Reloaded。
+ 注入
+ 注意:如果在应用程序运行且未暂停的情况下加载 Mod,可能并非所有Mod都能按预期工作。请不要因使用此功能而导致 Mod 无法工作而提交错误报告。
+
+
+ Reloaded 进程
+
+
+ Mods 配置
+ 启动应用程序
+
+ 拖放项目以更改 Mod 的加载顺序。会依序从顶部至底部的方式开始依次加载。
+ 使用 Reloaded 告诉您要下载一些 Mod。Reloaded-II 超酷!可爱即是正义,这是铁律!
+ Mod 配置
+ 打开文件夹
+
+ 加载 Mod 集
+ 保存 Mod 集
+ 选择文件以加载 Mod 列表。
+ 选择路径以保存 Mod 列表。
+
+
+ 兼容性警告
+ 您启用了与您的应用程序不兼容的 Mod:
+ 点击「确定」将会启用这些 Mods。点击「取消」将会禁用这些 Mods。
+
+
+ 继续
+ 暂停
+ 卸载
+ 刷新
+ 加载 Mod
+ 注意:此菜单主要供开发者使用。
+
+
+ 可用的 Mod 更新
+ 下载
+ Mod ID
+ 大小(MB)
+ 旧版本
+ 新版本
+
+
+ 应用程序运行中
+ 您目前有正在运行 Reloaded 的应用程序。
更新将会下载,但是直到关闭所有应用程序之后才会应用。
+
+
+ 更新可用
+ 当前版本
+ 新版本
+
+ 没有可用的更新日志
+ 在 GitHub 上查看更新日志
+ 更新
+
+ 应用程序正在运行
+ 您有应用程序目前正在 Reloaded 中运行。
无法进行更新,请关闭所有应用程序。
+ 进程列表
+
+
+ 下载
+ 下载中
+ 已下载
+
+ 访问网站
+ 检查更新和依赖项
+
+
+ 好耶!
+ 没有需要更新的 Mod!
+
+
+ 下载包
+ 以下包将会被安装:
+ 以下包未找到:
+ 您需要自己找到它们 :/
+
+
+ 糟糕!
+ 尝试加载 Reloaded-II 时发生错误。
+
+
+ 下载 Mod 压缩包
+ 文件名称
+
+
+ 您需要以系统管理员权限执行此应用程序。
需要管理员权限才能从 Windows 管理工具 (WMI) 接收应用程序启动/退出事件。
开发者:以管理员身份运行您喜欢的集成开发环境(IDE),例如 Visual Studio。
+
+
+ Mod 配置
+ 保存
+
+
+ 错误
+ 未知错误
+ 此应用程序的路径为空或未设置。请修正路径。
+ 无法获取要删除的 Mod 目录。
+ 无法获取要删除的应用程序目录。
+ 未找到。这可能是由于杀毒软件删除和/或在 GitHub 仓库中进行了破坏性更改所致。
+ 启动进程失败。这通常是由以下 3 个问题之一引起的:
- EXE 的路径不正确(您是否移动了应用程序?)
- 程序配置为以管理员身份运行(右键 -> 属性),但 Reloaded 正在用户模式下运行。
- 来自例如防病毒软件的干扰。检查您的日志。
以下是 Windows 返回的错误:
+ DLL 注入应用程序进程失败。
+ Reloaded 无法找到此程序的 EXE 文件。
+请考虑在「编辑应用程序」->「更新」中更新它。
+通常,这可能是因为:
+- 您移动了应用程序文件夹。
+- 应用程序文件夹已在更新后移动 (可能发生在 GamePass/UWP)。
+ 检查更新失败。我们可能被 GitHub 限制,或者未连接到互联网。
+ 警告:您可能缺少 x86 版本的 .NET Core,32 位应用程序将无法工作。
请重新检查下载页面上列出的要求。
实际错误:获取 x86 的 GetProcAddress 地址失败。
+
+
+ 部署 ASI Loader
+ ASI Loader 部署
+ 如果您希望 Reloaded 能够在不使用启动器或快捷方式的情况下自动启动,可以部署 ThirteenAG 的 Ultimate ASI Loader(第三方 DLL 加载器)。您想要部署这个加载器吗?
+ ASI Loader 成功部署到:
+ Reloaded 引导程序已部署至:
+ 未找到合适的 DLL 名称以添加 Ultimate ASI Loader。
+
+
+ 缺少依赖项
+ 看来您的系统缺少运行 Reloaded 所需的某些依赖项。启动器发现了以下缺少的依赖项:
+ 请下载上面提供的必要依赖项,然后关闭窗口。
+
+
+ 主要
+ 操作
+ 进度
+ 概要
+
+
+ 发推文给我
+ 在 GitHub 上赞助我
+ 加入 Discord 服务器
+
+
+ 配置 NuGet 源
+ 配置源
+
+ 名称(必填)
+ URL(必填)
+ 示例:http://167.71.128.50:5000/v3/index.json
+ 描述
+
+
+ Wine 兼容性提示
+ 看起来您正尝试通过 Wine 在启动器内运行一个应用程序。
这一操作可能会失败,因为目标应用程序需要特殊的技巧来运行,或者隔离(如不同的 wine 前缀)阻止了 DLL 注入。
果要在 Wine 中执行,建议你部署 ASI Loader(参见:「编辑应用程序」菜单)并在启动器外运行游戏。
请注意,Reloaded 不经常在 Wine 下进行测试。
+ 继续尝试
+
+
+ Reloaded 设置
+ 语言
+ 主题
+ 查看日志文件
+ 打开配置文件
+
+
+ 应用程序可能已经崩溃、Reloaded 加载失败,或 Reloaded 在超时限制内未初始化。
以下操作可能有帮助:
- 关闭此错误消息,等待几秒钟,然后再试一次。
- 如果应用程序的其他副本正在运行,请关闭它们。(使用任务管理器)
- 重启您的计算机。
这是一个警告,如果您的应用程序按预期工作,您可以忽略此消息。
未能获取在进程内运行的 Reloaded 实例的端口。
+
+
+ 引导程序已更新
+ Reloaded 引导程序已为以下程序自动更新:
{0}
+
+
+ 无法创建目录以部署引导程序。请考虑以系统管理员身份运行 Reloaded。
实际错误讯息: {0}
+
+
+ Reloaded II 教程
+
+ 添加应用程序
+ 提取 Mods
+ Mods 配置
+ 启用 Mods
+ 完成
+
+ 添加应用程序
+ 跳过教程
+ 跳过此步骤
+ 开始使用 Reloaded 的第一步是添加您要修改的应用程序。
(通常可以在启动器左下角的 + 按钮中找到)
+ 确保添加的是应用程序,而不是启动器,如上所示。
+
+ 要安装 Mod,只需将下载的 zip 文件解压到 'Mods' 文件夹中即可。
+ 如果下载的 Mod 中没有单独的文件夹,请自行创建一个。
+ 上一步
+ 下一步
+
+ 一些 Mod 可能支持额外的配置,允许你进行调整。
+ 如果当 Mod 被高亮显示时,「Mod 配置」选项变为红色,代表该 Mod 可以进行配置。
+
+ 要启用 Mods,只需点击方形复选框即可。
+ 如果复选框是红色的,则表示 Mod 已启用;如果是灰色的,则表示 Mod 已禁用。
+
+ 感谢您使用 Reloaded II。
+ 您可能也会发现以下链接有用:
+ 这些链接也可以在 [Mod 加载器设置] 中找到。
+
+
+ 发布
+ 编辑
+ 编辑 Mod
+ 用户配置
+ 打开文件夹
+ 配置
+
+
+ 编辑 Mod
+
+
+ 错误:无法确定 Reloaded II 版本
+ Mod(s) 使用中
+ 您目前有正在运行 Reloaded 的应用程序。
更新将会下载,但是直到关闭所有应用程序之后才会应用。
+ 缺失的依赖项
+ 版本号无效
+ Mod 使用的版本号不符合 `Semantic Versioning` (SemVer) 标准,因此无法接收更新。
请更新您的 Mod 版本号以使用 SemVer。
示例:`1.0.0`,`1.0`,`2.0-alpha`。
+
+
+ 描述
+
+ 一般详情
+ Mod 依赖项
+ 更新支持
+ 完成
+
+ 如果您的 Mod 需要其他 Mod 才能运行,请在此处选择它们。
+
+ 通用 Mod
+ 如果启用,该 Mod 默认在每个应用程序中都可见。
+
+ 库
+ 如果启用,这个 Mod 将不能被用户显式启用。只能作为依赖项被启用。
+
+ 保存
+ 选择哪些应用程序可以使用该 Mod。
+
+ JSON 元数据文件的名称。
如果您在同一个页面上发布多个 Mods,才需要更改此名称。
+
+
+ 右键点击 Mod 以获取更多选项
+
+
+ 唯一 Mod ID
+ 请按照 "game.type.name" 的格式设置一个唯一的 Mod ID。例如:`sonicheroes.skins.seasidehillmidnight`。一旦设置了 ID,就无法更改。
+
+
+ 应用程序 ID
+ 更新
+
+ 可执行文件名称警告
+ 应用程序添加的可执行文件名称与原始名称不同。
如果您正在添加另一个应用程序,您应该将其作为新应用程序添加。
+
+
+ 发布 Mod
+ 发布目标
+ 除非发布地点有特定选项,否则使用默认配置。
启用/禁用某些行为。
+
+ 排除文件
+ 包含文件
+ 之前的版本
+ 提供您的 Mod 的早期版本可以使用增量压缩技术,这是一种在从早期版本升级到后期版本时能够减小下载大小的方法。
+ 发布
+
+ 选择一个 Mod 配置文件 (ModConfig.json)。
+ Configuration
+
+ 无效的 Mod 配置
+ 提供的文件不代表一个有效的 Mod 配置。
+
+ 无效的 Mod 版本
+ 提供的 Mod 版本高于或已等同于即将发布的版本。
+
+ 无效的 Mod ID
+ 提供的 Mod ID 与即将发布的 Mod ID 不符。
+
+ 文件包含 & 排除
+ 正则表达式
+ 测试正则表达式
+ 排除的文件
+
+ 压缩设置
+ 压缩等级
+ 压缩方法
+
+ 输出文件夹
+ 设置输出文件夹
+
+ 自动增量
+ 自动从上一个版本创建增量包。
不要忘记将输出路径设置为现有发布的路径。
+
+ 自动增量警告
+ 在输出文件夹中未找到发布元数据文件。如果需要,请考虑重命名文件。
预期文件名:{0}。
+
+ 生成的 .7z 压缩包文件名称
+
+ 发布教程
+ 更新支持警告
+ 此 Mod 没有配置更新设置,这意味着它将无法接收自动更新。
发布前,请查看「发布教程」以了解如何设置。
+
+
+ 编辑用户配置
+ 允许 Beta 版本
+ 允许下载 Mod 的早期版本。
+
+
+ 下载完成
+ 下载已完成。此窗口将在几秒钟内自动关闭。
+ 正在下载依赖项
+
+
+ 来源
+ 所有来源
+
+
+ Mod 下载源
+
+
+ 错误的可执行文件
+ 哈希值不匹配
+ 警告
+
+ 该应用程序的主要可执行文件 (EXE) 已被修改或与预期版本不同。
+ 请注意,并非所有的 Mod 都可能按预期工作。请考虑遵循上方消息中提供的指导(如果有)。
+
+ 快速扫描游戏目录产生了以下警告:
+ 请注意,并非所有的 Mod 都可能按预期工作。请考虑遵循上方警告中提供的指导(如果有)。
+
+ 选择游戏
+ 请从以下列表中选择您想要的游戏,然后点击「确定」。
+ 如果您的游戏不在列表中,请选择「其他游戏」。
+ 其他游戏
+
+ 测试社区配置
+ 允许您在提交到 Reloaded 社区索引之前测试应用程序配置。
+
+ 选择 JSON 文件
+ 社区配置
+
+
+ 保存一个快捷方式,可用于在不使用 Reloaded 启动器的情况下启动已修改的应用程序。
+ 加载已启用的 Mods 列表。
+ 保存当前已启用的 Mods 列表。
+ 你可以使用我将 Reloaded 加载到此程序中。
+ 你可以使用我来管理当前加载的 Mods。
+
+
+ 重置
+ 将配置值重置为默认值。
[注意:需要 Mod 支持,如果不起作用请联系作者]
+
+
+ 控制器配置
+ 无
+ 无任何操作。
+
+ 上
+ 向上导航菜单。
+
+ 下
+ 向下导航菜单。
+
+ 左
+ 向左导航菜单。
+
+ 右
+ 向右导航菜单。
+
+ 确定
+ 点击按钮。
+
+ 拒绝
+ 关闭当前非主要窗口。
+
+ 下一页
+ 在含有多个页面的窗口中导航到下一页。
+
+ 上一页
+ 在含有多个页面的窗口中导航到上一页。
+
+ 增加
+ 将 UI 元素的目前值加上 1。
+
+ 减少
+ 将 UI 元素的目前值减去 1。
+
+ 修改器
+ 允许某些 UI 元素进行替代操作。
例如,使用 Mod+Up 可以重新排序 Mod 顺序。
+
+ 菜单左/右 [摇杆]
+ 在菜单中左/右导航
+
+ 菜单上/下 [摇杆]
+ 在菜单中上下移动
+
+ 主配置
+ 触发器
+ 如果要映射到控制器的触发器上,请使用此选项
+ Reloaded 控制器配置
+
+ 新增
+ 左键点击分配新的绑定。
中键点击反转值。
右键点击清除映射!
! 表示值被反转。
+ 超时
+
+
+ 需要启用 'Reloaded II Server' Mod 并配合 LiteNetLib 主机。
可从「下载 Mod」菜单中下载。
+ 状态
+
+
+ 从 Wine 启动
+ 您正在 Wine 中运行 Reloaded,请注意以下事项:
+
+ - 游戏将使用与此启动器相同的 Wine/Proton 配置启动。
+
+ - 如果这不是您所希望的,
+ 部署 ASI Loader
+ 并从启动器外部启动您的游戏。
+
+ - 使用 Proton 需要一些额外步骤,
+ 请参阅 Wiki。
+
+ 如需额外帮助,请
+ 参阅 Wiki
+ 或
+ 在 GitHub 上提交问题。
+
+ 启动应用程序
+
+
+ Readme & 更新日志
+ 指定 Readme 文件
+ 这允许你指定一个 Readme 文件。
Readme 文件用于在「下载 Mod」菜单中的描述。
使用 Markdown 格式。更多信息请参考 Wiki。
+ 指定更新日志文件
+ 这允许你指定一个更新日志 (Changelog) 文件。
更新日志在「下载 Mod」菜单和更新对话框中使用。
使用 Markdown 格式。更多信息请参考 Wiki。
+
+ 选择一个 Markdown 文件 (*.md).
+ Markdown 文件
+
+
+ NuGet
+
+ 项目网站
+ 关闭
+
+ Mod 来自:
+ Mod 作者
+ 点赞数
+ 下载次数
+ 浏览次数
+ 最近更新时间
+
+
+ 需要运行时更新
+ 新版本的 reloaded 需要进行 .NET 运行时更新。
在关闭此对话框后将会开始下载。
如果出现任何用户账户控制 (UAC) 对话框,请点击“是”以继续。
+
+
+ Mod 名称
+ 更新?
+
+
+ 网站
+ 您的网站的完整 HTTP(s) 链接。
+ 访问网站
+
+
+ 创建 Mod 包
+
+ 仅显示符合条件的 Mod。
+ Mod 必须支持更新才能符合条件。
+
+
+ 编辑 Mod 包
+
+ 注意:此轮播中的图片在安装期间会被正确调整大小。
您可以忽略任何比例问题。
+ 图片标题
+
+ 点击以编辑
+ 添加 Mod
+ 将一个 Mod 添加到此 Mod 包中。
注意:如果存在大量图片,这可能需要一些时间。
+ 移除 Mod
+ 从此 Mod 包中移除最后选择的 Mod。
+ 保存 Readme
+ 设置 Readme
+ 添加图片
+ 移除图片
+ 加载现有的 Mod 包
+ 保存 Mod 包
+ 测试 Mod 包
+
+ Mod 包的名称,在安装时显示。
+ Mod 的名称,在安装时显示。
+ 向 Mod 包/Mod 中添加新图片。
+ 移除目前正在显示的图片。
+ Mod 的简短说明。1 行。用户可以在安装过程中展开以显示完整的 Readme 文件。
+ 更新 Mod/Mod 包的完整 Readme 文件。Readme 文件使用 Markdown (.md) 格式。
+ 将 Readme 文件保存回磁盘,以便在外部编辑器中编辑。
+
+ 选择一个 Mod 包 (*.r2pack)。
+ R2 Mod 包
+ 无法保存 Mod 包
+
+
+ 安装 Mod 包
+
+ 下载?
+ 一些 Mod 下载失败
+
+ 确认要下载的 Mod
+ 在您仔细检查并确认想要下载的内容后,开始下载。
+
+ 正在下载 {0}...
+ 开始
+ 返回
+ 开始下载
+ 安装此 Mod
+ 跳过此 Mod
+
+
+ 选择图片
+ 图片
+
+
+ 隐藏已安装
+ 隐藏已安装的 Mod(如果可能)。
(支持 NuGet & “发布”导出的 Mod。)
+
+
+ 解析文件最终路径失败(符号链接检测)。
+可能由于 DRM 或一些奇怪的文件系统操作导致错误。如果发生这种情况,请从加载器日志中复制 EXE 路径到「编辑应用程序」菜单。
+
+ 从磁盘获取文件元数据/版本信息失败。我们会尽力猜测相关信息。
+(据报告,在某些特殊的网络配置下可能会发生这种情况 [例如 CIFS],如果你没有遇到更多问题,那么你应该是没问题的。)
+
+ 添加应用程序时发生错误,原因可能包括:
+- 不常见的文件系统设置(例如,应用程序位于网络共享上)
+- 您没有访问文件的权限。
+- 微软的 DRM(微软商店/GamePass)
+
+ 无法读取 EXE 文件。这可能由多种原因引起。
+- DRM(微软商店/GamePass)。
+- 您没有权限访问该 EXE 文件。
+- 文件已经从其原始位置移动。
+
+若涉及微软商店/GamePass,可能需手动部署 ASI LOADER 以解决 DRM(加密!!)问题,
+然后直接通过 EXE 启动程序。如需更多帮助,请咨询社区。
+
+DRM:“你将一无所有,并感到幸福。”
+
+
+ 无法读取 EXE 档案以部署 ASI Loader。这可能有多种原因。
+- DRM(微软商店/GamePass)。
+- 您没有权限访问该 EXE 文件。
+- 文件已经从其原始位置移动。
+
+
+ 安装所有 Mods
+
+
+ 无
+ 最后修改时间
+ 下载次数
+ 点赞数
+ 浏览次数
+
+ 升序
+ 降序
+
+ 请注意,某些下载源可能不支持排序/筛选功能。
+
+
+ 上传所有文件时请保留原文件名,不要对它们进行重命名。
+更多信息,请参考教程。不要忘记设置正确的发布目标。
+
+
+
+
+ 添加标签
+ 选择预设标签
+ 在此输入标签名称(或选择预设 👉)
+ 按标签筛选
+
+
+
+ 工作目录
+
+
diff --git a/source/Reloaded.Mod.Launcher/Assets/Languages/zh-TW.xaml b/source/Reloaded.Mod.Launcher/Assets/Languages/zh-TW.xaml
index f85bfea7..a5a70437 100644
--- a/source/Reloaded.Mod.Launcher/Assets/Languages/zh-TW.xaml
+++ b/source/Reloaded.Mod.Launcher/Assets/Languages/zh-TW.xaml
@@ -459,7 +459,7 @@
將設定數據重設為其預設值。
[注意:需要Mod支援,如果無法使用請聯繫作者]
- 控制器設定 [Beta]
+ 控制器設定
無
沒有任何動作。
@@ -505,7 +505,7 @@
主要設定
觸發器
如果要映射到控制器觸發器,請使用此選項
- 重新載入的控制器設定 [Beta]
+ 重新載入的控制器設定
新增
左鍵指定新的綁定。
中鍵翻轉值。
右鍵清除映射!
! 表示值已翻轉。
diff --git a/source/Reloaded.Mod.Launcher/Controls/PopupLabel.xaml b/source/Reloaded.Mod.Launcher/Controls/PopupLabel.xaml
index 0138aaca..cc2a3868 100644
--- a/source/Reloaded.Mod.Launcher/Controls/PopupLabel.xaml
+++ b/source/Reloaded.Mod.Launcher/Controls/PopupLabel.xaml
@@ -14,7 +14,7 @@
diff --git a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs
index 08aa67c6..9018ad04 100644
--- a/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs
+++ b/source/Reloaded.Mod.Launcher/MainWindow.xaml.cs
@@ -1,3 +1,9 @@
+using System.Text;
+using Reloaded.Mod.Loader.Update.Providers.Web;
+using Sewer56.DeltaPatchGenerator.Lib.Utility;
+using Sewer56.Update.Extractors.SevenZipSharp;
+using static Reloaded.Mod.Launcher.Lib.Static.Resources;
+
namespace Reloaded.Mod.Launcher;
///
@@ -5,6 +11,8 @@ namespace Reloaded.Mod.Launcher;
///
public partial class MainWindow : ReloadedWindow
{
+ private string[] SupportedDropFormats = [".7z", ".zip", ".rar"];
+
public Lib.Models.ViewModel.WindowViewModel RealViewModel { get; set; }
public MainWindow()
@@ -20,5 +28,77 @@ public MainWindow()
Lib.IoC.BindToConstant(this);
}
- protected override System.Windows.Automation.Peers.AutomationPeer OnCreateAutomationPeer() => new EmptyAutomationPeer(this);
+ private void InstallMod_DragOver(object sender, DragEventArgs e)
+ {
+ if (e.Data.GetDataPresent(DataFormats.FileDrop))
+ {
+ var files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
+
+ // Check if the file is a .zip file
+ foreach (var file in files)
+ {
+ var extension = Path.GetExtension(file);
+ if (!SupportedDropFormats.Contains(extension))
+ continue;
+
+ e.Effects = DragDropEffects.Copy;
+ e.Handled = true;
+ return;
+ }
+ }
+
+ e.Handled = true;
+ }
+
+ private async void InstallMod_Drop(object sender, DragEventArgs e)
+ {
+ if (!e.Data.GetDataPresent(DataFormats.FileDrop))
+ return;
+
+ var files = (string[])e.Data.GetData(DataFormats.FileDrop)!;
+ var config = Lib.IoC.GetConstant();
+ var modsFolder = config.GetModConfigDirectory();
+
+ // Get list of installed mods before
+ var modConfigService = Lib.IoC.GetConstant();
+ var modsBefore = new Dictionary>(modConfigService.ItemsById);
+
+ // Install mods.
+ foreach (var file in files)
+ {
+ var extension = Path.GetExtension(file);
+ if (!SupportedDropFormats.Contains(extension))
+ continue;
+
+ /* Extract to Temp Directory */
+ using var tempFolder = new TemporaryFolderAllocation();
+ var archiveExtractor = new SevenZipSharpExtractor();
+ await archiveExtractor.ExtractPackageAsync(file, tempFolder.FolderPath, new Progress(), default);
+
+ /* Get name of package. */
+ WebDownloadablePackage.CopyPackagesFromExtractFolderToTargetDir(modsFolder!, tempFolder.FolderPath, default);
+ }
+
+ // Find the new mods
+ modConfigService.ForceRefresh();
+ var newConfigs = new List();
+ foreach (var item in modConfigService.ItemsById.ToArray())
+ {
+ if (!modsBefore.ContainsKey(item.Key))
+ newConfigs.Add(item.Value.Config);
+ }
+
+ if (newConfigs.Count <= 0)
+ return;
+
+ // Print loaded mods.
+ var installedMods = new StringBuilder();
+ foreach (var conf in newConfigs)
+ installedMods.AppendLine($"{conf.ModName} ({conf.ModId})");
+
+ var loadedMods = string.Format(DragDropInstalledModsDescription.Get(), newConfigs.Count);
+ Actions.DisplayMessagebox?.Invoke(DragDropInstalledModsTitle.Get(),
+ $"{loadedMods}\n\n{installedMods}",
+ new Actions.DisplayMessageBoxParams() { StartupLocation = Actions.WindowStartupLocation.CenterScreen });
+ }
}
\ No newline at end of file
diff --git a/source/Reloaded.Mod.Launcher/Misc/Imaging.cs b/source/Reloaded.Mod.Launcher/Misc/Imaging.cs
index aa5c83f4..d7d4c97f 100644
--- a/source/Reloaded.Mod.Launcher/Misc/Imaging.cs
+++ b/source/Reloaded.Mod.Launcher/Misc/Imaging.cs
@@ -1,3 +1,4 @@
+using Reloaded.Memory.Extensions;
using Image = System.Drawing.Image;
using Rectangle = System.Drawing.Rectangle;
@@ -91,7 +92,7 @@ public static Image ImageFromUri(Uri source)
///
/// The input image.
/// The output stream.
- public static bool TryConvertToIcon(Bitmap inputBitmap, Stream output)
+ public unsafe static bool TryConvertToIcon(Bitmap inputBitmap, Stream output)
{
if (inputBitmap == null)
return false;
@@ -111,7 +112,7 @@ public static bool TryConvertToIcon(Bitmap inputBitmap, Stream output)
streams.Add(imageStream);
}
- using var iconWriter = new ExtendedMemoryStream();
+ using var iconWriter = new MemoryStream();
// Write ICO header.
iconWriter.Write(new IcoHeader()
@@ -121,7 +122,7 @@ public static bool TryConvertToIcon(Bitmap inputBitmap, Stream output)
});
// Make Image Headers
- var imageDataOffset = Struct.GetSize() + (Struct.GetSize() * sizes.Length);
+ var imageDataOffset = sizeof(IcoHeader) + (sizeof(IcoEntry) * sizes.Length);
for (int x = 0; x < sizes.Length; x++)
{
iconWriter.Write(new IcoEntry()
diff --git a/source/Reloaded.Mod.Launcher/Pages/BasePage.xaml b/source/Reloaded.Mod.Launcher/Pages/BasePage.xaml
index 8c4205ce..d294e31c 100644
--- a/source/Reloaded.Mod.Launcher/Pages/BasePage.xaml
+++ b/source/Reloaded.Mod.Launcher/Pages/BasePage.xaml
@@ -11,6 +11,7 @@
xmlns:properties="clr-namespace:Reloaded.Mod.Launcher.Controls.Properties"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="800"
+ AllowDrop="True"
Title="{DynamicResource TitleMainPage}">
diff --git a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml
index c46aa105..8c7f02f9 100644
--- a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml
+++ b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml
@@ -11,6 +11,7 @@
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="756"
+ AllowDrop="True"
Title="{DynamicResource TitleApplication}">
diff --git a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml.cs b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml.cs
index e7b58b2c..fd3c60be 100644
--- a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml.cs
+++ b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationPage.xaml.cs
@@ -98,7 +98,7 @@ private async void LaunchApplication_PreviewMouseDown(object sender, MouseButton
var launcher = ApplicationLauncher.FromApplicationConfig(appTuple);
if (!Environment.IsWine || (Environment.IsWine && CompatibilityDialogs.WineShowLaunchDialog()))
- launcher.Start();
+ launcher.Start(!appTuple.Config.DontInject);
}
catch (Exception ex)
{
diff --git a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/AppSummaryPage.xaml b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/AppSummaryPage.xaml
index 11b7d318..cd75387d 100644
--- a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/AppSummaryPage.xaml
+++ b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/AppSummaryPage.xaml
@@ -45,12 +45,14 @@
ItemsSource="{Binding AllTags}"
Margin="{DynamicResource CommonItemHorizontalMarginLeft}"
ToolTip="{DynamicResource ConfigureModsSelectTagTooltip}"
+ AutomationProperties.Name="{DynamicResource ConfigureModsSelectTagTooltip}"
ToolTipService.InitialShowDelay="0"
Width="130" />
@@ -106,7 +108,7 @@
Focusable="False"
IsThreeState="False" />
-
+
+
+
+
+
+
diff --git a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/NonReloadedProcessPage.xaml.cs b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/NonReloadedProcessPage.xaml.cs
index 840bd678..b8cf56d0 100644
--- a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/NonReloadedProcessPage.xaml.cs
+++ b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/ApplicationSubPages/NonReloadedProcessPage.xaml.cs
@@ -18,7 +18,7 @@ private void Button_Click(object sender, System.Windows.RoutedEventArgs e)
var process = ViewModel.ApplicationViewModel.SelectedProcess;
if (!process!.HasExited)
{
- var injector = new ApplicationInjector(ViewModel.ApplicationViewModel.SelectedProcess!);
+ using var injector = new ApplicationInjector(ViewModel.ApplicationViewModel.SelectedProcess!);
injector.Inject();
// Exit page.
diff --git a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/DownloadPackagesPage.xaml b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/DownloadPackagesPage.xaml
index 26ab7b76..f478035a 100644
--- a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/DownloadPackagesPage.xaml
+++ b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/DownloadPackagesPage.xaml
@@ -11,6 +11,7 @@
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
mc:Ignorable="d"
d:DesignHeight="600" d:DesignWidth="756"
+ AllowDrop="True"
Title="{DynamicResource TitleDownloadMods}">
@@ -106,6 +107,7 @@
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding CurrentPackageProvider, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
ItemsSource="{Binding PackageProviders, UpdateSourceTrigger=PropertyChanged}"
+ AutomationProperties.Name="{DynamicResource DownloadPackagesSource}"
MaxHeight="{Binding ActualHeight, ElementName=TextBox}"
Width="150" >
@@ -121,6 +123,7 @@
Grid.Column="1"
Style="{DynamicResource TextboxWithPlaceholder}"
Tag="{DynamicResource ModManagerSearchMods}"
+ AutomationProperties.Name="{DynamicResource ModManagerSearchMods}"
Text="{Binding SearchQuery, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Margin="{DynamicResource CommonItemHorizontalMarginSmaller}"
FontSize="{DynamicResource FontSizeLarger}"
@@ -141,12 +144,14 @@
@@ -162,6 +167,7 @@
@@ -21,7 +22,7 @@
-
+
@@ -158,7 +159,8 @@
@@ -167,6 +169,7 @@
Margin="{DynamicResource PanelMargin}"
Style="{DynamicResource TextboxWithPlaceholder}"
Tag="{DynamicResource ModManagerSearchApps}"
+ AutomationProperties.Name="{DynamicResource ModManagerSearchApps}"
TextChanged="AppsFilter_TextChanged"
Grid.Column="1"
Grid.Row="0"/>
@@ -184,7 +187,7 @@
VerticalContentAlignment="Top">
-
+
diff --git a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/SettingsPage.xaml b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/SettingsPage.xaml
index 8a54eac4..de51df81 100644
--- a/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/SettingsPage.xaml
+++ b/source/Reloaded.Mod.Launcher/Pages/BaseSubpages/SettingsPage.xaml
@@ -3,20 +3,18 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:launcher="clr-namespace:Reloaded.Mod.Launcher"
xmlns:converters="clr-namespace:Reloaded.Mod.Launcher.Converters"
xmlns:pages="clr-namespace:Reloaded.Mod.Launcher.Pages"
- xmlns:controls="clr-namespace:Reloaded.Mod.Launcher.Controls"
- xmlns:system="clr-namespace:System;assembly=System.Runtime"
- xmlns:hc="https://handyorg.github.io/handycontrol"
+ xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d"
d:DesignHeight="600"
d:DesignWidth="900"
+ AllowDrop="True"
Title="{DynamicResource TitleLoaderSettings}">
- 75,20,75,0
+ 75,0,75,0