added runtime patcher, added MemoryUtils, fixes

This commit is contained in:
kitbyte
2025-03-24 14:51:58 +02:00
parent 123307aed7
commit d3ff13a528
33 changed files with 1294 additions and 287 deletions
+71
View File
@@ -0,0 +1,71 @@
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
namespace WeModPatcher.Utils
{
public static class Extensions
{
public static bool CheckWeModPath(string root)
{
try
{
return File.Exists(Path.Combine(root, "WeMod.exe")) &&
File.Exists(Path.Combine(root, "resources", "app.asar"));
}
catch
{
return false;
}
}
public static string FindWeModDirectory()
{
string localAppDataPath = Environment.GetEnvironmentVariable("LOCALAPPDATA");
string defaultDir = Path.Combine(localAppDataPath ?? "", "WeMod");
if (!Directory.Exists(defaultDir))
{
return null;
}
return FindLatestWeMod(defaultDir);
}
public static string Base64Decode(string base64EncodedData)
{
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
public static string Base64Encode(string plainText)
{
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
public static string FindLatestWeMod(string root)
{
var appFolders = Directory.EnumerateDirectories(root)
.Select(folderPath => new DirectoryInfo(folderPath))
.Where(dirInfo => Regex.IsMatch(dirInfo.Name, @"^app-\w+"))
.Select(dirInfo => new
{
Name = dirInfo.Name,
Path = dirInfo.FullName,
LastModified = dirInfo.LastWriteTime
})
.OrderByDescending(item => item.LastModified)
.ToList();
return (
from folder
in appFolders
where CheckWeModPath(folder.Path)
select folder.Path
).FirstOrDefault();
}
}
}
+178
View File
@@ -0,0 +1,178 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using WeModPatcher.Models;
using WeModPatcher.Utils.Win32;
namespace WeModPatcher.Utils
{
public class MemoryUtils
{
public static int ScanMemoryBlock(byte[] buffer, int bufferLength, byte[] pattern, byte[] mask)
{
var patternLength = pattern.Length;
if (bufferLength < patternLength)
{
return -1;
}
// Make a length of length outside the first cycle for optimization
var searchEnd = bufferLength - patternLength;
// first pass - use the first non-empty byte of the mask for a quick check
var firstValidIndex = -1;
for (var i = 0; i < patternLength; i++)
{
if (mask[i] == 1)
{
firstValidIndex = i;
break;
}
}
if (firstValidIndex == -1)
{
return 0;
}
var firstByte = pattern[firstValidIndex];
for (var i = 0; i <= searchEnd; i++)
{
// quick check by the first byte before full comparison
if (buffer[i + firstValidIndex] != firstByte)
continue;
var found = true;
// check only those positions where mask = 1
for (var j = 0; j < patternLength; j++)
{
if (mask[j] == 0 || buffer[i + j] == pattern[j])
{
continue;
}
found = false;
break;
}
if (found)
{
return i;
}
}
return -1;
}
public static void ParseSignature(string signatureStr, out byte[] pattern, out byte[] mask)
{
var parts = signatureStr.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
var length = parts.Length;
pattern = new byte[length];
mask = new byte[length];
for (var i = 0; i < length; i++)
{
if (parts[i] == "??" || parts[i] == "?")
{
pattern[i] = 0;
// wildcard byte
mask[i] = 0;
continue;
}
pattern[i] = Convert.ToByte(parts[i], 16);
mask[i] = 1;
}
}
public static bool SafeWriteVirtualMemory(IntPtr hProcess, IntPtr address, byte[] bytes)
{
if (!Imports.VirtualProtectEx(hProcess, address, (IntPtr)1, 0x40, out uint oldProtect))
{
return false;
}
bool result = Imports.WriteProcessMemory(hProcess, address, bytes, bytes.Length, out _);
// Restore the previous access rights
Imports.VirtualProtectEx(hProcess, address, (IntPtr)1, oldProtect, out _);
return result;
}
public static IntPtr ScanVirtualMemory(IntPtr hProcess, IntPtr startAddress, int searchSize, byte[] signature, byte[] mask)
{
const int BUFFER_SIZE = 4096;
byte[] buffer = new byte[BUFFER_SIZE];
// We can't copy all the crap of the process into a byte array at once. Don't try this
for (long currentAddress = startAddress.ToInt64();
currentAddress < startAddress.ToInt64() + searchSize;
currentAddress += BUFFER_SIZE - signature.Length)
{
if (!Imports.ReadProcessMemory(hProcess, new IntPtr(currentAddress), buffer, BUFFER_SIZE, out int bytesRead) || bytesRead == 0)
{
// Read error or end of memory, throw mb?
continue;
}
var i = ScanMemoryBlock(buffer, bytesRead, signature, mask);
if (i != -1)
{
return new IntPtr(currentAddress + i);
}
}
return IntPtr.Zero;
}
public static int PatchFile(string filePath, Signature signature, byte[] patchBytes)
{
const int bufferSize = 8192;
var buffer = new byte[bufferSize + signature.Length - 1];
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
int filePosition = 0;
while (true)
{
int bytesRead = fileStream.Read(buffer, 0, bufferSize);
if (bytesRead == 0) break;
int matchIndex = ScanMemoryBlock(buffer, bytesRead, signature, signature.Mask);
if (matchIndex != -1)
{
int functionStartPosition = filePosition + matchIndex;
var checkBuffer = new byte[patchBytes.Length];
fileStream.Seek(functionStartPosition + signature.Offset, SeekOrigin.Begin);
fileStream.Read(checkBuffer, 0, patchBytes.Length);
if (checkBuffer.SequenceEqual(patchBytes))
{
return 0; // Memory already patched
}
// Go to patch position
fileStream.Seek(functionStartPosition + signature.Offset, SeekOrigin.Begin);
fileStream.Write(patchBytes, 0, patchBytes.Length);
return functionStartPosition; // Return the address of the function start by signature
}
filePosition += bytesRead;
Array.Copy(buffer, bufferSize, buffer, 0, signature.Length - 1);
}
}
return -1;
}
}
}
-208
View File
@@ -1,208 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using AsarSharp;
using WeModPatcher.Models;
using WeModPatcher.View.MainWindow;
namespace WeModPatcher.Utils
{
public class Patcher
{
private class PatchEntry
{
public Regex Target { get; set; }
public string Patch { get; set; }
public bool Applied { get; set; }
public bool SingleMatch { get; set; } = true;
public bool DynamicFieldResolve { get; set; }
}
private static readonly Dictionary<EPatchType, PatchEntry> Patches = new Dictionary<EPatchType, PatchEntry>()
{
{
EPatchType.ActivatePro,
new PatchEntry
{
DynamicFieldResolve = true,
Target = new Regex(@"getUserAccount\(\)\{.*?return\s+this\.#\w+\.fetch\(\{.*?\}\)\}", RegexOptions.Singleline),
Patch = "getUserAccount(){return this.#<fetch_field_name>.fetch({endpoint:\"/v3/account\",method:\"GET\",name:\"/v3/account\",collectMetrics:0}).then(response=>{response.subscription={period:\"yearly\",state:\"active\"};response.flags=78;return response;})}"
}
},
{
EPatchType.DisableUpdates,
new PatchEntry
{
Target = new Regex(@"registerHandler\(""ACTION_CHECK_FOR_UPDATE"".*?\)\)\)\)", RegexOptions.Singleline),
Patch = "registerHandler(\"ACTION_CHECK_FOR_UPDATE\",(e=>expectUpdateFeedUrl(e,(e=>null)))"
}
}
};
// ...
// test eax, eax (0x85 for r/m16/32/64)
// jnz short loc_1403A4DD2 (Integrity check failed)
// call near ptr funk_1445527E0
// ...
private const string PatchSignature = "E8 ?? ?? ?? ?? ?? C0 75 ?? F6 C3 01 74 ?? 48 89 F9 E8 ?? ?? ?? ??";
private static readonly byte[] PatchBytes = { 0x31 };
private const int PatchOffset = 0x5;
private readonly string _weModRootFolder;
private readonly Action<string, ELogType> _logger;
private readonly HashSet<EPatchType> _config;
private readonly string _asarPath;
private readonly string _backupPath;
private readonly string _unpackedPath;
private int _sumOfPatches = 0;
public Patcher(string weModRootFolder, Action<string, ELogType> logger, HashSet<EPatchType> config)
{
_weModRootFolder = weModRootFolder;
_logger = logger;
_config = config;
_asarPath = Path.Combine(weModRootFolder, "resources", "app.asar");
_unpackedPath = Path.Combine(weModRootFolder, "resources", "app.asar.unpacked");
_backupPath = Path.Combine(weModRootFolder, "resources", "app.asar.backup");
}
private static string GetFetchFieldName(string targetFunction)
{
var fetchMatch = Regex.Match(targetFunction, @"return\s+this\.#(\w+)\.fetch");
return fetchMatch.Success ? fetchMatch.Groups[1].Value : null;
}
private void ApplyJsPatch(string fileName, string js, PatchEntry patch, EPatchType patchType)
{
if (patch.Applied)
{
return;
}
var matches = patch.Target.Matches(js);
if (matches.Count == 0)
{
return;
}
if(matches.Count > 1 && patch.SingleMatch)
{
throw new Exception(
$"[PATCHER] [{patchType}] Patch failed. Multiple target functions found. Looks like the version is not supported");
}
if (patch.DynamicFieldResolve)
{
string fetchFieldName = GetFetchFieldName(matches[0].Value);
if (string.IsNullOrEmpty(fetchFieldName))
{
throw new Exception($"[PATCHER] [{patchType}] Fetch field name not found");
}
patch.Patch = patch.Patch.Replace("<fetch_field_name>", fetchFieldName);
}
_logger($"[PATCHER] [{patchType}] Found target function in: " + Path.GetFileName(fileName), ELogType.Info);
File.WriteAllText(fileName, patch.Target.Replace(js, patch.Patch));
_logger($"[PATCHER] [{patchType}] Patch applied", ELogType.Success);
patch.Applied = true;
_sumOfPatches -= (int)patchType;
}
private void PatchAsar()
{
var items = Directory.EnumerateFiles(_unpackedPath)
.Where(file => !Directory.Exists(file) && Regex.IsMatch(Path.GetFileName(file), @"^app-\w+|index\.js"))
.ToList();
if (!items.Any())
{
throw new Exception("[PATCHER] No app bundle found");
}
var requestedPatches = _config.ToList();
requestedPatches.ForEach(patch => _sumOfPatches += (int)patch);
foreach (var item in items)
{
if (_sumOfPatches <= 0)
{
break;
}
string data = File.ReadAllText(item);
foreach (var entry in requestedPatches)
{
ApplyJsPatch(item, data, Patches[entry], entry);
}
}
}
private async Task PatchPE()
{
_logger("[PATCHER] Patching PE...", ELogType.Info);
var pePath = Path.Combine(_weModRootFolder, "WeMod.exe");
var patchResult = await PatternScanner.PatchBySignature(pePath, PatchSignature, PatchBytes, PatchOffset);
if(patchResult == -1)
{
_logger("[PATCHER] Failed to patch PE", ELogType.Error);
return;
}
_logger(patchResult == 0 ? "[PATCHER] PE already patched!" : "[PATCHER] PE patched successfully!", ELogType.Success);
}
public async Task Patch()
{
if (!File.Exists(_backupPath))
{
_logger("[PATCHER] Creating backup...", ELogType.Info);
File.Copy(_asarPath, _backupPath);
}
else
{
_logger("[PATCHER] Backup already exists", ELogType.Warn);
}
if(!File.Exists(_asarPath))
{
_logger("[PATCHER] app.asar not found!", ELogType.Error);
return;
}
try
{
_logger("[PATCHER] Extracting app.asar...", ELogType.Info);
AsarExtractor.ExtractAll(_asarPath, _unpackedPath);
}
catch (Exception e)
{
_logger($"[PATCHER] Failed to unpack app.asar: {e.Message}", ELogType.Error);
return;
}
PatchAsar();
try
{
new AsarCreator(_unpackedPath, _asarPath, new CreateOptions
{
Unpack = new Regex(@"^static\\unpacked.*$")
}).CreatePackageWithOptions();
}
catch (Exception e)
{
_logger($"[PATCHER] Failed to pack app.asar: {e.Message}", ELogType.Error);
return;
}
// await PatchPE();
_logger("[PATCHER] Done!", ELogType.Success);
}
}
}
-101
View File
@@ -1,101 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WeModPatcher.Utils
{
public class PatternScanner
{
public static int FindPatternInBuffer(byte[] buffer, int bytesRead, byte[] signature, string mask)
{
int bufferLength = bytesRead + signature.Length - 1;
for (int i = 0; i <= bytesRead - signature.Length; i++)
{
if (IsMatch(buffer, signature, mask, i))
return i;
}
return -1;
}
private static bool IsMatch(byte[] buffer, byte[] signature, string mask, int offset)
{
for (int i = 0; i < signature.Length; i++)
{
if (mask[i] == 'x' && buffer[offset + i] != signature[i])
return false;
}
return true;
}
public static (byte[] signature, string mask) ParseSignature(string signature)
{
var signatureBytes = new List<byte>();
var mask = new StringBuilder();
var tokens = signature.Split(' ');
foreach (var token in tokens)
{
if (token == "??" || token == "?")
{
signatureBytes.Add(0);
mask.Append('?');
}
else
{
signatureBytes.Add(Convert.ToByte(token, 16));
mask.Append('x');
}
}
return (signatureBytes.ToArray(), mask.ToString());
}
public static async Task<int> PatchBySignature(string filePath, string functionSignature, byte[] patchBytes, int patchOffset)
{
var (signature, mask) = ParseSignature(functionSignature);
const int bufferSize = 8192;
var buffer = new byte[bufferSize + signature.Length - 1];
using (var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
int filePosition = 0;
while (true)
{
int bytesRead = await fileStream.ReadAsync(buffer, 0, bufferSize);
if (bytesRead == 0) break;
int matchIndex = FindPatternInBuffer(buffer, bytesRead, signature, mask);
if (matchIndex != -1)
{
int functionStartPosition = filePosition + matchIndex;
var checkBuffer = new byte[patchBytes.Length];
fileStream.Seek(functionStartPosition + patchOffset, SeekOrigin.Begin);
await fileStream.ReadAsync(checkBuffer, 0, patchBytes.Length);
if (checkBuffer.SequenceEqual(patchBytes))
{
return 0; // Memory already patched
}
// Go to patch position
fileStream.Seek(functionStartPosition + patchOffset, SeekOrigin.Begin);
await fileStream.WriteAsync(patchBytes, 0, patchBytes.Length);
return functionStartPosition; // Return the address of the function start by signature
}
filePosition += bytesRead;
Array.Copy(buffer, bufferSize, buffer, 0, signature.Length - 1);
}
}
return -1;
}
}
}
+258
View File
@@ -0,0 +1,258 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace WeModPatcher.Utils.Win32
{
public static class Imports
{
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ReadProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
[Out] byte[] lpBuffer,
int dwSize,
out int lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
int nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool VirtualProtectEx(
IntPtr hProcess,
IntPtr lpAddress,
IntPtr dwSize,
uint flNewProtect,
out uint lpflOldProtect);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WaitForDebugEvent(ref DEBUG_EVENT lpDebugEvent, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool DebugActiveProcessStop(uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool DebugActiveProcess(int dwProcessId);
[DllImport("psapi.dll", SetLastError = true)]
public static extern bool EnumProcessModules(
IntPtr hProcess,
IntPtr lphModule,
uint cb,
out uint lpcbNeeded);
[DllImport("psapi.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern int GetModuleFileNameEx(
IntPtr hProcess,
IntPtr hModule,
StringBuilder lpFilename,
int nSize);
[DllImport("psapi.dll", SetLastError = true)]
public static extern bool GetModuleInformation(IntPtr hProcess, IntPtr hModule, out MODULEINFO lpmodinfo, uint cb);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
public static extern bool CreateProcessA
(
String lpApplicationName,
String lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
Boolean bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
String lpCurrentDirectory,
[In] ref StartupInfo lpStartupInfo,
out ProcessInformation lpProcessInformation
);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool ContinueDebugEvent(uint dwProcessId, uint dwThreadId, uint dwContinueStatus);
[StructLayout(LayoutKind.Sequential)]
public struct StartupInfo
{
public Int32 cb ;
public IntPtr lpReserved ;
public IntPtr lpDesktop ;
public IntPtr lpTitle ;
public Int32 dwX ;
public Int32 dwY ;
public Int32 dwXSize ;
public Int32 dwYSize ;
public Int32 dwXCountChars ;
public Int32 dwYCountChars ;
public Int32 dwFillAttribute ;
public Int32 dwFlags ;
public Int16 wShowWindow ;
public Int16 cbReserved2 ;
public IntPtr lpReserved2 ;
public IntPtr hStdInput ;
public IntPtr hStdOutput ;
public IntPtr hStdError ;
}
[StructLayout(LayoutKind.Sequential)]
public struct ProcessInformation
{
public IntPtr hProcess;
public IntPtr hThread;
public Int32 dwProcessId;
public Int32 dwThreadId;
}
#region Debug event structures
[StructLayout(LayoutKind.Explicit)]
public struct DEBUG_EVENT
{
[FieldOffset(0)]
public uint dwDebugEventCode;
[FieldOffset(4)]
public uint dwProcessId;
[FieldOffset(8)]
public uint dwThreadId;
[FieldOffset(16)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 160)]
public byte[] Union;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct EXCEPTION_DEBUG_INFO
{
public EXCEPTION_RECORD ExceptionRecord;
public uint dwFirstChance;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct EXCEPTION_RECORD
{
public uint ExceptionCode;
public uint ExceptionFlags;
public IntPtr pExceptionRecord;
public IntPtr ExceptionAddress;
public uint NumberParameters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 15)]
public IntPtr[] ExceptionInformation;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CREATE_THREAD_DEBUG_INFO
{
public IntPtr hThread;
public IntPtr lpThreadLocalBase;
public IntPtr lpStartAddress;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct CREATE_PROCESS_DEBUG_INFO
{
public IntPtr hFile;
public IntPtr hProcess;
public IntPtr hThread;
public IntPtr lpBaseOfImage;
public uint dwDebugInfoFileOffset;
public uint nDebugInfoSize;
public IntPtr lpThreadLocalBase;
public IntPtr lpStartAddress;
public IntPtr lpImageName;
public ushort fUnicode;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct MODULEINFO
{
public IntPtr lpBaseOfDll;
public uint SizeOfImage;
public IntPtr EntryPoint;
}
[StructLayout(LayoutKind.Sequential, Pack = 8)]
public struct EXIT_THREAD_DEBUG_INFO
{
public uint dwExitCode;
}
[StructLayout(LayoutKind.Sequential)]
public struct EXIT_PROCESS_DEBUG_INFO
{
public uint dwExitCode;
}
[StructLayout(LayoutKind.Sequential)]
public struct LOAD_DLL_DEBUG_INFO
{
public IntPtr hFile;
public IntPtr lpBaseOfDll;
public uint dwDebugInfoFileOffset;
public uint nDebugInfoSize;
public IntPtr lpImageName;
public ushort fUnicode;
}
[StructLayout(LayoutKind.Sequential)]
public struct UNLOAD_DLL_DEBUG_INFO
{
public IntPtr lpBaseOfDll;
}
[StructLayout(LayoutKind.Sequential)]
public struct OUTPUT_DEBUG_STRING_INFO
{
public IntPtr lpDebugStringData;
public ushort fUnicode;
public ushort nDebugStringLength;
}
[StructLayout(LayoutKind.Sequential)]
public struct RIP_INFO
{
public uint dwError;
public uint dwType;
}
public static T MapUnmanagedStructure<T>(byte[] debugInfo)
{
GCHandle handle = GCHandle.Alloc(debugInfo, GCHandleType.Pinned);
try
{
return Marshal.PtrToStructure<T>(handle.AddrOfPinnedObject());
}
finally
{
handle.Free();
}
}
#endregion
// Determining constants for debugging
public const uint INFINITE = 0xFFFFFFFF;
public const uint DEBUG_PROCESS = 0x00000001;
public const uint DBG_CONTINUE = 0x00010002;
public const uint CREATE_PROCESS_DEBUG_EVENT = 3;
public const uint EXCEPTION_DEBUG_EVENT = 1;
public const uint LOAD_DLL_DEBUG_EVENT = 6;
public const uint OUTPUT_DEBUG_STRING_EVENT = 8;
public const uint EXCEPTION_BREAKPOINT = 0x80000003;
// Constants for VirtualProtectex
public const uint PAGE_EXECUTE_READWRITE = 0x40;
}
}
+61
View File
@@ -0,0 +1,61 @@
using System;
using System.Runtime.InteropServices;
namespace WeModPatcher.Utils.Win32
{
public class Shortcut
{
public class ShortcutParams
{
public string FileName { get; set; }
public string TargetPath { get; set; }
public string Arguments { get; set; }
public string WorkingDirectory { get; set; }
public string Description { get; set; }
public string Hotkey { get; set; }
public string IconPath { get; set; }
};
private static readonly Type m_type = Type.GetTypeFromProgID("WScript.Shell");
private static readonly object m_shell = Activator.CreateInstance(m_type);
[ComImport, TypeLibType(0x1040), Guid("F935DC23-1CF0-11D0-ADB9-00C04FD58A0B")]
private interface IWshShortcut
{
[DispId(0)]
string FullName { [return: MarshalAs(UnmanagedType.BStr)][DispId(0)] get; }
[DispId(0x3e8)]
string Arguments { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e8)] set; }
[DispId(0x3e9)]
string Description { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3e9)] set; }
[DispId(0x3ea)]
string Hotkey { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ea)] set; }
[DispId(0x3eb)]
string IconLocation { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3eb)] set; }
[DispId(0x3ec)]
string RelativePath { [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ec)] set; }
[DispId(0x3ed)]
string TargetPath { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ed)] set; }
[DispId(0x3ee)]
int WindowStyle { [DispId(0x3ee)] get; [param: In][DispId(0x3ee)] set; }
[DispId(0x3ef)]
string WorkingDirectory { [return: MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] get; [param: In, MarshalAs(UnmanagedType.BStr)][DispId(0x3ef)] set; }
[TypeLibFunc((short)0x40), DispId(0x7d0)]
void Load([In, MarshalAs(UnmanagedType.BStr)] string PathLink);
[DispId(0x7d1)]
void Save();
}
public static void CreateShortcut(string fileName, string targetPath, string arguments, string workingDirectory, string description, string iconPath)
{
IWshShortcut shortcut = (IWshShortcut)m_type.InvokeMember("CreateShortcut", System.Reflection.BindingFlags.InvokeMethod, null, m_shell, new object[] { fileName });
shortcut.Description = description;
shortcut.TargetPath = targetPath;
shortcut.WorkingDirectory = workingDirectory;
shortcut.Arguments = arguments;
if (!string.IsNullOrEmpty(iconPath))
shortcut.IconLocation = iconPath;
shortcut.Save();
}
}
}