-
-
Save codehz/38972dec42cb8ce9e400fbbc2967183c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
Version 2, December 2004 | |
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net> | |
Everyone is permitted to copy and distribute verbatim or modified | |
copies of this license document, and changing it is allowed as long | |
as the name is changed. | |
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE | |
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION | |
0. You just DO WHAT THE FUCK YOU WANT TO. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
extern "kernel32" fn GetCommandLineW() callconv(.Stdcall) [*:0]const u16; | |
extern "kernel32" fn Wow64DisableWow64FsRedirection(key: **c_void) callconv(.Stdcall) bool; | |
extern "shell32" fn ShellExecuteExW(info: *ShellExecuteInfo) callconv(.Stdcall) bool; | |
extern "ole32" fn CoInitializeEx(reserved: ?*c_void, flags: u32) callconv(.Stdcall) u32; | |
extern "kernel32" fn ExitProcess(code: c_uint) callconv(.Stdcall) noreturn; | |
extern "user32" fn MessageBoxA( | |
hwnd: usize, | |
text: ?[*:0]const u8, | |
caption: ?[*:0]const u8, | |
typ: c_uint, | |
) callconv(.Stdcall) c_int; | |
const ShellExecuteInfo = extern struct { | |
cbSize: u32 = @sizeOf(@This()), | |
fmask: u32 = 0, | |
hwnd: usize = 0, | |
verb: ?[*:0]const u16 = null, | |
file: ?[*:0]const u16 = null, | |
parameters: ?[*:0]const u16 = null, | |
directory: ?[*:0]const u16 = null, | |
show: c_int = 0, | |
app: usize = 0, | |
idlist: ?*c_void = null, | |
class: ?[*:0]const u16 = null, | |
hkey: usize = 0, | |
hotkey: u32 = 0, | |
moniter: usize = 0, | |
process: usize = 0, | |
}; | |
const L = std.unicode.utf8ToUtf16LeStringLiteral; | |
fn l(ch: u8) u16 { | |
return @intCast(u16, ch); | |
} | |
// Assume only ascii | |
pub fn panic(name: []const u8, bt: ?*std.builtin.StackTrace) noreturn { | |
const G = struct { | |
var buffer = [1]u8{0} ** 1024; | |
}; | |
for (name) |ch, i| G.buffer[i] = ch; | |
_ = MessageBoxA(0, G.buffer[0..:0], "ShellExecute panicked", 0x10); | |
ExitProcess(2); | |
} | |
fn help() noreturn { | |
const message = | |
\\Usage: shellexecute [/class <class>] [/verb <operation>] [/dir workdir] [/show <showflag>] [/silent] [/wow64] file [arguments...] | |
\\ | |
\\ - class: ProgId or URI protocol or file extension or registry path under HKEY_CLASSES_ROOT | |
\\ - operation: edit|explore|find|open|properties|print|runas or any custom operations | |
\\ - showflag: | |
\\ - 0: HIDE (default) | |
\\ - 1: SHOWNORMAL | |
\\ - 2: SHOWMINIMIZED | |
\\ - 3: MAXIMIZE | SHOWMAXIMIZED | |
\\ - 4: SHOWNOACTIVATE | |
\\ - 5: SHOW | |
\\ - 6: MINIMIZE | |
\\ - 7: SHOWMINNOACTIVE | |
\\ - 8: SHOWNA | |
\\ - 9: RESTORE | |
\\ - 10: SHOWDEFAULT | |
; | |
_ = MessageBoxA(0, message, "ShellExecute", 0x241020); | |
ExitProcess(1); | |
} | |
// UNSAFE | |
const Buffer16 = struct { | |
const BUFFER_SIZE = 16384; | |
const BufferRaw = [BUFFER_SIZE]u16; | |
var buffer: BufferRaw = undefined; | |
idx: usize = 0, | |
fn init() @This() { | |
return .{}; | |
} | |
fn push(self: *@This(), ch: u16) void { | |
buffer[self.idx] = ch; | |
self.idx += 1; | |
} | |
fn dump(self: *@This()) [:0]const u16 { | |
buffer[self.idx] = 0; | |
return buffer[0..self.idx :0]; | |
} | |
}; | |
const ArgIteratorW = struct { | |
index: usize, | |
cmd_line: [*:0]const u16, | |
fn init() @This() { | |
return @This(){ | |
.index = 0, | |
.cmd_line = GetCommandLineW(), | |
}; | |
} | |
const DataWithIndex = struct { | |
index: usize, | |
data: [:0]const u16, | |
cvt: bool, | |
}; | |
fn lower16(ch: u16, cvt: bool) u16 { | |
return if (cvt and ch > l('A') and ch < l('Z')) ch ^ 0x20 else ch; | |
} | |
fn next(self: *@This()) ?DataWithIndex { | |
var cvt = false; | |
while (true) : (self.index += 1) { | |
const byte = self.cmd_line[self.index]; | |
switch (byte) { | |
0 => return null, | |
l(' '), l('\t') => continue, | |
l('/') => { | |
cvt = true; | |
continue; | |
}, | |
else => break, | |
} | |
} | |
const cache = self.index; | |
return DataWithIndex{ | |
.index = cache, | |
.data = self.internalNext(cvt), | |
.cvt = cvt, | |
}; | |
} | |
fn internalNext(self: *@This(), cvt: bool) [:0]const u16 { | |
var buf = Buffer16.init(); | |
var backslash_count: usize = 0; | |
var in_quote = false; | |
while (true) : (self.index += 1) { | |
const byte = self.cmd_line[self.index]; | |
switch (byte) { | |
0 => return buf.dump(), | |
l('"') => { | |
const quote_is_real = backslash_count % 2 == 0; | |
self.emitBackslashes(&buf, backslash_count / 2); | |
backslash_count = 0; | |
if (quote_is_real) { | |
in_quote = !in_quote; | |
} else { | |
buf.push(l('"')); | |
} | |
}, | |
l('\\') => { | |
backslash_count += 1; | |
}, | |
l(' '), l('\t') => { | |
self.emitBackslashes(&buf, backslash_count); | |
backslash_count = 0; | |
if (in_quote) { | |
buf.push(byte); | |
} else { | |
return buf.dump(); | |
} | |
}, | |
else => { | |
self.emitBackslashes(&buf, backslash_count); | |
backslash_count = 0; | |
buf.push(lower16(byte, cvt)); | |
}, | |
} | |
} | |
} | |
fn emitBackslashes(self: *@This(), buf: *Buffer16, emit_count: usize) void { | |
var i: usize = 0; | |
while (i < emit_count) : (i += 1) { | |
buf.push(l('\\')); | |
} | |
} | |
}; | |
fn parseIntU16(input: []const u16) c_int { | |
var ret: c_int = 0; | |
for (input) |ch| { | |
if (ch >= l('0') and ch <= l('9')) { | |
var v = @intCast(c_int, ch - l('0')); | |
ret = ret * 10 + v; | |
} else @panic("Invalid number"); | |
} | |
return ret; | |
} | |
const FixedAllocator = struct { | |
memory: [1048576]u16 = undefined, | |
used: usize = 0, | |
fn dupe(self: *@This(), input: []const u16) [:0]const u16 { | |
defer self.used += input.len + 1; | |
std.mem.copy(u16, self.memory[self.used .. self.used + input.len], input); | |
self.memory[self.used + input.len] = 0; | |
return self.memory[self.used .. self.used + input.len :0]; | |
} | |
}; | |
var fixed = FixedAllocator{}; | |
const ExecInfo = struct { | |
options: Options, | |
file: ?[:0]const u16, | |
parameters: ?[*:0]const u16, | |
const Options = struct { | |
class: ?[:0]const u16 = null, | |
verb: ?[:0]const u16 = null, | |
dir: ?[:0]const u16 = null, | |
show: c_int = 0, | |
silent: bool = false, | |
wow64: bool = false, | |
}; | |
fn init() ExecInfo { | |
var iter = ArgIteratorW.init(); | |
_ = iter.next(); | |
var options: Options = .{}; | |
const fields: []const std.builtin.TypeInfo.StructField = std.meta.fields(Options); | |
var has_argument = false; | |
while (iter.next()) |arg| { | |
if (arg.cvt) { | |
has_argument = true; | |
var ok = false; | |
inline for (fields) |field| { | |
if (eq(field.name, arg.data)) { | |
switch (field.field_type) { | |
?[:0]const u16 => { | |
const val = iter.next() orelse @panic(field.name ++ " argument required"); | |
@field(options, field.name) = fixed.dupe(val.data); | |
}, | |
c_int => { | |
const val = iter.next() orelse @panic(field.name ++ " argument required"); | |
if (val.data.len > 8) @panic("invalid number for " ++ field.name); | |
@field(options, field.name) = parseIntU16(val.data); | |
}, | |
bool => @field(options, field.name) = true, | |
else => unreachable, | |
} | |
ok = true; | |
break; | |
} | |
} | |
if (!ok) help(); | |
} else { | |
const file = fixed.dupe(arg.data); | |
return ExecInfo{ | |
.options = options, | |
.file = file, | |
.parameters = if (iter.next()) |obj| iter.cmd_line + obj.index else null, | |
}; | |
} | |
} | |
if (has_argument) | |
return ExecInfo{ | |
.options = options, | |
.file = null, | |
.parameters = null, | |
}; | |
help(); | |
} | |
fn buildShellExecuteInfo(self: *const @This()) ShellExecuteInfo { | |
if (self.options.wow64) { | |
var key: *c_void = undefined; | |
_ = Wow64DisableWow64FsRedirection(&key); | |
} | |
var ret: ShellExecuteInfo = .{ | |
.show = self.options.show, | |
.file = if (self.file) |file| file.ptr else null, | |
.parameters = self.parameters, | |
}; | |
if (self.options.class) |class| { | |
ret.fmask |= 0x00000001; // SEE_MASK_CLASSNAME | |
ret.class = class.ptr; | |
} | |
if (self.options.silent) { | |
ret.fmask |= 0x00000400; // SEE_MASK_FLAG_NO_UI | |
} | |
if (self.options.verb) |verb| { | |
ret.verb = verb.ptr; | |
} | |
if (self.options.dir) |dir| { | |
ret.directory = dir.ptr; | |
} | |
return ret; | |
} | |
}; | |
fn eq(comptime precompiled: []const u8, text: []const u16) bool { | |
return std.mem.eql(u16, text, L(precompiled)); | |
} | |
pub fn main() anyerror!void { | |
_ = CoInitializeEx(null, 0x6); | |
const einfo = ExecInfo.init(); | |
var info = einfo.buildShellExecuteInfo(); | |
_ = ShellExecuteExW(&info); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment