mirror of https://github.com/icsharpcode/ILSpy.git
15 changed files with 1325 additions and 52 deletions
@ -0,0 +1,183 @@
@@ -0,0 +1,183 @@
|
||||
// Copyright (c) 2020 Daniel Grunwald
|
||||
//
|
||||
// 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 NUnit.Framework; |
||||
|
||||
namespace ICSharpCode.Decompiler.Util |
||||
{ |
||||
[TestFixture] |
||||
public class FileUtilityTests |
||||
{ |
||||
#region NormalizePath
|
||||
[Test] |
||||
public void NormalizePath() |
||||
{ |
||||
Assert.AreEqual(@"c:\temp\test.txt", FileUtility.NormalizePath(@"c:\temp\project\..\test.txt")); |
||||
Assert.AreEqual(@"c:\temp\test.txt", FileUtility.NormalizePath(@"c:\temp\project\.\..\test.txt")); |
||||
Assert.AreEqual(@"c:\temp\test.txt", FileUtility.NormalizePath(@"c:\temp\\test.txt")); // normalize double backslash
|
||||
Assert.AreEqual(@"c:\temp", FileUtility.NormalizePath(@"c:\temp\.")); |
||||
Assert.AreEqual(@"c:\temp", FileUtility.NormalizePath(@"c:\temp\subdir\..")); |
||||
} |
||||
|
||||
[Test] |
||||
public void NormalizePath_DriveRoot() |
||||
{ |
||||
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:\")); |
||||
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/")); |
||||
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:")); |
||||
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/.")); |
||||
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/..")); |
||||
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/./")); |
||||
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/..\")); |
||||
} |
||||
|
||||
[Test] |
||||
public void NormalizePath_UNC() |
||||
{ |
||||
Assert.AreEqual(@"\\server\share", FileUtility.NormalizePath(@"\\server\share")); |
||||
Assert.AreEqual(@"\\server\share", FileUtility.NormalizePath(@"\\server\share\")); |
||||
Assert.AreEqual(@"\\server\share", FileUtility.NormalizePath(@"//server/share/")); |
||||
Assert.AreEqual(@"\\server\share\otherdir", FileUtility.NormalizePath(@"//server/share/dir/..\otherdir")); |
||||
} |
||||
|
||||
[Test] |
||||
public void NormalizePath_Web() |
||||
{ |
||||
Assert.AreEqual(@"http://danielgrunwald.de/path/", FileUtility.NormalizePath(@"http://danielgrunwald.de/path/")); |
||||
Assert.AreEqual(@"browser://http://danielgrunwald.de/path/", FileUtility.NormalizePath(@"browser://http://danielgrunwald.de/wrongpath/../path/")); |
||||
} |
||||
|
||||
[Test] |
||||
public void NormalizePath_Relative() |
||||
{ |
||||
Assert.AreEqual(@"../b", FileUtility.NormalizePath(@"..\a\..\b")); |
||||
Assert.AreEqual(@".", FileUtility.NormalizePath(@".")); |
||||
Assert.AreEqual(@".", FileUtility.NormalizePath(@"a\..")); |
||||
} |
||||
|
||||
[Test] |
||||
public void NormalizePath_UnixStyle() |
||||
{ |
||||
Assert.AreEqual("/", FileUtility.NormalizePath("/")); |
||||
Assert.AreEqual("/a/b", FileUtility.NormalizePath("/a/b")); |
||||
Assert.AreEqual("/a/b", FileUtility.NormalizePath("/c/../a/./b")); |
||||
Assert.AreEqual("/a/b", FileUtility.NormalizePath("/c/../../a/./b")); |
||||
} |
||||
#endregion
|
||||
|
||||
[Test] |
||||
public void TestIsBaseDirectory() |
||||
{ |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\A\b\hello")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\a")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a\", @"C:\a\")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a\", @"C:\a")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\a\")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\A", @"C:\a")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\A")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a\x\fWufhweoe", @"C:\a\x\fwuFHweoe\a\b\hello")); |
||||
|
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\b\..\A", @"C:\a")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\HELLO\..\B\..\a", @"C:\b\..\a")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\.\B\..\.\.\a", @"C:\.\.\.\.\.\.\.\a")); |
||||
|
||||
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\b", @"C:\a\b\hello")); |
||||
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\a\b\hello", @"C:\b")); |
||||
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\a\x\fwufhweoe", @"C:\a\x\fwuFHweoex\a\b\hello")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\", @"C:\")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\", @"C:\a\b\hello")); |
||||
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\", @"D:\a\b\hello")); |
||||
} |
||||
|
||||
|
||||
[Test] |
||||
public void TestIsBaseDirectoryRelative() |
||||
{ |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@".", @"a\b")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@".", @"a")); |
||||
Assert.IsFalse(FileUtility.IsBaseDirectory(@".", @"c:\")); |
||||
Assert.IsFalse(FileUtility.IsBaseDirectory(@".", @"/")); |
||||
} |
||||
|
||||
[Test] |
||||
public void TestIsBaseDirectoryUnixStyle() |
||||
{ |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"/", @"/")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"/", @"/a")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"/", @"/a/subdir")); |
||||
} |
||||
|
||||
[Test] |
||||
public void TestIsBaseDirectoryUNC() |
||||
{ |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"\\server\share", @"\\server\share\dir\subdir")); |
||||
Assert.IsTrue(FileUtility.IsBaseDirectory(@"\\server\share", @"\\server\share\dir\subdir")); |
||||
Assert.IsFalse(FileUtility.IsBaseDirectory(@"\\server2\share", @"\\server\share\dir\subdir")); |
||||
} |
||||
|
||||
[Test] |
||||
public void TestGetRelativePath() |
||||
{ |
||||
Assert.AreEqual(@"blub", FileUtility.GetRelativePath(@"C:\hello\.\..\a", @"C:\.\a\blub")); |
||||
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\hello", @"C:\.\blub\.\..\.\a\.\blub")); |
||||
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\hello\", @"C:\.\blub\.\..\.\a\.\blub")); |
||||
Assert.AreEqual(@".", FileUtility.GetRelativePath(@"C:\hello", @"C:\.\hello")); |
||||
Assert.AreEqual(@".", FileUtility.GetRelativePath(@"C:\", @"C:\")); |
||||
Assert.AreEqual(@"blub", FileUtility.GetRelativePath(@"C:\", @"C:\blub")); |
||||
Assert.AreEqual(@"D:\", FileUtility.GetRelativePath(@"C:\", @"D:\")); |
||||
Assert.AreEqual(@"D:\def", FileUtility.GetRelativePath(@"C:\abc", @"D:\def")); |
||||
|
||||
// casing troubles
|
||||
Assert.AreEqual(@"blub", FileUtility.GetRelativePath(@"C:\hello\.\..\A", @"C:\.\a\blub")); |
||||
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\HELlo", @"C:\.\blub\.\..\.\a\.\blub")); |
||||
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\heLLo\A\..", @"C:\.\blub\.\..\.\a\.\blub")); |
||||
} |
||||
|
||||
[Test] |
||||
public void RelativeGetRelativePath() |
||||
{ |
||||
// Relative path
|
||||
Assert.AreEqual(@"a", FileUtility.GetRelativePath(@".", @"a")); |
||||
Assert.AreEqual(@"..", FileUtility.GetRelativePath(@"a", @".")); |
||||
Assert.AreEqual(@"..\b", FileUtility.GetRelativePath(@"a", @"b")); |
||||
Assert.AreEqual(@"..\..", FileUtility.GetRelativePath(@"a", @"..")); |
||||
|
||||
// Getting a path from an absolute path to a relative path isn't really possible;
|
||||
// so we just keep the existing relative path (don't introduce incorrect '..\').
|
||||
Assert.AreEqual(@"def", FileUtility.GetRelativePath(@"C:\abc", @"def")); |
||||
} |
||||
|
||||
[Test] |
||||
public void GetRelativePath_Unix() |
||||
{ |
||||
Assert.AreEqual(@"a", FileUtility.GetRelativePath("/", "/a")); |
||||
Assert.AreEqual(@"a\b", FileUtility.GetRelativePath("/", "/a/b")); |
||||
Assert.AreEqual(@"b", FileUtility.GetRelativePath("/a", "/a/b")); |
||||
} |
||||
|
||||
[Test] |
||||
public void TestIsEqualFile() |
||||
{ |
||||
Assert.IsTrue(FileUtility.IsEqualFileName(@"C:\.\Hello World.Exe", @"C:\HELLO WOrld.exe")); |
||||
Assert.IsTrue(FileUtility.IsEqualFileName(@"C:\bla\..\a\my.file.is.this", @"C:\gg\..\.\.\.\.\a\..\a\MY.FILE.IS.THIS")); |
||||
|
||||
Assert.IsFalse(FileUtility.IsEqualFileName(@"C:\.\Hello World.Exe", @"C:\HELLO_WOrld.exe")); |
||||
Assert.IsFalse(FileUtility.IsEqualFileName(@"C:\a\my.file.is.this", @"C:\gg\..\.\.\.\.\a\..\b\MY.FILE.IS.THIS")); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,304 @@
@@ -0,0 +1,304 @@
|
||||
// Copyright (c) 2020 Daniel Grunwald
|
||||
//
|
||||
// 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.Text; |
||||
|
||||
namespace ICSharpCode.Decompiler.Util |
||||
{ |
||||
static class FileUtility |
||||
{ |
||||
/// <summary>
|
||||
/// Gets the normalized version of fileName.
|
||||
/// Slashes are replaced with backslashes, backreferences "." and ".." are 'evaluated'.
|
||||
/// </summary>
|
||||
public static string NormalizePath(string fileName) |
||||
{ |
||||
if (string.IsNullOrEmpty(fileName)) |
||||
return fileName; |
||||
|
||||
int i; |
||||
|
||||
bool isWeb = false; |
||||
for (i = 0; i < fileName.Length; i++) |
||||
{ |
||||
if (fileName[i] == '/' || fileName[i] == '\\') |
||||
break; |
||||
if (fileName[i] == ':') |
||||
{ |
||||
if (i > 1) |
||||
isWeb = true; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
char outputSeparator = '/'; |
||||
bool isRelative; |
||||
bool isAbsoluteUnixPath = false; |
||||
|
||||
StringBuilder result = new StringBuilder(); |
||||
if (isWeb == false && IsUNCPath(fileName)) |
||||
{ |
||||
// UNC path
|
||||
i = 2; |
||||
outputSeparator = '\\'; |
||||
result.Append(outputSeparator); |
||||
isRelative = false; |
||||
} |
||||
else |
||||
{ |
||||
i = 0; |
||||
isAbsoluteUnixPath = fileName[0] == '/'; |
||||
isRelative = !isWeb && !isAbsoluteUnixPath && (fileName.Length < 2 || fileName[1] != ':'); |
||||
if (fileName.Length >= 2 && fileName[1] == ':') |
||||
{ |
||||
outputSeparator = '\\'; |
||||
} |
||||
} |
||||
int levelsBack = 0; |
||||
int segmentStartPos = i; |
||||
for (; i <= fileName.Length; i++) |
||||
{ |
||||
if (i == fileName.Length || fileName[i] == '/' || fileName[i] == '\\') |
||||
{ |
||||
int segmentLength = i - segmentStartPos; |
||||
switch (segmentLength) |
||||
{ |
||||
case 0: |
||||
// ignore empty segment (if not in web mode)
|
||||
if (isWeb) |
||||
{ |
||||
result.Append(outputSeparator); |
||||
} |
||||
break; |
||||
case 1: |
||||
// ignore /./ segment, but append other one-letter segments
|
||||
if (fileName[segmentStartPos] != '.') |
||||
{ |
||||
if (result.Length > 0) |
||||
result.Append(outputSeparator); |
||||
result.Append(fileName[segmentStartPos]); |
||||
} |
||||
break; |
||||
case 2: |
||||
if (fileName[segmentStartPos] == '.' && fileName[segmentStartPos + 1] == '.') |
||||
{ |
||||
// remove previous segment
|
||||
int j; |
||||
for (j = result.Length - 1; j >= 0 && result[j] != outputSeparator; j--) |
||||
{ |
||||
} |
||||
if (j > 0) |
||||
{ |
||||
result.Length = j; |
||||
} |
||||
else if (isAbsoluteUnixPath) |
||||
{ |
||||
result.Length = 0; |
||||
} |
||||
else if (isRelative) |
||||
{ |
||||
if (result.Length == 0) |
||||
levelsBack++; |
||||
else |
||||
result.Length = 0; |
||||
} |
||||
break; |
||||
} |
||||
else |
||||
{ |
||||
// append normal segment
|
||||
goto default; |
||||
} |
||||
default: |
||||
if (result.Length > 0) |
||||
result.Append(outputSeparator); |
||||
result.Append(fileName, segmentStartPos, segmentLength); |
||||
break; |
||||
} |
||||
segmentStartPos = i + 1; // remember start position for next segment
|
||||
} |
||||
} |
||||
if (isWeb == false) |
||||
{ |
||||
if (isRelative) |
||||
{ |
||||
for (int j = 0; j < levelsBack; j++) |
||||
{ |
||||
result.Insert(0, ".." + outputSeparator); |
||||
} |
||||
} |
||||
if (result.Length > 0 && result[result.Length - 1] == outputSeparator) |
||||
{ |
||||
result.Length -= 1; |
||||
} |
||||
if (isAbsoluteUnixPath) |
||||
{ |
||||
result.Insert(0, '/'); |
||||
} |
||||
if (result.Length == 2 && result[1] == ':') |
||||
{ |
||||
result.Append(outputSeparator); |
||||
} |
||||
if (result.Length == 0) |
||||
return "."; |
||||
} |
||||
return result.ToString(); |
||||
} |
||||
|
||||
static bool IsUNCPath(string fileName) |
||||
{ |
||||
return fileName.Length > 2 |
||||
&& (fileName[0] == '\\' || fileName[0] == '/') |
||||
&& (fileName[1] == '\\' || fileName[1] == '/'); |
||||
} |
||||
|
||||
public static bool IsEqualFileName(string fileName1, string fileName2) |
||||
{ |
||||
return string.Equals(NormalizePath(fileName1), |
||||
NormalizePath(fileName2), |
||||
StringComparison.OrdinalIgnoreCase); |
||||
} |
||||
|
||||
public static bool IsBaseDirectory(string baseDirectory, string testDirectory) |
||||
{ |
||||
if (baseDirectory == null || testDirectory == null) |
||||
return false; |
||||
baseDirectory = NormalizePath(baseDirectory); |
||||
if (baseDirectory == "." || baseDirectory == "") |
||||
return !Path.IsPathRooted(testDirectory); |
||||
baseDirectory = AddTrailingSeparator(baseDirectory); |
||||
testDirectory = AddTrailingSeparator(NormalizePath(testDirectory)); |
||||
|
||||
return testDirectory.StartsWith(baseDirectory, StringComparison.OrdinalIgnoreCase); |
||||
} |
||||
|
||||
static string AddTrailingSeparator(string input) |
||||
{ |
||||
if (string.IsNullOrEmpty(input)) |
||||
return input; |
||||
if (input[input.Length - 1] == Path.DirectorySeparatorChar || input[input.Length - 1] == Path.AltDirectorySeparatorChar) |
||||
return input; |
||||
else |
||||
return input + GetSeparatorForPath(input).ToString(); |
||||
} |
||||
|
||||
static char GetSeparatorForPath(string input) |
||||
{ |
||||
if (input.Length > 2 && input[1] == ':' || IsUNCPath(input)) |
||||
return '\\'; |
||||
return '/'; |
||||
} |
||||
|
||||
readonly static char[] separators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; |
||||
|
||||
/// <summary>
|
||||
/// Converts a given absolute path and a given base path to a path that leads
|
||||
/// from the base path to the absoulte path. (as a relative path)
|
||||
/// </summary>
|
||||
public static string GetRelativePath(string baseDirectoryPath, string absPath) |
||||
{ |
||||
if (string.IsNullOrEmpty(baseDirectoryPath)) |
||||
{ |
||||
return absPath; |
||||
} |
||||
|
||||
baseDirectoryPath = NormalizePath(baseDirectoryPath); |
||||
absPath = NormalizePath(absPath); |
||||
|
||||
string[] bPath = baseDirectoryPath != "." ? baseDirectoryPath.TrimEnd(separators).Split(separators) : new string[0]; |
||||
string[] aPath = absPath != "." ? absPath.TrimEnd(separators).Split(separators) : new string[0]; |
||||
int indx = 0; |
||||
for (; indx < Math.Min(bPath.Length, aPath.Length); ++indx) |
||||
{ |
||||
if (!bPath[indx].Equals(aPath[indx], StringComparison.OrdinalIgnoreCase)) |
||||
break; |
||||
} |
||||
|
||||
if (indx == 0 && (Path.IsPathRooted(baseDirectoryPath) || Path.IsPathRooted(absPath))) |
||||
{ |
||||
return absPath; |
||||
} |
||||
|
||||
if (indx == bPath.Length && indx == aPath.Length) |
||||
{ |
||||
return "."; |
||||
} |
||||
StringBuilder erg = new StringBuilder(); |
||||
for (int i = indx; i < bPath.Length; ++i) |
||||
{ |
||||
erg.Append(".."); |
||||
erg.Append(Path.DirectorySeparatorChar); |
||||
} |
||||
erg.Append(String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx)); |
||||
if (erg[erg.Length - 1] == Path.DirectorySeparatorChar) |
||||
erg.Length -= 1; |
||||
return erg.ToString(); |
||||
} |
||||
|
||||
public static string TrimPath(string path, int max_chars) |
||||
{ |
||||
const char ellipsis = '\u2026'; // HORIZONTAL ELLIPSIS
|
||||
const int ellipsisLength = 2; |
||||
|
||||
if (path == null || path.Length <= max_chars) |
||||
return path; |
||||
char sep = Path.DirectorySeparatorChar; |
||||
if (path.IndexOf(Path.AltDirectorySeparatorChar) >= 0 && path.IndexOf(Path.DirectorySeparatorChar) < 0) |
||||
{ |
||||
sep = Path.AltDirectorySeparatorChar; |
||||
} |
||||
string[] parts = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); |
||||
int len = ellipsisLength; // For initial ellipsis
|
||||
int index = parts.Length; |
||||
// From the end of the path, fit as many parts as possible:
|
||||
while (index > 1 && len + parts[index - 1].Length < max_chars) |
||||
{ |
||||
len += parts[index - 1].Length + 1; |
||||
index--; |
||||
} |
||||
|
||||
StringBuilder result = new StringBuilder(); |
||||
result.Append(ellipsis); |
||||
// If there's 5 chars left, partially fit another part:
|
||||
if (index > 1 && len + 5 <= max_chars) |
||||
{ |
||||
if (index == 2 && parts[0].Length <= ellipsisLength) |
||||
{ |
||||
// If the partial part is part #1,
|
||||
// and part #0 is as short as the ellipsis
|
||||
// (e.g. just a drive letter), use part #0
|
||||
// instead of the ellipsis.
|
||||
result.Clear(); |
||||
result.Append(parts[0]); |
||||
} |
||||
result.Append(sep); |
||||
result.Append(parts[index - 1], 0, max_chars - len - 3); |
||||
result.Append(ellipsis); |
||||
} |
||||
while (index < parts.Length) |
||||
{ |
||||
result.Append(sep); |
||||
result.Append(parts[index]); |
||||
index++; |
||||
} |
||||
return result.ToString(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,293 @@
@@ -0,0 +1,293 @@
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using System.Reflection.PortableExecutable; |
||||
|
||||
namespace ICSharpCode.Decompiler.Util |
||||
{ |
||||
/// <summary>
|
||||
/// Represents win32 resources
|
||||
/// </summary>
|
||||
public static class Win32Resources |
||||
{ |
||||
/// <summary>
|
||||
/// Reads win32 resource root directory
|
||||
/// </summary>
|
||||
/// <param name="pe"></param>
|
||||
/// <returns></returns>
|
||||
public static unsafe Win32ResourceDirectory ReadWin32Resources(this PEReader pe) |
||||
{ |
||||
if (pe == null) |
||||
{ |
||||
throw new ArgumentNullException(nameof(pe)); |
||||
} |
||||
|
||||
int rva = pe.PEHeaders.PEHeader.ResourceTableDirectory.RelativeVirtualAddress; |
||||
if (rva == 0) |
||||
return null; |
||||
byte* pRoot = pe.GetSectionData(rva).Pointer; |
||||
return new Win32ResourceDirectory(pe, pRoot, 0, new Win32ResourceName("Root")); |
||||
} |
||||
|
||||
public static Win32ResourceDirectory Find(this Win32ResourceDirectory root, Win32ResourceName type) |
||||
{ |
||||
if (root is null) |
||||
throw new ArgumentNullException(nameof(root)); |
||||
if (!root.Name.HasName || root.Name.Name != "Root") |
||||
throw new ArgumentOutOfRangeException(nameof(root)); |
||||
if (type is null) |
||||
throw new ArgumentNullException(nameof(type)); |
||||
|
||||
return root.FindDirectory(type); |
||||
} |
||||
|
||||
public static Win32ResourceDirectory Find(this Win32ResourceDirectory root, Win32ResourceName type, Win32ResourceName name) |
||||
{ |
||||
if (root is null) |
||||
throw new ArgumentNullException(nameof(root)); |
||||
if (!root.Name.HasName || root.Name.Name != "Root") |
||||
throw new ArgumentOutOfRangeException(nameof(root)); |
||||
if (type is null) |
||||
throw new ArgumentNullException(nameof(type)); |
||||
if (name is null) |
||||
throw new ArgumentNullException(nameof(name)); |
||||
|
||||
return root.FindDirectory(type)?.FindDirectory(name); |
||||
} |
||||
|
||||
public static Win32ResourceData Find(this Win32ResourceDirectory root, Win32ResourceName type, Win32ResourceName name, Win32ResourceName langId) |
||||
{ |
||||
if (root is null) |
||||
throw new ArgumentNullException(nameof(root)); |
||||
if (!root.Name.HasName || root.Name.Name != "Root") |
||||
throw new ArgumentOutOfRangeException(nameof(root)); |
||||
if (type is null) |
||||
throw new ArgumentNullException(nameof(type)); |
||||
if (name is null) |
||||
throw new ArgumentNullException(nameof(name)); |
||||
if (langId is null) |
||||
throw new ArgumentNullException(nameof(langId)); |
||||
|
||||
return root.FindDirectory(type)?.FindDirectory(name)?.FindData(langId); |
||||
} |
||||
} |
||||
|
||||
[DebuggerDisplay("Directory: {Name}")] |
||||
public sealed class Win32ResourceDirectory |
||||
{ |
||||
#region Structure
|
||||
public uint Characteristics { get; } |
||||
public uint TimeDateStamp { get; } |
||||
public ushort MajorVersion { get; } |
||||
public ushort MinorVersion { get; } |
||||
public ushort NumberOfNamedEntries { get; } |
||||
public ushort NumberOfIdEntries { get; } |
||||
#endregion
|
||||
|
||||
public Win32ResourceName Name { get; } |
||||
|
||||
public IList<Win32ResourceDirectory> Directories { get; } |
||||
|
||||
public IList<Win32ResourceData> Datas { get; } |
||||
|
||||
internal unsafe Win32ResourceDirectory(PEReader pe, byte* pRoot, int offset, Win32ResourceName name) |
||||
{ |
||||
var p = (IMAGE_RESOURCE_DIRECTORY*)(pRoot + offset); |
||||
Characteristics = p->Characteristics; |
||||
TimeDateStamp = p->TimeDateStamp; |
||||
MajorVersion = p->MajorVersion; |
||||
MinorVersion = p->MinorVersion; |
||||
NumberOfNamedEntries = p->NumberOfNamedEntries; |
||||
NumberOfIdEntries = p->NumberOfIdEntries; |
||||
|
||||
Name = name; |
||||
Directories = new List<Win32ResourceDirectory>(); |
||||
Datas = new List<Win32ResourceData>(); |
||||
var pEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(p + 1); |
||||
int total = NumberOfNamedEntries + NumberOfIdEntries; |
||||
for (int i = 0; i < total; i++) |
||||
{ |
||||
var pEntry = pEntries + i; |
||||
name = new Win32ResourceName(pRoot, pEntry); |
||||
if ((pEntry->OffsetToData & 0x80000000) == 0) |
||||
Datas.Add(new Win32ResourceData(pe, pRoot, (int)pEntry->OffsetToData, name)); |
||||
else |
||||
Directories.Add(new Win32ResourceDirectory(pe, pRoot, (int)(pEntry->OffsetToData & 0x7FFFFFFF), name)); |
||||
} |
||||
} |
||||
|
||||
static unsafe string ReadString(byte* pRoot, int offset) |
||||
{ |
||||
var pString = (IMAGE_RESOURCE_DIRECTORY_STRING*)(pRoot + offset); |
||||
return new string(pString->NameString, 0, pString->Length); |
||||
} |
||||
|
||||
public Win32ResourceDirectory FindDirectory(Win32ResourceName name) |
||||
{ |
||||
foreach (var directory in Directories) |
||||
{ |
||||
if (directory.Name == name) |
||||
return directory; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public Win32ResourceData FindData(Win32ResourceName name) |
||||
{ |
||||
foreach (var data in Datas) |
||||
{ |
||||
if (data.Name == name) |
||||
return data; |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public Win32ResourceDirectory FirstDirectory() |
||||
{ |
||||
return Directories.Count != 0 ? Directories[0] : null; |
||||
} |
||||
|
||||
public Win32ResourceData FirstData() |
||||
{ |
||||
return Datas.Count != 0 ? Datas[0] : null; |
||||
} |
||||
} |
||||
|
||||
[DebuggerDisplay("Data: {Name}")] |
||||
public sealed unsafe class Win32ResourceData |
||||
{ |
||||
#region Structure
|
||||
public uint OffsetToData { get; } |
||||
public uint Size { get; } |
||||
public uint CodePage { get; } |
||||
public uint Reserved { get; } |
||||
#endregion
|
||||
|
||||
private readonly void* _pointer; |
||||
|
||||
public Win32ResourceName Name { get; } |
||||
|
||||
public byte[] Data { |
||||
get { |
||||
byte[] data = new byte[Size]; |
||||
fixed (void* pData = data) |
||||
Buffer.MemoryCopy(_pointer, pData, Size, Size); |
||||
return data; |
||||
} |
||||
} |
||||
|
||||
internal Win32ResourceData(PEReader pe, byte* pRoot, int offset, Win32ResourceName name) |
||||
{ |
||||
var p = (IMAGE_RESOURCE_DATA_ENTRY*)(pRoot + offset); |
||||
OffsetToData = p->OffsetToData; |
||||
Size = p->Size; |
||||
CodePage = p->CodePage; |
||||
Reserved = p->Reserved; |
||||
|
||||
_pointer = pe.GetSectionData((int)OffsetToData).Pointer; |
||||
Name = name; |
||||
} |
||||
} |
||||
|
||||
public sealed class Win32ResourceName |
||||
{ |
||||
private readonly object _name; |
||||
|
||||
public bool HasName => _name is string; |
||||
|
||||
public bool HasId => _name is ushort; |
||||
|
||||
public string Name => (string)_name; |
||||
|
||||
public ushort Id => (ushort)_name; |
||||
|
||||
public Win32ResourceName(string name) |
||||
{ |
||||
_name = name ?? throw new ArgumentNullException(nameof(name)); |
||||
} |
||||
|
||||
public Win32ResourceName(int id) : this(checked((ushort)id)) |
||||
{ |
||||
} |
||||
|
||||
public Win32ResourceName(ushort id) |
||||
{ |
||||
_name = id; |
||||
} |
||||
|
||||
internal unsafe Win32ResourceName(byte* pRoot, IMAGE_RESOURCE_DIRECTORY_ENTRY* pEntry) |
||||
{ |
||||
_name = (pEntry->Name & 0x80000000) == 0 ? (object)(ushort)pEntry->Name : ReadString(pRoot, (int)(pEntry->Name & 0x7FFFFFFF)); |
||||
|
||||
static string ReadString(byte* pRoot, int offset) |
||||
{ |
||||
var pString = (IMAGE_RESOURCE_DIRECTORY_STRING*)(pRoot + offset); |
||||
return new string(pString->NameString, 0, pString->Length); |
||||
} |
||||
} |
||||
|
||||
public static bool operator ==(Win32ResourceName x, Win32ResourceName y) |
||||
{ |
||||
if (x.HasName) |
||||
{ |
||||
return y.HasName ? string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) == 0 : false; |
||||
} |
||||
else |
||||
{ |
||||
return y.HasId ? x.Id == y.Id : false; |
||||
} |
||||
} |
||||
|
||||
public static bool operator !=(Win32ResourceName x, Win32ResourceName y) |
||||
{ |
||||
return !(x == y); |
||||
} |
||||
|
||||
public override int GetHashCode() |
||||
{ |
||||
return _name.GetHashCode(); |
||||
} |
||||
|
||||
public override bool Equals(object obj) |
||||
{ |
||||
if (!(obj is Win32ResourceName name)) |
||||
return false; |
||||
return this == name; |
||||
} |
||||
|
||||
public override string ToString() |
||||
{ |
||||
return HasName ? $"Name: {Name}" : $"Id: {Id}"; |
||||
} |
||||
} |
||||
|
||||
internal struct IMAGE_RESOURCE_DIRECTORY |
||||
{ |
||||
public uint Characteristics; |
||||
public uint TimeDateStamp; |
||||
public ushort MajorVersion; |
||||
public ushort MinorVersion; |
||||
public ushort NumberOfNamedEntries; |
||||
public ushort NumberOfIdEntries; |
||||
} |
||||
|
||||
internal struct IMAGE_RESOURCE_DIRECTORY_ENTRY |
||||
{ |
||||
public uint Name; |
||||
public uint OffsetToData; |
||||
} |
||||
|
||||
internal unsafe struct IMAGE_RESOURCE_DIRECTORY_STRING |
||||
{ |
||||
public ushort Length; |
||||
public fixed char NameString[1]; |
||||
} |
||||
|
||||
internal struct IMAGE_RESOURCE_DATA_ENTRY |
||||
{ |
||||
public uint OffsetToData; |
||||
public uint Size; |
||||
public uint CodePage; |
||||
public uint Reserved; |
||||
} |
||||
} |
Loading…
Reference in new issue