diff --git a/ZuneModCore/Mod.cs b/ZuneModCore/Mod.cs index 6526cdf..5e4b31c 100644 --- a/ZuneModCore/Mod.cs +++ b/ZuneModCore/Mod.cs @@ -6,85 +6,82 @@ using System.IO; using System.Threading.Tasks; using ZuneModCore.Mods; -namespace ZuneModCore +namespace ZuneModCore; + +public abstract class Mod { - public abstract class Mod + /// + /// A list of all available mods + /// + public static readonly IReadOnlyList AvailableModTypes = new List { - /// - /// A list of all available mods - /// - public static readonly IReadOnlyList AvailableModTypes = new List + typeof(FeaturesOverrideMod), + typeof(VideoSyncMod), + typeof(WebservicesMod), + typeof(BackgroundImageMod), + typeof(MbidLocatorMod), + }.AsReadOnly(); + + public static string DefaultZuneInstallDir { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Zune"); + + public string ZuneInstallDir { get; set; } = DefaultZuneInstallDir; + + private static List? _mods; + public static IReadOnlyList AvailableMods + { + get { - typeof(FeaturesOverrideMod), - typeof(VideoSyncMod), - typeof(WebservicesMod), - typeof(BackgroundImageMod), - typeof(MbidLocatorMod), - }.AsReadOnly(); - - public static string DefaultZuneInstallDir { get; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "Zune"); - - public string ZuneInstallDir { get; set; } = DefaultZuneInstallDir; - - private static List? _mods; - public static IReadOnlyList AvailableMods - { - get + if (_mods is null) { - if (_mods is null) + _mods = new(AvailableModTypes.Count); + var services = CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default; + foreach (var modType in AvailableModTypes) { - _mods = new(AvailableModTypes.Count); - var services = CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default; - foreach (var modType in AvailableModTypes) - { - var instance = ActivatorUtilities.CreateInstance(services, modType); - if (instance is Mod mod) - _mods.Add(mod); - } + var instance = ActivatorUtilities.CreateInstance(services, modType); + if (instance is Mod mod) + _mods.Add(mod); } - return _mods; } + return _mods; } - - public abstract string Id { get; } - - public abstract string Title { get; } - - public abstract string Description { get; } - - public abstract string Author { get; } - - public virtual AbstractUICollection? GetDefaultOptionsUI() => null; - - public virtual Task Init() => Task.CompletedTask; - - public abstract Task Apply(); - - public abstract Task Reset(); - - private AbstractUICollection? _OptionsUI; - public AbstractUICollection? OptionsUI - { - get - { - if (_OptionsUI == null) - _OptionsUI = GetDefaultOptionsUI(); - return _OptionsUI; - } - set => _OptionsUI = value; - } - - public string StorageDirectory - { - get - { - string dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ZuneModCore", Id); - // Create the directory just in case the consumer assumes the folder exists already - Directory.CreateDirectory(dir); - return dir; - } - } - - public abstract IReadOnlyList? DependentMods { get; } } + + public abstract string Id { get; } + + public abstract string Title { get; } + + public abstract string Description { get; } + + public abstract string Author { get; } + + public virtual AbstractUICollection? GetDefaultOptionsUI() => null; + + public virtual Task Init() => Task.CompletedTask; + + public abstract Task Apply(); + + public abstract Task Reset(); + + private AbstractUICollection? _OptionsUI; + public AbstractUICollection? OptionsUI + { + get + { + return _OptionsUI ??= GetDefaultOptionsUI(); + } + set => _OptionsUI = value; + } + + public string StorageDirectory + { + get + { + string dir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ZuneModCore", Id); + // Create the directory just in case the consumer assumes the folder exists already + Directory.CreateDirectory(dir); + return dir; + } + } + + public abstract IReadOnlyList? DependentMods { get; } } diff --git a/ZuneModCore/Mods/BackgroundImageMod.cs b/ZuneModCore/Mods/BackgroundImageMod.cs index 3365b64..804c384 100644 --- a/ZuneModCore/Mods/BackgroundImageMod.cs +++ b/ZuneModCore/Mods/BackgroundImageMod.cs @@ -7,126 +7,125 @@ using System.Threading.Tasks; using Vestris.ResourceLib; using ZuneModCore.Win32; -namespace ZuneModCore.Mods +namespace ZuneModCore.Mods; + +public class BackgroundImageMod : Mod { - public class BackgroundImageMod : Mod + public override string Id => nameof(BackgroundImageMod); + + public override string Title => "Background Image"; + + public override string Description => "Replaces the \"Zero\" background with an image of your choice."; + + public override string Author => "Joshua \"Yoshi\" Askharoun"; + + public override AbstractUICollection? GetDefaultOptionsUI() { - public override string Id => nameof(BackgroundImageMod); - - public override string Title => "Background Image"; - - public override string Description => "Replaces the \"Zero\" background with an image of your choice."; - - public override string Author => "Joshua \"Yoshi\" Askharoun"; - - public override AbstractUICollection? GetDefaultOptionsUI() + AbstractUICollection optionsUi = new(nameof(BackgroundImageMod)) { - AbstractUICollection optionsUi = new(nameof(BackgroundImageMod)) - { - new AbstractTextBox("fileBox", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)), - }; - optionsUi.Title = "select background"; - optionsUi.Subtitle = "CHOOSE A BACKGROUND IMAGE OF YOUR CHOICE."; - return optionsUi; + new AbstractTextBox("fileBox", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)), + }; + optionsUi.Title = "select background"; + optionsUi.Subtitle = "CHOOSE A BACKGROUND IMAGE OF YOUR CHOICE."; + return optionsUi; + } + + public override IReadOnlyList? DependentMods => null; + + public override async Task Apply() + { + string bgimgPath = ((AbstractTextBox)OptionsUI![0]).Value; + FileInfo bgimgInfo = new(bgimgPath); + if (!bgimgInfo.Exists) + { + return $"The file '{bgimgInfo.FullName}' does not exist."; } - public override IReadOnlyList? DependentMods => null; - - public override async Task Apply() + FileInfo zsrDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneShellResources.dll")); + if (!zsrDllInfo.Exists) { - string bgimgPath = ((AbstractTextBox)OptionsUI![0]).Value; - FileInfo bgimgInfo = new(bgimgPath); - if (!bgimgInfo.Exists) + return $"The file '{zsrDllInfo.FullName}' does not exist."; + } + + // Make a backup if it doesn't already exist + FileInfo zsrDllBackupInfo = new(Path.Combine(StorageDirectory, "ZuneShellResources.original.dll")); + if (!zsrDllBackupInfo.Exists) + { + File.Copy(zsrDllInfo.FullName, zsrDllBackupInfo.FullName, true); + } + + string zsrDllTempPath = Path.Combine(StorageDirectory, zsrDllInfo.Name); + + // Take ownership of DLL + TokenManipulator.TakeOwnership(zsrDllInfo); + + try + { + using (ResourceInfo vi = new()) { - return $"The file '{bgimgInfo.FullName}' does not exist."; - } - - FileInfo zsrDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneShellResources.dll")); - if (!zsrDllInfo.Exists) - { - return $"The file '{zsrDllInfo.FullName}' does not exist."; - } - - // Make a backup if it doesn't already exist - FileInfo zsrDllBackupInfo = new(Path.Combine(StorageDirectory, "ZuneShellResources.original.dll")); - if (!zsrDllBackupInfo.Exists) - { - File.Copy(zsrDllInfo.FullName, zsrDllBackupInfo.FullName, true); - } - - string zsrDllTempPath = Path.Combine(StorageDirectory, zsrDllInfo.Name); - - // Take ownership of DLL - TokenManipulator.TakeOwnership(zsrDllInfo); - - try - { - using (ResourceInfo vi = new()) + // Load the RCDATA section + vi.Load(zsrDllInfo.FullName); + ResourceId? resId = vi.ResourceTypes.Find(id => { - // Load the RCDATA section - vi.Load(zsrDllInfo.FullName); - ResourceId? resId = vi.ResourceTypes.Find(id => + // This must be wrapped in a try-catch block, + // otherwise resourcelib will error when attempting + // to read the WAVE type (or any other non-default type). + try { - // This must be wrapped in a try-catch block, - // otherwise resourcelib will error when attempting - // to read the WAVE type (or any other non-default type). - try - { - return id.ResourceType == Kernel32.ResourceTypes.RT_RCDATA; - } - catch - { - return false; - } - }); - if (resId == null) - return $"Failed to locate image resources in Zune software."; + return id.ResourceType == Kernel32.ResourceTypes.RT_RCDATA; + } + catch + { + return false; + } + }); + if (resId == null) + return $"Failed to locate image resources in Zune software."; - // Locate the image resource - // TODO: Allow users to specify which background they want to replace - List RCDATA = vi.Resources[resId]; - int idx = RCDATA.FindIndex(r => r.Name.Name == "FRAMEBACKGROUND06.PNG"); - if (idx < 0) - return $"Failed to locate background image resource in Zune software."; - GenericResource ogRes = (GenericResource)RCDATA[idx]; + // Locate the image resource + // TODO: Allow users to specify which background they want to replace + List RCDATA = vi.Resources[resId]; + int idx = RCDATA.FindIndex(r => r.Name.Name == "FRAMEBACKGROUND06.PNG"); + if (idx < 0) + return $"Failed to locate background image resource in Zune software."; + GenericResource ogRes = (GenericResource)RCDATA[idx]; - // Replace image - byte[] data = await File.ReadAllBytesAsync(bgimgInfo.FullName); - BitmapResource newRes = new(IntPtr.Zero, IntPtr.Zero, resId, ogRes.Name, ogRes.Language, data.Length); - newRes.Bitmap = new(data); - RCDATA[idx] = newRes; - File.Copy(zsrDllInfo.FullName, zsrDllTempPath, true); - newRes.SaveTo(zsrDllTempPath); - } - File.Copy(zsrDllTempPath, zsrDllInfo.FullName, true); + // Replace image + byte[] data = await File.ReadAllBytesAsync(bgimgInfo.FullName); + BitmapResource newRes = new(IntPtr.Zero, IntPtr.Zero, resId, ogRes.Name, ogRes.Language, data.Length); + newRes.Bitmap = new(data); + RCDATA[idx] = newRes; + File.Copy(zsrDllInfo.FullName, zsrDllTempPath, true); + newRes.SaveTo(zsrDllTempPath); + } + File.Copy(zsrDllTempPath, zsrDllInfo.FullName, true); - return null; - } - catch (IOException) - { - return $"Unable to replace '{zsrDllInfo.FullName}'. Verify that the Zune software is not running and try again."; - } - catch (Exception ex) - { - Debug.WriteLine(ex.ToString()); - return ex.Message; - } + return null; } - - public override Task Reset() + catch (IOException) { - try - { - // Copy backup to application folder - File.Copy(Path.Combine(StorageDirectory, "ZuneShellResources.original.dll"), - Path.Combine(ZuneInstallDir, "ZuneShellResources.dll"), true); + return $"Unable to replace '{zsrDllInfo.FullName}'. Verify that the Zune software is not running and try again."; + } + catch (Exception ex) + { + Debug.WriteLine(ex.ToString()); + return ex.Message; + } + } - return Task.FromResult(null); - } - catch (Exception ex) - { - return Task.FromResult(ex.Message)!; - } + public override Task Reset() + { + try + { + // Copy backup to application folder + File.Copy(Path.Combine(StorageDirectory, "ZuneShellResources.original.dll"), + Path.Combine(ZuneInstallDir, "ZuneShellResources.dll"), true); + + return Task.FromResult(null); + } + catch (Exception ex) + { + return Task.FromResult(ex.Message)!; } } } diff --git a/ZuneModCore/Mods/FeaturesOverrideMod.cs b/ZuneModCore/Mods/FeaturesOverrideMod.cs index f7142cb..224590d 100644 --- a/ZuneModCore/Mods/FeaturesOverrideMod.cs +++ b/ZuneModCore/Mods/FeaturesOverrideMod.cs @@ -4,116 +4,115 @@ using System.Collections.Generic; using System.Threading.Tasks; using ZuneModCore.Win32; -namespace ZuneModCore.Mods +namespace ZuneModCore.Mods; + +public class FeaturesOverrideMod : Mod { - public class FeaturesOverrideMod : Mod + private const string ZUNE_FEATURESOVERRIDE_REGKEY = RegEdit.ZUNE_REG_PATH + "FeaturesOverride"; + + public override string Id => nameof(FeaturesOverrideMod); + + public override string Title => "Features Override"; + + public override string Description => "Re-enables access to some features disabled by Microsoft, such as the Social and Marketplace tabs.\r\n" + + "Does not restore functionality of those features, but shows them in the software."; + + public override string Author => "Rafael Rivera"; + + public override AbstractUICollection? GetDefaultOptionsUI() { - private const string ZUNE_FEATURESOVERRIDE_REGKEY = RegEdit.ZUNE_REG_PATH + "FeaturesOverride"; - - public override string Id => nameof(FeaturesOverrideMod); - - public override string Title => "Features Override"; - - public override string Description => "Re-enables access to some features disabled by Microsoft, such as the Social and Marketplace tabs.\r\n" + - "Does not restore functionality of those features, but shows them in the software."; - - public override string Author => "Rafael Rivera"; - - public override AbstractUICollection? GetDefaultOptionsUI() + AbstractUICollection optionsUi = new(nameof(FeaturesOverrideMod)) { - AbstractUICollection optionsUi = new(nameof(FeaturesOverrideMod)) - { - // We don't know what some of these overrides do exactly, so hide them from the user. - // The ID is the name of the registry key, the label is the display name + // We don't know what some of these overrides do exactly, so hide them from the user. + // The ID is the name of the registry key, the label is the display name - new AbstractBoolean("Apps", "Apps"), - new AbstractBoolean("Art", "Art"), - new AbstractBoolean("Channels", "Channels"), - new AbstractBoolean("FirstLaunchIntroVideo", "First Launch Intro Video"), - new AbstractBoolean("Games", "Games"), - new AbstractBoolean("Marketplace", "Marketplace"), - new AbstractBoolean("MBRPreview", "[Marketplace] Media Preview"), - new AbstractBoolean("MBRPurchase", "[Marketplace] Media Purchase"), - new AbstractBoolean("MBRRental", "[Marketplace] Media Rental"), - new AbstractBoolean("Music", "Music"), - new AbstractBoolean("MusicVideos", "Music Videos"), - new AbstractBoolean("Nowplaying", "Now Playing"), - new AbstractBoolean("NowplayingArt", "Now Playing Art"), - new AbstractBoolean("Picks", "Picks"), - new AbstractBoolean("Podcasts", "Podcasts"), - new AbstractBoolean("QuickMixLocal", "Quick Mix (Local)"), - new AbstractBoolean("QuickMixZMP", "Quick Mix (ZMP)"), - new AbstractBoolean("Quickplay", "Quickplay"), - new AbstractBoolean("Sign In Available", "Sign In"), - new AbstractBoolean("Social", "Social"), - new AbstractBoolean("SocialMarketplace", "Social Marketplace"), - new AbstractBoolean("SubscriptionFreeTracks", "Subscription Free Tracks"), - new AbstractBoolean("Videos", "Videos"), - }; - optionsUi.Title = "select features"; - optionsUi.Subtitle = "CHOOSE WHICH ZUNE FEATURES YOU WISH TO ENABLE."; - return optionsUi; - } + new AbstractBoolean("Apps", "Apps"), + new AbstractBoolean("Art", "Art"), + new AbstractBoolean("Channels", "Channels"), + new AbstractBoolean("FirstLaunchIntroVideo", "First Launch Intro Video"), + new AbstractBoolean("Games", "Games"), + new AbstractBoolean("Marketplace", "Marketplace"), + new AbstractBoolean("MBRPreview", "[Marketplace] Media Preview"), + new AbstractBoolean("MBRPurchase", "[Marketplace] Media Purchase"), + new AbstractBoolean("MBRRental", "[Marketplace] Media Rental"), + new AbstractBoolean("Music", "Music"), + new AbstractBoolean("MusicVideos", "Music Videos"), + new AbstractBoolean("Nowplaying", "Now Playing"), + new AbstractBoolean("NowplayingArt", "Now Playing Art"), + new AbstractBoolean("Picks", "Picks"), + new AbstractBoolean("Podcasts", "Podcasts"), + new AbstractBoolean("QuickMixLocal", "Quick Mix (Local)"), + new AbstractBoolean("QuickMixZMP", "Quick Mix (ZMP)"), + new AbstractBoolean("Quickplay", "Quickplay"), + new AbstractBoolean("Sign In Available", "Sign In"), + new AbstractBoolean("Social", "Social"), + new AbstractBoolean("SocialMarketplace", "Social Marketplace"), + new AbstractBoolean("SubscriptionFreeTracks", "Subscription Free Tracks"), + new AbstractBoolean("Videos", "Videos"), + }; + optionsUi.Title = "select features"; + optionsUi.Subtitle = "CHOOSE WHICH ZUNE FEATURES YOU WISH TO ENABLE."; + return optionsUi; + } - public override IReadOnlyList? DependentMods => null; + public override IReadOnlyList? DependentMods => null; - public override Task Init() + public override Task Init() + { + foreach (AbstractUIElement uiElem in OptionsUI!) { - foreach (AbstractUIElement uiElem in OptionsUI!) + if (uiElem is AbstractBoolean boolElem) { - if (uiElem is AbstractBoolean boolElem) - { - bool? featureOverride = GetFeatureOverride(boolElem.Id); - boolElem.State = featureOverride ?? false; - } + bool? featureOverride = GetFeatureOverride(boolElem.Id); + boolElem.State = featureOverride ?? false; } - - return Task.CompletedTask; } - public override async Task Apply() => await Apply(false); + return Task.CompletedTask; + } - public async Task Apply(bool applyAll = false) + public override async Task Apply() => await Apply(false); + + public async Task Apply(bool applyAll = false) + { + // Use user choices from AbstractUI + foreach (AbstractUIElement uiElem in OptionsUI!) { - // Use user choices from AbstractUI - foreach (AbstractUIElement uiElem in OptionsUI!) + if (uiElem is AbstractBoolean boolElem && (boolElem.State || applyAll)) { - if (uiElem is AbstractBoolean boolElem && (boolElem.State || applyAll)) + bool isSuccess = SetFeatureOverride(boolElem.Id, true); + if (!isSuccess) { - bool isSuccess = SetFeatureOverride(boolElem.Id, true); - if (!isSuccess) + string? resetStatus = await Reset(); + if (resetStatus != null) { - string? resetStatus = await Reset(); - if (resetStatus != null) - { - // The reset failed as well, return both errors - return "Failed to set registry keys. Unable to clean up partial overrides:\r\n" + resetStatus; - } - else - { - return "Failed to set registry keys. Automatically cleaned up partial changes."; - } + // The reset failed as well, return both errors + return "Failed to set registry keys. Unable to clean up partial overrides:\r\n" + resetStatus; + } + else + { + return "Failed to set registry keys. Automatically cleaned up partial changes."; } } } - - return null; } - public override async Task Reset() - { - RegEdit.CurrentUserDeleteKey(ZUNE_FEATURESOVERRIDE_REGKEY); - - return null; - } - - public static bool SetFeatureOverride(string feature, bool value) => - RegEdit.CurrentUserSetBoolValue(ZUNE_FEATURESOVERRIDE_REGKEY, feature, value); - - public static bool? GetFeatureOverride(string feature) => - RegEdit.CurrentUserGetBoolValue(ZUNE_FEATURESOVERRIDE_REGKEY, feature); - - public static void ResetFeatureOverride(string feature) => - RegEdit.CurrentUserDeleteValue(ZUNE_FEATURESOVERRIDE_REGKEY, feature); + return null; } + + public override async Task Reset() + { + RegEdit.CurrentUserDeleteKey(ZUNE_FEATURESOVERRIDE_REGKEY); + + return null; + } + + public static bool SetFeatureOverride(string feature, bool value) => + RegEdit.CurrentUserSetBoolValue(ZUNE_FEATURESOVERRIDE_REGKEY, feature, value); + + public static bool? GetFeatureOverride(string feature) => + RegEdit.CurrentUserGetBoolValue(ZUNE_FEATURESOVERRIDE_REGKEY, feature); + + public static void ResetFeatureOverride(string feature) => + RegEdit.CurrentUserDeleteValue(ZUNE_FEATURESOVERRIDE_REGKEY, feature); } diff --git a/ZuneModCore/Mods/MbidLocatorMod.cs b/ZuneModCore/Mods/MbidLocatorMod.cs index 514f23a..02db4df 100644 --- a/ZuneModCore/Mods/MbidLocatorMod.cs +++ b/ZuneModCore/Mods/MbidLocatorMod.cs @@ -5,167 +5,163 @@ using System.IO; using System.Linq; using System.Threading.Tasks; -namespace ZuneModCore.Mods +namespace ZuneModCore.Mods; + +public class MbidLocatorMod : Mod { - public class MbidLocatorMod : Mod + public const string ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME = "ZuneAlbumArtistMediaID"; + public const string ZUNE_ALBUM_MEDIA_ID_NAME = "ZuneAlbumMediaID"; + public const string ZUNE_MEDIA_ID_NAME = "ZuneMediaID"; + public const string ZUNE_COLLECTION_ID_NAME = "ZuneCollectionID"; + + private static readonly string[] KNOWN_EXTS = [".mp3", ".mp4", ".m4a", ".wav"]; + + public override string Id => nameof(MbidLocatorMod); + + public override string Title => "MusicBrainz ID Locator"; + + public override string Description => "Puts MusicBrainz IDs added by MusicBrainz Picard where the Zune " + + "software can use it to show additional information provided by Community Webservices.\r\n" + + "Note that this will only have an effect if you have used MusicBrainz Picard on your music library."; + + public override string Author => "Joshua \"Yoshi\" Askharoun"; + + public override AbstractUICollection? GetDefaultOptionsUI() { - public const string ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME = "ZuneAlbumArtistMediaID"; - public const string ZUNE_ALBUM_MEDIA_ID_NAME = "ZuneAlbumMediaID"; - public const string ZUNE_MEDIA_ID_NAME = "ZuneMediaID"; - public const string ZUNE_COLLECTION_ID_NAME = "ZuneCollectionID"; - - private static readonly string[] KNOWN_EXTS = new[] + AbstractUICollection optionsUi = new(nameof(MbidLocatorMod)) { - ".mp3", ".mp4", ".m4a", ".wav" + new AbstractTextBox("folderBox", Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)), + new AbstractBoolean("recursiveBox", "Search recursively") }; + optionsUi.Title = "select music"; + optionsUi.Subtitle = "CHOOSE YOUR MUSIC FOLDER."; + return optionsUi; + } - public override string Id => nameof(MbidLocatorMod); + public override IReadOnlyList? DependentMods => null; - public override string Title => "MusicBrainz ID Locator"; + public override async Task Apply() + { + // Use user choices from AbstractUI + string folderPath = ((AbstractTextBox)OptionsUI![0]).Value; + bool recursive = ((AbstractBoolean)OptionsUI[1]).State; + string errorString = string.Empty; - public override string Description => "Puts MusicBrainz IDs added by MusicBrainz Picard where the Zune " + - "software can use it to show additional information provided by Community Webservices.\r\n" + - "Note that this will only have an effect if you have used MusicBrainz Picard on your music library."; - - public override string Author => "Joshua \"Yoshi\" Askharoun"; - - public override AbstractUICollection? GetDefaultOptionsUI() + // Verify that the folder exists + DirectoryInfo folder = new(folderPath); + if (!folder.Exists) { - AbstractUICollection optionsUi = new(nameof(MbidLocatorMod)) - { - new AbstractTextBox("folderBox", Environment.GetFolderPath(Environment.SpecialFolder.MyMusic)), - new AbstractBoolean("recursiveBox", "Search recursively") - }; - optionsUi.Title = "select music"; - optionsUi.Subtitle = "CHOOSE YOUR MUSIC FOLDER."; - return optionsUi; + return $"The folder '{folder.FullName}' does not exist."; } - public override IReadOnlyList? DependentMods => null; - - public override async Task Apply() + foreach (FileInfo file in folder.GetFiles("*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) { - // Use user choices from AbstractUI - string folderPath = ((AbstractTextBox)OptionsUI![0]).Value; - bool recursive = ((AbstractBoolean)OptionsUI[1]).State; - string errorString = string.Empty; - - // Verify that the folder exists - DirectoryInfo folder = new(folderPath); - if (!folder.Exists) + try { - return $"The folder '{folder.FullName}' does not exist."; - } - - foreach (FileInfo file in folder.GetFiles("*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)) - { - try - { - if (KNOWN_EXTS.Contains(file.Extension)) - UpdateMbidInFile(file); + if (KNOWN_EXTS.Contains(file.Extension)) + UpdateMbidInFile(file); #if DEBUG - System.Diagnostics.Debug.WriteLine(file); + System.Diagnostics.Debug.WriteLine(file); #endif - } - catch (Exception ex) - { - errorString += ex.Message + "\r\n"; - continue; - } } - - if (errorString.Length != 0) - return errorString; - else - return null; - } - - public override Task Reset() - { - return Task.FromResult(null); - } - - public static void UpdateMbidInFile(FileInfo file) - { - var tfile = TagLib.File.Create(file.FullName); - - if (tfile.Tag is TagLib.Asf.Tag asfTag) + catch (Exception ex) { - var albumArtistIdDesc = asfTag.ExtendedContentDescriptionObject - .FirstOrDefault(cd => cd.Name == ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME); - if (albumArtistIdDesc == null && asfTag.MusicBrainzReleaseArtistId != null) - { - asfTag.ExtendedContentDescriptionObject.AddDescriptor(new TagLib.Asf.ContentDescriptor( - ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME, asfTag.MusicBrainzReleaseArtistId)); - } - - var albumIdDesc = asfTag.ExtendedContentDescriptionObject - .FirstOrDefault(cd => cd.Name == ZUNE_ALBUM_MEDIA_ID_NAME); - if (albumIdDesc == null && asfTag.MusicBrainzReleaseId != null) - { - asfTag.ExtendedContentDescriptionObject.AddDescriptor(new TagLib.Asf.ContentDescriptor( - ZUNE_ALBUM_MEDIA_ID_NAME, asfTag.MusicBrainzReleaseId)); - } - - var trackIdDesc = asfTag.ExtendedContentDescriptionObject - .FirstOrDefault(cd => cd.Name == ZUNE_MEDIA_ID_NAME); - if (trackIdDesc == null && asfTag.MusicBrainzTrackId != null) - { - asfTag.ExtendedContentDescriptionObject.AddDescriptor(new TagLib.Asf.ContentDescriptor( - ZUNE_MEDIA_ID_NAME, asfTag.MusicBrainzTrackId)); - } + errorString += ex.Message + "\r\n"; + continue; } - else if (tfile.Tag is TagLib.NonContainer.Tag ncTag) - { - var tag = ncTag.Tags.FirstOrDefault(t => t.TagTypes == TagLib.TagTypes.Id3v2); - if (tag is TagLib.Id3v2.Tag id3v2) - { - var frames = id3v2.GetFrames(); - - // Note the use of .ToList(): this is because calling RemoveFrame() will - // modify the underlying collection, causing an exception to be thrown - // when attempting the next iteration of the for loop - - if (tag.MusicBrainzReleaseArtistId != null) - { - foreach (var oldAlbumArtistFrame in frames.Where(f => f.Owner == ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME).ToList()) - id3v2.RemoveFrame(oldAlbumArtistFrame); - - TagLib.Id3v2.PrivateFrame albumArtistFrame = new(ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME) - { - PrivateData = new(new Guid(tag.MusicBrainzReleaseArtistId).ToByteArray()) - }; - id3v2.AddFrame(albumArtistFrame); - } - - if (tag.MusicBrainzReleaseId != null) - { - foreach (var oldAlbumFrame in frames.Where(f => f.Owner == ZUNE_ALBUM_MEDIA_ID_NAME).ToList()) - id3v2.RemoveFrame(oldAlbumFrame); - - TagLib.Id3v2.PrivateFrame albumFrame = new(ZUNE_ALBUM_MEDIA_ID_NAME) - { - PrivateData = new(new Guid(tag.MusicBrainzReleaseId).ToByteArray()) - }; - id3v2.AddFrame(albumFrame); - } - - if (tag.MusicBrainzTrackId != null) - { - foreach (var oldTrackFrame in frames.Where(f => f.Owner == ZUNE_MEDIA_ID_NAME).ToList()) - id3v2.RemoveFrame(oldTrackFrame); - - TagLib.Id3v2.PrivateFrame trackFrame = new(ZUNE_MEDIA_ID_NAME) - { - PrivateData = new(new Guid(tag.MusicBrainzTrackId).ToByteArray()) - }; - id3v2.AddFrame(trackFrame); - } - } - } - - tfile.Save(); } + + if (errorString.Length != 0) + return errorString; + else + return null; + } + + public override Task Reset() + { + return Task.FromResult(null); + } + + public static void UpdateMbidInFile(FileInfo file) + { + var tfile = TagLib.File.Create(file.FullName); + + if (tfile.Tag is TagLib.Asf.Tag asfTag) + { + var albumArtistIdDesc = asfTag.ExtendedContentDescriptionObject + .FirstOrDefault(cd => cd.Name == ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME); + if (albumArtistIdDesc == null && asfTag.MusicBrainzReleaseArtistId != null) + { + asfTag.ExtendedContentDescriptionObject.AddDescriptor(new TagLib.Asf.ContentDescriptor( + ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME, asfTag.MusicBrainzReleaseArtistId)); + } + + var albumIdDesc = asfTag.ExtendedContentDescriptionObject + .FirstOrDefault(cd => cd.Name == ZUNE_ALBUM_MEDIA_ID_NAME); + if (albumIdDesc == null && asfTag.MusicBrainzReleaseId != null) + { + asfTag.ExtendedContentDescriptionObject.AddDescriptor(new TagLib.Asf.ContentDescriptor( + ZUNE_ALBUM_MEDIA_ID_NAME, asfTag.MusicBrainzReleaseId)); + } + + var trackIdDesc = asfTag.ExtendedContentDescriptionObject + .FirstOrDefault(cd => cd.Name == ZUNE_MEDIA_ID_NAME); + if (trackIdDesc == null && asfTag.MusicBrainzTrackId != null) + { + asfTag.ExtendedContentDescriptionObject.AddDescriptor(new TagLib.Asf.ContentDescriptor( + ZUNE_MEDIA_ID_NAME, asfTag.MusicBrainzTrackId)); + } + } + else if (tfile.Tag is TagLib.NonContainer.Tag ncTag) + { + var tag = ncTag.Tags.FirstOrDefault(t => t.TagTypes == TagLib.TagTypes.Id3v2); + if (tag is TagLib.Id3v2.Tag id3v2) + { + var frames = id3v2.GetFrames(); + + // Note the use of .ToList(): this is because calling RemoveFrame() will + // modify the underlying collection, causing an exception to be thrown + // when attempting the next iteration of the for loop + + if (tag.MusicBrainzReleaseArtistId != null) + { + foreach (var oldAlbumArtistFrame in frames.Where(f => f.Owner == ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME).ToList()) + id3v2.RemoveFrame(oldAlbumArtistFrame); + + TagLib.Id3v2.PrivateFrame albumArtistFrame = new(ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME) + { + PrivateData = new(new Guid(tag.MusicBrainzReleaseArtistId).ToByteArray()) + }; + id3v2.AddFrame(albumArtistFrame); + } + + if (tag.MusicBrainzReleaseId != null) + { + foreach (var oldAlbumFrame in frames.Where(f => f.Owner == ZUNE_ALBUM_MEDIA_ID_NAME).ToList()) + id3v2.RemoveFrame(oldAlbumFrame); + + TagLib.Id3v2.PrivateFrame albumFrame = new(ZUNE_ALBUM_MEDIA_ID_NAME) + { + PrivateData = new(new Guid(tag.MusicBrainzReleaseId).ToByteArray()) + }; + id3v2.AddFrame(albumFrame); + } + + if (tag.MusicBrainzTrackId != null) + { + foreach (var oldTrackFrame in frames.Where(f => f.Owner == ZUNE_MEDIA_ID_NAME).ToList()) + id3v2.RemoveFrame(oldTrackFrame); + + TagLib.Id3v2.PrivateFrame trackFrame = new(ZUNE_MEDIA_ID_NAME) + { + PrivateData = new(new Guid(tag.MusicBrainzTrackId).ToByteArray()) + }; + id3v2.AddFrame(trackFrame); + } + } + } + + tfile.Save(); } } diff --git a/ZuneModCore/Mods/VideoSyncMod.cs b/ZuneModCore/Mods/VideoSyncMod.cs index e0a1292..d302577 100644 --- a/ZuneModCore/Mods/VideoSyncMod.cs +++ b/ZuneModCore/Mods/VideoSyncMod.cs @@ -4,92 +4,91 @@ using System.Diagnostics; using System.IO; using System.Threading.Tasks; -namespace ZuneModCore.Mods +namespace ZuneModCore.Mods; + +public class VideoSyncMod : Mod { - public class VideoSyncMod : Mod + private const int ZUNEENCENG_WMVCORA_OFFSET = 0x161E; + + public override string Title => "Fix Video Sync"; + + public override string Description => + "Resolves \"Error C00D11CD\" when attempting to sync video to a Zune device using Windows 10 1607 (Anniversary Update) or newer"; + + public override string Author => "sylvathemoth"; + + public override string Id => nameof(VideoSyncMod); + + public override async Task Apply() { - private const int ZUNEENCENG_WMVCORA_OFFSET = 0x161E; + // Verify that ZuneEncEng.dll exists + FileInfo zeeDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneEncEng.dll")); + if (!zeeDllInfo.Exists) + return $"The file '{zeeDllInfo.FullName}' does not exist."; - public override string Title => "Fix Video Sync"; + // Make a backup if it doesn't already exist + FileInfo zeeDllBackupInfo = new(Path.Combine(StorageDirectory, "ZuneEncEng.original.dll")); + if (!zeeDllBackupInfo.Exists) + File.Copy(zeeDllInfo.FullName, zeeDllBackupInfo.FullName, true); - public override string Description => - "Resolves \"Error C00D11CD\" when attempting to sync video to a Zune device using Windows 10 1607 (Anniversary Update) or newer"; - - public override string Author => "sylvathemoth"; - - public override string Id => nameof(VideoSyncMod); - - public override async Task Apply() + try { - // Verify that ZuneEncEng.dll exists - FileInfo zeeDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneEncEng.dll")); - if (!zeeDllInfo.Exists) - return $"The file '{zeeDllInfo.FullName}' does not exist."; + // Verify that the DLL is from v4.8 (other versions not tested) + var zeeDllVersion = FileVersionInfo.GetVersionInfo(zeeDllInfo.FullName); + if (zeeDllVersion is null || zeeDllVersion.FileMajorPart != 4 || zeeDllVersion.FileMinorPart != 8) + return "This mod has not been tested on versions earlier than 4.8."; - // Make a backup if it doesn't already exist - FileInfo zeeDllBackupInfo = new(Path.Combine(StorageDirectory, "ZuneEncEng.original.dll")); - if (!zeeDllBackupInfo.Exists) - File.Copy(zeeDllInfo.FullName, zeeDllBackupInfo.FullName, true); - - try + // Open the file + using (FileStream zeeDll = zeeDllInfo.Open(FileMode.Open)) + using (BinaryWriter zeeDllWriter = new(zeeDll)) { - // Verify that the DLL is from v4.8 (other versions not tested) - var zeeDllVersion = FileVersionInfo.GetVersionInfo(zeeDllInfo.FullName); - if (zeeDllVersion is null || zeeDllVersion.FileMajorPart != 4 || zeeDllVersion.FileMinorPart != 8) - return "This mod has not been tested on versions earlier than 4.8."; + // Patch ZuneEncEng.dll to point to the local WMVCORE.DLL + zeeDllWriter.Seek(ZUNEENCENG_WMVCORA_OFFSET, SeekOrigin.Begin); + zeeDllWriter.Write((byte)'a'); - // Open the file - using (FileStream zeeDll = zeeDllInfo.Open(FileMode.Open)) - using (BinaryWriter zeeDllWriter = new(zeeDll)) - { - // Patch ZuneEncEng.dll to point to the local WMVCORE.DLL - zeeDllWriter.Seek(ZUNEENCENG_WMVCORA_OFFSET, SeekOrigin.Begin); - zeeDllWriter.Write((byte)'a'); - - zeeDllWriter.Flush(); - } - - // Get the working WMVCORE.dll - byte[] wmvDllAniv = ModResources.WMVCORE; - - // Copy the WMVCore files to the Zune directory. - // Only ZuneEncEng.dll searches in System32 first; all other dependent - // DLLs will search the Zune folder. - await File.WriteAllBytesAsync(Path.Combine(ZuneInstallDir, "wmvcora.dll"), wmvDllAniv); - await File.WriteAllBytesAsync(Path.Combine(ZuneInstallDir, "wmvcore.dll"), wmvDllAniv); - - return null; - } - catch (IOException) - { - return $"Unable to replace '{zeeDllInfo.FullName}'. Verify that the Zune software is not running and try again."; - } - catch (Exception ex) - { - return ex.Message; + zeeDllWriter.Flush(); } + + // Get the working WMVCORE.dll + byte[] wmvDllAniv = ModResources.WMVCORE; + + // Copy the WMVCore files to the Zune directory. + // Only ZuneEncEng.dll searches in System32 first; all other dependent + // DLLs will search the Zune folder. + await File.WriteAllBytesAsync(Path.Combine(ZuneInstallDir, "wmvcora.dll"), wmvDllAniv); + await File.WriteAllBytesAsync(Path.Combine(ZuneInstallDir, "wmvcore.dll"), wmvDllAniv); + + return null; } - - public override Task Reset() + catch (IOException) { - var zeeDllPath = Path.Combine(ZuneInstallDir, "ZuneEncEng.dll"); - try - { - // Copy backup to application folder - File.Copy(Path.Combine(StorageDirectory, "ZuneEncEng.original.dll"), zeeDllPath, true); - - // Delete WMVCore files - File.Delete(Path.Combine(ZuneInstallDir, "wmvcora.dll")); - File.Delete(Path.Combine(ZuneInstallDir, "wmvcore.dll")); - - return Task.FromResult(null); - } - catch (Exception ex) - { - return Task.FromResult(ex.Message)!; - } + return $"Unable to replace '{zeeDllInfo.FullName}'. Verify that the Zune software is not running and try again."; + } + catch (Exception ex) + { + return ex.Message; } - - public override IReadOnlyList? DependentMods => null; } + + public override Task Reset() + { + var zeeDllPath = Path.Combine(ZuneInstallDir, "ZuneEncEng.dll"); + try + { + // Copy backup to application folder + File.Copy(Path.Combine(StorageDirectory, "ZuneEncEng.original.dll"), zeeDllPath, true); + + // Delete WMVCore files + File.Delete(Path.Combine(ZuneInstallDir, "wmvcora.dll")); + File.Delete(Path.Combine(ZuneInstallDir, "wmvcore.dll")); + + return Task.FromResult(null); + } + catch (Exception ex) + { + return Task.FromResult(ex.Message)!; + } + } + + public override IReadOnlyList? DependentMods => null; } diff --git a/ZuneModCore/Mods/WebservicesMod.cs b/ZuneModCore/Mods/WebservicesMod.cs index a9ef11c..5a3c9fe 100644 --- a/ZuneModCore/Mods/WebservicesMod.cs +++ b/ZuneModCore/Mods/WebservicesMod.cs @@ -7,288 +7,266 @@ using System.Threading; using System.Threading.Tasks; using static ZuneModCore.Mods.FeaturesOverrideMod; -namespace ZuneModCore.Mods +namespace ZuneModCore.Mods; + +public class WebservicesMod : Mod { - public class WebservicesMod : Mod + private const int ZUNESERVICES_ENDPOINTS_BLOCK_OFFSET = 0x14D60; + private const int ZUNESERVICES_ENDPOINTS_BLOCK_LENGTH = 0x884; + private const string WEBSERVICE_ICON_TESTING = "\uE72C"; + private const string WEBSERVICE_ICON_SUCCESS = "\uE73E"; + private const string WEBSERVICE_ICON_UNREACHABLE = "\uF384"; + private const string WEBSERVICE_ICON_UNAVAILABLE = "\uE894"; + private const string WEBSERVICE_SUBTITLE_TESTING = "Testing..."; + private const string WEBSERVICE_SUBTITLE_SUCCESS = "OK"; + private const string WEBSERVICE_SUBTITLE_UNREACHABLE = "Unreachable"; + + private static readonly string[] _affectedFeatures = + [ + "Apps", "Channels", "Games", "Marketplace", + "MBRPreview", "MBRPurchase", "MBRRental", + "Music", "MusicVideos", "Picks", "Podcasts", + "Sign In Available", "Social", "Videos", + "SocialMarketplace", "SubscriptionFreeTracks", + ]; + + private HttpClient _client; + private CancellationTokenSource _cts = new(); + + public override string Id => nameof(WebservicesMod); + + public override string Title => "Community Webservices"; + + public override string Description => "Partially restores online features such as the Marketplace by patching the Zune desktop software " + + "to use the community's recreation of Microsoft's Zune servers at zunes.me (instead of zune.net)."; + + public override string Author => "Joshua \"Yoshi\" Askharoun"; + + public override AbstractUICollection? GetDefaultOptionsUI() { - private const int ZUNESERVICES_ENDPOINTS_BLOCK_OFFSET = 0x14D60; - private const int ZUNESERVICES_ENDPOINTS_BLOCK_LENGTH = 0x884; - private const string WEBSERVICE_ICON_TESTING = "\uE72C"; - private const string WEBSERVICE_ICON_SUCCESS = "\uE73E"; - private const string WEBSERVICE_ICON_UNREACHABLE = "\uF384"; - private const string WEBSERVICE_ICON_UNAVAILABLE = "\uE894"; - private const string WEBSERVICE_SUBTITLE_TESTING = "Testing..."; - private const string WEBSERVICE_SUBTITLE_SUCCESS = "OK"; - private const string WEBSERVICE_SUBTITLE_UNREACHABLE = "Unreachable"; - - private HttpClient _client; - private CancellationTokenSource _cts = new(); - - public override string Id => nameof(WebservicesMod); - - public override string Title => "Community Webservices"; - - public override string Description => "Partially restores online features such as the Marketplace by patching the Zune desktop software " + - "to use the community's recreation of Microsoft's Zune servers at zunes.me (instead of zune.net)."; - - public override string Author => "Joshua \"Yoshi\" Askharoun"; - - public override AbstractUICollection? GetDefaultOptionsUI() + AbstractUICollection optionsUi = new(nameof(FeaturesOverrideMod)) { - AbstractUICollection optionsUi = new(nameof(FeaturesOverrideMod)) + new AbstractTextBox("hostBox", string.Empty, "zune.net") { - new AbstractTextBox("hostBox", string.Empty, "zune.net") + Title = "host", + TooltipText = "The host where the replacement servers are located. Must be the same length as \"zune.net\"." + }, + new AbstractDataList("hostsTest", new List() { - Title = "host", - TooltipText = "The host where the replacement servers are located. Must be the same length as \"zune.net\"." - }, - new AbstractDataList("hostsTest", new List() - { - GetWebserviceAvailabilityUI("Main website", "www"), - GetWebserviceAvailabilityUI("Social website", "social"), - GetWebserviceAvailabilityUI("Catalog", "catalog"), - GetWebserviceAvailabilityUI("Image catalog", "image.catalog"), - GetWebserviceAvailabilityUI("Social", "socialapi"), - GetWebserviceAvailabilityUI("Social [Comments]", "comments"), - GetWebserviceAvailabilityUI("Social [Inbox]", "inbox"), - GetWebserviceAvailabilityUI("Commerce [Sign in]", "commerce"), - GetWebserviceAvailabilityUI("Mix", "mix"), - GetWebserviceAvailabilityUI("Resources [Firmware]", "resources"), - GetWebserviceAvailabilityUI("Statistics", "stats"), - } - ) - { - Subtitle = "TEST AVAILABILITY OF EACH KNOWN WEB SERVICE ON THE NEW HOST.".ToUpper(), - IsUserEditingEnabled = false, - PreferredDisplayMode = AbstractDataListPreferredDisplayMode.Grid, + GetWebserviceAvailabilityUI("Main website", "www"), + GetWebserviceAvailabilityUI("Social website", "social"), + GetWebserviceAvailabilityUI("Catalog", "catalog"), + GetWebserviceAvailabilityUI("Image catalog", "image.catalog"), + GetWebserviceAvailabilityUI("Social", "socialapi"), + GetWebserviceAvailabilityUI("Social [Comments]", "comments"), + GetWebserviceAvailabilityUI("Social [Inbox]", "inbox"), + GetWebserviceAvailabilityUI("Commerce [Sign in]", "commerce"), + GetWebserviceAvailabilityUI("Mix", "mix"), + GetWebserviceAvailabilityUI("Resources [Firmware]", "resources"), + GetWebserviceAvailabilityUI("Statistics", "stats"), } - }; - return optionsUi; + ) + { + Subtitle = "TEST AVAILABILITY OF EACH KNOWN WEB SERVICE ON THE NEW HOST.".ToUpper(), + IsUserEditingEnabled = false, + PreferredDisplayMode = AbstractDataListPreferredDisplayMode.Grid, + } + }; + return optionsUi; + } + + private static AbstractUIMetadata GetWebserviceAvailabilityUI(string name, string subDomain) + { + return new("serviceTestUI_" + subDomain) + { + Title = name, + IconCode = WEBSERVICE_ICON_TESTING + }; + } + + public override IReadOnlyList? DependentMods => null; + + public override Task Init() + { + _client = new() + { + Timeout = TimeSpan.FromSeconds(3) + }; + + AbstractTextBox newHostBox = (AbstractTextBox)OptionsUI![0]; + newHostBox.ValueChanged += OnHostChanged; + newHostBox.Value = "zunes.me"; + + return Task.CompletedTask; + } + + public override async Task Apply() + { + // Verify that ZuneServices.dll exists + FileInfo zsDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneService.dll")); + if (!zsDllInfo.Exists) + { + return $"The file '{zsDllInfo.FullName}' does not exist."; } - private static AbstractUIMetadata GetWebserviceAvailabilityUI(string name, string subDomain) + // Make a backup if it doesn't already exist + FileInfo zsDllBackupInfo = new(Path.Combine(StorageDirectory, "ZuneService.original.dll")); + if (!zsDllBackupInfo.Exists) { - return new("serviceTestUI_" + subDomain) - { - Title = name, - IconCode = WEBSERVICE_ICON_TESTING - }; + File.Copy(zsDllInfo.FullName, zsDllBackupInfo.FullName, true); } - public override IReadOnlyList? DependentMods => null; - - public override Task Init() + try { - _client = new() + // Open the file + using FileStream zsDll = zsDllInfo.Open(FileMode.Open); + using BinaryWriter zsDllWriter = new(zsDll); + using BinaryReader zsDllReader = new(zsDll); + + // Verify that the DLL is from v4.8 (other versions not tested) + zsDllReader.BaseStream.Position = 0x12C824; + var versionBytes = zsDllReader.ReadBytes(6); + if (versionBytes[0] != '4' || versionBytes[2] != '.' || versionBytes[4] != '8') { - Timeout = TimeSpan.FromSeconds(3) - }; + return "This mod has not been tested on versions earlier than 4.8."; + } - AbstractTextBox newHostBox = (AbstractTextBox)OptionsUI![0]; - newHostBox.ValueChanged += OnHostChanged; - newHostBox.Value = "zunes.me"; + // Get and validate replacement host + string oldHost = "zune.net"; + string newHost = ((AbstractTextBox)OptionsUI![0]).Value; + if (newHost.Length != oldHost.Length) + { + return $"The new host (\"{newHost}\") must have the same length as \"{oldHost}\"."; + } + var ping = await new System.Net.NetworkInformation.Ping().SendPingAsync(newHost); + if (ping.Status != System.Net.NetworkInformation.IPStatus.Success) + { + return $"Failed to reach \"{newHost}\". Ping status: {ping.Status}"; + } - return Task.CompletedTask; + // Patch ZuneServices.dll to use the new host instead of zune.net + zsDllReader.BaseStream.Position = ZUNESERVICES_ENDPOINTS_BLOCK_OFFSET; + string endpointBlock = System.Text.Encoding.Unicode.GetString(zsDllReader.ReadBytes(ZUNESERVICES_ENDPOINTS_BLOCK_LENGTH)); + endpointBlock = endpointBlock.Replace("resources." + oldHost, "www.zuneupdate.com"); // Use zuneupdate.com until resources.zunes.me is online + endpointBlock = endpointBlock.Replace(oldHost, newHost); + byte[] endpointBytes = System.Text.Encoding.Unicode.GetBytes(endpointBlock); + if (endpointBytes.Length != ZUNESERVICES_ENDPOINTS_BLOCK_LENGTH) + { + return "Failed to safely overwrite strings in DLL."; + } + zsDllWriter.Seek(ZUNESERVICES_ENDPOINTS_BLOCK_OFFSET, SeekOrigin.Begin); + zsDllWriter.Write(endpointBytes); + + + // Enable all feature overrides affected by new servers + bool setOverrideSuccess = true; + foreach (var feature in _affectedFeatures) + setOverrideSuccess &= SetFeatureOverride(feature, true); + + if (setOverrideSuccess != true) + return "Unable to set feature overrides. The mod was successful, but you may not be able to see it in the Zune software."; + + return null; + } + catch (IOException) + { + return $"Unable to replace '{zsDllInfo.FullName}'. Verify that the Zune software is not running and try again."; + } + catch (Exception ex) + { + return ex.Message; + } + } + + public override async Task Reset() + { + string zsDllPath = Path.Combine(ZuneInstallDir, "ZuneService.dll"); + try + { + // Copy backup to application folder + File.Copy(Path.Combine(StorageDirectory, "ZuneService.original.dll"), zsDllPath, true); + + // Disable all feature overrides affected by new servers + bool setOverrideSuccess = true; + foreach (var feature in _affectedFeatures) + setOverrideSuccess &= SetFeatureOverride(feature, false); + + if (setOverrideSuccess != true) + return "Unable to reset feature overrides. The mod was successfully removed, but you may still be able to see it in the Zune software."; + + return null; + } + catch (IOException) + { + return $"Unable to replace '{zsDllPath}'. Verify that the Zune software is not running and try again."; + } + catch (Exception ex) + { + return ex.Message; + } + } + + async void OnHostChanged(object? sender, string newHost) + { + await _cts.CancelAsync(); + _client.CancelPendingRequests(); + + _cts = new(); + + AbstractDataList list = (AbstractDataList)OptionsUI![1]; + + // Reset all statuses + foreach (AbstractUIMetadata metadata in list.Items) + { + metadata.IconCode = WEBSERVICE_ICON_TESTING; + metadata.Subtitle = WEBSERVICE_SUBTITLE_TESTING; } - public override async Task Apply() + // We want to update all the statuses, but we also need to be able + // to cancel it when the user enters a new host. + foreach (AbstractUIMetadata metadata in list.Items) + UpdateStatus(metadata, newHost, _cts.Token); + } + + private async Task Ping(string url, CancellationToken token = default) + { + try { - // Verify that ZuneServices.dll exists - FileInfo zsDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneService.dll")); - if (!zsDllInfo.Exists) - { - return $"The file '{zsDllInfo.FullName}' does not exist."; - } - - // Make a backup if it doesn't already exist - FileInfo zsDllBackupInfo = new(Path.Combine(StorageDirectory, "ZuneService.original.dll")); - if (!zsDllBackupInfo.Exists) - { - File.Copy(zsDllInfo.FullName, zsDllBackupInfo.FullName, true); - } - - try - { - // Open the file - using FileStream zsDll = zsDllInfo.Open(FileMode.Open); - using BinaryWriter zsDllWriter = new(zsDll); - using BinaryReader zsDllReader = new(zsDll); - - // Verify that the DLL is from v4.8 (other versions not tested) - zsDllReader.BaseStream.Position = 0x12C824; - var versionBytes = zsDllReader.ReadBytes(6); - if (versionBytes[0] != '4' || versionBytes[2] != '.' || versionBytes[4] != '8') - { - return "This mod has not been tested on versions earlier than 4.8."; - } - - // Get and validate replacement host - string oldHost = "zune.net"; - string newHost = ((AbstractTextBox)OptionsUI![0]).Value; - if (newHost.Length != oldHost.Length) - { - return $"The new host (\"{newHost}\") must have the same length as \"{oldHost}\"."; - } - var ping = await new System.Net.NetworkInformation.Ping().SendPingAsync(newHost); - if (ping.Status != System.Net.NetworkInformation.IPStatus.Success) - { - return $"Failed to reach \"{newHost}\". Ping status: {ping.Status}"; - } - - // Patch ZuneServices.dll to use the new host instead of zune.net - zsDllReader.BaseStream.Position = ZUNESERVICES_ENDPOINTS_BLOCK_OFFSET; - string endpointBlock = System.Text.Encoding.Unicode.GetString(zsDllReader.ReadBytes(ZUNESERVICES_ENDPOINTS_BLOCK_LENGTH)); - endpointBlock = endpointBlock.Replace("resources." + oldHost, "www.zuneupdate.com"); // Use zuneupdate.com until resources.zunes.me is online - endpointBlock = endpointBlock.Replace(oldHost, newHost); - byte[] endpointBytes = System.Text.Encoding.Unicode.GetBytes(endpointBlock); - if (endpointBytes.Length != ZUNESERVICES_ENDPOINTS_BLOCK_LENGTH) - { - return "Failed to safely overwrite strings in DLL."; - } - zsDllWriter.Seek(ZUNESERVICES_ENDPOINTS_BLOCK_OFFSET, SeekOrigin.Begin); - zsDllWriter.Write(endpointBytes); - - - // Enable all feature overrides affected by new servers - bool setOverrideSuccess = true; - setOverrideSuccess &= SetFeatureOverride("Apps", true); - setOverrideSuccess &= SetFeatureOverride("Channels", true); - setOverrideSuccess &= SetFeatureOverride("Games", true); - setOverrideSuccess &= SetFeatureOverride("Marketplace", true); - setOverrideSuccess &= SetFeatureOverride("MBRPreview", true); - setOverrideSuccess &= SetFeatureOverride("MBRPurchase", true); - setOverrideSuccess &= SetFeatureOverride("MBRRental", true); - setOverrideSuccess &= SetFeatureOverride("Music", true); - setOverrideSuccess &= SetFeatureOverride("MusicVideos", true); - setOverrideSuccess &= SetFeatureOverride("Picks", true); - setOverrideSuccess &= SetFeatureOverride("Podcasts", true); - setOverrideSuccess &= SetFeatureOverride("Sign In Available", true); - setOverrideSuccess &= SetFeatureOverride("Social", true); - setOverrideSuccess &= SetFeatureOverride("SocialMarketplace", true); - setOverrideSuccess &= SetFeatureOverride("SubscriptionFreeTracks", true); - setOverrideSuccess &= SetFeatureOverride("Videos", true); - if (setOverrideSuccess != true) - { - return "Unable to set feature overrides. The mod was successful, but you may not be able to see it in the Zune software."; - } - - return null; - } - catch (IOException) - { - return $"Unable to replace '{zsDllInfo.FullName}'. Verify that the Zune software is not running and try again."; - } - catch (Exception ex) - { - return ex.Message; - } + var response = await _client.GetAsync(url, token); + response.EnsureSuccessStatusCode(); + return null; } - - public override async Task Reset() + catch (HttpRequestException webEx) { - string zsDllPath = Path.Combine(ZuneInstallDir, "ZuneService.dll"); - try - { - // Copy backup to application folder - File.Copy(Path.Combine(StorageDirectory, "ZuneService.original.dll"), zsDllPath, true); - - // Disable all feature overrides affected by new servers - bool setOverrideSuccess = true; - setOverrideSuccess &= SetFeatureOverride("Apps", false); - setOverrideSuccess &= SetFeatureOverride("Channels", false); - setOverrideSuccess &= SetFeatureOverride("Games", false); - setOverrideSuccess &= SetFeatureOverride("Marketplace", false); - setOverrideSuccess &= SetFeatureOverride("MBRPreview", false); - setOverrideSuccess &= SetFeatureOverride("MBRPurchase", false); - setOverrideSuccess &= SetFeatureOverride("MBRRental", false); - setOverrideSuccess &= SetFeatureOverride("Music", false); - setOverrideSuccess &= SetFeatureOverride("MusicVideos", false); - setOverrideSuccess &= SetFeatureOverride("Picks", false); - setOverrideSuccess &= SetFeatureOverride("Podcasts", false); - setOverrideSuccess &= SetFeatureOverride("Sign In Available", false); - setOverrideSuccess &= SetFeatureOverride("Social", false); - setOverrideSuccess &= SetFeatureOverride("SocialMarketplace", false); - setOverrideSuccess &= SetFeatureOverride("SubscriptionFreeTracks", false); - setOverrideSuccess &= SetFeatureOverride("Videos", false); - if (setOverrideSuccess != true) - { - return "Unable to reset feature overrides. The mod was successfully removed, but you may still be able to see it in the Zune software."; - } - - return null; - } - catch (IOException) - { - return $"Unable to replace '{zsDllPath}'. Verify that the Zune software is not running and try again."; - } - catch (Exception ex) - { - return ex.Message; - } + if (webEx.InnerException?.InnerException != null) + return webEx.InnerException.InnerException.Message; + return webEx?.Message ?? string.Empty; } - - async void OnHostChanged(object? sender, string newHost) + catch (Exception ex) { - await _cts.CancelAsync(); - _client.CancelPendingRequests(); - - _cts = new(); - - AbstractDataList list = (AbstractDataList)OptionsUI![1]; - - // Reset all statuses - foreach (AbstractUIMetadata metadata in list.Items) - { - metadata.IconCode = WEBSERVICE_ICON_TESTING; - metadata.Subtitle = WEBSERVICE_SUBTITLE_TESTING; - } - - // We want to update all the statuses, but we also need to be able - // to cancel it when the user enters a new host. - foreach (AbstractUIMetadata metadata in list.Items) - UpdateStatus(metadata, newHost, _cts.Token); + return ex?.Message ?? string.Empty; } + } - private async Task Ping(string url, CancellationToken token = default) + private async Task UpdateStatus(AbstractUIMetadata metadata, string newHost, CancellationToken token = default) + { + string url = "http://" + (metadata.Id.Split('_')[1] + '.' + newHost).Replace("www.", string.Empty); + string? pingResult = await Ping(url, token); + + if (pingResult == null) { - try - { - var response = await _client.GetAsync(url, token); - response.EnsureSuccessStatusCode(); - return null; - } - catch (HttpRequestException webEx) - { - if (webEx.InnerException?.InnerException != null) - return webEx.InnerException.InnerException.Message; - return webEx?.Message ?? string.Empty; - } - catch (Exception ex) - { - return ex?.Message ?? string.Empty; - } + metadata.IconCode = WEBSERVICE_ICON_SUCCESS; + metadata.Subtitle = WEBSERVICE_SUBTITLE_SUCCESS; } - - private async Task UpdateStatus(AbstractUIMetadata metadata, string newHost, CancellationToken token = default) + else if (pingResult != string.Empty) { - string url = "http://" + (metadata.Id.Split('_')[1] + '.' + newHost).Replace("www.", string.Empty); - string? pingResult = await Ping(url, token); - - if (pingResult == null) - { - metadata.IconCode = WEBSERVICE_ICON_SUCCESS; - metadata.Subtitle = WEBSERVICE_SUBTITLE_SUCCESS; - } - else if (pingResult != string.Empty) - { - metadata.IconCode = WEBSERVICE_ICON_UNAVAILABLE; - metadata.Subtitle = pingResult; - } - else - { - metadata.IconCode = WEBSERVICE_ICON_UNREACHABLE; - metadata.Subtitle = WEBSERVICE_SUBTITLE_UNREACHABLE; - } + metadata.IconCode = WEBSERVICE_ICON_UNAVAILABLE; + metadata.Subtitle = pingResult; + } + else + { + metadata.IconCode = WEBSERVICE_ICON_UNREACHABLE; + metadata.Subtitle = WEBSERVICE_SUBTITLE_UNREACHABLE; } } } diff --git a/ZuneModCore/Services/IModCoreConfig.cs b/ZuneModCore/Services/IModCoreConfig.cs index e402270..028b11e 100644 --- a/ZuneModCore/Services/IModCoreConfig.cs +++ b/ZuneModCore/Services/IModCoreConfig.cs @@ -1,7 +1,6 @@ -namespace ZuneModCore.Services +namespace ZuneModCore.Services; + +public interface IModCoreConfig { - public interface IModCoreConfig - { - string ZuneInstallDir { get; } - } + string ZuneInstallDir { get; } } diff --git a/ZuneModCore/Win32/FileUtil.cs b/ZuneModCore/Win32/FileUtil.cs index b224c4e..5b9a476 100644 --- a/ZuneModCore/Win32/FileUtil.cs +++ b/ZuneModCore/Win32/FileUtil.cs @@ -1,151 +1,143 @@ -using System; +namespace ZuneModCore.Win32; + +using System.Runtime.InteropServices; +using System.Diagnostics; +using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -namespace ZuneModCore.Win32 +public static partial class FileUtil { - using System.Runtime.InteropServices; - using System.Diagnostics; - using System; - using System.Collections.Generic; - - static public class FileUtil + [StructLayout(LayoutKind.Sequential)] + struct RM_UNIQUE_PROCESS { - [StructLayout(LayoutKind.Sequential)] - struct RM_UNIQUE_PROCESS + public int dwProcessId; + public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; + } + + const int RmRebootReasonNone = 0; + const int CCH_RM_MAX_APP_NAME = 255; + const int CCH_RM_MAX_SVC_NAME = 63; + + enum RM_APP_TYPE + { + RmUnknownApp = 0, + RmMainWindow = 1, + RmOtherWindow = 2, + RmService = 3, + RmExplorer = 4, + RmConsole = 5, + RmCritical = 1000 + } + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + struct RM_PROCESS_INFO + { + public RM_UNIQUE_PROCESS Process; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] + public string strAppName; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] + public string strServiceShortName; + + public RM_APP_TYPE ApplicationType; + public uint AppStatus; + public uint TSSessionId; + [MarshalAs(UnmanagedType.Bool)] + public bool bRestartable; + } + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] + static extern int RmRegisterResources(uint pSessionHandle, + uint nFiles, + string[] rgsFilenames, + uint nApplications, + [In] RM_UNIQUE_PROCESS[] rgApplications, + uint nServices, + string[] rgsServiceNames); + + [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] + static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); + + [LibraryImport("rstrtmgr.dll")] + private static partial int RmEndSession(uint pSessionHandle); + + [DllImport("rstrtmgr.dll")] + static extern int RmGetList(uint dwSessionHandle, + out uint pnProcInfoNeeded, + ref uint pnProcInfo, + [In, Out] RM_PROCESS_INFO[] rgAffectedApps, + ref uint lpdwRebootReasons); + + /// + /// Find out what process(es) have a lock on the specified file. + /// + /// Path of the file. + /// Processes locking the file + /// See also: + /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx + /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing) + /// + /// + static public List WhoIsLocking(string path) + { + string key = Guid.NewGuid().ToString(); + List processes = []; + + int res = RmStartSession(out uint handle, 0, key); + if (res != 0) + throw new Exception("Could not begin restart session. Unable to determine file locker."); + + try { - public int dwProcessId; - public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime; - } + const int ERROR_MORE_DATA = 234; + uint pnProcInfo = 0, + lpdwRebootReasons = RmRebootReasonNone; - const int RmRebootReasonNone = 0; - const int CCH_RM_MAX_APP_NAME = 255; - const int CCH_RM_MAX_SVC_NAME = 63; + string[] resources = [path]; // Just checking on one resource. - enum RM_APP_TYPE - { - RmUnknownApp = 0, - RmMainWindow = 1, - RmOtherWindow = 2, - RmService = 3, - RmExplorer = 4, - RmConsole = 5, - RmCritical = 1000 - } + res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - struct RM_PROCESS_INFO - { - public RM_UNIQUE_PROCESS Process; + if (res != 0) throw new Exception("Could not register resource."); - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)] - public string strAppName; + //Note: there's a race condition here -- the first call to RmGetList() returns + // the total number of process. However, when we call RmGetList() again to get + // the actual processes this number may have increased. + res = RmGetList(handle, out uint pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); - [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_SVC_NAME + 1)] - public string strServiceShortName; - - public RM_APP_TYPE ApplicationType; - public uint AppStatus; - public uint TSSessionId; - [MarshalAs(UnmanagedType.Bool)] - public bool bRestartable; - } - - [DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)] - static extern int RmRegisterResources(uint pSessionHandle, - UInt32 nFiles, - string[] rgsFilenames, - UInt32 nApplications, - [In] RM_UNIQUE_PROCESS[] rgApplications, - UInt32 nServices, - string[] rgsServiceNames); - - [DllImport("rstrtmgr.dll", CharSet = CharSet.Auto)] - static extern int RmStartSession(out uint pSessionHandle, int dwSessionFlags, string strSessionKey); - - [DllImport("rstrtmgr.dll")] - static extern int RmEndSession(uint pSessionHandle); - - [DllImport("rstrtmgr.dll")] - static extern int RmGetList(uint dwSessionHandle, - out uint pnProcInfoNeeded, - ref uint pnProcInfo, - [In, Out] RM_PROCESS_INFO[] rgAffectedApps, - ref uint lpdwRebootReasons); - - /// - /// Find out what process(es) have a lock on the specified file. - /// - /// Path of the file. - /// Processes locking the file - /// See also: - /// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373661(v=vs.85).aspx - /// http://wyupdate.googlecode.com/svn-history/r401/trunk/frmFilesInUse.cs (no copyright in code at time of viewing) - /// - /// - static public List WhoIsLocking(string path) - { - uint handle; - string key = Guid.NewGuid().ToString(); - List processes = new List(); - - int res = RmStartSession(out handle, 0, key); - if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker."); - - try + if (res == ERROR_MORE_DATA) { - const int ERROR_MORE_DATA = 234; - uint pnProcInfoNeeded = 0, - pnProcInfo = 0, - lpdwRebootReasons = RmRebootReasonNone; + // Create an array to store the process results + RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; + pnProcInfo = pnProcInfoNeeded; - string[] resources = new string[] { path }; // Just checking on one resource. - - res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null); - - if (res != 0) throw new Exception("Could not register resource."); - - //Note: there's a race condition here -- the first call to RmGetList() returns - // the total number of process. However, when we call RmGetList() again to get - // the actual processes this number may have increased. - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, null, ref lpdwRebootReasons); - - if (res == ERROR_MORE_DATA) + // Get the list + res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); + if (res == 0) { - // Create an array to store the process results - RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded]; - pnProcInfo = pnProcInfoNeeded; + processes = new List((int)pnProcInfo); - // Get the list - res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons); - if (res == 0) + // Enumerate all of the results and add them to the + // list to be returned + for (int i = 0; i < pnProcInfo; i++) { - processes = new List((int)pnProcInfo); - - // Enumerate all of the results and add them to the - // list to be returned - for (int i = 0; i < pnProcInfo; i++) + try { - try - { - processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); - } - // catch the error -- in case the process is no longer running - catch (ArgumentException) { } + processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId)); } + // catch the error -- in case the process is no longer running + catch (ArgumentException) { } } - else throw new Exception("Could not list processes locking resource."); } - else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result."); + else throw new Exception("Could not list processes locking resource."); } - finally - { - RmEndSession(handle); - } - - return processes; + else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result."); } + finally + { + RmEndSession(handle); + } + + return processes; } } diff --git a/ZuneModCore/Win32/RegEdit.cs b/ZuneModCore/Win32/RegEdit.cs index 6f75735..4ae1c2a 100644 --- a/ZuneModCore/Win32/RegEdit.cs +++ b/ZuneModCore/Win32/RegEdit.cs @@ -1,73 +1,65 @@ using Microsoft.Win32; -namespace ZuneModCore.Win32 +namespace ZuneModCore.Win32; + +public class RegEdit { + public const string ZUNE_REG_PATH = @"SOFTWARE\Microsoft\Zune\"; -#pragma warning disable CA1416 // Validate platform compatibility - - public class RegEdit + public static bool CurrentUserSetBoolValue(string key, string name, bool value) { - public const string ZUNE_REG_PATH = @"SOFTWARE\Microsoft\Zune\"; + RegistryKey? regKey = Registry.CurrentUser.OpenSubKey(key, true); + regKey ??= Registry.CurrentUser.CreateSubKey(key, true); - public static bool CurrentUserSetBoolValue(string key, string name, bool value) - { - RegistryKey? regKey = Registry.CurrentUser.OpenSubKey(key, true); - if (regKey == null) - regKey = Registry.CurrentUser.CreateSubKey(key, true); + regKey.SetValue(name, value, RegistryValueKind.DWord); + regKey.Close(); + regKey.Dispose(); - regKey.SetValue(name, value, RegistryValueKind.DWord); - regKey.Close(); - regKey.Dispose(); - - // Read the key to make sure it was set properly - if (CurrentUserGetBoolValue(key, name) == null) - return false; - return true; - } - - public static bool? CurrentUserGetBoolValue(string key, string name) - { - using RegistryKey? regKey = Registry.CurrentUser.OpenSubKey(key, true); - if (regKey == null) - return false; - - // Return null if a boolean value couldn't be read from the key - int? value = regKey.GetValue(name, false) as int?; - if (!value.HasValue) - return null; - return value != 0; - } - - public static void CurrentUserDeleteKey(string key) - { - using RegistryKey? targetKey = Registry.CurrentUser.OpenSubKey(key, true); - if (targetKey == null) - // Key is already deleted - return; - - // Delete target subkeys - if (targetKey.SubKeyCount > 0) - targetKey.DeleteSubKeyTree(key, false); - - // Delete target values - string[] values = targetKey.GetValueNames(); - foreach (string value in values) - targetKey.DeleteValue(value, false); - - // Delete target key - Registry.CurrentUser.DeleteSubKey(key); - } - - public static void CurrentUserDeleteValue(string key, string name) - { - using RegistryKey? regKey = Registry.CurrentUser.OpenSubKey(key, true); - if (regKey == null) - return; - - regKey.DeleteValue(name, false); - } + // Read the key to make sure it was set properly + if (CurrentUserGetBoolValue(key, name) == null) + return false; + return true; } -#pragma warning restore CA1416 // Validate platform compatibility + public static bool? CurrentUserGetBoolValue(string key, string name) + { + using RegistryKey? regKey = Registry.CurrentUser.OpenSubKey(key, true); + if (regKey == null) + return false; + // Return null if a boolean value couldn't be read from the key + int? value = regKey.GetValue(name, false) as int?; + if (!value.HasValue) + return null; + return value != 0; + } + + public static void CurrentUserDeleteKey(string key) + { + using RegistryKey? targetKey = Registry.CurrentUser.OpenSubKey(key, true); + if (targetKey == null) + // Key is already deleted + return; + + // Delete target subkeys + if (targetKey.SubKeyCount > 0) + targetKey.DeleteSubKeyTree(key, false); + + // Delete target values + string[] values = targetKey.GetValueNames(); + foreach (string value in values) + targetKey.DeleteValue(value, false); + + // Delete target key + Registry.CurrentUser.DeleteSubKey(key); + } + + public static void CurrentUserDeleteValue(string key, string name) + { + using RegistryKey? regKey = Registry.CurrentUser.OpenSubKey(key, true); + if (regKey == null) + return; + + regKey.DeleteValue(name, false); + } } diff --git a/ZuneModCore/Win32/TokenManipulator.cs b/ZuneModCore/Win32/TokenManipulator.cs index a0146d5..f4e2194 100644 --- a/ZuneModCore/Win32/TokenManipulator.cs +++ b/ZuneModCore/Win32/TokenManipulator.cs @@ -1,123 +1,119 @@ using System; using System.IO; -using System.Runtime.InteropServices; using System.Security.AccessControl; using System.Security.Principal; using Vanara.PInvoke; -namespace ZuneModCore.Win32 +namespace ZuneModCore.Win32; + +public class TokenManipulator { - public class TokenManipulator + public static bool AddPrivilege(string privilege) { - public static bool AddPrivilege(string privilege) + try { - try - { - bool retVal; - var hproc = Kernel32.GetCurrentProcess(); + bool retVal; + var hproc = Kernel32.GetCurrentProcess(); - retVal = AdvApi32.OpenProcessToken(hproc, - AdvApi32.TokenAccess.TOKEN_ADJUST_PRIVILEGES | AdvApi32.TokenAccess.TOKEN_QUERY, out var htok); + retVal = AdvApi32.OpenProcessToken(hproc, + AdvApi32.TokenAccess.TOKEN_ADJUST_PRIVILEGES | AdvApi32.TokenAccess.TOKEN_QUERY, out var htok); - retVal = AdvApi32.LookupPrivilegeValue(null, privilege, out var tpLuid); - AdvApi32.TOKEN_PRIVILEGES tp = new(tpLuid, AdvApi32.PrivilegeAttributes.SE_PRIVILEGE_ENABLED); + retVal = AdvApi32.LookupPrivilegeValue(null, privilege, out var tpLuid); + AdvApi32.TOKEN_PRIVILEGES tp = new(tpLuid, AdvApi32.PrivilegeAttributes.SE_PRIVILEGE_ENABLED); - AdvApi32.AdjustTokenPrivileges(htok, false, in tp, out _).ThrowIfFailed(); + AdvApi32.AdjustTokenPrivileges(htok, false, in tp, out _).ThrowIfFailed(); - return retVal; - } - catch - { - throw; - } + return retVal; } - public static bool RemovePrivilege(string privilege) + catch { - try - { - bool retVal; - var hproc = Kernel32.GetCurrentProcess(); - - retVal = AdvApi32.OpenProcessToken(hproc, - AdvApi32.TokenAccess.TOKEN_ADJUST_PRIVILEGES | AdvApi32.TokenAccess.TOKEN_QUERY, out var htok); - - retVal = AdvApi32.LookupPrivilegeValue(null, privilege, out var tpLuid); - AdvApi32.TOKEN_PRIVILEGES tp = new(tpLuid, AdvApi32.PrivilegeAttributes.SE_PRIVILEGE_DISABLED); - - AdvApi32.AdjustTokenPrivileges(htok, false, in tp, out _).ThrowIfFailed(); - - return retVal; - } - catch - { - throw; - } - } - -#pragma warning disable CA1416 // Validate platform compatibility - public static void TakeOwnership(FileInfo file) - { - EscalateToAdmin(); - - // Get access control - FileSecurity security = file.GetAccessControl(); - SecurityIdentifier? cu = WindowsIdentity.GetCurrent().User; - - // Set owner to current user - security.SetOwner(cu); - security.SetAccessRule(new FileSystemAccessRule(cu, FileSystemRights.Modify, AccessControlType.Allow)); - - // Update the Access Control on the original file - file.SetAccessControl(security); - } - - public static void TakeOwnership(DirectoryInfo dir) - { - EscalateToAdmin(); - - // Get access control - DirectorySecurity security = dir.GetAccessControl(); - SecurityIdentifier? cu = WindowsIdentity.GetCurrent().User; - - // Set owner to current user - security.SetOwner(cu); - security.SetAccessRule(new FileSystemAccessRule(cu, FileSystemRights.Modify, AccessControlType.Allow)); - - // Update the Access Control on the original file - dir.SetAccessControl(security); - } - - public static void TakeOwnership(FileSystemInfo info) - { - if (info is FileInfo file) - TakeOwnership(file); - else if (info is DirectoryInfo dir) - TakeOwnership(dir); - } -#pragma warning restore CA1416 // Validate platform compatibility - - public static bool TryTakeOwnership(FileInfo file, out Exception? exception) - { - try - { - TakeOwnership(file); - - exception = null; - return true; - } - catch (Exception ex) - { - exception = ex; - return false; - } - } - - public static void EscalateToAdmin() - { - // Activate necessary admin privileges to make changes without NTFS perms - AddPrivilege("SeRestorePrivilege"); // Necessary to set Owner Permissions - AddPrivilege("SeBackupPrivilege"); // Necessary to bypass Traverse Checking - AddPrivilege("SeTakeOwnershipPrivilege"); // Necessary to override FilePermissions + throw; } } + public static bool RemovePrivilege(string privilege) + { + try + { + bool retVal; + var hproc = Kernel32.GetCurrentProcess(); + + retVal = AdvApi32.OpenProcessToken(hproc, + AdvApi32.TokenAccess.TOKEN_ADJUST_PRIVILEGES | AdvApi32.TokenAccess.TOKEN_QUERY, out var htok); + + retVal = AdvApi32.LookupPrivilegeValue(null, privilege, out var tpLuid); + AdvApi32.TOKEN_PRIVILEGES tp = new(tpLuid, AdvApi32.PrivilegeAttributes.SE_PRIVILEGE_DISABLED); + + AdvApi32.AdjustTokenPrivileges(htok, false, in tp, out _).ThrowIfFailed(); + + return retVal; + } + catch + { + throw; + } + } + + public static void TakeOwnership(FileInfo file) + { + EscalateToAdmin(); + + // Get access control + FileSecurity security = file.GetAccessControl(); + SecurityIdentifier? cu = WindowsIdentity.GetCurrent().User; + + // Set owner to current user + security.SetOwner(cu); + security.SetAccessRule(new FileSystemAccessRule(cu, FileSystemRights.Modify, AccessControlType.Allow)); + + // Update the Access Control on the original file + file.SetAccessControl(security); + } + + public static void TakeOwnership(DirectoryInfo dir) + { + EscalateToAdmin(); + + // Get access control + DirectorySecurity security = dir.GetAccessControl(); + SecurityIdentifier? cu = WindowsIdentity.GetCurrent().User; + + // Set owner to current user + security.SetOwner(cu); + security.SetAccessRule(new FileSystemAccessRule(cu, FileSystemRights.Modify, AccessControlType.Allow)); + + // Update the Access Control on the original file + dir.SetAccessControl(security); + } + + public static void TakeOwnership(FileSystemInfo info) + { + if (info is FileInfo file) + TakeOwnership(file); + else if (info is DirectoryInfo dir) + TakeOwnership(dir); + } + + public static bool TryTakeOwnership(FileInfo file, out Exception? exception) + { + try + { + TakeOwnership(file); + + exception = null; + return true; + } + catch (Exception ex) + { + exception = ex; + return false; + } + } + + public static void EscalateToAdmin() + { + // Activate necessary admin privileges to make changes without NTFS perms + AddPrivilege("SeRestorePrivilege"); // Necessary to set Owner Permissions + AddPrivilege("SeBackupPrivilege"); // Necessary to bypass Traverse Checking + AddPrivilege("SeTakeOwnershipPrivilege"); // Necessary to override FilePermissions + } } diff --git a/ZuneModCore/ZuneModCore.csproj b/ZuneModCore/ZuneModCore.csproj index 42a0fe1..2de2ee6 100644 --- a/ZuneModCore/ZuneModCore.csproj +++ b/ZuneModCore/ZuneModCore.csproj @@ -1,8 +1,8 @@  - net8.0 - 9 + net8.0-windows + 12 enable AnyCPU;x64;x86 true