diff --git a/Patcher/.gitignore b/Patcher/.gitignore new file mode 100644 index 0000000..9c9348a --- /dev/null +++ b/Patcher/.gitignore @@ -0,0 +1,334 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +# *.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ diff --git a/Patcher/AutoPatcher/App.config b/Patcher/AutoPatcher/App.config new file mode 100644 index 0000000..88fa402 --- /dev/null +++ b/Patcher/AutoPatcher/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Patcher/AutoPatcher/ArmDisassembler.cs b/Patcher/AutoPatcher/ArmDisassembler.cs new file mode 100644 index 0000000..5c5be96 --- /dev/null +++ b/Patcher/AutoPatcher/ArmDisassembler.cs @@ -0,0 +1,301 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Gee.External.Capstone; +using Gee.External.Capstone.Arm; +//using PeNet; + +namespace Patcher +{ + public static class ArmDisassembler + { + public static AnalyzedFile Analyze(string FilePath, string AsmPath = null) + { + PeFile File = new(FilePath); + + SortedList AnalyzedCode = new(0x1000000); // Default capacity of 0x100000 was not enough for analyzing ntoskrnl.exe + + if ((AsmPath != null) && System.IO.File.Exists(AsmPath)) + { + using StreamReader Reader = new(AsmPath); + while (Reader.Peek() >= 0) + { + ArmInstruction Instruction = new(Reader.ReadLine()); + AnalyzedCode.Add(Instruction.Address, Instruction); + } + } + else + { + CapstoneArmDisassembler Disassembler = CapstoneDisassembler.CreateArmDisassembler(ArmDisassembleMode.Thumb); + + // Initially use a Dictionary and sort it afterwards. For analyzing ntoskrnl.exe this is about 60 times faster than using a SortedList from the start. + // Default capacity of 0x100000 was not enough for analyzing ntoskrnl.exe + Dictionary TempCode = new(0x1000000); + + // Analyze from entrypoint + Analyze(Disassembler, File.Sections, TempCode, (UInt32)(File.ImageBase + File.EntryPoint)); + + // Analyze from exports + foreach (FunctionDescriptor Function in File.Exports) + Analyze(Disassembler, File.Sections, TempCode, (UInt32)Function.VirtualAddress); + + // Analyze from imports + foreach (FunctionDescriptor Function in File.Imports) + Analyze(Disassembler, File.Sections, TempCode, (UInt32)Function.VirtualAddress); + + // Analyze from runtime-functions + foreach (FunctionDescriptor Function in File.RuntimeFunctions) + Analyze(Disassembler, File.Sections, TempCode, (UInt32)Function.VirtualAddress); + + // Sort the instructions. + // SortedList is used, because it can be indexed by value (not only by key). + List Keys = TempCode.Keys.ToList(); + Keys.Sort(); + foreach (UInt32 Key in Keys) + AnalyzedCode.Add(Key, TempCode[Key]); + + if (AsmPath != null) + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(AsmPath)); + + using StreamWriter Writer = new(AsmPath, false); + for (int i = 0; i < AnalyzedCode.Count; i++) + { + Writer.WriteLine(AnalyzedCode.Values[i].ToString()); + } + } + } + + return new AnalyzedFile() { File = File, Code = AnalyzedCode }; + } + + public static void Analyze(CapstoneArmDisassembler Disassembler, List
Sections, Dictionary AnalyzedCode, UInt32 VirtualAddress) + { + VirtualAddress -= (VirtualAddress % 2); + List AddressesToAnalyze = new(); + AddressesToAnalyze.Add(VirtualAddress); + Section CurrentSection = null; + + while (AddressesToAnalyze.Count > 0) + { + UInt32 CurrentAddress = AddressesToAnalyze[0]; + if ((CurrentSection == null) || (CurrentAddress < CurrentSection.VirtualAddress) || (CurrentAddress > (CurrentSection.VirtualAddress + CurrentSection.VirtualSize))) + { + CurrentSection = Sections.Find(s => (CurrentAddress >= s.VirtualAddress) && (CurrentAddress < (s.VirtualAddress + s.VirtualSize)) && s.IsCode); + if (CurrentSection == null) + { + // throw new Exception("Address 0x" + CurrentAddress.ToString("X8") + " is not inside boundaries of code-sections"); + // Probably jumped to this address because data was disassembled as if it were code. Ignore this. + // return; + AddressesToAnalyze.RemoveAt(0); + continue; + } + } + + if (AnalyzedCode.ContainsKey(CurrentAddress)) + { + // return; + AddressesToAnalyze.RemoveAt(0); + continue; + } + + Gee.External.Capstone.Arm.ArmInstruction[] NewInstructions = Disassembler.Disassemble(CurrentSection.Buffer.Skip((int)CurrentAddress - (int)CurrentSection.VirtualAddress).ToArray(), CurrentAddress); + if (NewInstructions.Any()) + { + UInt32 StartAddress = (UInt32)NewInstructions.First().Address; + UInt32 EndAddress = (UInt32)NewInstructions.Last().Address; + + ArmInstruction PreviousInstruction = null; + foreach (Gee.External.Capstone.Arm.ArmInstruction DisassemblerInstruction in NewInstructions) + { + // ArmInstruction Instruction = new ArmInstruction(DisassemblerInstruction); + ArmInstruction Instruction = new() + { + Address = (UInt32)DisassemblerInstruction.Address, + Bytes = DisassemblerInstruction.Bytes, + Mnemonic = DisassemblerInstruction.Mnemonic, + Operand = DisassemblerInstruction.Operand.Replace("sb", "r9").Replace("sl", "r10").Replace("fp", "r11").Replace("ip", "r12") + }; + + if (AnalyzedCode.ContainsKey((UInt32)Instruction.Address)) + break; + + // Merge movw + movt into one command + // movw r3, #0x6010 + movt r3, #0x1000 = mov r3, #0x10006010 + UInt32 HighPart, LowPart; + string HighString, LowString; + if ((PreviousInstruction?.Mnemonic == "movt") && (Instruction.Mnemonic == "movw") && (PreviousInstruction.Operand.Split(new char[] { ',' })[0] == Instruction.Operand.Split(new char[] { ',' })[0])) + { + byte[] Combined = new byte[8]; + System.Buffer.BlockCopy(PreviousInstruction.Bytes, 0, Combined, 0, 4); + System.Buffer.BlockCopy(Instruction.Bytes, 0, Combined, 4, 4); + PreviousInstruction.Bytes = Combined; + PreviousInstruction.Mnemonic = "mov"; + + HighString = PreviousInstruction.Operand[(PreviousInstruction.Operand.IndexOf('#') + 1)..]; + HighPart = (HighString.Length >= 2) && (HighString.Substring(0, 2) == "0x") + ? UInt32.Parse(HighString[2..], System.Globalization.NumberStyles.HexNumber) + : UInt32.Parse(HighString); + LowString = Instruction.Operand[(Instruction.Operand.IndexOf('#') + 1)..]; + LowPart = (LowString.Length >= 2) && (LowString.Substring(0, 2) == "0x") + ? UInt32.Parse(LowString[2..], System.Globalization.NumberStyles.HexNumber) + : UInt32.Parse(LowString); + PreviousInstruction.Operand = string.Concat(PreviousInstruction.Operand.AsSpan(0, PreviousInstruction.Operand.IndexOf('#') + 1), "0x", ((HighPart << 16) + LowPart).ToString("X8")); + continue; + } + if ((PreviousInstruction?.Mnemonic == "movw") && (Instruction.Mnemonic == "movt") && (PreviousInstruction.Operand.Split(new char[] { ',' })[0] == Instruction.Operand.Split(new char[] { ',' })[0])) + { + byte[] Combined = new byte[8]; + System.Buffer.BlockCopy(PreviousInstruction.Bytes, 0, Combined, 0, 4); + System.Buffer.BlockCopy(Instruction.Bytes, 0, Combined, 4, 4); + PreviousInstruction.Bytes = Combined; + PreviousInstruction.Mnemonic = "mov"; + + HighString = Instruction.Operand[(Instruction.Operand.IndexOf('#') + 1)..]; + HighPart = (HighString.Length >= 2) && (HighString.Substring(0, 2) == "0x") + ? UInt32.Parse(HighString[2..], System.Globalization.NumberStyles.HexNumber) + : UInt32.Parse(HighString); + LowString = PreviousInstruction.Operand[(PreviousInstruction.Operand.IndexOf('#') + 1)..]; + LowPart = (LowString.Length >= 2) && (LowString.Substring(0, 2) == "0x") + ? UInt32.Parse(LowString[2..], System.Globalization.NumberStyles.HexNumber) + : UInt32.Parse(LowString); + PreviousInstruction.Operand = string.Concat(PreviousInstruction.Operand.AsSpan(0, PreviousInstruction.Operand.IndexOf('#') + 1), "0x", ((HighPart << 16) + LowPart).ToString("X8")); + continue; + } + + AnalyzedCode.Add((UInt32)Instruction.Address, Instruction); + + int IndexOfIndirectConstant = Instruction.Operand.IndexOf("[pc, #0x"); + if (IndexOfIndirectConstant >= 0) + { + int IndexOfEnd = Instruction.Operand.IndexOf("]", IndexOfIndirectConstant); + string PCOffsetString = Instruction.Operand.Substring(IndexOfIndirectConstant + 8, IndexOfEnd - IndexOfIndirectConstant - 8); + UInt32 PCOffset = UInt32.Parse(PCOffsetString, System.Globalization.NumberStyles.HexNumber); + UInt32 PC = (UInt32)Instruction.Address + 4; + UInt32 PCforIndirect = PC - (PC % 4); + UInt32 VirtualAddressOfIndirectConstant = PCforIndirect + PCOffset; + + // If the address is outside the range of the section, then this is probably data which is compiled as code. + // In this case we will ignore this and not do this part of the analysis. + if ((VirtualAddressOfIndirectConstant >= CurrentSection.VirtualAddress) && (VirtualAddressOfIndirectConstant < (CurrentSection.VirtualAddress + CurrentSection.VirtualSize))) + { + UInt32 RawOffsetOfIndirectConstant = VirtualAddressOfIndirectConstant - CurrentSection.VirtualAddress; + UInt32 IndirectConstant = BitConverter.ToUInt32(CurrentSection.Buffer, (int)RawOffsetOfIndirectConstant); + Instruction.Operand = Instruction.Operand.Substring(0, IndexOfIndirectConstant) + "#0x" + IndirectConstant.ToString("x8") + Instruction.Operand[(IndexOfEnd + 1)..]; + } + } + + if (JumpCommands.Contains(Instruction.Mnemonic)) + { + UInt32 NewAddress = UInt32.Parse(Instruction.Operand[(Instruction.Operand.IndexOf("#0x") + 3)..], System.Globalization.NumberStyles.HexNumber); + NewAddress -= (NewAddress % 2); + if (((NewAddress < StartAddress) || (NewAddress > EndAddress)) && !AddressesToAnalyze.Any(a => a == NewAddress)) + AddressesToAnalyze.Add(NewAddress); + } + + PreviousInstruction = Instruction; + } + } + + AddressesToAnalyze.RemoveAt(0); + } + } + + public static string[] JumpCommands = new string[] + { + "b", "b.w", "bl", "bl.w", "beq", "beq.w", "bne", "bne.w", "bhs", "bhs.w", "blo", "blo.w", + "bmi", "bmi.w", "bpl", "bpl.w", "bvs", "bvs.w", "bvc", "bvc.w", "bhi", "bhi.w", "bls", "bls.w", + "bge", "bge.w", "blt", "blt.w", "bgt", "bgt.w", "ble", "ble.w", "bal", "bal.w", "cbnz", "cbz" + }; + + public static string[] ConditionalJumpInstructions = new string[] + { + "beq", "beq.w", "bne", "bne.w", "bhs", "bhs.w", "blo", "blo.w", "bmi", "bmi.w", + "bpl", "bpl.w", "bvs", "bvs.w", "bvc", "bvc.w", "bhi", "bhi.w", "bls", "bls.w", + "bge", "bge.w", "blt", "blt.w", "bgt", "bgt.w", "ble", "ble.w", "bal", "bal.w", "cbnz", "cbz" + }; + + public static string WriteCode(SortedDictionary AnalyzedCode) + { + StringBuilder Code = new(1000); + + foreach (var Instruction in AnalyzedCode) + { + Code.AppendFormat("{0:X}: \t {1} \t {2}\r\n", Instruction.Value.Address, Instruction.Value.Mnemonic, Instruction.Value.Operand); + } + + return Code.ToString(); + } + } + + public class ArmInstruction + { + public UInt32 Address; + public byte[] Bytes; + public string Mnemonic; + public string Operand; + + public ArmInstruction() + { + } + + public ArmInstruction(string Assembly) + { + Address = UInt32.Parse(Assembly.Substring(0, 8), System.Globalization.NumberStyles.HexNumber); + string Hex = Assembly.Substring(12, 24).Trim(); + Bytes = new byte[(Hex.Length + 1) / 3]; + for (int i = 0; i < Bytes.Length; i++) + Bytes[i] = byte.Parse(Hex.Substring(i * 3, 2), System.Globalization.NumberStyles.HexNumber); + Mnemonic = Assembly.Substring(39, 16).Trim(); + Operand = Assembly[55..]; + } + + public override string ToString() + { + StringBuilder Result = new(); + + Result.Append(Address.ToString("X8")); // 0 + Result.Append(" "); + for (int i = 0; i < Bytes.Length; i++) // 12 + { + Result.Append(Bytes[i].ToString("X2")); + Result.Append(' '); + } + Result.Append(new String(' ', (8 - Bytes.Length) * 3)); + Result.Append(" "); + Result.Append(Mnemonic.PadRight(16)); // 39 + Result.Append(Operand); // 55 + + return Result.ToString(); + } + } + + public class AnalyzedFile + { + public PeFile File; + public SortedList Code; + } +} diff --git a/Patcher/AutoPatcher/AutoPatcher.csproj b/Patcher/AutoPatcher/AutoPatcher.csproj new file mode 100644 index 0000000..210c5f2 --- /dev/null +++ b/Patcher/AutoPatcher/AutoPatcher.csproj @@ -0,0 +1,56 @@ + + + net5.0-windows + WinExe + Patcher + false + true + + + bin\x86\Debug\ + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + MinimumRecommendedRules.ruleset + + + bin\x64\Debug\ + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + MinimumRecommendedRules.ruleset + + + + ArmCompiler.cs + + + ByteOperations.cs + + + HelperClasses.cs + + + ObjectFileParser.cs + + + PatchEngine.cs + + + PeFile.cs + + + LICENSE + + + Always + + + + + + + + \ No newline at end of file diff --git a/Patcher/AutoPatcher/AutoPatcher.csproj.user b/Patcher/AutoPatcher/AutoPatcher.csproj.user new file mode 100644 index 0000000..6696a72 --- /dev/null +++ b/Patcher/AutoPatcher/AutoPatcher.csproj.user @@ -0,0 +1,9 @@ + + + + + + Form + + + \ No newline at end of file diff --git a/Patcher/AutoPatcher/BootUnllockAndRootAccessPatchScript.pds b/Patcher/AutoPatcher/BootUnllockAndRootAccessPatchScript.pds new file mode 100644 index 0000000..f2d6e75 --- /dev/null +++ b/Patcher/AutoPatcher/BootUnllockAndRootAccessPatchScript.pds @@ -0,0 +1,698 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Patch Definition Script for Boot Unlock and Root Access on Windows Mobile +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +PatchDefinition Name="RootAccess-MainOS" VersionFrom="EFIESP\Windows\System32\Boot\mobilestartup.efi" + + PatchFile Path="Windows\System32\sspisrv.dll" + + JumpToImport "RpcImpersonateClient" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "CheckLowboxAccess" // Optional here + PatchCode + MOVS R1, #1 + STR R1, [R0] + MOVS R0, #0 + BX LR + EndPatch + PatchChecksum + + PatchFile Path="Windows\System32\NtlmShared.dll" + + JumpToExport "MsvpPasswordValidate" + PatchCode + MOVS R0, #1 + BX LR + EndPatch + PatchChecksum + + PatchFile Path="Windows\System32\pacmanserver.dll" + + FindFirstUnicode "GetMaxCountForDeployedApp" + JumpToReference + FindPreviousInstruction "PUSH.W" + PatchCode + LDR R1, =0x7FFFFFFF + STR R1, [R0] + MOVS R0, #0 + BX LR + EndPatch + PatchChecksum + + PatchFile Path="Windows\System32\mscoree.dll" + + JumpToImport "GetModuleFileNameW" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "CompareWithWhiteList" // Optional here + PatchCode + MOVS R0, #0 + BX LR + EndPatch + PatchChecksum + + PatchFile Path="Windows\System32\DeploymentExt.dll" + + FindFirstUnicode "MaxUnsignedApp" + JumpToReference + FindValue 0x800413A0 + FindPreviousConditionalJump + MakeJumpUnconditional + PatchChecksum + + PatchFile Path="Windows\System32\ntoskrnl.exe" + + // Fase 1: find all kernel-functions + + JumpToExport "SeAccessCheckWithHint" + CreateLabel "SeAccessCheckWithHint" + + FindFunctionCall R0 = "ADD R0, SP, #0x7C" R1 = "MOV R1, R?" + JumpToTarget + CreateLabel "SepFilterToDiscretionary" + + JumpToReference R0 = "ADDS R0, R?, #0xD0" + FindPreviousInstruction "PUSH" + FindPreviousInstruction "PUSH" + CreateLabel "SeAccessCheckByType" + + FindFunctionCall R0 = "ADDS R0, R?, #0xF8" R1 = "MOV R1, R?" R2 = "LDR R2, [R?,#0x28]" R3 = "MOV R3, R?" + JumpToTarget + CreateLabel "SepConstrainByMandatory" + + JumpBack // to SeAccessCheckByType + JumpBack // to SepFilterToDiscretionary + + JumpToReference R1 = "LDR R1, [R?,#8]" + FindPreviousInstruction "PUSH" + CreateLabel "SepCommonAccessCheckEx" + + FindFunctionCall Result = "STR R0, [SP,#0xD4]" + JumpToTarget + CreateLabel "SepAccessCheckEx" + + JumpBack // to SepCommonAccessCheckEx + JumpBack // to SepFilterToDiscretionary + + JumpToReference R0 = "ADDS R0, R?, #0x130" + FindPreviousInstruction "PUSH" + FindPreviousInstruction "PUSH" + CreateLabel "SepAccessCheckAndAuditAlarm" + + FindFunctionCall R0 = "LDR R0, [R?,#0x130]" R1 = "MOV R1, R?" R2 = "LDR R2, [R?,#0x50]" R3 = "MOV R3, R?" + JumpToTarget + CreateLabel "SepConstrainByConstraintMask" + FindNextConditionalJump + JumpToTarget + CreateLabel "SepConstrainByConstraintMask_FunctionChunk01" + + JumpBack // to SepConstrainByConstraintMask + JumpBack // to SepAccessCheckAndAuditAlarm + JumpBack // to SepFilterToDiscretionary + JumpBack // to SeAccessCheckWithHint + + FindFunctionCall R0 = "ADD R0, SP, #0x88" R1 = "MOV R1, R?" + JumpToTarget + CreateLabel "SepMandatoryToDiscretionary" + JumpBack + + FindFunctionCall Result = "STR R0, [SP,#0x70]" + JumpToTarget + CreateLabel "SepAccessCheck" + + JumpToExport "SePrivilegeCheck" + FindFunctionCall + JumpToTarget + CreateLabel "SepPrivilegeCheck" + + JumpToExport "SeSinglePrivilegeCheck" + CreateLabel "SeSinglePrivilegeCheck" + + JumpToExport "ObReferenceObjectByHandleWithTag" + CreateLabel "ObReferenceObjectByHandleWithTag" + + // Fase 2: patches + + JumpToLabel "SeAccessCheckByType" + + // Patch 1: + FindNextValue 0xC0000022 + FindPreviousConditionalJump + FindPreviousConditionalJump + FindPreviousConditionalJump + FindPreviousConditionalJump + MakeJumpUnconditional + FindNextValue 0xC0000022 + + // Patch 2: + FindNextValue 0xC0000022 + FindStore + FindPreviousConditionalJump + MakeJumpUnconditional + + // Patch 3: + FindNextValue 0xC0000022 + FindPreviousConditionalJump + MakeJumpUnconditional // This jump is right above the value 0xC0000022. After patch the pointer is back on that value. + // FindNextValue 0xC0000022 + + // Patch 4: + FindNextValue 0xC0000022 + FindPreviousConditionalJump + MakeJumpUnconditional // This jump is right above the value 0xC0000022. After patch the pointer is back on that value. + // FindNextValue 0xC0000022 + + // Patch 5: + FindNextValue 0xC0000022 + FindNextInstruction "BNE" + JumpToTarget + CreateLabel "TargetPatch5" + JumpBack + FindPreviousInstruction "BEQ" + PatchCode + B TargetPatch5 + EndPatch + + // Patch 6: + FindNextValue 0xC0000022 + FindNextConditionalJump + MakeJumpUnconditional + + // Patch 7: + FindNextValue 0xC0000022 + FindStore + FindPreviousConditionalJump + MakeJumpUnconditional + + // Patch 8: + FindNextValue 0xC0000022 + JumpToReference + ClearInstruction + JumpBack + + // Patch 9: + FindNextValue 0xC0000022 + JumpToReference + ClearInstruction + JumpBack + + JumpToLabel "SepAccessCheckAndAuditAlarm" + + // Patch 10: + FindNextValue 0xC0000022 + FindPreviousConditionalJump + MakeJumpUnconditional + FindNextValue 0xC0000022 + + // Patch 11: + FindNextValue 0xC0000022 + FindStore + CreateLabel "Patch11" + FindNextConditionalJump + JumpToTarget + CreateLabel "TargetPatch11" + JumpToLabel "Patch11" + PatchCode + B TargetPatch11 + EndPatch + + // Patch 12: + FindNextValue 0xC0000022 + PatchCode + MOV.W R2, #0 + EndPatch + + JumpToLabel "SepCommonAccessCheckEx" + + // Patch 13: + FindNextInstruction "TST" + FindNextInstruction "TST" + FindPreviousConditionalJump + ClearInstruction + + JumpToLabel "SeAccessCheckWithHint" + + // Patch 14: + FindNextInstruction "BEQ" + MakeJumpUnconditional + + JumpToLabel "SeSinglePrivilegeCheck" + + // Patch 15: + PatchCode + MOVS R0, #1 + BX LR + EndPatch + + JumpToLabel "ObReferenceObjectByHandleWithTag" + + FindFunctionCall + JumpToTarget + CreateLabel "ObpReferenceObjectByHandleWithTag" + FindInstructionPattern "LDR R?, [R?,#0x74]; CMP R?, #0; BNE ?" InstructionIndex = 2 + JumpToTarget + + // Patch 16: + FindNextConditionalJump + MakeJumpUnconditional // This jump is right above the value 0xC0000022. After patch the pointer is on the error-value. + + // Patch 17: + JumpToReference + ClearInstruction + JumpBack + JumpBack + + // Patch 18: + FindNextValue 0xC0000022 + JumpToReference + ClearInstruction + + JumpToLabel "SepPrivilegeCheck" + + // Patch 19: + PatchCode + MOVS R0, #1 + BX LR + EndPatch + + JumpToLabel "SepMandatoryToDiscretionary" + + // Patch 20: + PatchCode + MOVS R0, #0 + BX LR + EndPatch + + JumpToLabel "SepAccessCheckEx" + + // Patch 21: + FindNextValue 0x2000000 + CreateLabel "Patch21" + FindNextInstruction "B" + JumpToTarget + CreateLabel "TargetPatch21" + JumpToLabel "Patch21" + PatchCode + B TargetPatch21 + EndPatch + FindNextValue 0xC0000022 + + // Patch 22: + FindNextValue 0xC0000022 + FindPreviousConditionalJump + MakeJumpUnconditional // This jump is right above the value 0xC0000022. After patch the pointer is back on that value. + // FindNextValue 0xC0000022 + + // Patch 23: + JumpToReference 0 + ClearInstruction + JumpBack + + // Patch 24: + JumpToReference 1 + ClearInstruction + JumpBack + + // Patch 25: + JumpToReference 2 + ClearInstruction + JumpBack + + // Patch 26: + FindNextValue 0xC0000022 + FindPreviousConditionalJump + MakeJumpUnconditional + FindNextValue 0xC0000022 + + // Patch 27: + FindNextValue 0xC0000022 + FindPreviousConditionalJump + MakeJumpUnconditional + FindNextValue 0xC0000022 + + // Patch 28: + JumpToReference + ClearInstruction + + JumpToLabel "SepAccessCheck" + + // Patch 29: + FindFunctionCall R0 = "LDR R0, [SP,#0x28]" + JumpToTarget + CreateLabel "SepNormalAccessCheck" + JumpBack + FindNextInstruction "TST" + FindNextConditionalJump + ClearInstruction + + // Patch 30: + FindFunctionCall R0 = "MOV R0, R?" R1 = "MOV R1, R?" R2 = "MOV R2, R?" R3 = "LDR R3, [SP,#0x38]" + JumpToTarget + CreateLabel "SepMaximumAccessCheck" + JumpBack + FindNextConditionalJump + ClearInstruction + + // Patch 31: + FindNextConditionalJump + ClearInstruction + + // Patch 32: + FindNextValue 0xC0000022 + JumpToReference 1 + ClearInstruction + JumpBack + + // Patch 33: + JumpToReference 2 + ClearInstruction + JumpBack + + // Patch 34: + FindNextValue 0xC0000022 + FindPreviousInstruction "MOVS" + FindPreviousInstruction "MOVS" + JumpToReference + ClearInstruction + JumpBack + FindNextValue 0xC0000022 + + // Patch 35: + JumpToReference CodePattern = "BEQ" + ClearInstruction + JumpBack + + // Patch 36: + JumpToReference CodePattern = "MOVS; B" + FindPreviousInstruction "B" + JumpToTarget + CreateLabel "TargetPatch36" + JumpBack + FindPreviousInstruction "CMP" + PatchCode + B.W TargetPatch36 + EndPatch + JumpBack + + // Patch 37: + JumpToReference CodePattern = "STR; B" + FindPreviousConditionalJump + MakeJumpUnconditional + + // Patch 38: + // Stay in function-chunk. Error-code is between previous two patches. + FindPreviousValue 0xC0000022 + FindPreviousConditionalJump + MakeJumpUnconditional + + JumpToLabel "SepConstrainByMandatory" + + // Patch 39: + FindNextInstruction "BNE" + JumpToTarget + FindNextInstruction "CBNZ" + JumpToTarget + CreateLabel "TargetPatch39" + JumpBack + FindPreviousInstruction "BEQ" + PatchCode + B TargetPatch39 + EndPatch + JumpBack + + // Patch 40: + FindNextInstruction "B" + JumpToTarget + FindNextInstruction "CBNZ" + JumpToTarget + CreateLabel "TargetPatch40" + JumpBack + FindPreviousInstruction "BEQ" + PatchCode + B TargetPatch40 + EndPatch + + JumpToLabel "SepFilterToDiscretionary" + + // Patch 41: + PatchCode + MOVS R0, #0 + BX LR + EndPatch + + JumpToLabel "SepConstrainByConstraintMask_FunctionChunk01" + + // Patch 42: + FindNextInstruction "TST" + FindNextInstruction "CBNZ" + JumpToTarget + CreateLabel "TargetPatch42" + JumpBack + FindPreviousInstruction "BEQ" + PatchCode + B TargetPatch42 + EndPatch + + // Patch 43: + FindNextInstruction "TST" + FindNextInstruction "CBNZ" + JumpToTarget + CreateLabel "TargetPatch43" + JumpBack + FindPreviousInstruction "BEQ" + FindPreviousInstruction "BEQ" // This one is actually not necessary. Kept here for consistency. + PatchCode + B TargetPatch43 + EndPatch + + PatchChecksum + +PatchDefinition Name="SecureBootHack-MainOS" VersionFrom="EFIESP\Windows\System32\Boot\mobilestartup.efi" + + PatchFile Path="Windows\System32\BOOT\winload.efi" + + FindFirstAscii "1.3.6.1.4.1.311.61.4.1" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "ImgpValidateImageHash" + PatchCode + MOVS R0, #0 + BX LR + EndPatch + PatchChecksum + + PatchFile Path="Windows\System32\ci.dll" + + JumpToImport "PsGetProcessSignatureLevel" + JumpToReference + CreateLabel "PsGetProcessSignatureLevelWrapper" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "CipReportAndReprieveUMCIFailure" + FindNextInstruction "TST.W" + FindNextConditionalJump + MakeJumpUnconditional "BNE" // BNE -> B, BEQ -> NOP + PatchChecksum + +PatchDefinition Name="SecureBootHack-V1-EFIESP" VersionFrom="EFIESP\Windows\System32\Boot\mobilestartup.efi" RelativePath="EFIESP" RelativeOutputPath="SecureBootHack-V1" + + PatchFile Path="Windows\System32\boot\mobilestartup.efi" // Symbols taken from pdb from version 10.0.10586.107 + + FindFirstAscii "1.3.6.1.4.1.311.61.4.1" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "ImgpValidateImageHash" + PatchCode + MOVS R0, #0 + BX LR + EndPatch + FindFirstUnicode "BootDebugPolicyApplied" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "ApplyBootDebugPolicy" + PatchCode // This patch is for the new unlock for Lumia Spec A + MOVS R0, #0 + BX LR + EndPatch + PatchChecksum + + PatchFile Path="efi\boot\bootarm.efi" + + FindFirstAscii "1.3.6.1.4.1.311.61.4.1" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "ImgpValidateImageHash" + PatchCode + MOVS R0, #0 + BX LR + EndPatch + PatchChecksum + +PatchDefinition Name="SecureBootHack-V2-EFIESP" VersionFrom="EFIESP\Windows\System32\Boot\mobilestartup.efi" RelativePath="EFIESP" + + PatchFile Path="Windows\System32\boot\mobilestartup.efi" + + FindFirstAscii "MZ" + CreateLabel "ImageBase" + FindFirstAscii "1.3.6.1.4.1.311.61.4.1" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "ImgpValidateImageHash" + PatchCode + MOVS R0, #0 + BX LR + EndPatch + FindFirstUnicode "BootDebugPolicyApplied" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "ApplyBootDebugPolicy" + PatchCode + MOVS R0, #0 + BX LR + EndPatch + CreateLabel "EnterMassStorageModeShellCode" // Use the left-over space of the ApplyBootDebugPolicy-function to insert shell-code later on + FindFirstUnicode "MassStorageFlag" + CreateLabel "MassStorageName" + PatchUnicode "Heathcliff74MSM" + FindFirstBytes "41 E5 C1 A0 CE 73 7F 46 88 EC D4 4F 92 34 50 4A" + CreateLabel "MassStorageGuid" + JumpToLabel "MassStorageName" + JumpToReference + FindNextInstruction "BL" + JumpToTarget + CreateLabel "EfiGetVariableVolatile" + FindValue 2 + FindNextConditionalJump + MakeJumpUnconditional "BEQ" + FindFirstUnicode "\Windows\System32\boot\ui\boot.ums.waiting.bmpx" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "EnterMassStorageMode" + JumpToReference + PatchCode + B.W EnterMassStorageModeShellCode + EndPatch + CreateLabel ReturnFromMassStorageMode + FindFirstValue 0x26000145 + IfNotFoundGo PatchForSetErrorDone + FindPreviousInstruction "PUSH.W" + CreateLabel "SetError" + PatchCode + MOVS R0, #1 + BX LR + EndPatch + PatchForSetErrorDone: + FindFirstUnicode "DeviceIDVersion" + JumpToReference + FindNextInstruction "BL" + JumpToTarget + CreateLabel "EfiSetVariable" + FindFirstAscii "charge: DisplayPowerState protocol successfully loaded" + JumpToReference + FindPreviousInstruction "PUSH.W" + CreateLabel "InitGraphicsSubsystem" + FindNextInstruction "BL" + JumpToTarget + CreateLabel "BlpArchQueryCurrentContextType" + JumpBack + FindNextInstruction "BL" + FindNextInstruction "BL" + FindNextInstruction "BL" + JumpToTarget + CreateLabel "BlpArchSwitchContext" + JumpBack + FindNextInstruction "LDR" + JumpToTarget + CreateLabel "EfiBS" + JumpToLabel "EnterMassStorageModeShellCode" + PatchCode + MOV R0, PC + LDR R1, =(ApplyBootDebugPolicy - ImageBase + 8) // Subtract (Offset of shell-code + 4) + SUB R0, R0, R1 // R0 = relocated base of mobilestartup.efi + PUSH {R4-R6} + SUB SP, SP, #4 + MOV R4, R0 // R4 = relocated base of mobilestartup.efi + + LDR R3, =(MassStorageName - ImageBase) // Offset of NV var name (which is patched to "Heathcliff74MSM") + ADD R0, R4, R3 + LDR R3, =(MassStorageGuid - ImageBase) // Offset of NV var Guid + ADD R1, R4, R3 + MOVS R2, #3 // Non-volatile, boot-services + MOVS R3, #0 // Data-size + STR R3, [SP] // Pointer to data-buffer = NULL + LDR R6, =(EfiSetVariable - ImageBase + 1) // Offset of SetVariable + 1 + ADD R5, R4, R6 + BLX R5 // EfiSetVariable -> Delete variable + + LDR R1, =(BlpArchQueryCurrentContextType - ImageBase + 1) // Offset to first thread-function + 1 + ADD R5, R4, R1 + BLX R5 + MOV R6, R0 + CMP R6, #1 + BEQ ContextSwitchDone1 + MOVS R0, #1 + LDR R1, =(BlpArchSwitchContext - ImageBase + 1) // Offset to second thread-function + 1 + ADD R5, R4, R1 + BLX R5 + ContextSwitchDone1: + + LDR R0, =(EfiBS - ImageBase) // Offset of pointer to BootServices function-table + ADD R1, R4, R0 // R1 = pointer to pointer to BootServices function-table + LDR R1, [R1] // R1 = pointer to BootServices function-table + LDR.W R5, [R1,#0xAC] // LocateProtocol + ADR R0, VarServicesGuid // This is relative, no need to relocate + MOVS R1, #0 + MOV R2, SP + BLX R5 // LocateProtocol - pVarServices in [SP] + LDR R5, [SP] // R5 = Pointer to VariableServices interface + LDR R5, [R5,#4] // R5 = pointer to FlushVariableNV() + CMP R5, #0 + BNE PointerFound + LDR R5, [SP] // R5 = Pointer to VariableServices interface + LDR R5, [R5,#8] // R5 = pointer to FlushVariableNV() + PointerFound: + BLX R5 // FlushVariableNV() + + CMP R6, #1 + BEQ ContextSwitchDone2 + MOV R0, R6 + LDR R1, =(BlpArchSwitchContext - ImageBase + 1) // Offset to second thread-function + 1 + ADD R5, R4, R1 + BLX R5 + ContextSwitchDone2: + + LDR R6, =(EnterMassStorageMode - ImageBase + 1) // Offset of EnterMassStorageMode + 1 + ADD R5, R4, R6 + BLX R5 // EnterMassStorageMode + + LDR R6, =(ReturnFromMassStorageMode - ImageBase + 1) // Offset of return address + 1 + ADD R0, R4, R6 + ADD SP, SP, #4 + POP {R4-R6} + BX R0 + + VarServicesGuid: + DCD 0xf9085b9d + DCW 0x9304, 0x40fb + DCB 0x8f, 0xe0, 0x4a, 0xee, 0x3b, 0x1a, 0x78, 0x4b + EndPatch + PatchChecksum diff --git a/Patcher/AutoPatcher/FolderSelectDialog.cs b/Patcher/AutoPatcher/FolderSelectDialog.cs new file mode 100644 index 0000000..01aeb3e --- /dev/null +++ b/Patcher/AutoPatcher/FolderSelectDialog.cs @@ -0,0 +1,120 @@ +// This class was found online. +// Original author is probably: Swizzy +// https://github.com/ttgxdinger/Random/blob/master/CPUKey%20Checker/CPUKey%20Checker/FolderSelectDialog.cs + +using System; +using System.Windows.Forms; + +namespace WPinternals +{ + /// + /// Wraps System.Windows.Forms.OpenFileDialog to make it present + /// a vista-style dialog. + /// + public class FolderSelectDialog + { + // Wrapped dialog + private readonly OpenFileDialog ofd = null; + + /// + /// Default constructor + /// + public FolderSelectDialog() + { + ofd = new OpenFileDialog + { + Filter = "Folders|\n", + AddExtension = false, + CheckFileExists = false, + DereferenceLinks = true, + Multiselect = false + }; + } + + #region Properties + + /// + /// Gets/Sets the initial folder to be selected. A null value selects the current directory. + /// + public string InitialDirectory + { + get { return ofd.InitialDirectory; } + set { ofd.InitialDirectory = string.IsNullOrEmpty(value) ? Environment.CurrentDirectory : value; } + } + + /// + /// Gets/Sets the title to show in the dialog + /// + public string Title + { + get { return ofd.Title; } + set { ofd.Title = value ?? "Select a folder"; } + } + + /// + /// Gets the selected folder + /// + public string FileName + { + get { return ofd.FileName; } + } + + #endregion + + #region Methods + + /// + /// Shows the dialog + /// + /// True if the user presses OK else false + public bool ShowDialog() + { + return ShowDialog(IntPtr.Zero); + } + + /// + /// Shows the dialog + /// + /// Handle of the control to be parent + /// True if the user presses OK else false + public bool ShowDialog(IntPtr hWndOwner) + { + var fbd = new FolderBrowserDialog + { + Description = this.Title, + SelectedPath = this.InitialDirectory, + ShowNewFolderButton = false + }; + if (fbd.ShowDialog(new WindowWrapper(hWndOwner)) != DialogResult.OK) + { + return false; + } + + ofd.FileName = fbd.SelectedPath; + + return true; + } + + #endregion + } + + /// + /// Creates IWin32Window around an IntPtr + /// + public class WindowWrapper : IWin32Window + { + /// + /// Constructor + /// + /// Handle to wrap + public WindowWrapper(IntPtr handle) + { + Handle = handle; + } + + /// + /// Original ptr + /// + public IntPtr Handle { get; } + } +} diff --git a/Patcher/AutoPatcher/MainForm.Designer.cs b/Patcher/AutoPatcher/MainForm.Designer.cs new file mode 100644 index 0000000..1c19cd8 --- /dev/null +++ b/Patcher/AutoPatcher/MainForm.Designer.cs @@ -0,0 +1,423 @@ +namespace Patcher +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.txtVisualStudioPath = new System.Windows.Forms.TextBox(); + this.FolderBrowserDialog = new System.Windows.Forms.FolderBrowserDialog(); + this.OpenFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.SaveFileDialog = new System.Windows.Forms.SaveFileDialog(); + this.cmdVisualStudioPath = new System.Windows.Forms.Button(); + this.cmdInputFolder = new System.Windows.Forms.Button(); + this.txtInputFolder = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.cmdOutputFolder = new System.Windows.Forms.Button(); + this.txtOutputFolder = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.cmdPatchDefinitionsFile = new System.Windows.Forms.Button(); + this.txtPatchDefinitionsFile = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.txtConsole = new System.Windows.Forms.TextBox(); + this.label9 = new System.Windows.Forms.Label(); + this.cmdCompile = new System.Windows.Forms.Button(); + this.cmdPatch = new System.Windows.Forms.Button(); + this.cmdScriptFile = new System.Windows.Forms.Button(); + this.txtScriptFile = new System.Windows.Forms.TextBox(); + this.label5 = new System.Windows.Forms.Label(); + this.cmdBackupFolder = new System.Windows.Forms.Button(); + this.txtBackupFolder = new System.Windows.Forms.TextBox(); + this.label6 = new System.Windows.Forms.Label(); + this.label7 = new System.Windows.Forms.Label(); + this.CapstoneLink = new System.Windows.Forms.LinkLabel(); + this.label8 = new System.Windows.Forms.Label(); + this.CapstoneNetLink = new System.Windows.Forms.LinkLabel(); + this.label10 = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(15, 13); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(191, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Path to Visual Studio with ARM32 SDK"; + // + // txtVisualStudioPath + // + this.txtVisualStudioPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtVisualStudioPath.Location = new System.Drawing.Point(18, 29); + this.txtVisualStudioPath.Name = "txtVisualStudioPath"; + this.txtVisualStudioPath.Size = new System.Drawing.Size(665, 20); + this.txtVisualStudioPath.TabIndex = 1; + // + // OpenFileDialog + // + this.OpenFileDialog.FileName = "openFileDialog1"; + // + // cmdVisualStudioPath + // + this.cmdVisualStudioPath.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdVisualStudioPath.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdVisualStudioPath.Location = new System.Drawing.Point(689, 28); + this.cmdVisualStudioPath.Name = "cmdVisualStudioPath"; + this.cmdVisualStudioPath.Size = new System.Drawing.Size(35, 22); + this.cmdVisualStudioPath.TabIndex = 2; + this.cmdVisualStudioPath.Text = "..."; + this.cmdVisualStudioPath.UseVisualStyleBackColor = true; + this.cmdVisualStudioPath.Click += new System.EventHandler(this.cmdVisualStudioPath_Click); + // + // cmdInputFolder + // + this.cmdInputFolder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdInputFolder.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdInputFolder.Location = new System.Drawing.Point(689, 224); + this.cmdInputFolder.Name = "cmdInputFolder"; + this.cmdInputFolder.Size = new System.Drawing.Size(35, 22); + this.cmdInputFolder.TabIndex = 8; + this.cmdInputFolder.Text = "..."; + this.cmdInputFolder.UseVisualStyleBackColor = true; + this.cmdInputFolder.Click += new System.EventHandler(this.cmdInputFolder_Click); + // + // txtInputFolder + // + this.txtInputFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtInputFolder.Location = new System.Drawing.Point(18, 225); + this.txtInputFolder.Name = "txtInputFolder"; + this.txtInputFolder.Size = new System.Drawing.Size(665, 20); + this.txtInputFolder.TabIndex = 7; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(15, 209); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(71, 13); + this.label2.TabIndex = 12; + this.label2.Text = "Input location"; + // + // cmdOutputFolder + // + this.cmdOutputFolder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdOutputFolder.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdOutputFolder.Location = new System.Drawing.Point(689, 322); + this.cmdOutputFolder.Name = "cmdOutputFolder"; + this.cmdOutputFolder.Size = new System.Drawing.Size(35, 22); + this.cmdOutputFolder.TabIndex = 12; + this.cmdOutputFolder.Text = "..."; + this.cmdOutputFolder.UseVisualStyleBackColor = true; + this.cmdOutputFolder.Click += new System.EventHandler(this.cmdOutputFolder_Click); + // + // txtOutputFolder + // + this.txtOutputFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtOutputFolder.Location = new System.Drawing.Point(18, 323); + this.txtOutputFolder.Name = "txtOutputFolder"; + this.txtOutputFolder.Size = new System.Drawing.Size(665, 20); + this.txtOutputFolder.TabIndex = 11; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(15, 307); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(125, 13); + this.label3.TabIndex = 15; + this.label3.Text = "Output location (optional)"; + // + // cmdPatchDefinitionsFile + // + this.cmdPatchDefinitionsFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdPatchDefinitionsFile.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdPatchDefinitionsFile.Location = new System.Drawing.Point(689, 91); + this.cmdPatchDefinitionsFile.Name = "cmdPatchDefinitionsFile"; + this.cmdPatchDefinitionsFile.Size = new System.Drawing.Size(35, 22); + this.cmdPatchDefinitionsFile.TabIndex = 4; + this.cmdPatchDefinitionsFile.Text = "..."; + this.cmdPatchDefinitionsFile.UseVisualStyleBackColor = true; + this.cmdPatchDefinitionsFile.Click += new System.EventHandler(this.cmdPatchDefinitionsFile_Click); + // + // txtPatchDefinitionsFile + // + this.txtPatchDefinitionsFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtPatchDefinitionsFile.Location = new System.Drawing.Point(18, 92); + this.txtPatchDefinitionsFile.Name = "txtPatchDefinitionsFile"; + this.txtPatchDefinitionsFile.Size = new System.Drawing.Size(665, 20); + this.txtPatchDefinitionsFile.TabIndex = 3; + this.txtPatchDefinitionsFile.Leave += new System.EventHandler(this.txtPatchDefinitionsFile_Leave); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(15, 76); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(117, 13); + this.label4.TabIndex = 3; + this.label4.Text = "Patch defintions xml-file"; + // + // txtConsole + // + this.txtConsole.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtConsole.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtConsole.Location = new System.Drawing.Point(18, 383); + this.txtConsole.Multiline = true; + this.txtConsole.Name = "txtConsole"; + this.txtConsole.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtConsole.Size = new System.Drawing.Size(706, 392); + this.txtConsole.TabIndex = 13; + // + // label9 + // + this.label9.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(15, 367); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(78, 13); + this.label9.TabIndex = 24; + this.label9.Text = "Console output"; + // + // cmdCompile + // + this.cmdCompile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cmdCompile.Location = new System.Drawing.Point(470, 792); + this.cmdCompile.Name = "cmdCompile"; + this.cmdCompile.Size = new System.Drawing.Size(120, 33); + this.cmdCompile.TabIndex = 14; + this.cmdCompile.Text = "Compile"; + this.cmdCompile.UseVisualStyleBackColor = true; + this.cmdCompile.Click += new System.EventHandler(this.cmdCompile_Click); + // + // cmdPatch + // + this.cmdPatch.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cmdPatch.Location = new System.Drawing.Point(604, 792); + this.cmdPatch.Name = "cmdPatch"; + this.cmdPatch.Size = new System.Drawing.Size(120, 33); + this.cmdPatch.TabIndex = 15; + this.cmdPatch.Text = "Patch"; + this.cmdPatch.UseVisualStyleBackColor = true; + this.cmdPatch.Click += new System.EventHandler(this.cmdPatch_Click); + // + // cmdScriptFile + // + this.cmdScriptFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdScriptFile.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdScriptFile.Location = new System.Drawing.Point(689, 155); + this.cmdScriptFile.Name = "cmdScriptFile"; + this.cmdScriptFile.Size = new System.Drawing.Size(35, 22); + this.cmdScriptFile.TabIndex = 6; + this.cmdScriptFile.Text = "..."; + this.cmdScriptFile.UseVisualStyleBackColor = true; + this.cmdScriptFile.Click += new System.EventHandler(this.cmdScriptFile_Click); + // + // txtScriptFile + // + this.txtScriptFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtScriptFile.Location = new System.Drawing.Point(18, 156); + this.txtScriptFile.Name = "txtScriptFile"; + this.txtScriptFile.Size = new System.Drawing.Size(665, 20); + this.txtScriptFile.TabIndex = 5; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(15, 140); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(50, 13); + this.label5.TabIndex = 28; + this.label5.Text = "Script-file"; + // + // cmdBackupFolder + // + this.cmdBackupFolder.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdBackupFolder.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdBackupFolder.Location = new System.Drawing.Point(689, 273); + this.cmdBackupFolder.Name = "cmdBackupFolder"; + this.cmdBackupFolder.Size = new System.Drawing.Size(35, 22); + this.cmdBackupFolder.TabIndex = 10; + this.cmdBackupFolder.Text = "..."; + this.cmdBackupFolder.UseVisualStyleBackColor = true; + this.cmdBackupFolder.Click += new System.EventHandler(this.cmdBackupFolder_Click); + // + // txtBackupFolder + // + this.txtBackupFolder.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtBackupFolder.Location = new System.Drawing.Point(18, 274); + this.txtBackupFolder.Name = "txtBackupFolder"; + this.txtBackupFolder.Size = new System.Drawing.Size(665, 20); + this.txtBackupFolder.TabIndex = 9; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(15, 258); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(130, 13); + this.label6.TabIndex = 31; + this.label6.Text = "Backup location (optional)"; + // + // label7 + // + this.label7.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(20, 807); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(66, 13); + this.label7.TabIndex = 32; + this.label7.Text = "Powered by "; + // + // CapstoneLink + // + this.CapstoneLink.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.CapstoneLink.AutoSize = true; + this.CapstoneLink.Location = new System.Drawing.Point(80, 807); + this.CapstoneLink.Name = "CapstoneLink"; + this.CapstoneLink.Size = new System.Drawing.Size(52, 13); + this.CapstoneLink.TabIndex = 33; + this.CapstoneLink.TabStop = true; + this.CapstoneLink.Text = "Capstone"; + this.CapstoneLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.CapstoneLink_LinkClicked); + // + // label8 + // + this.label8.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(129, 807); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(28, 13); + this.label8.TabIndex = 34; + this.label8.Text = "and "; + // + // CapstoneNetLink + // + this.CapstoneNetLink.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.CapstoneNetLink.AutoSize = true; + this.CapstoneNetLink.Location = new System.Drawing.Point(151, 807); + this.CapstoneNetLink.Name = "CapstoneNetLink"; + this.CapstoneNetLink.Size = new System.Drawing.Size(77, 13); + this.CapstoneNetLink.TabIndex = 35; + this.CapstoneNetLink.TabStop = true; + this.CapstoneNetLink.Text = "Capstone.NET"; + this.CapstoneNetLink.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.CapstoneNetLink_LinkClicked); + // + // label10 + // + this.label10.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(225, 807); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(42, 13); + this.label10.TabIndex = 36; + this.label10.Text = "libraries"; + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(742, 840); + this.Controls.Add(this.label10); + this.Controls.Add(this.CapstoneNetLink); + this.Controls.Add(this.label8); + this.Controls.Add(this.CapstoneLink); + this.Controls.Add(this.label7); + this.Controls.Add(this.cmdBackupFolder); + this.Controls.Add(this.txtBackupFolder); + this.Controls.Add(this.label6); + this.Controls.Add(this.cmdScriptFile); + this.Controls.Add(this.txtScriptFile); + this.Controls.Add(this.label5); + this.Controls.Add(this.cmdPatch); + this.Controls.Add(this.cmdCompile); + this.Controls.Add(this.txtConsole); + this.Controls.Add(this.label9); + this.Controls.Add(this.cmdPatchDefinitionsFile); + this.Controls.Add(this.txtPatchDefinitionsFile); + this.Controls.Add(this.label4); + this.Controls.Add(this.cmdOutputFolder); + this.Controls.Add(this.txtOutputFolder); + this.Controls.Add(this.label3); + this.Controls.Add(this.cmdInputFolder); + this.Controls.Add(this.txtInputFolder); + this.Controls.Add(this.label2); + this.Controls.Add(this.cmdVisualStudioPath); + this.Controls.Add(this.txtVisualStudioPath); + this.Controls.Add(this.label1); + this.Name = "MainForm"; + this.Text = "ARM Auto-patcher by Rene Lergner"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed); + this.Load += new System.EventHandler(this.MainForm_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox txtVisualStudioPath; + private System.Windows.Forms.FolderBrowserDialog FolderBrowserDialog; + private System.Windows.Forms.OpenFileDialog OpenFileDialog; + private System.Windows.Forms.SaveFileDialog SaveFileDialog; + private System.Windows.Forms.Button cmdVisualStudioPath; + private System.Windows.Forms.Button cmdInputFolder; + private System.Windows.Forms.TextBox txtInputFolder; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Button cmdOutputFolder; + private System.Windows.Forms.TextBox txtOutputFolder; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button cmdPatchDefinitionsFile; + private System.Windows.Forms.TextBox txtPatchDefinitionsFile; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.TextBox txtConsole; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.Button cmdCompile; + private System.Windows.Forms.Button cmdPatch; + private System.Windows.Forms.Button cmdScriptFile; + private System.Windows.Forms.TextBox txtScriptFile; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.Button cmdBackupFolder; + private System.Windows.Forms.TextBox txtBackupFolder; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.LinkLabel CapstoneLink; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.LinkLabel CapstoneNetLink; + private System.Windows.Forms.Label label10; + } +} + diff --git a/Patcher/AutoPatcher/MainForm.cs b/Patcher/AutoPatcher/MainForm.cs new file mode 100644 index 0000000..f5515c1 --- /dev/null +++ b/Patcher/AutoPatcher/MainForm.cs @@ -0,0 +1,371 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using WPinternals; + +namespace Patcher +{ + public partial class MainForm : Form + { + private PatchEngine PatchEngine = null; + + public MainForm() + { + InitializeComponent(); + } + + private void MainForm_Load(object sender, EventArgs e) + { + LoadPaths(); + CenterToScreen(); + } + + private void LoadPaths() + { + RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\Patcher", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\Patcher"); + + txtVisualStudioPath.Text = (string)Key.GetValue("VisualStudioPath", ""); + if (txtVisualStudioPath.Text.Length == 0) + txtVisualStudioPath.Text = FindVisualStudioPath(); + + txtPatchDefinitionsFile.Text = (string)Key.GetValue("PatchDefinitionsFilePath", ""); + txtScriptFile.Text = (string)Key.GetValue("ScriptFilePath", ""); + txtInputFolder.Text = (string)Key.GetValue("InputFolderPath", ""); + txtOutputFolder.Text = (string)Key.GetValue("OutputFolderPath", ""); + txtBackupFolder.Text = (string)Key.GetValue("BackupFolderPath", ""); + + LoadPatchDefinitions(); + } + + public static string[] FindMSVCBinaryPaths(string s) + { + string LegacyPath = Path.Combine(s, @"VC\bin"); + if (Directory.Exists(LegacyPath)) + { + return new string[] { LegacyPath }; + } + + if (Directory.Exists(Path.Combine(s, @"VC\Tools\MSVC"))) + { + IEnumerable MSVCs = Directory.EnumerateDirectories(Path.Combine(s, @"VC\Tools\MSVC")); + IEnumerable Bins = MSVCs.Select(s => Path.Combine(s, "bin")).Where(s => Directory.Exists(s)); + return Bins.ToArray(); + } + + return Array.Empty(); + } + + public static string FindArmAsmPath(string s) + { + foreach (string MSVCBin in FindMSVCBinaryPaths(s)) + { + string path1 = Path.Combine(MSVCBin, "x86_arm"); + string path2 = Path.Combine(MSVCBin, @"Hostx86\arm"); + + if (File.Exists(Path.Combine(path1, "armasm.exe"))) + { + return path1; + } + + if (File.Exists(Path.Combine(path2, "armasm.exe"))) + { + return path2; + } + } + + return ""; + } + + private static string FindVisualStudioPath() + { + IEnumerable MainX86VSDirectories = Directory.EnumerateDirectories(@"C:\Program Files (x86)\", "Microsoft Visual Studio*"); + IEnumerable MainX64VSDirectories = Directory.EnumerateDirectories(@"C:\Program Files\", "Microsoft Visual Studio*"); + + IEnumerable MainVSDirectories = MainX86VSDirectories.Union(MainX64VSDirectories); + + IEnumerable SubMainVSDirectories = MainVSDirectories.SelectMany(s => Directory.EnumerateDirectories(s)); + IEnumerable SubSubMainVSDirectories = SubMainVSDirectories.SelectMany(s => Directory.EnumerateDirectories(s)); + IEnumerable Directories = MainVSDirectories.Union(SubMainVSDirectories).Union(SubSubMainVSDirectories); + + string attempt1 = Directories.Where(s => FindArmAsmPath(s) != "").OrderByDescending(s => File.GetCreationTime(Path.Combine(s, @"VC\bin\x86_arm\armasm.exe"))).FirstOrDefault() ?? ""; + + if (attempt1 != "") + return attempt1; + + return Directories.Where(s => Directory.Exists(Path.Combine(s, @"VC\Tools\MSVC"))).Select(s => Path.Combine(s, @"VC\Tools\MSVC")).SelectMany(s => Directory.EnumerateDirectories(s)).Where(s => File.Exists(Path.Combine(s, @"bin\Hostx86\arm\armasm.exe"))).OrderByDescending(s => File.GetCreationTime(Path.Combine(s, @"bin\Hostx86\arm\armasm.exe"))).FirstOrDefault() ?? ""; + } + + private void StorePaths() + { + RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\Patcher", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\Patcher"); + + string VisualStudioPath = txtVisualStudioPath.Text.Trim(); + if (VisualStudioPath.Length == 0) + { + if (Key.GetValue("VisualStudioPath") != null) + Key.DeleteValue("VisualStudioPath"); + } + else + { + Key.SetValue("VisualStudioPath", VisualStudioPath); + } + + string PatchDefinitionsFilePath = txtPatchDefinitionsFile.Text.Trim(); + if (PatchDefinitionsFilePath.Length == 0) + { + if (Key.GetValue("PatchDefinitionsFilePath") != null) + Key.DeleteValue("PatchDefinitionsFilePath"); + } + else + { + Key.SetValue("PatchDefinitionsFilePath", PatchDefinitionsFilePath); + } + + string ScriptFilePath = txtScriptFile.Text.Trim(); + if (ScriptFilePath.Length == 0) + { + if (Key.GetValue("ScriptFilePath") != null) + Key.DeleteValue("ScriptFilePath"); + } + else + { + Key.SetValue("ScriptFilePath", ScriptFilePath); + } + + string InputFolderPath = txtInputFolder.Text.Trim(); + if (InputFolderPath.Length == 0) + { + if (Key.GetValue("InputFolderPath") != null) + Key.DeleteValue("InputFolderPath"); + } + else + { + Key.SetValue("InputFolderPath", InputFolderPath); + } + + string OutputFolderPath = txtOutputFolder.Text.Trim(); + if (OutputFolderPath.Length == 0) + { + if (Key.GetValue("OutputFolderPath") != null) + Key.DeleteValue("OutputFolderPath"); + } + else + { + Key.SetValue("OutputFolderPath", OutputFolderPath); + } + + string BackupFolderPath = txtBackupFolder.Text.Trim(); + if (BackupFolderPath.Length == 0) + { + if (Key.GetValue("BackupFolderPath") != null) + Key.DeleteValue("BackupFolderPath"); + } + else + { + Key.SetValue("BackupFolderPath", BackupFolderPath); + } + } + + private bool LoadingPatchDefinitions = false; + + private void LoadPatchDefinitions() + { + if (LoadingPatchDefinitions) + return; + LoadingPatchDefinitions = true; + + try + { + string Definitions = File.ReadAllText(txtPatchDefinitionsFile.Text); + PatchEngine = new PatchEngine(Definitions); + } + catch + { + PatchEngine = new PatchEngine(); + } + + LoadingPatchDefinitions = false; + } + + private void cmdVisualStudioPath_Click(object sender, EventArgs e) + { + FolderBrowserDialog.SelectedPath = txtVisualStudioPath.Text; + FolderBrowserDialog.Description = "Select path to Visual Studio with ARM32 SDK"; + System.Windows.Forms.DialogResult Result = FolderBrowserDialog.ShowDialog(); + if (Result == System.Windows.Forms.DialogResult.OK) + txtVisualStudioPath.Text = FolderBrowserDialog.SelectedPath; + } + + private void cmdPatchDefinitionsFile_Click(object sender, EventArgs e) + { + OpenFileDialog.CheckFileExists = false; + OpenFileDialog.DefaultExt = "xml"; + try + { + OpenFileDialog.FileName = Path.GetFileName(txtPatchDefinitionsFile.Text); + OpenFileDialog.InitialDirectory = Path.GetDirectoryName(txtPatchDefinitionsFile.Text); + } + catch { } + OpenFileDialog.Multiselect = false; + OpenFileDialog.Title = "Open patch-definitions file"; + System.Windows.Forms.DialogResult Result = OpenFileDialog.ShowDialog(); + if (Result == System.Windows.Forms.DialogResult.OK) + { + txtPatchDefinitionsFile.Text = OpenFileDialog.FileName; + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + } + + private void txtPatchDefinitionsFile_Leave(object sender, EventArgs e) + { + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + + private void MainForm_FormClosed(object sender, FormClosedEventArgs e) + { + StorePaths(); + } + + private void cmdInputFolder_Click(object sender, EventArgs e) + { + FolderSelectDialog Dialog = new(); + Dialog.Title = "Select input location"; + Dialog.InitialDirectory = txtInputFolder.Text; + try + { + Dialog.InitialDirectory = txtInputFolder.Text; + } + catch { } + bool Result = Dialog.ShowDialog(); + if (Result) + { + txtInputFolder.Text = Dialog.FileName; + txtOutputFolder.Text = ""; + } + } + + private void cmdOutputFolder_Click(object sender, EventArgs e) + { + FolderSelectDialog Dialog = new(); + Dialog.Title = "Select output location"; + Dialog.InitialDirectory = txtOutputFolder.Text; + try + { + Dialog.InitialDirectory = txtOutputFolder.Text; + } + catch { } + bool Result = Dialog.ShowDialog(); + if (Result) + { + txtOutputFolder.Text = Dialog.FileName; + } + } + + private void cmdScriptFile_Click(object sender, EventArgs e) + { + OpenFileDialog.CheckFileExists = true; + OpenFileDialog.DefaultExt = "pds"; + try + { + OpenFileDialog.FileName = Path.GetFileName(txtScriptFile.Text); + OpenFileDialog.InitialDirectory = Path.GetDirectoryName(txtScriptFile.Text); + } + catch { } + OpenFileDialog.Multiselect = false; + OpenFileDialog.Title = "Open patch-definition-script-file"; + System.Windows.Forms.DialogResult Result = OpenFileDialog.ShowDialog(); + if (Result == System.Windows.Forms.DialogResult.OK) + { + txtScriptFile.Text = OpenFileDialog.FileName; + } + } + + private void cmdCompile_Click(object sender, EventArgs e) + { + ClearLog(); + StorePaths(); + ScriptEngine.ExecuteScript(txtVisualStudioPath.Text.Trim(), txtScriptFile.Text.Trim(), txtInputFolder.Text.Trim(), PatchEngine: PatchEngine, WriteLog: WriteLog); + } + + private void cmdPatch_Click(object sender, EventArgs e) + { + ClearLog(); + StorePaths(); + ScriptEngine.ExecuteScript(txtVisualStudioPath.Text.Trim(), txtScriptFile.Text.Trim(), txtInputFolder.Text.Trim(), PatchEngine, txtOutputFolder.Text.Trim(), txtBackupFolder.Text.Trim().Length == 0 ? null : txtBackupFolder.Text.Trim(), WriteLog); + + PatchEngine.WriteDefinitions(txtPatchDefinitionsFile.Text); + WriteLog("Patch-definitions written to: " + txtPatchDefinitionsFile.Text); + } + + private void ClearLog() + { + txtConsole.Clear(); + } + + private void WriteLog(string Line) + { + if (txtConsole.InvokeRequired) + { + txtConsole.Invoke((MethodInvoker)delegate { WriteLog(Line); }); + } + else + { + txtConsole.AppendText(Line + Environment.NewLine); + txtConsole.Select(txtConsole.Text.Length, 0); + txtConsole.ScrollToCaret(); + System.Diagnostics.Debug.WriteLine(Line); + } + } + + private void cmdBackupFolder_Click(object sender, EventArgs e) + { + FolderSelectDialog Dialog = new(); + Dialog.Title = "Select backup location"; + Dialog.InitialDirectory = txtBackupFolder.Text; + try + { + Dialog.InitialDirectory = txtBackupFolder.Text; + } + catch { } + bool Result = Dialog.ShowDialog(); + if (Result) + { + txtBackupFolder.Text = Dialog.FileName; + } + } + + private void CapstoneLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + System.Diagnostics.Process.Start("https://github.com/aquynh/capstone/blob/master/LICENSE.TXT"); + } + + private void CapstoneNetLink_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + System.Diagnostics.Process.Start("https://github.com/9ee1/Capstone.NET/blob/master/LICENSE"); + } + } +} diff --git a/Patcher/AutoPatcher/MainForm.resx b/Patcher/AutoPatcher/MainForm.resx new file mode 100644 index 0000000..28d85c9 --- /dev/null +++ b/Patcher/AutoPatcher/MainForm.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 182, 17 + + + 317, 17 + + \ No newline at end of file diff --git a/Patcher/AutoPatcher/Program.cs b/Patcher/AutoPatcher/Program.cs new file mode 100644 index 0000000..9e8a06d --- /dev/null +++ b/Patcher/AutoPatcher/Program.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Windows.Forms; + +namespace Patcher +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/Patcher/AutoPatcher/Properties/AssemblyInfo.cs b/Patcher/AutoPatcher/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..903ac15 --- /dev/null +++ b/Patcher/AutoPatcher/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutoPatcher")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AutoPatcher")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("95cf9509-c1c4-40f5-a60e-9d93ea6f438c")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Patcher/AutoPatcher/Properties/Resources.Designer.cs b/Patcher/AutoPatcher/Properties/Resources.Designer.cs new file mode 100644 index 0000000..e88730f --- /dev/null +++ b/Patcher/AutoPatcher/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Patcher.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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", "15.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() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [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("Patcher.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + } +} diff --git a/Patcher/AutoPatcher/Properties/Resources.resx b/Patcher/AutoPatcher/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Patcher/AutoPatcher/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Patcher/AutoPatcher/Properties/Settings.Designer.cs b/Patcher/AutoPatcher/Properties/Settings.Designer.cs new file mode 100644 index 0000000..717a487 --- /dev/null +++ b/Patcher/AutoPatcher/Properties/Settings.Designer.cs @@ -0,0 +1,26 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Patcher.Properties { + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.8.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default { + get { + return defaultInstance; + } + } + } +} diff --git a/Patcher/AutoPatcher/Properties/Settings.settings b/Patcher/AutoPatcher/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Patcher/AutoPatcher/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Patcher/AutoPatcher/ScriptEngine.cs b/Patcher/AutoPatcher/ScriptEngine.cs new file mode 100644 index 0000000..117e03c --- /dev/null +++ b/Patcher/AutoPatcher/ScriptEngine.cs @@ -0,0 +1,1976 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using WPinternals; + +namespace Patcher +{ + public static class ScriptEngine + { + private static List ScriptCode; + private static int Pointer; + private static Action WriteLog; + private static string InputFolderPath; + private static string OutputFolderPath; + private static string BackupFolderPath; + private static string RelativePath; + private static string RelativeOutputPath; + private static string PathToVisualStudio; + private static string PatchDefinitionName; + private static string PatchDefinitionVersion; + private static AnalyzedFile AnalyzedFile; + private static byte[] FileBuffer; + private static string FilePath; + private static TargetFile FilePatchCollection; + private static PatchEngine PatchEngine; + private static UInt32 CurrentVirtualAddressTarget; + private static bool FindSuccess; + private static List Labels; + private static List JumpHistory; + + private static readonly char[] Operators = new char[] { '!', '@', '#', '$', '%', '^', '&', '*', '-', '+', '=', '|', '/', '?', '<', '>' }; + private static readonly char[] Separators = new char[] { ',' }; + private static readonly char[] Brackets = new char[] { '[', ']', '(', ')', '{', '}' }; + + internal static void ExecuteScript(string PathToVisualStudio, string ScriptFilePath, string InputFolderPath, PatchEngine PatchEngine = null, string OutputFolderPath = null, string BackupFolderPath = null, Action WriteLog = null) + { + try + { + ScriptEngine.WriteLog = WriteLog ?? ((s) => { }); + + ScriptCode = new List(); + JumpHistory = new List(); + Pointer = 0; + PatchDefinitionName = null; + PatchDefinitionVersion = null; + AnalyzedFile = null; + + ScriptEngine.InputFolderPath = InputFolderPath; + ScriptEngine.OutputFolderPath = OutputFolderPath?.Length == 0 ? null : OutputFolderPath; + ScriptEngine.BackupFolderPath = BackupFolderPath?.Length == 0 ? null : BackupFolderPath; + ScriptEngine.PathToVisualStudio = PathToVisualStudio; + ScriptEngine.PatchEngine = PatchEngine; + + string[] ScriptCodeLines = File.ReadAllText(ScriptFilePath).Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + + CodeLine CurrentLine = new(); + bool InCode = false; + for (int i = 0; i < ScriptCodeLines.Length; i++) + { + string Line = RemoveComment(ScriptCodeLines[i]).Trim(new char[] { ' ', '\t' }); + if (Line.Length > 0) + { + string Command = Line.Split(new char[] { ' ', '\t' })[0]; + if (string.Equals(Command, "PatchCode", StringComparison.CurrentCultureIgnoreCase)) + { + InCode = true; + CurrentLine.Code = Line; + } + else if ((string.Equals(Command, "EndCode", StringComparison.CurrentCultureIgnoreCase)) || (string.Equals(Command, "EndPatch", StringComparison.CurrentCultureIgnoreCase))) + { + InCode = false; + ScriptCode.Add(CurrentLine); + CurrentLine = new CodeLine(); + } + else if (InCode) + { + CurrentLine.PatchCode += Line + Environment.NewLine; + } + else if (Line.EndsWith(":")) + { + if (CurrentLine.Label?.Length > 0) + throw new ScriptParserException("Two labels at the same location"); + + string Label = Line.TrimEnd(new char[] { ' ', ':' }); + if (Label.Contains(' ')) + throw new ScriptParserException("No spaces allowed in label"); + + CurrentLine.Label = Label; + } + else + { + CurrentLine.Code = Line; + ScriptCode.Add(CurrentLine); + CurrentLine = new CodeLine(); + } + } + } + + do + { + CurrentLine = ScriptCode[Pointer]; + Pointer++; + ExecuteCode(CurrentLine); + } + while (Pointer < ScriptCode.Count); + + CloseFile(); + + WriteLog("Script finished!"); + } + catch (ScriptParserException Ex) + { + WriteLog("Script parser error: " + Ex.Message); + } + catch (Exception Ex) + { + WriteLog("Script execution error: " + Ex.Message); + } + } + + private static string RemoveComment(string Line) + { + int q; + bool InString; + int p; + do + { + InString = false; + p = Line.IndexOf("//"); + if (p >= 0) + { + q = 0; + do + { + q = Line.IndexOf("\"", q); + if ((q >= 0) && (q < p)) + { + if ((q == 0) || (Line[q - 1] != '\\')) + InString = !InString; + q++; + } + } + while ((q >= 0) && (q < p)); + + if (!InString) + { + return Line.Substring(0, p); + } + + p++; + } + } + while (p >= 0); + + return Line; + } + + private static void ExecuteCode(CodeLine Line) + { + List Tokens = Tokenizer(Line.Code); + ParseTokens(Tokens, out string Command, out List> Params); + + // Invoke method + MethodInfo Method = Array.Find(typeof(ScriptEngine).GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static), m => string.Equals(m.Name, Command, StringComparison.CurrentCultureIgnoreCase)); + + if (Method == null) + throw new ScriptParserException("Unrecognized command: " + Command); + + ParameterInfo[] ParamInfos = Method.GetParameters(); + object[] ParamObjects = new object[ParamInfos.Length]; + for (int i = 0; i < ParamInfos.Length; i++) + { + if (ParamInfos[i].HasDefaultValue) + ParamObjects[i] = ParamInfos[i].DefaultValue; + else ParamObjects[i] = ParamInfos[i].ParameterType.IsValueType ? Activator.CreateInstance(ParamInfos[i].ParameterType) : null; + } + for (int i = 0; i < Params.Count; i++) + { + int ParamIndex; + if (Params[i].Item1 == null) + { + ParamIndex = i < ParamObjects.Length ? i : throw new ScriptParserException("Wrong number of parameters for command: " + Command); + } + else + { + ParameterInfo ParamInfo = Array.Find(ParamInfos, p => string.Equals(p.Name, Params[i].Item1, StringComparison.CurrentCultureIgnoreCase)); + ParamIndex = ParamInfo != null + ? ParamInfo.Position + : throw new ScriptParserException("Unrecognized parameters " + Params[i].Item1 + " for command: " + Command); + } + + if ((Params[i].Item3 == TokenType.Text) && (ParamInfos[ParamIndex].ParameterType == typeof(string))) + { + ParamObjects[ParamIndex] = Params[i].Item2; + } + else if ((Params[i].Item3 == TokenType.Number) && ((ParamInfos[ParamIndex].ParameterType == typeof(int)) || (ParamInfos[ParamIndex].ParameterType == typeof(uint)))) + { + if (Params[i].Item2.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + { + // Hex + UInt32 Value = Convert.ToUInt32(Params[i].Item2[2..], 16); + ParamObjects[ParamIndex] = ParamInfos[ParamIndex].ParameterType == typeof(uint) ? Value : (object)(int)Value; + } + else + { + if (Params[i].Item2.StartsWith("-")) + { + Int32 IntValue = Int32.Parse(Params[i].Item2); + ParamObjects[ParamIndex] = ParamInfos[ParamIndex].ParameterType == typeof(uint) ? (uint)IntValue : (object)IntValue; + } + else + { + UInt32 UIntValue = UInt32.Parse(Params[i].Item2); + ParamObjects[ParamIndex] = ParamInfos[ParamIndex].ParameterType == typeof(uint) ? UIntValue : (object)(int)UIntValue; + } + } + } + else + { + throw new ScriptParserException("Wrong parametertype for parameter " + ParamInfos[ParamIndex].Name + " of command " + Command); + } + } + + if (Method.Name == "PatchCode") + ParamObjects[^1] = Line.PatchCode; + + try + { + Method.Invoke(null, ParamObjects); + } + catch (ScriptExecutionException) + { + throw; + } + catch (Exception Ex) + { + Exception Ex2 = Ex; + if (Ex2.InnerException != null) + Ex2 = Ex2.InnerException; + if (Ex2 is ScriptExecutionException) + throw Ex2; + throw new ScriptExecutionException(Ex2.Message, Ex2); + } + } + + private static List Tokenizer(string Line) + { + // Name: Test_123 + // Text: Test "Dit is een \"test\"..." + // Number: -12 0xA1 +334 + // Operator: !@#$%^&*-+=|/?<> (combination of multiple characters possible) + // Separator: , + // Bracket: []{}() + + List Tokens = new(); + int p = 0; + + while (p < Line.Length) + { + if (char.IsWhiteSpace(Line[p])) + { + p++; + continue; + } + + if (Line[p] == '\"') + { + int q = Line.IndexOf('\"', p + 1); + if (q == -1) + { + Tokens.Add(new Token() { Type = TokenType.Text, Text = Line[(p + 1)..] }); + p = Line.Length; + } + else + { + Tokens.Add(new Token() { Type = TokenType.Text, Text = Line.Substring(p + 1, q - p - 1) }); + p = q + 1; + } + continue; + } + + if (char.IsLetter(Line[p]) || (Line[p] == '_')) + { + int q = p + 1; + while (q < Line.Length) + { + if (char.IsLetterOrDigit(Line[q]) || (Line[q] == '_') || (Line[q] == '.')) + q++; + else + break; + } + + if ((q == Line.Length) || char.IsWhiteSpace(Line[q]) || Separators.Contains(Line[q]) || Brackets.Contains(Line[q]) || Operators.Contains(Line[q])) + { + Tokens.Add(new Token() { Type = TokenType.Text, Text = Line[p..q] }); + p = q; + continue; + } + } + + // Int + if ((((Line[p] == '+') || (Line[p] == '-')) && (p < Line.Length) && char.IsNumber(Line[p + 1])) || char.IsNumber(Line[p])) + { + int q = p + 1; + while (q < Line.Length) + { + if (char.IsDigit(Line[q])) + q++; + else + break; + } + + if ((q == Line.Length) || char.IsWhiteSpace(Line[q]) || Separators.Contains(Line[q]) || Brackets.Contains(Line[q]) || Operators.Contains(Line[q])) + { + Tokens.Add(new Token() { Type = TokenType.Number, Text = Line[p..q], Value = (UInt32)Int32.Parse(Line[p..q]) }); + p = q; + continue; + } + } + + // Hex + if (((Line.Length - p) >= 3) && (string.Equals(Line.Substring(p, 2), "0x", StringComparison.CurrentCultureIgnoreCase))) + { + int q = p + 2; + while (q < Line.Length) + { + char CurrentChar = Line[q]; + if (char.IsDigit(CurrentChar) || ((CurrentChar >= 'a') && (CurrentChar <= 'f')) || ((CurrentChar >= 'A') && (CurrentChar <= 'F'))) + q++; + else + break; + } + + if ((q == Line.Length) || char.IsWhiteSpace(Line[q]) || Separators.Contains(Line[q]) || Brackets.Contains(Line[q]) || Operators.Contains(Line[q])) + { + Tokens.Add(new Token() { Type = TokenType.Number, Text = Line[p..q], Value = UInt32.Parse(Line.Substring(p + 2, q - p - 2), System.Globalization.NumberStyles.HexNumber) }); + p = q; + continue; + } + } + + // Operators + if (Operators.Contains(Line[p])) + { + int q = p + 1; + while (q < Line.Length) + { + if (Operators.Contains(Line[q])) + q++; + else + break; + } + + Tokens.Add(new Token() { Type = TokenType.Operator, Text = Line[p..q] }); + p = q; + continue; + } + + // Brackets + if (Brackets.Contains(Line[p])) + { + Tokens.Add(new Token() { Type = TokenType.Bracket, Text = Line.Substring(p, 1) }); + p++; + continue; + } + + // Separators + if (Separators.Contains(Line[p])) + { + Tokens.Add(new Token() { Type = TokenType.Separator, Text = Line.Substring(p, 1) }); + p++; + continue; + } + + throw new ScriptParserException("Syntax error in line: " + Line); + } + + return Tokens; + } + + private static List ArmThumbTokenizer(string Line) + { + List Tokens; + try + { + Tokens = Tokenizer(Line.ToLower().Replace("#", "")); + } + catch + { + Tokens = new List(); + } + + for (int i = 0; i < (Tokens.Count - 1); i++) + { + if ((Tokens[i].Text == "r") && (Tokens[i + 1].Text == "?")) + { + Tokens[i].Text = "r?"; + Tokens.RemoveAt(i + 1); + } + } + + return Tokens; + } + + private static void ParseTokens(List Tokens, out string Command, out List> Params) + { + Command = null; + Params = new List>(); + + if ((Tokens[0].Type == TokenType.Text) && (!IsName(Tokens[0].Text))) + throw new ScriptParserException("Invalid command"); + + Command = Tokens[0].Text; + Tokens.RemoveAt(0); + + if (Tokens.Count > 0) + { + if ((Tokens[0].Text == "(") && (Tokens[^1].Text == ")")) + { + Tokens.RemoveAt(0); + Tokens.RemoveAt(Tokens.Count - 1); + } + + // Parse params + bool GotNamedParam = false; + while (Tokens.Count > 0) + { + if ((Tokens.Count >= 3) && (Tokens[0].Type == TokenType.Text) && IsName(Tokens[0].Text) && (Tokens[1].Text == "=") && ((Tokens[2].Type == TokenType.Text) || (Tokens[2].Type == TokenType.Number))) + { + Params.Add(new Tuple(Tokens[0].Text, Tokens[2].Text, Tokens[2].Type)); + GotNamedParam = true; + Tokens.RemoveRange(0, 3); + } + else if ((Tokens[0].Type == TokenType.Text) || (Tokens[0].Type == TokenType.Number)) + { + if (GotNamedParam) + throw new ScriptParserException("Named parameter cannot preceed an unnamed parameter"); + + Params.Add(new Tuple(null, Tokens[0].Text, Tokens[0].Type)); + Tokens.RemoveAt(0); + } + else + { + throw new ScriptParserException("Syntax error"); + } + + if ((Tokens.Count > 0) && (Tokens[0].Text == ",")) + Tokens.RemoveAt(0); + } + } + } + + private static bool IsName(string Token) + { + if (Token.Length == 0) + return false; + for (int i = 0; i < Token.Length; i++) + { + if (!(char.IsLetter(Token[i]) || (Token[i] == '_') || (Token[i] == '.') || ((i > 0) && char.IsNumber(Token[i])))) + return false; + } + return true; + } + + private static void PatchDefinition(string Name, string VersionFrom, string Version, string RelativePath, string RelativeOutputPath) + { + CloseFile(); + + PatchDefinitionName = Name; + ScriptEngine.RelativePath = RelativePath; + ScriptEngine.RelativeOutputPath = RelativeOutputPath; + if (Version != null) + { + PatchDefinitionVersion = Version; + } + else if (VersionFrom != null) + { + string FullPath = System.IO.Path.Combine(InputFolderPath, VersionFrom); + PeFile File = new(FullPath); + Version ProductVersion = File.GetProductVersion(); + PatchDefinitionVersion = ProductVersion.Major.ToString() + "." + ProductVersion.Minor.ToString() + "." + ProductVersion.Build.ToString() + "." + ProductVersion.Revision.ToString(); + } + else + { + throw new ScriptExecutionException("Patch definition version is mandatory"); + } + WriteLog("PatchDefinition: " + PatchDefinitionName); + WriteLog("Version: " + PatchDefinitionVersion); + } + + private static void PatchFile(string Path) + { + if (PatchDefinitionName == null) + throw new ScriptExecutionException("PatchDefinition not defined"); + + CloseFile(); + string FullPath = System.IO.Path.Combine(InputFolderPath, RelativePath ?? "", Path); + + string AsmFilePath = BackupFolderPath == null + ? System.IO.Path.Combine(InputFolderPath, RelativePath ?? "", Path) + : System.IO.Path.Combine(BackupFolderPath, RelativePath ?? "", Path).Replace("%VERSION%", PatchDefinitionVersion, StringComparison.OrdinalIgnoreCase); + AsmFilePath = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(AsmFilePath), System.IO.Path.GetFileNameWithoutExtension(AsmFilePath) + ".asm"); + + bool AsmFileExists = File.Exists(AsmFilePath); + if (AsmFileExists) + WriteLog("Loading file: " + AsmFilePath); + else + WriteLog("Analyzing file: " + FullPath); + + AnalyzedFile = ArmDisassembler.Analyze(FullPath, AsmFilePath); + FilePath = Path; + FileBuffer = AnalyzedFile.File.Buffer; + + if (!AsmFileExists) + { + WriteLog("Writing file: " + AsmFilePath); + WriteLog("Analysis done"); + } + + if (BackupFolderPath != null) + { + FullPath = System.IO.Path.Combine(BackupFolderPath, RelativePath ?? "", Path).Replace("%VERSION%", PatchDefinitionVersion, StringComparison.OrdinalIgnoreCase); + + WriteLog("Create backup to: " + FullPath); + File.WriteAllBytes(FullPath, FileBuffer); + } + + CurrentVirtualAddressTarget = (UInt32)AnalyzedFile.File.ImageBase; + + PatchDefinition PatchDefinition = PatchEngine.PatchDefinitions.Find(d => string.Equals(d.Name, PatchDefinitionName, StringComparison.CurrentCultureIgnoreCase)); + if (PatchDefinition == null) + { + PatchDefinition = new PatchDefinition + { + Name = PatchDefinitionName + }; + PatchEngine.PatchDefinitions.Add(PatchDefinition); + } + TargetVersion TargetVersion = PatchDefinition.TargetVersions.Find(v => string.Equals(v.Description, PatchDefinitionVersion, StringComparison.CurrentCultureIgnoreCase)); + if (TargetVersion == null) + { + TargetVersion = new TargetVersion + { + Description = PatchDefinitionVersion + }; + PatchDefinition.TargetVersions.Add(TargetVersion); + } + TargetFile TargetFile = TargetVersion.TargetFiles.Find(f => (f.Path != null) && (string.Equals(f.Path.TrimStart(new char[] { '\\' }), Path.TrimStart(new char[] { '\\' }), StringComparison.CurrentCultureIgnoreCase))); + if (TargetFile != null) + TargetVersion.TargetFiles.Remove(TargetFile); // Remove any old patches for this file + TargetFile = new TargetFile(); + TargetVersion.TargetFiles.Add(TargetFile); + TargetFile.Path = Path.TrimStart(new char[] { '\\' }); + SHA1Managed SHA = new(); + TargetFile.HashOriginal = SHA.ComputeHash(AnalyzedFile.File.Buffer); + FilePatchCollection = TargetFile; + + Labels = new List(); + } + + private static void CloseFile() + { + if (AnalyzedFile != null) + { + // Update hash in patch definition + SHA1Managed SHA = new(); + FilePatchCollection.HashPatched = SHA.ComputeHash(FileBuffer); + WriteLog("New hash for patched file: " + Converter.ConvertHexToString(FilePatchCollection.HashPatched, "")); + + // Write patched file + if (OutputFolderPath != null) + { + string FullPath = System.IO.Path.Combine(OutputFolderPath, RelativeOutputPath ?? "", RelativePath ?? "", FilePath).Replace("%VERSION%", PatchDefinitionVersion, StringComparison.OrdinalIgnoreCase); + + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(FullPath)); + + WriteLog("Writing patched file: " + FullPath); + + File.WriteAllBytes(FullPath, FileBuffer); + } + + AnalyzedFile = null; + } + } + + private static void PatchAtRawOffset(byte[] Bytes, UInt32 RawOffset) + { + // Read original bytes + byte[] Original = new byte[Bytes.Length]; + System.Buffer.BlockCopy(FileBuffer, (int)RawOffset, Original, 0, Bytes.Length); + + // Patch bytes in buffer + System.Buffer.BlockCopy(Bytes, 0, FileBuffer, (int)RawOffset, Bytes.Length); + + // Add patch to defintions (original and patched bytes) + Patch CurrentPatch = FilePatchCollection.Patches.Find(p => p.Address == RawOffset); + if (CurrentPatch == null) + { + CurrentPatch = new Patch + { + Address = RawOffset, + OriginalBytes = Original + }; + FilePatchCollection.Patches.Add(CurrentPatch); + } + CurrentPatch.PatchedBytes = Bytes; + + WriteLog("Patched file at raw offset: 0x" + RawOffset.ToString("X8")); + WriteLog(" Original bytes: " + Converter.ConvertHexToString(Original, " ")); + WriteLog(" Patched bytes: " + Converter.ConvertHexToString(Bytes, " ")); + } + + private static void PatchAtVirtualAddress(byte[] Bytes, UInt32 VirtualAddress) + { + PatchAtRawOffset(Bytes, AnalyzedFile.File.ConvertVirtualAddressToRawOffset(CurrentVirtualAddressTarget)); + } + + private static void FindFirstAscii(string SearchString) + { + CurrentVirtualAddressTarget = (UInt32)AnalyzedFile.File.ImageBase; + WriteLog("Set search start point to virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindNextAscii(SearchString); + } + + private static void FindNextAscii(string SearchString) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for ascii string: " + SearchString); + UInt32? FindIndex = ByteOperations.FindAscii(FileBuffer, AnalyzedFile.File.ConvertVirtualAddressToRawOffset(CurrentVirtualAddressTarget), SearchString); + FindSuccess = FindIndex != null; + if (FindIndex != null) + { + CurrentVirtualAddressTarget = AnalyzedFile.File.ConvertRawOffsetToVirtualAddress((UInt32)FindIndex); + WriteLog("Ascii string found at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + } + else + { + WriteLog("String not found"); + } + } + + private static void FindAscii(string SearchString) + { + FindNextAscii(SearchString); + } + + private static void FindFirstUnicode(string SearchString) + { + CurrentVirtualAddressTarget = (UInt32)AnalyzedFile.File.ImageBase; + WriteLog("Set search start point to virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindNextUnicode(SearchString); + } + + private static void FindNextUnicode(string SearchString) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for unicode string: " + SearchString); + UInt32? FindIndex = ByteOperations.FindUnicode(FileBuffer, AnalyzedFile.File.ConvertVirtualAddressToRawOffset(CurrentVirtualAddressTarget), SearchString); + FindSuccess = FindIndex != null; + if (FindIndex != null) + { + CurrentVirtualAddressTarget = AnalyzedFile.File.ConvertRawOffsetToVirtualAddress((UInt32)FindIndex); + WriteLog("Unicode string found at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + } + else + { + WriteLog("String not found"); + } + } + + private static void FindUnicode(string SearchString) + { + FindNextUnicode(SearchString); + } + + private static void FindFirstBytes(string SearchString) + { + CurrentVirtualAddressTarget = (UInt32)AnalyzedFile.File.ImageBase; + WriteLog("Set search start point to virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindNextBytes(SearchString); + } + + private static void FindNextBytes(string SearchString) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for bytes: " + SearchString); + byte[] Bytes = GetBytesFromString(SearchString); + UInt32? FindIndex = ByteOperations.FindPattern(FileBuffer, AnalyzedFile.File.ConvertVirtualAddressToRawOffset(CurrentVirtualAddressTarget), null, Bytes, null, null); + FindSuccess = FindIndex != null; + if (FindIndex != null) + { + CurrentVirtualAddressTarget = AnalyzedFile.File.ConvertRawOffsetToVirtualAddress((UInt32)FindIndex); + WriteLog("Binary search pattern found at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + } + else + { + WriteLog("Binary search pattern not found"); + } + } + + private static void FindBytes(string SearchString) + { + FindNextBytes(SearchString); + } + + private static byte[] GetBytesFromString(string Bytes) + { + Bytes = Bytes.ToUpper().Replace("0X", ""); + for (int i = Bytes.Length -1 ; i >= 0; i--) + { + char CurrentChar = Bytes[i]; + if (((CurrentChar < '0') || (CurrentChar > '9')) && ((CurrentChar < 'A') || (CurrentChar > 'F'))) + Bytes = Bytes.Substring(0, i) + Bytes[(i + 1)..]; + } + if ((Bytes.Length % 2) > 0) + throw new ScriptExecutionException("Not a valid binary search string: " + Bytes); + byte[] Result = new byte[Bytes.Length / 2]; + for (int i = 0; i < (Bytes.Length / 2); i++) + Result[i] = byte.Parse(Bytes.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber); + return Result; + } + + private static void JumpToReference(int ReferenceIndex, string CodePattern, string R0, string R1, string R2, string R3, string Result) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for reference " + (ReferenceIndex == 0 ? "" : "with index " + ReferenceIndex.ToString() + " ") + "to virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + int FoundIndex = -1; + for (int i = 0; i < AnalyzedFile.Code.Count; i++) + { + if ((AnalyzedFile.Code.Values[i].Operand.Contains("#0x" + CurrentVirtualAddressTarget.ToString("X8"), StringComparison.OrdinalIgnoreCase)) || + (AnalyzedFile.Code.Values[i].Operand.Contains("#0x" + CurrentVirtualAddressTarget.ToString("X"), StringComparison.OrdinalIgnoreCase))) + { + // Reference found. Now check criteria. + bool Match = false; + + if (CodePattern != null) + { + string[] PatternLines = CodePattern.Split(new char[] { ';' }); + if (i < (PatternLines.Length - 1)) + continue; + for (int j = 0; j < PatternLines.Length; j++) + { + int k = i - PatternLines.Length + j + 1; + Match = MatchPattern(AnalyzedFile.Code.Values[k], PatternLines[j]); + if (!Match) + break; + } + if (!Match) + continue; + } + + if (R0 != null) + { + const string Register = "r0"; + string Pattern = R0; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R1 != null) + { + const string Register = "r1"; + string Pattern = R1; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R2 != null) + { + const string Register = "r2"; + string Pattern = R2; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R3 != null) + { + const string Register = "r3"; + string Pattern = R3; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (Result != null) + { + const string Register = "r0"; + string Pattern = Result; + Match = false; + int j = i + 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && Tokens.Any(t => t.Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j++; + } + while (j < AnalyzedFile.Code.Count); + if (!Match) + continue; + } + + FoundIndex++; + if (FoundIndex == ReferenceIndex) + { + JumpHistory.Add(CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found reference in code at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindSuccess = true; + return; + } + } + } + + // throw new ScriptExecutionException("Reference not found"); + FindSuccess = false; + } + + private static bool MatchPattern(ArmInstruction Instruction, string Pattern) + { + List PatternTokens = ArmThumbTokenizer(Pattern); + return MatchPattern(Instruction, PatternTokens); + } + + private static bool MatchPattern(ArmInstruction Instruction, List PatternTokens) + { + List InstructionTokens = null; + try + { + InstructionTokens = ArmThumbTokenizer(Instruction.Mnemonic + " " + Instruction.Operand); + } + catch { } + if (InstructionTokens == null) + return false; + + // Sanity check + if ((InstructionTokens.Count == 0) || (PatternTokens.Count == 0)) + return false; + + // Complete wildcard pattern + if (PatternTokens[0].Text == "?") + return true; + + // instruction must match + if ((InstructionTokens[0].Text != PatternTokens[0].Text) && (InstructionTokens[0].Text != (PatternTokens[0].Text + ".w"))) + return false; + + if ((PatternTokens.Count == 1) || ((PatternTokens.Count == 2) && (PatternTokens[1].Text == "?"))) + return true; + + for (int i = 1; i < InstructionTokens.Count; i++) + { + if (PatternTokens[i].Text == "?") + continue; + + if ((PatternTokens[i].Text == "r?") && InstructionTokens[i].Text.StartsWith("r")) + continue; + + if (PatternTokens[i].Text == InstructionTokens[i].Text) + continue; + + if ((PatternTokens[i].Type == TokenType.Number) && (InstructionTokens[i].Type == TokenType.Number) && (PatternTokens[i].Value == InstructionTokens[i].Value)) + continue; + + return false; + } + + return true; + } + + private static void FindFirstInstructionPattern(string Pattern, int InstructionIndex) + { + CurrentVirtualAddressTarget = (UInt32)AnalyzedFile.File.ImageBase; + WriteLog("Set search start point to virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindNextInstructionPattern(Pattern, InstructionIndex); + } + + private static void FindNextInstructionPattern(string Pattern, int InstructionIndex) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for instruction-pattern"); + + List> PatternTokens = new(); + string[] PatternInstructions = Pattern.Split(new char[] { ';' }); + for (int i = 0; i < PatternInstructions.Length; i++) + PatternTokens.Add(ArmThumbTokenizer(PatternInstructions[i])); + + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) + 1; i < AnalyzedFile.Code.Count; i++) + { + bool IsMatch = true; + + for (int j = 0; j < PatternTokens.Count; j++) + { + if (!MatchPattern(AnalyzedFile.Code.Values[i + j], PatternTokens[j])) + { + IsMatch = false; + break; + } + } + + if (IsMatch) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i + InstructionIndex].Address; + WriteLog("Found instruction-pattern at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + return; + } + } + + FindSuccess = false; + WriteLog("Instruction-pattern not found"); + } + + private static void FindPreviousInstructionPattern(string Pattern, int InstructionIndex) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for instruction-pattern"); + + List> PatternTokens = new(); + string[] PatternInstructions = Pattern.Split(new char[] { ';' }); + for (int i = 0; i < PatternInstructions.Length; i++) + PatternTokens.Add(ArmThumbTokenizer(PatternInstructions[i])); + + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) - 1; i >= 0; i--) + { + bool IsMatch = true; + + for (int j = 0; j < PatternTokens.Count; j++) + { + if (!MatchPattern(AnalyzedFile.Code.Values[i + j], PatternTokens[j])) + { + IsMatch = false; + break; + } + } + + if (IsMatch) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i + InstructionIndex].Address; + WriteLog("Found instruction-pattern at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + return; + } + } + + FindSuccess = false; + WriteLog("Instruction-pattern not found"); + } + + private static void FindInstructionPattern(string Pattern, int InstructionIndex) + { + FindNextInstructionPattern(Pattern, InstructionIndex); + } + + private static void FindFirstValue(UInt32 Value) + { + CurrentVirtualAddressTarget = (UInt32)AnalyzedFile.File.ImageBase; + WriteLog("Set search start point to virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindNextValue(Value); + } + + private static void FindPreviousValue(UInt32 Value) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + bool GotValue; + UInt32 FoundValue = 0; + WriteLog("Looking for previous value: 0x" + Value.ToString("X8")); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) - 1; i >= 0; i--) + { + GotValue = false; + + int Index = AnalyzedFile.Code.Values[i].Operand.IndexOf("#0x"); + if (Index >= 0) + GotValue = UInt32.TryParse(AnalyzedFile.Code.Values[i].Operand[(Index + 3)..].TrimEnd(new char[] { ']' }), System.Globalization.NumberStyles.HexNumber, null, out FoundValue); + if (!GotValue) + { + Index = AnalyzedFile.Code.Values[i].Operand.IndexOf("#"); + if (Index >= 0) + GotValue = UInt32.TryParse(AnalyzedFile.Code.Values[i].Operand[(Index + 1)..].TrimEnd(new char[] { ']' }), out FoundValue); + } + if (GotValue && (FoundValue == Value)) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found value in code at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + return; + } + } + + FindSuccess = false; + WriteLog("Value not found"); + } + + private static void FindNextValue(UInt32 Value) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + bool GotValue; + UInt32 FoundValue = 0; + WriteLog("Looking for value: 0x" + Value.ToString("X8")); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) + 1; i < AnalyzedFile.Code.Count; i++) + { + GotValue = false; + + int Index = AnalyzedFile.Code.Values[i].Operand.IndexOf("#0x"); + if (Index >= 0) + GotValue = UInt32.TryParse(AnalyzedFile.Code.Values[i].Operand[(Index + 3)..].TrimEnd(new char[] { ']' }), System.Globalization.NumberStyles.HexNumber, null, out FoundValue); + if (!GotValue) + { + Index = AnalyzedFile.Code.Values[i].Operand.IndexOf("#"); + if (Index >= 0) + GotValue = UInt32.TryParse(AnalyzedFile.Code.Values[i].Operand[(Index + 1)..].TrimEnd(new char[] { ']' }), out FoundValue); + } + if (GotValue && (FoundValue == Value)) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found value in code at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + return; + } + } + + FindSuccess = false; + WriteLog("Value not found"); + } + + private static void FindValue(UInt32 Value) + { + FindNextValue(Value); + } + + private static void FindPreviousConditionalJump() + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for previous conditional jump"); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) - 1; i >= 0; i--) + { + if (ArmDisassembler.ConditionalJumpInstructions.Contains(AnalyzedFile.Code.Values[i].Mnemonic)) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found conditional jump at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + WriteLog(" " + AnalyzedFile.Code.Values[i].Mnemonic + " " + AnalyzedFile.Code.Values[i].Operand); + return; + } + } + + FindSuccess = false; + WriteLog("Conditional jump not found"); + } + + private static void FindNextConditionalJump() + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for next conditional jump"); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) + 1; i < AnalyzedFile.Code.Count; i++) + { + if (ArmDisassembler.ConditionalJumpInstructions.Contains(AnalyzedFile.Code.Values[i].Mnemonic)) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found conditional jump at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + WriteLog(" " + AnalyzedFile.Code.Values[i].Mnemonic + " " + AnalyzedFile.Code.Values[i].Operand); + return; + } + } + + FindSuccess = false; + WriteLog("Conditional jump not found"); + } + + private static void MakeJumpUnconditional(string Instruction) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + ArmInstruction CurrentInstruction = AnalyzedFile.Code[CurrentVirtualAddressTarget]; + if (ArmDisassembler.ConditionalJumpInstructions.Contains(CurrentInstruction.Mnemonic)) + { + if ((Instruction == null) || (string.Equals(Instruction, CurrentInstruction.Mnemonic, StringComparison.CurrentCultureIgnoreCase)) || (string.Equals(Instruction + ".w", CurrentInstruction.Mnemonic, StringComparison.CurrentCultureIgnoreCase))) + { + WriteLog("Making instruction unconditional at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + string AddressString = CurrentInstruction.Operand[(CurrentInstruction.Operand.IndexOf("#0x") + 3)..]; + UInt32 Address = Convert.ToUInt32(AddressString, 16); + string NewInstruction = CurrentInstruction.Mnemonic.EndsWith(".w") ? "b.w" : "b"; + WriteLog(" Original: " + CurrentInstruction.Mnemonic + " " + CurrentInstruction.Operand); + WriteLog(" Patch: " + NewInstruction + " #0x" + AddressString); + string NewCode = NewInstruction + " 0x" + AddressString; + byte[] CompiledCode = Compile(NewCode); + PatchAtVirtualAddress(CompiledCode, CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget += (UInt32)CompiledCode.Length; + } + else + { + WriteLog("Looking for conditional jump: " + Instruction); + WriteLog("Instead this conditional jump was found: " + CurrentInstruction.Mnemonic + " " + CurrentInstruction.Operand); + WriteLog("Instead of making the jump unconditional, the jump will be cleared"); + string NewInstruction = (CurrentInstruction.Bytes.Length == 2) ? "nop" : "nop.w"; + WriteLog("Patch: " + NewInstruction); + byte[] CompiledCode = Compile(NewInstruction); + PatchAtVirtualAddress(CompiledCode, CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget += (UInt32)CompiledCode.Length; + } + } + else + { + throw new ScriptExecutionException("Instruction cannot be made unconditional because it isn't a jump-instruction"); + } + } + + private static void PatchChecksum() + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Calculating new checksum for file"); + UInt32 ChecksumOffset = AnalyzedFile.File.GetChecksumOffset(); + UInt32 Checksum = AnalyzedFile.File.CalculateChecksum(); + byte[] ChecksumBytes = new byte[4]; + ByteOperations.WriteUInt32(ChecksumBytes, 0, Checksum); + PatchAtRawOffset(ChecksumBytes, ChecksumOffset); + } + + private static void PatchCode(string CodeType, string AsmCode) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + Patcher.CodeType PatcherCodeType = Patcher.CodeType.Thumb2; + if (CodeType != null) + { + PatcherCodeType = CodeType.ToLower() switch + { + "arm" => Patcher.CodeType.ARM, + "thumb" => Patcher.CodeType.Thumb, + "thumb2" => Patcher.CodeType.Thumb2, + _ => throw new ScriptExecutionException("Invalid Assembly Type"), + }; + } + + WriteLog("Compiling new code at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + byte[] CompiledCode = Compile(PatcherCodeType, AsmCode); + PatchAtVirtualAddress(CompiledCode, CurrentVirtualAddressTarget); + + CurrentVirtualAddressTarget += (UInt32)CompiledCode.Length; + if (!AnalyzedFile.Code.ContainsKey(CurrentVirtualAddressTarget)) + CurrentVirtualAddressTarget += 2; + if (!AnalyzedFile.Code.ContainsKey(CurrentVirtualAddressTarget)) + { + for (int i = 0; i < AnalyzedFile.Code.Count; i++) + { + if (AnalyzedFile.Code.Values[i].Address > CurrentVirtualAddressTarget) + { + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + break; + } + } + } + } + + private static void JumpToImport(string FunctionName) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + FunctionDescriptor Import = AnalyzedFile.File.Imports.Find(i => string.Equals(i.Name, FunctionName, StringComparison.CurrentCultureIgnoreCase)); + if (Import == null) + { + throw new ScriptExecutionException("Import not found: " + FunctionName); + } + else + { + WriteLog("Import " + FunctionName + " found at: 0x" + Import.VirtualAddress.ToString("X8")); + JumpHistory.Add(CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget = Import.VirtualAddress; + } + } + + private static void JumpToExport(string FunctionName) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + FunctionDescriptor Export = AnalyzedFile.File.Exports.Find(i => string.Equals(i.Name, FunctionName, StringComparison.CurrentCultureIgnoreCase)); + if (Export == null) + { + throw new ScriptExecutionException("Export not found: " + FunctionName); + } + else + { + WriteLog("Export " + FunctionName + " found at: 0x" + Export.VirtualAddress.ToString("X8")); + JumpHistory.Add(CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget = Export.VirtualAddress; + } + } + + private static void FindPreviousInstruction(string Instruction) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for previous instruction: " + Instruction); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) - 1; i >= 0; i--) + { + if ((string.Equals(Instruction, AnalyzedFile.Code.Values[i].Mnemonic, StringComparison.CurrentCultureIgnoreCase)) || (string.Equals(Instruction + ".W", AnalyzedFile.Code.Values[i].Mnemonic, StringComparison.CurrentCultureIgnoreCase))) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found instruction at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + return; + } + } + + FindSuccess = false; + WriteLog("Instruction not found"); + } + + private static void FindNextInstruction(string Instruction) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for instruction: " + Instruction); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) + 1; i < AnalyzedFile.Code.Count; i++) + { + if ((string.Equals(Instruction, AnalyzedFile.Code.Values[i].Mnemonic, StringComparison.CurrentCultureIgnoreCase)) || (string.Equals(Instruction + ".W", AnalyzedFile.Code.Values[i].Mnemonic, StringComparison.CurrentCultureIgnoreCase))) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found instruction at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + return; + } + } + + FindSuccess = false; + WriteLog("Instruction not found"); + } + + private static void FindInstruction(string Instruction) + { + FindNextInstruction(Instruction); + } + + private static void CreateLabel(string Label) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + if (Labels.Any(l => string.Equals(l.Name, Label, StringComparison.CurrentCultureIgnoreCase))) + throw new ScriptExecutionException("Label already exists: " + Label); + + Labels.Add(new FunctionDescriptor() { Name = Label, VirtualAddress = CurrentVirtualAddressTarget }); + WriteLog("Label created: " + Label + " = 0x" + CurrentVirtualAddressTarget.ToString("X8")); + } + + private static string GetLabelsForAsm() + { + string Result = ""; + foreach (FunctionDescriptor Label in Labels) + { + Result += Label.Name + " EQU 0x" + Label.VirtualAddress.ToString("X8") + Environment.NewLine; + } + return Result; + } + + private static void CompareInstructionGo(string Instruction, string Label) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + string CurrentInstruction = AnalyzedFile.Code[CurrentVirtualAddressTarget].Mnemonic; + WriteLog("Current instruction: " + CurrentInstruction); + if ((string.Equals(CurrentInstruction, Instruction, StringComparison.CurrentCultureIgnoreCase)) || (string.Equals(CurrentInstruction, Instruction + ".w", StringComparison.CurrentCultureIgnoreCase))) + Go(Label); + } + + private static void ClearInstruction() + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + ArmInstruction CurrentInstruction = AnalyzedFile.Code[CurrentVirtualAddressTarget]; + WriteLog("clearing instruction at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + string Instruction = (CurrentInstruction.Bytes.Length == 2) ? "nop" : "nop.w"; + WriteLog(" Original: " + CurrentInstruction.Mnemonic + " " + CurrentInstruction.Operand); + WriteLog(" Patch: " + Instruction); + byte[] CompiledCode = Compile(Instruction); + PatchAtVirtualAddress(CompiledCode, CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget += (UInt32)CompiledCode.Length; + } + + private static byte[] Compile(string AsmCode) + { + return Compile(CodeType.Thumb2, AsmCode); + } + + private static byte[] Compile(CodeType Type, string AsmCode) + { + byte[] Result = ArmCompiler.Compile(PathToVisualStudio, CurrentVirtualAddressTarget, Type, GetLabelsForAsm() + AsmCode); + if (Result == null) + throw new ScriptExecutionException("ARM compiler output: " + ArmCompiler.Output); + else + return Result; + } + + private static void Go(string Label) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + CodeLine NewLine = ScriptCode.Find(l => string.Equals(l.Label, Label, StringComparison.CurrentCultureIgnoreCase)); + if (NewLine == null) + { + throw new ScriptExecutionException("Label " + Label + " not found"); + } + else + { + Pointer = ScriptCode.IndexOf(NewLine); + WriteLog("Go to label: " + Label); + } + } + + private static void PatchUnicode(string Text) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Patching zero-terminated unicode string: " + Text); + if (!Text.EndsWith("\0")) + Text += "\0"; + byte[] Bytes = Encoding.Unicode.GetBytes(Text); + PatchAtVirtualAddress(Bytes, CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget += (UInt32)Bytes.Length; + } + + private static void PatchAscii(string Text) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Patching zero-terminated ascii string: " + Text); + if (!Text.EndsWith("\0")) + Text += "\0"; + byte[] Bytes = Encoding.ASCII.GetBytes(Text); + PatchAtVirtualAddress(Bytes, CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget += (UInt32)Bytes.Length; + } + + private static void JumpToTarget() + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + ArmInstruction CurrentInstruction = AnalyzedFile.Code[CurrentVirtualAddressTarget]; + string AddressString = CurrentInstruction.Operand[(CurrentInstruction.Operand.IndexOf("#0x") + 3)..]; + if (UInt32.TryParse(AddressString, System.Globalization.NumberStyles.HexNumber, null, out uint Address)) + { + if (AnalyzedFile.File.GetSectionForVirtualAddress(Address) == null) + throw new ScriptExecutionException("Target at virtual address 0x" + Address.ToString("X8") + " is invalid"); + WriteLog("Jumping to target: 0x" + Address.ToString("X8")); + JumpHistory.Add(CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget = Address; + } + else + { + throw new ScriptExecutionException("Could not jump to target: " + CurrentInstruction.Operand); + } + } + + private static void JumpToLabel(string Label) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + FunctionDescriptor FoundLabel = Labels.Find(l => string.Equals(l.Name, Label, StringComparison.CurrentCultureIgnoreCase)); + if (FoundLabel == null) + throw new ScriptExecutionException("Label not found: " + Label); + + WriteLog("Jumping to label: " + Label); + WriteLog("New virtual address: 0x" + FoundLabel.VirtualAddress.ToString("X8")); + JumpHistory.Add(CurrentVirtualAddressTarget); + CurrentVirtualAddressTarget = FoundLabel.VirtualAddress; + } + + private static void IfFoundGo(string Label) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + if (FindSuccess) + { + FunctionDescriptor FoundLabel = Labels.Find(l => string.Equals(l.Name, Label, StringComparison.CurrentCultureIgnoreCase)); + if (FoundLabel == null) + throw new ScriptExecutionException("Label not found: " + Label); + + WriteLog("Condition was found, jumping to label: " + Label); + WriteLog("New virtual address: 0x" + FoundLabel.VirtualAddress.ToString("X8")); + CurrentVirtualAddressTarget = FoundLabel.VirtualAddress; + } + } + + private static void IfNotFoundGo(string Label) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + if (!FindSuccess) + { + FunctionDescriptor FoundLabel = Labels.Find(l => string.Equals(l.Name, Label, StringComparison.CurrentCultureIgnoreCase)); + if (FoundLabel == null) + throw new ScriptExecutionException("Label not found: " + Label); + + WriteLog("Condition was not found, jumping to label: " + Label); + WriteLog("New virtual address: 0x" + FoundLabel.VirtualAddress.ToString("X8")); + CurrentVirtualAddressTarget = FoundLabel.VirtualAddress; + } + } + + private static void IfFoundThrowError(string Message) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + if (FindSuccess) + { + if (Message == null) + throw new ScriptExecutionException("Script execution error"); + else + throw new ScriptExecutionException(Message); + } + } + + private static void IfNotFoundThrowError(string Message) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + if (!FindSuccess) + { + if (Message == null) + throw new ScriptExecutionException("Script execution error"); + else + throw new ScriptExecutionException(Message); + } + } + + private static void JumpBack() + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + if (JumpHistory.Count == 0) + throw new ScriptExecutionException("No jump history, can't jump back"); + + CurrentVirtualAddressTarget = JumpHistory.Last(); + JumpHistory.RemoveAt(JumpHistory.Count - 1); + WriteLog("Jumping back to: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + } + + private static void FindStore() + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + string Operand = AnalyzedFile.Code[CurrentVirtualAddressTarget].Operand; + if (Operand.Contains(',')) + { + string Register = Operand.Substring(0, Operand.IndexOf(',')).Trim(); + if (Register.StartsWith("r")) + { + WriteLog("Looking for instruction where " + Register + " is being stored"); + + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) + 1; i < AnalyzedFile.Code.Count; i++) + { + if ((string.Equals("str", AnalyzedFile.Code.Values[i].Mnemonic, StringComparison.CurrentCultureIgnoreCase)) || (string.Equals("str.w", AnalyzedFile.Code.Values[i].Mnemonic, StringComparison.CurrentCultureIgnoreCase))) + { + Operand = AnalyzedFile.Code.Values[i].Operand; + if (Operand.Contains(',')) + { + if (Register == Operand.Substring(0, Operand.IndexOf(',')).Trim()) + { + FindSuccess = true; + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found instruction at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + return; + } + } + } + } + + FindSuccess = false; + WriteLog("Instruction not found"); + } + else + { + throw new ScriptExecutionException("Could not locate register to search for"); + } + } + else + { + throw new ScriptExecutionException("Could not locate register to search for"); + } + } + + private static void FindFunctionCall(string R0, string R1, string R2, string R3, string Result) + { + FindNextFunctionCall(R0, R1, R2, R3, Result); + } + + private static void FindNextFunctionCall(string R0, string R1, string R2, string R3, string Result) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for function call"); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) + 1; i < AnalyzedFile.Code.Count; i++) + { + if (AnalyzedFile.Code.Values[i].Mnemonic == "bl") + { + // Function call found. Now check criteria. + bool Match; + + if (R0 != null) + { + const string Register = "r0"; + string Pattern = R0; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R1 != null) + { + const string Register = "r1"; + string Pattern = R1; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R2 != null) + { + const string Register = "r2"; + string Pattern = R2; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R3 != null) + { + const string Register = "r3"; + string Pattern = R3; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (Result != null) + { + const string Register = "r0"; + string Pattern = Result; + Match = false; + int j = i + 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && Tokens.Any(t => t.Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j++; + } + while (j < AnalyzedFile.Code.Count); + if (!Match) + continue; + } + + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found function-call in code at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindSuccess = true; + return; + } + } + + // throw new ScriptExecutionException("Reference not found"); + FindSuccess = false; + } + + private static void FindPreviousFunctionCall(string R0, string R1, string R2, string R3, string Result) + { + if (AnalyzedFile == null) + throw new ScriptExecutionException("PatchFile not defined"); + + WriteLog("Looking for function call"); + for (int i = AnalyzedFile.Code.IndexOfKey(CurrentVirtualAddressTarget) - 1; i >= 0; i--) + { + if (AnalyzedFile.Code.Values[i].Mnemonic == "bl") + { + // Function call found. Now check criteria. + bool Match; + + if (R0 != null) + { + const string Register = "r0"; + string Pattern = R0; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R1 != null) + { + const string Register = "r1"; + string Pattern = R1; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R2 != null) + { + const string Register = "r2"; + string Pattern = R2; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (R3 != null) + { + const string Register = "r3"; + string Pattern = R3; + Match = false; + int j = i - 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && (Tokens[0].Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j--; + } + while (j >= 0); + if (!Match) + continue; + } + + if (Result != null) + { + const string Register = "r0"; + string Pattern = Result; + Match = false; + int j = i + 1; + do + { + List Tokens = ArmThumbTokenizer(AnalyzedFile.Code.Values[j].Operand); + if ((Tokens.Count > 0) && Tokens.Any(t => t.Text == Register)) + { + Match = MatchPattern(AnalyzedFile.Code.Values[j], Pattern); + break; + } + j++; + } + while (j < AnalyzedFile.Code.Count); + if (!Match) + continue; + } + + CurrentVirtualAddressTarget = AnalyzedFile.Code.Values[i].Address; + WriteLog("Found function-call in code at virtual address: 0x" + CurrentVirtualAddressTarget.ToString("X8")); + FindSuccess = true; + return; + } + } + + // throw new ScriptExecutionException("Reference not found"); + FindSuccess = false; + } + } + + public class CodeLine + { + public string Label; + public string Code; + public string PatchCode; + } + + public enum TokenType + { + Bracket, + Separator, + Operator, + Text, + Number + } + + public class Token + { + public TokenType Type; + public string Text; + public UInt32 Value; + } + + public class ScriptParserException: Exception + { + public ScriptParserException(string Message): base(Message) + { + } + + public ScriptParserException() : base() + { + } + + public ScriptParserException(string message, Exception innerException) : base(message, innerException) + { + } + } + + public class ScriptExecutionException: Exception + { + public ScriptExecutionException(string Message) : base(Message) + { + } + + public ScriptExecutionException(string Message, Exception InnerException) : base(Message, InnerException) + { + } + + public ScriptExecutionException() : base() + { + } + } + + /// + /// Extension method by: Oleg Zarevennyi + /// https://stackoverflow.com/a/45756981 + /// + static public class StringExtensions + { + public static string Replace(this string str, string oldValue, string @newValue, StringComparison comparisonType) + { + if (str == null) + { + throw new ArgumentNullException(nameof(str)); + } + if (str.Length == 0) + { + return str; + } + if (oldValue == null) + { + throw new ArgumentNullException(nameof(oldValue)); + } + if (oldValue.Length == 0) + { + throw new ArgumentException("String cannot be of zero length."); + } + + StringBuilder resultStringBuilder = new(str.Length); + bool isReplacementNullOrEmpty = string.IsNullOrEmpty(@newValue); + + const int valueNotFound = -1; + int foundAt; + int startSearchFromIndex = 0; + while ((foundAt = str.IndexOf(oldValue, startSearchFromIndex, comparisonType)) != valueNotFound) + { + int @charsUntilReplacment = foundAt - startSearchFromIndex; + bool isNothingToAppend = @charsUntilReplacment == 0; + if (!isNothingToAppend) + { + resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilReplacment); + } + if (!isReplacementNullOrEmpty) + { + resultStringBuilder.Append(@newValue); + } + startSearchFromIndex = foundAt + oldValue.Length; + if (startSearchFromIndex == str.Length) + { + return resultStringBuilder.ToString(); + } + } + + int @charsUntilStringEnd = str.Length - startSearchFromIndex; + resultStringBuilder.Append(str, startSearchFromIndex, @charsUntilStringEnd); + + return resultStringBuilder.ToString(); + } + } +} diff --git a/Patcher/Patcher.sln b/Patcher/Patcher.sln new file mode 100644 index 0000000..e3dfc6b --- /dev/null +++ b/Patcher/Patcher.sln @@ -0,0 +1,81 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28010.2041 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Patcher", "Patcher\Patcher.csproj", "{B67C62AE-86C4-4C18-99AB-4E94A3E09D36}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoPatcher", "AutoPatcher\AutoPatcher.csproj", "{95CF9509-C1C4-40F5-A60E-9D93EA6F438C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + .NET45Debug|Any CPU = .NET45Debug|Any CPU + .NET45Debug|x64 = .NET45Debug|x64 + .NET45Debug|x86 = .NET45Debug|x86 + .NET45Release|Any CPU = .NET45Release|Any CPU + .NET45Release|x64 = .NET45Release|x64 + .NET45Release|x86 = .NET45Release|x86 + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Debug|Any CPU.Build.0 = Debug|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Debug|x64.ActiveCfg = Debug|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Debug|x64.Build.0 = Debug|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Debug|x86.ActiveCfg = Debug|x86 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Debug|x86.Build.0 = Debug|x86 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Release|Any CPU.ActiveCfg = Release|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Release|Any CPU.Build.0 = Release|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Release|x64.ActiveCfg = Release|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Release|x64.Build.0 = Release|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Release|x86.ActiveCfg = Release|x86 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}..NET45Release|x86.Build.0 = Release|x86 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Debug|x64.ActiveCfg = Debug|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Debug|x64.Build.0 = Debug|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Debug|x86.ActiveCfg = Debug|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Debug|x86.Build.0 = Debug|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Release|Any CPU.Build.0 = Release|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Release|x64.ActiveCfg = Release|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Release|x64.Build.0 = Release|x64 + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Release|x86.ActiveCfg = Release|Any CPU + {B67C62AE-86C4-4C18-99AB-4E94A3E09D36}.Release|x86.Build.0 = Release|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Debug|Any CPU.Build.0 = Debug|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Debug|x64.ActiveCfg = Debug|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Debug|x64.Build.0 = Debug|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Debug|x86.ActiveCfg = Debug|x86 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Debug|x86.Build.0 = Debug|x86 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Release|Any CPU.ActiveCfg = Release|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Release|Any CPU.Build.0 = Release|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Release|x64.ActiveCfg = Release|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Release|x64.Build.0 = Release|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Release|x86.ActiveCfg = Release|x86 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}..NET45Release|x86.Build.0 = Release|x86 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Debug|x64.ActiveCfg = Debug|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Debug|x64.Build.0 = Debug|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Debug|x86.ActiveCfg = Debug|x86 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Debug|x86.Build.0 = Debug|x86 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Release|Any CPU.Build.0 = Release|Any CPU + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Release|x64.ActiveCfg = Release|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Release|x64.Build.0 = Release|x64 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Release|x86.ActiveCfg = Release|x86 + {95CF9509-C1C4-40F5-A60E-9D93EA6F438C}.Release|x86.Build.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6763D5C5-7BCE-437C-9FD0-67BDD8D79FD2} + EndGlobalSection +EndGlobal diff --git a/Patcher/Patcher/App.config b/Patcher/Patcher/App.config new file mode 100644 index 0000000..8e15646 --- /dev/null +++ b/Patcher/Patcher/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Patcher/Patcher/ArmCompiler.cs b/Patcher/Patcher/ArmCompiler.cs new file mode 100644 index 0000000..408ee64 --- /dev/null +++ b/Patcher/Patcher/ArmCompiler.cs @@ -0,0 +1,229 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; + +namespace Patcher +{ + public enum CodeType + { + ARM, + Thumb, + Thumb2 + } + + public static class ArmCompiler + { + public static Int32 LastErrorCode; + public static string Output; + + public static byte[] Compile(string PathToVisualStudio, UInt32 Origin, CodeType CodeType, string ArmCodeFragment) + { + if (PathToVisualStudio.Length == 0) + { + Output = "ARM SDK is missing."; + return null; + } + + string AssemblyFilePath = Path.GetTempFileName(); + string ObjectFilePathTmp = Path.GetTempFileName(); + string ObjectFilePath = ObjectFilePathTmp.Replace(".tmp", ".obj"); + File.Move(ObjectFilePathTmp, ObjectFilePath); + + string CodeTypeDirective = null; + switch (CodeType) + { + case Patcher.CodeType.ARM: + CodeTypeDirective = "CODE32"; + break; + case Patcher.CodeType.Thumb: + CodeTypeDirective = "CODE16"; + break; + case Patcher.CodeType.Thumb2: + CodeTypeDirective = "THUMB"; + break; + } + + string FullAssemblyCode = + " AREA ARM_AREA, CODE, READONLY" + Environment.NewLine + + " " + CodeTypeDirective + Environment.NewLine; + + string ProcessedAssembly = ProcessArmCodeFragment(ArmCodeFragment, Origin, out uint Padding); + + if (Padding > 0) + FullAssemblyCode += " SPACE " + Padding.ToString() + Environment.NewLine; + + FullAssemblyCode += + "start" + Environment.NewLine + + ProcessedAssembly; + + FullAssemblyCode += " end" + Environment.NewLine; + + File.WriteAllText(AssemblyFilePath, FullAssemblyCode); + + string ArmAsmPath = MainForm.FindArmAsmPath(PathToVisualStudio); + string BinPath = MainForm.FindMSVCBinaryPaths(PathToVisualStudio).FirstOrDefault() ?? ""; + ProcessStartInfo psi = new(Path.Combine(ArmAsmPath, "armasm.exe")); + psi.EnvironmentVariables["PATH"] += ";" + Path.Combine(PathToVisualStudio, @"Common7\IDE\"); + psi.EnvironmentVariables["PATH"] += ";" + Path.Combine(PathToVisualStudio, @"Common7\Tools\"); + psi.EnvironmentVariables["PATH"] += ";" + BinPath; + psi.EnvironmentVariables["PATH"] += ";" + ArmAsmPath; + psi.UseShellExecute = false; + psi.RedirectStandardOutput = true; + psi.CreateNoWindow = true; + psi.Arguments = "-g \"" + AssemblyFilePath + "\" \"" + ObjectFilePath + "\""; + Process ArmAsmProcess = Process.Start(psi); + ArmAsmProcess.WaitForExit(); + LastErrorCode = ArmAsmProcess.ExitCode; + if (ArmAsmProcess.ExitCode != 0) + { + using StreamReader reader = ArmAsmProcess.StandardOutput; + Output = reader.ReadToEnd(); + + string[] Lines = Output.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + + Output = ""; + + foreach (string Line in Lines) + { + string Out = Line.Trim(); + if (Out.Length == 0) continue; + if (Out.StartsWith("Microsoft (R) ARM Macro Assembler")) continue; + if (Out.StartsWith("Copyright (C) Microsoft Corporation")) continue; + if (Out.StartsWith(AssemblyFilePath)) + { + Out = Out[AssemblyFilePath.Length..]; + int P = Out.IndexOf(':'); + Out = Out[(P + 1)..].Trim(); + } + Output += Out + Environment.NewLine; + } + } + + byte[] Result = null; + + if (LastErrorCode == 0) + Result = COFF.ObjectFileParser.ParseObjectFile(ObjectFilePath).SectionHeaders.First(h => h.Name == "ARM_AREA").RawData; + + File.Delete(AssemblyFilePath); + File.Delete(ObjectFilePath); + + if ((Result != null) && (Padding > 0)) + { + byte[] RemovedPadding = new byte[Result.Length - Padding]; + Buffer.BlockCopy(Result, (int)Padding, RemovedPadding, 0, RemovedPadding.Length); + Result = RemovedPadding; + } + + return Result; + } + + private static string ProcessArmCodeFragment(string ArmCodeFragment, UInt32 Origin, out UInt32 Padding) + { + string[] BranchOpcodes = new string[] { "B", "BEQ", "BNE", "BCS", "BHS", "BCC", "BLO", "BMI", "BPL", "BVS", "BVC", "BHI", "BLS", "BGE", "BLT", "BGT", "BLE", "BAL"}; + + StringBuilder Result = new(1000); + Padding = 0; + + string[] Lines = ArmCodeFragment.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + List> Labels = new(); + + foreach (string Line in Lines) + { + string Code = Line; + if (Line.Contains(':')) + { + string Label = Line.Substring(0, Line.IndexOf(':')); + Label = Label.Trim(); + if (Label.Length > 0) + Result.AppendLine(Label); + Code = Line[(Line.IndexOf(':') + 1)..]; + } + + int EquPos = Code.IndexOf("EQU", StringComparison.OrdinalIgnoreCase); + if ((EquPos > 0) && (EquPos > 0) && (EquPos < (Code.Length - 3))) + { + if (new char[] { '\t', ' ' }.Contains(Line[EquPos - 1]) && + new char[] { '\t', ' ' }.Contains(Line[EquPos + 3])) + { + Result.AppendLine(Line.Trim()); + Labels.Add(new Tuple(Line.Substring(0, EquPos).Trim(), Line[(EquPos + 3)..].Trim())); + continue; + } + } + + Code = Code.Trim(); + + bool IsAbsoluteAddress = false; + + int OpcodeLength = Code.IndexOfAny(new char [] { '\t', ' ', '.' }); + if (OpcodeLength > 0) + { + string Opcode = Code.Substring(0, OpcodeLength).ToUpper(); + + string PossibleAddress = null; + + if (Opcode == "LDR") + { + PossibleAddress = Code[(Code.IndexOf(',') + 1)..].Trim(); + } + else if (BranchOpcodes.Contains(Opcode)) + { + PossibleAddress = Code[(Code.IndexOfAny(new char[] { '\t', ' ' }) + 1)..].Trim(); + } + + if (PossibleAddress != null) + { + foreach (Tuple Label in Labels) + { + if (string.Equals(Label.Item1, PossibleAddress, StringComparison.CurrentCultureIgnoreCase)) + { + PossibleAddress = Label.Item2; + break; + } + } + if (PossibleAddress.StartsWith("0x")) + PossibleAddress = PossibleAddress[2..]; + IsAbsoluteAddress = UInt32.TryParse(PossibleAddress, NumberStyles.HexNumber, CultureInfo.CurrentCulture, out uint ParsedValue); + if (IsAbsoluteAddress && (ParsedValue < Origin)) + Padding = Math.Max(Padding, Origin - ParsedValue); + } + } + + Result.Append(' '); + Result.Append(Code); + if (IsAbsoluteAddress) + { + Result.Append(" + start - 0x"); + Result.Append(Origin.ToString("X8")); + } + Result.Append(Environment.NewLine); + } + + return Result.ToString(); + } + } +} diff --git a/Patcher/Patcher/ByteOperations.cs b/Patcher/Patcher/ByteOperations.cs new file mode 100644 index 0000000..e1700c9 --- /dev/null +++ b/Patcher/Patcher/ByteOperations.cs @@ -0,0 +1,382 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; + +namespace WPinternals +{ + internal static class ByteOperations + { + internal static string ReadAsciiString(byte[] ByteArray, UInt32 Offset) + { + UInt32 Length = 0; + while (((Offset + Length) < ByteArray.Length) && (ByteArray[Offset + Length] != 0)) + Length++; + byte[] Bytes = new byte[Length]; + Buffer.BlockCopy(ByteArray, (int)Offset, Bytes, 0, (int)Length); + return System.Text.Encoding.ASCII.GetString(Bytes); + } + + internal static string ReadAsciiString(byte[] ByteArray, UInt32 Offset, UInt32 Length) + { + byte[] Bytes = new byte[Length]; + Buffer.BlockCopy(ByteArray, (int)Offset, Bytes, 0, (int)Length); + return System.Text.Encoding.ASCII.GetString(Bytes); + } + + internal static string ReadUnicodeString(byte[] ByteArray, UInt32 Offset, UInt32 Length) + { + byte[] Bytes = new byte[Length]; + Buffer.BlockCopy(ByteArray, (int)Offset, Bytes, 0, (int)Length); + return System.Text.Encoding.Unicode.GetString(Bytes); + } + + internal static void WriteUnicodeString(byte[] ByteArray, UInt32 Offset, string Text, UInt32? MaxBufferLength = null) + { + if (MaxBufferLength != null) + Array.Clear(ByteArray, (int)Offset, (int)MaxBufferLength); + + byte[] TextBytes = System.Text.UnicodeEncoding.Unicode.GetBytes(Text); + int WriteLength = TextBytes.Length; + if (WriteLength > MaxBufferLength) + WriteLength = (int)MaxBufferLength; + + Buffer.BlockCopy(TextBytes, 0, ByteArray, (int)Offset, WriteLength); + } + + internal static UInt32 ReadUInt32(byte[] ByteArray, UInt32 Offset) + { + // Assume CPU and FFU are both Little Endian + return BitConverter.ToUInt32(ByteArray, (int)Offset); + } + + internal static void WriteUInt32(byte[] ByteArray, UInt32 Offset, UInt32 Value) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 4); + } + + internal static Int32 ReadInt32(byte[] ByteArray, UInt32 Offset) + { + // Assume CPU and FFU are both Little Endian + return BitConverter.ToInt32(ByteArray, (int)Offset); + } + + internal static void WriteInt32(byte[] ByteArray, UInt32 Offset, Int32 Value) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 4); + } + + internal static UInt16 ReadUInt16(byte[] ByteArray, UInt32 Offset) + { + // Assume CPU and FFU are both Little Endian + return BitConverter.ToUInt16(ByteArray, (int)Offset); + } + + internal static void WriteUInt16(byte[] ByteArray, UInt32 Offset, UInt16 Value) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 2); + } + + internal static Int16 ReadInt16(byte[] ByteArray, UInt32 Offset) + { + // Assume CPU and FFU are both Little Endian + return BitConverter.ToInt16(ByteArray, (int)Offset); + } + + internal static void WriteInt16(byte[] ByteArray, UInt32 Offset, Int16 Value) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 2); + } + + internal static byte ReadUInt8(byte[] ByteArray, UInt32 Offset) + { + return ByteArray[Offset]; + } + + internal static void WriteUInt8(byte[] ByteArray, UInt32 Offset, byte Value) + { + ByteArray[Offset] = Value; + } + + internal static UInt32 ReadUInt24(byte[] ByteArray, UInt32 Offset) + { + return (UInt32)(ByteArray[Offset] + (ByteArray[Offset + 1] << 8) + (ByteArray[Offset + 2] << 16)); + } + + internal static void WriteUInt24(byte[] ByteArray, UInt32 Offset, UInt32 Value) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 3); + } + + internal static UInt64 ReadUInt64(byte[] ByteArray, UInt32 Offset) + { + // Assume CPU and FFU are both Little Endian + return BitConverter.ToUInt64(ByteArray, (int)Offset); + } + + internal static void WriteUInt64(byte[] ByteArray, UInt32 Offset, UInt64 Value) + { + System.Buffer.BlockCopy(BitConverter.GetBytes(Value), 0, ByteArray, (int)Offset, 8); + } + + internal static Guid ReadGuid(byte[] ByteArray, UInt32 Offset) + { + byte[] GuidBuffer = new byte[0x10]; + Buffer.BlockCopy(ByteArray, (int)Offset, GuidBuffer, 0, 0x10); + return new Guid(GuidBuffer); + } + + internal static void WriteGuid(byte[] ByteArray, UInt32 Offset, Guid Value) + { + Buffer.BlockCopy(Value.ToByteArray(), 0, ByteArray, (int)Offset, 0x10); + } + + internal static UInt32 Align(UInt32 Base, UInt32 Offset, UInt32 Alignment) + { + if (((Offset - Base) % Alignment) == 0) + return Offset; + else + return ((((Offset - Base) / Alignment) + 1) * Alignment) + Base; + } + + internal static UInt32? FindPatternInFile(string FileName, byte[] Pattern, byte[] Mask, out byte[] OutPattern) + { + // The mask is optional. + // In the mask 0x00 means the value must match, and 0xFF means that this position is a wildcard. + + UInt32? Result = null; + + FileStream Stream = new(FileName, FileMode.Open, FileAccess.Read); + + byte[] Buffer = new byte[0x10000 + Pattern.Length - 1]; + UInt32 BytesInBuffer = 0; + UInt32 BytesRead; + UInt32 SearchPositionFile = 0; + UInt32 SearchPositionBuffer = 0; + UInt32 BufferFileOffset = 0; // Offset in file where data from buffer is located. + int i; + + OutPattern = null; + + while (SearchPositionFile <= (Stream.Length - Pattern.Length)) + { + if ((SearchPositionBuffer + Pattern.Length) > BytesInBuffer) + { + // Need to read next chunk + if ((BytesInBuffer - SearchPositionBuffer) > 0) + { + System.Buffer.BlockCopy(Buffer, (int)SearchPositionBuffer, Buffer, 0, (int)(BytesInBuffer - SearchPositionBuffer)); + } + uint BufferReadPosition = BytesInBuffer - SearchPositionBuffer; + BytesInBuffer -= SearchPositionBuffer; + BufferFileOffset += SearchPositionBuffer; + SearchPositionBuffer = 0; + + BytesRead = (UInt32)Stream.Read(Buffer, (int)BufferReadPosition, Buffer.Length - (int)BufferReadPosition); + BytesInBuffer += BytesRead; + } + + bool Match = true; + for (i = 0; i < Pattern.Length; i++) + { + if (Buffer[SearchPositionBuffer + i] != Pattern[i]) + { + if ((Mask == null) || (Mask[i] == 0)) + { + Match = false; + break; + } + } + } + + if (Match) + { + Result = SearchPositionFile; + + OutPattern = new byte[Pattern.Length]; + System.Buffer.BlockCopy(Buffer, (int)SearchPositionBuffer, OutPattern, 0, Pattern.Length); + break; + } + + SearchPositionBuffer++; + SearchPositionFile++; + } + + Stream.Close(); + + return Result; + } + + internal static UInt32? FindAscii(byte[] SourceBuffer, string Pattern) + { + return FindPattern(SourceBuffer, System.Text.ASCIIEncoding.ASCII.GetBytes((string)Pattern), null, null); + } + + internal static UInt32? FindAscii(byte[] SourceBuffer, uint SourceOffset, string Pattern) + { + return FindPattern(SourceBuffer, SourceOffset, null, System.Text.ASCIIEncoding.ASCII.GetBytes((string)Pattern), null, null); + } + + internal static UInt32? FindUnicode(byte[] SourceBuffer, string Pattern) + { + return FindPattern(SourceBuffer, System.Text.UnicodeEncoding.Unicode.GetBytes((string)Pattern), null, null); + } + + internal static UInt32? FindUnicode(byte[] SourceBuffer, uint SourceOffset, string Pattern) + { + return FindPattern(SourceBuffer, SourceOffset, null, System.Text.UnicodeEncoding.Unicode.GetBytes((string)Pattern), null, null); + } + + internal static UInt32? FindUint(byte[] SourceBuffer, UInt32 Pattern) + { + return FindPattern(SourceBuffer, BitConverter.GetBytes((UInt32)Pattern), null, null); + } + + internal static UInt32? FindPattern(byte[] SourceBuffer, byte[] Pattern, byte[] Mask, byte[] OutPattern) + { + return FindPattern(SourceBuffer, 0, null, Pattern, Mask, OutPattern); + } + + internal static UInt32? FindPattern(byte[] SourceBuffer, uint SourceOffset, uint? SourceSize, byte[] Pattern, byte[] Mask, byte[] OutPattern) + { + // The mask is optional. + // In the mask 0x00 means the value must match, and 0xFF means that this position is a wildcard. + + UInt32? Result = null; + + UInt32 SearchPosition = SourceOffset; + int i; + + while ((SearchPosition <= (SourceBuffer.Length - Pattern.Length)) && ((SourceSize == null) || (SearchPosition <= (SourceOffset + SourceSize - Pattern.Length)))) + { + bool Match = true; + for (i = 0; i < Pattern.Length; i++) + { + if (SourceBuffer[SearchPosition + i] != Pattern[i]) + { + if ((Mask == null) || (Mask[i] == 0)) + { + Match = false; + break; + } + } + } + + if (Match) + { + Result = SearchPosition; + + if (OutPattern != null) + System.Buffer.BlockCopy(SourceBuffer, (int)SearchPosition, OutPattern, 0, Pattern.Length); + break; + } + + SearchPosition++; + } + + return Result; + } + + internal static byte CalculateChecksum8(byte[] Buffer, UInt32 Offset, UInt32 Size) + { + byte Checksum = 0; + + for (UInt32 i = Offset; i < (Offset + Size); i++) + Checksum += Buffer[i]; + + return (byte)(0x100 - Checksum); + } + + internal static UInt16 CalculateChecksum16(byte[] Buffer, UInt32 Offset, UInt32 Size) + { + UInt16 Checksum = 0; + + for (UInt32 i = Offset; i < (Offset + Size - 1); i += 2) + Checksum += BitConverter.ToUInt16(Buffer, (int)i); + + return (UInt16)(0x10000 - Checksum); + } + + private static readonly UInt32[] CRC32Table = new UInt32[] { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, + 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, + 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, + 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, + 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, + 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, + 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, + 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, + 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, 0x76DC4190, 0x01DB7106, + 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, + 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, + 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, + 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, + 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, + 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, + 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, + 0xB7BD5C3B, 0xC0BA6CAD, 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, + 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, + 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, + 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, + 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, + 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, + 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, + 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, + 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, + 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, + 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, + 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, + 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, + 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, + 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, + 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, + 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + internal static UInt32 CRC32(byte[] Input, UInt32 Offset, UInt32 Length) + { + if ((Input == null) || ((Offset + Length) > Input.Length)) + throw new ArgumentException(); + + unchecked + { + uint crc = (uint)(((uint)0) ^ (-1)); + for (var i = Offset; i < (Offset + Length); i++) + { + crc = (crc >> 8) ^ CRC32Table[ (crc ^ Input[i]) & 0xFF ]; + } + crc = (uint)(crc ^ (-1)); + + return crc; + } + } + } +} diff --git a/Patcher/Patcher/HelperClasses.cs b/Patcher/Patcher/HelperClasses.cs new file mode 100644 index 0000000..7d53634 --- /dev/null +++ b/Patcher/Patcher/HelperClasses.cs @@ -0,0 +1,66 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Text; + +namespace WPinternals +{ + public static class Converter + { + public static string ConvertHexToString(byte[] Bytes, string Separator) + { + StringBuilder s = new(1000); + for (int i = Bytes.GetLowerBound(0); i <= Bytes.GetUpperBound(0); i++) + { + if (i != Bytes.GetLowerBound(0)) + s.Append(Separator); + s.Append(Bytes[i].ToString("X2")); + } + return s.ToString(); + } + + public static byte[] ConvertStringToHex(string HexString) + { + if (HexString.Length % 2 == 1) + throw new Exception("The binary key cannot have an odd number of digits"); + + byte[] arr = new byte[HexString.Length >> 1]; + + for (int i = 0; i < (HexString.Length >> 1); ++i) + { + arr[i] = (byte)((GetHexVal(HexString[i << 1]) << 4) + GetHexVal(HexString[(i << 1) + 1])); + } + + return arr; + } + + public static int GetHexVal(char hex) + { + int val = (int)hex; + //For uppercase A-F letters: + //return val - (val < 58 ? 48 : 55); + //For lowercase a-f letters: + //return val - (val < 58 ? 48 : 87); + //Or the two combined, but a bit slower: + return val - (val < 58 ? 48 : (val < 97 ? 55 : 87)); + } + } +} diff --git a/Patcher/Patcher/LICENSE b/Patcher/Patcher/LICENSE new file mode 100644 index 0000000..f879c1c --- /dev/null +++ b/Patcher/Patcher/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Patcher/Patcher/MainForm.Designer.cs b/Patcher/Patcher/MainForm.Designer.cs new file mode 100644 index 0000000..fc529b5 --- /dev/null +++ b/Patcher/Patcher/MainForm.Designer.cs @@ -0,0 +1,426 @@ +namespace Patcher +{ + partial class MainForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new System.Windows.Forms.Label(); + this.txtVisualStudioPath = new System.Windows.Forms.TextBox(); + this.FolderBrowserDialog = new System.Windows.Forms.FolderBrowserDialog(); + this.OpenFileDialog = new System.Windows.Forms.OpenFileDialog(); + this.SaveFileDialog = new System.Windows.Forms.SaveFileDialog(); + this.cmdVisualStudioPath = new System.Windows.Forms.Button(); + this.cmdInputFile = new System.Windows.Forms.Button(); + this.txtInputFile = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + this.cmdOutputFile = new System.Windows.Forms.Button(); + this.txtOutputFile = new System.Windows.Forms.TextBox(); + this.label3 = new System.Windows.Forms.Label(); + this.cmdPatchDefinitionsFile = new System.Windows.Forms.Button(); + this.txtPatchDefinitionsFile = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.cmbPatchDefinitionName = new System.Windows.Forms.ComboBox(); + this.cmbTargetVersion = new System.Windows.Forms.ComboBox(); + this.label6 = new System.Windows.Forms.Label(); + this.cmbTargetPath = new System.Windows.Forms.ComboBox(); + this.label7 = new System.Windows.Forms.Label(); + this.label8 = new System.Windows.Forms.Label(); + this.txtAssemblyCode = new System.Windows.Forms.TextBox(); + this.txtCompiledOpcodes = new System.Windows.Forms.TextBox(); + this.label9 = new System.Windows.Forms.Label(); + this.txtVirtualOffset = new System.Windows.Forms.TextBox(); + this.label10 = new System.Windows.Forms.Label(); + this.cmbCodeType = new System.Windows.Forms.ComboBox(); + this.label11 = new System.Windows.Forms.Label(); + this.cmdCompile = new System.Windows.Forms.Button(); + this.cmdPatch = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(15, 13); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(191, 13); + this.label1.TabIndex = 0; + this.label1.Text = "Path to Visual Studio with ARM32 SDK"; + // + // txtVisualStudioPath + // + this.txtVisualStudioPath.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtVisualStudioPath.Location = new System.Drawing.Point(18, 29); + this.txtVisualStudioPath.Name = "txtVisualStudioPath"; + this.txtVisualStudioPath.Size = new System.Drawing.Size(665, 20); + this.txtVisualStudioPath.TabIndex = 1; + // + // OpenFileDialog + // + this.OpenFileDialog.FileName = "openFileDialog1"; + // + // cmdVisualStudioPath + // + this.cmdVisualStudioPath.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdVisualStudioPath.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdVisualStudioPath.Location = new System.Drawing.Point(689, 28); + this.cmdVisualStudioPath.Name = "cmdVisualStudioPath"; + this.cmdVisualStudioPath.Size = new System.Drawing.Size(35, 22); + this.cmdVisualStudioPath.TabIndex = 2; + this.cmdVisualStudioPath.Text = "..."; + this.cmdVisualStudioPath.UseVisualStyleBackColor = true; + this.cmdVisualStudioPath.Click += new System.EventHandler(this.cmdVisualStudioPath_Click); + // + // cmdInputFile + // + this.cmdInputFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdInputFile.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdInputFile.Location = new System.Drawing.Point(689, 301); + this.cmdInputFile.Name = "cmdInputFile"; + this.cmdInputFile.Size = new System.Drawing.Size(35, 22); + this.cmdInputFile.TabIndex = 14; + this.cmdInputFile.Text = "..."; + this.cmdInputFile.UseVisualStyleBackColor = true; + this.cmdInputFile.Click += new System.EventHandler(this.cmdInputFile_Click); + // + // txtInputFile + // + this.txtInputFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtInputFile.Location = new System.Drawing.Point(18, 302); + this.txtInputFile.Name = "txtInputFile"; + this.txtInputFile.Size = new System.Drawing.Size(665, 20); + this.txtInputFile.TabIndex = 13; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(15, 286); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(47, 13); + this.label2.TabIndex = 12; + this.label2.Text = "Input file"; + // + // cmdOutputFile + // + this.cmdOutputFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdOutputFile.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdOutputFile.Location = new System.Drawing.Point(689, 349); + this.cmdOutputFile.Name = "cmdOutputFile"; + this.cmdOutputFile.Size = new System.Drawing.Size(35, 22); + this.cmdOutputFile.TabIndex = 17; + this.cmdOutputFile.Text = "..."; + this.cmdOutputFile.UseVisualStyleBackColor = true; + this.cmdOutputFile.Click += new System.EventHandler(this.cmdOutputFile_Click); + // + // txtOutputFile + // + this.txtOutputFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtOutputFile.Location = new System.Drawing.Point(18, 350); + this.txtOutputFile.Name = "txtOutputFile"; + this.txtOutputFile.Size = new System.Drawing.Size(665, 20); + this.txtOutputFile.TabIndex = 16; + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(15, 334); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(101, 13); + this.label3.TabIndex = 15; + this.label3.Text = "Output file (optional)"; + // + // cmdPatchDefinitionsFile + // + this.cmdPatchDefinitionsFile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.cmdPatchDefinitionsFile.Font = new System.Drawing.Font("Arial", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.cmdPatchDefinitionsFile.Location = new System.Drawing.Point(689, 91); + this.cmdPatchDefinitionsFile.Name = "cmdPatchDefinitionsFile"; + this.cmdPatchDefinitionsFile.Size = new System.Drawing.Size(35, 22); + this.cmdPatchDefinitionsFile.TabIndex = 5; + this.cmdPatchDefinitionsFile.Text = "..."; + this.cmdPatchDefinitionsFile.UseVisualStyleBackColor = true; + this.cmdPatchDefinitionsFile.Click += new System.EventHandler(this.cmdPatchDefinitionsFile_Click); + // + // txtPatchDefinitionsFile + // + this.txtPatchDefinitionsFile.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtPatchDefinitionsFile.Location = new System.Drawing.Point(18, 92); + this.txtPatchDefinitionsFile.Name = "txtPatchDefinitionsFile"; + this.txtPatchDefinitionsFile.Size = new System.Drawing.Size(665, 20); + this.txtPatchDefinitionsFile.TabIndex = 4; + this.txtPatchDefinitionsFile.Leave += new System.EventHandler(this.txtPatchDefinitionsFile_Leave); + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(15, 76); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(117, 13); + this.label4.TabIndex = 3; + this.label4.Text = "Patch defintions xml-file"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(15, 124); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(107, 13); + this.label5.TabIndex = 6; + this.label5.Text = "Patch defintion name"; + // + // cmbPatchDefinitionName + // + this.cmbPatchDefinitionName.FormattingEnabled = true; + this.cmbPatchDefinitionName.Location = new System.Drawing.Point(18, 140); + this.cmbPatchDefinitionName.Name = "cmbPatchDefinitionName"; + this.cmbPatchDefinitionName.Size = new System.Drawing.Size(302, 21); + this.cmbPatchDefinitionName.TabIndex = 7; + this.cmbPatchDefinitionName.SelectedValueChanged += new System.EventHandler(this.cmbPatchDefinitionName_SelectedValueChanged); + this.cmbPatchDefinitionName.Leave += new System.EventHandler(this.cmbPatchDefinitionName_Leave); + // + // cmbTargetVersion + // + this.cmbTargetVersion.FormattingEnabled = true; + this.cmbTargetVersion.Location = new System.Drawing.Point(18, 189); + this.cmbTargetVersion.Name = "cmbTargetVersion"; + this.cmbTargetVersion.Size = new System.Drawing.Size(302, 21); + this.cmbTargetVersion.TabIndex = 9; + this.cmbTargetVersion.SelectedValueChanged += new System.EventHandler(this.cmbTargetVersion_SelectedValueChanged); + this.cmbTargetVersion.Leave += new System.EventHandler(this.cmbTargetVersion_Leave); + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(15, 173); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(129, 13); + this.label6.TabIndex = 8; + this.label6.Text = "Target version description"; + // + // cmbTargetPath + // + this.cmbTargetPath.FormattingEnabled = true; + this.cmbTargetPath.Location = new System.Drawing.Point(18, 238); + this.cmbTargetPath.Name = "cmbTargetPath"; + this.cmbTargetPath.Size = new System.Drawing.Size(302, 21); + this.cmbTargetPath.TabIndex = 11; + // + // label7 + // + this.label7.AutoSize = true; + this.label7.Location = new System.Drawing.Point(15, 222); + this.label7.Name = "label7"; + this.label7.Size = new System.Drawing.Size(269, 13); + this.label7.TabIndex = 10; + this.label7.Text = "Folder for target file relative to Patch Defintion rootfolder"; + // + // label8 + // + this.label8.AutoSize = true; + this.label8.Location = new System.Drawing.Point(15, 497); + this.label8.Name = "label8"; + this.label8.Size = new System.Drawing.Size(365, 13); + this.label8.TabIndex = 22; + this.label8.Text = "ARM32 Patch / Shell assembly code (labels need to be followed by a colon)"; + // + // txtAssemblyCode + // + this.txtAssemblyCode.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtAssemblyCode.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtAssemblyCode.Location = new System.Drawing.Point(18, 513); + this.txtAssemblyCode.Multiline = true; + this.txtAssemblyCode.Name = "txtAssemblyCode"; + this.txtAssemblyCode.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtAssemblyCode.Size = new System.Drawing.Size(706, 161); + this.txtAssemblyCode.TabIndex = 23; + // + // txtCompiledOpcodes + // + this.txtCompiledOpcodes.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.txtCompiledOpcodes.Font = new System.Drawing.Font("Courier New", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.txtCompiledOpcodes.Location = new System.Drawing.Point(18, 713); + this.txtCompiledOpcodes.Multiline = true; + this.txtCompiledOpcodes.Name = "txtCompiledOpcodes"; + this.txtCompiledOpcodes.ScrollBars = System.Windows.Forms.ScrollBars.Vertical; + this.txtCompiledOpcodes.Size = new System.Drawing.Size(706, 59); + this.txtCompiledOpcodes.TabIndex = 25; + // + // label9 + // + this.label9.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.label9.AutoSize = true; + this.label9.Location = new System.Drawing.Point(15, 697); + this.label9.Name = "label9"; + this.label9.Size = new System.Drawing.Size(94, 13); + this.label9.TabIndex = 24; + this.label9.Text = "Compiled opcodes"; + // + // txtVirtualOffset + // + this.txtVirtualOffset.Location = new System.Drawing.Point(18, 416); + this.txtVirtualOffset.Name = "txtVirtualOffset"; + this.txtVirtualOffset.Size = new System.Drawing.Size(302, 20); + this.txtVirtualOffset.TabIndex = 19; + // + // label10 + // + this.label10.AutoSize = true; + this.label10.Location = new System.Drawing.Point(15, 400); + this.label10.Name = "label10"; + this.label10.Size = new System.Drawing.Size(309, 13); + this.label10.TabIndex = 18; + this.label10.Text = "Virtual offset (hex) (leave empty for only recalculating checksum)"; + // + // cmbCodeType + // + this.cmbCodeType.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbCodeType.FormattingEnabled = true; + this.cmbCodeType.Items.AddRange(new object[] { + "ARM", + "Thumb", + "Thumb2"}); + this.cmbCodeType.Location = new System.Drawing.Point(18, 464); + this.cmbCodeType.Name = "cmbCodeType"; + this.cmbCodeType.Size = new System.Drawing.Size(302, 21); + this.cmbCodeType.TabIndex = 21; + // + // label11 + // + this.label11.AutoSize = true; + this.label11.Location = new System.Drawing.Point(15, 448); + this.label11.Name = "label11"; + this.label11.Size = new System.Drawing.Size(55, 13); + this.label11.TabIndex = 20; + this.label11.Text = "Code type"; + // + // cmdCompile + // + this.cmdCompile.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cmdCompile.Location = new System.Drawing.Point(470, 792); + this.cmdCompile.Name = "cmdCompile"; + this.cmdCompile.Size = new System.Drawing.Size(120, 33); + this.cmdCompile.TabIndex = 26; + this.cmdCompile.Text = "Compile"; + this.cmdCompile.UseVisualStyleBackColor = true; + this.cmdCompile.Click += new System.EventHandler(this.cmdCompile_Click); + // + // cmdPatch + // + this.cmdPatch.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.cmdPatch.Location = new System.Drawing.Point(604, 792); + this.cmdPatch.Name = "cmdPatch"; + this.cmdPatch.Size = new System.Drawing.Size(120, 33); + this.cmdPatch.TabIndex = 27; + this.cmdPatch.Text = "Patch"; + this.cmdPatch.UseVisualStyleBackColor = true; + this.cmdPatch.Click += new System.EventHandler(this.cmdPatch_Click); + // + // MainForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(742, 840); + this.Controls.Add(this.cmdPatch); + this.Controls.Add(this.cmdCompile); + this.Controls.Add(this.cmbCodeType); + this.Controls.Add(this.label11); + this.Controls.Add(this.txtVirtualOffset); + this.Controls.Add(this.label10); + this.Controls.Add(this.txtCompiledOpcodes); + this.Controls.Add(this.label9); + this.Controls.Add(this.txtAssemblyCode); + this.Controls.Add(this.label8); + this.Controls.Add(this.cmbTargetPath); + this.Controls.Add(this.label7); + this.Controls.Add(this.cmbTargetVersion); + this.Controls.Add(this.label6); + this.Controls.Add(this.cmbPatchDefinitionName); + this.Controls.Add(this.label5); + this.Controls.Add(this.cmdPatchDefinitionsFile); + this.Controls.Add(this.txtPatchDefinitionsFile); + this.Controls.Add(this.label4); + this.Controls.Add(this.cmdOutputFile); + this.Controls.Add(this.txtOutputFile); + this.Controls.Add(this.label3); + this.Controls.Add(this.cmdInputFile); + this.Controls.Add(this.txtInputFile); + this.Controls.Add(this.label2); + this.Controls.Add(this.cmdVisualStudioPath); + this.Controls.Add(this.txtVisualStudioPath); + this.Controls.Add(this.label1); + this.Name = "MainForm"; + this.Text = "ARM patcher by Rene Lergner"; + this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.MainForm_FormClosed); + this.Load += new System.EventHandler(this.MainForm_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox txtVisualStudioPath; + private System.Windows.Forms.FolderBrowserDialog FolderBrowserDialog; + private System.Windows.Forms.OpenFileDialog OpenFileDialog; + private System.Windows.Forms.SaveFileDialog SaveFileDialog; + private System.Windows.Forms.Button cmdVisualStudioPath; + private System.Windows.Forms.Button cmdInputFile; + private System.Windows.Forms.TextBox txtInputFile; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Button cmdOutputFile; + private System.Windows.Forms.TextBox txtOutputFile; + private System.Windows.Forms.Label label3; + private System.Windows.Forms.Button cmdPatchDefinitionsFile; + private System.Windows.Forms.TextBox txtPatchDefinitionsFile; + private System.Windows.Forms.Label label4; + private System.Windows.Forms.Label label5; + private System.Windows.Forms.ComboBox cmbPatchDefinitionName; + private System.Windows.Forms.ComboBox cmbTargetVersion; + private System.Windows.Forms.Label label6; + private System.Windows.Forms.ComboBox cmbTargetPath; + private System.Windows.Forms.Label label7; + private System.Windows.Forms.Label label8; + private System.Windows.Forms.TextBox txtAssemblyCode; + private System.Windows.Forms.TextBox txtCompiledOpcodes; + private System.Windows.Forms.Label label9; + private System.Windows.Forms.TextBox txtVirtualOffset; + private System.Windows.Forms.Label label10; + private System.Windows.Forms.ComboBox cmbCodeType; + private System.Windows.Forms.Label label11; + private System.Windows.Forms.Button cmdCompile; + private System.Windows.Forms.Button cmdPatch; + } +} + diff --git a/Patcher/Patcher/MainForm.cs b/Patcher/Patcher/MainForm.cs new file mode 100644 index 0000000..df0e7f2 --- /dev/null +++ b/Patcher/Patcher/MainForm.cs @@ -0,0 +1,410 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Microsoft.Win32; +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using WPinternals; + +namespace Patcher +{ + public partial class MainForm : Form + { + public MainForm() + { + InitializeComponent(); + } + + private void MainForm_Load(object sender, EventArgs e) + { + cmbCodeType.SelectedItem = "Thumb2"; + LoadPaths(); + CenterToScreen(); + } + + private void cmdCompile_Click(object sender, EventArgs e) + { + string VirtualOffsetString = txtVirtualOffset.Text.Trim(); + if (VirtualOffsetString.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + VirtualOffsetString = VirtualOffsetString[2..]; + + CodeType CodeType = Patcher.CodeType.Thumb2; + byte[] CompiledCode = null; + if (UInt32.TryParse(VirtualOffsetString, System.Globalization.NumberStyles.HexNumber, null, out uint VirtualOffset)) + { + CodeType = (CodeType)Enum.Parse(typeof(Patcher.CodeType), cmbCodeType.SelectedItem.ToString()); + CompiledCode = ArmCompiler.Compile(txtVisualStudioPath.Text, VirtualOffset, CodeType, txtAssemblyCode.Text); + } + if ((VirtualOffset != 0) && (CompiledCode == null)) + txtCompiledOpcodes.Text = ArmCompiler.Output; + else if (CompiledCode != null) + txtCompiledOpcodes.Text = Converter.ConvertHexToString(CompiledCode, " "); + } + + private void LoadPaths() + { + RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\Patcher", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\Patcher"); + + txtVisualStudioPath.Text = (string)Key.GetValue("VisualStudioPath", ""); + if (txtVisualStudioPath.Text.Length == 0) + txtVisualStudioPath.Text = FindVisualStudioPath(); + + txtPatchDefinitionsFile.Text = (string)Key.GetValue("PatchDefinitionsFilePath", ""); + cmbPatchDefinitionName.Text = (string)Key.GetValue("PatchDefinitionName", ""); + cmbTargetVersion.Text = (string)Key.GetValue("TargetVersion", ""); + cmbTargetPath.Text = (string)Key.GetValue("TargetFilePath", ""); + txtInputFile.Text = (string)Key.GetValue("InputFilePath", ""); + txtOutputFile.Text = (string)Key.GetValue("OutputFilePath", ""); + + LoadPatchDefinitions(); + } + + public static string[] FindMSVCBinaryPaths(string s) + { + string LegacyPath = Path.Combine(s, @"VC\bin"); + if (Directory.Exists(LegacyPath)) + { + return new string[] { LegacyPath }; + } + + if (Directory.Exists(Path.Combine(s, @"VC\Tools\MSVC"))) + { + IEnumerable MSVCs = Directory.EnumerateDirectories(Path.Combine(s, @"VC\Tools\MSVC")); + IEnumerable Bins = MSVCs.Select(s => Path.Combine(s, "bin")).Where(s => Directory.Exists(s)); + return Bins.ToArray(); + } + + return Array.Empty(); + } + + public static string FindArmAsmPath(string s) + { + foreach (string MSVCBin in FindMSVCBinaryPaths(s)) + { + string path1 = Path.Combine(MSVCBin, "x86_arm"); + string path2 = Path.Combine(MSVCBin, @"Hostx86\arm"); + + if (File.Exists(Path.Combine(path1, "armasm.exe"))) + { + return path1; + } + + if (File.Exists(Path.Combine(path2, "armasm.exe"))) + { + return path2; + } + } + + return ""; + } + + private static string FindVisualStudioPath() + { + IEnumerable MainX86VSDirectories = Directory.EnumerateDirectories(@"C:\Program Files (x86)\", "Microsoft Visual Studio*"); + IEnumerable MainX64VSDirectories = Directory.EnumerateDirectories(@"C:\Program Files\", "Microsoft Visual Studio*"); + + IEnumerable MainVSDirectories = MainX86VSDirectories.Union(MainX64VSDirectories); + + IEnumerable SubMainVSDirectories = MainVSDirectories.SelectMany(s => Directory.EnumerateDirectories(s)); + IEnumerable SubSubMainVSDirectories = SubMainVSDirectories.SelectMany(s => Directory.EnumerateDirectories(s)); + IEnumerable Directories = MainVSDirectories.Union(SubMainVSDirectories).Union(SubSubMainVSDirectories); + + string attempt1 = Directories.Where(s => FindArmAsmPath(s) != "").OrderByDescending(s => File.GetCreationTime(Path.Combine(s, @"VC\bin\x86_arm\armasm.exe"))).FirstOrDefault() ?? ""; + + if (attempt1 != "") + return attempt1; + + return Directories.Where(s => Directory.Exists(Path.Combine(s, @"VC\Tools\MSVC"))).Select(s => Path.Combine(s, @"VC\Tools\MSVC")).SelectMany(s => Directory.EnumerateDirectories(s)).Where(s => File.Exists(Path.Combine(s, @"bin\Hostx86\arm\armasm.exe"))).OrderByDescending(s => File.GetCreationTime(Path.Combine(s, @"bin\Hostx86\arm\armasm.exe"))).FirstOrDefault() ?? ""; + } + + private void StorePaths() + { + RegistryKey Key = Registry.CurrentUser.OpenSubKey(@"Software\Patcher", true) ?? Registry.CurrentUser.CreateSubKey(@"Software\Patcher"); + + string VisualStudioPath = txtVisualStudioPath.Text.Trim(); + if (VisualStudioPath.Length == 0) + { + if (Key.GetValue("VisualStudioPath") != null) + Key.DeleteValue("VisualStudioPath"); + } + else + { + Key.SetValue("VisualStudioPath", VisualStudioPath); + } + + string PatchDefinitionsFilePath = txtPatchDefinitionsFile.Text.Trim(); + if (PatchDefinitionsFilePath.Length == 0) + { + if (Key.GetValue("PatchDefinitionsFilePath") != null) + Key.DeleteValue("PatchDefinitionsFilePath"); + } + else + { + Key.SetValue("PatchDefinitionsFilePath", PatchDefinitionsFilePath); + } + + string PatchDefinitionName = cmbPatchDefinitionName.Text.Trim(); + if (PatchDefinitionName.Length == 0) + { + if (Key.GetValue("PatchDefinitionName") != null) + Key.DeleteValue("PatchDefinitionName"); + } + else + { + Key.SetValue("PatchDefinitionName", PatchDefinitionName); + } + + string TargetVersion = cmbTargetVersion.Text.Trim(); + if (TargetVersion.Length == 0) + { + if (Key.GetValue("TargetVersion") != null) + Key.DeleteValue("TargetVersion"); + } + else + { + Key.SetValue("TargetVersion", TargetVersion); + } + + string TargetFilePath = cmbTargetPath.Text.Trim(); + if (TargetFilePath.Length == 0) + { + if (Key.GetValue("TargetFilePath") != null) + Key.DeleteValue("TargetFilePath"); + } + else + { + Key.SetValue("TargetFilePath", TargetFilePath); + } + + string InputFilePath = txtInputFile.Text.Trim(); + if (InputFilePath.Length == 0) + { + if (Key.GetValue("InputFilePath") != null) + Key.DeleteValue("InputFilePath"); + } + else + { + Key.SetValue("InputFilePath", InputFilePath); + } + + string OutputFilePath = txtOutputFile.Text.Trim(); + if (OutputFilePath.Length == 0) + { + if (Key.GetValue("OutputFilePath") != null) + Key.DeleteValue("OutputFilePath"); + } + else + { + Key.SetValue("OutputFilePath", OutputFilePath); + } + } + + private bool LoadingPatchDefinitions = false; + + private void LoadPatchDefinitions() + { + if (LoadingPatchDefinitions) + return; + LoadingPatchDefinitions = true; + + string PatchDefinitionName = cmbPatchDefinitionName.Text; + string TargetVersion = cmbTargetVersion.Text; + string TargetPath = cmbTargetPath.Text; + + cmbPatchDefinitionName.SelectedIndex = -1; + cmbTargetVersion.SelectedIndex = -1; + cmbTargetPath.SelectedIndex = -1; + + cmbPatchDefinitionName.Items.Clear(); + cmbTargetVersion.Items.Clear(); + cmbTargetPath.Items.Clear(); + + cmbPatchDefinitionName.Text = PatchDefinitionName; + cmbTargetVersion.Text = TargetVersion; + cmbTargetPath.Text = TargetPath; + + try + { + string Definitions = File.ReadAllText(txtPatchDefinitionsFile.Text); + PatchEngine Engine = new(Definitions); + Engine.PatchDefinitions.Where(d => !string.IsNullOrEmpty(d.Name)).Select(d => d.Name).Distinct().ToList().ForEach(n => cmbPatchDefinitionName.Items.Add(n)); + PatchDefinition Definition = null; + if (cmbPatchDefinitionName.Text.Trim().Length > 0) + Definition = Engine.PatchDefinitions.Find(d => string.Equals(d.Name, cmbPatchDefinitionName.Text.Trim(), StringComparison.CurrentCultureIgnoreCase)); + if (Definition != null) + { + Definition.TargetVersions.Where(v => !string.IsNullOrEmpty(v.Description)).Select(v => v.Description).Distinct().ToList().ForEach(d => cmbTargetVersion.Items.Add(d)); + TargetVersion Version = null; + if (cmbTargetVersion.Text.Trim().Length > 0) + Version = Definition.TargetVersions.Find(v => string.Equals(v.Description, cmbTargetVersion.Text.Trim(), StringComparison.CurrentCultureIgnoreCase)); + if (Version != null) + { + Version.TargetFiles.Where(f => !string.IsNullOrEmpty(f.Path)).Select(f => Path.GetDirectoryName(f.Path)).Distinct().ToList().ForEach(f => cmbTargetPath.Items.Add(f)); + } + } + } + catch { } + + LoadingPatchDefinitions = false; + } + + private void cmdVisualStudioPath_Click(object sender, EventArgs e) + { + FolderBrowserDialog.SelectedPath = txtVisualStudioPath.Text; + FolderBrowserDialog.Description = "Select path to Visual Studio with ARM32 SDK"; + System.Windows.Forms.DialogResult Result = FolderBrowserDialog.ShowDialog(); + if (Result == System.Windows.Forms.DialogResult.OK) + txtVisualStudioPath.Text = FolderBrowserDialog.SelectedPath; + } + + private void cmdPatchDefinitionsFile_Click(object sender, EventArgs e) + { + OpenFileDialog.CheckFileExists = false; + OpenFileDialog.DefaultExt = "xml"; + try + { + OpenFileDialog.FileName = Path.GetFileName(txtPatchDefinitionsFile.Text); + OpenFileDialog.InitialDirectory = Path.GetDirectoryName(txtPatchDefinitionsFile.Text); + } + catch { } + OpenFileDialog.Multiselect = false; + OpenFileDialog.Title = "Open patch-definitions file"; + System.Windows.Forms.DialogResult Result = OpenFileDialog.ShowDialog(); + if (Result == System.Windows.Forms.DialogResult.OK) + { + txtPatchDefinitionsFile.Text = OpenFileDialog.FileName; + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + } + + private void txtPatchDefinitionsFile_Leave(object sender, EventArgs e) + { + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + + private void MainForm_FormClosed(object sender, FormClosedEventArgs e) + { + StorePaths(); + } + + private void cmbPatchDefinitionName_SelectedValueChanged(object sender, EventArgs e) + { + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + + private void cmbPatchDefinitionName_Leave(object sender, EventArgs e) + { + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + + private void cmbTargetVersion_SelectedValueChanged(object sender, EventArgs e) + { + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + + private void cmbTargetVersion_Leave(object sender, EventArgs e) + { + WindowsFormsSynchronizationContext.Current.Post(s => LoadPatchDefinitions(), null); + } + + private void cmdInputFile_Click(object sender, EventArgs e) + { + OpenFileDialog.CheckFileExists = true; + OpenFileDialog.DefaultExt = ""; + try + { + OpenFileDialog.FileName = Path.GetFileName(txtInputFile.Text); + OpenFileDialog.InitialDirectory = Path.GetDirectoryName(txtInputFile.Text); + } + catch { } + OpenFileDialog.Multiselect = false; + OpenFileDialog.Title = "Open input file"; + System.Windows.Forms.DialogResult Result = OpenFileDialog.ShowDialog(); + if (Result == System.Windows.Forms.DialogResult.OK) + { + txtInputFile.Text = OpenFileDialog.FileName; + txtOutputFile.Text = ""; + } + } + + private void cmdOutputFile_Click(object sender, EventArgs e) + { + SaveFileDialog.CheckFileExists = false; + SaveFileDialog.DefaultExt = ""; + try + { + SaveFileDialog.FileName = Path.GetFileName(txtInputFile.Text); + SaveFileDialog.InitialDirectory = Path.GetDirectoryName(txtOutputFile.Text); + } + catch { } + SaveFileDialog.Title = "Open input file"; + System.Windows.Forms.DialogResult Result = SaveFileDialog.ShowDialog(); + if (Result == System.Windows.Forms.DialogResult.OK) + txtOutputFile.Text = SaveFileDialog.FileName; + } + + private void cmdPatch_Click(object sender, EventArgs e) + { + string VirtualOffsetString = txtVirtualOffset.Text.Trim(); + if (VirtualOffsetString.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + VirtualOffsetString = VirtualOffsetString[2..]; + CodeType CodeType = Patcher.CodeType.Thumb2; + byte[] CompiledCode = null; + if (UInt32.TryParse(VirtualOffsetString, System.Globalization.NumberStyles.HexNumber, null, out uint VirtualAddress)) + { + CodeType = (CodeType)Enum.Parse(typeof(Patcher.CodeType), cmbCodeType.SelectedItem.ToString()); + CompiledCode = ArmCompiler.Compile(txtVisualStudioPath.Text, VirtualAddress, CodeType, txtAssemblyCode.Text); + } + if ((VirtualAddress != 0) && (CompiledCode == null)) + { + txtCompiledOpcodes.Text = ArmCompiler.Output; + } + else + { + if (CompiledCode != null) + txtCompiledOpcodes.Text = Converter.ConvertHexToString(CompiledCode, " "); + + string TargetFilePath = Path.Combine(cmbTargetPath.Text, Path.GetFileName(txtInputFile.Text)); + if (TargetFilePath.StartsWith(@"\")) + TargetFilePath = TargetFilePath[1..]; + + try + { + MainPatcher.AddPatch(txtInputFile.Text, (txtOutputFile.Text.Trim().Length > 0) ? txtOutputFile.Text : null, cmbPatchDefinitionName.Text, cmbTargetVersion.Text, TargetFilePath, txtVisualStudioPath.Text, VirtualAddress, CodeType, txtAssemblyCode.Text, txtPatchDefinitionsFile.Text); + } + catch (ArgumentOutOfRangeException) + { + txtCompiledOpcodes.Text = "BAD VIRTUAL OFFSET"; + } + catch + { + txtCompiledOpcodes.Text = "UNKNOWN ERROR"; + } + + txtVirtualOffset.Focus(); + txtVirtualOffset.SelectAll(); + } + } + } +} diff --git a/Patcher/Patcher/MainForm.resx b/Patcher/Patcher/MainForm.resx new file mode 100644 index 0000000..28d85c9 --- /dev/null +++ b/Patcher/Patcher/MainForm.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + + 182, 17 + + + 317, 17 + + \ No newline at end of file diff --git a/Patcher/Patcher/MainPatcher.cs b/Patcher/Patcher/MainPatcher.cs new file mode 100644 index 0000000..4ffde52 --- /dev/null +++ b/Patcher/Patcher/MainPatcher.cs @@ -0,0 +1,189 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using WPinternals; + +namespace Patcher +{ + public static class MainPatcher + { + /// + /// TargetFilePath is relative to the root of the PatchDefinition + /// OutputFilePath can be null + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static void AddPatch(string InputFilePath, string OutputFilePath, string PatchDefinitionName, string TargetVersionDescription, string TargetFilePath, string PathToVisualStudioWithWP8SDK, UInt32 VirtualAddress, CodeType CodeType, string ArmCodeFragment, string PatchDefintionsXmlPath) + { + SHA1Managed SHA = new(); + + // Compile ARM code + byte[] CompiledCode = null; + if (VirtualAddress != 0) + CompiledCode = ArmCompiler.Compile(PathToVisualStudioWithWP8SDK, VirtualAddress, CodeType, ArmCodeFragment); + + // Read original binary + byte[] Binary = File.ReadAllBytes(InputFilePath); + + // Backup original checksum + UInt32 ChecksumOffset = GetChecksumOffset(Binary); + UInt32 OriginalChecksum = ByteOperations.ReadUInt32(Binary, ChecksumOffset); + + // Determine Raw Offset + PeFile PeFile = new(Binary); + UInt32 RawOffset = 0; + if (VirtualAddress != 0) + RawOffset = PeFile.ConvertVirtualAddressToRawOffset(VirtualAddress); + + // Add or replace patch + string PatchDefintionsXml = File.ReadAllText(PatchDefintionsXmlPath); + PatchEngine PatchEngine = new(PatchDefintionsXml); + PatchDefinition PatchDefinition = PatchEngine.PatchDefinitions.Find(d => string.Equals(d.Name, PatchDefinitionName, StringComparison.CurrentCultureIgnoreCase)); + if (PatchDefinition == null) + { + PatchDefinition = new PatchDefinition + { + Name = PatchDefinitionName + }; + PatchEngine.PatchDefinitions.Add(PatchDefinition); + } + TargetVersion TargetVersion = PatchDefinition.TargetVersions.Find(v => string.Equals(v.Description, TargetVersionDescription, StringComparison.CurrentCultureIgnoreCase)); + if (TargetVersion == null) + { + TargetVersion = new TargetVersion + { + Description = TargetVersionDescription + }; + PatchDefinition.TargetVersions.Add(TargetVersion); + } + TargetFile TargetFile = TargetVersion.TargetFiles.Find(f => (f.Path != null) && (string.Equals(f.Path.TrimStart(new char[] { '\\' }), TargetFilePath.TrimStart(new char[] { '\\' }), StringComparison.CurrentCultureIgnoreCase))); + if (TargetFile == null) + { + TargetFile = new TargetFile(); + TargetVersion.TargetFiles.Add(TargetFile); + } + TargetFile.Path = TargetFilePath; + TargetFile.HashOriginal = SHA.ComputeHash(Binary); + Patch Patch; + if (VirtualAddress != 0) + { + Patch = TargetFile.Patches.Find(p => p.Address == RawOffset); + if (Patch == null) + { + Patch = new Patch + { + Address = RawOffset + }; + TargetFile.Patches.Add(Patch); + } + Patch.OriginalBytes = new byte[CompiledCode.Length]; + Buffer.BlockCopy(Binary, (int)RawOffset, Patch.OriginalBytes, 0, CompiledCode.Length); + Patch.PatchedBytes = CompiledCode; + } + + // Apply all patches + foreach (Patch CurrentPatch in TargetFile.Patches) + { + Buffer.BlockCopy(CurrentPatch.PatchedBytes, 0, Binary, (int)CurrentPatch.Address, CurrentPatch.PatchedBytes.Length); + } + + // Calculate checksum + // This also modifies the binary + // Original checksum is already backed up + UInt32 Checksum = CalculateChecksum(Binary); + + // Add or replace checksum patch + Patch = TargetFile.Patches.Find(p => p.Address == ChecksumOffset); + if (Patch == null) + { + Patch = new Patch + { + Address = ChecksumOffset + }; + TargetFile.Patches.Add(Patch); + } + Patch.OriginalBytes = new byte[4]; + ByteOperations.WriteUInt32(Patch.OriginalBytes, 0, OriginalChecksum); + Patch.PatchedBytes = new byte[4]; + ByteOperations.WriteUInt32(Patch.PatchedBytes, 0, Checksum); + + // Calculate hash for patched target file + TargetFile.HashPatched = SHA.ComputeHash(Binary); + + // Write patched file + if (OutputFilePath != null) + File.WriteAllBytes(OutputFilePath, Binary); + + // Write PatchDefintions + PatchEngine.WriteDefinitions(PatchDefintionsXmlPath); + } + + private static UInt32 CalculateChecksum(byte[] PEFile) + { + UInt32 Checksum = 0; + UInt32 Hi; + + // Clear file checksum + ByteOperations.WriteUInt32(PEFile, GetChecksumOffset(PEFile), 0); + + for (UInt32 i = 0; i < ((UInt32)PEFile.Length & 0xfffffffe); i += 2) + { + Checksum += ByteOperations.ReadUInt16(PEFile, i); + Hi = Checksum >> 16; + if (Hi != 0) + { + Checksum = Hi + (Checksum & 0xFFFF); + } + } + if ((PEFile.Length % 2) != 0) + { + Checksum += (UInt32)ByteOperations.ReadUInt8(PEFile, (UInt32)PEFile.Length - 1); + Hi = Checksum >> 16; + if (Hi != 0) + { + Checksum = Hi + (Checksum & 0xFFFF); + } + } + Checksum += (UInt32)PEFile.Length; + + // Write file checksum + ByteOperations.WriteUInt32(PEFile, GetChecksumOffset(PEFile), Checksum); + + return Checksum; + } + + private static UInt32 GetChecksumOffset(byte[] PEFile) + { + return ByteOperations.ReadUInt32(PEFile, 0x3C) + +0x58; + } + } +} diff --git a/Patcher/Patcher/ObjectFileParser.cs b/Patcher/Patcher/ObjectFileParser.cs new file mode 100644 index 0000000..ac5057d --- /dev/null +++ b/Patcher/Patcher/ObjectFileParser.cs @@ -0,0 +1,819 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// This code is based on Get-ObjDump from Matthew Graeber (@mattifestation) + +using System; +using System.IO; +using System.Text; +using System.Linq; + +namespace COFF +{ + public enum Machine : ushort + { + UNKNOWN = 0, + /// + /// Intel 386. + /// + I386 = 0x014C, + /// + /// MIPS little-endian =0x160 big-endian + /// + R3000 = 0x0162, + /// + /// MIPS little-endian + /// + R4000 = 0x0166, + /// + /// MIPS little-endian + /// + R10000 = 0x0168, + /// + /// MIPS little-endian WCE v2 + /// + WCEMIPSV2 = 0x0169, + /// + /// Alpha_AXP + /// + ALPHA = 0x0184, + /// + /// SH3 little-endian + /// + SH3 = 0x01A2, + SH3DSP = 0x01A3, + /// + /// SH3E little-endian + /// + SH3E = 0x01A4, + /// + /// SH4 little-endian + /// + SH4 = 0x01A6, + /// + /// SH5 + /// + SH5 = 0x01A8, + /// + /// ARM Little-Endian + /// + ARM = 0x01C0, + THUMB = 0x01C2, + /// + /// ARM Thumb-2 Little-Endian + /// + ARMV7 = 0x01C4, + AM33 = 0x01D3, + /// + /// IBM PowerPC Little-Endian + /// + POWERPC = 0x01F0, + POWERPCFP = 0x01F1, + /// + /// Intel 64 + /// + IA64 = 0x0200, + /// + /// MIPS + /// + MIPS16 = 0x0266, + /// + /// ALPHA64 + /// + ALPHA64 = 0x0284, + /// + /// MIPS + /// + MIPSFPU = 0x0366, + /// + /// MIPS + /// + MIPSFPU16 = 0x0466, + AXP64 = ALPHA64, + /// + /// Infineon + /// + TRICORE = 0x0520, + CEF = 0x0CEF, + /// + /// EFI public byte Code + /// + EBC = 0x0EBC, + /// + /// AMD64 (K8) + /// + AMD64 = 0x8664, + /// + /// M32R little-endian + /// + M32R = 0x9041, + /// + /// ARMv8 in 64-bit mode + /// + ARM64 = 0xAA64, + CEE = 0xC0EE + } + + [Flags] + public enum CoffHeaderCharacteristics : ushort + { + /// + /// Relocation info stripped from file. + /// + RELOCS_STRIPPED = 0x0001, + /// + /// File is executable (i.e. no unresolved external references). + /// + EXECUTABLE_IMAGE = 0x0002, + /// + /// Line nunbers stripped from file. + /// + LINE_NUMS_STRIPPED = 0x0004, + /// + /// Local symbols stripped from file. + /// + LOCAL_SYMS_STRIPPED = 0x0008, + /// + /// Agressively trim working set + /// + AGGRESIVE_WS_TRIM = 0x0010, + /// + /// App can handle >2gb addresses + /// + LARGE_ADDRESS_AWARE = 0x0020, + /// + /// public bytes of machine public ushort are reversed. + /// + REVERSED_LO = 0x0080, + /// + /// 32 bit public ushort machine. + /// + BIT32_MACHINE = 0x0100, + /// + /// Debugging info stripped from file in .DBG file + /// + DEBUG_STRIPPED = 0x0200, + /// + /// If Image is on removable media =copy and run from the swap file. + /// + REMOVABLE_RUN_FROM_SWAP = 0x0400, + /// + /// If Image is on Net =copy and run from the swap file. + /// + NET_RUN_FROM_SWAP = 0x0800, + /// + /// System File. + /// + SYSTEM = 0x1000, + /// + /// File is a DLL. + /// + DLL = 0x2000, + /// + /// File should only be run on a UP machine + /// + UP_SYSTEM_ONLY = 0x4000, + /// + /// public bytes of machine public ushort are reversed. + /// + REVERSED_HI = 0x8000 + } + + public class HEADER + { + public Machine Machine; + public ushort NumberOfSections; + public DateTime TimeDateStamp; + public uint PointerToSymbolTable; + public uint NumberOfSymbols; + public ushort SizeOfOptionalHeader; + public CoffHeaderCharacteristics Characteristics; + + public HEADER(BinaryReader br) + { + this.Machine = (Machine)br.ReadUInt16(); + this.NumberOfSections = br.ReadUInt16(); + this.TimeDateStamp = new DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(br.ReadUInt32()); + this.PointerToSymbolTable = br.ReadUInt32(); + this.NumberOfSymbols = br.ReadUInt32(); + this.SizeOfOptionalHeader = br.ReadUInt16(); + this.Characteristics = (CoffHeaderCharacteristics)br.ReadUInt16(); + } + } + + [Flags] + public enum SectionHeaderCharacteristics : uint + { + /// + /// Reserved. + /// + TYPE_NO_PAD = 0x00000008, + /// + /// Section contains code. + /// + CNT_CODE = 0x00000020, + /// + /// Section contains initialized data. + /// + CNT_INITIALIZED_DATA = 0x00000040, + /// + /// Section contains uninitialized data. + /// + CNT_UNINITIALIZED_DATA = 0x00000080, + /// + /// Section contains comments or some other type of information. + /// + LNK_INFO = 0x00000200, + /// + /// Section contents will not become part of image. + /// + LNK_REMOVE = 0x00000800, + /// + /// Section contents comdat. + /// + LNK_COMDAT = 0x00001000, + /// + /// Reset speculative exceptions handling bits in the TLB entries for this section. + /// + NO_DEFER_SPEC_EXC = 0x00004000, + /// + /// Section content can be accessed relative to GP + /// + GPREL = 0x00008000, + MEM_FARDATA = 0x00008000, + MEM_PURGEABLE = 0x00020000, + MEM_16BIT = 0x00020000, + MEM_LOCKED = 0x00040000, + MEM_PRELOAD = 0x00080000, + ALIGN_1BYTES = 0x00100000, + ALIGN_2BYTES = 0x00200000, + ALIGN_4BYTES = 0x00300000, + ALIGN_8BYTES = 0x00400000, + /// + /// Default alignment if no others are specified. + /// + ALIGN_16BYTES = 0x00500000, + ALIGN_32BYTES = 0x00600000, + ALIGN_64BYTES = 0x00700000, + ALIGN_128BYTES = 0x00800000, + ALIGN_256BYTES = 0x00900000, + ALIGN_512BYTES = 0x00A00000, + ALIGN_1024BYTES = 0x00B00000, + ALIGN_2048BYTES = 0x00C00000, + ALIGN_4096BYTES = 0x00D00000, + ALIGN_8192BYTES = 0x00E00000, + ALIGN_MASK = 0x00F00000, + /// + /// Section contains extended relocations. + /// + LNK_NRELOC_OVFL = 0x01000000, + /// + /// Section can be discarded. + /// + MEM_DISCARDABLE = 0x02000000, + /// + /// Section is not cachable. + /// + MEM_NOT_CACHED = 0x04000000, + /// + /// Section is not pageable. + /// + MEM_NOT_PAGED = 0x08000000, + /// + /// Section is shareable. + /// + MEM_SHARED = 0x10000000, + /// + /// Section is executable. + /// + MEM_EXECUTE = 0x20000000, + /// + /// Section is readable. + /// + MEM_READ = 0x40000000, + /// + /// Section is writeable. + /// + MEM_WRITE = 0x80000000 + } + + public enum AMD64RelocationType : ushort + { + ABSOLUTE, + ADDR64, + ADDR32, + ADDR32NB, + REL32, + REL32_1, + REL32_2, + REL32_3, + REL32_4, + REL32_5, + SECTION, + SECREL, + SECREL7, + TOKEN, + SREL32, + PAIR, + SSPAN32 + } + + public enum ARMRelocationType : ushort + { + ABSOLUTE, + ADDR32, + ADDR32NB, + BRANCH24, + BRANCH11, + TOKEN, + BLX24 = 0x08, + BLX11 = 0x09, + SECTION = 0x0E, + SECREL = 0x0F, + MOV32A = 0x10, + MOV32T = 0x11, + BRANCH20T = 0x12, + BRANCH24T = 0x14, + BLX23T = 0x15 + } + + public enum ARMv8RelocationType : ushort + { + ABSOLUTE, + ADDR32, + ADDR32NB, + BRANCH26, + PAGEBASE_REL21, + REL21, + PAGEOFFSET_12A, + PAGEOFFSET_12L, + SECREL, + SECREL_LOW12A, + SECREL_HIGH12A, + SECREL_LOW12L, + TOKEN, + SECTION, + ADDR64 + } + + public enum X86RelocationType : ushort + { + ABSOLUTE, + DIR16, + DIR32 = 0x06, + DIR32NB = 0x07, + SEG12 = 0x09, + SECTION = 0x0A, + SECREL = 0x0B, + TOKEN = 0x0C, + SECREL7 = 0x0D, + REL32 = 0x14 + } + + public class RelocationEntry + { + public uint VirtualAddress; + public uint SymbolTableIndex; + public Enum Type; + public string Name; + + public RelocationEntry(BinaryReader br) + { + this.VirtualAddress = br.ReadUInt32(); + this.SymbolTableIndex = br.ReadUInt32(); + // Default to X86RelocationType. This will be changed once the processor type is determined + this.Type = (X86RelocationType)br.ReadUInt16(); + } + } + + public class SECTION_HEADER + { + public string Name; + public uint PhysicalAddress; + public uint VirtualSize; + public uint VirtualAddress; + public uint SizeOfRawData; + public uint PointerToRawData; + public uint PointerToRelocations; + public uint PointerToLinenumbers; + public ushort NumberOfRelocations; + public ushort NumberOfLinenumbers; + public SectionHeaderCharacteristics Characteristics; + public Byte[] RawData; + public RelocationEntry[] Relocations; + + public SECTION_HEADER(BinaryReader br) + { + this.Name = Encoding.UTF8.GetString(br.ReadBytes(8)).Split((Char)0)[0]; + this.PhysicalAddress = br.ReadUInt32(); + this.VirtualSize = this.PhysicalAddress; + this.VirtualAddress = br.ReadUInt32(); + this.SizeOfRawData = br.ReadUInt32(); + this.PointerToRawData = br.ReadUInt32(); + this.PointerToRelocations = br.ReadUInt32(); + this.PointerToLinenumbers = br.ReadUInt32(); + this.NumberOfRelocations = br.ReadUInt16(); + this.NumberOfLinenumbers = br.ReadUInt16(); + this.Characteristics = (SectionHeaderCharacteristics)br.ReadUInt32(); + } + } + + public enum SectionNumber : short + { + UNDEFINED, + ABSOLUTE = -1, + DEBUG = -2 + } + + [Flags] + public enum TypeClass : short + { + TYPE_NULL, + TYPE_VOID, + TYPE_CHAR, + TYPE_SHORT, + TYPE_INT, + TYPE_LONG, + TYPE_FLOAT, + TYPE_DOUBLE, + TYPE_STRUCT, + TYPE_UNION, + TYPE_ENUM, + TYPE_MOE, + TYPE_BYTE, + TYPE_WORD, + TYPE_UINT, + TYPE_DWORD, + DTYPE_POINTER = 0x100, + DTYPE_FUNCTION = 0x200, + DTYPE_ARRAY = 0x300, + /// + /// Technically, this is defined as 0 in the MSB + /// + DTYPE_NULL = 0x400 + } + + public enum StorageClass : byte + { + NULL, + AUTOMATIC, + EXTERNAL, + STATIC, + REGISTER, + EXTERNAL_DEF, + LABEL, + UNDEFINED_LABEL, + MEMBER_OF_STRUCT, + ARGUMENT, + STRUCT_TAG, + MEMBER_OF_UNION, + UNION_TAG, + TYPE_DEFINITION, + ENUM_TAG, + MEMBER_OF_ENUM, + REGISTER_PARAM, + BIT_FIELD, + BLOCK = 0x64, + FUNCTION = 0x65, + END_OF_STRUCT = 0x66, + FILE = 0x67, + SECTION = 0x68, + WEAK_EXTERNAL = 0x69, + CLR_TOKEN = 0x6B, + END_OF_FUNCTION = 0xFF + } + + public class SYMBOL_TABLE + { + public string Name; + public uint Value; + public SectionNumber SectionNumber; + public TypeClass Type; + public StorageClass StorageClass; + public byte NumberOfAuxSymbols; + public Object AuxSymbols; + private readonly Byte[] NameArray; + + public SYMBOL_TABLE(BinaryReader br) + { + this.NameArray = br.ReadBytes(8); + + if (this.NameArray[0] == 0 && this.NameArray[1] == 0 && this.NameArray[2] == 0 && this.NameArray[3] == 0) + { + // Per specification, if the high DWORD is 0, then then low DWORD is an index into the string table + this.Name = "/" + BitConverter.ToInt32(NameArray, 4).ToString(); + } + else + { + this.Name = Encoding.UTF8.GetString(NameArray).Trim((char)0); + } + + this.Value = br.ReadUInt32(); + this.SectionNumber = (SectionNumber)br.ReadInt16(); + this.Type = (TypeClass)br.ReadInt16(); + if ((((int)this.Type) & 0xff00) == 0) { this.Type = (TypeClass)Enum.Parse(typeof(TypeClass), ((int)this.Type | 0x400).ToString()); } + this.StorageClass = (StorageClass)br.ReadByte(); + this.NumberOfAuxSymbols = br.ReadByte(); + } + } + + public class SECTION_DEFINITION + { + public uint Length; + public ushort NumberOfRelocations; + public ushort NumberOfLinenumbers; + public uint CheckSum; + public ushort Number; + public byte Selection; + + public SECTION_DEFINITION(BinaryReader br) + { + this.Length = br.ReadUInt32(); + this.NumberOfRelocations = br.ReadUInt16(); + this.NumberOfLinenumbers = br.ReadUInt16(); + this.CheckSum = br.ReadUInt32(); + this.Number = br.ReadUInt16(); + this.Selection = br.ReadByte(); + br.ReadBytes(3); + } + } + + public class ParsedObjectFile + { + public HEADER CoffHeader; + public SECTION_HEADER[] SectionHeaders; + public SYMBOL_TABLE[] SymbolTable; + } + + public static class ObjectFileParser + { + public static ParsedObjectFile ParseObjectFile(string ObjectFilePath) + { + ParsedObjectFile Result = null; + + // Fixed structure sizes + const int SizeofCOFFFileHeader = 20; + const int SizeofSectionHeader = 40; + const int SizeofSymbolTableEntry = 18; + const int SizeofRelocationEntry = 10; + + // Open the object file for reading + using (FileStream FileStream = System.IO.File.OpenRead(ObjectFilePath)) + { + long FileLength = FileStream.Length; + + if (FileLength < SizeofCOFFFileHeader) + { + // You cannot parse the COFF header if the file is not big enough to contain a COFF header. + throw new Exception("ObjectFile is too small to store a COFF header."); + } + + // Open a BinaryReader object for the object file + using BinaryReader BinaryReader = new(FileStream); + // Parse the COFF header + COFF.HEADER CoffHeader = new(BinaryReader); + + if (CoffHeader.SizeOfOptionalHeader != 0) + { + // Per the PECOFF specification, an object file does not have an optional header + throw new Exception("Coff header indicates the existence of an optional header. An object file cannot have an optional header."); + } + + if (CoffHeader.PointerToSymbolTable == 0) + { + throw new Exception("An object file is supposed to have a symbol table."); + } + + if (FileLength < ((CoffHeader.NumberOfSections * SizeofSectionHeader) + SizeofCOFFFileHeader)) + { + // The object file isn't big enough to store the number of sections present. + throw new Exception("ObjectFile is too small to store section header data."); + } + + // A string collection used to store section header names. This collection is referenced while + // parsing the symbol table entries whose name is the same as the section header. In this case, + // the symbol entry will have a particular auxiliary symbol table entry. + System.Collections.Specialized.StringCollection SectionHeaderNames = new(); + + // Correlate the processor type to the relocation type. There are more relocation type defined + // in the PECOFF specification, but I don't expect those to be present. In that case, relocation + // entries default to X86RelocationType. + COFF.SECTION_HEADER[] SectionHeaders = new COFF.SECTION_HEADER[CoffHeader.NumberOfSections]; + + // Parse section headers + for (int i = 0; i < CoffHeader.NumberOfSections; i++) + { + SectionHeaders[i] = new COFF.SECTION_HEADER(BinaryReader); + + // Add the section name to the string collection. This will be referenced during symbol table parsing. + SectionHeaderNames.Add(SectionHeaders[i].Name); + + // Save the current filestream position. We are about to jump out of place. + long SavedFilePosition = FileStream.Position; + + // Check to see if the raw data points beyond the actual file size + if ((SectionHeaders[i].PointerToRawData + SectionHeaders[i].SizeOfRawData) > FileLength) + { + throw new Exception("Section header's raw data exceeds the size of the object file."); + } + else + { + // Read the raw data into a byte array + FileStream.Seek(SectionHeaders[i].PointerToRawData, SeekOrigin.Begin); + SectionHeaders[i].RawData = BinaryReader.ReadBytes((int)SectionHeaders[i].SizeOfRawData); + } + + // Check to see if the section has a relocation table + if ((SectionHeaders[i].PointerToRelocations != 0) && (SectionHeaders[i].NumberOfRelocations != 0)) + { + // Check to see if the relocation entries point beyond the actual file size + if ((SectionHeaders[i].PointerToRelocations + (SizeofRelocationEntry * SectionHeaders[i].NumberOfRelocations)) > FileLength) + { + throw new Exception("(SectionHeaders[i].Name) section header's relocation entries exceeds the soze of the object file."); + } + + FileStream.Seek(SectionHeaders[i].PointerToRelocations, SeekOrigin.Begin); + + COFF.RelocationEntry[] Relocations = new COFF.RelocationEntry[SectionHeaders[i].NumberOfRelocations]; + + for (int j = 0; j < SectionHeaders[i].NumberOfRelocations; j++) + { + Relocations[j] = new COFF.RelocationEntry(BinaryReader); + // Cast the relocation as its respective type + switch (CoffHeader.Machine) + { + case Machine.I386: + Relocations[j].Type = (COFF.X86RelocationType)Relocations[j].Type; + break; + case Machine.AMD64: + Relocations[j].Type = (COFF.AMD64RelocationType)Relocations[j].Type; + break; + case Machine.ARMV7: + Relocations[j].Type = (COFF.ARMRelocationType)Relocations[j].Type; + break; + case Machine.ARM64: + Relocations[j].Type = (COFF.ARMv8RelocationType)Relocations[j].Type; + break; + } + } + + // Add the relocation table entry to the section header + SectionHeaders[i].Relocations = Relocations; + } + + // Restore the original filestream pointer + FileStream.Seek(SavedFilePosition, SeekOrigin.Begin); + } + + // Retrieve the contents of the COFF string table + long SymTableSize = CoffHeader.NumberOfSymbols * SizeofSymbolTableEntry; + long StringTableOffset = CoffHeader.PointerToSymbolTable + SymTableSize; + + if (StringTableOffset > FileLength) + { + throw new Exception("The string table points beyond the end of the file."); + } + + FileStream.Seek(StringTableOffset, SeekOrigin.Begin); + UInt32 StringTableLength = BinaryReader.ReadUInt32(); + + if (StringTableLength > FileLength) + { + throw new Exception("The string table's length exceeds the length of the file."); + } + + string StringTable = System.Text.Encoding.UTF8.GetString(BinaryReader.ReadBytes((int)StringTableLength)); + + COFF.SYMBOL_TABLE[] RawSymbolTable = new COFF.SYMBOL_TABLE[CoffHeader.NumberOfSymbols]; + + // Retrieve the symbol table + if (FileLength < StringTableOffset) + { + throw new Exception("Symbol table is larger than the file size."); + } + + FileStream.Seek(CoffHeader.PointerToSymbolTable, SeekOrigin.Begin); + int NumberofRegularSymbols = 0; + + /* + Go through each symbol table looking for auxiliary symbols to parse + + Currently supported auxiliary symbol table entry formats: + 1) .file + 2) Entry names that match the name of a section header + */ + + for (int i = 0; i < CoffHeader.NumberOfSymbols; i++) + { + // Parse the symbol tables regardless of whether they are normal or auxiliary symbols + RawSymbolTable[i] = new COFF.SYMBOL_TABLE(BinaryReader); + + if (RawSymbolTable[i].NumberOfAuxSymbols == 0) + { + // This symbol table entry has no auxiliary symbols + NumberofRegularSymbols++; + } + else if (RawSymbolTable[i].Name == ".file") + { + long TempPosition = FileStream.Position; // Save filestream position + // Retrieve the file name + RawSymbolTable[i].AuxSymbols = System.Text.Encoding.UTF8.GetString(BinaryReader.ReadBytes(RawSymbolTable[i].NumberOfAuxSymbols * SizeofSymbolTableEntry)).TrimEnd((Char)0); + FileStream.Seek(TempPosition, SeekOrigin.Begin); // Restore filestream position + } + else if (SectionHeaderNames.Contains(RawSymbolTable[i].Name)) + { + long TempPosition = FileStream.Position; // Save filestream position + RawSymbolTable[i].AuxSymbols = new COFF.SECTION_DEFINITION(BinaryReader); + FileStream.Seek(TempPosition, SeekOrigin.Begin); // Restore filestream position + } + } + + // Create an array of symbol table entries without auxiliary table entries + COFF.SYMBOL_TABLE[] SymbolTable = new COFF.SYMBOL_TABLE[NumberofRegularSymbols]; + int k = 0; + + for (int i = 0; i < CoffHeader.NumberOfSymbols; i++) + { + SymbolTable[k] = RawSymbolTable[i]; // FYI, the first symbol table entry will never be an aux symbol + k++; + + // Skip over the auxiliary symbols + if (RawSymbolTable[i].NumberOfAuxSymbols != 0) + { + i += RawSymbolTable[i].NumberOfAuxSymbols; + } + } + + // Fix the section names if any of them point to the COFF string table + for (int i = 0; i < CoffHeader.NumberOfSections; i++) + { + if (SectionHeaders[i].Name?.IndexOf('/') == 0) + { + string StringTableIndexString = SectionHeaders[i].Name[1..]; + + if (int.TryParse(StringTableIndexString, out int StringTableIndex)) + { + StringTableIndex -= 4; + + if (StringTableIndex > (StringTableLength + 4)) + { + throw new Exception("String table entry exceeds the bounds of the object file."); + } + + int Length = StringTable.IndexOf((Char)0, StringTableIndex); + SectionHeaders[i].Name = StringTable.Substring(StringTableIndex, Length); + } + } + } + + // Fix the symbol table names + for (int i = 0; i < SymbolTable.Length; i++) + { + if (SymbolTable[i].Name?.IndexOf('/') == 0) + { + string StringTableIndexString = SymbolTable[i].Name[1..]; + + if (int.TryParse(StringTableIndexString, out int StringTableIndex)) + { + StringTableIndex -= 4; + int Length = StringTable.IndexOf((Char)0, StringTableIndex) - StringTableIndex; + SymbolTable[i].Name = StringTable.Substring(StringTableIndex, Length); + } + } + } + + // Apply symbol names to the relocation entries + // SectionHeaders | Where-Object { _.Relocations } | % { + // _.Relocations | % { _.Name = RawSymbolTable[_.SymbolTableIndex].Name } + // } + SectionHeaders.Where(h => h.Relocations != null).ToList().ForEach(h => h.Relocations.ToList().ForEach(r => r.Name = RawSymbolTable[r.SymbolTableIndex].Name)); + + Result = new ParsedObjectFile + { + CoffHeader = CoffHeader, + SectionHeaders = SectionHeaders, + SymbolTable = SymbolTable + }; + } + + return Result; + } + } +} diff --git a/Patcher/Patcher/PatchEngine.cs b/Patcher/Patcher/PatchEngine.cs new file mode 100644 index 0000000..9a97964 --- /dev/null +++ b/Patcher/Patcher/PatchEngine.cs @@ -0,0 +1,261 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Xml.Serialization; + +namespace WPinternals +{ + internal class PatchEngine + { + internal List PatchDefinitions = new(); + internal readonly List TargetRedirections = new(); + + internal PatchEngine() { } + + internal PatchEngine(string PatchDefinitionsXmlString) + { + XmlSerializer x = new(PatchDefinitions.GetType(), null, Array.Empty(), new XmlRootAttribute("PatchDefinitions"), ""); + MemoryStream s = new(System.Text.Encoding.ASCII.GetBytes(PatchDefinitionsXmlString)); + PatchDefinitions = (List)x.Deserialize(s); + } + + internal void WriteDefinitions(string FilePath) + { + XmlSerializer x = new(PatchDefinitions.GetType(), null, Array.Empty(), new XmlRootAttribute("PatchDefinitions"), ""); + + XmlSerializerNamespaces ns = new(); + ns.Add("", ""); + + System.IO.StreamWriter FileWriter = new(FilePath); + XmlWriter XmlWriter = XmlWriter.Create(FileWriter, new XmlWriterSettings() { OmitXmlDeclaration = true, Indent = true, NewLineHandling = NewLineHandling.Entitize }); + + FileWriter.WriteLine(""); + FileWriter.WriteLine(""); + FileWriter.WriteLine(""); + FileWriter.WriteLine(""); + + x.Serialize(XmlWriter, PatchDefinitions, ns); + + FileWriter.Close(); + } + + private string _TargetPath = null; + internal string TargetPath + { + get + { + return _TargetPath; + } + set + { + _TargetPath = value.TrimEnd(new char[] { '\\' }); + } + } + } + + internal class TargetRedirection + { + private string _RelativePath; + private string _TargetPath; + + internal TargetRedirection(string RelativePath, string TargetPath) + { + this.RelativePath = RelativePath; + this.TargetPath = TargetPath; + } + + internal string RelativePath + { + get + { + return _RelativePath; + } + set + { + _RelativePath = value.TrimStart(new char[] { '\\' }).TrimEnd(new char[] { '\\' }); + } + } + + internal string TargetPath + { + get + { + return _TargetPath; + } + set + { + _TargetPath = value.TrimEnd(new char[] { '\\' }); + } + } + } + + /// + /// Must be public to be serializable + /// + public class PatchDefinition + { + [XmlAttribute] + public string Name; + + public List TargetVersions = new(); + } + + /// + /// Must be public to be serializable + /// + public class TargetVersion + { + [XmlAttribute] + public string Description; + + public List TargetFiles = new(); + } + + /// + /// Must be public to be serializable + /// + public class TargetFile + { + private string _Path; + [XmlAttribute] + public string Path + { + get + { + return _Path; + } + set + { + _Path = value.TrimStart(new char[] { '\\' }); + } + } + + [XmlIgnore] + public byte[] HashOriginal; + [XmlAttribute("HashOriginal")] + public string HashOriginalAsString + { + get + { + return Converter.ConvertHexToString(HashOriginal, ""); + } + set + { + HashOriginal = Converter.ConvertStringToHex(value); + } + } + + [XmlIgnore] + public byte[] HashPatched; + [XmlAttribute("HashPatched")] + public string HashPatchedAsString + { + get + { + return Converter.ConvertHexToString(HashPatched, ""); + } + set + { + HashPatched = Converter.ConvertStringToHex(value); + } + } + + public List Patches = new(); + public List Obsolete = new(); + } + + /// + /// Must be public to be serializable + /// + public class Patch + { + [XmlIgnore] + public UInt32 Address; + [XmlAttribute("Address")] + public string AddressAsString + { + get + { + return "0x" + Address.ToString("X8"); + } + set + { + string NewValue = value; + if (NewValue.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) + NewValue = NewValue[2..]; + Address = Convert.ToUInt32(NewValue, 16); + } + } + + [XmlIgnore] + public byte[] OriginalBytes; + [XmlAttribute("OriginalBytes")] + public string OriginalBytesAsString + { + get + { + return Converter.ConvertHexToString(OriginalBytes, ""); + } + set + { + OriginalBytes = Converter.ConvertStringToHex(value); + } + } + + [XmlIgnore] + public byte[] PatchedBytes; + [XmlAttribute("PatchedBytes")] + public string PatchedBytesAsString + { + get + { + return Converter.ConvertHexToString(PatchedBytes, ""); + } + set + { + PatchedBytes = Converter.ConvertStringToHex(value); + } + } + } +} diff --git a/Patcher/Patcher/Patcher.csproj b/Patcher/Patcher/Patcher.csproj new file mode 100644 index 0000000..7f9acf5 --- /dev/null +++ b/Patcher/Patcher/Patcher.csproj @@ -0,0 +1,28 @@ + + + net5.0-windows + WinExe + false + true + + + bin\x86\Debug\ + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + MinimumRecommendedRules.ruleset + + + bin\x64\Debug\ + MinimumRecommendedRules.ruleset + + + bin\x64\Release\ + MinimumRecommendedRules.ruleset + + + + + + \ No newline at end of file diff --git a/Patcher/Patcher/Patcher.csproj.user b/Patcher/Patcher/Patcher.csproj.user new file mode 100644 index 0000000..6696a72 --- /dev/null +++ b/Patcher/Patcher/Patcher.csproj.user @@ -0,0 +1,9 @@ + + + + + + Form + + + \ No newline at end of file diff --git a/Patcher/Patcher/PeFile.cs b/Patcher/Patcher/PeFile.cs new file mode 100644 index 0000000..85d0a6a --- /dev/null +++ b/Patcher/Patcher/PeFile.cs @@ -0,0 +1,818 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +// Explanation of PE header here: +// https://msdn.microsoft.com/en-us/library/ms809762.aspx + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; +using System.Linq; +using WPinternals; + +namespace Patcher +{ + #region Structs + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DOS_HEADER + { + public UInt16 e_magic; + public UInt16 e_cblp; + public UInt16 e_cp; + public UInt16 e_crlc; + public UInt16 e_cparhdr; + public UInt16 e_minalloc; + public UInt16 e_maxalloc; + public UInt16 e_ss; + public UInt16 e_sp; + public UInt16 e_csum; + public UInt16 e_ip; + public UInt16 e_cs; + public UInt16 e_lfarlc; + public UInt16 e_ovno; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)] + public UInt16[] e_res1; + public UInt16 e_oemid; + public UInt16 e_oeminfo; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)] + public UInt16[] e_res2; + public UInt32 e_lfanew; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_NT_HEADERS + { + public UInt32 Signature; + public IMAGE_FILE_HEADER FileHeader; + public IMAGE_OPTIONAL_HEADER32 OptionalHeader32; + public IMAGE_OPTIONAL_HEADER64 OptionalHeader64; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_FILE_HEADER + { + public UInt16 Machine; + public UInt16 NumberOfSections; + public UInt32 TimeDateStamp; + public UInt32 PointerToSymbolTable; + public UInt32 NumberOfSymbols; + public UInt16 SizeOfOptionalHeader; + public UInt16 Characteristics; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_OPTIONAL_HEADER32 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt32 BaseOfData; + public UInt32 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt32 SizeOfStackReserve; + public UInt32 SizeOfStackCommit; + public UInt32 SizeOfHeapReserve; + public UInt32 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public IMAGE_DATA_DIRECTORY[] DataDirectory; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_OPTIONAL_HEADER64 + { + public UInt16 Magic; + public Byte MajorLinkerVersion; + public Byte MinorLinkerVersion; + public UInt32 SizeOfCode; + public UInt32 SizeOfInitializedData; + public UInt32 SizeOfUninitializedData; + public UInt32 AddressOfEntryPoint; + public UInt32 BaseOfCode; + public UInt64 ImageBase; + public UInt32 SectionAlignment; + public UInt32 FileAlignment; + public UInt16 MajorOperatingSystemVersion; + public UInt16 MinorOperatingSystemVersion; + public UInt16 MajorImageVersion; + public UInt16 MinorImageVersion; + public UInt16 MajorSubsystemVersion; + public UInt16 MinorSubsystemVersion; + public UInt32 Win32VersionValue; + public UInt32 SizeOfImage; + public UInt32 SizeOfHeaders; + public UInt32 CheckSum; + public UInt16 Subsystem; + public UInt16 DllCharacteristics; + public UInt64 SizeOfStackReserve; + public UInt64 SizeOfStackCommit; + public UInt64 SizeOfHeapReserve; + public UInt64 SizeOfHeapCommit; + public UInt32 LoaderFlags; + public UInt32 NumberOfRvaAndSizes; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] + public IMAGE_DATA_DIRECTORY[] DataDirectory; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_DATA_DIRECTORY + { + public UInt32 VirtualAddress; + public UInt32 Size; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_SECTION_HEADER + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)] + public string Name; + public Misc Misc; + public UInt32 VirtualAddress; + public UInt32 SizeOfRawData; + public UInt32 PointerToRawData; + public UInt32 PointerToRelocations; + public UInt32 PointerToLinenumbers; + public UInt16 NumberOfRelocations; + public UInt16 NumberOfLinenumbers; + public UInt32 Characteristics; + } + + [StructLayout(LayoutKind.Explicit)] + public struct Misc + { + [FieldOffset(0)] + public UInt32 PhysicalAddress; + [FieldOffset(0)] + public UInt32 VirtualSize; + } + + [StructLayout(LayoutKind.Sequential)] + public struct IMAGE_EXPORT_DIRECTORY + { + public UInt32 Characteristics; + public UInt32 TimeDateStamp; + public UInt16 MajorVersion; + public UInt16 MinorVersion; + public UInt32 Name; + public UInt32 Base; + public UInt32 NumberOfFunctions; + public UInt32 NumberOfNames; + /// + /// RVA from base of image + /// + public UInt32 AddressOfFunctions; + /// + /// RVA from base of image + /// + public UInt32 AddressOfNames; + /// + /// RVA from base of image + /// + public UInt32 AddressOfNameOrdinals; + } + + [StructLayout(LayoutKind.Explicit)] + public struct IMAGE_IMPORT_DESCRIPTOR + { + #region union + /// + /// CSharp doesnt really support unions, but they can be emulated by a field offset 0 + /// + [FieldOffset(0)] + public uint Characteristics; // 0 for terminating null import descriptor + /// + /// RVA to original unbound IAT (PIMAGE_THUNK_DATA) + /// + [FieldOffset(0)] + public uint OriginalFirstThunk; + #endregion + + [FieldOffset(4)] + public uint TimeDateStamp; + [FieldOffset(8)] + public uint ForwarderChain; + [FieldOffset(12)] + public uint Name; + [FieldOffset(16)] + public uint FirstThunk; + } + + public struct RUNTIME_FUNCTION_64 + { + public UInt64 RVAofBeginAddress; + public UInt64 RVAofEndAddress; + public UInt64 RVAofUnwindData; + } + + public struct RUNTIME_FUNCTION_32 + { + public UInt32 RVAofBeginAddress; + public UInt32 RVAofUnwindData; + } + + public static class Constants + { + public static class SectionFlags + { + public const UInt32 IMAGE_SCN_CNT_CODE = 0x00000020; + } + } + + internal enum ResourceType + { + /// + /// Accelerator table. + /// + RT_ACCELERATOR = 9, + /// + /// Animated cursor. + /// + RT_ANICURSOR = 21, + /// + /// Animated icon. + /// + RT_ANIICON = 22, + /// + /// Bitmap resource. + /// + RT_BITMAP = 2, + /// + /// Hardware-dependent cursor resource. + /// + RT_CURSOR = 1, + /// + /// Dialog box. + /// + RT_DIALOG = 5, + /// + /// Allows + /// + RT_DLGINCLUDE = 17, + /// + /// Font resource. + /// + RT_FONT = 8, + /// + /// Font directory resource. + /// + RT_FONTDIR = 7, + /// + /// Hardware-independent cursor resource. + /// + RT_GROUP_CURSOR = RT_CURSOR + 11, + /// + /// Hardware-independent icon resource. + /// + RT_GROUP_ICON = RT_ICON + 11, + /// + /// HTML resource. + /// + RT_HTML = 23, + /// + /// Hardware-dependent icon resource. + /// + RT_ICON = 3, + /// + /// Side-by-Side Assembly Manifest. + /// + RT_MANIFEST = 24, + /// + /// Menu resource. + /// + RT_MENU = 4, + /// + /// Message-table entry. + /// + RT_MESSAGETABLE = 11, + /// + /// Plug and Play resource. + /// + RT_PLUGPLAY = 19, + /// + /// Application-defined resource (raw data). + /// + RT_RCDATA = 10, + /// + /// String-table entry. + /// + RT_STRING = 6, + /// + /// Version resource. + /// + RT_VERSION = 16, + /// + /// VXD + /// + RT_VXD = 20, + RT_DLGINIT = 240, + RT_TOOLBAR = 241 + }; + + #endregion + + public class PeFile + { + #region Fields + + public readonly IMAGE_DOS_HEADER DosHeader; + public IMAGE_NT_HEADERS NtHeaders; + private readonly IList _sectionHeaders = new List(); + + public List
Sections = new(); + public List Exports = new(); + public List Imports = new(); + public List RuntimeFunctions = new(); + public byte[] Buffer; + public UInt64 ImageBase; + public UInt64 EntryPoint; + public UInt64 ExportDirectoryVirtualOffset; + public UInt64 ImportDirectoryVirtualOffset; + public UInt64 RuntimeDirectoryVirtualOffset; + public UInt32 RuntimeDirectorySize; + + #endregion + + public PeFile(string Path): this(File.ReadAllBytes(Path)) + { + } + + public PeFile(byte[] Buffer) + { + int P = 0; + + this.Buffer = Buffer; + + // Read MS-DOS header section + DosHeader = MarshalBytesTo(Buffer, P); + + // MS-DOS magic number should read 'MZ' + if (DosHeader.e_magic != 0x5a4d) + { + throw new InvalidOperationException("File is not a portable executable."); + } + + // Read NT Headers + P = (int)DosHeader.e_lfanew; + NtHeaders.Signature = MarshalBytesTo(Buffer, P); + + // Make sure we have 'PE' in the pe signature + if (NtHeaders.Signature != 0x4550) + { + throw new InvalidOperationException("Invalid portable executable signature in NT header."); + } + + P += sizeof(UInt32); + NtHeaders.FileHeader = MarshalBytesTo(Buffer, P); + + // Read optional headers + P += Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)); + if (Is32bitAssembly()) + { + Load32bitOptionalHeaders(Buffer, P); + ImageBase = NtHeaders.OptionalHeader32.ImageBase; + EntryPoint = NtHeaders.OptionalHeader32.AddressOfEntryPoint; + ExportDirectoryVirtualOffset = NtHeaders.OptionalHeader32.DataDirectory[0].VirtualAddress; + ImportDirectoryVirtualOffset = NtHeaders.OptionalHeader32.DataDirectory[1].VirtualAddress; + RuntimeDirectoryVirtualOffset = NtHeaders.OptionalHeader32.DataDirectory[3].VirtualAddress; + RuntimeDirectorySize = NtHeaders.OptionalHeader32.DataDirectory[3].Size; + } + else + { + Load64bitOptionalHeaders(Buffer, P); + ImageBase = NtHeaders.OptionalHeader64.ImageBase; + EntryPoint = NtHeaders.OptionalHeader64.AddressOfEntryPoint; + ExportDirectoryVirtualOffset = NtHeaders.OptionalHeader64.DataDirectory[0].VirtualAddress; + ImportDirectoryVirtualOffset = NtHeaders.OptionalHeader64.DataDirectory[1].VirtualAddress; + RuntimeDirectoryVirtualOffset = NtHeaders.OptionalHeader64.DataDirectory[3].VirtualAddress; + RuntimeDirectorySize = NtHeaders.OptionalHeader64.DataDirectory[3].Size; + } + + // Read Sections + _sectionHeaders.ToList().ForEach(s => + { + byte[] RawCode = new byte[s.SizeOfRawData]; + System.Buffer.BlockCopy(Buffer, (int)s.PointerToRawData, RawCode, 0, (int)s.SizeOfRawData); + + Sections.Add(new Section { Header = s, Buffer = RawCode, VirtualAddress = s.VirtualAddress + (UInt32)ImageBase, VirtualSize = s.Misc.VirtualSize, IsCode = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_CNT_CODE) != 0 }); + }); + + // Read Exports + // TODO: Proper support for 64-bit files + if (ExportDirectoryVirtualOffset != 0) + { + IMAGE_EXPORT_DIRECTORY ExportDirectory = MarshalBytesTo(Buffer, (int)ConvertVirtualOffsetToRawOffset((uint)ExportDirectoryVirtualOffset)); + if (ExportDirectory.AddressOfNames != 0) + { + Section ExportsSection = GetSectionForVirtualAddress((uint)(ImageBase + ExportDirectory.AddressOfNames)); + UInt32 OffsetNames = (UInt32)(ImageBase + ExportDirectory.AddressOfNames - ExportsSection.VirtualAddress); + UInt32 OffsetOrdinals = (UInt32)(ImageBase + ExportDirectory.AddressOfNameOrdinals - ExportsSection.VirtualAddress); + UInt32 OffsetFunctions = (UInt32)(ImageBase + ExportDirectory.AddressOfFunctions - ExportsSection.VirtualAddress); + string[] ExportNames = new string[ExportDirectory.NumberOfNames]; + UInt16[] Ordinals = new UInt16[ExportDirectory.NumberOfNames]; + UInt32[] VirtualAddresses = new UInt32[ExportDirectory.NumberOfFunctions]; + for (int i = 0; i < ExportDirectory.NumberOfNames; i++) + { + UInt32 NamesRVA = ByteOperations.ReadUInt32(ExportsSection.Buffer, (UInt32)(OffsetNames + (i * sizeof(UInt32)))); + UInt32 NameOffset = (UInt32)(NamesRVA + ImageBase - ExportsSection.VirtualAddress); + ExportNames[i] = ByteOperations.ReadAsciiString(ExportsSection.Buffer, NameOffset); + + Ordinals[i] = ByteOperations.ReadUInt16(ExportsSection.Buffer, (UInt32)(OffsetOrdinals + (i * sizeof(UInt16)))); + } + for (int i = 0; i < ExportDirectory.NumberOfFunctions; i++) + { + VirtualAddresses[i] = ByteOperations.ReadUInt32(ExportsSection.Buffer, (UInt32)(OffsetFunctions + (i * sizeof(UInt32)))); + VirtualAddresses[i] -= VirtualAddresses[i] % 2; // Round down for Thumb2 + } + for (int i = 0; i < ExportDirectory.NumberOfNames; i++) + { + Exports.Add(new FunctionDescriptor() { Name = ExportNames[i], VirtualAddress = (UInt32)(ImageBase + VirtualAddresses[Ordinals[i]]) }); + } + } + } + + // Read Imports + // TODO: Proper support for 64-bit files + if (ImportDirectoryVirtualOffset != 0) + { + Section ImportsSection = GetSectionForVirtualAddress((uint)(ImageBase + ImportDirectoryVirtualOffset)); + IMAGE_IMPORT_DESCRIPTOR ImportDirectory; + do + { + ImportDirectory = MarshalBytesTo(ImportsSection.Buffer, (int)(ImportDirectoryVirtualOffset - (ImportsSection.VirtualAddress - ImageBase))); + if (ImportDirectory.OriginalFirstThunk != 0) + { + // ImportDirectory.OriginalFirstThunk is the VirtualOffset to an array of VirtualOffsets. They point to a struct with a word-value, followed by a zero-terminated ascii-string, which is the name of the import. + // ImportDirectory.FirstThunk points to an array pointers which is the actual import table. + UInt32 NameArrayOffset = ImportDirectory.OriginalFirstThunk - (ImportsSection.VirtualAddress - (UInt32)ImageBase); + UInt32 NameOffset; + int i = 0; + do + { + NameOffset = ByteOperations.ReadUInt32(ImportsSection.Buffer, NameArrayOffset); + if ((NameOffset < (ImportsSection.VirtualAddress - ImageBase)) || (NameOffset >= (ImportsSection.VirtualAddress + ImportsSection.VirtualSize - ImageBase))) + NameOffset = 0; // ImportDirectory.OriginalFirstThunk seems to contain Characteristics, not an offset to an array. + NameArrayOffset += sizeof(UInt32); + if (NameOffset != 0) + { + string Name = ByteOperations.ReadAsciiString(ImportsSection.Buffer, NameOffset + 2 - (ImportsSection.VirtualAddress - (UInt32)ImageBase)); + Imports.Add(new FunctionDescriptor() { Name = Name, VirtualAddress = ImportDirectory.FirstThunk + (UInt32)ImageBase + (UInt32)(i * sizeof(UInt32)) }); + i++; + } + } + while (NameOffset != 0); + + ImportDirectoryVirtualOffset += (UInt64)Marshal.SizeOf(typeof(IMAGE_IMPORT_DESCRIPTOR)); + } + } + while (ImportDirectory.OriginalFirstThunk != 0); + } + + // Read Runtime functions + // TODO: Proper support for 64-bit files + if (RuntimeDirectoryVirtualOffset != 0) + { + Section RuntimeSection = GetSectionForVirtualAddress((uint)(ImageBase + RuntimeDirectoryVirtualOffset)); + RUNTIME_FUNCTION_32 RuntimeFunction; + for (int i = 0; i < (RuntimeDirectorySize / Marshal.SizeOf(typeof(RUNTIME_FUNCTION_32))); i++) + { + RuntimeFunction = MarshalBytesTo(RuntimeSection.Buffer, (int)(RuntimeDirectoryVirtualOffset - (RuntimeSection.VirtualAddress - ImageBase)) + (i * Marshal.SizeOf(typeof(RUNTIME_FUNCTION_32)))); + RuntimeFunctions.Add(new FunctionDescriptor() { Name = null, VirtualAddress = (UInt32)(RuntimeFunction.RVAofBeginAddress + ImageBase) }); + } + } + } + + public UInt32 GetChecksumOffset() + { + return ByteOperations.ReadUInt32(Buffer, 0x3C) + +0x58; + } + + internal UInt32 CalculateChecksum() + { + UInt32 Checksum = 0; + UInt32 Hi; + + // Clear file checksum + // ByteOperations.WriteUInt32(PEFile, GetChecksumOffset(), 0); + UInt32 ChecksumOffset = GetChecksumOffset(); + + for (UInt32 i = 0; i < ((UInt32)Buffer.Length & 0xfffffffe); i += 2) + { + if ((i < ChecksumOffset) || (i >= (ChecksumOffset + 4))) + Checksum += ByteOperations.ReadUInt16(Buffer, i); + + Hi = Checksum >> 16; + if (Hi != 0) + { + Checksum = Hi + (Checksum & 0xFFFF); + } + } + if ((Buffer.Length % 2) != 0) + { + Checksum += (UInt32)ByteOperations.ReadUInt8(Buffer, (UInt32)Buffer.Length - 1); + Hi = Checksum >> 16; + if (Hi != 0) + { + Checksum = Hi + (Checksum & 0xFFFF); + } + } + Checksum += (UInt32)Buffer.Length; + + // Write file checksum + // ByteOperations.WriteUInt32(Buffer, GetChecksumOffset(), Checksum); + + return Checksum; + } + + public IMAGE_DOS_HEADER GetDOSHeader() + { + return DosHeader; + } + + public UInt32 GetPESignature() + { + return NtHeaders.Signature; + } + + public IMAGE_FILE_HEADER GetFileHeader() + { + return NtHeaders.FileHeader; + } + + public IMAGE_OPTIONAL_HEADER32 GetOptionalHeaders32() + { + return NtHeaders.OptionalHeader32; + } + + public IMAGE_OPTIONAL_HEADER64 GetOptionalHeaders64() + { + return NtHeaders.OptionalHeader64; + } + + public IList GetSectionHeaders() + { + return _sectionHeaders; + } + + public bool Is32bitAssembly() + { + return (NtHeaders.FileHeader.Characteristics & 0x0100) == 0x0100; + } + + private void Load64bitOptionalHeaders(byte[] Buffer, int Offset) + { + NtHeaders.OptionalHeader64 = MarshalBytesTo(Buffer, Offset); + + // Should have 10 data directories + if (NtHeaders.OptionalHeader64.NumberOfRvaAndSizes != 0x10) + { + throw new InvalidOperationException("Invalid number of data directories in NT header"); + } + Offset += Marshal.SizeOf(typeof(IMAGE_OPTIONAL_HEADER64)); + + for (int i = 0; i < NtHeaders.FileHeader.NumberOfSections; i++) + { + _sectionHeaders.Add(MarshalBytesTo(Buffer, Offset)); + Offset += Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)); + } + } + + private void Load32bitOptionalHeaders(byte[] Buffer, int Offset) + { + NtHeaders.OptionalHeader32 = MarshalBytesTo(Buffer, Offset); + + // Should have 10 data directories + if (NtHeaders.OptionalHeader32.NumberOfRvaAndSizes != 0x10) + { + throw new InvalidOperationException("Invalid number of data directories in NT header"); + } + Offset += Marshal.SizeOf(typeof(IMAGE_OPTIONAL_HEADER32)); + + for (int i = 0; i < NtHeaders.FileHeader.NumberOfSections; i++) + { + _sectionHeaders.Add(MarshalBytesTo(Buffer, Offset)); + Offset += Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)); + } + } + + public UInt32 ConvertVirtualOffsetToRawOffset(UInt32 VirtualOffset) + { + // TODO: Add 64-bit support + if (VirtualOffset < (Sections.OrderBy(s => s.VirtualAddress).First().VirtualAddress - GetOptionalHeaders32().ImageBase)) + return VirtualOffset; + + IMAGE_SECTION_HEADER? SectionHeaderSelection = _sectionHeaders.FirstOrDefault(h => (h.VirtualAddress <= VirtualOffset) && ((h.VirtualAddress + h.SizeOfRawData) > VirtualOffset)); + if (SectionHeaderSelection == null) + throw new ArgumentOutOfRangeException(); + + IMAGE_SECTION_HEADER SectionHeader = (IMAGE_SECTION_HEADER)SectionHeaderSelection; + + if (string.IsNullOrEmpty(SectionHeader.Name) || (SectionHeader.SizeOfRawData == 0)) + throw new ArgumentOutOfRangeException(); + + return SectionHeader.PointerToRawData + (VirtualOffset - SectionHeader.VirtualAddress); + } + + public UInt32 ConvertVirtualAddressToRawOffset(UInt32 VirtualAddress) + { + return ConvertVirtualOffsetToRawOffset((UInt32)(VirtualAddress - ImageBase)); + } + + internal uint ConvertRawOffsetToVirtualAddress(uint RawOffset) + { + // TODO: Add 64-bit support + if (RawOffset < Sections.OrderBy(s => s.VirtualAddress).First().Header.PointerToRawData) + return RawOffset + GetOptionalHeaders32().ImageBase; + + IMAGE_SECTION_HEADER? SectionHeaderSelection = _sectionHeaders.FirstOrDefault(h => (h.PointerToRawData <= RawOffset) && ((h.PointerToRawData + h.SizeOfRawData) > RawOffset)); + if (SectionHeaderSelection == null) + throw new ArgumentOutOfRangeException(); + + IMAGE_SECTION_HEADER SectionHeader = (IMAGE_SECTION_HEADER)SectionHeaderSelection; + + if (string.IsNullOrEmpty(SectionHeader.Name) || (SectionHeader.SizeOfRawData == 0)) + throw new ArgumentOutOfRangeException(); + + return RawOffset - SectionHeader.PointerToRawData + SectionHeader.VirtualAddress + GetOptionalHeaders32().ImageBase; + } + + public Section GetSectionForVirtualAddress(UInt32 VirtualAddress) + { + return Sections.Find(s => (VirtualAddress >= s.VirtualAddress) && (VirtualAddress < (s.VirtualAddress + s.VirtualSize))); + } + + private static T MarshalBytesTo(BinaryReader reader) + { + // Unmanaged data + byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); + + // Create a pointer to the unmanaged data pinned in memory to be accessed by unmanaged code + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + + // Use our previously created pointer to unmanaged data and marshal to the specified type + T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + + // Deallocate pointer + handle.Free(); + + return theStructure; + } + + private static T MarshalBytesTo(byte[] Binary, int Offset) + { + // Unmanaged data + byte[] bytes = new byte[Marshal.SizeOf(typeof(T))]; + System.Buffer.BlockCopy(Binary, Offset, bytes, 0, Marshal.SizeOf(typeof(T))); + + // Create a pointer to the unmanaged data pinned in memory to be accessed by unmanaged code + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + + // Use our previously created pointer to unmanaged data and marshal to the specified type + T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + + // Deallocate pointer + handle.Free(); + + return theStructure; + } + + internal byte[] GetResource(int[] Index) + { + UInt32 PEPointer = ByteOperations.ReadUInt32(Buffer, 0x3C); + UInt16 OptionalHeaderSize = ByteOperations.ReadUInt16(Buffer, PEPointer + 0x14); + UInt32 SectionTablePointer = PEPointer + 0x18 + OptionalHeaderSize; + UInt16 SectionCount = ByteOperations.ReadUInt16(Buffer, PEPointer + 0x06); + UInt32? ResourceSectionEntryPointer = null; + for (int i = 0; i < SectionCount; i++) + { + string SectionName = ByteOperations.ReadAsciiString(Buffer, (UInt32)(SectionTablePointer + (i * 0x28)), 8); + int e = SectionName.IndexOf('\0'); + if (e >= 0) + SectionName = SectionName.Substring(0, e); + if (SectionName == ".rsrc") + { + ResourceSectionEntryPointer = (UInt32)(SectionTablePointer + (i * 0x28)); + break; + } + } + if (ResourceSectionEntryPointer == null) + throw new Exception("Resource-section not found"); + UInt32 ResourceRawSize = ByteOperations.ReadUInt32(Buffer, (UInt32)ResourceSectionEntryPointer + 0x10); + UInt32 ResourceRawPointer = ByteOperations.ReadUInt32(Buffer, (UInt32)ResourceSectionEntryPointer + 0x14); + UInt32 ResourceVirtualPointer = ByteOperations.ReadUInt32(Buffer, (UInt32)ResourceSectionEntryPointer + 0x0C); + + UInt32 p = ResourceRawPointer; + for (int i = 0; i < Index.Length; i++) + { + UInt16 ResourceNamedEntryCount = ByteOperations.ReadUInt16(Buffer, p + 0x0c); + UInt16 ResourceIdEntryCount = ByteOperations.ReadUInt16(Buffer, p + 0x0e); + for (int j = ResourceNamedEntryCount; j < ResourceNamedEntryCount + ResourceIdEntryCount; j++) + { + UInt32 ResourceID = ByteOperations.ReadUInt32(Buffer, (UInt32)(p + 0x10 + (j * 8))); + UInt32 NextPointer = ByteOperations.ReadUInt32(Buffer, (UInt32)(p + 0x10 + (j * 8) + 4)); + if (ResourceID == (UInt32)Index[i]) + { + // Check high bit + if ((NextPointer & 0x80000000) == 0 != (i == (Index.Length - 1))) + throw new Exception("Bad resource path"); + + p = ResourceRawPointer + (NextPointer & 0x7fffffff); + break; + } + } + } + + UInt32 ResourceValuePointer = ByteOperations.ReadUInt32(Buffer, p) - ResourceVirtualPointer + ResourceRawPointer; + UInt32 ResourceValueSize = ByteOperations.ReadUInt32(Buffer, p + 4); + + byte[] ResourceValue = new byte[ResourceValueSize]; + Array.Copy(Buffer, ResourceValuePointer, ResourceValue, 0, ResourceValueSize); + + return ResourceValue; + } + + internal Version GetFileVersion() + { + byte[] version = GetResource(new int[] { (int)ResourceType.RT_VERSION, 1, 1033 }); + + // RT_VERSION format: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms646997(v=vs.85).aspx + const UInt32 FixedFileInfoPointer = 0x28; + UInt16 Major = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0A); + UInt16 Minor = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x08); + UInt16 Build = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0E); + UInt16 Revision = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x0C); + + return new Version(Major, Minor, Build, Revision); + } + + internal Version GetProductVersion() + { + byte[] version = GetResource(new int[] { (int)ResourceType.RT_VERSION, 1, 1033 }); + + // RT_VERSION format: + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms647001(v=vs.85).aspx + // https://msdn.microsoft.com/en-us/library/windows/desktop/ms646997(v=vs.85).aspx + const UInt32 FixedFileInfoPointer = 0x28; + UInt16 Major = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x12); + UInt16 Minor = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x10); + UInt16 Build = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x16); + UInt16 Revision = ByteOperations.ReadUInt16(version, FixedFileInfoPointer + 0x14); + + return new Version(Major, Minor, Build, Revision); + } + } + + public class Section + { + public IMAGE_SECTION_HEADER Header; + public byte[] Buffer; + public UInt32 VirtualAddress; + public UInt32 VirtualSize; + public bool IsCode; + } + + public class FunctionDescriptor + { + public string Name; + public UInt32 VirtualAddress; + } +} diff --git a/Patcher/Patcher/Program.cs b/Patcher/Patcher/Program.cs new file mode 100644 index 0000000..9e8a06d --- /dev/null +++ b/Patcher/Patcher/Program.cs @@ -0,0 +1,39 @@ +// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Windows.Forms; + +namespace Patcher +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + private static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } + } +} diff --git a/Patcher/Patcher/Properties/AssemblyInfo.cs b/Patcher/Patcher/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3ef8a58 --- /dev/null +++ b/Patcher/Patcher/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ARM Patcher")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("WPinternals.net")] +[assembly: AssemblyProduct("ARM Patcher")] +[assembly: AssemblyCopyright("Copyright © 2018 by Rene Lergner")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b9aebca3-bcf8-4e4a-bf0e-5e764867be07")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Patcher/Patcher/Properties/Resources.Designer.cs b/Patcher/Patcher/Properties/Resources.Designer.cs new file mode 100644 index 0000000..0187c74 --- /dev/null +++ b/Patcher/Patcher/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Patcher.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // 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", "4.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() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Patcher.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/Patcher/Patcher/Properties/Resources.resx b/Patcher/Patcher/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/Patcher/Patcher/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Patcher/Patcher/Properties/Settings.Designer.cs b/Patcher/Patcher/Properties/Settings.Designer.cs new file mode 100644 index 0000000..ddbc4e1 --- /dev/null +++ b/Patcher/Patcher/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +namespace Patcher.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/Patcher/Patcher/Properties/Settings.settings b/Patcher/Patcher/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/Patcher/Patcher/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Patcher/nuget.config b/Patcher/nuget.config new file mode 100644 index 0000000..efb6ffe --- /dev/null +++ b/Patcher/nuget.config @@ -0,0 +1,11 @@ + + + + + + + +