├── MCLauncher ├── .gitignore ├── App.config ├── Properties │ ├── Settings.settings │ ├── Settings.Designer.cs │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ └── Resources.resx ├── SampleData │ ├── Versions.xaml │ └── SampleClasses.cs ├── App.xaml ├── App.xaml.cs ├── MCLauncher.csproj.user ├── Preferences.cs ├── ProgressDialog.xaml.cs ├── VersionListEndpointDialog.xaml.cs ├── ProgressDialog.xaml ├── RelayCommand.cs ├── LinkResolver.cs ├── VersionListEndpointDialog.xaml ├── app.manifest ├── WUTokenHelper.cs ├── VersionDownloader.cs ├── WUProtocol.cs ├── VersionList.cs ├── MCLauncher.csproj ├── MainWindow.xaml └── MainWindow.xaml.cs ├── WUTokenHelper ├── pch.cpp ├── .gitignore ├── packages.config ├── WUTokenHelper.vcxproj.user ├── pch.h ├── TokenBrokerInternal.idl ├── WUTokenHelper.vcxproj.filters ├── main.cpp └── WUTokenHelper.vcxproj ├── .gitignore ├── README.md ├── MCLauncher.sln └── LICENSE.txt /MCLauncher/.gitignore: -------------------------------------------------------------------------------- 1 | obj/ 2 | bin/ -------------------------------------------------------------------------------- /WUTokenHelper/pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | x64 3 | packages 4 | Debug -------------------------------------------------------------------------------- /WUTokenHelper/.gitignore: -------------------------------------------------------------------------------- 1 | Generated Files/ 2 | x64/ -------------------------------------------------------------------------------- /WUTokenHelper/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /WUTokenHelper/WUTokenHelper.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /MCLauncher/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /MCLauncher/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /WUTokenHelper/pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "winrt/Windows.Internal.Security.Authentication.Web.h" -------------------------------------------------------------------------------- /MCLauncher/SampleData/Versions.xaml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /MCLauncher/App.xaml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MCLauncher/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Windows; 3 | 4 | namespace MCLauncher { 5 | /// 6 | /// Interaction logic for App.xaml 7 | /// 8 | public partial class App : Application { 9 | 10 | protected override void OnStartup(StartupEventArgs e) { 11 | base.OnStartup(e); 12 | Debug.Listeners.Add(new TextWriterTraceListener("Log.txt")); 13 | Debug.AutoFlush = true; 14 | } 15 | 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /MCLauncher/MCLauncher.csproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | publish\ 5 | 6 | 7 | 8 | 9 | 10 | en-US 11 | false 12 | 13 | -------------------------------------------------------------------------------- /MCLauncher/Preferences.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace MCLauncher { 4 | public class Preferences { 5 | public bool ShowInstalledOnly { get; set; } = false; 6 | 7 | public bool DeleteAppxAfterDownload { get; set; } = true; 8 | 9 | [JsonProperty("VersionsApi")] 10 | public string VersionsApiUWP { get; set; } = ""; 11 | 12 | public string VersionsApiGDK { get; set; } = ""; 13 | 14 | public bool HasPreviouslyUsedGDK { get; set; } = false; 15 | 16 | public bool ShowLegacyBetaTab { get; set; } = false; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MCLauncher/SampleData/SampleClasses.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MCLauncher.SampleData 8 | { 9 | public class SampleVersion 10 | { 11 | public string Name { get; set; } 12 | 13 | public string DisplayName { get; set; } 14 | public string DisplayInstallStatus { get; set; } 15 | 16 | public bool IsInstalled { get; set; } 17 | 18 | public bool IsBeta { get; set; } 19 | } 20 | 21 | public class SampleVersionList : List { } 22 | } 23 | -------------------------------------------------------------------------------- /MCLauncher/ProgressDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Shapes; 14 | 15 | namespace MCLauncher { 16 | /// 17 | /// Interaction logic for ProgressDialog.xaml 18 | /// 19 | public partial class ProgressDialog : Window { 20 | public ProgressDialog() { 21 | InitializeComponent(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /WUTokenHelper/TokenBrokerInternal.idl: -------------------------------------------------------------------------------- 1 | namespace Windows.Internal.Security.Authentication.Web{ 2 | 3 | [version(1)] 4 | [uuid(07650a66-66ea-489d-aa90-0dabc75f3567)] 5 | interface ITokenBrokerInternalStatics : IInspectable { 6 | 7 | HRESULT filler_GetTokenSilently(); 8 | HRESULT filler_GetSecureInputParameters(); 9 | HRESULT filler_ReportBackgroundCompletion(); 10 | HRESULT filler_FindAccount(); 11 | HRESULT filler_FindAccountForApp(); 12 | HRESULT filler_FindAccountForProvider(); 13 | 14 | HRESULT FindAllAccountsAsync( 15 | [out][retval] Windows.Foundation.IAsyncOperation *> ** operation); 16 | 17 | } 18 | 19 | [version(1)] 20 | [static(Windows.Internal.Security.Authentication.Web.ITokenBrokerInternalStatics, 1)] 21 | [marshaling_behavior(agile)] 22 | [threading(both)] 23 | runtimeclass TokenBrokerInternal { 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /MCLauncher/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace MCLauncher.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /MCLauncher/VersionListEndpointDialog.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Windows; 7 | using System.Windows.Controls; 8 | using System.Windows.Data; 9 | using System.Windows.Documents; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Windows.Media.Imaging; 13 | using System.Windows.Shapes; 14 | 15 | namespace MCLauncher 16 | { 17 | /// 18 | /// Interaction logic for VersionListEndpointDialog.xaml 19 | /// 20 | public partial class VersionListEndpointDialog : Window 21 | { 22 | public event SetEndpointHandler OnEndpointChanged; 23 | 24 | public delegate void SetEndpointHandler(object sender, string newUwpIdsEndpoint, string newGdkPackageUrlsEndpoint); 25 | 26 | 27 | public VersionListEndpointDialog(string currentEndpoint) { 28 | InitializeComponent(); 29 | EndpointTextBox.Text = currentEndpoint; 30 | } 31 | 32 | private void okButton_Click(object sender, RoutedEventArgs e) { 33 | OnEndpointChanged?.Invoke(this, EndpointTextBox.Text, GDKUrlsEndpointTextBox.Text); 34 | Close(); 35 | } 36 | 37 | private void cancelButton_Click(object sender, RoutedEventArgs e) { 38 | Close(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /MCLauncher/ProgressDialog.xaml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 30 | 31 | -------------------------------------------------------------------------------- /WUTokenHelper/WUTokenHelper.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Header Files 20 | 21 | 22 | 23 | 24 | Source Files 25 | 26 | 27 | Source Files 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Source Files 39 | 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCLauncher 2 | 3 | This tool allows you to install several versions of Minecraft: Windows 10 Edition (Bedrock) side-by-side. 4 | This is useful if you want to test beta versions, releases or anything else side-by-side without needing to uninstall and reinstall the game. 5 | 6 | ## Disclaimer 7 | This tool will **not** help you to pirate the game; it requires that you have a Microsoft account which can be used to download Minecraft from the Store. 8 | 9 | ## Prerequisites 10 | - A Microsoft account connected to Microsoft Store which **owns Minecraft for Windows 10** 11 | - **Administrator permissions** on your user account (or access to an account that has) 12 | - **Developer mode** enabled for app installation in Windows 10 Settings 13 | - If you want to be able to use beta versions, you'll additionally need to **subscribe to the Minecraft Beta program using Xbox Insider Hub**. 14 | - [Microsoft Visual C++ Redistributable](https://aka.ms/vs/16/release/vc_redist.x64.exe) installed. 15 | 16 | ## Setup 17 | - Download the latest release from the [Releases](https://github.com/MCMrARM/mc-w10-version-launcher/releases) section. Unzip it somewhere. 18 | - Run `MCLauncher.exe` to start the launcher. 19 | 20 | ## Compiling the launcher yourself 21 | You'll need Visual Studio with Windows 10 SDK version 10.0.17763 and .NET Framework 4.6.1 SDK installed. You can find these in the Visual Studio Installer if you don't have them out of the box. 22 | The project should build out of the box with VS as long as you haven't done anything bizarre. 23 | 24 | ## Frequently Asked Questions 25 | **Does this allow running multiple instances of Minecraft: Bedrock at the same time?** 26 | 27 | At the time of writing, no. It allows you to _install_ multiple versions, but only one version can run at a time. 28 | -------------------------------------------------------------------------------- /MCLauncher/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Windows.Input; 4 | 5 | namespace MCLauncher { 6 | /// 7 | /// A command whose sole purpose is to relay its functionality to other objects by invoking delegates. The 8 | /// default return value for the CanExecute method is 'true'. 9 | /// See: https://stackoverflow.com/a/3531935 10 | /// 11 | public class RelayCommand : ICommand { 12 | 13 | readonly Action _execute; 14 | readonly Predicate _canExecute; 15 | 16 | /// 17 | /// Creates a new command that can always execute. 18 | /// 19 | /// The execution logic. 20 | public RelayCommand(Action execute) : this(execute, null) { 21 | } 22 | 23 | /// 24 | /// Creates a new command. 25 | /// 26 | /// The execution logic. 27 | /// The execution status logic. 28 | public RelayCommand(Action execute, Predicate canExecute) { 29 | _execute = execute ?? throw new ArgumentNullException("execute"); 30 | _canExecute = canExecute; 31 | } 32 | 33 | [DebuggerStepThrough] 34 | public bool CanExecute(object parameters) { 35 | return _canExecute == null ? true : _canExecute(parameters); 36 | } 37 | 38 | public event EventHandler CanExecuteChanged { 39 | add { CommandManager.RequerySuggested += value; } 40 | remove { CommandManager.RequerySuggested -= value; } 41 | } 42 | 43 | public void Execute(object parameters) { 44 | _execute(parameters); 45 | } 46 | 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MCLauncher/LinkResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Runtime.InteropServices; 4 | using Microsoft.Win32.SafeHandles; 5 | 6 | namespace MCLauncher 7 | { 8 | 9 | public static class LinkResolver 10 | { 11 | private const int fileFlagBackupSemantics = 0x02000000; 12 | private const int fileShareAll = 0x07; 13 | private const int openExisting = 3; 14 | 15 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 16 | private static extern SafeFileHandle CreateFile( 17 | string path, 18 | int desiredAccess, 19 | int shareMode, 20 | IntPtr securityAttributes, 21 | int creationDisposition, 22 | int flags, 23 | IntPtr templateFile); 24 | 25 | [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] 26 | private static extern int GetFinalPathNameByHandle( 27 | SafeFileHandle handle, 28 | System.Text.StringBuilder buffer, 29 | int bufferSize, 30 | int flags); 31 | 32 | public static string Resolve(string directory) 33 | { 34 | SafeFileHandle handle = CreateFile( 35 | directory, 36 | 0, 37 | fileShareAll, 38 | IntPtr.Zero, 39 | openExisting, 40 | fileFlagBackupSemantics, 41 | IntPtr.Zero); 42 | 43 | if (handle.IsInvalid) 44 | { 45 | throw new IOException("Could not open directory"); 46 | } 47 | 48 | var buffer = new System.Text.StringBuilder(1024); 49 | int result = GetFinalPathNameByHandle(handle, buffer, buffer.Capacity, 0); 50 | 51 | if (result <= 0) 52 | { 53 | throw new IOException("Could not resolve final path"); 54 | } 55 | 56 | string raw = buffer.ToString(); 57 | 58 | return raw.StartsWith(@"\\?\") ? raw.Substring(4) : raw; 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /WUTokenHelper/main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "combaseapi.h" 3 | #include 4 | 5 | using namespace winrt; 6 | using namespace Windows::Foundation; 7 | using namespace Windows::Security::Authentication::Web::Core; 8 | using namespace Windows::Internal::Security::Authentication::Web; 9 | using namespace Windows::Security::Cryptography; 10 | 11 | #define WU_NO_ACCOUNT MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x200) 12 | #define WU_TOKEN_FETCH_ERROR_BASE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 0x400) 13 | 14 | extern "C" __declspec(dllexport) int __stdcall GetWUToken(wchar_t** retToken) { 15 | auto tokenBrokerStatics = get_activation_factory(); 16 | auto statics = tokenBrokerStatics.as(); 17 | auto accounts = statics.FindAllAccountsAsync().get(); 18 | wprintf(L"Account count = %i\n", accounts.Size()); 19 | if (accounts.Size() == 0) 20 | return WU_NO_ACCOUNT; 21 | auto accountInfo = accounts.GetAt(0); 22 | wprintf(L"ID = %s\n", accountInfo.Id().c_str()); 23 | wprintf(L"Name = %s\n", accountInfo.UserName().c_str()); 24 | 25 | auto accountProvider = WebAuthenticationCoreManager::FindAccountProviderAsync(L"https://login.microsoft.com", L"consumers").get(); 26 | WebTokenRequest request(accountProvider, L"service::dcat.update.microsoft.com::MBI_SSL", L"{28520974-CE92-4F36-A219-3F255AF7E61E}"); 27 | auto result = WebAuthenticationCoreManager::GetTokenSilentlyAsync(request, accountInfo).get(); 28 | if (result.ResponseStatus() != WebTokenRequestStatus::Success) { 29 | return WU_TOKEN_FETCH_ERROR_BASE | static_cast(result.ResponseStatus()); 30 | } 31 | auto token = result.ResponseData().GetAt(0).Token(); 32 | wprintf(L"Token = %s\n", token.c_str()); 33 | auto tokenBinary = CryptographicBuffer::ConvertStringToBinary(token, BinaryStringEncoding::Utf16LE); 34 | auto tokenBase64 = CryptographicBuffer::EncodeToBase64String(tokenBinary); 35 | wprintf(L"Encoded token = %s\n", tokenBase64.c_str()); 36 | 37 | *retToken = (wchar_t*)::CoTaskMemAlloc((tokenBase64.size() + 1) * sizeof(wchar_t)); 38 | memcpy(*retToken, tokenBase64.data(), (tokenBase64.size() + 1) * sizeof(wchar_t)); 39 | return S_OK; 40 | } 41 | -------------------------------------------------------------------------------- /MCLauncher/VersionListEndpointDialog.xaml: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 48 | 49 | -------------------------------------------------------------------------------- /MCLauncher/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | using System.Windows; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MCLauncher")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MCLauncher")] 13 | [assembly: AssemblyCopyright("Copyright © 2019")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | //In order to begin building localizable applications, set 23 | //CultureYouAreCodingWith in your .csproj file 24 | //inside a . For example, if you are using US english 25 | //in your source files, set the to en-US. Then uncomment 26 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 27 | //the line below to match the UICulture setting in the project file. 28 | 29 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 30 | 31 | 32 | [assembly: ThemeInfo( 33 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 34 | //(used if a resource is not found in the page, 35 | // or application resource dictionaries) 36 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 37 | //(used if a resource is not found in the page, 38 | // app, or any theme specific resource dictionaries) 39 | )] 40 | 41 | 42 | // Version information for an assembly consists of the following four values: 43 | // 44 | // Major Version 45 | // Minor Version 46 | // Build Number 47 | // Revision 48 | // 49 | // You can specify all the values or you can default the Build and Revision Numbers 50 | // by using the '*' as shown below: 51 | // [assembly: AssemblyVersion("1.0.*")] 52 | [assembly: AssemblyVersion("1.0.0.0")] 53 | [assembly: AssemblyFileVersion("1.0.0.0")] 54 | -------------------------------------------------------------------------------- /MCLauncher/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace MCLauncher.Properties { 12 | 13 | 14 | /// 15 | /// A strongly-typed resource class, for looking up localized strings, etc. 16 | /// 17 | // This class was auto-generated by the StronglyTypedResourceBuilder 18 | // class via a tool like ResGen or Visual Studio. 19 | // To add or remove a member, edit your .ResX file then rerun ResGen 20 | // with the /str option, or rebuild your VS project. 21 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 22 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 23 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 24 | internal class Resources { 25 | 26 | private static global::System.Resources.ResourceManager resourceMan; 27 | 28 | private static global::System.Globalization.CultureInfo resourceCulture; 29 | 30 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 31 | internal Resources() { 32 | } 33 | 34 | /// 35 | /// Returns the cached ResourceManager instance used by this class. 36 | /// 37 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 38 | internal static global::System.Resources.ResourceManager ResourceManager { 39 | get { 40 | if ((resourceMan == null)) { 41 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MCLauncher.Properties.Resources", typeof(Resources).Assembly); 42 | resourceMan = temp; 43 | } 44 | return resourceMan; 45 | } 46 | } 47 | 48 | /// 49 | /// Overrides the current thread's CurrentUICulture property for all 50 | /// resource lookups using this strongly typed resource class. 51 | /// 52 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 53 | internal static global::System.Globalization.CultureInfo Culture { 54 | get { 55 | return resourceCulture; 56 | } 57 | set { 58 | resourceCulture = value; 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /MCLauncher.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.489 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MCLauncher", "MCLauncher\MCLauncher.csproj", "{9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7} = {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WUTokenHelper", "WUTokenHelper\WUTokenHelper.vcxproj", "{B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Debug|x64 = Debug|x64 17 | Debug|x86 = Debug|x86 18 | Release|Any CPU = Release|Any CPU 19 | Release|x64 = Release|x64 20 | Release|x86 = Release|x86 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Debug|x64.ActiveCfg = Debug|x64 26 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Debug|x64.Build.0 = Debug|x64 27 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Debug|x86.Build.0 = Debug|Any CPU 29 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Release|x64.ActiveCfg = Release|x64 32 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Release|x64.Build.0 = Release|x64 33 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Release|x86.ActiveCfg = Release|Any CPU 34 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77}.Release|x86.Build.0 = Release|Any CPU 35 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Debug|Any CPU.ActiveCfg = Debug|Win32 36 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Debug|x64.ActiveCfg = Debug|x64 37 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Debug|x64.Build.0 = Debug|x64 38 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Debug|x86.ActiveCfg = Debug|Win32 39 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Debug|x86.Build.0 = Debug|Win32 40 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Release|Any CPU.ActiveCfg = Release|Win32 41 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Release|x64.ActiveCfg = Release|x64 42 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Release|x64.Build.0 = Release|x64 43 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Release|x86.ActiveCfg = Release|Win32 44 | {B7336BA7-1E83-43D8-BD0E-3D44C6E3D4E7}.Release|x86.Build.0 = Release|Win32 45 | EndGlobalSection 46 | GlobalSection(SolutionProperties) = preSolution 47 | HideSolutionNode = FALSE 48 | EndGlobalSection 49 | GlobalSection(ExtensibilityGlobals) = postSolution 50 | SolutionGuid = {4B611BC5-61AA-4DAA-9A13-DF3631882AE0} 51 | EndGlobalSection 52 | EndGlobal 53 | -------------------------------------------------------------------------------- /MCLauncher/app.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 52 | 59 | 60 | 61 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /MCLauncher/WUTokenHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using Windows.Security.Authentication.Web.Core; 4 | 5 | namespace MCLauncher { 6 | class WUTokenHelper { 7 | 8 | public static string GetWUToken() { 9 | try { 10 | string token; 11 | int status = GetWUToken(out token); 12 | if (status >= WU_ERRORS_START && status <= WU_ERRORS_END) 13 | throw new WUTokenException(status); 14 | else if (status != 0) 15 | Marshal.ThrowExceptionForHR(status); 16 | return token; 17 | } catch (SEHException e) { 18 | Marshal.ThrowExceptionForHR(e.HResult); 19 | return ""; //ghey 20 | } 21 | } 22 | 23 | private const int WU_ERRORS_START = unchecked((int) 0x80040200); 24 | private const int WU_NO_ACCOUNT = unchecked((int) 0x80040200); 25 | 26 | private const int WU_TOKEN_FETCH_ERROR_BASE = unchecked((int) 0x80040300); 27 | private const int WU_TOKEN_FETCH_ERROR_END = unchecked((int) 0x80040400); 28 | 29 | private const int WU_ERRORS_END = unchecked((int) 0x80040400); 30 | 31 | [DllImport("WUTokenHelper.dll", CallingConvention = CallingConvention.StdCall)] 32 | private static extern int GetWUToken([MarshalAs(UnmanagedType.LPWStr)] out string token); 33 | 34 | public class WUTokenException : Exception { 35 | public WUTokenException(int exception) : base(GetExceptionText(exception)) { 36 | HResult = exception; 37 | } 38 | private static String GetExceptionText(int e) { 39 | if (e >= WU_TOKEN_FETCH_ERROR_BASE && e < WU_TOKEN_FETCH_ERROR_END) 40 | { 41 | var actualCode = (byte) e & 0xff; 42 | 43 | if(!Enum.IsDefined(typeof(WebTokenRequestStatus), e)) 44 | { 45 | return $"WUTokenHelper returned bogus HRESULT: {e} (THIS IS A BUG)"; 46 | } 47 | var status = (WebTokenRequestStatus) Enum.ToObject(typeof(WebTokenRequestStatus), actualCode); 48 | switch (status) 49 | { 50 | case WebTokenRequestStatus.Success: 51 | return "Success (THIS IS A BUG)"; 52 | case WebTokenRequestStatus.UserCancel: 53 | return "User cancelled token request (THIS IS A BUG)"; //TODO: should never happen? 54 | case WebTokenRequestStatus.AccountSwitch: 55 | return "User requested account switch (THIS IS A BUG)"; //TODO: should never happen? 56 | case WebTokenRequestStatus.UserInteractionRequired: 57 | return "User interaction required to complete token request (THIS IS A BUG)"; 58 | case WebTokenRequestStatus.AccountProviderNotAvailable: 59 | return "Xbox Live account services are currently unavailable"; 60 | case WebTokenRequestStatus.ProviderError: 61 | return "Unknown Xbox Live error"; 62 | } 63 | } 64 | switch (e) { 65 | case WU_NO_ACCOUNT: return "No Microsoft account found"; 66 | default: return "Unknown " + e; 67 | } 68 | } 69 | } 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /MCLauncher/VersionDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Xml; 9 | using System.Xml.Linq; 10 | using System.Collections.Generic; 11 | 12 | namespace MCLauncher { 13 | 14 | class BadUpdateIdentityException: ArgumentException{ 15 | public BadUpdateIdentityException() : base("Bad updateIdentity") { } 16 | } 17 | 18 | class VersionDownloader { 19 | 20 | private HttpClient client = new HttpClient(); 21 | private WUProtocol protocol = new WUProtocol(); 22 | 23 | private async Task PostXmlAsync(string url, XDocument data) { 24 | HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url); 25 | using (var stringWriter = new StringWriter()) { 26 | using (XmlWriter xmlWriter = XmlWriter.Create(stringWriter, new XmlWriterSettings { Indent = false, OmitXmlDeclaration = true })) { 27 | data.Save(xmlWriter); 28 | } 29 | request.Content = new StringContent(stringWriter.ToString(), Encoding.UTF8, "application/soap+xml"); 30 | } 31 | using (var resp = await client.SendAsync(request)) { 32 | string str = await resp.Content.ReadAsStringAsync(); 33 | return XDocument.Parse(str); 34 | } 35 | } 36 | 37 | private async Task DownloadFile(string url, string to, DownloadProgress progress, CancellationToken cancellationToken) { 38 | using (var resp = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) { 39 | using (var inStream = await resp.Content.ReadAsStreamAsync()) 40 | using (var outStream = new FileStream(to, FileMode.Create)) { 41 | long? totalSize = resp.Content.Headers.ContentLength; 42 | progress(0, totalSize); 43 | long transferred = 0; 44 | byte[] buf = new byte[1024 * 1024]; 45 | while (true) { 46 | int n = await inStream.ReadAsync(buf, 0, buf.Length, cancellationToken); 47 | if (n == 0) 48 | break; 49 | await outStream.WriteAsync(buf, 0, n, cancellationToken); 50 | transferred += n; 51 | progress(transferred, totalSize); 52 | } 53 | } 54 | } 55 | } 56 | 57 | private async Task GetDownloadUrl(string updateIdentity, string revisionNumber) { 58 | XDocument result = await PostXmlAsync(protocol.GetDownloadUrl(), 59 | protocol.BuildDownloadRequest(updateIdentity, revisionNumber)); 60 | Debug.WriteLine($"GetDownloadUrl() response for updateIdentity {updateIdentity}, revision {revisionNumber}:\n{result.ToString()}"); 61 | foreach (string s in protocol.ExtractDownloadResponseUrls(result)) { 62 | if (s.StartsWith("http://tlu.dl.delivery.mp.microsoft.com/")) 63 | return s; 64 | } 65 | return null; 66 | } 67 | 68 | public void EnableUserAuthorization() { 69 | protocol.SetMSAUserToken(WUTokenHelper.GetWUToken()); 70 | } 71 | 72 | public async Task DownloadAppx(string updateIdentity, string revisionNumber, string destination, DownloadProgress progress, CancellationToken cancellationToken) { 73 | string link = await GetDownloadUrl(updateIdentity, revisionNumber); 74 | if (link == null) 75 | throw new BadUpdateIdentityException(); 76 | Debug.WriteLine("Resolved download link: " + link); 77 | await DownloadFile(link, destination, progress, cancellationToken); 78 | } 79 | 80 | public async Task DownloadMsixvc(List downloadUrls, string destination, DownloadProgress progress, CancellationToken cancellationToken) { 81 | await DownloadFile(downloadUrls[0], destination, progress, cancellationToken); 82 | } 83 | 84 | public delegate void DownloadProgress(long current, long? total); 85 | 86 | 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /MCLauncher/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /MCLauncher/WUProtocol.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Xml; 4 | using System.Xml.Linq; 5 | using System.Xml.XPath; 6 | 7 | namespace MCLauncher { 8 | class WUProtocol { 9 | private static readonly string DEFAULT_URL = "https://fe3.delivery.mp.microsoft.com/ClientWebService/client.asmx"; 10 | private static readonly string SECURED_URL = "https://fe3.delivery.mp.microsoft.com/ClientWebService/client.asmx/secured"; 11 | 12 | private static XNamespace soap = "http://www.w3.org/2003/05/soap-envelope"; 13 | private static XNamespace addressing = "http://www.w3.org/2005/08/addressing"; 14 | private static XNamespace secext = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; 15 | private static XNamespace secutil = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"; 16 | private static XNamespace wuws = "http://schemas.microsoft.com/msus/2014/10/WindowsUpdateAuthorization"; 17 | private static XNamespace wuclient = "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService"; 18 | 19 | private string _msaUserToken; 20 | 21 | public void SetMSAUserToken(string token) { 22 | _msaUserToken = token; 23 | } 24 | 25 | private XElement BuildWUTickets() { 26 | XElement tickets = new XElement(wuws + "WindowsUpdateTicketsToken", 27 | new XAttribute(secutil + "id", "ClientMSA"), 28 | new XAttribute(XNamespace.Xmlns + "wsu", secutil), 29 | new XAttribute(XNamespace.Xmlns + "wuws", wuws)); 30 | if (_msaUserToken != null) { 31 | tickets.Add(new XElement("TicketType", 32 | new XAttribute("Name", "MSA"), 33 | new XAttribute("Version", "1.0"), 34 | new XAttribute("Policy", "MBI_SSL"), 35 | new XElement("User", _msaUserToken))); 36 | } 37 | tickets.Add(new XElement("TicketType", "", 38 | new XAttribute("Name", "AAD"), 39 | new XAttribute("Version", "1.0"), 40 | new XAttribute("Policy", "MBI_SSL"))); 41 | return tickets; 42 | } 43 | 44 | private XElement BuildHeader(string url, string methodName) { 45 | DateTime now = DateTime.UtcNow; 46 | XElement header = new XElement(soap + "Header", 47 | new XElement(addressing + "Action", "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService/" + methodName, 48 | new XAttribute(soap + "mustUnderstand", 1)), 49 | new XElement(addressing + "MessageID", "urn:uuid:5754a03d-d8d5-489f-b24d-efc31b3fd32d"), 50 | new XElement(addressing + "To", url, 51 | new XAttribute(soap + "mustUnderstand", 1)), 52 | new XElement(secext + "Security", 53 | new XAttribute(soap + "mustUnderstand", 1), 54 | new XAttribute(XNamespace.Xmlns + "o", secext), 55 | new XElement(secutil + "Timestamp", 56 | new XElement(secutil + "Created", now.ToString("o")), 57 | new XElement(secutil + "Expires", (now.AddMinutes(5)).ToString("o"))), 58 | BuildWUTickets())); 59 | return header; 60 | } 61 | 62 | public string GetDownloadUrl() { 63 | return SECURED_URL; 64 | } 65 | 66 | public XDocument BuildDownloadRequest(string updateIdentity, string revisionNumber) { 67 | XElement envelope = new XElement(soap + "Envelope"); 68 | envelope.Add(new XAttribute(XNamespace.Xmlns + "a", addressing)); 69 | envelope.Add(new XAttribute(XNamespace.Xmlns + "s", soap)); 70 | envelope.Add(BuildHeader(GetDownloadUrl(), "GetExtendedUpdateInfo2")); 71 | envelope.Add(new XElement(soap + "Body", 72 | new XElement(wuclient + "GetExtendedUpdateInfo2", 73 | new XElement(wuclient + "updateIDs", 74 | new XElement(wuclient + "UpdateIdentity", 75 | new XElement(wuclient + "UpdateID", updateIdentity), 76 | new XElement(wuclient + "RevisionNumber", revisionNumber))), 77 | new XElement(wuclient + "infoTypes", 78 | new XElement(wuclient + "XmlUpdateFragmentType", "FileUrl")), 79 | new XElement(wuclient + "deviceAttributes", "E:BranchReadinessLevel=CBB&DchuNvidiaGrfxExists=1&ProcessorIdentifier=Intel64%20Family%206%20Model%2063%20Stepping%202&CurrentBranch=rs4_release&DataVer_RS5=1942&FlightRing=Retail&AttrDataVer=57&InstallLanguage=en-US&DchuAmdGrfxExists=1&OSUILocale=en-US&InstallationType=Client&FlightingBranchName=&Version_RS5=10&UpgEx_RS5=Green&GStatus_RS5=2&OSSkuId=48&App=WU&InstallDate=1529700913&ProcessorManufacturer=GenuineIntel&AppVer=10.0.17134.471&OSArchitecture=AMD64&UpdateManagementGroup=2&IsDeviceRetailDemo=0&HidOverGattReg=C%3A%5CWINDOWS%5CSystem32%5CDriverStore%5CFileRepository%5Chidbthle.inf_amd64_467f181075371c89%5CMicrosoft.Bluetooth.Profiles.HidOverGatt.dll&IsFlightingEnabled=0&DchuIntelGrfxExists=1&TelemetryLevel=1&DefaultUserRegion=244&DeferFeatureUpdatePeriodInDays=365&Bios=Unknown&WuClientVer=10.0.17134.471&PausedFeatureStatus=1&Steam=URL%3Asteam%20protocol&Free=8to16&OSVersion=10.0.17134.472&DeviceFamily=Windows.Desktop")))); 80 | return new XDocument(envelope); 81 | } 82 | 83 | public string[] ExtractDownloadResponseUrls(XDocument doc) { 84 | XmlNamespaceManager nsmgr = new XmlNamespaceManager(new NameTable()); 85 | nsmgr.AddNamespace("s", "http://www.w3.org/2003/05/soap-envelope"); 86 | nsmgr.AddNamespace("wu", "http://www.microsoft.com/SoftwareDistribution/Server/ClientWebService"); 87 | XElement result = doc.XPathSelectElement("/s:Envelope/s:Body/wu:GetExtendedUpdateInfo2Response/wu:GetExtendedUpdateInfo2Result", nsmgr); 88 | if (result == null) 89 | return new string[0]; 90 | return result.XPathSelectElements("wu:FileLocations/wu:FileLocation/wu:Url", nsmgr).Select(u => u.Value).ToArray(); 91 | } 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /WUTokenHelper/WUTokenHelper.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | true 6 | true 7 | 15.0 8 | {b7336ba7-1e83-43d8-bd0e-3d44c6e3d4e7} 9 | Win32Proj 10 | WUTokenHelper 11 | 10.0.26100.0 12 | 10.0.17134.0 13 | 14 | 15 | 16 | 17 | Debug 18 | Win32 19 | 20 | 21 | Release 22 | Win32 23 | 24 | 25 | Debug 26 | x64 27 | 28 | 29 | Release 30 | x64 31 | 32 | 33 | 34 | DynamicLibrary 35 | v143 36 | v141 37 | v142 38 | Unicode 39 | 40 | 41 | true 42 | true 43 | 44 | 45 | false 46 | true 47 | false 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Use 61 | pch.h 62 | $(IntDir)pch.pch 63 | _CONSOLE;%(PreprocessorDefinitions) 64 | Level4 65 | %(AdditionalOptions) /permissive- /bigobj 66 | 67 | 68 | 69 | 70 | Disabled 71 | _DEBUG;%(PreprocessorDefinitions) 72 | 73 | 74 | Console 75 | false 76 | 77 | 78 | 79 | 80 | WIN32;%(PreprocessorDefinitions) 81 | 82 | 83 | 84 | 85 | MaxSpeed 86 | true 87 | true 88 | NDEBUG;%(PreprocessorDefinitions) 89 | 90 | 91 | Console 92 | true 93 | true 94 | false 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Create 104 | 105 | 106 | 107 | 108 | false 109 | 110 | 111 | 112 | 113 | Designer 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /MCLauncher/VersionList.cs: -------------------------------------------------------------------------------- 1 | using MCLauncher.WPFDataTypes; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Collections.ObjectModel; 6 | using System.Collections.Specialized; 7 | using System.ComponentModel; 8 | using System.Diagnostics; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Net.Http; 12 | using System.Threading.Tasks; 13 | 14 | namespace MCLauncher { 15 | public class VersionList : ObservableCollection { 16 | 17 | public string VersionsApiUWP { get; set; } 18 | public string VersionsApiGDK { get; set; } 19 | 20 | private static readonly string GDK_CONFIG_FILENAME = "MicrosoftGame.Config"; 21 | 22 | private readonly string _cacheFileUWP; 23 | private readonly string _cacheFileGDK; 24 | private readonly string _importedDirectory; 25 | private readonly WPFDataTypes.ICommonVersionCommands _commands; 26 | private readonly HttpClient _client = new HttpClient(); 27 | HashSet dbVersions = new HashSet(); 28 | 29 | private PropertyChangedEventHandler _versionPropertyChangedHandler; 30 | public VersionList(string cacheFileUWP, string importedDirectory, string versionsApiUWP, WPFDataTypes.ICommonVersionCommands commands, PropertyChangedEventHandler versionPropertyChangedEventHandler, string cacheFileGDK, string versionsApiGDK) { 31 | _cacheFileUWP = cacheFileUWP; 32 | _cacheFileGDK = cacheFileGDK; 33 | _importedDirectory = importedDirectory; 34 | VersionsApiUWP = versionsApiUWP; 35 | VersionsApiGDK = versionsApiGDK; 36 | _commands = commands; 37 | _versionPropertyChangedHandler = versionPropertyChangedEventHandler; 38 | CollectionChanged += versionListOnCollectionChanged; 39 | } 40 | 41 | private void versionListOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { 42 | if (e.OldItems != null) { 43 | foreach (var item in e.OldItems) { 44 | var version = item as WPFDataTypes.Version; 45 | version.PropertyChanged -= _versionPropertyChangedHandler; 46 | } 47 | } 48 | if (e.NewItems != null) { 49 | foreach (var item in e.NewItems) { 50 | var version = item as WPFDataTypes.Version; 51 | version.PropertyChanged += _versionPropertyChangedHandler; 52 | } 53 | } 54 | } 55 | 56 | private void ParseListUWP(JArray data, bool isCache) { 57 | // ([name, uuid, versionType])[] 58 | foreach (JArray o in data.AsEnumerable().Reverse()) { 59 | bool exists = !dbVersions.Add(o[0].Value()); 60 | bool isNew = !exists && !isCache; 61 | int versionType = o[2].Value(); 62 | if (!Enum.IsDefined(typeof(WPFDataTypes.VersionType), versionType) || versionType == (int) WPFDataTypes.VersionType.Imported) 63 | continue; 64 | Add(new WPFDataTypes.Version(o[1].Value(), o[0].Value(), (WPFDataTypes.VersionType)versionType, isNew, _commands, PackageType.UWP, null)); 65 | } 66 | } 67 | 68 | private void ParseListGDK(JObject data, bool isCache, WPFDataTypes.VersionType versionType) { 69 | foreach (var keyValue in data.Properties().Reverse()) { 70 | string versionName = keyValue.Name; 71 | JArray urls = (JArray)keyValue.Value; 72 | 73 | List downloadUrls = new List(); 74 | foreach (var url in urls) { 75 | downloadUrls.Add(url.Value()); 76 | } 77 | if (downloadUrls.Count == 0) { 78 | Debug.WriteLine("Not showing version " + versionName + " because it has no download URLs"); 79 | continue; 80 | } 81 | bool exists = !dbVersions.Add(versionName); 82 | bool isNew = !exists && !isCache; 83 | 84 | Add(new WPFDataTypes.Version(WPFDataTypes.Version.UNKNOWN_UUID, versionName, versionType, isNew, _commands, PackageType.GDK, downloadUrls)); 85 | } 86 | } 87 | 88 | private void ParseDataGDK(JObject data, bool isCache) { 89 | ParseListGDK(data["release"] as JObject, isCache, WPFDataTypes.VersionType.Release); 90 | ParseListGDK(data["preview"] as JObject, isCache, WPFDataTypes.VersionType.Preview); 91 | } 92 | 93 | public void PrepareForReload() { 94 | for (int i = Count - 1; i >= 0; i--) { 95 | if (this[i].VersionType != WPFDataTypes.VersionType.Imported) { 96 | RemoveAt(i); 97 | } 98 | } 99 | } 100 | 101 | public async Task LoadImported() { 102 | string[] subdirectoryEntries = await Task.Run(() => Directory.Exists(_importedDirectory) ? Directory.GetDirectories(_importedDirectory) : Array.Empty()); 103 | foreach (string subdirectory in subdirectoryEntries) { 104 | if (dbVersions.Add("IMPORTED_" + Path.GetFileName(subdirectory))) { 105 | AddEntry(Path.GetFileName(subdirectory), subdirectory, File.Exists(Path.Combine(subdirectory, GDK_CONFIG_FILENAME)) ? PackageType.GDK : PackageType.UWP); 106 | } 107 | } 108 | } 109 | 110 | public async Task LoadFromCacheUWP() { 111 | try { 112 | using (var reader = File.OpenText(_cacheFileUWP)) { 113 | var data = await reader.ReadToEndAsync(); 114 | ParseListUWP(JArray.Parse(data), true); 115 | } 116 | } catch (FileNotFoundException) { // ignore 117 | } 118 | } 119 | 120 | public async Task LoadFromCacheGDK() { 121 | try { 122 | using (var reader = File.OpenText(_cacheFileGDK)) { 123 | var data = await reader.ReadToEndAsync(); 124 | ParseDataGDK(JObject.Parse(data), true); 125 | } 126 | } catch (FileNotFoundException) { // ignore 127 | } 128 | } 129 | 130 | public async Task DownloadVersionsGDK() { 131 | var resp = await _client.GetAsync(VersionsApiGDK); 132 | resp.EnsureSuccessStatusCode(); 133 | var data = await resp.Content.ReadAsStringAsync(); 134 | File.WriteAllText(_cacheFileGDK, data); 135 | ParseDataGDK(JObject.Parse(data), false); 136 | 137 | } 138 | 139 | public async Task DownloadVersionsUWP() { 140 | var resp = await _client.GetAsync(VersionsApiUWP); 141 | resp.EnsureSuccessStatusCode(); 142 | var data = await resp.Content.ReadAsStringAsync(); 143 | File.WriteAllText(_cacheFileUWP, data); 144 | ParseListUWP(JArray.Parse(data), false); 145 | } 146 | 147 | public WPFDataTypes.Version AddEntry(string name, string path, PackageType packageType) { 148 | var result = new WPFDataTypes.Version(name.Replace(".appx", ""), path, _commands, packageType); 149 | Add(result); 150 | return result; 151 | } 152 | 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /MCLauncher/MCLauncher.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {9FEBCF6C-14EF-49E9-B57D-FCBC3BA4BC77} 8 | WinExe 9 | MCLauncher 10 | MCLauncher 11 | v4.8 12 | 512 13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 4 15 | true 16 | true 17 | publish\ 18 | true 19 | Disk 20 | false 21 | Foreground 22 | 7 23 | Days 24 | false 25 | false 26 | true 27 | 0 28 | 1.0.0.%2a 29 | false 30 | false 31 | true 32 | 33 | 34 | 35 | 36 | 37 | AnyCPU 38 | true 39 | full 40 | false 41 | $(SolutionDir)\$(Configuration)\ 42 | DEBUG;TRACE 43 | prompt 44 | 4 45 | 46 | 47 | AnyCPU 48 | pdbonly 49 | true 50 | $(SolutionDir)\$(Configuration)\ 51 | DEBUG;TRACE 52 | prompt 53 | 4 54 | 55 | 56 | app.manifest 57 | 58 | 59 | true 60 | $(SolutionDir)$(Platform)\$(Configuration)\ 61 | DEBUG;TRACE 62 | full 63 | x64 64 | prompt 65 | MinimumRecommendedRules.ruleset 66 | true 67 | 68 | 69 | $(SolutionDir)$(Platform)\$(Configuration)\ 70 | DEBUG;TRACE 71 | true 72 | pdbonly 73 | x64 74 | prompt 75 | MinimumRecommendedRules.ruleset 76 | true 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 4.0 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | MSBuild:Compile 101 | Designer 102 | 103 | 104 | 105 | 106 | ProgressDialog.xaml 107 | 108 | 109 | 110 | 111 | 112 | VersionListEndpointDialog.xaml 113 | 114 | 115 | 116 | 117 | MSBuild:Compile 118 | Designer 119 | 120 | 121 | App.xaml 122 | Code 123 | 124 | 125 | MainWindow.xaml 126 | Code 127 | 128 | 129 | Designer 130 | MSBuild:Compile 131 | 132 | 133 | Designer 134 | MSBuild:Compile 135 | 136 | 137 | Designer 138 | MSBuild:Compile 139 | 140 | 141 | 142 | 143 | Code 144 | 145 | 146 | True 147 | True 148 | Resources.resx 149 | 150 | 151 | True 152 | Settings.settings 153 | True 154 | 155 | 156 | ResXFileCodeGenerator 157 | Resources.Designer.cs 158 | 159 | 160 | Designer 161 | 162 | 163 | SettingsSingleFileGenerator 164 | Settings.Designer.cs 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | False 173 | Microsoft .NET Framework 4.6.1 %28x86 and x64%29 174 | true 175 | 176 | 177 | False 178 | .NET Framework 3.5 SP1 179 | false 180 | 181 | 182 | 183 | 184 | 10.0.17763.1000 185 | 186 | 187 | 13.0.3 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /MCLauncher/MainWindow.xaml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 18 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |