Create hosts editor and add metaservices

This commit is contained in:
Yoshi Askharoun
2025-09-08 15:41:56 -04:00
parent 5a1e18fa93
commit ba8494fa94
2 changed files with 160 additions and 51 deletions
+39 -51
View File
@@ -10,10 +10,10 @@ using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using ZuneModCore.Services; using ZuneModCore.Services;
using ZuneModCore.Win32;
using static ZuneModCore.Mods.FeaturesOverrideMod; using static ZuneModCore.Mods.FeaturesOverrideMod;
namespace ZuneModCore.Mods; namespace ZuneModCore.Mods;
@@ -146,6 +146,18 @@ public partial class WebservicesMod(ModMetadata metadata, IModCoreConfig modConf
await RecordAppliedMethod(ApplicationMethod.BinaryPatch); await RecordAppliedMethod(ApplicationMethod.BinaryPatch);
} }
// Also set up the metaservices hosts
var ipAddress = IPAddress.Parse("135.181.88.32");
IEnumerable<string> hostnames = [
"toc.music.metaservices.microsoft.com",
"info.music.metaservices.microsoft.com",
"images.metaservices.microsoft.com",
"redir.metaservices.microsoft.com",
"windowsmedia.com",
];
var newEntries = hostnames.Select(h => new HostsEntry(ipAddress, h));
await HostsEditor.AddOrUpdateHostnamesWithIPAsync(newEntries);
return errorMessage; return errorMessage;
} }
@@ -303,57 +315,36 @@ public partial class WebservicesMod(ModMetadata metadata, IModCoreConfig modConf
return null; return null;
} }
private static async Task<string?> AddEntriesToHostsFile(string newHost) private async Task<string?> AddEntriesToHostsFile(string newHost)
{ {
var hostEntry = await Dns.GetHostEntryAsync(newHost); List<HostsEntry> newEntries = new(_knownSubdomains.Length);
var ipAddress = hostEntry?.AddressList?.FirstOrDefault();
if (ipAddress is null) foreach (var subdomain in _knownSubdomains.Select(e => e.subdomain))
{ {
return $"Failed to resolve host '{newHost}' to an IP address."; var newHostname = GetDomainName(subdomain, newHost);
IPAddress? ipAddress;
try
{
var dnsEntry = await Dns.GetHostEntryAsync(newHostname);
ipAddress = dnsEntry?.AddressList?.FirstOrDefault();
if (ipAddress is null)
{
log.LogError("Failed to resolve host '{hostname}' to an IP address.", newHostname);
continue;
}
}
catch (Exception ex)
{
log.Log(LogLevel.Error, ex, "Failed to resolve host '{hostname}' to an IP address.", newHostname);
continue;
}
var oldHostname = GetDomainName(subdomain, "zune.net");
newEntries.Add(new(ipAddress, oldHostname));
} }
var hostsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), @"drivers\etc\hosts"); await HostsEditor.AddOrUpdateHostnamesWithIPAsync(newEntries);
var hostLines = (await File.ReadAllLinesAsync(hostsPath)).ToList();
var domains = _knownSubdomains
.Select(e => GetDomainName(e.subdomain, "zune.net"))
.ToHashSet();
// Update existing entries
Regex rx = HostsEntryRegex();
for (int l = 0; l < hostLines.Count; l++)
{
var line = hostLines[l].Trim();
// Ignore comments and empty lines
if (line.Length <= 0 || line[0] is '#')
continue;
var match = rx.Match(line);
if (!match.Success)
continue;
var domain = match.Groups["name"].Value;
// Ignore entries unrelated to Zune
if (!domain.EndsWith("zune.net"))
continue;
// Reconstruct entry with new IP address
hostLines[l] = $"{ipAddress} {domain}";
// Track that we handled this domain
domains.Remove(domain);
}
// Add any entries that didn't already exist
if (domains.Count > 0)
{
foreach (var domain in domains)
hostLines.Add($"{ipAddress} {domain}");
}
await File.WriteAllLinesAsync(hostsPath, hostLines);
return null; return null;
} }
@@ -441,8 +432,5 @@ public partial class WebservicesMod(ModMetadata metadata, IModCoreConfig modConf
return Enum.Parse<ApplicationMethod>(methodText); return Enum.Parse<ApplicationMethod>(methodText);
} }
[GeneratedRegex(@"(?<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(?<name>[\w.-]+?)(\s+|$)")]
private static partial Regex HostsEntryRegex();
private enum ApplicationMethod { None, BinaryPatch, HostsEntries } private enum ApplicationMethod { None, BinaryPatch, HostsEntries }
} }
+121
View File
@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace ZuneModCore.Win32;
public static partial class HostsEditor
{
private static readonly string _hostsPath = Environment.ExpandEnvironmentVariables(@"%SystemRoot%\System32\drivers\etc\hosts");
[GeneratedRegex(@"(?<ip>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\s+(?<name>[\w.-]+?)(\s+|$)")]
private static partial Regex HostsEntryRegex();
public static async Task AddOrUpdateHostnamesWithIPAsync(IEnumerable<HostsEntry> newEntries)
{
var unhandledEntries = newEntries
.ToDictionary(e => e.Hostname, e => e.IPAddress);
var hostLines = await ReadHostsFileAsync();
// Update existing entries
Regex rx = HostsEntryRegex();
for (int l = 0; l < hostLines.Count; l++)
{
var line = hostLines[l].Trim();
// Ignore comments and empty lines
if (line.Length <= 0 || line[0] is '#')
continue;
var match = rx.Match(line);
if (!match.Success)
continue;
var hostname = match.Groups["name"].Value;
var ip = match.Groups["ip"].Value;
// Ignore entries we've already handled
if (!unhandledEntries.TryGetValue(hostname, out var newIP))
continue;
// Avoid changing lines when we don't have to. This
// preserves trivia such as whitespace and comments when possible
if (IPAddress.Parse(ip) == newIP)
continue;
// Reconstruct entry with new IP address
hostLines[l] = $"{newIP} {hostname}";
// Track that we handled this domain
unhandledEntries.Remove(hostname);
}
// Add any entries that didn't already exist
if (unhandledEntries.Count > 0)
{
foreach (var (hostname, ip) in unhandledEntries)
hostLines.Add($"{ip} {hostname}");
}
await WriteHostsFileAsync(hostLines);
}
private static async Task<List<string>> ReadHostsFileAsync()
{
var lines = await File.ReadAllLinesAsync(_hostsPath);
return lines.ToList();
}
private static async Task WriteHostsFileAsync(List<string> lines)
{
await File.WriteAllLinesAsync(_hostsPath, lines);
}
}
public class HostsEntry(IPAddress ipAddress, string hostname, string? originalString = null)
: IEquatable<HostsEntry>
{
private IPAddress _ipAddress = ipAddress;
private string _hostname = hostname;
private string? _originalString = originalString;
public IPAddress IPAddress
{
get => _ipAddress;
set
{
_originalString = null;
_ipAddress = value;
}
}
public string Hostname
{
get => _hostname;
set
{
_originalString = null;
_hostname = value;
}
}
public bool Equals(HostsEntry? other)
{
if (other is null)
return false;
return IPAddress.Equals(other.IPAddress)
&& Hostname.Equals(other.Hostname, StringComparison.InvariantCulture);
}
public override bool Equals(object obj) => Equals(obj as HostsEntry);
public override string ToString() => _originalString ?? $"{IPAddress} {Hostname}";
public override int GetHashCode() => (IPAddress, Hostname).GetHashCode();
}