Initial version
This commit is contained in:
		
							
								
								
									
										25
									
								
								EonaCat.NightReign.sln
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								EonaCat.NightReign.sln
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
|  | ||||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||||
| # Visual Studio Version 17 | ||||
| VisualStudioVersion = 17.14.36121.58 | ||||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EonaCat.NightReign", "EonaCat.NightReign\EonaCat.NightReign.csproj", "{9262F2EA-FE30-7164-AEA2-4C1D022324C0}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
| 		Release|Any CPU = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||||
| 		{9262F2EA-FE30-7164-AEA2-4C1D022324C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{9262F2EA-FE30-7164-AEA2-4C1D022324C0}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{9262F2EA-FE30-7164-AEA2-4C1D022324C0}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{9262F2EA-FE30-7164-AEA2-4C1D022324C0}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {811090C0-4BF2-4F9E-838C-B324FFAD61E6} | ||||
| 	EndGlobalSection | ||||
| EndGlobal | ||||
							
								
								
									
										37
									
								
								EonaCat.NightReign/EonaCat.NightReign.csproj
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								EonaCat.NightReign/EonaCat.NightReign.csproj
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <OutputType>WinExe</OutputType> | ||||
|     <TargetFramework>net8.0-windows</TargetFramework> | ||||
|     <Nullable>enable</Nullable> | ||||
|     <UseWindowsForms>true</UseWindowsForms> | ||||
|     <ImplicitUsings>enable</ImplicitUsings> | ||||
|     <ApplicationIcon>EonaCat.ico</ApplicationIcon> | ||||
|     <SignAssembly>True</SignAssembly> | ||||
|   </PropertyGroup> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|   <SignAssembly>true</SignAssembly> | ||||
|   <AssemblyOriginatorKeyFile>EonaCat.snk</AssemblyOriginatorKeyFile> | ||||
| </PropertyGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Content Include="EonaCat.ico" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Compile Update="Properties\Resources.Designer.cs"> | ||||
|       <DesignTime>True</DesignTime> | ||||
|       <AutoGen>True</AutoGen> | ||||
|       <DependentUpon>Resources.resx</DependentUpon> | ||||
|     </Compile> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <EmbeddedResource Update="Properties\Resources.resx"> | ||||
|       <Generator>ResXFileCodeGenerator</Generator> | ||||
|       <LastGenOutput>Resources.Designer.cs</LastGenOutput> | ||||
|     </EmbeddedResource> | ||||
|   </ItemGroup> | ||||
|  | ||||
| </Project> | ||||
							
								
								
									
										
											BIN
										
									
								
								EonaCat.NightReign/EonaCat.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								EonaCat.NightReign/EonaCat.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 248 KiB | 
							
								
								
									
										
											BIN
										
									
								
								EonaCat.NightReign/EonaCat.snk
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								EonaCat.NightReign/EonaCat.snk
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										99
									
								
								EonaCat.NightReign/FileEngine.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								EonaCat.NightReign/FileEngine.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,99 @@ | ||||
| using EonaCat.NightReign.Helpers; | ||||
| using EonaCat.NightReign.Models; | ||||
|  | ||||
| namespace EonaCat.NightReign | ||||
| { | ||||
|     static class FileEngine | ||||
|     { | ||||
|         private const int HeaderLength = 12; | ||||
|         private const int DataBlock = 32; | ||||
|         private const int DataBlockSize = 64; | ||||
|  | ||||
|         private static readonly List<BND4Entry> _entries = new(); | ||||
|         private static byte[] _rawData; | ||||
|  | ||||
|         private static readonly string _outputFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "EonaCat", "NightReign", "Temp"); | ||||
|  | ||||
|         public static string OutputFolder => _outputFolder; | ||||
|  | ||||
|         public static string Decrypt(string inputFile, Action<string> logger = null) | ||||
|         { | ||||
|             _rawData = File.ReadAllBytes(inputFile); | ||||
|  | ||||
|             if (!SL2Helper.IsValidHeader(BND4Entry.FILEIDENTIFIER, _rawData)) | ||||
|             { | ||||
|                 logger?.Invoke("Invalid SL2 file."); | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             _entries.Clear(); | ||||
|             int numEntries = BitConverter.ToInt32(_rawData, HeaderLength); | ||||
|  | ||||
|             for (int i = 0; i < numEntries; i++) | ||||
|             { | ||||
|                 int pos = DataBlockSize + (i * DataBlock); | ||||
|                 if (pos + DataBlock > _rawData.Length) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 if (!SL2Helper.IsValidDivider(_rawData, pos)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 int size = BitConverter.ToInt32(_rawData, pos + 8); | ||||
|                 int dataOffset = BitConverter.ToInt32(_rawData, pos + 16); | ||||
|                 int footerLength = BitConverter.ToInt32(_rawData, pos + 24); | ||||
|  | ||||
|                 var entry = new BND4Entry(_rawData, i, _outputFolder, size, dataOffset, footerLength); | ||||
|                 entry.Decrypt(); | ||||
|                 _entries.Add(entry); | ||||
|  | ||||
|                 logger?.Invoke($"Decrypted Entry #{i}: {entry.Name}"); | ||||
|             } | ||||
|  | ||||
|             FileHelper.TryCreateDirectory(_outputFolder, logger); | ||||
|             return _outputFolder; | ||||
|         } | ||||
|  | ||||
|         public static void Encrypt(string outputFile) | ||||
|         { | ||||
|             var newData = new byte[_rawData.Length]; | ||||
|             Array.Copy(_rawData, newData, _rawData.Length); | ||||
|  | ||||
|             foreach (var entry in _entries) | ||||
|             { | ||||
|                 string modifiedPath = Path.Combine(_outputFolder, entry.Name); | ||||
|                 if (!File.Exists(modifiedPath)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 byte[] modified = File.ReadAllBytes(modifiedPath); | ||||
|                 entry.SetModifiedData(modified); | ||||
|                 entry.PatchChecksum(); | ||||
|  | ||||
|                 byte[] encrypted = entry.EncryptSL2Data(); | ||||
|                 Array.Copy(encrypted, 0, newData, entry.DataOffset, encrypted.Length); | ||||
|             } | ||||
|  | ||||
|             File.WriteAllBytes(outputFile, newData); | ||||
|         } | ||||
|  | ||||
|         public static void RemoveEncryptedFolder() | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (Directory.Exists(_outputFolder)) | ||||
|                 { | ||||
|                     Directory.Delete(_outputFolder, recursive: true); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 Console.WriteLine($"Failed to delete output directory: {ex.Message}"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										56
									
								
								EonaCat.NightReign/Helpers/BytesHelper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								EonaCat.NightReign/Helpers/BytesHelper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| using System.Security.Cryptography; | ||||
|  | ||||
| namespace EonaCat.NightReign.Helpers | ||||
| { | ||||
|     public static class BytesHelper | ||||
|     { | ||||
|         public static string BytesToIntStr(byte[] data) => | ||||
|             string.Join(",", data.Select(b => b.ToString())); | ||||
|  | ||||
|         public static byte[] Md5Hash(byte[] data) | ||||
|         { | ||||
|             using var md5 = MD5.Create(); | ||||
|             return md5.ComputeHash(data); | ||||
|         } | ||||
|  | ||||
|         public static bool SequenceEquals(this byte[] a, byte[] b) => | ||||
|             a.AsSpan().SequenceEqual(b); | ||||
|  | ||||
|         public static bool ContainsSubsequence(this byte[] array, byte[] subsequence) | ||||
|         { | ||||
|             if (subsequence.Length == 0 || array.Length < subsequence.Length) | ||||
|                 return false; | ||||
|  | ||||
|             for (int i = 0; i <= array.Length - subsequence.Length; i++) | ||||
|             { | ||||
|                 if (array.AsSpan(i, subsequence.Length).SequenceEqual(subsequence)) | ||||
|                     return true; | ||||
|             } | ||||
|  | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         public static byte[] ReplaceBytes(byte[] source, byte[] oldBytes, byte[] newBytes) | ||||
|         { | ||||
|             using var ms = new MemoryStream(); | ||||
|             int i = 0; | ||||
|  | ||||
|             while (i < source.Length) | ||||
|             { | ||||
|                 if (i <= source.Length - oldBytes.Length && | ||||
|                     source.AsSpan(i, oldBytes.Length).SequenceEqual(oldBytes)) | ||||
|                 { | ||||
|                     ms.Write(newBytes, 0, newBytes.Length); | ||||
|                     i += oldBytes.Length; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     ms.WriteByte(source[i]); | ||||
|                     i++; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return ms.ToArray(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								EonaCat.NightReign/Helpers/FileHelper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								EonaCat.NightReign/Helpers/FileHelper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| namespace EonaCat.NightReign.Helpers | ||||
| { | ||||
|     internal class FileHelper | ||||
|     { | ||||
|         public static void TryCreateDirectory(string path, Action<string> logCallback) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 Directory.CreateDirectory(path); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 logCallback?.Invoke($"Failed to create output directory: {ex.Message}"); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								EonaCat.NightReign/Helpers/SL2Helper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								EonaCat.NightReign/Helpers/SL2Helper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| using System.Text; | ||||
|  | ||||
| namespace EonaCat.NightReign.Helpers | ||||
| { | ||||
|     internal class SL2Helper | ||||
|     { | ||||
|         private static readonly byte[] DATADIVIDER = [0x40, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF]; | ||||
|  | ||||
|         internal static bool IsValidHeader(string fileIdentifier, byte[] data) => | ||||
|         data != null && | ||||
|         data.Length >= fileIdentifier.Length && | ||||
|         data.Take(fileIdentifier.Length).SequenceEqual(Encoding.ASCII.GetBytes(fileIdentifier)); | ||||
|  | ||||
|         internal static bool IsValidDivider(byte[] data, int position) => | ||||
|             data.Skip(position).Take(DATADIVIDER.Length).SequenceEqual(DATADIVIDER); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										62
									
								
								EonaCat.NightReign/Helpers/SteamIdHelper.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								EonaCat.NightReign/Helpers/SteamIdHelper.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| using Microsoft.Win32; | ||||
| using System.Text.RegularExpressions; | ||||
|  | ||||
| namespace EonaCat.NightReign.Helpers | ||||
| { | ||||
|     public static class SteamHelper | ||||
|     { | ||||
|         public const int STEAM_ID_HEX_LENGTH = 16; | ||||
|         public static Dictionary<string, string> GetAllSteamAccounts() | ||||
|         { | ||||
|             var accounts = new Dictionary<string, string>(); | ||||
|  | ||||
|             string steamPath = GetSteamInstallPath(); | ||||
|             if (steamPath == null) | ||||
|                 return accounts; | ||||
|  | ||||
|             string loginUsersPath = Path.Combine(steamPath, "config", "loginusers.vdf"); | ||||
|             if (!File.Exists(loginUsersPath)) | ||||
|                 return accounts; | ||||
|  | ||||
|             string fileContent = File.ReadAllText(loginUsersPath); | ||||
|  | ||||
|             var userBlockPattern = new Regex(@"""(\d{17})""\s*{[^}]*?""AccountName""\s*""([^""]+)""", RegexOptions.Singleline); | ||||
|  | ||||
|             foreach (Match match in userBlockPattern.Matches(fileContent)) | ||||
|             { | ||||
|                 if (match.Groups.Count == 3) | ||||
|                 { | ||||
|                     string steamId = match.Groups[1].Value; | ||||
|                     string accountName = match.Groups[2].Value; | ||||
|                     accounts[steamId] = accountName; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return accounts; | ||||
|         } | ||||
|  | ||||
|         internal static byte[] ConvertToSteamIdBytes(string steamId) | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(steamId) || steamId.Length != 17 || !steamId.All(char.IsDigit)) | ||||
|             { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             var hex = Convert.ToUInt64(steamId).ToString("x").PadLeft(STEAM_ID_HEX_LENGTH, '0'); | ||||
|             var steamIdBytes = Enumerable.Range(0, hex.Length) | ||||
|                                          .Where(i => i % 2 == 0) | ||||
|                                          .Select(i => Convert.ToByte(hex.Substring(i, 2), 16)) | ||||
|                                          .Reverse().ToArray(); | ||||
|             return steamIdBytes; | ||||
|         } | ||||
|  | ||||
|         private static string GetSteamInstallPath() | ||||
|         { | ||||
|             string key = Environment.Is64BitOperatingSystem | ||||
|                 ? @"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Valve\Steam" | ||||
|                 : @"HKEY_LOCAL_MACHINE\SOFTWARE\Valve\Steam"; | ||||
|  | ||||
|             return Registry.GetValue(key, "InstallPath", null) as string; | ||||
|         } | ||||
| } | ||||
| } | ||||
							
								
								
									
										298
									
								
								EonaCat.NightReign/MainForm.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										298
									
								
								EonaCat.NightReign/MainForm.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,298 @@ | ||||
| using EonaCat.NightReign.EonaCat.NightReign; | ||||
| using EonaCat.NightReign.Helpers; | ||||
|  | ||||
| namespace EonaCat.NightReign | ||||
| { | ||||
|     public partial class MainForm : Form | ||||
|     { | ||||
|         private const int STEAM_ID_BYTE_LENGTH = 8; | ||||
|  | ||||
|         private System.ComponentModel.IContainer components = null; | ||||
|         private string _steamId; | ||||
|  | ||||
|         public object NeightReignFileName => "NR0000"; | ||||
|  | ||||
|         public MainForm() | ||||
|         { | ||||
|             InitializeComponent(); | ||||
|             SetupFormUI(); | ||||
|         } | ||||
|  | ||||
|         protected override void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing) | ||||
|             { | ||||
|                 components?.Dispose(); | ||||
|             } | ||||
|  | ||||
|             base.Dispose(disposing); | ||||
|         } | ||||
|  | ||||
|         private void SetupFormUI() | ||||
|         { | ||||
|             Text = "EonaCat ER NightReign Save Transfer"; | ||||
|             Size = new Size(400, 150); | ||||
|             MaximizeBox = false; | ||||
|             MinimizeBox = false; | ||||
|             FormBorderStyle = FormBorderStyle.FixedDialog; | ||||
|             StartPosition = FormStartPosition.CenterScreen; | ||||
|  | ||||
|             var label = new Label | ||||
|             { | ||||
|                 Text = "This tool allows you to change the Steam ID in your Elden Ring NightReign save file.", | ||||
|                 Width = 360, | ||||
|                 Height = 40, | ||||
|                 ForeColor = Color.White, | ||||
|                 BackColor = Color.Transparent, | ||||
|                 Top = 10, | ||||
|                 Left = 10 | ||||
|             }; | ||||
|  | ||||
|             var decryptButton = new Button | ||||
|             { | ||||
|                 Text = "Select save file for steam ID change", | ||||
|                 Width = 220, | ||||
|                 Height = 30, | ||||
|                 Top = 50, | ||||
|                 Left = 35 | ||||
|             }; | ||||
|             decryptButton.Click += DecryptButton_Click; | ||||
|  | ||||
|             Controls.Add(label); | ||||
|             Controls.Add(decryptButton); | ||||
|         } | ||||
|  | ||||
|         private void InitializeComponent() | ||||
|         { | ||||
|             SuspendLayout(); | ||||
|             AutoScaleDimensions = new SizeF(7F, 15F); | ||||
|             AutoScaleMode = AutoScaleMode.Font; | ||||
|             AutoSize = true; | ||||
|             BackgroundImage = Properties.Resources._1; | ||||
|             ClientSize = new Size(800, 450); | ||||
|             Name = "MainForm"; | ||||
|             ResumeLayout(false); | ||||
|         } | ||||
|  | ||||
|         private void DecryptButton_Click(object sender, EventArgs e) | ||||
|         { | ||||
|             var inputFile = GetInputFile(); | ||||
|             if (string.IsNullOrWhiteSpace(inputFile)) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             string folderPath; | ||||
|             try | ||||
|             { | ||||
|                 folderPath = FileEngine.Decrypt(inputFile, Console.WriteLine); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 ShowError("Failed to decrypt SL2 file", ex.Message); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             var ERDataFiles = Directory.GetFiles(folderPath, "ELDENRING_DATA*").OrderBy(f => f).ToArray(); | ||||
|             string ERData10Path = Path.Combine(folderPath, "ELDENRING_DATA_10"); | ||||
|  | ||||
|             if (!File.Exists(ERData10Path)) | ||||
|             { | ||||
|                 ShowError("Missing File", $"ELDENRING_DATA_10 not found in {folderPath}"); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             byte[] oldSteamId; | ||||
|             try | ||||
|             { | ||||
|                 using var fs = new FileStream(ERData10Path, FileMode.Open, FileAccess.Read); | ||||
|                 fs.Seek(0x8, SeekOrigin.Begin); | ||||
|                 oldSteamId = new byte[STEAM_ID_BYTE_LENGTH]; | ||||
|                 fs.Read(oldSteamId, 0, STEAM_ID_BYTE_LENGTH); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 ShowError("Failed to read ELDENRING_DATA_10", ex.Message); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             Console.WriteLine("Old Steam ID (bytes): " + BitConverter.ToString(oldSteamId)); | ||||
|  | ||||
|             var steamIds = SteamHelper.GetAllSteamAccounts(); | ||||
|             var newSteamId = string.Empty; | ||||
|             byte[] newSteamIdBytes = null; | ||||
|  | ||||
|             // If there are steamIds, show them in a dialog to select | ||||
|             if (steamIds != null && steamIds.Count > 0) | ||||
|             { | ||||
|                 var steamIdForm = new SteamIdSelectionForm(steamIds, oldSteamId); | ||||
|                 if (steamIdForm.ShowDialog() == DialogResult.OK) | ||||
|                 { | ||||
|                     newSteamId = steamIdForm.SelectedSteamId; | ||||
|                     newSteamIdBytes = SteamHelper.ConvertToSteamIdBytes(newSteamId); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (steamIds == null || steamIds.Count() == 0 || string.IsNullOrEmpty(newSteamId) || newSteamId.Length != 17 || !newSteamId.All(char.IsDigit) || newSteamIdBytes  == null) | ||||
|             { | ||||
|                 AskSteamIdWindow(x => | ||||
|                 { | ||||
|                     newSteamIdBytes = x; | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             if (newSteamIdBytes == null) | ||||
|             { | ||||
|                 MessageBox.Show("Invalid Steam ID format. Please enter a valid 17-digit Steam ID.", "Invalid Input", MessageBoxButtons.OK, MessageBoxIcon.Error); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if (oldSteamId.SequenceEqual(newSteamIdBytes)) | ||||
|             { | ||||
|                 MessageBox.Show("The new Steam ID is the same as the old one.", "No Changes", MessageBoxButtons.OK, MessageBoxIcon.Information); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             Console.WriteLine("New Steam ID (bytes): " + BitConverter.ToString(newSteamIdBytes)); | ||||
|             int filesModified = 0; | ||||
|  | ||||
|             foreach (var file in ERDataFiles) | ||||
|             { | ||||
|                 byte[] data = File.ReadAllBytes(file); | ||||
|                 if (!data.ContainsSubsequence(oldSteamId)) | ||||
|                 { | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var newData = BytesHelper.ReplaceBytes(data, oldSteamId, newSteamIdBytes); | ||||
|                 if (!data.SequenceEqual(newData)) | ||||
|                 { | ||||
|                     File.WriteAllBytes(file, newData); | ||||
|                     filesModified++; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (filesModified == 0) | ||||
|             { | ||||
|                 MessageBox.Show("No files were modified. The old Steam ID might not be present in any slots.", "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             _steamId = newSteamId; | ||||
|             Console.WriteLine($"Steam ID replaced in {filesModified} file(s)"); | ||||
|  | ||||
|             var outputFile = GetOutputFile(); | ||||
|             if (string.IsNullOrEmpty(outputFile)) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             try | ||||
|             { | ||||
|                 FileEngine.Encrypt(outputFile); | ||||
|                 MessageBox.Show($"Save file saved as {outputFile}", "Success", MessageBoxButtons.OK, MessageBoxIcon.Information); | ||||
|                 FileEngine.RemoveEncryptedFolder(); | ||||
|             } | ||||
|             catch (Exception ex) | ||||
|             { | ||||
|                 ShowError("Failed to re-encrypt and save", ex.Message); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         private string GetInputFile() | ||||
|         { | ||||
|             using var ofd = new OpenFileDialog | ||||
|             { | ||||
|                 Title = "Select SL2 File", | ||||
|                 Filter = "SL2 Files (*.sl2)|*.sl2|All Files (*.*)|*.*" | ||||
|             }; | ||||
|             return ofd.ShowDialog() == DialogResult.OK ? ofd.FileName : null; | ||||
|         } | ||||
|  | ||||
|         private string GetOutputFile() | ||||
|         { | ||||
|             string basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Nightreign"); | ||||
|             string path = !string.IsNullOrEmpty(_steamId) && _steamId.Length == 17 && Directory.Exists(Path.Combine(basePath, _steamId)) | ||||
|                 ? Path.Combine(basePath, _steamId) | ||||
|                 : basePath; | ||||
|  | ||||
|             using var sfd = new SaveFileDialog | ||||
|             { | ||||
|                 Title = "Save New Encrypted SL2 File As", | ||||
|                 Filter = "SL2 Files (*.sl2)|*.sl2|All Files (*.*)|*.*", | ||||
|                 FileName = $"{NeightReignFileName}.sl2", | ||||
|                 DefaultExt = "sl2", | ||||
|                 InitialDirectory = path | ||||
|             }; | ||||
|             return sfd.ShowDialog() == DialogResult.OK ? sfd.FileName : null; | ||||
|         } | ||||
|  | ||||
|         private void AskSteamIdWindow(Action<byte[]> callback) | ||||
|         { | ||||
|             var inputForm = new Form | ||||
|             { | ||||
|                 Text = "Enter your 17 digits Steam ID (steamID64 (Dec))", | ||||
|                 Size = new Size(450, 150), | ||||
|                 MaximizeBox = false, | ||||
|                 MinimizeBox = false, | ||||
|                 FormBorderStyle = FormBorderStyle.FixedDialog, | ||||
|                 StartPosition = FormStartPosition.CenterParent, | ||||
|                 BackgroundImage = Properties.Resources._1, | ||||
|                 ForeColor = Color.White, | ||||
|             }; | ||||
|  | ||||
|             var label = new Label | ||||
|             { | ||||
|                 Text = "Enter your 17-digit Steam ID:", | ||||
|                 Top = 20, | ||||
|                 Left = 20, | ||||
|                 Width = 300, | ||||
|                 ForeColor = Color.White, | ||||
|                 BackColor = Color.Transparent | ||||
|             }; | ||||
|  | ||||
|             var inputBox = new TextBox | ||||
|             { | ||||
|                 Top = 50, | ||||
|                 Left = 20, | ||||
|                 Width = 200 | ||||
|             }; | ||||
|  | ||||
|             var submitBtn = new Button | ||||
|             { | ||||
|                 Text = "Submit", | ||||
|                 Top = 80, | ||||
|                 Left = 20, | ||||
|                 BackColor = Color.FromArgb(30, 30, 30), | ||||
|                 ForeColor = Color.White, | ||||
|             }; | ||||
|  | ||||
|             submitBtn.Click += (s, e) => | ||||
|             { | ||||
|                 string input = inputBox.Text.Trim(); | ||||
|                 if (!IsValidSteamId(input)) | ||||
|                 { | ||||
|                     MessageBox.Show("Steam ID must be exactly 17 digits!", "Invalid Steam ID", MessageBoxButtons.OK, MessageBoxIcon.Error); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 _steamId = input; | ||||
|                 byte[] steamIdBytes = SteamHelper.ConvertToSteamIdBytes(_steamId); | ||||
|                 inputForm.Close(); | ||||
|                 callback(steamIdBytes); | ||||
|             }; | ||||
|  | ||||
|             inputForm.Controls.Add(label); | ||||
|             inputForm.Controls.Add(inputBox); | ||||
|             inputForm.Controls.Add(submitBtn); | ||||
|             inputForm.AcceptButton = submitBtn; | ||||
|             inputForm.ShowDialog(); | ||||
|         } | ||||
|  | ||||
|         private bool IsValidSteamId(string input) => | ||||
|             input.Length == 17 && input.All(char.IsDigit); | ||||
|  | ||||
|         private void ShowError(string title, string message) => | ||||
|             MessageBox.Show(message, title, MessageBoxButtons.OK, MessageBoxIcon.Error); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										120
									
								
								EonaCat.NightReign/MainForm.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								EonaCat.NightReign/MainForm.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!-- | ||||
|     Microsoft ResX Schema | ||||
|  | ||||
|     Version 2.0 | ||||
|  | ||||
|     The primary goals of this format is to allow a simple XML format | ||||
|     that is mostly human readable. The generation and parsing of the | ||||
|     various data types are done through the TypeConverter classes | ||||
|     associated with the data types. | ||||
|  | ||||
|     Example: | ||||
|  | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|  | ||||
|     There are any number of "resheader" rows that contain simple | ||||
|     name/value pairs. | ||||
|  | ||||
|     Each data row contains a name, and value. The row also contains a | ||||
|     type or mimetype. Type corresponds to a .NET class that support | ||||
|     text/value conversion through the TypeConverter architecture. | ||||
|     Classes that don't support this are serialized and stored with the | ||||
|     mimetype set. | ||||
|  | ||||
|     The mimetype is used for serialized objects, and tells the | ||||
|     ResXResourceReader how to depersist the object. This is currently not | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|  | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format | ||||
|     that the ResXResourceWriter will generate, however the reader can | ||||
|     read any of the formats listed below. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
| </root> | ||||
							
								
								
									
										88
									
								
								EonaCat.NightReign/Models/Bnd4Entry.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								EonaCat.NightReign/Models/Bnd4Entry.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,88 @@ | ||||
| using System.Security.Cryptography; | ||||
|  | ||||
| namespace EonaCat.NightReign.Models | ||||
| { | ||||
|     public class BND4Entry | ||||
|     { | ||||
|         public const string FILEIDENTIFIER = "BND4"; | ||||
|         public int Index { get; } | ||||
|         public int Size { get; } | ||||
|         public int DataOffset { get; } | ||||
|         public int FooterLength { get; } | ||||
|         public string Name => $"ELDENRING_DATA_{Index:00}"; | ||||
|         public bool IsDecrypted { get; private set; } | ||||
|  | ||||
|         private const int IV_SIZE = 16; | ||||
|         private const int PADDING_LENGTH = 28; | ||||
|         private readonly byte[] _rawData; | ||||
|         private readonly byte[] _encryptedData; | ||||
|         private readonly byte[] _iv; | ||||
|         private readonly byte[] _encryptedPayload; | ||||
|         private byte[] _data; | ||||
|  | ||||
|         public string OutputFolder { get; } | ||||
|  | ||||
|         private static readonly byte[] DS2_KEY = { 0x18, 0xF6, 0x32, 0x66, 0x05, 0xBD, 0x17, 0x8A, 0x55, 0x24, 0x52, 0x3A, 0xC0, 0xA0, 0xC6, 0x09 }; | ||||
|  | ||||
|         public BND4Entry(byte[] rawData, int index, string outputFolder, int size, int offset, int footerLength) | ||||
|         { | ||||
|             Index = index; Size = size; DataOffset = offset; FooterLength = footerLength; | ||||
|             _rawData = rawData; | ||||
|             _encryptedData = rawData.Skip(offset).Take(size).ToArray(); | ||||
|             _iv = _encryptedData.Take(IV_SIZE).ToArray(); | ||||
|             _encryptedPayload = _encryptedData.Skip(IV_SIZE).ToArray(); | ||||
|             OutputFolder = outputFolder; | ||||
|         } | ||||
|  | ||||
|         public void Decrypt() | ||||
|         { | ||||
|             using var aes = Aes.Create(); | ||||
|             aes.Key = DS2_KEY; aes.IV = _iv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; | ||||
|  | ||||
|             using var transform = aes.CreateDecryptor(); | ||||
|             _data = transform.TransformFinalBlock(_encryptedPayload, 0, _encryptedPayload.Length); | ||||
|  | ||||
|             var filePath = Path.Combine(OutputFolder, Name); | ||||
|             if (!Directory.Exists(OutputFolder)) | ||||
|             { | ||||
|                 try | ||||
|                 { | ||||
|                     Directory.CreateDirectory(OutputFolder); | ||||
|                 } | ||||
|                 catch (Exception ex) | ||||
|                 { | ||||
|                     throw new InvalidOperationException($"Failed to create output directory: {ex.Message}"); | ||||
|                 } | ||||
|             } | ||||
|             File.WriteAllBytes(filePath, _data); | ||||
|  | ||||
|             IsDecrypted = true; | ||||
|         } | ||||
|  | ||||
|         public void PatchChecksum() | ||||
|         { | ||||
|             int checksumEnd = _data.Length - PADDING_LENGTH; | ||||
|             byte[] checksum = CalculateChecksum(); | ||||
|             Array.Copy(checksum, 0, _data, checksumEnd, checksum.Length); | ||||
|         } | ||||
|  | ||||
|         public byte[] CalculateChecksum() | ||||
|         { | ||||
|             int checksumEnd = _data.Length - 28; | ||||
|             using var md5 = MD5.Create(); | ||||
|             return md5.ComputeHash(_data.Skip(FILEIDENTIFIER.Length).Take(checksumEnd - FILEIDENTIFIER.Length).ToArray()); | ||||
|         } | ||||
|  | ||||
|         public byte[] EncryptSL2Data() | ||||
|         { | ||||
|             using var aes = Aes.Create(); | ||||
|             aes.Key = DS2_KEY; aes.IV = _iv; aes.Mode = CipherMode.CBC; aes.Padding = PaddingMode.None; | ||||
|  | ||||
|             using var transform = aes.CreateEncryptor(); | ||||
|             byte[] encrypted = transform.TransformFinalBlock(_data, 0, _data.Length); | ||||
|             return _iv.Concat(encrypted).ToArray(); | ||||
|         } | ||||
|  | ||||
|         public void SetModifiedData(byte[] data) => _data = data; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										15
									
								
								EonaCat.NightReign/Program.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								EonaCat.NightReign/Program.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| namespace EonaCat.NightReign | ||||
| { | ||||
|     internal static class Program | ||||
|     { | ||||
|         /// <summary> | ||||
|         ///  The main entry point for the application. | ||||
|         /// </summary> | ||||
|         [STAThread] | ||||
|         static void Main() | ||||
|         { | ||||
|             ApplicationConfiguration.Initialize(); | ||||
|             Application.Run(new MainForm()); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										73
									
								
								EonaCat.NightReign/Properties/Resources.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								EonaCat.NightReign/Properties/Resources.Designer.cs
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| //------------------------------------------------------------------------------ | ||||
| // <auto-generated> | ||||
| //     This code was generated by a tool. | ||||
| //     Runtime Version:4.0.30319.42000 | ||||
| // | ||||
| //     Changes to this file may cause incorrect behavior and will be lost if | ||||
| //     the code is regenerated. | ||||
| // </auto-generated> | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
| namespace EonaCat.NightReign.Properties { | ||||
|     using System; | ||||
|      | ||||
|      | ||||
|     /// <summary> | ||||
|     ///   A strongly-typed resource class, for looking up localized strings, etc. | ||||
|     /// </summary> | ||||
|     // This class was auto-generated by the StronglyTypedResourceBuilder | ||||
|     // class via a tool like ResGen or Visual Studio. | ||||
|     // To add or remove a member, edit your .ResX file then rerun ResGen | ||||
|     // with the /str option, or rebuild your VS project. | ||||
|     [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] | ||||
|     [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] | ||||
|     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] | ||||
|     internal class Resources { | ||||
|          | ||||
|         private static global::System.Resources.ResourceManager resourceMan; | ||||
|          | ||||
|         private static global::System.Globalization.CultureInfo resourceCulture; | ||||
|          | ||||
|         [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] | ||||
|         internal Resources() { | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Returns the cached ResourceManager instance used by this class. | ||||
|         /// </summary> | ||||
|         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | ||||
|         internal static global::System.Resources.ResourceManager ResourceManager { | ||||
|             get { | ||||
|                 if (object.ReferenceEquals(resourceMan, null)) { | ||||
|                     global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("EonaCat.NightReign.Properties.Resources", typeof(Resources).Assembly); | ||||
|                     resourceMan = temp; | ||||
|                 } | ||||
|                 return resourceMan; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Overrides the current thread's CurrentUICulture property for all | ||||
|         ///   resource lookups using this strongly typed resource class. | ||||
|         /// </summary> | ||||
|         [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] | ||||
|         internal static global::System.Globalization.CultureInfo Culture { | ||||
|             get { | ||||
|                 return resourceCulture; | ||||
|             } | ||||
|             set { | ||||
|                 resourceCulture = value; | ||||
|             } | ||||
|         } | ||||
|          | ||||
|         /// <summary> | ||||
|         ///   Looks up a localized resource of type System.Drawing.Bitmap. | ||||
|         /// </summary> | ||||
|         internal static System.Drawing.Bitmap _1 { | ||||
|             get { | ||||
|                 object obj = ResourceManager.GetObject("1", resourceCulture); | ||||
|                 return ((System.Drawing.Bitmap)(obj)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										124
									
								
								EonaCat.NightReign/Properties/Resources.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								EonaCat.NightReign/Properties/Resources.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|     associated with the data types. | ||||
|      | ||||
|     Example: | ||||
|      | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> | ||||
|   <data name="1" type="System.Resources.ResXFileRef, System.Windows.Forms"> | ||||
|     <value>..\Resources\1.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value> | ||||
|   </data> | ||||
| </root> | ||||
							
								
								
									
										
											BIN
										
									
								
								EonaCat.NightReign/Resources/1.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								EonaCat.NightReign/Resources/1.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 350 KiB | 
							
								
								
									
										116
									
								
								EonaCat.NightReign/SteamIdSelectionForm.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								EonaCat.NightReign/SteamIdSelectionForm.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | ||||
| using EonaCat.NightReign.Helpers; | ||||
|  | ||||
| namespace EonaCat.NightReign | ||||
| { | ||||
|     namespace EonaCat.NightReign | ||||
|     { | ||||
|         public class SteamIdSelectionForm : Form | ||||
|         { | ||||
|             private List<bool> isMatchingIdList = new(); | ||||
|  | ||||
|             private ListBox listBox; | ||||
|             private Button okButton; | ||||
|             private Button cancelButton; | ||||
|             private readonly Dictionary<string, string> steamAccounts; | ||||
|             private readonly byte[] oldSteamId; | ||||
|  | ||||
|             public string SelectedSteamId { get; private set; } | ||||
|  | ||||
|             public SteamIdSelectionForm(Dictionary<string, string> steamAccounts, byte[] oldSteamId) | ||||
|             { | ||||
|                 this.steamAccounts = steamAccounts ?? new(); | ||||
|                 this.oldSteamId = oldSteamId; | ||||
|  | ||||
|                 InitializeComponents(); | ||||
|             } | ||||
|  | ||||
|             private void InitializeComponents() | ||||
|             { | ||||
|                 Text = "Select Your Steam Account"; | ||||
|                 Size = new Size(400, 300); | ||||
|                 StartPosition = FormStartPosition.CenterParent; | ||||
|                 FormBorderStyle = FormBorderStyle.FixedDialog; | ||||
|                 MaximizeBox = false; | ||||
|                 MinimizeBox = false; | ||||
|  | ||||
|                 listBox = new ListBox | ||||
|                 { | ||||
|                     Dock = DockStyle.Top, | ||||
|                     Height = 200, | ||||
|                     DrawMode = DrawMode.OwnerDrawFixed | ||||
|                 }; | ||||
|                 listBox.DrawItem += ListBox_DrawItem; | ||||
|  | ||||
|                 foreach (var kvp in steamAccounts) | ||||
|                 { | ||||
|                     bool isMatch = oldSteamId != null && oldSteamId.SequenceEqual(SteamHelper.ConvertToSteamIdBytes(kvp.Key)); | ||||
|                     listBox.Items.Add($"{kvp.Value} ({kvp.Key})"); | ||||
|                     isMatchingIdList.Add(isMatch); | ||||
|                 } | ||||
|  | ||||
|                 okButton = new Button | ||||
|                 { | ||||
|                     Text = "OK", | ||||
|                     DialogResult = DialogResult.OK, | ||||
|                     Anchor = AnchorStyles.Bottom | AnchorStyles.Right, | ||||
|                     Width = 80, | ||||
|                     Left = 200, | ||||
|                     Top = 220 | ||||
|                 }; | ||||
|                 okButton.Click += OkButton_Click; | ||||
|  | ||||
|                 cancelButton = new Button | ||||
|                 { | ||||
|                     Text = "Cancel", | ||||
|                     DialogResult = DialogResult.Cancel, | ||||
|                     Anchor = AnchorStyles.Bottom | AnchorStyles.Right, | ||||
|                     Width = 80, | ||||
|                     Left = 290, | ||||
|                     Top = 220 | ||||
|                 }; | ||||
|  | ||||
|                 Controls.Add(listBox); | ||||
|                 Controls.Add(okButton); | ||||
|                 Controls.Add(cancelButton); | ||||
|  | ||||
|                 AcceptButton = okButton; | ||||
|                 CancelButton = cancelButton; | ||||
|             } | ||||
|  | ||||
|             private void ListBox_DrawItem(object sender, DrawItemEventArgs e) | ||||
|             { | ||||
|                 if (e.Index < 0 || e.Index >= listBox.Items.Count) return; | ||||
|  | ||||
|                 e.DrawBackground(); | ||||
|  | ||||
|                 bool isMatch = isMatchingIdList[e.Index]; | ||||
|                 string text = listBox.Items[e.Index].ToString(); | ||||
|  | ||||
|                 using (Brush brush = new SolidBrush(isMatch ? Color.Red : e.ForeColor)) | ||||
|                 { | ||||
|                     e.Graphics.DrawString(text, e.Font, brush, e.Bounds); | ||||
|                 } | ||||
|  | ||||
|                 e.DrawFocusRectangle(); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             private void OkButton_Click(object sender, EventArgs e) | ||||
|             { | ||||
|                 if (listBox.SelectedItem != null) | ||||
|                 { | ||||
|                     string selected = listBox.SelectedItem.ToString(); | ||||
|                     var match = steamAccounts.FirstOrDefault(kvp => | ||||
|                         selected.Contains(kvp.Key) && selected.Contains(kvp.Value)); | ||||
|                     SelectedSteamId = match.Key; | ||||
|                     DialogResult = DialogResult.OK; | ||||
|                     Close(); | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     MessageBox.Show("Please select a Steam account.", "Selection Required", MessageBoxButtons.OK, MessageBoxIcon.Warning); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										120
									
								
								EonaCat.NightReign/SteamIdSelectionForm.resx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								EonaCat.NightReign/SteamIdSelectionForm.resx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <root> | ||||
|   <!--  | ||||
|     Microsoft ResX Schema  | ||||
|      | ||||
|     Version 2.0 | ||||
|      | ||||
|     The primary goals of this format is to allow a simple XML format  | ||||
|     that is mostly human readable. The generation and parsing of the  | ||||
|     various data types are done through the TypeConverter classes  | ||||
|     associated with the data types. | ||||
|      | ||||
|     Example: | ||||
|      | ||||
|     ... ado.net/XML headers & schema ... | ||||
|     <resheader name="resmimetype">text/microsoft-resx</resheader> | ||||
|     <resheader name="version">2.0</resheader> | ||||
|     <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> | ||||
|     <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader> | ||||
|     <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data> | ||||
|     <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data> | ||||
|     <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> | ||||
|         <value>[base64 mime encoded serialized .NET Framework object]</value> | ||||
|     </data> | ||||
|     <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> | ||||
|         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value> | ||||
|         <comment>This is a comment</comment> | ||||
|     </data> | ||||
|                  | ||||
|     There are any number of "resheader" rows that contain simple  | ||||
|     name/value pairs. | ||||
|      | ||||
|     Each data row contains a name, and value. The row also contains a  | ||||
|     type or mimetype. Type corresponds to a .NET class that support  | ||||
|     text/value conversion through the TypeConverter architecture.  | ||||
|     Classes that don't support this are serialized and stored with the  | ||||
|     mimetype set. | ||||
|      | ||||
|     The mimetype is used for serialized objects, and tells the  | ||||
|     ResXResourceReader how to depersist the object. This is currently not  | ||||
|     extensible. For a given mimetype the value must be set accordingly: | ||||
|      | ||||
|     Note - application/x-microsoft.net.object.binary.base64 is the format  | ||||
|     that the ResXResourceWriter will generate, however the reader can  | ||||
|     read any of the formats listed below. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.binary.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|      | ||||
|     mimetype: application/x-microsoft.net.object.soap.base64 | ||||
|     value   : The object must be serialized with  | ||||
|             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter | ||||
|             : and then encoded with base64 encoding. | ||||
|  | ||||
|     mimetype: application/x-microsoft.net.object.bytearray.base64 | ||||
|     value   : The object must be serialized into a byte array  | ||||
|             : using a System.ComponentModel.TypeConverter | ||||
|             : and then encoded with base64 encoding. | ||||
|     --> | ||||
|   <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"> | ||||
|     <xsd:import namespace="http://www.w3.org/XML/1998/namespace" /> | ||||
|     <xsd:element name="root" msdata:IsDataSet="true"> | ||||
|       <xsd:complexType> | ||||
|         <xsd:choice maxOccurs="unbounded"> | ||||
|           <xsd:element name="metadata"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" use="required" type="xsd:string" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="assembly"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:attribute name="alias" type="xsd:string" /> | ||||
|               <xsd:attribute name="name" type="xsd:string" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="data"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|                 <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" /> | ||||
|               <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" /> | ||||
|               <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" /> | ||||
|               <xsd:attribute ref="xml:space" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|           <xsd:element name="resheader"> | ||||
|             <xsd:complexType> | ||||
|               <xsd:sequence> | ||||
|                 <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" /> | ||||
|               </xsd:sequence> | ||||
|               <xsd:attribute name="name" type="xsd:string" use="required" /> | ||||
|             </xsd:complexType> | ||||
|           </xsd:element> | ||||
|         </xsd:choice> | ||||
|       </xsd:complexType> | ||||
|     </xsd:element> | ||||
|   </xsd:schema> | ||||
|   <resheader name="resmimetype"> | ||||
|     <value>text/microsoft-resx</value> | ||||
|   </resheader> | ||||
|   <resheader name="version"> | ||||
|     <value>2.0</value> | ||||
|   </resheader> | ||||
|   <resheader name="reader"> | ||||
|     <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
|   <resheader name="writer"> | ||||
|     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> | ||||
|   </resheader> | ||||
| </root> | ||||
		Reference in New Issue
	
	Block a user