Programmatically add cursor resource to Exe/Dll with UpdateResource api?

Get help with using AutoHotkey and its commands and hotkeys
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Programmatically add cursor resource to Exe/Dll with UpdateResource api?

30 Jun 2015, 22:51

Taking the function ReplaceIcon() (or ReplaceAhkIcon(), care of fincs and Pulover) I'm trying to create a comparable ReplaceCursor() to use UpdateResource API to add cursors to exe (or dll)

Any help greatly appreciated:

Code: Select all

ReplaceCursor(re, CurFile, cursorID)
{
f := FileOpen(CurFile, "r")
if !IsObject(f)
return false
VarSetCapacity(igh, 8), f.RawRead(igh, 6)
if NumGet(igh, 0, "UShort") != 0 || NumGet(igh, 2, "UShort") != 2 ;2 for cursor
return false
wCount := NumGet(igh, 4, "UShort")
, VarSetCapacity(rsrcCursorGroup, rsrcCursorGroupSize := 6 + wCount*14)
, NumPut(NumGet(igh, "Int64"), rsrcCursorGroup, "Int64") ; fast copy
, ige := &rsrcCursorGroup + 6
Loop, %wCount%
{
thisID := (cursorID-1) + A_Index
, f.RawRead(ige+0, 12) ; read all but the offset
, NumPut(thisID, ige+12, "UShort")
, imgOffset := f.ReadUInt()
, oldPos := f.Pos
, f.Pos := imgOffset
, VarSetCapacity(cursorData, cursorDataSize := NumGet(ige+8, "UInt"))
, f.RawRead(cursorData, cursorDataSize)
, f.Pos := oldPos
, DllCall("UpdateResource", "ptr", re, "ptr", 1, "ptr", thisID, "ushort", 0x409, "ptr", &cursorData, "uint", cursorDataSize, "uint") ; 1 for RT_CURSOR
, ige += 14
} ; 12 for RT_GOUP_CURSOR
return thisID + 1, DllCall("UpdateResource", "ptr", re, "ptr", 12, "ptr", cursorID, "ushort", 0x409, "ptr", &rsrcCursorGroup, "uint", rsrcCursorGroupSize, "uint")
}
called with something like:

Code: Select all

 module := DllCall("BeginUpdateResource", "str", ExeName, "uint", 0, "ptr")
 Loop, Cursors\*.cur
 {
  SplitPath, A_LoopFileFullPath,,,,CursorName
  ReplaceCursor(module, A_LoopFileFullPath, CursorName)
 }
 DllCall("EndUpdateResource", "ptr", module, "uint", 0)
The equivalent ReplaceIcon() works as expected, i'm assuming i'm missing a size difference but can't figure it out
Thanks in advance
- gwarble

Edit: the changes from working function for icons include RT_CURSOR = 1, RT_GROUP_CURSOR = 12, the second byte checked for 2 for cursor instead of 1 for icon... What i can't figure out is there are 4 bytes ahead of the BITMAPINFOHEADER that are the X and Y hotspots of the cursor i need to compensate for...

Edit: some details here: http://www.skynet.ie/~caolan/publink/wi ... resfmt.txt
Cursor resources are very much like icon resources. They are formed
in groups with the components preceding the header. This header also
employs a fixed-length component index that allows random access of
the individual components. The structure of the cursor header is as
follows:

[Resource header (type = 12)]

struct CursorHeader {
WORD wReserved; // Currently zero
WORD wType; // 2 for cursors
WORD cwCount; // Number of components
WORD padding; // filler for DWORD alignment
};

The next portion is repeated for each component resource, and starts
on a DWORD boundary.

struct ResourceDirectory {
WORD wWidth;
WORD wHeight;
WORD wPlanes;
WORD wBitCount;
DWORD lBytesInRes;
WORD wNameOrdinal; // Points to component
WORD padding; // filler for DWORD alignment
};

Each cursor component is also similar to each icon component. There
is, however, one significant difference between the two: cursors
have the concept of a `hotspot' where icons do not. Here is the
component structure:

[Resource header (Type = 1)]

struct CursorComponent {
short xHotspot;
short yHotspot;
}
[Monochrome XOR mask]
[Monochrome AND mask]

These masks are bitmaps copied from the .CUR file. The main
difference from icons in this regard is that cursors do not have
color DIBs used for XOR masks like cursors. Although the bitmaps are
monochrome and do not have DIB headers or color tables, the bits are
still in DIB format with respect to alignment and direction. See the
SDK Reference for more information on DIB formats.
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

08 Jul 2015, 12:21

Any of you experts have any words of wisdom or advice on what i should be researching to make sense of this?

One or more RESDIR structures immediately follow the NEWHEADER structure in the .res file. The ResCount member of the NEWHEADER structure specifies the number of RESDIR structures. Note that the RESDIR structure consists of either an ICONRESDIR structure or a CURSORDIR structure followed by the Planes, BitCount, BytesInRes, and IconCursorId members. If the RESDIR structure contains information about a cursor, the first two WORDs the resource compiler writes to the RT_CURSOR resource are the xHotSpot and yHotSpot members of the LOCALHEADER structure.
Edit: see last post for structure definitions instead
Last edited by gwarble on 08 Sep 2016, 21:39, edited 1 time in total.
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

25 Jan 2016, 17:35

bumping in the hopes of some help, I still haven't figured this out
thanks
- joel
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

01 Aug 2016, 16:38

this is my "tried for a year and failed" bump, anyone have any suggestions on what I should look in to?

i'd love to build my EitherMouse executable programmatically with new versions of ahk rather than mess around with Resource Hacker, but can't figure out how to add the cursors!

thanks
- joel
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

08 Sep 2016, 20:10

I'm just going to add as much info as I can in the hopes someone knows what I'm doing wrong... I'll also edit out a couple bits of misinformation above (re: differing struct sizes between ico and cur)

So far, I'm keeping it integer based for simplicity, once working it should be trivial to change to named resources
I have the cursor resources enumerated, next available integer used for the cursor (or multiple images if the cursor file has them)
I get the Cursor Group resource created, pointing to the correct cursor image resources (ordinal number: in ResHacker)

but, I can't get the cursor size (width and height) to come out right in the Group resource, usually I'm getting 8224x8224 instead of 32x32 and a color depth of 1, and I think its because of the difference between cursors and icons seen here but I can't make sense of what to do about the difference (since it appears the file format and resource format both have the difference):

Code: Select all

struct IconHeader {          struct CursorHeader {
  WORD   wReserved;            WORD   wReserved;          ;// Currently zero
  WORD   wType;                WORD   wType;              ;// 1 for icons, 2 for cursors
  WORD   wCount;               WORD   cwCount;            ;// Number of components
  WORD   padding;              WORD   padding;            ;// filler for DWORD alignment
  };                           };
   								
struct IconResDir {          struct CursorResDir {
  BYTE   bWidth;               WORD   wWidth;             ;// NOTE DIFFERENCE BETWEEN
  BYTE   bHeight;                                         ;// CURSOR AND ICON WIDTH
  BYTE   bColorCount;          WORD   wHeight;            ;// AND HEIGHT COMPONENTS
  BYTE   bReserved;            
  WORD   wPlanes;              WORD   wPlanes;
  WORD   wBitCount;            WORD   wBitCount;
  DWORD  lBytesInRes;          DWORD  lBytesInRes;
  WORD   wNameOrdinal;         WORD   wNameOrdinal;       ;// Points to component
  WORD   padding;              WORD   padding;            ;// filler for DWORD alignment
  };                           };
also the cursor resource itself has the right size of 32x32 but no image...

Here's a sample script if you care to try:

Code: Select all

 Exe = Resource_AddCursor_Test.exe
 Cur = Resource_AddCursor_Test.cur

 SetWorkingDir %A_ScriptDir%
 SplitPath, A_AhkPath,, A_AhkPath_
 FileCopy, %A_AhkPath_%\Compiler\Unicode 32-bit.bin, %Exe%, 1

 IfNotExist, %Cur%
  FileCopy, %A_WinDir%\Cursors\arrow_l.cur, %Cur%

 module := DllCall("BeginUpdateResource", "str", Exe, "uint", 0, "ptr")
;Resource_AddIcon(module, Cur, Exe, 159) ;this works fine
 Resource_AddCursor(module, Cur, Exe, 121)
 DllCall("EndUpdateResource", "ptr", module, "uint", 0)
;MsgBox, % Resource_String_Read(DllCall("LoadLibrary", "Str", "Test.exe"),1)

ExitApp

Resource_AddCursor(re, CurFile, ExeFile, CurID) {
	global _EnumCursorNames_HighestCurID

	ids := Resource_Cursor_EnumNames(ExeFile, CurID)
	if !IsObject(ids)
	 Return false
	f := FileOpen(CurFile, "r")
	if !IsObject(f)
	 Return false
	VarSetCapacity(igh, 8), f.RawRead(igh, 6)
	if NumGet(igh, 0, "UShort") != 0 || NumGet(igh, 2, "UShort") != 2 ;2 for cursors
	 Return false
	
	wCount := NumGet(igh, 4, "UShort")
	VarSetCapacity(rsrcCurGroup, rsrcCurGroupSize := 6 + wCount*14)  ;not 16??
	NumPut(NumGet(igh, "Int64"), rsrcCurGroup, "Int64") ; fast copy?
	ige := &rsrcCurGroup + 6
	Loop, % ids._MaxIndex() ; Delete all the images
	 DllCall("UpdateResource", "ptr", re, "ptr", RT_CURSOR:=1, "ptr", ids[A_Index], "ushort", 0x409, "ptr", 0, "uint", 0, "uint")
	Loop, %wCount%
	{
		thisID := ids[A_Index]
		if !thisID
		 thisID := ++ _EnumCursorNames_HighestCurID
		f.RawRead(ige+0, 12) ; read all but the offset
		NumPut(thisID, ige+12, "UShort")
		imgOffset := f.ReadUInt()
		oldPos := f.Pos
		f.Pos := imgOffset
		VarSetCapacity(CurData, CurDataSize := NumGet(ige+8, "UInt"))
		f.RawRead(CurData, CurDataSize)
		f.Pos := oldPos
		DllCall("UpdateResource", "ptr", re, "ptr", RT_CURSOR:=1, "ptr", thisID, "ushort", 0x409, "ptr", &CurData, "uint", CurDataSize, "uint")
		ige += 14  ;not 16??
	}
	DllCall("UpdateResource", "ptr", re, "ptr", RT_GROUP_CURSOR:=12, "ptr", CurID, "ushort", 0x409, "ptr", &rsrcCurGroup, "uint", rsrcCurGroupSize, "uint")
	Return True
}

Resource_Cursor_EnumNames(ExeFile, CurID) {
	global _EnumCursorNames_HighestCurID
	static pEnumFunc := RegisterCallback("Resource_Cursor_EnumNames_Callback")
	If !hModule := DllCall("LoadLibraryEx", "str", ExeFile, "ptr", 0, "ptr", 2, "ptr")
		Return
	_EnumCursorNames_HighestCurID := 0
	DllCall("EnumResourceNames",     "ptr", hModule, "ptr", RT_CURSOR:=1, "ptr", pEnumFunc, "uint", 0)
	hRsrc := DllCall("FindResource", "ptr", hModule, "ptr", CurID,        "ptr", RT_GROUP_CURSOR:=12, "ptr")
	hMem  := DllCall("LoadResource", "ptr", hModule, "ptr", hRsrc,        "ptr")
	pDirHeader := DllCall("LockResource", "ptr", hMem, "ptr")
	pResDir := pDirHeader + 6
	wCount := NumGet(pDirHeader+4, "UShort")
	CurIDs := []
	Loop, %wCount% {
		pResDirEntry := pResDir + (A_Index-1)*14  ;not 16??
		CurIDs[A_Index] := NumGet(pResDirEntry+12, "UShort")
	}
	DllCall("FreeLibrary", "ptr", hModule)
	Return CurIDs
}

Resource_Cursor_EnumNames_Callback(hModule, type, name, lParam) {
	global _EnumCursorNames_HighestCurID
	_EnumCursorNames_HighestCurID := ( ( name > _EnumCursorNames_HighestCurID ) ? name : _EnumCursorNames_HighestCurID )
	Return True
}
i still need to figure out how to prepend the bitmap data with the 4 bytes for hotspot x and y... and maybe i need to recreate the resource header from the data in the file format header as I'm reading now they may be different
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

14 Dec 2016, 14:03

Code: Select all

typedef struct {
  WORD   wReserved;          // Currently zero
  WORD   wType;              // 2 for cursors
  WORD   cwCount;            // Number of components
//Fout in docs, geen padding
//  WORD   padding;            // filler for DWORD alignment
} CursorHeader;

typedef struct {
  WORD   wWidth;
  WORD   wHeight;
  WORD   wPlanes;
  WORD   wBitCount;
  DWORD  lBytesInRes;
  WORD   wNameOrdinal;       // Points to component
//Fout in docs, geen padding
//  WORD   padding;            // filler for DWORD alignment
} CursorResDir;

typedef struct {
  short  xHotspot;
  short  yHotspot;
}  CursorComponent;

Code: Select all

typedef struct
{
        DWORD        bV4Size;
        LONG         bV4Width;
        LONG         bV4Height;
        WORD         bV4Planes;
        WORD         bV4BitCount;
        DWORD        bV4V4Compression;
        DWORD        bV4SizeImage;
        LONG         bV4XPelsPerMeter;
        LONG         bV4YPelsPerMeter;
        DWORD        bV4ClrUsed;
        DWORD        bV4ClrImportant;
        DWORD        bV4RedMask;
        DWORD        bV4GreenMask;
        DWORD        bV4BlueMask;
        DWORD        bV4AlphaMask;
        DWORD        bV4CSType;
        CIEXYZTRIPLE bV4Endpoints;
        DWORD        bV4GammaRed;
        DWORD        bV4GammaGreen;
        DWORD        bV4GammaBlue;
} BITMAPV4HEADER,       /*FAR *LPBITMAPV4HEADER,*/ *PBITMAPV4HEADER;

/* constants for the biCompression field */
#define BI_RGB        0L
#define BI_RLE8       1L
#define BI_RLE4       2L
#define BI_BITFIELDS  3L

typedef struct {
  WORD   wReserved;          // Currently zero
  WORD   wType;              // 1 for icons
  WORD   wCount;             // Number of components
} IconHeader;

typedef struct tagWINBITMAPINFOHEADER{
        DWORD      biSize;
        LONG       biWidth;
        LONG       biHeight;
        WORD       biPlanes;
        WORD       biBitCount;
        DWORD      biCompression;
        DWORD      biSizeImage;
        LONG       biXPelsPerMeter;
        LONG       biYPelsPerMeter;
        DWORD      biClrUsed;
        DWORD      biClrImportant;
} WINBITMAPINFOHEADER;

typedef struct
{
   BYTE                      blue;
   BYTE                      green;
   BYTE                      red;
   BYTE                      res;
}  RGBQUAD;

//The next portion is repeated for each component resource:
typedef struct {
  BYTE   bWidth;
  BYTE   bHeight;
  BYTE   bColorCount;
  BYTE   bReserved;
  WORD   wPlanes;
  WORD   wBitCount;
  DWORD  lBytesInRes;
  WORD   wNameOrdinal;       // Points to component
} ResourceDirectory;

typedef struct
{
	BYTE bWidth;
	BYTE bHeight;
	BYTE bColorCount;
	BYTE bReserved;
	WORD wPlanes;
	WORD wBitCount;
	DWORD dwBytesInRes;
	DWORD dwImageOffset;
} ICONDIRENTRY;

typedef struct
{
	WORD idReserved;
	WORD idType;
	WORD idCount;
} ICONDIR;
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

24 Jul 2017, 19:12

Bumping this up in case any new viewers can help me out with this... I think most if not all resource types can now be read to and written from Exe's and Dll's with AHK except cursors. I would greatly appreciate any advice, tips, solutions, links, etc to help me understand what I'm missing to accomplish this.
Thanks in advance,
- joel
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
Helgef
Posts: 3299
Joined: 17 Jul 2016, 01:02
Contact:

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

25 Jul 2017, 07:51

Edit: I'm talking about the code in the second codebox in this post :wave:
Hello gwarble, I have minimum contribution. However, browsing the code i noticed

Code: Select all

Loop, % ids._MaxIndex()
I thought it was an error, the docs says MaxIndex(), not _MaxIndex, but, it seems both are ok. Then I also noticed that ids is always emtpy, at least here on 64 bit 1.1.26.00. Maybe it doesn't matter. I don't know anything about this.
Checking a in Resource_Cursor_EnumNames() I notice that DllCall("EnumResourceNames", "ptr", hModule, "ptr", RT_CURSOR:=1, "ptr", pEnumFunc, "uint", 0) fails and sets the LastError to:

Code: Select all

ERROR_RESOURCE_TYPE_NOT_FOUND
1813 (0x715)
The specified resource type cannot be found in the image file.
Good luck.
Spoiler
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

25 Jul 2017, 14:22

Sweet, thanks Helgef!
You're right, it seems I'm not enumerating them properly and MaxIndex() is always an empty string... hmm
I will experiment further with this bit of info, thanks
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

25 Jul 2017, 17:45

Alright this seems to get rid of the ERROR_RESOURCE_TYPE_NOT_FOUND error, but my MaxIndex is still 0...
It does successfully add the Cursor resource item(s) depending on how many are in the .cur file, and adds the Cursor Group resource item, but the data is wrong for the color depth/resolution:

Code: Select all

12336 x 12336 (4 colors) - Ordinal name: 0
0 x 0 (1 colors) - Ordinal name: 0
while it should be

Code: Select all

32 x 32 (2 colors) - Ordinal name: 27
48 x 48 (2 colors) - Ordinal name: 28

Code: Select all

SetWorkingDir %A_ScriptDir%

 SplitPath, A_AhkPath,, A_AhkPath_
 Exe = Test.exe
 Cur = Test.cur

 IfNotExist, %Cur%
  FileCopy, %A_WinDir%\Cursors\arrow_l.cur, %Cur%

 IfNotExist, %Exe%
  FileCopy, %A_AhkPath_%\Compiler\Unicode 32-bit.bin, %Exe%, 1

 module := DllCall("BeginUpdateResource", "str", Exe, "uint", 0, "ptr")
 Resource_Cursor_Add(module, "Test.cur", Exe, 121)
 DllCall("EndUpdateResource", "ptr", module, "uint", 0)

ExitApp

Resource_Cursor_Add(re, CurFile, ExeFile, CurID) {
	global _EnumCursorNames_HighestCurID

	ids := Resource_Cursor_EnumNames(ExeFile, CurID)
	if !IsObject(ids)
	 Return false
	f := FileOpen(CurFile, "r")
	if !IsObject(f)
	 Return false
	VarSetCapacity(igh, 8), f.RawRead(igh, 6)
	if NumGet(igh, 0, "UShort") != 0 || NumGet(igh, 2, "UShort") != 2 ;2 for cursors
	 Return false
	
	wCount := NumGet(igh, 4, "UShort")
	VarSetCapacity(rsrcCurGroup, rsrcCurGroupSize := 6 + wCount*16)
	NumPut(NumGet(igh, "Int64"), rsrcCurGroup, "Int64") ; fast copy?
	ige := &rsrcCurGroup + 6
	Loop, % ids._MaxIndex() ; Delete all the images
	 DllCall("UpdateResource", "ptr", re, "ptr", RT_CURSOR:=1, "ptr", ids[A_Index], "ushort", 0x409, "ptr", 0, "uint", 0, "uint")
	Loop, %wCount%
	{
		thisID := ids[A_Index]
		if !thisID
		 thisID := ++ _EnumCursorNames_HighestCurID
		f.RawRead(ige+0, 12) ; read all but the offset
		NumPut(thisID, ige+8, "UShort")
		imgOffset := f.ReadUInt()
		oldPos := f.Pos
		f.Pos := imgOffset
		VarSetCapacity(CurData, CurDataSize := NumGet(ige+8, "UInt"))
		f.RawRead(CurData, CurDataSize)
		f.Pos := oldPos
		DllCall("UpdateResource", "ptr", re, "ptr", RT_CURSOR:=1, "ptr", thisID, "ushort", 0x409, "ptr", &CurData, "uint", CurDataSize, "uint")
		ige += 16
	}
	DllCall("UpdateResource", "ptr", re, "ptr", RT_GROUP_CURSOR:=12, "ptr", CurID, "ushort", 0x409, "ptr", &rsrcCurGroup, "uint", rsrcCurGroupSize, "uint")
	Return True
}

Resource_Cursor_EnumNames(ExeFile, CurID) {
	global _EnumCursorNames_HighestCurID
	static pEnumFunc := RegisterCallback("Resource_Cursor_EnumNames_Callback")
	If !hModule := DllCall("LoadLibraryEx", "str", ExeFile, "ptr", 0, "ptr", 2, "ptr")
		Return
	_EnumCursorNames_HighestCurID := 0
	DllCall("EnumResourceNames",     "ptr", hModule, "ptr", RT_CURSOR:=1, "ptr", pEnumFunc, "uint", 0)
	hRsrc := DllCall("FindResource", "ptr", hModule, "ptr", CurID,        "ptr", RT_GROUP_CURSOR:=12, "ptr")
	hMem  := DllCall("LoadResource", "ptr", hModule, "ptr", hRsrc,        "ptr")
	pDirHeader := DllCall("LockResource", "ptr", hMem, "ptr")
	pResDir := pDirHeader + 6
	wCount := NumGet(pDirHeader+4, "UShort")
	CurIDs := []
	Loop, %wCount% {
		pResDirEntry := pResDir + (A_Index-1)*16
		CurIDs[A_Index] := NumGet(pResDirEntry+12, "UShort")
	}
	DllCall("FreeLibrary", "ptr", hModule)
	Return CurIDs
}

Resource_Cursor_EnumNames_Callback(hModule, type, name, lParam) {
	global _EnumCursorNames_HighestCurID
	_EnumCursorNames_HighestCurID := ( ( name > _EnumCursorNames_HighestCurID ) ? name : _EnumCursorNames_HighestCurID )
	Return True
}

Here is a useful program to compile with a cursor resource so running it shows the cursor:

Code: Select all

#NoEnv
#NoTrayIcon
SetWorkingDir, %A_ScriptDir%
SetBatchLines, -1
Compile()
OnExit, Exit

 SystemCursor("BLUE") ; cursor group resource name is BLUE

Return

Exit:
 RestoreCursors()
ExitApp

SystemCursor(Cursor) {
 str := Cursor
 hModule := DllCall("GetModuleHandle", Ptr, 0)
 hArrow  := DllCall( "LoadCursor", Uint,hModule, Int, &str )
 hCursor := DllCall("CopyImage",uint, hArrow, uint,2,int,0,int,0,uint,0)
 DllCall("SetSystemCursor",Uint,hCursor,Int,32512) ;normal arrow cursor
Return
}

RestoreCursors() {
  Return DllCall("SystemParametersInfo",UInt,0x57,UInt,0,UInt,0,UInt,0)
}

Esc::ExitApp
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

27 Jul 2017, 17:21

gwarble wrote:It does successfully add the Cursor resource item(s) depending on how many are in the .cur file, and adds the Cursor Group resource item, but the data is wrong for the color depth/resolution
After reading the useful Wikipedia page on the ico/cur file format and the code of this project, I wrote an AutoHotkey script that does appear to successfully add cursor resources to a given exe file, but it doesn't do the deletion or enumeration etc. of what the code you posted was doing. But I also didn't understand what was going on there, either, so there's that.

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SetWorkingDir %A_ScriptDir%

SplitPath, A_AhkPath,, AhkDir
Exe := "Test.exe"
Cur := "Test.cur"

if (!FileExist(Cur))
	FileCopy, %A_WinDir%\Cursors\arrow_l.cur, %Cur%

if (!FileExist(Exe))
	FileCopy, %AhkDir%\Compiler\Unicode 32-bit.bin, %Exe%

if ((module := DllCall("BeginUpdateResource", "Str", Exe, "Int", False, "Ptr"))) {
	Resource_Cursor_Add(module, Cur, Exe, 121)
	DllCall("EndUpdateResource", "Ptr", module, "Int", False)
}

ExitApp

Resource_Cursor_Add(module, CurFileName, ExeFile, CurID) {
	static RT_CURSOR := 1, RT_GROUP_CURSOR := 12, langId := 0x0, cbICONDIR := 6, cbRT_CUR_ENTRY := 14

	f := FileOpen(CurFileName, "r")
	if (!IsObject(f))
		return False

	ret := False

	VarSetCapacity(ICONDIR, cbICONDIR, 0), f.RawRead(ICONDIR, cbICONDIR)
	if (NumGet(ICONDIR, 0, "UShort") != 0 || NumGet(ICONDIR, 2, "UShort") != 2) ; wReserved sanity check. Check type - it should be 2 for cursors
		goto Resource_Cursor_Add_end
	wCount := NumGet(ICONDIR, 4, "UShort")
	if (!wCount)
		goto Resource_Cursor_Add_end

	VarSetCapacity(RT_CUR_ENTRY, (rsrcCurGroupSize := cbICONDIR + (cbRT_CUR_ENTRY * wCount)))
	DllCall("RtlMoveMemory", "Ptr", &RT_CUR_ENTRY, "Ptr", &ICONDIR, "Ptr", cbICONDIR)

	f.Pos := cbICONDIR ; move onto the first ICONDIRENTRY structure
	Loop %wCount%
	{
		wID := A_Index

		; Get cursor details from cursor file's header
		bWidth := f.ReadUChar()
		bHeight := f.ReadUChar()
		bColorCount := f.ReadUChar()
		f.Pos += 1 ; skip bReserved
		wHotspotX := f.ReadUShort()
		wHotspotY := f.ReadUShort()
		dwSize := f.ReadUInt()
		dwOffset := f.ReadUInt()

		currOffset := cbICONDIR + (cbRT_CUR_ENTRY * (A_Index - 1))
		;f.RawRead(&RT_CUR_ENTRY+currOffset, cbRT_CUR_ENTRY)
		NumPut(bWidth, RT_CUR_ENTRY, currOffset, "UShort")
		NumPut(bHeight * 2, RT_CUR_ENTRY, currOffset + 2, "UShort")
		NumPut(1, RT_CUR_ENTRY, currOffset + 4, "UShort") ; wPlanes must be 1 apparently
		NumPut(bColorCount >> 1, RT_CUR_ENTRY, currOffset + 6, "UShort") ; really not sure if this is the right thing to do to set wBitCount
		NumPut(wID, RT_CUR_ENTRY, currOffset + 12, "UShort")

		; Apparently when adding a cursor resource, its hotspot structure is meant to precede the cursor bitmap
		dwSize += 4
		NumPut(dwSize, RT_CUR_ENTRY, currOffset + 8, "UInt")
		VarSetCapacity(curData, dwSize)
		NumPut(wHotspotX, curData, 0, "UShort")
		NumPut(wHotspotY, curData, 2, "UShort")

		; Move file pointer to cursor bitmap offset and read it into the memory chunk for UpdateResource
		curPos := f.Pos
		f.Pos := dwOffset
		f.RawRead(&curData+4, dwSize-4)
		f.Pos := curPos
		DllCall("UpdateResource", "Ptr", module, "Ptr", RT_CURSOR, "Ptr", wID, "UShort", langId, "Ptr", &curData, "UInt", dwSize)		
	}
	ret := DllCall("UpdateResource", "Ptr", module, "Ptr", RT_GROUP_CURSOR, "Ptr", CurID, "UShort", langId, "Ptr", &RT_CUR_ENTRY, "UInt", rsrcCurGroupSize)

Resource_Cursor_Add_end:
	f.Close(), f := ""
	return ret
}
After changing Test.ahk to load the cursor from a numbered resource, compiling it, running the script above to add the cursor resources and then running Test.exe, my cursor did change:

Code: Select all

#NoEnv
#NoTrayIcon
SetWorkingDir, %A_ScriptDir%
SetBatchLines, -1
OnExit, Exit

 SystemCursor(121) ; cursor group resource name is BLUE

Return

Exit:
 RestoreCursors()
ExitApp

SystemCursor(Cursor) {
 str := Cursor
 hModule := DllCall("GetModuleHandle", Ptr, 0)
 hArrow  := DllCall( "LoadCursor", Uint,hModule, Ptr, str )
 hCursor := DllCall("CopyImage",uint, hArrow, uint,2,int,0,int,0,uint,0)
 DllCall("SetSystemCursor",Uint,hCursor,Int,32512) ;normal arrow cursor
Return
}

RestoreCursors() {
  Return DllCall("SystemParametersInfo",UInt,0x57,UInt,0,UInt,0,UInt,0)
}

Esc::ExitApp
Last edited by qwerty12 on 28 Jul 2017, 09:47, edited 4 times in total.
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

27 Jul 2017, 18:21

nice!

thanks for the links and code example... it was successful on my end as well...
It also made apparent a bug in ResHack, as inspecting the data after adding it reports as a 1 color cursor... and the previous ones i've added in ResHack itself say 2 colors, even though the cursor is really 32 bit (True color with alpha, so is that 16.8mil colors?) Icons report correctly in ResHack but not cursors... anyway long story short that at least tells me that reshack is not a good method to check if the data is being added correctly for this bit. The resolution and ordinal number do report correctly so you definitely fixed something.

Since the enumeration part is working above, I'm hoping that after studying your code and links I'll be able to merge the two and make some more progress! Thanks again
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
qwerty12
Posts: 468
Joined: 04 Mar 2016, 04:33
GitHub: qwerty12

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

27 Jul 2017, 19:39

gwarble wrote:thanks for the links and code example... it was successful on my end as well...
No problem, and good to hear :-)

I made a mistake in the function: f.RawRead(&curData+4, dwSize) should be f.RawRead(&curData+4, dwSize-4) :oops:
It also made apparent a bug in ResHack, as inspecting the data after adding it reports as a 1 color cursor... and the previous ones i've added in ResHack itself say 2 colors, even though the cursor is really 32 bit (True color with alpha, so is that 16.8mil colors?) Icons report correctly in ResHack but not cursors... anyway long story short that at least tells me that reshack is not a good method to check if the data is being added correctly for this bit. The resolution and ordinal number do report correctly so you definitely fixed something.
I might be off with the colours - while XN Resource Editor says the added cursors are all 2 colour (which is what bColorCount taken straight from the cursor file says for all of them), Resource Hacker says the added cursors are 4 colour. I'm not sure which one to believe. Though, odds are, I've messed up somewhere. If I figure it out, I'll come back :-)

I edited my post above and addressed three things:
  • Apparently, wPlanes must always be 1
  • The language ID was wrong - it should be 0 to reflect the cursors' language neutrality
  • I managed to get both XN Resource Editor and Resource Hacker to show 2 colours for the cursors, but I have no idea if wBitCount was calculated correctly to do this. I don't know anything about the bitmap file format
Good luck!
Last edited by qwerty12 on 27 Jul 2017, 21:11, edited 2 times in total.
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

27 Jul 2017, 19:45

Totally kludged together and its working... after two years i have a way (proof of concept at least) to build new EitherMouse binaries on new releases of AHK without using ResourceHacker! Very cool

Still need to make sense of it all to wrap it into a nice library for general PE cursor resource management, deletions, etc but thanks guys for the help getting it functioning!

The MaxIndex problem i think is related to named vs integer resources, still investigating, but the enumeration is working for the individual cursor resources, and named groups works for me

Other than that i was clearly not managing the bytes properly

Code: Select all

#NoEnv  ; Recommended for performance and compatibility with future AutoHotkey releases.
SetWorkingDir %A_ScriptDir%

SplitPath, A_AhkPath,, AhkDir
Exe := "Test.exe"
Cur := "Test.cur"

if (!FileExist(Cur))
	FileCopy, %A_WinDir%\Cursors\arrow_l.cur, %Cur%

if (!FileExist(Exe))
	FileCopy, %AhkDir%\Compiler\Unicode 32-bit.bin, %Exe%

if ((module := DllCall("BeginUpdateResource", "Str", Exe, "Int", False, "Ptr"))) {
	Resource_Cursor_Add(module, Cur, Exe, "BLUE")
	DllCall("EndUpdateResource", "Ptr", module, "Int", False)
}

ExitApp

Resource_Cursor_Add(module, CurFileName, ExeFile, CurID) {
	global _EnumCursorNames_HighestCurID

	static RT_CURSOR := 1, RT_GROUP_CURSOR := 12, langId := 0x409, cbICONDIR := 6, cbRT_CUR_ENTRY := 14

	f := FileOpen(CurFileName, "r")
	if (!IsObject(f))
		return False

	ids := Resource_Cursor_EnumNames(ExeFile, CurID)
	if !IsObject(ids)
	 Return false


	ret := False

	VarSetCapacity(ICONDIR, cbICONDIR, 0), f.RawRead(ICONDIR, cbICONDIR)
	if (NumGet(ICONDIR, 0, "UShort") != 0 || NumGet(ICONDIR, 2, "UShort") != 2) ; wReserved sanity check. Check type - it should be 2 for cursors
		goto Resource_Cursor_Add_end
	wCount := NumGet(ICONDIR, 4, "UShort")
	if (!wCount)
		goto Resource_Cursor_Add_end

	VarSetCapacity(RT_CUR_ENTRY, (rsrcCurGroupSize := cbICONDIR + (cbRT_CUR_ENTRY * wCount)))
	DllCall("RtlMoveMemory", "Ptr", &RT_CUR_ENTRY, "Ptr", &ICONDIR, "Ptr", cbICONDIR)

	f.Pos := cbICONDIR ; move onto the first ICONDIRENTRY structure

	Loop %wCount%
	{
		wID := A_Index + _EnumCursorNames_HighestCurID

		; Get cursor details from cursor file's header
		bWidth := f.ReadUChar()
		bHeight := f.ReadUChar()
		bColorCount := f.ReadUChar()
		f.Pos += 1 ; skip bReserved
		wHotspotX := f.ReadUShort()
		wHotspotY := f.ReadUShort()
		dwSize := f.ReadUInt()
		dwOffset := f.ReadUInt()

		currOffset := cbICONDIR + (cbRT_CUR_ENTRY * (A_Index - 1))
		;f.RawRead(&RT_CUR_ENTRY+currOffset, cbRT_CUR_ENTRY)
		NumPut(bWidth, RT_CUR_ENTRY, currOffset, "UShort")
		NumPut(bHeight * 2, RT_CUR_ENTRY, currOffset + 2, "UShort")
		; Note: I haven't a clue what wPlanes should be set to. But it seems OK for me to leave it out. I think:
		NumPut(0, RT_CUR_ENTRY, currOffset + 4, "UShort")
		NumPut(bColorCount, RT_CUR_ENTRY, currOffset + 6, "UShort")
		NumPut(wID, RT_CUR_ENTRY, currOffset + 12, "UShort")

		; Apparently when adding a cursor resource, its hotspot structure is meant to precede the cursor bitmap
		dwSize += 4
		NumPut(dwSize, RT_CUR_ENTRY, currOffset + 8, "UInt")
		VarSetCapacity(curData, dwSize)
		NumPut(wHotspotX, curData, 0, "UShort")
		NumPut(wHotspotY, curData, 2, "UShort")

		; Move file pointer to cursor bitmap offset and read it into the memory chunk for UpdateResource
		curPos := f.Pos
		f.Pos := dwOffset
		f.RawRead(&curData+4, dwSize)
		f.Pos := curPos
		DllCall("UpdateResource", "Ptr", module, "Ptr", RT_CURSOR, "Ptr", wID, "UShort", langId, "Ptr", &curData, "UInt", dwSize)		
	}
	ret := DllCall("UpdateResource", "Ptr", module, "Ptr", RT_GROUP_CURSOR, "str", CurID, "UShort", langId, "Ptr", &RT_CUR_ENTRY, "UInt", rsrcCurGroupSize)

Resource_Cursor_Add_end:
	f.Close(), f := ""
	return ret
}



Resource_Cursor_EnumNames(ExeFile, CurID) {
	global _EnumCursorNames_HighestCurID
	static pEnumFunc := RegisterCallback("Resource_Cursor_EnumNames_Callback")
	If !hModule := DllCall("LoadLibraryEx", "str", ExeFile, "ptr", 0, "ptr", 2, "ptr")
		Return
	_EnumCursorNames_HighestCurID := 0
	DllCall("EnumResourceNames",     "ptr", hModule, "ptr", RT_CURSOR:=1, "ptr", pEnumFunc, "uint", 0)
	hRsrc := DllCall("FindResource", "ptr", hModule, "str", CurID,        "ptr", RT_GROUP_CURSOR:=12, "ptr")
	hMem  := DllCall("LoadResource", "ptr", hModule, "ptr", hRsrc,        "ptr")
	pDirHeader := DllCall("LockResource", "ptr", hMem, "ptr")
	pResDir := pDirHeader + 6
	wCount := NumGet(pDirHeader+4, "UShort")
	CurIDs := []

	Loop, %wCount% {
		pResDirEntry := pResDir + (A_Index-1)*16
		CurIDs[A_Index] := NumGet(pResDirEntry+12, "UShort")
	}
	DllCall("FreeLibrary", "ptr", hModule)
	Return CurIDs
}

Resource_Cursor_EnumNames_Callback(hModule, type, name, lParam) {
	global _EnumCursorNames_HighestCurID
	_EnumCursorNames_HighestCurID := ( ( name > _EnumCursorNames_HighestCurID ) ? name : _EnumCursorNames_HighestCurID )
 	Return True
}
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .
gwarble
Posts: 283
Joined: 30 Sep 2013, 15:01

Re: Programmatically add cursor resource to Exe/Dll with UpdateResource api?

28 Jul 2017, 11:05

Edits noted, thanks again!

Its good that its at least reporting in ResHack the same as a cursor added thru itself, but I wouldn't worry about the color depth showing wrong since when used you can clearly see the cursor has more than 2 colors
EitherMouse - Multiple mice, individual settings . . . . www.EitherMouse.com . . . . forum . . . .

Return to “Ask For Help”

Who is online

Users browsing this forum: hoppfrosch, swagfag and 60 guests