245 lines
7.1 KiB
TypeScript
245 lines
7.1 KiB
TypeScript
|
|
declare const monaco: any;
|
||
|
|
|
||
|
|
export function SetupFileDropHandler(): void {
|
||
|
|
if (typeof monaco === "undefined")
|
||
|
|
return;
|
||
|
|
|
||
|
|
const editors = monaco.editor.getEditors();
|
||
|
|
const editor = editors[editors.length - 1];
|
||
|
|
|
||
|
|
if (!editor)
|
||
|
|
return;
|
||
|
|
|
||
|
|
const domNode = editor.getDomNode();
|
||
|
|
|
||
|
|
if (!domNode)
|
||
|
|
return;
|
||
|
|
|
||
|
|
domNode.addEventListener("dragover", (e: DragEvent) => {
|
||
|
|
if (e.dataTransfer?.types?.includes("Files")) {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
domNode.addEventListener("drop", (e: DragEvent) => {
|
||
|
|
const files = e.dataTransfer?.files;
|
||
|
|
|
||
|
|
if (files && files.length > 0) {
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
handleFile(editor, files[0]);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
document.addEventListener("paste", (e: ClipboardEvent) => {
|
||
|
|
if (!editor.hasWidgetFocus())
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (!e.clipboardData?.files.length)
|
||
|
|
return;
|
||
|
|
|
||
|
|
e.preventDefault();
|
||
|
|
e.stopPropagation();
|
||
|
|
|
||
|
|
handleFile(editor, e.clipboardData.files[0]);
|
||
|
|
}, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
const MAX_FILE_SIZE = 1024 * 1024; // 1 MB
|
||
|
|
|
||
|
|
function IsTextFile(file: File): boolean {
|
||
|
|
if (file.type.startsWith("text/")) return true;
|
||
|
|
|
||
|
|
const textExtensions = [
|
||
|
|
".reg", ".ps1", ".txt", ".cfg", ".ini", ".conf",
|
||
|
|
".json", ".xml", ".yaml", ".yml", ".csv", ".log",
|
||
|
|
".bat", ".cmd", ".sh", ".py", ".lua",
|
||
|
|
];
|
||
|
|
|
||
|
|
return textExtensions.includes(getFileExtension(file.name));
|
||
|
|
}
|
||
|
|
|
||
|
|
function handleFile(editor: any, file: File): void {
|
||
|
|
if (file.size > MAX_FILE_SIZE)
|
||
|
|
return;
|
||
|
|
|
||
|
|
if (!IsTextFile(file))
|
||
|
|
return;
|
||
|
|
|
||
|
|
const reader = new FileReader();
|
||
|
|
|
||
|
|
reader.onload = () => {
|
||
|
|
const content = reader.result as string;
|
||
|
|
const ext = getFileExtension(file.name);
|
||
|
|
|
||
|
|
if (ext === ".reg") {
|
||
|
|
const psCommands = ParseRegToPowerShell(content);
|
||
|
|
insertTextAtCursor(editor, psCommands);
|
||
|
|
} else if (ext === ".ps1") {
|
||
|
|
editor.setValue(content);
|
||
|
|
} else {
|
||
|
|
const hereString = `@'\n${content}\n'@`;
|
||
|
|
|
||
|
|
insertTextAtCursor(editor, hereString);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
reader.readAsText(file);
|
||
|
|
}
|
||
|
|
|
||
|
|
function getFileExtension(filename: string): string {
|
||
|
|
const idx = filename.lastIndexOf(".");
|
||
|
|
|
||
|
|
return idx >= 0 ? filename.substring(idx).toLowerCase() : "";
|
||
|
|
}
|
||
|
|
|
||
|
|
function insertTextAtCursor(editor: any, text: string): void {
|
||
|
|
const selection = editor.getSelection();
|
||
|
|
|
||
|
|
editor.executeEdits("fileImport", [{
|
||
|
|
range: selection,
|
||
|
|
text: text,
|
||
|
|
forceMoveMarkers: true,
|
||
|
|
}]);
|
||
|
|
}
|
||
|
|
|
||
|
|
interface RegEntry {
|
||
|
|
type: "key" | "sz" | "dword" | "binary";
|
||
|
|
path: string;
|
||
|
|
property?: string;
|
||
|
|
value?: any;
|
||
|
|
}
|
||
|
|
|
||
|
|
function ParseRegToPowerShell(regContent: string): string {
|
||
|
|
const entries = ParseRegFile(regContent);
|
||
|
|
const lines: string[] = [];
|
||
|
|
|
||
|
|
for (const entry of entries) {
|
||
|
|
switch (entry.type) {
|
||
|
|
case "key":
|
||
|
|
if (lines.length > 0)
|
||
|
|
lines.push("");
|
||
|
|
|
||
|
|
lines.push(`New-Item -Path "registry::\\${entry.path}"`);
|
||
|
|
break;
|
||
|
|
case "sz": {
|
||
|
|
const escaped = (entry.value as string).replace(/"/g, '""""');
|
||
|
|
|
||
|
|
lines.push(`New-ItemProperty -Path "registry::\\${entry.path}" -Name "${entry.property}" -Value "${escaped}" -Force`);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case "dword":
|
||
|
|
lines.push(`New-ItemProperty -Path "registry::\\${entry.path}" -Name "${entry.property}" -Value ${entry.value} -Force`);
|
||
|
|
break;
|
||
|
|
case "binary": {
|
||
|
|
const bytes = entry.value as number[];
|
||
|
|
const chunks: string[] = [];
|
||
|
|
|
||
|
|
for (let i = 0; i < bytes.length; i += 32) {
|
||
|
|
const chunk = bytes.slice(i, i + 32);
|
||
|
|
chunks.push(chunk.map(b => "0x" + b.toString(16).toUpperCase().padStart(2, "0")).join(", "));
|
||
|
|
}
|
||
|
|
|
||
|
|
const formatted = chunks.join("\n\t");
|
||
|
|
|
||
|
|
lines.push(`New-ItemProperty -Path "registry::\\${entry.path}" -Name "${entry.property}" -PropertyType Binary -Value ([byte[]]@(\n\t${formatted}\n)) -Force`);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return lines.join("\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
function ParseRegFile(content: string): RegEntry[] {
|
||
|
|
const entries: RegEntry[] = [];
|
||
|
|
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||
|
|
const joined = normalized.replace(/\\\n\s*/g, "");
|
||
|
|
const rawLines = joined.split("\n");
|
||
|
|
|
||
|
|
let currentPath = "";
|
||
|
|
|
||
|
|
for (const line of rawLines) {
|
||
|
|
const trimmed = line.trim();
|
||
|
|
|
||
|
|
// Skip empty lines, comments, and headers
|
||
|
|
if (!trimmed || trimmed.startsWith(";") ||
|
||
|
|
trimmed === "Windows Registry Editor Version 5.00" ||
|
||
|
|
trimmed === "REGEDIT4") {
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Key section: [HKEY_...]
|
||
|
|
const keyMatch = trimmed.match(/^\[(.+)\]$/);
|
||
|
|
|
||
|
|
if (keyMatch) {
|
||
|
|
const path = keyMatch[1];
|
||
|
|
|
||
|
|
if (path.startsWith("-"))
|
||
|
|
continue;
|
||
|
|
currentPath = path;
|
||
|
|
entries.push({ type: "key", path: currentPath });
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!currentPath)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
// Parse value lines
|
||
|
|
let name: string;
|
||
|
|
let valueStr: string;
|
||
|
|
|
||
|
|
if (trimmed.startsWith("@=")) {
|
||
|
|
name = "@";
|
||
|
|
valueStr = trimmed.substring(2);
|
||
|
|
} else {
|
||
|
|
const valueMatch = trimmed.match(/^"(.+?)"=(.*)$/);
|
||
|
|
|
||
|
|
if (!valueMatch)
|
||
|
|
continue;
|
||
|
|
|
||
|
|
name = valueMatch[1].replace(/\\\\/g, "\\").replace(/\\"/g, '"');
|
||
|
|
valueStr = valueMatch[2];
|
||
|
|
}
|
||
|
|
|
||
|
|
// Skip delete values
|
||
|
|
if (valueStr === "-")
|
||
|
|
continue;
|
||
|
|
|
||
|
|
// String value: "..."
|
||
|
|
if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
|
||
|
|
const str = valueStr.slice(1, -1).replace(/\\\\/g, "\\").replace(/\\"/g, '"');
|
||
|
|
|
||
|
|
entries.push({ type: "sz", path: currentPath, property: name, value: str });
|
||
|
|
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// DWORD: dword:XXXXXXXX
|
||
|
|
const dwordMatch = valueStr.match(/^dword:([0-9a-fA-F]{1,8})$/);
|
||
|
|
|
||
|
|
if (dwordMatch) {
|
||
|
|
const val = parseInt(dwordMatch[1], 16);
|
||
|
|
|
||
|
|
entries.push({ type: "dword", path: currentPath, property: name, value: val });
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Binary/hex types: hex:XX,XX,... or hex(N):XX,XX,...
|
||
|
|
const hexMatch = valueStr.match(/^hex(?:\([0-9a-fA-F]+\))?:(.*)$/);
|
||
|
|
|
||
|
|
if (hexMatch) {
|
||
|
|
const hexStr = hexMatch[1].replace(/\s/g, "");
|
||
|
|
const bytes = hexStr.split(",")
|
||
|
|
.filter(s => s.length > 0)
|
||
|
|
.map(s => parseInt(s, 16));
|
||
|
|
|
||
|
|
entries.push({ type: "binary", path: currentPath, property: name, value: bytes });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return entries;
|
||
|
|
}
|