From 632e5f8d08448fca30f927fc3dd5d59742c17863 Mon Sep 17 00:00:00 2001 From: VaDiM Date: Thu, 30 Nov 2023 04:35:58 +0300 Subject: [PATCH] [GUI] Add support for .lnk files via Drag&Drop. Close #13 --- AssetStudio/AssetsManager.cs | 2 +- .../Extensions/BinaryReaderExtensions.cs | 26 ++- AssetStudioGUI/AssetStudioGUIForm.cs | 23 ++- AssetStudioGUI/Components/LnkReader.cs | 164 ++++++++++++++++++ 4 files changed, 205 insertions(+), 10 deletions(-) create mode 100644 AssetStudioGUI/Components/LnkReader.cs diff --git a/AssetStudio/AssetsManager.cs b/AssetStudio/AssetsManager.cs index fc9283b..a07c521 100644 --- a/AssetStudio/AssetsManager.cs +++ b/AssetStudio/AssetsManager.cs @@ -82,7 +82,7 @@ namespace AssetStudio MergeSplitAssets(fullPath, true); fileList.AddRange(Directory.GetFiles(fullPath, "*.*", SearchOption.AllDirectories)); } - else + else if (File.Exists(fullPath)) { parentPath = Path.GetDirectoryName(fullPath); fileList.Add(fullPath); diff --git a/AssetStudio/Extensions/BinaryReaderExtensions.cs b/AssetStudio/Extensions/BinaryReaderExtensions.cs index 5d6fc06..b9753fb 100644 --- a/AssetStudio/Extensions/BinaryReaderExtensions.cs +++ b/AssetStudio/Extensions/BinaryReaderExtensions.cs @@ -35,10 +35,13 @@ namespace AssetStudio return ""; } - public static string ReadStringToNull(this BinaryReader reader, int maxLength = 32767) + public static string ReadStringToNull(this BinaryReader reader, int maxLength = 32767, Encoding encoding = null) { + if (encoding?.CodePage == 1200) //Unicode (UTF-16LE) + return reader.ReadUnicodeStringToNull(maxLength * 2); + var bytes = new List(); - int count = 0; + var count = 0; while (reader.BaseStream.Position != reader.BaseStream.Length && count < maxLength) { var b = reader.ReadByte(); @@ -49,7 +52,24 @@ namespace AssetStudio bytes.Add(b); count++; } - return Encoding.UTF8.GetString(bytes.ToArray()); + return encoding?.GetString(bytes.ToArray()) ?? Encoding.UTF8.GetString(bytes.ToArray()); + } + + private static string ReadUnicodeStringToNull(this BinaryReader reader, int maxLength) + { + var bytes = new List(); + var count = 0; + while (reader.BaseStream.Position != reader.BaseStream.Length && count < maxLength) + { + var b = reader.ReadBytes(2); + if (b.Length < 2 || (b[0] == 0 && b[1] == 0)) + { + break; + } + bytes.AddRange(b); + count += 2; + } + return Encoding.Unicode.GetString(bytes.ToArray()); } public static Quaternion ReadQuaternion(this BinaryReader reader) diff --git a/AssetStudioGUI/AssetStudioGUIForm.cs b/AssetStudioGUI/AssetStudioGUIForm.cs index 76d3c7e..87f9905 100644 --- a/AssetStudioGUI/AssetStudioGUIForm.cs +++ b/AssetStudioGUI/AssetStudioGUIForm.cs @@ -146,14 +146,25 @@ namespace AssetStudioGUI private async void AssetStudioGUIForm_DragDrop(object sender, DragEventArgs e) { var paths = (string[])e.Data.GetData(DataFormats.FileDrop); - if (paths.Length > 0) + if (paths.Length == 0) + return; + + ResetForm(); + for (var i = 0; i < paths.Length; i++) { - ResetForm(); - assetsManager.SpecifyUnityVersion = specifyUnityVersion.Text; - await Task.Run(() => assetsManager.LoadFilesAndFolders(out openDirectoryBackup, paths)); - saveDirectoryBackup = openDirectoryBackup; - BuildAssetStructures(); + if (paths[i].ToLower().EndsWith(".lnk")) + { + var targetPath = LnkReader.GetLnkTarget(paths[i]); + if (!string.IsNullOrEmpty(targetPath)) + { + paths[i] = targetPath; + } + } } + assetsManager.SpecifyUnityVersion = specifyUnityVersion.Text; + await Task.Run(() => assetsManager.LoadFilesAndFolders(out openDirectoryBackup, paths)); + saveDirectoryBackup = openDirectoryBackup; + BuildAssetStructures(); } private async void loadFile_Click(object sender, EventArgs e) diff --git a/AssetStudioGUI/Components/LnkReader.cs b/AssetStudioGUI/Components/LnkReader.cs new file mode 100644 index 0000000..aa3a3e3 --- /dev/null +++ b/AssetStudioGUI/Components/LnkReader.cs @@ -0,0 +1,164 @@ +// Shortcut (.lnk) file reader +// by aelurum +// Based on https://github.com/libyal/liblnk/blob/main/documentation/Windows%20Shortcut%20File%20(LNK)%20format.asciidoc + +using AssetStudio; +using System; +using System.IO; +using System.Text; + +namespace AssetStudioGUI +{ + public static class LnkReader + { + [Flags] + private enum LnkDataFlags + { + //The LNK file contains a link target identifier + HasTargetIDList = 0x00000001, + //The LNK file contains location information + HasLinkInfo = 0x00000002, + } + + [Flags] + private enum LnkLocFlags + { + //The linked file is on a volume + //If set the volume information and the local path contain data + VolumeIDAndLocalBasePath = 0x0001, + //The linked file is on a network share + //If set the network share information and common path contain data + CommonNetworkRelativeLinkAndPathSuffix = 0x0002 + } + + [Flags] + private enum PathTypeFlags + { + IsUnicodeLocalPath = 0x01, + IsUnicodeNetShareName = 0x02, + IsUnicodeCommonPath = 0x04 + } + + public static string GetLnkTarget(string filePath) + { + var targetPath = string.Empty; + var pathType = (PathTypeFlags)0; + Encoding sysEncoding; + try + { + sysEncoding = GetSysEncoding(); + Logger.Debug($"System default text encoding: {sysEncoding.CodePage}"); + } + catch (Exception ex) + { + Logger.Error("Text encoding error", ex); + return null; + } + + using (var reader = new FileReader(filePath)) + { + reader.Endian = EndianType.LittleEndian; + + var headerSize = reader.ReadUInt32(); //76 bytes + reader.Position = 20; //skip LNK class identifier (GUID) + var dataFlags = (LnkDataFlags)reader.ReadUInt32(); + if ((dataFlags & LnkDataFlags.HasLinkInfo) == 0) + { + Logger.Warning("Unsupported type of .lnk file. Link info was not found."); + return null; + } + reader.Position = headerSize; + + //Skip the shell item ID list + if ((dataFlags & LnkDataFlags.HasTargetIDList) != 0) + { + var itemIDListSize = reader.ReadUInt16(); + reader.Position += itemIDListSize; + } + + //The offsets is relative to the start of the location information block + var locInfoPos = reader.Position; + var locInfoFullSize = reader.ReadUInt32(); + if (locInfoFullSize == 0) + { + Logger.Warning("Unsupported type of .lnk file. Link info was not found."); + return null; + } + var locInfoHeaderSize = reader.ReadUInt32(); + var locFlags = (LnkLocFlags)reader.ReadUInt32(); + //Offset to the volume information block + var offsetVolumeInfo = reader.ReadUInt32(); + //Offset to the ANSI local path + var offsetLocalPath = reader.ReadUInt32(); + //Offset to the network share information block + var offsetNetInfo = reader.ReadUInt32(); + //Offset to the ANSI common path. 0 if not available + var offsetCommonPath = reader.ReadUInt32(); + if (locInfoHeaderSize > 28) + { + //Offset to the Unicode local path + offsetLocalPath = reader.ReadUInt32(); + pathType |= PathTypeFlags.IsUnicodeLocalPath; + } + if (locInfoHeaderSize > 32) + { + //Offset to the Unicode common path + offsetCommonPath = reader.ReadUInt32(); + pathType |= PathTypeFlags.IsUnicodeCommonPath; + } + + //Read local path, if exist + if (offsetLocalPath > 0) + { + reader.Position = locInfoPos + offsetLocalPath; + targetPath = (pathType & PathTypeFlags.IsUnicodeLocalPath) != 0 + ? reader.ReadStringToNull(encoding: Encoding.Unicode) + : reader.ReadStringToNull(encoding: sysEncoding); + } + + //Read network path, if exist + if (locFlags == LnkLocFlags.CommonNetworkRelativeLinkAndPathSuffix) + { + reader.Position = locInfoPos + offsetNetInfo; + var netInfoSize = reader.ReadUInt32(); + var netInfoFlags = reader.ReadUInt32(); + //Offset to the ANSI network share name. The offset is relative to the start of the network share information block + var offsetNetShareName = reader.ReadUInt32(); + if (offsetNetShareName > 20) + { + reader.Position = locInfoPos + offsetNetInfo + 20; + //Offset to the Unicode network share name + offsetNetShareName = reader.ReadUInt32(); + pathType |= PathTypeFlags.IsUnicodeNetShareName; + } + if (offsetNetShareName > 0) + { + reader.Position = locInfoPos + offsetNetInfo + offsetNetShareName; + targetPath = (pathType & PathTypeFlags.IsUnicodeNetShareName) != 0 + ? reader.ReadStringToNull(encoding: Encoding.Unicode) + : reader.ReadStringToNull(encoding: sysEncoding); + } + } + + //Read common path, if exist + if (offsetCommonPath > 0) + { + reader.Position = locInfoPos + offsetCommonPath; + var commonPath = (pathType & PathTypeFlags.IsUnicodeCommonPath) != 0 + ? reader.ReadStringToNull(encoding: Encoding.Unicode) + : reader.ReadStringToNull(encoding: sysEncoding); + targetPath = Path.Combine(targetPath, commonPath); + } + } + return targetPath; + } + + private static Encoding GetSysEncoding() + { +#if !NETFRAMEWORK + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +#endif + return Encoding.GetEncoding(0); + } + } +}