rc.zig (13194B) - Raw
1 const std = @import("std"); 2 const utils = @import("utils.zig"); 3 const res = @import("res.zig"); 4 const SourceBytes = @import("literals.zig").SourceBytes; 5 6 // https://learn.microsoft.com/en-us/windows/win32/menurc/about-resource-files 7 8 pub const ResourceType = enum { 9 accelerators, 10 bitmap, 11 cursor, 12 dialog, 13 dialogex, 14 /// As far as I can tell, this is undocumented; the most I could find was this: 15 /// https://www.betaarchive.com/wiki/index.php/Microsoft_KB_Archive/91697 16 dlginclude, 17 /// Undocumented, basically works exactly like RCDATA 18 dlginit, 19 font, 20 html, 21 icon, 22 menu, 23 menuex, 24 messagetable, 25 plugplay, // Obsolete 26 rcdata, 27 stringtable, 28 /// Undocumented 29 toolbar, 30 user_defined, 31 versioninfo, 32 vxd, // Obsolete 33 34 // Types that are treated as a user-defined type when encountered, but have 35 // special meaning without the Visual Studio GUI. We match the Win32 RC compiler 36 // behavior by acting as if these keyword don't exist when compiling the .rc 37 // (thereby treating them as user-defined). 38 //textinclude, // A special resource that is interpreted by Visual C++. 39 //typelib, // A special resource that is used with the /TLBID and /TLBOUT linker options 40 41 // Types that can only be specified by numbers, they don't have keywords 42 cursor_num, 43 icon_num, 44 string_num, 45 anicursor_num, 46 aniicon_num, 47 fontdir_num, 48 manifest_num, 49 50 const map = std.StaticStringMapWithEql( 51 ResourceType, 52 std.static_string_map.eqlAsciiIgnoreCase, 53 ).initComptime(.{ 54 .{ "ACCELERATORS", .accelerators }, 55 .{ "BITMAP", .bitmap }, 56 .{ "CURSOR", .cursor }, 57 .{ "DIALOG", .dialog }, 58 .{ "DIALOGEX", .dialogex }, 59 .{ "DLGINCLUDE", .dlginclude }, 60 .{ "DLGINIT", .dlginit }, 61 .{ "FONT", .font }, 62 .{ "HTML", .html }, 63 .{ "ICON", .icon }, 64 .{ "MENU", .menu }, 65 .{ "MENUEX", .menuex }, 66 .{ "MESSAGETABLE", .messagetable }, 67 .{ "PLUGPLAY", .plugplay }, 68 .{ "RCDATA", .rcdata }, 69 .{ "STRINGTABLE", .stringtable }, 70 .{ "TOOLBAR", .toolbar }, 71 .{ "VERSIONINFO", .versioninfo }, 72 .{ "VXD", .vxd }, 73 }); 74 75 pub fn fromString(bytes: SourceBytes) ResourceType { 76 const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes); 77 if (maybe_ordinal) |ordinal| { 78 if (ordinal.ordinal >= 256) return .user_defined; 79 return fromRT(@enumFromInt(ordinal.ordinal)); 80 } 81 return map.get(bytes.slice) orelse .user_defined; 82 } 83 84 // TODO: Some comptime validation that RT <-> ResourceType conversion is synced? 85 pub fn fromRT(rt: res.RT) ResourceType { 86 return switch (rt) { 87 .ACCELERATOR => .accelerators, 88 .ANICURSOR => .anicursor_num, 89 .ANIICON => .aniicon_num, 90 .BITMAP => .bitmap, 91 .CURSOR => .cursor_num, 92 .DIALOG => .dialog, 93 .DLGINCLUDE => .dlginclude, 94 .DLGINIT => .dlginit, 95 .FONT => .font, 96 .FONTDIR => .fontdir_num, 97 .GROUP_CURSOR => .cursor, 98 .GROUP_ICON => .icon, 99 .HTML => .html, 100 .ICON => .icon_num, 101 .MANIFEST => .manifest_num, 102 .MENU => .menu, 103 .MESSAGETABLE => .messagetable, 104 .PLUGPLAY => .plugplay, 105 .RCDATA => .rcdata, 106 .STRING => .string_num, 107 .TOOLBAR => .toolbar, 108 .VERSION => .versioninfo, 109 .VXD => .vxd, 110 _ => .user_defined, 111 }; 112 } 113 114 pub fn canUseRawData(resource: ResourceType) bool { 115 return switch (resource) { 116 .user_defined, 117 .html, 118 .plugplay, // Obsolete 119 .rcdata, 120 .vxd, // Obsolete 121 .manifest_num, 122 .dlginit, 123 => true, 124 else => false, 125 }; 126 } 127 128 pub fn nameForErrorDisplay(resource: ResourceType) []const u8 { 129 return switch (resource) { 130 // zig fmt: off 131 .accelerators, .bitmap, .cursor, .dialog, .dialogex, .dlginclude, .dlginit, .font, 132 .html, .icon, .menu, .menuex, .messagetable, .plugplay, .rcdata, .stringtable, 133 .toolbar, .versioninfo, .vxd => @tagName(resource), 134 // zig fmt: on 135 .user_defined => "user-defined", 136 .cursor_num => std.fmt.comptimePrint("{d} (cursor)", .{@intFromEnum(res.RT.CURSOR)}), 137 .icon_num => std.fmt.comptimePrint("{d} (icon)", .{@intFromEnum(res.RT.ICON)}), 138 .string_num => std.fmt.comptimePrint("{d} (string)", .{@intFromEnum(res.RT.STRING)}), 139 .anicursor_num => std.fmt.comptimePrint("{d} (anicursor)", .{@intFromEnum(res.RT.ANICURSOR)}), 140 .aniicon_num => std.fmt.comptimePrint("{d} (aniicon)", .{@intFromEnum(res.RT.ANIICON)}), 141 .fontdir_num => std.fmt.comptimePrint("{d} (fontdir)", .{@intFromEnum(res.RT.FONTDIR)}), 142 .manifest_num => std.fmt.comptimePrint("{d} (manifest)", .{@intFromEnum(res.RT.MANIFEST)}), 143 }; 144 } 145 }; 146 147 /// https://learn.microsoft.com/en-us/windows/win32/menurc/stringtable-resource#parameters 148 /// https://learn.microsoft.com/en-us/windows/win32/menurc/dialog-resource#parameters 149 /// https://learn.microsoft.com/en-us/windows/win32/menurc/dialogex-resource#parameters 150 pub const OptionalStatements = enum { 151 characteristics, 152 language, 153 version, 154 155 // DIALOG 156 caption, 157 class, 158 exstyle, 159 font, 160 menu, 161 style, 162 163 pub const map = std.StaticStringMapWithEql( 164 OptionalStatements, 165 std.static_string_map.eqlAsciiIgnoreCase, 166 ).initComptime(.{ 167 .{ "CHARACTERISTICS", .characteristics }, 168 .{ "LANGUAGE", .language }, 169 .{ "VERSION", .version }, 170 }); 171 172 pub const dialog_map = std.StaticStringMapWithEql( 173 OptionalStatements, 174 std.static_string_map.eqlAsciiIgnoreCase, 175 ).initComptime(.{ 176 .{ "CAPTION", .caption }, 177 .{ "CLASS", .class }, 178 .{ "EXSTYLE", .exstyle }, 179 .{ "FONT", .font }, 180 .{ "MENU", .menu }, 181 .{ "STYLE", .style }, 182 }); 183 }; 184 185 pub const Control = enum { 186 auto3state, 187 autocheckbox, 188 autoradiobutton, 189 checkbox, 190 combobox, 191 control, 192 ctext, 193 defpushbutton, 194 edittext, 195 hedit, 196 iedit, 197 groupbox, 198 icon, 199 listbox, 200 ltext, 201 pushbox, 202 pushbutton, 203 radiobutton, 204 rtext, 205 scrollbar, 206 state3, 207 userbutton, 208 209 pub const map = std.StaticStringMapWithEql( 210 Control, 211 std.static_string_map.eqlAsciiIgnoreCase, 212 ).initComptime(.{ 213 .{ "AUTO3STATE", .auto3state }, 214 .{ "AUTOCHECKBOX", .autocheckbox }, 215 .{ "AUTORADIOBUTTON", .autoradiobutton }, 216 .{ "CHECKBOX", .checkbox }, 217 .{ "COMBOBOX", .combobox }, 218 .{ "CONTROL", .control }, 219 .{ "CTEXT", .ctext }, 220 .{ "DEFPUSHBUTTON", .defpushbutton }, 221 .{ "EDITTEXT", .edittext }, 222 .{ "HEDIT", .hedit }, 223 .{ "IEDIT", .iedit }, 224 .{ "GROUPBOX", .groupbox }, 225 .{ "ICON", .icon }, 226 .{ "LISTBOX", .listbox }, 227 .{ "LTEXT", .ltext }, 228 .{ "PUSHBOX", .pushbox }, 229 .{ "PUSHBUTTON", .pushbutton }, 230 .{ "RADIOBUTTON", .radiobutton }, 231 .{ "RTEXT", .rtext }, 232 .{ "SCROLLBAR", .scrollbar }, 233 .{ "STATE3", .state3 }, 234 .{ "USERBUTTON", .userbutton }, 235 }); 236 237 pub fn hasTextParam(control: Control) bool { 238 switch (control) { 239 .scrollbar, .listbox, .iedit, .hedit, .edittext, .combobox => return false, 240 else => return true, 241 } 242 } 243 }; 244 245 pub const ControlClass = struct { 246 pub const map = std.StaticStringMapWithEql( 247 res.ControlClass, 248 std.static_string_map.eqlAsciiIgnoreCase, 249 ).initComptime(.{ 250 .{ "BUTTON", .button }, 251 .{ "EDIT", .edit }, 252 .{ "STATIC", .static }, 253 .{ "LISTBOX", .listbox }, 254 .{ "SCROLLBAR", .scrollbar }, 255 .{ "COMBOBOX", .combobox }, 256 }); 257 258 /// Like `map.get` but works on WTF16 strings, for use with parsed 259 /// string literals ("BUTTON", or even "\x42UTTON") 260 pub fn fromWideString(str: []const u16) ?res.ControlClass { 261 const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; 262 return if (ascii.eqlIgnoreCaseW(str, utf16Literal("BUTTON"))) 263 .button 264 else if (ascii.eqlIgnoreCaseW(str, utf16Literal("EDIT"))) 265 .edit 266 else if (ascii.eqlIgnoreCaseW(str, utf16Literal("STATIC"))) 267 .static 268 else if (ascii.eqlIgnoreCaseW(str, utf16Literal("LISTBOX"))) 269 .listbox 270 else if (ascii.eqlIgnoreCaseW(str, utf16Literal("SCROLLBAR"))) 271 .scrollbar 272 else if (ascii.eqlIgnoreCaseW(str, utf16Literal("COMBOBOX"))) 273 .combobox 274 else 275 null; 276 } 277 }; 278 279 const ascii = struct { 280 /// Compares ASCII values case-insensitively, non-ASCII values are compared directly 281 pub fn eqlIgnoreCaseW(a: []const u16, b: []const u16) bool { 282 if (a.len != b.len) return false; 283 for (a, b) |a_c, b_c| { 284 if (a_c < 128) { 285 if (std.ascii.toLower(@intCast(a_c)) != std.ascii.toLower(@intCast(b_c))) return false; 286 } else { 287 if (a_c != b_c) return false; 288 } 289 } 290 return true; 291 } 292 }; 293 294 pub const MenuItem = enum { 295 menuitem, 296 popup, 297 298 pub const map = std.StaticStringMapWithEql( 299 MenuItem, 300 std.static_string_map.eqlAsciiIgnoreCase, 301 ).initComptime(.{ 302 .{ "MENUITEM", .menuitem }, 303 .{ "POPUP", .popup }, 304 }); 305 306 pub fn isSeparator(bytes: []const u8) bool { 307 return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR"); 308 } 309 310 pub const Option = enum { 311 checked, 312 grayed, 313 help, 314 inactive, 315 menubarbreak, 316 menubreak, 317 318 pub const map = std.StaticStringMapWithEql( 319 Option, 320 std.static_string_map.eqlAsciiIgnoreCase, 321 ).initComptime(.{ 322 .{ "CHECKED", .checked }, 323 .{ "GRAYED", .grayed }, 324 .{ "HELP", .help }, 325 .{ "INACTIVE", .inactive }, 326 .{ "MENUBARBREAK", .menubarbreak }, 327 .{ "MENUBREAK", .menubreak }, 328 }); 329 }; 330 }; 331 332 pub const ToolbarButton = enum { 333 button, 334 separator, 335 336 pub const map = std.StaticStringMapWithEql( 337 ToolbarButton, 338 std.static_string_map.eqlAsciiIgnoreCase, 339 ).initComptime(.{ 340 .{ "BUTTON", .button }, 341 .{ "SEPARATOR", .separator }, 342 }); 343 }; 344 345 pub const VersionInfo = enum { 346 file_version, 347 product_version, 348 file_flags_mask, 349 file_flags, 350 file_os, 351 file_type, 352 file_subtype, 353 354 pub const map = std.StaticStringMapWithEql( 355 VersionInfo, 356 std.static_string_map.eqlAsciiIgnoreCase, 357 ).initComptime(.{ 358 .{ "FILEVERSION", .file_version }, 359 .{ "PRODUCTVERSION", .product_version }, 360 .{ "FILEFLAGSMASK", .file_flags_mask }, 361 .{ "FILEFLAGS", .file_flags }, 362 .{ "FILEOS", .file_os }, 363 .{ "FILETYPE", .file_type }, 364 .{ "FILESUBTYPE", .file_subtype }, 365 }); 366 }; 367 368 pub const VersionBlock = enum { 369 block, 370 value, 371 372 pub const map = std.StaticStringMapWithEql( 373 VersionBlock, 374 std.static_string_map.eqlAsciiIgnoreCase, 375 ).initComptime(.{ 376 .{ "BLOCK", .block }, 377 .{ "VALUE", .value }, 378 }); 379 }; 380 381 /// Keywords that are be the first token in a statement and (if so) dictate how the rest 382 /// of the statement is parsed. 383 pub const TopLevelKeywords = enum { 384 language, 385 version, 386 characteristics, 387 stringtable, 388 389 pub const map = std.StaticStringMapWithEql( 390 TopLevelKeywords, 391 std.static_string_map.eqlAsciiIgnoreCase, 392 ).initComptime(.{ 393 .{ "LANGUAGE", .language }, 394 .{ "VERSION", .version }, 395 .{ "CHARACTERISTICS", .characteristics }, 396 .{ "STRINGTABLE", .stringtable }, 397 }); 398 }; 399 400 pub const CommonResourceAttributes = enum { 401 preload, 402 loadoncall, 403 fixed, 404 moveable, 405 discardable, 406 pure, 407 impure, 408 shared, 409 nonshared, 410 411 pub const map = std.StaticStringMapWithEql( 412 CommonResourceAttributes, 413 std.static_string_map.eqlAsciiIgnoreCase, 414 ).initComptime(.{ 415 .{ "PRELOAD", .preload }, 416 .{ "LOADONCALL", .loadoncall }, 417 .{ "FIXED", .fixed }, 418 .{ "MOVEABLE", .moveable }, 419 .{ "DISCARDABLE", .discardable }, 420 .{ "PURE", .pure }, 421 .{ "IMPURE", .impure }, 422 .{ "SHARED", .shared }, 423 .{ "NONSHARED", .nonshared }, 424 }); 425 }; 426 427 pub const AcceleratorTypeAndOptions = enum { 428 virtkey, 429 ascii, 430 noinvert, 431 alt, 432 shift, 433 control, 434 435 pub const map = std.StaticStringMapWithEql( 436 AcceleratorTypeAndOptions, 437 std.static_string_map.eqlAsciiIgnoreCase, 438 ).initComptime(.{ 439 .{ "VIRTKEY", .virtkey }, 440 .{ "ASCII", .ascii }, 441 .{ "NOINVERT", .noinvert }, 442 .{ "ALT", .alt }, 443 .{ "SHIFT", .shift }, 444 .{ "CONTROL", .control }, 445 }); 446 };