Skip to content

Commit 98e0296

Browse files
committed
--icon implementation for windows exe
1 parent 7d42923 commit 98e0296

1 file changed

Lines changed: 130 additions & 1 deletion

File tree

opend/opend.d

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ struct Commands {
299299

300300
int processIcon(string filename) {
301301
// FIXME: error if target is not windows prolly here instead of in the compiler
302+
// https://devblogs.microsoft.com/oldnewthing/20120720-00/?p=7083
302303
// gotta create a Windows resource file. this means:
303304
/+
304305
1) if it is a png, convert to ico. ico can be used directly. other formats not supported by simplifiying choice
@@ -308,7 +309,135 @@ struct Commands {
308309
3) pass this file to the linker
309310
+/
310311
//
311-
argsToKeep ~= filename ~ ".res";
312+
313+
import std.string;
314+
auto idx = lastIndexOf(filename, ".");
315+
auto ext = idx == -1 ? null : filename[idx .. $].toLower;
316+
317+
static import std.file;
318+
import arsd.png;
319+
320+
const(ubyte)[] icoBytes;
321+
ubyte imageWidth;
322+
ubyte imageHeight;
323+
switch(ext) {
324+
case ".png":
325+
icoBytes = cast(ubyte[]) std.file.read(filename);
326+
auto hdr = getHeader(readPng(icoBytes));
327+
if(hdr.width > 256 || hdr.height > 256) {
328+
import std.stdio;
329+
stderr.writeln("Error: only files 256x256 or smaller supported as exe icons");
330+
return 1;
331+
}
332+
imageWidth = cast(ubyte) hdr.width;
333+
imageHeight = cast(ubyte) hdr.height;
334+
break;
335+
default:
336+
import std.stdio;
337+
stderr.writeln("Error: only .png files supported for --icon");
338+
return 1;
339+
}
340+
341+
ubyte[] resBytes;
342+
343+
static struct ResourceHeader {
344+
align(1):
345+
uint dataSize;
346+
uint headerSize;
347+
ushort typeFirst;
348+
ushort typeSecond;
349+
ushort nameFirst;
350+
ushort nameSecond;
351+
uint dataVersion;
352+
ushort memoryFlags;
353+
ushort languageId;
354+
uint Version;
355+
uint characteristics;
356+
}
357+
static assert(ResourceHeader.sizeof == 32);
358+
359+
ResourceHeader rh;
360+
rh.dataSize = cast(int) icoBytes.length;
361+
rh.headerSize = cast(int) ResourceHeader.sizeof;
362+
rh.typeFirst = 0xffff; // indicating typeSecond is an integer instead of this being a 16 bit string
363+
rh.typeSecond = 3; // RT_ICON
364+
rh.nameFirst = 0xffff; // indicate integral
365+
rh.nameSecond = 1; // arbitrary ID number
366+
rh.dataVersion = 0;
367+
rh.memoryFlags = 0x1010; // MOVABLE (0x10) | DISCARDABLE (0x1000)
368+
rh.languageId = 0x0409; // English (United States)
369+
rh.Version = 0;
370+
rh.characteristics = 0;
371+
372+
// create the .res file header (id #1, type 0, etc)
373+
resBytes.length = 32;
374+
resBytes[0 .. 32] = 0;
375+
resBytes[4] = 0x20;
376+
resBytes[0x08] = 0xff;
377+
resBytes[0x09] = 0xff;
378+
resBytes[0x0c] = 0xff;
379+
resBytes[0x0d] = 0xff;
380+
381+
// add the rest of the data
382+
resBytes ~= cast(ubyte[]) ((&rh)[0 .. 1]);
383+
resBytes ~= icoBytes;
384+
while(resBytes.length % 4)
385+
resBytes ~= 0; // pad to DWORD boundary
386+
387+
// and now also need the group directory and entries
388+
static struct GRPICONDIR {
389+
align(1):
390+
ushort idReserved;
391+
ushort idType;
392+
ushort idCount;
393+
}
394+
static struct GRPICONDIRENTRY {
395+
align(1):
396+
ubyte bWidth;
397+
ubyte bHeight;
398+
ubyte bColorCount;
399+
ubyte bReserved;
400+
ushort wPlanes;
401+
ushort wBitCount;
402+
uint dwBytesInRes;
403+
ushort nId;
404+
}
405+
// should be 20 bytes for an icon dir with a single entry, do
406+
// a GRPICONDIR immediately followed by however many GRPICONDIRENTRYs for it
407+
408+
// here we just support one image
409+
GRPICONDIR gid;
410+
gid.idType = 1; // icon
411+
gid.idCount = 1;
412+
413+
ResourceHeader rhDir;
414+
rhDir.dataSize = cast(int) GRPICONDIR.sizeof + cast(int) GRPICONDIRENTRY.sizeof * gid.idCount;
415+
rhDir.headerSize = cast(int) ResourceHeader.sizeof;
416+
rhDir.typeFirst = 0xffff;
417+
rhDir.typeSecond = 0x0e; // RT_GROUP_ICON
418+
rhDir.nameFirst = 0xffff;
419+
rhDir.nameSecond = 1; // must be same ID number as the other header we put in
420+
rhDir.dataVersion = 0;
421+
rhDir.memoryFlags = 0x1010; // MOVABLE (0x10) | DISCARDABLE (0x1000)
422+
rhDir.languageId = 0x0409; // English (United States)
423+
rhDir.Version = 0;
424+
rhDir.characteristics = 0;
425+
426+
GRPICONDIRENTRY[1] gide;
427+
gide[0].bWidth = imageWidth;
428+
gide[0].bHeight = imageHeight;
429+
gide[0].wPlanes = 1;
430+
gide[0].dwBytesInRes = cast(int) icoBytes.length;
431+
gide[0].nId = 1; // again, same ID number as the other header for the img data
432+
433+
resBytes ~= cast(ubyte[]) ((&rhDir)[0 .. 1]);
434+
resBytes ~= cast(ubyte[]) ((&gid)[0 .. 1]);
435+
resBytes ~= cast(ubyte[]) gide[];
436+
437+
438+
auto resName = filename ~ ".res";
439+
std.file.write(resName, resBytes);
440+
argsToKeep ~= resName;
312441
return 0;
313442
}
314443

0 commit comments

Comments
 (0)