113 lines
6.0 KiB
C#
113 lines
6.0 KiB
C#
using System.ComponentModel;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using Microsoft.Win32.SafeHandles;
|
|
|
|
// Some upstream DXF readers still use narrow std::ifstream for resource files.
|
|
// Give the native runtime an ASCII executable path while retaining the real
|
|
// portable folder for settings, projects and QI_ROOT. No CAD files are copied.
|
|
static class RuntimePath
|
|
{
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
static extern uint GetShortPathNameW(string longPath, StringBuilder shortPath, uint capacity);
|
|
|
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
static extern SafeFileHandle CreateFileW(string name, uint access, uint share, IntPtr security,
|
|
uint creation, uint flags, IntPtr template);
|
|
|
|
[DllImport("kernel32.dll", SetLastError = true)]
|
|
[return: MarshalAs(UnmanagedType.Bool)]
|
|
static extern bool DeviceIoControl(SafeFileHandle file, uint control, byte[] input, uint inputSize,
|
|
IntPtr output, uint outputSize, out uint returned, IntPtr overlapped);
|
|
|
|
static bool IsAscii(string value) => value.All(c => c < 128);
|
|
|
|
static string? ShortAsciiPath(string path)
|
|
{
|
|
if (IsAscii(path)) return path;
|
|
var buffer = new StringBuilder(32768);
|
|
var length = GetShortPathNameW(path, buffer, (uint)buffer.Capacity);
|
|
return length > 0 && length < buffer.Capacity && IsAscii(buffer.ToString())
|
|
? buffer.ToString() : null;
|
|
}
|
|
|
|
public static string ForEngine(string root, string engine, bool allowShortPath = true)
|
|
{
|
|
if (allowShortPath && ShortAsciiPath(engine) is { } shortEngine) return shortEngine;
|
|
var relativeEngine = Path.GetRelativePath(root, engine);
|
|
if (relativeEngine.StartsWith(".." + Path.DirectorySeparatorChar) || Path.IsPathRooted(relativeEngine))
|
|
throw new IOException("Путь CAD-движка находится за пределами переносимой папки.");
|
|
var key = Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(root.ToUpperInvariant())))[..32];
|
|
var candidates = new[]
|
|
{
|
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
|
|
Path.GetTempPath(),
|
|
Environment.GetFolderPath(Environment.SpecialFolder.CommonDocuments),
|
|
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
|
|
};
|
|
Exception? lastError = null;
|
|
foreach (var parent in candidates.Where(p => !string.IsNullOrWhiteSpace(p)).Distinct())
|
|
{
|
|
var asciiParent = ShortAsciiPath(Path.TrimEndingDirectorySeparator(parent));
|
|
if (asciiParent is null) continue;
|
|
var cache = Path.Combine(asciiParent, "QInventor", "RuntimeLinks");
|
|
var link = Path.Combine(cache, key);
|
|
try
|
|
{
|
|
Directory.CreateDirectory(cache);
|
|
var info = new DirectoryInfo(link);
|
|
if (info.Exists)
|
|
{
|
|
var target = info.ResolveLinkTarget(false)?.FullName;
|
|
if (target is null || !string.Equals(Path.TrimEndingDirectorySeparator(Path.GetFullPath(target)),
|
|
root, StringComparison.OrdinalIgnoreCase))
|
|
throw new IOException("Существующая ссылка CAD-движка указывает на другую папку.");
|
|
}
|
|
else
|
|
{
|
|
// Never remove an existing or dangling junction. Only create
|
|
// a fresh directory whose exact target is the validated root.
|
|
if (info.LinkTarget is not null)
|
|
throw new IOException("Служебная ссылка CAD-движка недоступна.");
|
|
Directory.CreateDirectory(link);
|
|
CreateJunction(link, root);
|
|
}
|
|
var aliasedEngine = Path.Combine(link, relativeEngine);
|
|
if (!File.Exists(aliasedEngine))
|
|
throw new IOException("Служебная ссылка CAD-движка не содержит исполняемый файл.");
|
|
return aliasedEngine;
|
|
}
|
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or Win32Exception)
|
|
{
|
|
lastError = ex;
|
|
}
|
|
}
|
|
throw new IOException("Не удалось подготовить служебный путь CAD-движка. Перенесите папку QInventor в путь из латинских символов.", lastError);
|
|
}
|
|
|
|
static void CreateJunction(string link, string target)
|
|
{
|
|
if (target.StartsWith(@"\\"))
|
|
throw new IOException("Сетевая папка без короткого пути не поддерживается CAD-движком.");
|
|
var substitute = Encoding.Unicode.GetBytes(@"\??\" + target);
|
|
var print = Encoding.Unicode.GetBytes(target);
|
|
var buffer = new byte[16 + substitute.Length + 2 + print.Length + 2];
|
|
void Write16(int offset, int value) => BitConverter.GetBytes(checked((ushort)value)).CopyTo(buffer, offset);
|
|
BitConverter.GetBytes(0xA0000003u).CopyTo(buffer, 0); // IO_REPARSE_TAG_MOUNT_POINT
|
|
Write16(4, buffer.Length - 8);
|
|
Write16(8, 0);
|
|
Write16(10, substitute.Length);
|
|
Write16(12, substitute.Length + 2);
|
|
Write16(14, print.Length);
|
|
substitute.CopyTo(buffer, 16);
|
|
print.CopyTo(buffer, 18 + substitute.Length);
|
|
using var handle = CreateFileW(link, 0x40000000, 0, IntPtr.Zero, 3,
|
|
0x00200000 | 0x02000000, IntPtr.Zero);
|
|
if (handle.IsInvalid) throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
if (!DeviceIoControl(handle, 0x000900A4, buffer, (uint)buffer.Length,
|
|
IntPtr.Zero, 0, out _, IntPtr.Zero))
|
|
throw new Win32Exception(Marshal.GetLastWin32Error());
|
|
}
|
|
}
|