分离玩家信息插件,以及更新检测

This commit is contained in:
lanyi 2021-10-22 08:54:04 +02:00
parent 2a96f8efac
commit 08700abd4f
25 changed files with 860 additions and 234 deletions

View File

@ -1,4 +1,5 @@
<Window x:Class="AnotherReplayReader.About"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@ -6,29 +7,76 @@
xmlns:local="clr-namespace:AnotherReplayReader"
mc:Ignorable="d"
Title="About"
Height="301.893"
Width="429">
<Grid Margin="0,0,-8,-59">
Height="420"
Width="450"
WindowStartupLocation="CenterOwner"
Loaded="OnAboutWindowLoaded">
<Grid Margin="0,0,0,-50">
<Grid.RowDefinitions>
<RowDefinition Height="280"/>
<RowDefinition Height="50"/>
<RowDefinition Height="420" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox x:Name="_idBox" HorizontalAlignment="Left" Margin="54,23,0,10" TextWrapping="Wrap" Text="TextBox" Width="300" BorderBrush="White" RenderTransformOrigin="0.497,0.438" Grid.Row="1"/>
<TextBlock x:Name="textBlock" Margin="36,10,36,32" TextWrapping="Wrap" Grid.RowSpan="2">
<Run Text="【自动录像机0.7】"/><LineBreak/>
<Run Text="本工具目前额外支持以下 Mod 的读取AR、日冕、大蜗牛、Ins、FS、WOP、Eisenreich、TNW"/><LineBreak/>
<Run Text="当 Mod 增加新的阵营时,会出现未知阵营和阵营错乱现象"/><LineBreak/>
<Run Text="有任何问题可以先去找苏醒或节操"/><LineBreak/>
<Run Text="解析录像的代码主要来源于 louisdx 的研究:"/><LineBreak/>
<Hyperlink NavigateUri="https://github.com/louisdx/cnc-replayreaders"><Run Text="https://github.com/louisdx/cnc-replayreaders"/></Hyperlink><LineBreak/>
<Run Text="解析 Big 的代码来源于" />
<Hyperlink NavigateUri="https://github.com/Qibbi"><Run Text="Jana Mohn" /></Hyperlink>
<Run Text="的 TechnologyAssembler" /><LineBreak />
<Run Text="解析 Tga 的代码来源于Pfim"/><LineBreak/>
<Hyperlink NavigateUri="https://github.com/nickbabcock/Pfim"><Run Text="https://github.com/nickbabcock/Pfim"/></Hyperlink><LineBreak/>
<Run Text="欢迎来到红警3吧"/><LineBreak/>
<Hyperlink NavigateUri="https://tieba.baidu.com/f?kw=%BA%EC%BE%AF3"><Run Text="https://tieba.baidu.com/f?kw=%BA%EC%BE%AF3"/></Hyperlink><LineBreak/>
<Run Text="ARMod 群号161660710"/></TextBlock>
<TextBlock x:Name="textBlock1" HorizontalAlignment="Left" Margin="10,23,0,11" TextWrapping="Wrap" Width="60" Grid.Row="1"><Run Text="ID"/><LineBreak/><Run/></TextBlock>
<StackPanel Grid.Row="0"
Orientation="Vertical">
<StackPanel x:Name="_updatePanel"
Orientation="Vertical"
Visibility="Collapsed">
<TextBlock x:Name="_updateInfo"
Margin="24,16">
<Run FontWeight="Bold">已经有新版本了呢!</Run>
<LineBreak />
</TextBlock>
<Separator />
</StackPanel>
<StackPanel Orientation="Vertical"
Margin="24,16">
<TextBlock TextWrapping="Wrap">
<Run Text="{Binding Source={x:Static local:App.NameWithVersion},
Mode=OneWay,
StringFormat={}【{0}】}" />
<LineBreak />
这个工具目前额外支持以下 Mod 的读取AR、日冕、大蜗牛、Ins、FS、WOP、Eisenreich、TNW<LineBreak />
有任何问题可以先去找苏醒或节操问题(<LineBreak />
解析录像的代码主要来源于
<Hyperlink NavigateUri="https://www.gamereplays.org/community/index.php?showtopic=706067">R Schneider 的研究</Hyperlink>
以及 BoolBada 的
<Hyperlink NavigateUri="https://github.com/forcecore/KWReplayAutoSaver">KWReplayAutoSaver</Hyperlink>
<LineBreak />
解析 Big 的代码来源于
<Hyperlink NavigateUri="https://github.com/Qibbi">Jana Mohn</Hyperlink>
的 TechnologyAssembler<LineBreak />
解析 Tga 的代码来源于
<Hyperlink NavigateUri="https://github.com/nickbabcock/Pfim">Pfim</Hyperlink><LineBreak />
使用了
<Hyperlink NavigateUri="https://github.com/2881099/NPinyin">NPinyin</Hyperlink>
以支持按照拼音来查询信息<LineBreak />
APM 图表是通过
<Hyperlink NavigateUri="https://oxyplot.github.io/">OxyPlot</Hyperlink>
画出来的<LineBreak />
<LineBreak />
欢迎来到红警3吧
<Hyperlink NavigateUri="https://tieba.baidu.com/f?kw=%BA%EC%BE%AF3">https://tieba.baidu.com/ra3</Hyperlink><LineBreak />
<Run Text="ARMod 群号161660710" />
</TextBlock>
<CheckBox x:Name="_checkForUpdates"
Margin="0,16,0,0"
Content="自动检查更新"
Checked="OnCheckForUpdatesCheckedChanged"
Unchecked="OnCheckForUpdatesCheckedChanged" />
</StackPanel>
</StackPanel>
<DockPanel x:Name="_bottom"
Grid.Row="1">
<Label DockPanel.Dock="Left"
VerticalAlignment="Center"
Content="ID" />
<TextBox x:Name="_idBox"
VerticalAlignment="Center"
VerticalContentAlignment="Center"
TextWrapping="Wrap"
Text="TextBox"
BorderBrush="White" />
</DockPanel>
</Grid>
</Window>

View File

@ -1,22 +1,80 @@
using System.Diagnostics;
using AnotherReplayReader.Utils;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Documents;
namespace AnotherReplayReader
{
/// <summary>
/// About.xaml 的交互逻辑
/// </summary>
public partial class About : Window
internal partial class About : Window
{
public About()
private readonly Cache _cache;
private readonly UpdateCheckerVersionData? _updateData;
public About(Cache cache)
{
_cache = cache;
InitializeComponent();
_idBox.Text = Auth.Id;
}
public About(Cache cache, UpdateCheckerVersionData updateData)
{
_cache = cache;
_updateData = updateData;
InitializeComponent();
}
private async void OnAboutWindowLoaded(object sender, RoutedEventArgs e)
{
foreach (var hyperlink in this.FindVisualChildren<Hyperlink>())
{
hyperlink.RequestNavigate += OnHyperlinkRequestNavigate;
}
await _cache.Initialization;
_checkForUpdates.IsChecked = _cache.GetOrDefault(UpdateChecker.CheckForUpdatesKey, false);
var data = _updateData
?? _cache.GetOrDefault<UpdateCheckerVersionData?>(UpdateChecker.CachedDataKey, null);
if (data is { } updateData && updateData.IsNewVersion())
{
_updatePanel.Visibility = Visibility.Visible;
_updateInfo.Inlines.Add(updateData.Description);
_updateInfo.Inlines.Add(new LineBreak());
updateData.Urls.Select(u =>
{
var h = new Hyperlink();
h.Inlines.Add(u);
h.NavigateUri = new(u);
h.RequestNavigate += OnHyperlinkRequestNavigate;
return h;
});
}
_ = Auth.Id.ContinueWith(t => Dispatcher.Invoke(() =>
{
if (t.Result is { } id)
{
_idBox.Text = id;
}
else
{
_bottom.Visibility = Visibility.Collapsed;
}
}));
}
private void OnHyperlinkRequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
{
Process.Start(e.Uri.ToString());
}
private async void OnCheckForUpdatesCheckedChanged(object sender, RoutedEventArgs e)
{
_cache.Set(UpdateChecker.CheckForUpdatesKey, _checkForUpdates.IsChecked is true);
await _cache.Save();
}
}
}

View File

@ -3,6 +3,7 @@
<OutputType>WinExe</OutputType>
<TargetFramework>net461</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<PublishUrl>publish\</PublishUrl>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWPF>true</UseWPF>
@ -13,6 +14,7 @@
<OutputPath>bin\$(Configuration)\</OutputPath>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<Compile Remove="publish\**" />
@ -20,9 +22,6 @@
<None Remove="publish\**" />
<Page Remove="publish\**" />
</ItemGroup>
<ItemGroup>
<None Remove="ReplayAutoSaver.cs~RF188cd1d1.TMP" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Web" />
<Reference Include="TechnologyAssembler.Core">
@ -31,14 +30,15 @@
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="ILMerge" Version="3.0.41" />
<PackageReference Include="ILMerge.MSBuild.Task" Version="1.0.7" />
<PackageReference Include="NPinyin.Core" Version="3.0.0" />
<PackageReference Include="OxyPlot.Wpf" Version="2.1.0" />
<PackageReference Include="Pfim" Version="0.10.1" />
<PackageReference Include="System.Collections.Immutable" Version="5.0.0" />
<PackageReference Include="System.Text.Json" Version="5.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AnotherReplayReader.PluginSystem\AnotherReplayReader.PluginSystem.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Settings.Designer.cs">
<DesignTimeSharedInput>True</DesignTimeSharedInput>
@ -52,4 +52,16 @@
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<Target Name="CustomAfterBuild" AfterTargets="Build">
<ItemGroup>
<_FilesToMove Include="$(OutputPath)*.dll"/>
</ItemGroup>
<Message Text="_FilesToMove: @(_FilesToMove->'%(Filename)%(Extension)')" Importance="high"/>
<Message Text="DestFiles:
@(_FilesToMove->'$(OutputPath)$(ProjectName)Data\%(Filename)%(Extension)')"
Importance="high"/>
<Move SourceFiles="@(_FilesToMove)"
DestinationFiles=
"@(_FilesToMove->'$(OutputPath)$(ProjectName)Data\%(Filename)%(Extension)')"/>
</Target>
</Project>

View File

@ -3,7 +3,11 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30907.101
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnotherReplayReader", "AnotherReplayReader.csproj", "{A54AEAB3-D99C-4E29-8C47-3DFD5B1A0FDE}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnotherReplayReader", "AnotherReplayReader.csproj", "{A54AEAB3-D99C-4E29-8C47-3DFD5B1A0FDE}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AnotherReplayReader.PlayerIdentity", "..\AnotherReplayReader.PlayerIdentity\AnotherReplayReader.PlayerIdentity.csproj", "{78678E57-CBA2-46E4-B764-4CB6E66EF156}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnotherReplayReader.PluginSystem", "..\AnotherReplayReader.PluginSystem\AnotherReplayReader.PluginSystem.csproj", "{0730D7D0-64A3-4F3F-8D8C-46E7676C659F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -15,6 +19,14 @@ Global
{A54AEAB3-D99C-4E29-8C47-3DFD5B1A0FDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A54AEAB3-D99C-4E29-8C47-3DFD5B1A0FDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A54AEAB3-D99C-4E29-8C47-3DFD5B1A0FDE}.Release|Any CPU.Build.0 = Release|Any CPU
{78678E57-CBA2-46E4-B764-4CB6E66EF156}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78678E57-CBA2-46E4-B764-4CB6E66EF156}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78678E57-CBA2-46E4-B764-4CB6E66EF156}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78678E57-CBA2-46E4-B764-4CB6E66EF156}.Release|Any CPU.Build.0 = Release|Any CPU
{0730D7D0-64A3-4F3F-8D8C-46E7676C659F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0730D7D0-64A3-4F3F-8D8C-46E7676C659F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0730D7D0-64A3-4F3F-8D8C-46E7676C659F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0730D7D0-64A3-4F3F-8D8C-46E7676C659F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -112,7 +112,7 @@ namespace AnotherReplayReader.Apm
{
string GetIpAndName(Player player) => player.IsComputer
? "这是 AI"
: identity.QueryRealNameAndIP(player.PlayerIp);
: identity.QueryRealNameAndIP(player.PlayerIp) ?? string.Empty;
dataList.Add(new("局域网 IP", plotter.Players.Select(GetIpAndName)));
}
apmRowIndex = dataList.Count;

View File

@ -11,6 +11,7 @@
Title="APM"
Height="550"
Width="800"
WindowStartupLocation="CenterOwner"
Loaded="OnApmWindowLoaded">
<Grid Margin="0">
<Grid.RowDefinitions>

View File

@ -83,7 +83,7 @@ namespace AnotherReplayReader
}
catch (Exception e)
{
MessageBox.Show($"加载录像信息失败:\r\n{e}");
MessageBox.Show(this, $"加载录像信息失败:\r\n{e}");
}
}

View File

@ -1,4 +1,7 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
@ -10,8 +13,25 @@ namespace AnotherReplayReader
public partial class App : Application
{
private int _isInException = 0;
public const string Version = "0.7";
public const string Name = "自动录像机";
public const string NameWithVersion = Name + " v" + Version;
private static readonly Lazy<string> _libsFolder = new(GetLibraryFolder, LazyThreadSafetyMode.PublicationOnly);
public static string LibsFolder => _libsFolder.Value;
public App()
{
static Assembly? LoadFromLibsFolder(object sender, ResolveEventArgs args)
{
var assemblyPath = Path.Combine(LibsFolder, new AssemblyName(args.Name).Name + ".dll");
return File.Exists(assemblyPath)
? Assembly.LoadFrom(assemblyPath)
: null;
}
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(LoadFromLibsFolder);
AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) =>
{
if (Interlocked.Increment(ref _isInException) > 1)
@ -21,7 +41,7 @@ namespace AnotherReplayReader
Dispatcher.Invoke(() =>
{
const string message = "哎呀呀,出现了一些无法处理的问题,只能退出了。要不要尝试保存一下日志文件呢?";
var choice = MessageBox.Show($"{message}\r\n{eventArgs.ExceptionObject}", "自动录像机", MessageBoxButton.YesNo);
var choice = MessageBox.Show($"{message}\r\n{eventArgs.ExceptionObject}", Name, MessageBoxButton.YesNo);
if (choice == MessageBoxResult.Yes)
{
Debug.Instance.RequestSave();
@ -29,5 +49,27 @@ namespace AnotherReplayReader
});
};
}
private static string GetLibraryFolder() => Path.Combine(GetExecutableFolder(), nameof(AnotherReplayReader) + "Data");
private static string GetExecutableFolder()
{
char[]? buffer = null;
uint result;
do
{
buffer = new char[(buffer?.Length ?? 128) * 2];
result = GetModuleFileNameW(IntPtr.Zero, buffer, buffer.Length);
if (result is 0)
{
throw new Exception("Failed to retrieve executable name");
}
}
while (result >= buffer.Length);
return Path.GetDirectoryName(new(buffer, 0, Array.IndexOf(buffer, '\0')));
}
[DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern uint GetModuleFileNameW(IntPtr module, char[] fileName, int size);
}
}

88
Auth.cs
View File

@ -1,95 +1,31 @@
using Microsoft.Win32;
using AnotherReplayReader.PluginSystem;
using AnotherReplayReader.Utils;
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace AnotherReplayReader
{
internal static class Auth
{
public static string? Id { get; }
private static readonly TaskCompletionSource<Task<string?>> _source = new();
public static Task<string?> Id { get; } = _source.Task.Unwrap();
static Auth()
public static void LoadPlugin(IPlugin plugin)
{
Id = null;
string? windowsID;
try
{
using var view64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
using var winNt = view64?.OpenSubKey(@"Software\Microsoft\Windows NT\CurrentVersion", false);
windowsID = winNt?.GetValue("ProductId") as string;
}
catch
{
return;
}
string? randomKey;
try
{
var folderPath = Cache.CacheDirectory;
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
var keyPath = Path.Combine(folderPath, "id");
if (!File.Exists(keyPath))
{
File.WriteAllText(keyPath, Guid.NewGuid().ToString());
}
randomKey = File.ReadAllText(keyPath);
}
catch
{
return;
}
if (string.IsNullOrWhiteSpace(windowsID) || string.IsNullOrWhiteSpace(randomKey))
{
return;
}
using var sha = SHA256.Create();
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(windowsID + randomKey));
Id = string.Concat(hash.Skip(3).Take(10).Select(x => $"{x:X2}"));
var authService = plugin.CreateAuthService(RegistryUtils.RetrieveInHklm64, Cache.CacheDirectory);
authService.Id.ContinueWith(_source.TrySetResult);
}
public static string GetKey()
public static async Task<byte[]?> IdAsKey()
{
if (Id == null)
{
return string.Empty;
}
var text = $"{Id}{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";
var bytes = Encoding.UTF8.GetBytes(text);
var pre = Encoding.UTF8.GetBytes("playertable!");
var salt = new byte[9];
using (var rng = new RNGCryptoServiceProvider())
{
rng.GetNonZeroBytes(salt);
}
for (var i = 0; i < bytes.Length; ++i)
{
bytes[i] = (byte)(bytes[i] ^ salt[i % salt.Length]);
}
return Convert.ToBase64String(salt.Concat(bytes).Select((x, i) => (byte)(x ^ pre[i % pre.Length])).ToArray());
}
public static byte[]? IdAsKey()
{
if (string.IsNullOrEmpty(Id))
var id = await Id.ConfigureAwait(false);
if (string.IsNullOrEmpty(id))
{
return null;
}
var bytes = Encoding.UTF8.GetBytes(Id);
var bytes = Encoding.UTF8.GetBytes(id);
var destination = Enumerable.Repeat<byte>(0xEA, 24).ToArray();
Array.Copy(bytes, destination, Math.Min(bytes.Length, destination.Length));
return destination;

View File

@ -3,6 +3,7 @@ using Microsoft.Win32;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using TechnologyAssembler.Core.IO;
@ -102,6 +103,14 @@ namespace AnotherReplayReader
DronePlatform.BuildTechnologyAssembler();
_skudefFileSystem = new SkuDefFileSystemProvider("config", highestSkudef);
}
catch (ReflectionTypeLoadException e)
{
Debug.Instance.DebugMessage += $"Exception during initialization of BigMinimapCache: \r\n{e}\r\nLoader Exceptions: {e.LoaderExceptions.Length}";
foreach (var e2 in e.LoaderExceptions)
{
Debug.Instance.DebugMessage += $"Exception during initialization of BigMinimapCache: \r\n{e2}\r\n";
}
}
catch (Exception exception)
{
Debug.Instance.DebugMessage += $"Exception during initialization of BigMinimapCache: \r\n{exception}\r\n";

View File

@ -2,18 +2,20 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using static System.Text.Json.JsonSerializer;
namespace AnotherReplayReader
{
internal sealed class Cache
public sealed class Cache
{
public static string CacheDirectory => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RA3Bar.Lanyi.AnotherReplayReader");
public static string OldCacheDirectory => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RA3Bar.Lanyi.AnotherReplayReader");
public static string CacheDirectory => App.LibsFolder;
public static string CacheFilePath => Path.Combine(CacheDirectory, "AnotherReplayReader.cache");
private readonly ConcurrentDictionary<string, string> _storage = new();
public Task Initialization { get; }
public Cache()
@ -27,6 +29,28 @@ namespace AnotherReplayReader
Directory.CreateDirectory(CacheDirectory);
}
try
{
var old = new DirectoryInfo(OldCacheDirectory);
if (old.Exists)
{
foreach (var file in old.EnumerateFiles())
{
try
{
file.MoveTo(Path.Combine(CacheDirectory, file.Name));
}
catch { }
}
try
{
old.Delete();
}
catch { }
}
}
catch { }
using var cacheStream = File.OpenRead(CacheFilePath);
var futureCache = DeserializeAsync<Dictionary<string, string>>(cacheStream).ConfigureAwait(false);
if (await futureCache is not { } cached)
@ -42,7 +66,7 @@ namespace AnotherReplayReader
});
}
public T GetOrDefault<T>(string key, in T defaultValue)
public T GetOrDefault<T>(string key, T defaultValue)
{
try
{
@ -55,12 +79,11 @@ namespace AnotherReplayReader
return defaultValue;
}
public void Set<T>(string key, in T value)
public void Set<T>(string key, T value)
{
try
{
_storage[key] = Serialize(value);
Save();
}
catch { }
}
@ -73,7 +96,6 @@ namespace AnotherReplayReader
{
_storage[key] = Serialize(value);
}
Save();
}
catch { }
}
@ -81,10 +103,9 @@ namespace AnotherReplayReader
public void Remove(string key)
{
_storage.TryRemove(key, out _);
Save();
}
public async void Save()
public async Task Save()
{
try
{

View File

@ -1,36 +0,0 @@
{
"General": {
"OutputFile": null,
"TargetPlatform": null,
"KeyFile": null,
"AlternativeILMergePath": null,
"InputAssemblies": [
"NPinyin.Core.dll",
"Pfim.dll",
"TechnologyAssembler.Core.dll"
]
},
"Advanced": {
"AllowDuplicateType": null,
"AllowMultipleAssemblyLevelAttributes": false,
"AllowWildCards": false,
"AllowZeroPeKind": false,
"AttributeFile": null,
"Closed": false,
"CopyAttributes": false,
"DebugInfo": true,
"DelaySign": false,
"DeleteCopiesOverwriteTarget": false,
"ExcludeFile": "",
"FileAlignment": 512,
"Internalize": false,
"Log": false,
"LogFile": null,
"PublicKeyTokens": true,
"SearchDirectories": [],
"TargetKind": null,
"UnionMerge": false,
"Version": null,
"XmlDocumentation": false
}
}

44
IpAndPlayer.cs Normal file
View File

@ -0,0 +1,44 @@
using AnotherReplayReader.Utils;
using System.Text.Json.Serialization;
namespace AnotherReplayReader
{
public sealed class IpAndPlayer
{
public static string SimpleIPToString(uint ip)
{
return $"{ip / 256 / 256 / 256}.{ip / 256 / 256 % 256}.{ip / 256 % 256}.{ip % 256}";
}
[JsonPropertyName("IP")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public uint Ip
{
get => _ip;
set
{
_ip = value;
IpString = SimpleIPToString(_ip);
}
}
[JsonIgnore]
public string IpString { get; private set; } = "0.0.0.0";
[JsonPropertyName("ID")]
public string Id
{
get => _id;
set
{
_id = value;
_pinyin = _id.ToPinyin();
}
}
[JsonIgnore]
public string? PinyinId => _pinyin;
private uint _ip;
private string _id = string.Empty;
private string? _pinyin;
}
}

View File

@ -6,6 +6,7 @@
xmlns:local="clr-namespace:AnotherReplayReader"
mc:Ignorable="d"
Title="MainWindow" Width="800" Height="600"
WindowStartupLocation="CenterScreen"
Loaded="OnMainWindowLoaded">
<Grid>
<Grid.RowDefinitions>

View File

@ -125,17 +125,54 @@ namespace AnotherReplayReader
InitializeComponent();
Closing += (sender, eventArgs) =>
{
_cache.Save();
_cache.Save().Wait();
Application.Current.Shutdown();
};
}
private async void OnMainWindowLoaded(object sender, EventArgs e)
private async void OnMainWindowLoaded(object sender, EventArgs eventArgs)
{
Debug.Initialize();
await _cache.Initialization;
ReplayAutoSaver.SpawnAutoSaveReplaysTask(_properties.RA3ReplayFolderPath);
var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
await _taskQueue.Enqueue(() => LoadReplays(null, token), token);
_ = _taskQueue.Enqueue(() => LoadReplays(null, token), token);
var wantUsePlugin = true;
try
{
wantUsePlugin = await Plugin.LoadPlayerIdentityPlugin(_playerIdentity);
}
catch (Exception e)
{
Debug.Instance.DebugMessage += $"Failed to load plugin: {e}";
MessageBox.Show(this, $"插件加载失败:{e.Message}");
}
const string permissionKey = "questionAsked";
if (!wantUsePlugin && _cache.GetOrDefault(permissionKey, false) is not true)
{
_cache.Set(permissionKey, true);
var sb = new StringWriter();
sb.WriteLine("要不要自动检查更新呢?");
sb.WriteLine("之后也可以在“关于”窗口里,设置自动更新的选项");
var choice = MessageBox.Show(this, sb.ToString(), App.Name, MessageBoxButton.YesNo);
_cache.Set(UpdateChecker.CheckForUpdatesKey, choice is MessageBoxResult.Yes);
await _cache.Save();
}
_ = UpdateChecker.CheckForUpdates(_cache).ContinueWith(t => Dispatcher.InvokeAsync(() =>
{
var updateData = t.Result;
if (updateData.IsNewVersion())
{
var about = new About(_cache, updateData)
{
Owner = this
};
about.ShowDialog();
}
}));
}
private async Task LoadReplays(string? nextSelected, CancellationToken cancelToken)
@ -318,6 +355,22 @@ namespace AnotherReplayReader
{
var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);
await _taskQueue.Enqueue(() => LoadReplays(null, token), token);
var text = _replayFolderPathBox.Text;
const string assemblyMagic = "!DreamSign";
const string jsonMagic = "!FantasySeal";
switch (_replayFolderPathBox.Text)
{
case "!SpellCard":
_replayDetailsBox.Text = $"{assemblyMagic}\r\n";
_replayDetailsBox.Text += $"{jsonMagic}\r\n";
break;
case assemblyMagic:
Plugin.Sign();
break;
case jsonMagic:
UpdateChecker.Sign();
break;
}
}
private async void OnReplaySelectionChanged(object sender, EventArgs e)
@ -334,7 +387,10 @@ namespace AnotherReplayReader
private void OnAboutButtonClick(object sender, RoutedEventArgs e)
{
var aboutWindow = new About();
var aboutWindow = new About(_cache)
{
Owner = this
};
aboutWindow.ShowDialog();
}
@ -348,7 +404,7 @@ namespace AnotherReplayReader
InitialDirectory = _properties.ReplayFolderPath,
};
var result = openFileDialog.ShowDialog();
var result = openFileDialog.ShowDialog(this);
if (result == true)
{
var fileName = openFileDialog.FileName;
@ -364,7 +420,10 @@ namespace AnotherReplayReader
private void OnDetailsButtonClick(object sender, RoutedEventArgs e)
{
var detailsWindow = new ApmWindow(_properties.CurrentReplay!, _playerIdentity);
var detailsWindow = new ApmWindow(_properties.CurrentReplay!, _playerIdentity)
{
Owner = this
};
detailsWindow.ShowDialog();
}
@ -399,7 +458,7 @@ namespace AnotherReplayReader
}
catch (Exception exception)
{
MessageBox.Show($"无法修复录像:\r\n{exception}");
MessageBox.Show(this, $"无法修复录像:\r\n{exception}");
}
var token = _cancelLoadReplays.ResetAndGetToken(CancellationToken.None);

View File

@ -1,55 +1,29 @@
using AnotherReplayReader.Utils;
using AnotherReplayReader.PluginSystem;
using AnotherReplayReader.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using System.Web;
using static System.Text.Json.JsonSerializer;
namespace AnotherReplayReader
{
internal sealed class IpAndPlayer
{
public static string SimpleIPToString(uint ip)
{
return $"{ip / 256 / 256 / 256}.{ip / 256 / 256 % 256}.{ip / 256 % 256}.{ip % 256}";
}
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public uint Ip
{
get => _ip;
set
{
_ip = value;
IpString = SimpleIPToString(_ip);
}
}
public string IpString { get; private set; } = "0.0.0.0";
public string Id
{
get => _id;
set
{
_id = value;
_pinyin = _id.ToPinyin();
}
}
public string? PinyinId => _pinyin;
private uint _ip;
private string _id = string.Empty;
private string? _pinyin;
}
internal class PlayerIdentity
{
private class NetworkSerializer : IGenericCallable
{
public TResult Invoke<TArg, TResult>(TArg arg) => throw new NotImplementedException();
public Task<TResult?> InvokeAsync<TArg, TResult>(TArg arg)
{
var url = arg as string ?? throw new NotImplementedException();
return Network.HttpGetJson<TResult>(url);
}
}
private const string StoredKey = "pt";
private const string IvKey = "jw";
private static readonly JsonSerializerOptions _jsonOptions = new()
@ -58,6 +32,7 @@ namespace AnotherReplayReader
};
private readonly object _lock = new();
private readonly Cache _cache;
private readonly TaskCompletionSource<IPlayerIdentityService> _serviceSource = new();
private IReadOnlyDictionary<uint, string>? _list;
public bool IsUsable => Lock.Run(_lock, () => _list is not null);
@ -69,24 +44,32 @@ namespace AnotherReplayReader
Fetch();
}
public void LoadPlugin(IPlugin plugin)
{
var service = plugin.CreatePlayerIdentityService(Network.UrlEncode, new NetworkSerializer());
_serviceSource.TrySetResult(service);
}
public Task Fetch()
{
return Task.Run(async () =>
{
try
{
var key = HttpUtility.UrlEncode(Auth.GetKey());
var request = WebRequest.Create($"https://lanyi.altervista.org/playertable/playertable.php?do=getTable&key={key}");
using var response = await request.GetResponseAsync().ConfigureAwait(false);
using var stream = response.GetResponseStream();
var list = await DeserializeAsync<List<IpAndPlayer>>(stream, _jsonOptions).ConfigureAwait(false);
if (await Auth.Id is null)
{
return;
}
var service = await _serviceSource.Task.ConfigureAwait(false);
var list = await service.Fetch<IpAndPlayer>().ConfigureAwait(false);
var converted = list?.ToDictionary(x => x.Ip, x => x.Id);
lock (_lock)
{
_list = converted;
}
if (list is null || Auth.IdAsKey() is not { } encryptKey)
if (list is null || await Auth.IdAsKey() is not { } encryptKey)
{
_cache.Set<string?>(StoredKey, null);
return;
@ -106,6 +89,12 @@ namespace AnotherReplayReader
});
}
public async Task<bool> UpdateIpTable(uint ip, string idText)
{
var service = await _serviceSource.Task.ConfigureAwait(false);
return await service.UpdateIpTable(ip, idText).ConfigureAwait(false);
}
public List<IpAndPlayer> AsSortedList()
{
using var locker = new Lock(_lock);
@ -116,15 +105,15 @@ namespace AnotherReplayReader
.ToList() ?? new(0);
}
public string GetRealName(uint ip)
public string? GetRealName(uint ip)
{
if (ip == 0)
using var locker = new Lock(_lock);
if (_list is null)
{
return string.Empty;
return null;
}
using var locker = new Lock(_lock);
if (_list is null || !_list.TryGetValue(ip, out var name))
if (ip is 0 || !_list.TryGetValue(ip, out var name))
{
return string.Empty;
}
@ -135,6 +124,10 @@ namespace AnotherReplayReader
public string FormatRealName(uint ip)
{
var name = GetRealName(ip);
if (name is null)
{
return string.Empty;
}
if (string.IsNullOrEmpty(name))
{
name = IpAndPlayer.SimpleIPToString(ip);
@ -142,10 +135,14 @@ namespace AnotherReplayReader
return $"{name}";
}
public string QueryRealNameAndIP(uint ip)
public string? QueryRealNameAndIP(uint ip)
{
var ipText = IpAndPlayer.SimpleIPToString(ip);
var name = GetRealName(ip);
if (name is null)
{
return null;
}
var ipText = IpAndPlayer.SimpleIPToString(ip);
if (string.IsNullOrEmpty(name))
{
return ipText;
@ -160,7 +157,7 @@ namespace AnotherReplayReader
await _cache.Initialization;
var stored = _cache.GetOrDefault(StoredKey, string.Empty);
var iv = Convert.FromBase64String(_cache.GetOrDefault(IvKey, string.Empty));
if (string.IsNullOrWhiteSpace(stored) || Auth.IdAsKey() is not { } key)
if (string.IsNullOrWhiteSpace(stored) || await Auth.IdAsKey() is not { } key)
{
return;
}

78
Plugin.cs Normal file
View File

@ -0,0 +1,78 @@
using AnotherReplayReader.PluginSystem;
using AnotherReplayReader.Utils;
using Microsoft.Win32;
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text.Json;
using System.Threading.Tasks;
namespace AnotherReplayReader
{
internal static class Plugin
{
public static Task<bool> LoadPlayerIdentityPlugin(PlayerIdentity playerIdentity)
{
return Task.Run(async () =>
{
var name = $"{nameof(AnotherReplayReader)}.{nameof(PlayerIdentity)}";
var fileName = Path.Combine(App.LibsFolder, $"{name}.dll");
if (File.Exists(fileName))
{
var assembly = await Load(fileName);
var pluginType = assembly.GetType($"{name}.Plugin", true);
var plugin = (IPlugin)Activator.CreateInstance(pluginType);
Auth.LoadPlugin(plugin);
playerIdentity.LoadPlugin(plugin);
return true;
}
return false;
});
}
public static async Task<Assembly> Load(string path)
{
using var file = File.OpenRead(path);
using var metaFile = File.OpenRead($"{path}.meta");
var metaTask = JsonSerializer.DeserializeAsync<VerifierPayload>(metaFile, Network.CommonJsonOptions);
using var memory = new MemoryStream();
await file.CopyToAsync(memory);
var array = memory.ToArray();
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(array);
var meta = await metaTask ?? throw new InvalidDataException("Failed to load meta file");
if (!Enumerable.SequenceEqual(hash, meta.ByteData.Value))
{
throw new InvalidDataException($"Hash does not match, might be corrupt {Convert.ToBase64String(hash)} vs {meta.Data}");
}
if (!Verifier.Verify(meta))
{
throw new InvalidDataException("Invalid metadata, might be corrupt");
}
return Assembly.Load(array);
}
public static void Sign()
{
var openFileDialog = new OpenFileDialog
{
Filter = "dll (*.dll)|*.dll|所有文件 (*.*)|*.*",
InitialDirectory = AppContext.BaseDirectory,
};
var result = openFileDialog.ShowDialog();
if (result is true)
{
using var file = openFileDialog.OpenFile();
using var memory = new MemoryStream();
file.CopyTo(memory);
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(memory.ToArray());
Verifier.Sign(hash);
}
}
}
}

90
UpdateChecker.cs Normal file
View File

@ -0,0 +1,90 @@
using AnotherReplayReader.Utils;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Windows;
namespace AnotherReplayReader
{
internal class UpdateCheckerVersionData
{
public string NewVersion { get; }
public string Description { get; }
public ImmutableArray<string> Urls { get; }
public UpdateCheckerVersionData(string newVersion,
string description,
ImmutableArray<string> urls)
{
NewVersion = newVersion;
Description = description;
Urls = urls;
}
public bool IsNewVersion() => NewVersion != App.Version;
}
internal class UpdateChecker
{
public const string CheckForUpdatesKey = "checkForUpdates";
public const string CachedDataKey = "cachedUpdateData";
private static readonly ImmutableArray<string> _updateSources = new[]
{
"https://lanyi.altervista.org/playertable/file.json"
}.ToImmutableArray();
public static Task<UpdateCheckerVersionData> CheckForUpdates(Cache cache)
{
var taskSource = new TaskCompletionSource<UpdateCheckerVersionData>();
if (cache.GetOrDefault(CheckForUpdatesKey, false) is not true)
{
return taskSource.Task;
}
foreach (var source in _updateSources)
{
Task.Run(async () =>
{
try
{
var data = await DownloadAndVerify(source).ConfigureAwait(false);
if (data.IsNewVersion())
{
cache.Set(CachedDataKey, data);
taskSource.TrySetResult(data);
}
}
catch (Exception e)
{
Debug.Instance.DebugMessage += $"Update check failed: {e}";
}
});
}
return taskSource.Task;
}
private static async Task<UpdateCheckerVersionData> DownloadAndVerify(string url)
{
var payload = await Network.HttpGetJson<VerifierPayload>(url)
?? throw new InvalidDataException(nameof(VerifierPayload) + " is null");
if (!Verifier.Verify(payload))
{
throw new InvalidDataException(nameof(VerifierPayload) + " cannot be verified");
}
return JsonSerializer.Deserialize<UpdateCheckerVersionData>(payload.ByteData.Value, Network.CommonJsonOptions)
?? throw new InvalidDataException(nameof(UpdateCheckerVersionData) + " is null");
}
public static void Sign()
{
var sample = new UpdateCheckerVersionData(App.Version, "Alice Margatroid", _updateSources);
var sampleText = JsonSerializer.Serialize(sample, Network.CommonJsonOptions);
Verifier.Sign(sampleText);
}
}
}

27
Utils/Network.cs Normal file
View File

@ -0,0 +1,27 @@
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace AnotherReplayReader.Utils
{
public static class Network
{
public static readonly JsonSerializerOptions CommonJsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
public static string UrlEncode(string text) => HttpUtility.UrlEncode(text);
public static async Task<T?> HttpGetJson<T>(string url,
CancellationToken cancelToken = default)
{
using var client = new HttpClient();
using var response = await client.GetAsync(url, cancelToken).ConfigureAwait(false);
using var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
return await JsonSerializer.DeserializeAsync<T>(stream, CommonJsonOptions, cancelToken).ConfigureAwait(false);
}
}
}

View File

@ -3,15 +3,15 @@ using System;
namespace AnotherReplayReader.Utils
{
internal static class RegistryUtils
public static class RegistryUtils
{
public static string? Retrieve(RegistryHive hive, string path, string value)
public static string? Retrieve32(RegistryHive hive, string path, string value)
{
try
{
using var view32 = RegistryKey.OpenBaseKey(hive, RegistryView.Registry32);
using var ra3Key = view32.OpenSubKey(path, false);
return ra3Key?.GetValue(value) as string;
using var key = view32.OpenSubKey(path, false);
return key?.GetValue(value) as string;
}
catch (Exception e)
{
@ -20,9 +20,24 @@ namespace AnotherReplayReader.Utils
}
}
public static string? RetrieveInHklm64(string path, string value)
{
try
{
using var view64 = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64);
using var key = view64?.OpenSubKey(path, false);
return key?.GetValue(value) as string;
}
catch (Exception e)
{
Debug.Instance.DebugMessage += $"Failed to retrieve registy HKLM64:{path}:{value}: {e}";
return null;
}
}
public static string? RetrieveInRa3(RegistryHive hive, string value)
{
return Retrieve(hive, @"Software\Electronic Arts\Electronic Arts\Red Alert 3", value);
return Retrieve32(hive, @"Software\Electronic Arts\Electronic Arts\Red Alert 3", value);
}
}
}

View File

@ -21,8 +21,9 @@ namespace AnotherReplayReader.Utils
using var locker = new Lock(_lock);
_current = _current.ContinueWith(async t =>
{
await _dispatcher.InvokeAsync(getTask, DispatcherPriority.Background, cancelToken);
}, cancelToken);
var task = await _dispatcher.InvokeAsync(getTask, DispatcherPriority.Background, cancelToken);
await task.ConfigureAwait(false);
}, cancelToken).Unwrap();
return _current.IgnoreCancel();
}

115
Utils/Verifier.cs Normal file
View File

@ -0,0 +1,115 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Windows;
namespace AnotherReplayReader.Utils
{
internal class VerifierPayload
{
public string Data { get; }
public string Signature { get; }
[JsonIgnore]
public Lazy<byte[]> ByteData { get; }
[JsonIgnore]
public Lazy<byte[]> ByteSignature { get; }
public VerifierPayload(string data, string signature)
{
Data = data;
Signature = signature;
ByteData = new(() => Convert.FromBase64String(Data),
LazyThreadSafetyMode.PublicationOnly);
ByteSignature = new(() => Convert.FromBase64String(Signature),
LazyThreadSafetyMode.PublicationOnly);
}
public static VerifierPayload FromBytes(byte[] data, byte[] signature)
{
return new(Convert.ToBase64String(data), Convert.ToBase64String(signature));
}
}
internal class Verifier
{
private const string _publicKey = @"<RSAKeyValue><Modulus>3vw5CoFRDFt2ri4jLDTu75cw1U/tCRjya7q8X/IdULaOJOYG8C+uqrF2Atb4ou+4SrmF+bvJM9cFsf3yO7XpeIDpkxD3KGbIEw+0JixTIIm+y5xlLKDDwbZHnYjJOBTt6JBn0yqwx7vY2UEZIcRU6wlOmUapnkpiaC2anNhSPqk=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>";
public static bool Verify(VerifierPayload payload)
{
using var rsa = new RSACng();
rsa.FromXmlString(_publicKey);
return rsa.VerifyData(payload.ByteData.Value, payload.ByteSignature.Value, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
public static void Sign(object sample)
{
var fileName = Path.GetTempFileName();
try
{
var ea = Enumerable.Repeat<byte>(0xEA, 1024).ToArray();
const string splitter = "|DuplexBarrier|";
var isByteArray = false;
if (sample is string sampleText)
{
File.WriteAllText(fileName, $"输入私钥信息以及需要签名的数据,用 `{splitter}` 分开\r\n\r\n{sampleText}");
}
else if (sample is byte[] readyArray)
{
isByteArray = true;
File.WriteAllText(fileName, $"输入私钥信息以及需要签名的数据{splitter}{Convert.ToBase64String(readyArray)}{splitter}");
}
using (var process = Process.Start("notepad.exe", fileName))
{
process.WaitForExit();
}
var splitted = File.ReadAllText(fileName).Split(new[] { splitter }, StringSplitOptions.RemoveEmptyEntries);
File.WriteAllBytes(fileName, ea);
byte[] bytes;
byte[] signature;
using (var rsa = new RSACng())
{
rsa.FromXmlString(splitted[0]);
var text = splitted[1];
splitted = null;
GC.Collect();
MessageBox.Show(text, $"快来确认一下~");
bytes = isByteArray
? Convert.FromBase64String(text)
: Encoding.UTF8.GetBytes(text);
signature = rsa.SignData(bytes, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
}
GC.Collect();
var payload = VerifierPayload.FromBytes(bytes, signature);
var serialized = JsonSerializer.Serialize(payload, Network.CommonJsonOptions);
var choice = MessageBox.Show(serialized, "嗯哼", MessageBoxButton.YesNo);
while (choice == MessageBoxResult.Yes)
{
File.WriteAllText(fileName, serialized);
using (var process = Process.Start("notepad.exe", fileName))
{
process.WaitForExit();
}
using var rsa = new RSACng();
rsa.FromXmlString(_publicKey);
var checkContent = File.ReadAllText(fileName);
var check = JsonSerializer.Deserialize<VerifierPayload>(checkContent, Network.CommonJsonOptions) ?? new("", "");
var checkData = Convert.FromBase64String(check.Data);
var checkSignature = Convert.FromBase64String(check.Signature);
var verified = rsa.VerifyData(checkData, checkSignature, HashAlgorithmName.SHA256, RSASignaturePadding.Pss);
choice = MessageBox.Show($"结果:{verified}", "嗯哼", MessageBoxButton.YesNo);
}
}
finally
{
File.Delete(fileName);
}
}
}
}

29
Utils/WpfExtensions.cs Normal file
View File

@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.Windows;
namespace AnotherReplayReader.Utils
{
internal static class WpfExtensions
{
public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
{
foreach (var x in LogicalTreeHelper.GetChildren(depObj))
{
if (x is not DependencyObject child)
{
continue;
}
if (child is T tchild)
{
yield return tchild;
}
foreach (var childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
}

View File

@ -1,13 +1,10 @@
using AnotherReplayReader.Utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Web;
using System.Windows;
using static System.Text.Json.JsonSerializer;
namespace AnotherReplayReader
{
@ -113,17 +110,11 @@ namespace AnotherReplayReader
}
}
private static async Task<bool> UpdateIpTable(IPAddress ip, string idText)
private async Task<bool> UpdateIpTable(IPAddress ip, string idText)
{
var bytes = ip.GetAddressBytes();
var ipNum = (uint)bytes[0] * 256 * 256 * 256 + bytes[1] * 256 * 256 + bytes[2] * 256 + bytes[3];
var text = HttpUtility.UrlEncode(idText);
var key = HttpUtility.UrlEncode(Auth.GetKey());
var request = WebRequest.Create($"https://lanyi.altervista.org/playertable/playertable.php?do=setIP&ip={ipNum}&id={text}&key={key}");
using var response = await request.GetResponseAsync().ConfigureAwait(false);
using var stream = response.GetResponseStream();
return await DeserializeAsync<bool>(stream).ConfigureAwait(false);
return await _identity.UpdateIpTable(checked((uint)ipNum), idText).ConfigureAwait(false);
}
private async void OnIpFieldChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)

76
app.manifest Normal file
View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on
and is designed to work with. Uncomment the appropriate elements
and Windows will automatically select the most compatible environment. -->
<!-- Windows Vista -->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
<!-- Windows 7 -->
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
<!-- Windows 8 -->
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
<!-- Windows 8.1 -->
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
<!-- Windows 10 -->
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
</application>
</compatibility>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config.
Makes the application long-path aware. See https://docs.microsoft.com/windows/win32/fileio/maximum-file-path-limitation -->
<!--
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
</windowsSettings>
</application>
-->
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
</assembly>