Orion9

|
Posted: Wed Sep 09, 2026 12:34 Post subject: |
|
|
| Destiny wrote: | | Мне казалось слова всегда лучше всего рассказывают о проблеме, потому что я могу внести пояснения туда - куда видео их не вставит. |
Не всегда так. Иногда лучше один раз увидеть, чем сто раз услышать.
Вы говорите, что проблема у вас с любой папкой появляется, ну тогда чем не вариант:
 Hidden text
Как видите, все работает.
| Destiny wrote: | | автор вернется и прочтет тут наши размышления и выложит исходники в доступ. |
И что это даст? Болван уже расписал все, как там устроено )
 Hidden text Если проанализировать импорты исполняемого файла NTLinksMaker, вы не увидите там привычную функцию CreateSymbolicLinkW.
Вместо использования высокоуровневого Win32 API автор утилиты (MVV) работает напрямую с низкоуровневой структурой файловой системы NTFS. NTLinksMaker создает символические ссылки и точки соединения (Junctions) вручную через прямую запись данных файловой системы в репарсы.
Процесс создания ссылки изнутри выглядит следующим образом:
## 1. Создание пустого объекта
В зависимости от типа ссылки программа вызывает стандартную функцию CreateFileW (с флагами FILE_FLAG_OPEN_REPARSE_POINT и нужными атрибутами) или CreateDirectoryW. На этом этапе создается обычный «нулевой» файл или пустая папка.
## 2. Формирование буфера данных репарса
В памяти структуры выделяется специальный буфер — REPARSE_DATA_BUFFER. Программа вручную заполняет его заголовки и данные:
* Устанавливается правильный идентификатор — Reparse Tag. Для символических ссылок это константа IOCTL_REPARSE_TAG_SYMLINK (0xA000000C), а для точек соединения — IOCTL_REPARSE_TAG_MOUNT_POINT (0xA0000003).
* Внутрь буфера записываются пути к целевому объекту: как заменяющий путь (Substitute Name, имеющий вид вроде \??\C:\Target\), так и отображаемый путь (Printable Name). [1]
## 3. Запись через DeviceIoControl
Когда буфер сформирован, NTLinksMaker отправляет управляющий код драйверу файловой системы NTFS с помощью функции DeviceIoControl: [2]
DeviceIoControl(
hDevice, // Хэндл созданного файла/папки
FSCTL_SET_REPARSE_POINT, // Управляющий код для установки точки повторного анализа
lpInBuffer, // Сформированный буфер REPARSE_DATA_BUFFER
nInBufferSize, // Размер буфера
...
);
После этого NTFS превращает обычный файл или папку в полноценный системный симлинк. [2, 3]
## Зачем нужен такой сложный путь?
Прямая работа с DeviceIoControl и репарсами дает утилите несколько ключевых преимуществ:
1. Универсальность кода: Архитектура программы построена вокруг низкоуровневых точек повторного анализа (Reparse Points). Одним и тем же методом (просто меняя Reparse Tag в буфере) утилита создает и Junctions, и файловые/папочные симлинки. [4]
2. Точечный контроль: Такой подход позволяет вручную управлять флагами внутри ссылки (например, жестко задавать относительный или абсолютный путь) и исключает влияние некоторых ограничений высокоуровневого API.
3. Кросс-версионность и оптимизация: MVV проектировал плагин NTLinks и утилиту NTLinksMaker для максимальной скорости работы с Total Commander. Чтение и редактирование ссылок в самом плагине происходит через зеркальный метод — FSCTL_GET_REPARSE_POINT. Имея уже готовый код для парсинга и сборки буферов репарсов, автору было проще использовать его и для создания ссылок. [1, 4]
(Примечание: Жесткие ссылки (hardlinks) утилита создает стандартным образом через CreateHardLinkW, так как они не являются репарсами).
Хотите посмотреть на структуру буфера REPARSE_DATA_BUFFER для символических ссылок на C++, или вас интересует, как обойти требование прав администратора при создании ссылок низкоуровневым методом?
 Python Для того чтобы превратить скрипт в полноценную утилиту с графическим интерфейсом (как у NTLinksMaker), мы можем использовать встроенную в Python библиотеку tkinter.
Этот скрипт объединяет логику низкоуровневой работы с NTFS через DeviceIoControl и простое диалоговое окно для выбора папок.
| Code: | import ctypesfrom ctypes import wintypesimport osimport structimport sysimport tkinter as tkfrom tkinter import filedialog, messagebox
# --- Константы Win32 API ---GENERIC_WRITE = 0x40000000OPEN_EXISTING = 3FILE_SHARE_WRITE = 0x00000002FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000FILE_FLAG_BACKUP_SEMANTICS = 0x02000000FSCTL_SET_REPARSE_POINT = 0x000900A4IOCTL_REPARSE_TAG_SYMLINK = 0xA000000CSYMLINK_FLAG_RELATIVE = 0x00000000
def create_symlink_lowlevel(link_path, target_path):
"""Низкоуровневое создание символической ссылки (метод NTLinksMaker)"""
os.makedirs(link_path, exist_ok=True)
substitute_name = f"\\??\\{os.path.abspath(target_path)}"
print_name = os.path.abspath(target_path)
sub_bytes = substitute_name.encode('utf-16le')
print_bytes = print_name.encode('utf-16le')
sub_len = len(sub_bytes)
print_len = len(print_bytes)
sub_offset = 0
print_offset = sub_len
path_buffer = sub_bytes + print_bytes
reparse_data_length = 12 + len(path_buffer)
header = struct.pack(
"<IHHHHHHI",
IOCTL_REPARSE_TAG_SYMLINK, # ReparseTag
reparse_data_length, # ReparseDataLength
0, # Reserved
sub_offset, # SubstituteNameOffset
sub_len, # SubstituteNameLength
print_offset, # PrintNameOffset
print_len, # PrintNameLength
SYMLINK_FLAG_RELATIVE # Flags
)
full_buffer = header + path_buffer
handle = ctypes.windll.kernel32.CreateFileW(
wintypes.LPCWSTR(link_path),
GENERIC_WRITE,
FILE_SHARE_WRITE,
None,
OPEN_EXISTING,
FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
None
)
if handle == -1:
raise ctypes.WinError(ctypes.get_last_error())
try:
bytes_returned = wintypes.DWORD(0)
success = ctypes.windll.kernel32.DeviceIoControl(
handle,
FSCTL_SET_REPARSE_POINT,
full_buffer,
len(full_buffer),
None,
0,
ctypes.byref(bytes_returned),
None
)
if not success:
raise ctypes.WinError(ctypes.get_last_error())
finally:
ctypes.windll.kernel32.CloseHandle(handle)
# --- Графический интерфейс (GUI) ---class NTLinksApp:
def __init__(self, root):
self.root = root
self.root.title("NTLinksMaker Python Clone")
self.root.geometry("550x200")
self.root.resizable(False, False)
# Переменные путей
self.target_dir = tk.StringVar()
self.link_dir = tk.StringVar()
# Строка 1: Исходная папка (Target)
tk.Label(root, text="Целевая папка (куда ссылаемся):").grid(row=0, column=0, sticky="w", px=10, py=10)
tk.Entry(root, textvariable=self.target_dir, width=50).grid(row=0, column=1, py=10)
tk.Button(root, text="Обзор...", command=self.browse_target).grid(row=0, column=2, px=5, py=10)
# Строка 2: Папка-ссылка (Link)
tk.Label(root, text="Имя ссылки (что создаем):").grid(row=1, column=0, sticky="w", px=10, py=10)
tk.Entry(root, textvariable=self.link_dir, width=50).grid(row=1, column=1, py=10)
tk.Button(root, text="Обзор...", command=self.browse_link).grid(row=1, column=2, px=5, py=10)
# Кнопка создания
tk.Button(root, text="Создать Симлинк (DeviceIoControl)", command=self.process_creation,
bg="#4CAF50", fg="white", font=("Arial", 10, "bold"), height=2).grid(row=2, column=0, columnspan=3, py=20)
def browse_target(self):
folder = filedialog.askdirectory(title="Выберите целевую папку")
if folder:
self.target_dir.set(os.path.normpath(folder))
def browse_link(self):
# Запрашиваем имя будущей папки-ссылки
folder = filedialog.asksaveasfilename(title="Укажите путь и имя для будущей ссылки", initialfile="NewLink")
if folder:
self.link_dir.set(os.path.normpath(folder))
def process_creation(self):
tgt = self.target_dir.get()
lnk = self.link_dir.get()
if not tgt or not lnk:
messagebox.showerror("Ошибка", "Заполните оба пути!")
return
if not os.path.exists(tgt):
messagebox.showerror("Ошибка", "Целевая папка не существует!")
return
try:
create_symlink_lowlevel(lnk, tgt)
messagebox.showinfo("Успех", f"Символическая ссылка успешно создана!\n\n{lnk} -> {tgt}")
except Exception as e:
messagebox.showerror("Ошибка Windows API", f"Не удалось создать ссылку.\nВозможно, утилита запущена без прав Администратора.\n\nДетали: {e}")
if __name__ == "__main__":
# Проверка на запуск с правами Администратора (нужно для создания Symlink в Windows)
if not ctypes.windll.shell32.IsUserAnAdmin():
# Перезапускаем скрипт с запросом прав администратора
ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
sys.exit()
root = tk.Tk()
# Принудительно настраиваем отступы grid для совместимости
for child in root.winfo_children():
pass
# Конфигурация сетки для красивых отступов (замена устаревшему px/py во внутреннем синтаксисе)
app = NTLinksApp(root)
root.mainloop()
## Как запустить этот скрипт:
1. Сохраните код в файл, например ntlinks_ui.py.
2. Запустите его двойным кликом или через консоль.
3. Скрипт автоматически перезапустится с запросом прав Администратора (появится стандартное окно UAC Windows), так как я добавил проверку IsUserAnAdmin() в блок __main__.
4. В открывшемся окне выберите исходную папку и путь, где создать новую ссылку.
|
Добавлено спустя 7 минут:
| Jonmey wrote: | | Может тогда сразу Гислеру это предложить? (там даже целую паству криворуких можно организовать). |
 Hidden text I'm in
| Jonmey wrote: | | А вообще отличный был бы метод получения исходников нужной программы - заявляешь автору, что его программа кривая и требуешь дать ее исходники.... |
Идеальная схема. Безотказная  |
|