mirror of
https://github.com/ZuneDev/ZuneModdingHelper.git
synced 2026-07-27 13:12:21 -07:00
Update to net8.0-windows and C# 12
This commit is contained in:
+69
-72
@@ -6,85 +6,82 @@ using System.IO;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ZuneModCore.Mods;
|
using ZuneModCore.Mods;
|
||||||
|
|
||||||
namespace ZuneModCore
|
namespace ZuneModCore;
|
||||||
|
|
||||||
|
public abstract class Mod
|
||||||
{
|
{
|
||||||
public abstract class Mod
|
/// <summary>
|
||||||
|
/// A list of all available mods
|
||||||
|
/// </summary>
|
||||||
|
public static readonly IReadOnlyList<Type> AvailableModTypes = new List<Type>
|
||||||
{
|
{
|
||||||
/// <summary>
|
typeof(FeaturesOverrideMod),
|
||||||
/// A list of all available mods
|
typeof(VideoSyncMod),
|
||||||
/// </summary>
|
typeof(WebservicesMod),
|
||||||
public static readonly IReadOnlyList<Type> AvailableModTypes = new List<Type>
|
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<Mod>? _mods;
|
||||||
|
public static IReadOnlyList<Mod> AvailableMods
|
||||||
|
{
|
||||||
|
get
|
||||||
{
|
{
|
||||||
typeof(FeaturesOverrideMod),
|
if (_mods is null)
|
||||||
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<Mod>? _mods;
|
|
||||||
public static IReadOnlyList<Mod> AvailableMods
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
{
|
||||||
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 instance = ActivatorUtilities.CreateInstance(services, modType);
|
||||||
var services = CommunityToolkit.Mvvm.DependencyInjection.Ioc.Default;
|
if (instance is Mod mod)
|
||||||
foreach (var modType in AvailableModTypes)
|
_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<string?> Apply();
|
|
||||||
|
|
||||||
public abstract Task<string?> 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<Type>? 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<string?> Apply();
|
||||||
|
|
||||||
|
public abstract Task<string?> 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<Type>? DependentMods { get; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,126 +7,125 @@ using System.Threading.Tasks;
|
|||||||
using Vestris.ResourceLib;
|
using Vestris.ResourceLib;
|
||||||
using ZuneModCore.Win32;
|
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);
|
AbstractUICollection optionsUi = new(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))
|
new AbstractTextBox("fileBox", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)),
|
||||||
{
|
};
|
||||||
new AbstractTextBox("fileBox", Environment.GetFolderPath(Environment.SpecialFolder.MyPictures)),
|
optionsUi.Title = "select background";
|
||||||
};
|
optionsUi.Subtitle = "CHOOSE A BACKGROUND IMAGE OF YOUR CHOICE.";
|
||||||
optionsUi.Title = "select background";
|
return optionsUi;
|
||||||
optionsUi.Subtitle = "CHOOSE A BACKGROUND IMAGE OF YOUR CHOICE.";
|
}
|
||||||
return optionsUi;
|
|
||||||
|
public override IReadOnlyList<Type>? DependentMods => null;
|
||||||
|
|
||||||
|
public override async Task<string?> 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<Type>? DependentMods => null;
|
FileInfo zsrDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneShellResources.dll"));
|
||||||
|
if (!zsrDllInfo.Exists)
|
||||||
public override async Task<string?> Apply()
|
|
||||||
{
|
{
|
||||||
string bgimgPath = ((AbstractTextBox)OptionsUI![0]).Value;
|
return $"The file '{zsrDllInfo.FullName}' does not exist.";
|
||||||
FileInfo bgimgInfo = new(bgimgPath);
|
}
|
||||||
if (!bgimgInfo.Exists)
|
|
||||||
|
// 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.";
|
// Load the RCDATA section
|
||||||
}
|
vi.Load(zsrDllInfo.FullName);
|
||||||
|
ResourceId? resId = vi.ResourceTypes.Find(id =>
|
||||||
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
|
// This must be wrapped in a try-catch block,
|
||||||
vi.Load(zsrDllInfo.FullName);
|
// otherwise resourcelib will error when attempting
|
||||||
ResourceId? resId = vi.ResourceTypes.Find(id =>
|
// to read the WAVE type (or any other non-default type).
|
||||||
|
try
|
||||||
{
|
{
|
||||||
// This must be wrapped in a try-catch block,
|
return id.ResourceType == Kernel32.ResourceTypes.RT_RCDATA;
|
||||||
// otherwise resourcelib will error when attempting
|
}
|
||||||
// to read the WAVE type (or any other non-default type).
|
catch
|
||||||
try
|
{
|
||||||
{
|
return false;
|
||||||
return id.ResourceType == Kernel32.ResourceTypes.RT_RCDATA;
|
}
|
||||||
}
|
});
|
||||||
catch
|
if (resId == null)
|
||||||
{
|
return $"Failed to locate image resources in Zune software.";
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (resId == null)
|
|
||||||
return $"Failed to locate image resources in Zune software.";
|
|
||||||
|
|
||||||
// Locate the image resource
|
// Locate the image resource
|
||||||
// TODO: Allow users to specify which background they want to replace
|
// TODO: Allow users to specify which background they want to replace
|
||||||
List<Resource> RCDATA = vi.Resources[resId];
|
List<Resource> RCDATA = vi.Resources[resId];
|
||||||
int idx = RCDATA.FindIndex(r => r.Name.Name == "FRAMEBACKGROUND06.PNG");
|
int idx = RCDATA.FindIndex(r => r.Name.Name == "FRAMEBACKGROUND06.PNG");
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return $"Failed to locate background image resource in Zune software.";
|
return $"Failed to locate background image resource in Zune software.";
|
||||||
GenericResource ogRes = (GenericResource)RCDATA[idx];
|
GenericResource ogRes = (GenericResource)RCDATA[idx];
|
||||||
|
|
||||||
// Replace image
|
// Replace image
|
||||||
byte[] data = await File.ReadAllBytesAsync(bgimgInfo.FullName);
|
byte[] data = await File.ReadAllBytesAsync(bgimgInfo.FullName);
|
||||||
BitmapResource newRes = new(IntPtr.Zero, IntPtr.Zero, resId, ogRes.Name, ogRes.Language, data.Length);
|
BitmapResource newRes = new(IntPtr.Zero, IntPtr.Zero, resId, ogRes.Name, ogRes.Language, data.Length);
|
||||||
newRes.Bitmap = new(data);
|
newRes.Bitmap = new(data);
|
||||||
RCDATA[idx] = newRes;
|
RCDATA[idx] = newRes;
|
||||||
File.Copy(zsrDllInfo.FullName, zsrDllTempPath, true);
|
File.Copy(zsrDllInfo.FullName, zsrDllTempPath, true);
|
||||||
newRes.SaveTo(zsrDllTempPath);
|
newRes.SaveTo(zsrDllTempPath);
|
||||||
}
|
}
|
||||||
File.Copy(zsrDllTempPath, zsrDllInfo.FullName, true);
|
File.Copy(zsrDllTempPath, zsrDllInfo.FullName, true);
|
||||||
|
|
||||||
return null;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (IOException)
|
||||||
public override Task<string?> Reset()
|
|
||||||
{
|
{
|
||||||
try
|
return $"Unable to replace '{zsrDllInfo.FullName}'. Verify that the Zune software is not running and try again.";
|
||||||
{
|
}
|
||||||
// Copy backup to application folder
|
catch (Exception ex)
|
||||||
File.Copy(Path.Combine(StorageDirectory, "ZuneShellResources.original.dll"),
|
{
|
||||||
Path.Combine(ZuneInstallDir, "ZuneShellResources.dll"), true);
|
Debug.WriteLine(ex.ToString());
|
||||||
|
return ex.Message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Task.FromResult<string?>(null);
|
public override Task<string?> Reset()
|
||||||
}
|
{
|
||||||
catch (Exception ex)
|
try
|
||||||
{
|
{
|
||||||
return Task.FromResult(ex.Message)!;
|
// Copy backup to application folder
|
||||||
}
|
File.Copy(Path.Combine(StorageDirectory, "ZuneShellResources.original.dll"),
|
||||||
|
Path.Combine(ZuneInstallDir, "ZuneShellResources.dll"), true);
|
||||||
|
|
||||||
|
return Task.FromResult<string?>(null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Task.FromResult(ex.Message)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,116 +4,115 @@ using System.Collections.Generic;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ZuneModCore.Win32;
|
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";
|
AbstractUICollection optionsUi = new(nameof(FeaturesOverrideMod))
|
||||||
|
|
||||||
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))
|
// 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("Apps", "Apps"),
|
||||||
new AbstractBoolean("Art", "Art"),
|
new AbstractBoolean("Art", "Art"),
|
||||||
new AbstractBoolean("Channels", "Channels"),
|
new AbstractBoolean("Channels", "Channels"),
|
||||||
new AbstractBoolean("FirstLaunchIntroVideo", "First Launch Intro Video"),
|
new AbstractBoolean("FirstLaunchIntroVideo", "First Launch Intro Video"),
|
||||||
new AbstractBoolean("Games", "Games"),
|
new AbstractBoolean("Games", "Games"),
|
||||||
new AbstractBoolean("Marketplace", "Marketplace"),
|
new AbstractBoolean("Marketplace", "Marketplace"),
|
||||||
new AbstractBoolean("MBRPreview", "[Marketplace] Media Preview"),
|
new AbstractBoolean("MBRPreview", "[Marketplace] Media Preview"),
|
||||||
new AbstractBoolean("MBRPurchase", "[Marketplace] Media Purchase"),
|
new AbstractBoolean("MBRPurchase", "[Marketplace] Media Purchase"),
|
||||||
new AbstractBoolean("MBRRental", "[Marketplace] Media Rental"),
|
new AbstractBoolean("MBRRental", "[Marketplace] Media Rental"),
|
||||||
new AbstractBoolean("Music", "Music"),
|
new AbstractBoolean("Music", "Music"),
|
||||||
new AbstractBoolean("MusicVideos", "Music Videos"),
|
new AbstractBoolean("MusicVideos", "Music Videos"),
|
||||||
new AbstractBoolean("Nowplaying", "Now Playing"),
|
new AbstractBoolean("Nowplaying", "Now Playing"),
|
||||||
new AbstractBoolean("NowplayingArt", "Now Playing Art"),
|
new AbstractBoolean("NowplayingArt", "Now Playing Art"),
|
||||||
new AbstractBoolean("Picks", "Picks"),
|
new AbstractBoolean("Picks", "Picks"),
|
||||||
new AbstractBoolean("Podcasts", "Podcasts"),
|
new AbstractBoolean("Podcasts", "Podcasts"),
|
||||||
new AbstractBoolean("QuickMixLocal", "Quick Mix (Local)"),
|
new AbstractBoolean("QuickMixLocal", "Quick Mix (Local)"),
|
||||||
new AbstractBoolean("QuickMixZMP", "Quick Mix (ZMP)"),
|
new AbstractBoolean("QuickMixZMP", "Quick Mix (ZMP)"),
|
||||||
new AbstractBoolean("Quickplay", "Quickplay"),
|
new AbstractBoolean("Quickplay", "Quickplay"),
|
||||||
new AbstractBoolean("Sign In Available", "Sign In"),
|
new AbstractBoolean("Sign In Available", "Sign In"),
|
||||||
new AbstractBoolean("Social", "Social"),
|
new AbstractBoolean("Social", "Social"),
|
||||||
new AbstractBoolean("SocialMarketplace", "Social Marketplace"),
|
new AbstractBoolean("SocialMarketplace", "Social Marketplace"),
|
||||||
new AbstractBoolean("SubscriptionFreeTracks", "Subscription Free Tracks"),
|
new AbstractBoolean("SubscriptionFreeTracks", "Subscription Free Tracks"),
|
||||||
new AbstractBoolean("Videos", "Videos"),
|
new AbstractBoolean("Videos", "Videos"),
|
||||||
};
|
};
|
||||||
optionsUi.Title = "select features";
|
optionsUi.Title = "select features";
|
||||||
optionsUi.Subtitle = "CHOOSE WHICH ZUNE FEATURES YOU WISH TO ENABLE.";
|
optionsUi.Subtitle = "CHOOSE WHICH ZUNE FEATURES YOU WISH TO ENABLE.";
|
||||||
return optionsUi;
|
return optionsUi;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<Type>? DependentMods => null;
|
public override IReadOnlyList<Type>? 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<string?> Apply() => await Apply(false);
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<string?> Apply(bool applyAll = false)
|
public override async Task<string?> Apply() => await Apply(false);
|
||||||
|
|
||||||
|
public async Task<string?> Apply(bool applyAll = false)
|
||||||
|
{
|
||||||
|
// Use user choices from AbstractUI
|
||||||
|
foreach (AbstractUIElement uiElem in OptionsUI!)
|
||||||
{
|
{
|
||||||
// Use user choices from AbstractUI
|
if (uiElem is AbstractBoolean boolElem && (boolElem.State || applyAll))
|
||||||
foreach (AbstractUIElement uiElem in OptionsUI!)
|
|
||||||
{
|
{
|
||||||
if (uiElem is AbstractBoolean boolElem && (boolElem.State || applyAll))
|
bool isSuccess = SetFeatureOverride(boolElem.Id, true);
|
||||||
|
if (!isSuccess)
|
||||||
{
|
{
|
||||||
bool isSuccess = SetFeatureOverride(boolElem.Id, true);
|
string? resetStatus = await Reset();
|
||||||
if (!isSuccess)
|
if (resetStatus != null)
|
||||||
{
|
{
|
||||||
string? resetStatus = await Reset();
|
// The reset failed as well, return both errors
|
||||||
if (resetStatus != null)
|
return "Failed to set registry keys. Unable to clean up partial overrides:\r\n" + resetStatus;
|
||||||
{
|
}
|
||||||
// The reset failed as well, return both errors
|
else
|
||||||
return "Failed to set registry keys. Unable to clean up partial overrides:\r\n" + resetStatus;
|
{
|
||||||
}
|
return "Failed to set registry keys. Automatically cleaned up partial changes.";
|
||||||
else
|
|
||||||
{
|
|
||||||
return "Failed to set registry keys. Automatically cleaned up partial changes.";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<string?> Reset()
|
return null;
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override async Task<string?> 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);
|
||||||
}
|
}
|
||||||
|
|||||||
+139
-143
@@ -5,167 +5,163 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
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";
|
AbstractUICollection optionsUi = new(nameof(MbidLocatorMod))
|
||||||
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[]
|
|
||||||
{
|
{
|
||||||
".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<Type>? DependentMods => null;
|
||||||
|
|
||||||
public override string Title => "MusicBrainz ID Locator";
|
public override async Task<string?> 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 " +
|
// Verify that the folder exists
|
||||||
"software can use it to show additional information provided by Community Webservices.\r\n" +
|
DirectoryInfo folder = new(folderPath);
|
||||||
"Note that this will only have an effect if you have used MusicBrainz Picard on your music library.";
|
if (!folder.Exists)
|
||||||
|
|
||||||
public override string Author => "Joshua \"Yoshi\" Askharoun";
|
|
||||||
|
|
||||||
public override AbstractUICollection? GetDefaultOptionsUI()
|
|
||||||
{
|
{
|
||||||
AbstractUICollection optionsUi = new(nameof(MbidLocatorMod))
|
return $"The folder '{folder.FullName}' does not exist.";
|
||||||
{
|
|
||||||
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 IReadOnlyList<Type>? DependentMods => null;
|
foreach (FileInfo file in folder.GetFiles("*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
|
||||||
|
|
||||||
public override async Task<string?> Apply()
|
|
||||||
{
|
{
|
||||||
// Use user choices from AbstractUI
|
try
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
return $"The folder '{folder.FullName}' does not exist.";
|
if (KNOWN_EXTS.Contains(file.Extension))
|
||||||
}
|
UpdateMbidInFile(file);
|
||||||
|
|
||||||
foreach (FileInfo file in folder.GetFiles("*.*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (KNOWN_EXTS.Contains(file.Extension))
|
|
||||||
UpdateMbidInFile(file);
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
System.Diagnostics.Debug.WriteLine(file);
|
System.Diagnostics.Debug.WriteLine(file);
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
errorString += ex.Message + "\r\n";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
if (errorString.Length != 0)
|
|
||||||
return errorString;
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override Task<string?> Reset()
|
|
||||||
{
|
|
||||||
return Task.FromResult<string?>(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
|
errorString += ex.Message + "\r\n";
|
||||||
.FirstOrDefault(cd => cd.Name == ZUNE_ALBUM_ARTIST_MEDIA_ID_NAME);
|
continue;
|
||||||
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<TagLib.Id3v2.PrivateFrame>();
|
|
||||||
|
|
||||||
// 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<string?> Reset()
|
||||||
|
{
|
||||||
|
return Task.FromResult<string?>(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<TagLib.Id3v2.PrivateFrame>();
|
||||||
|
|
||||||
|
// 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,92 +4,91 @@ using System.Diagnostics;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
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<string?> 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 =>
|
try
|
||||||
"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<string?> Apply()
|
|
||||||
{
|
{
|
||||||
// Verify that ZuneEncEng.dll exists
|
// Verify that the DLL is from v4.8 (other versions not tested)
|
||||||
FileInfo zeeDllInfo = new(Path.Combine(ZuneInstallDir, "ZuneEncEng.dll"));
|
var zeeDllVersion = FileVersionInfo.GetVersionInfo(zeeDllInfo.FullName);
|
||||||
if (!zeeDllInfo.Exists)
|
if (zeeDllVersion is null || zeeDllVersion.FileMajorPart != 4 || zeeDllVersion.FileMinorPart != 8)
|
||||||
return $"The file '{zeeDllInfo.FullName}' does not exist.";
|
return "This mod has not been tested on versions earlier than 4.8.";
|
||||||
|
|
||||||
// Make a backup if it doesn't already exist
|
// Open the file
|
||||||
FileInfo zeeDllBackupInfo = new(Path.Combine(StorageDirectory, "ZuneEncEng.original.dll"));
|
using (FileStream zeeDll = zeeDllInfo.Open(FileMode.Open))
|
||||||
if (!zeeDllBackupInfo.Exists)
|
using (BinaryWriter zeeDllWriter = new(zeeDll))
|
||||||
File.Copy(zeeDllInfo.FullName, zeeDllBackupInfo.FullName, true);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// Verify that the DLL is from v4.8 (other versions not tested)
|
// Patch ZuneEncEng.dll to point to the local WMVCORE.DLL
|
||||||
var zeeDllVersion = FileVersionInfo.GetVersionInfo(zeeDllInfo.FullName);
|
zeeDllWriter.Seek(ZUNEENCENG_WMVCORA_OFFSET, SeekOrigin.Begin);
|
||||||
if (zeeDllVersion is null || zeeDllVersion.FileMajorPart != 4 || zeeDllVersion.FileMinorPart != 8)
|
zeeDllWriter.Write((byte)'a');
|
||||||
return "This mod has not been tested on versions earlier than 4.8.";
|
|
||||||
|
|
||||||
// Open the file
|
zeeDllWriter.Flush();
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
public override Task<string?> Reset()
|
|
||||||
{
|
{
|
||||||
var zeeDllPath = Path.Combine(ZuneInstallDir, "ZuneEncEng.dll");
|
return $"Unable to replace '{zeeDllInfo.FullName}'. Verify that the Zune software is not running and try again.";
|
||||||
try
|
}
|
||||||
{
|
catch (Exception ex)
|
||||||
// Copy backup to application folder
|
{
|
||||||
File.Copy(Path.Combine(StorageDirectory, "ZuneEncEng.original.dll"), zeeDllPath, true);
|
return ex.Message;
|
||||||
|
|
||||||
// Delete WMVCore files
|
|
||||||
File.Delete(Path.Combine(ZuneInstallDir, "wmvcora.dll"));
|
|
||||||
File.Delete(Path.Combine(ZuneInstallDir, "wmvcore.dll"));
|
|
||||||
|
|
||||||
return Task.FromResult<string?>(null);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
return Task.FromResult(ex.Message)!;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IReadOnlyList<Type>? DependentMods => null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override Task<string?> 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<string?>(null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
return Task.FromResult(ex.Message)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IReadOnlyList<Type>? DependentMods => null;
|
||||||
}
|
}
|
||||||
|
|||||||
+233
-255
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,6 @@
|
|||||||
namespace ZuneModCore.Services
|
namespace ZuneModCore.Services;
|
||||||
|
|
||||||
|
public interface IModCoreConfig
|
||||||
{
|
{
|
||||||
public interface IModCoreConfig
|
string ZuneInstallDir { get; }
|
||||||
{
|
|
||||||
string ZuneInstallDir { get; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
+120
-128
@@ -1,151 +1,143 @@
|
|||||||
using System;
|
namespace ZuneModCore.Win32;
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
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;
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
using System.Diagnostics;
|
struct RM_UNIQUE_PROCESS
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
static public class FileUtil
|
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
public int dwProcessId;
|
||||||
struct RM_UNIQUE_PROCESS
|
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);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Find out what process(es) have a lock on the specified file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="path">Path of the file.</param>
|
||||||
|
/// <returns>Processes locking the file</returns>
|
||||||
|
/// <remarks>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)
|
||||||
|
///
|
||||||
|
/// </remarks>
|
||||||
|
static public List<Process> WhoIsLocking(string path)
|
||||||
|
{
|
||||||
|
string key = Guid.NewGuid().ToString();
|
||||||
|
List<Process> 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;
|
const int ERROR_MORE_DATA = 234;
|
||||||
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
|
uint pnProcInfo = 0,
|
||||||
}
|
lpdwRebootReasons = RmRebootReasonNone;
|
||||||
|
|
||||||
const int RmRebootReasonNone = 0;
|
string[] resources = [path]; // Just checking on one resource.
|
||||||
const int CCH_RM_MAX_APP_NAME = 255;
|
|
||||||
const int CCH_RM_MAX_SVC_NAME = 63;
|
|
||||||
|
|
||||||
enum RM_APP_TYPE
|
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
|
||||||
{
|
|
||||||
RmUnknownApp = 0,
|
|
||||||
RmMainWindow = 1,
|
|
||||||
RmOtherWindow = 2,
|
|
||||||
RmService = 3,
|
|
||||||
RmExplorer = 4,
|
|
||||||
RmConsole = 5,
|
|
||||||
RmCritical = 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
if (res != 0) throw new Exception("Could not register resource.");
|
||||||
struct RM_PROCESS_INFO
|
|
||||||
{
|
|
||||||
public RM_UNIQUE_PROCESS Process;
|
|
||||||
|
|
||||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCH_RM_MAX_APP_NAME + 1)]
|
//Note: there's a race condition here -- the first call to RmGetList() returns
|
||||||
public string strAppName;
|
// 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)]
|
if (res == ERROR_MORE_DATA)
|
||||||
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);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Find out what process(es) have a lock on the specified file.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path">Path of the file.</param>
|
|
||||||
/// <returns>Processes locking the file</returns>
|
|
||||||
/// <remarks>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)
|
|
||||||
///
|
|
||||||
/// </remarks>
|
|
||||||
static public List<Process> WhoIsLocking(string path)
|
|
||||||
{
|
|
||||||
uint handle;
|
|
||||||
string key = Guid.NewGuid().ToString();
|
|
||||||
List<Process> processes = new List<Process>();
|
|
||||||
|
|
||||||
int res = RmStartSession(out handle, 0, key);
|
|
||||||
if (res != 0) throw new Exception("Could not begin restart session. Unable to determine file locker.");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
const int ERROR_MORE_DATA = 234;
|
// Create an array to store the process results
|
||||||
uint pnProcInfoNeeded = 0,
|
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
|
||||||
pnProcInfo = 0,
|
pnProcInfo = pnProcInfoNeeded;
|
||||||
lpdwRebootReasons = RmRebootReasonNone;
|
|
||||||
|
|
||||||
string[] resources = new string[] { path }; // Just checking on one resource.
|
// Get the list
|
||||||
|
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
|
||||||
res = RmRegisterResources(handle, (uint)resources.Length, resources, 0, null, 0, null);
|
if (res == 0)
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// Create an array to store the process results
|
processes = new List<Process>((int)pnProcInfo);
|
||||||
RM_PROCESS_INFO[] processInfo = new RM_PROCESS_INFO[pnProcInfoNeeded];
|
|
||||||
pnProcInfo = pnProcInfoNeeded;
|
|
||||||
|
|
||||||
// Get the list
|
// Enumerate all of the results and add them to the
|
||||||
res = RmGetList(handle, out pnProcInfoNeeded, ref pnProcInfo, processInfo, ref lpdwRebootReasons);
|
// list to be returned
|
||||||
if (res == 0)
|
for (int i = 0; i < pnProcInfo; i++)
|
||||||
{
|
{
|
||||||
processes = new List<Process>((int)pnProcInfo);
|
try
|
||||||
|
|
||||||
// Enumerate all of the results and add them to the
|
|
||||||
// list to be returned
|
|
||||||
for (int i = 0; i < pnProcInfo; i++)
|
|
||||||
{
|
{
|
||||||
try
|
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
|
||||||
{
|
|
||||||
processes.Add(Process.GetProcessById(processInfo[i].Process.dwProcessId));
|
|
||||||
}
|
|
||||||
// catch the error -- in case the process is no longer running
|
|
||||||
catch (ArgumentException) { }
|
|
||||||
}
|
}
|
||||||
|
// 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
|
else if (res != 0) throw new Exception("Could not list processes locking resource. Failed to get size of result.");
|
||||||
{
|
|
||||||
RmEndSession(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
return processes;
|
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
RmEndSession(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return processes;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,73 +1,65 @@
|
|||||||
using Microsoft.Win32;
|
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 static bool CurrentUserSetBoolValue(string key, string name, bool value)
|
||||||
|
|
||||||
public class RegEdit
|
|
||||||
{
|
{
|
||||||
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)
|
regKey.SetValue(name, value, RegistryValueKind.DWord);
|
||||||
{
|
regKey.Close();
|
||||||
RegistryKey? regKey = Registry.CurrentUser.OpenSubKey(key, true);
|
regKey.Dispose();
|
||||||
if (regKey == null)
|
|
||||||
regKey = Registry.CurrentUser.CreateSubKey(key, true);
|
|
||||||
|
|
||||||
regKey.SetValue(name, value, RegistryValueKind.DWord);
|
// Read the key to make sure it was set properly
|
||||||
regKey.Close();
|
if (CurrentUserGetBoolValue(key, name) == null)
|
||||||
regKey.Dispose();
|
return false;
|
||||||
|
return true;
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,123 +1,119 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Security.AccessControl;
|
using System.Security.AccessControl;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using Vanara.PInvoke;
|
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,
|
retVal = AdvApi32.OpenProcessToken(hproc,
|
||||||
AdvApi32.TokenAccess.TOKEN_ADJUST_PRIVILEGES | AdvApi32.TokenAccess.TOKEN_QUERY, out var htok);
|
AdvApi32.TokenAccess.TOKEN_ADJUST_PRIVILEGES | AdvApi32.TokenAccess.TOKEN_QUERY, out var htok);
|
||||||
|
|
||||||
retVal = AdvApi32.LookupPrivilegeValue(null, privilege, out var tpLuid);
|
retVal = AdvApi32.LookupPrivilegeValue(null, privilege, out var tpLuid);
|
||||||
AdvApi32.TOKEN_PRIVILEGES tp = new(tpLuid, AdvApi32.PrivilegeAttributes.SE_PRIVILEGE_ENABLED);
|
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;
|
return retVal;
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
public static bool RemovePrivilege(string privilege)
|
catch
|
||||||
{
|
{
|
||||||
try
|
throw;
|
||||||
{
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<LangVersion>9</LangVersion>
|
<LangVersion>12</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<Platforms>AnyCPU;x64;x86</Platforms>
|
<Platforms>AnyCPU;x64;x86</Platforms>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
|||||||
Reference in New Issue
Block a user