ReadCustomerData: Difference between revisions
No edit summary |
(Added example) |
||
(7 intermediate revisions by 4 users not shown) | |||
Line 1: | Line 1: | ||
{{PageAuthor|andreyvit}} | {{PageAuthor|andreyvit}} | ||
[[Category:Disk, Path & File Functions]] | |||
== The problem == | == The problem == | ||
Line 25: | Line 26: | ||
; Author: | ; Author: | ||
; Andrey Tarantsov <andreyvit@gmail.com> -- please e-mail me useful modifications you make | ; Andrey Tarantsov <andreyvit@gmail.com> -- please e-mail me useful modifications you make | ||
;Corrected the example below. | |||
; The customer data in the original (bugged) version supposed to have been returned in $1 | |||
; Actually it is returned in $R1. | |||
;Rafi Wiener | |||
; Example: | ; Example: | ||
; Push "CUSTDATA:" | ; Push "CUSTDATA:" | ||
; Call ReadCustomerData | ; Call ReadCustomerData | ||
; Pop $ | ; Pop $R1 | ||
; StrCmp $ | ; StrCmp $R1 "" 0 +3 | ||
; MessageBox MB_OK "No data found" | ; MessageBox MB_OK "No data found" | ||
; Abort | ; Abort | ||
; MessageBox MB_OK "Customer data: '$ | ; MessageBox MB_OK "Customer data: '$R1'" | ||
Function ReadCustomerData | Function ReadCustomerData | ||
; arguments | ; arguments | ||
Line 42: | Line 49: | ||
Push $4 ; length of $R1 | Push $4 ; length of $R1 | ||
FileOpen $1 $EXEPATH r | |||
FileOpen $1 $ | |||
; change 1024 here to, e.g., 2048 to scan the last 2Kb of EXE file | ; change 1024 here to, e.g., 2048 to scan the last 2Kb of EXE file | ||
Line 75: | Line 79: | ||
FunctionEnd</highlight-nsis> | FunctionEnd</highlight-nsis> | ||
=== Example === | |||
<highlight-nsis> | |||
Function ReadCustomerData | |||
... | |||
FunctionEnd | |||
Section | |||
Push "CUSTDATA:" | |||
Call ReadCustomerData | |||
Pop $R1 | |||
MessageBox mb_ok $R1 | |||
SectionEnd | |||
!finalize '>> "%1" echo.CUSTDATA:Name=%USERNAME%,Id=%RANDOM%' ; Somehow append the customer data | |||
</highlight-nsis> | |||
== Performance == | |||
The simplest implementation of ReadCustomerData, provided by Andrey, searches the end of the file 1 byte at a time for the magic data prefix. It is often possible to advance the scanning process more quickly, by checking for substrings of the magic data prefix within the trial buffer. A more complex implementation can therefore progress more quickly and so may be approprate if it is necessary to scan a larger chunk of the EXE. | |||
<highlight-nsis>; ReadCustomerData ( data_prefix -> customer_data ) | |||
; Reads string data appended to the end of the installer EXE. | |||
; The data must be preceded by a known string. | |||
; Only last 4Kb of EXE is searched for the prefix | |||
; (but this can be easily changed, see comment below). | |||
; Inputs: | |||
; data_prefix (string) -- the string after which customer data begins | |||
; Outputs: | |||
; customer_data (string) -- the data after the prefix (does NOT include the prefix), | |||
; empty if prefix not found | |||
; Author: | |||
; Andrey Tarantsov <andreyvit@gmail.com> -- please e-mail me useful modifications you make | |||
; Stephen White <swhite-nsiswiki@corefiling.com> | |||
; Example: | |||
; Push "CUSTDATA:" | |||
; Call ReadCustomerData | |||
; Pop $1 | |||
; StrCmp $1 "" 0 +3 | |||
; MessageBox MB_OK "No data found" | |||
; Abort | |||
; MessageBox MB_OK "Customer data: '$1'" | |||
Function ReadCustomerData | |||
; arguments | |||
Exch $R1 ; customer data magic value | |||
; locals | |||
Push $1 ; file name or (later) file handle | |||
Push $2 ; current trial offset | |||
Push $3 ; current trial string (which will match $R1 when customer data is found) | |||
Push $4 ; length of $R1 | |||
Push $5 ; half length of $R1 | |||
Push $6 ; first half of $R1 | |||
Push $7 ; tmp | |||
FileOpen $1 $EXEPATH r | |||
; change 4096 here to, e.g., 2048 to scan just the last 2Kb of EXE file | |||
IntOp $2 0 - 4096 | |||
StrLen $4 $R1 | |||
IntOp $5 $4 / 2 | |||
StrCpy $6 $R1 $5 | |||
loop: | |||
FileSeek $1 $2 END | |||
FileRead $1 $3 $4 | |||
StrCmpS $3 $R1 found | |||
${StrLoc} $7 $3 $6 ">" | |||
StrCmpS $7 "" NotFound | |||
IntCmp $7 0 FoundAtStart | |||
; We can jump forwards to the position at which we found the partial match | |||
IntOp $2 $2 + $7 | |||
IntCmp $2 0 loop loop | |||
FoundAtStart: | |||
; We should make progress | |||
IntOp $2 $2 + 1 | |||
IntCmp $2 0 loop loop | |||
NotFound: | |||
; We can safely jump forward half the length of the magic | |||
IntOp $2 $2 + $5 | |||
IntCmp $2 0 loop loop | |||
StrCpy $R1 "" | |||
goto fin | |||
found: | |||
IntOp $2 $2 + $4 | |||
FileSeek $1 $2 END | |||
FileRead $1 $3 | |||
StrCpy $R1 $3 | |||
fin: | |||
Pop $7 | |||
Pop $6 | |||
Pop $5 | |||
Pop $4 | |||
Pop $3 | |||
Pop $2 | |||
Pop $1 | |||
Exch $R1 | |||
FunctionEnd</highlight-nsis> | |||
== How to parse the returned data == | == How to parse the returned data == |
Latest revision as of 22:14, 18 August 2021
Author: andreyvit (talk, contrib) |
The problem
You want to personalize (customize, preconfigure etc) each copy of installer you give out. But, of course, you do not want to recompile the installer every time.
The solution
Append the customization data directly to the installer EXE file, prefixed with some fixed string. For example, you can use "CUSTDATA:" prefix, and then the whole customization data could read like "CUSTDATA:12;Andrey Tarantsov;www.somesite.com/myhandler".
Implementing this solution
ReadCustomerData function does exactly what you expect: it scans through the end of the installer EXE file, finds your magic string ("CUSTDATA:"), and then returns everything after this string up to the end of the file.
; ReadCustomerData ( data_prefix -> customer_data ) ; Reads string data appended to the end of the installer EXE. ; The data must be preceded by a known string. ; Only last 1Kb of EXE is searched for the prefix ; (but this can be easily change, see comment below). ; Inputs: ; data_prefix (string) -- the string after which customer data begins ; Outputs: ; customer_data (string) -- the data after the prefix (does NOT include the prefix), ; empty if prefix not found ; Author: ; Andrey Tarantsov <andreyvit@gmail.com> -- please e-mail me useful modifications you make ;Corrected the example below. ; The customer data in the original (bugged) version supposed to have been returned in $1 ; Actually it is returned in $R1. ;Rafi Wiener ; Example: ; Push "CUSTDATA:" ; Call ReadCustomerData ; Pop $R1 ; StrCmp $R1 "" 0 +3 ; MessageBox MB_OK "No data found" ; Abort ; MessageBox MB_OK "Customer data: '$R1'" Function ReadCustomerData ; arguments Exch $R1 ; customer data magic value ; locals Push $1 ; file name or (later) file handle Push $2 ; current trial offset Push $3 ; current trial string (which will match $R1 when customer data is found) Push $4 ; length of $R1 FileOpen $1 $EXEPATH r ; change 1024 here to, e.g., 2048 to scan the last 2Kb of EXE file IntOp $2 0 - 1024 StrLen $4 $R1 loop: FileSeek $1 $2 END FileRead $1 $3 $4 StrCmp $3 $R1 found IntOp $2 $2 + 1 IntCmp $2 0 loop loop StrCpy $R1 "" goto fin found: IntOp $2 $2 + $4 FileSeek $1 $2 END FileRead $1 $3 StrCpy $R1 $3 fin: Pop $4 Pop $3 Pop $2 Pop $1 Exch $R1 FunctionEnd
Example
Function ReadCustomerData ... FunctionEnd Section Push "CUSTDATA:" Call ReadCustomerData Pop $R1 MessageBox mb_ok $R1 SectionEnd !finalize '>> "%1" echo.CUSTDATA:Name=%USERNAME%,Id=%RANDOM%' ; Somehow append the customer data
Performance
The simplest implementation of ReadCustomerData, provided by Andrey, searches the end of the file 1 byte at a time for the magic data prefix. It is often possible to advance the scanning process more quickly, by checking for substrings of the magic data prefix within the trial buffer. A more complex implementation can therefore progress more quickly and so may be approprate if it is necessary to scan a larger chunk of the EXE.
; ReadCustomerData ( data_prefix -> customer_data ) ; Reads string data appended to the end of the installer EXE. ; The data must be preceded by a known string. ; Only last 4Kb of EXE is searched for the prefix ; (but this can be easily changed, see comment below). ; Inputs: ; data_prefix (string) -- the string after which customer data begins ; Outputs: ; customer_data (string) -- the data after the prefix (does NOT include the prefix), ; empty if prefix not found ; Author: ; Andrey Tarantsov <andreyvit@gmail.com> -- please e-mail me useful modifications you make ; Stephen White <swhite-nsiswiki@corefiling.com> ; Example: ; Push "CUSTDATA:" ; Call ReadCustomerData ; Pop $1 ; StrCmp $1 "" 0 +3 ; MessageBox MB_OK "No data found" ; Abort ; MessageBox MB_OK "Customer data: '$1'" Function ReadCustomerData ; arguments Exch $R1 ; customer data magic value ; locals Push $1 ; file name or (later) file handle Push $2 ; current trial offset Push $3 ; current trial string (which will match $R1 when customer data is found) Push $4 ; length of $R1 Push $5 ; half length of $R1 Push $6 ; first half of $R1 Push $7 ; tmp FileOpen $1 $EXEPATH r ; change 4096 here to, e.g., 2048 to scan just the last 2Kb of EXE file IntOp $2 0 - 4096 StrLen $4 $R1 IntOp $5 $4 / 2 StrCpy $6 $R1 $5 loop: FileSeek $1 $2 END FileRead $1 $3 $4 StrCmpS $3 $R1 found ${StrLoc} $7 $3 $6 ">" StrCmpS $7 "" NotFound IntCmp $7 0 FoundAtStart ; We can jump forwards to the position at which we found the partial match IntOp $2 $2 + $7 IntCmp $2 0 loop loop FoundAtStart: ; We should make progress IntOp $2 $2 + 1 IntCmp $2 0 loop loop NotFound: ; We can safely jump forward half the length of the magic IntOp $2 $2 + $5 IntCmp $2 0 loop loop StrCpy $R1 "" goto fin found: IntOp $2 $2 + $4 FileSeek $1 $2 END FileRead $1 $3 StrCpy $R1 $3 fin: Pop $7 Pop $6 Pop $5 Pop $4 Pop $3 Pop $2 Pop $1 Exch $R1 FunctionEnd
How to parse the returned data
ReadCustomerData returns simply a string. When I need to split it into individual fields, I first declare variables to hold these fields:
var customer_name var customer_account var customer_cookie var customer_role var server_url
and then use the following code. (It calls ReadCSV and Trim functions, more on them below.)
; ParseCustomerData ( customer_data -> ) ; Parses semicolon-separated customer data into individual fields Function ParseCustomerData Exch $R1 ; customer data, then item count Push $1 ; current item index (0-based) Push $2 ; current item Push $R1 Call ReadCSV Pop $R1 StrCpy $1 0 loop: IntCmp $1 $R1 done Call Trim Pop $2 ; TODO: add dispatching code here IntCmp $1 0 item0 IntCmp $1 1 item1 IntCmp $1 2 item2 IntCmp $1 3 item3 IntCmp $1 4 item4 MessageBox MB_OK|MB_ICONEXCLAMATION "There are too many items in customer data; extra items ignored." goto done ; TODO: add saving code here item0: StrCpy $customer_name $2 goto ok item1: StrCpy $customer_account $2 goto ok item2: StrCpy $customer_cookie $2 goto ok item3: StrCpy $customer_role $2 goto ok item4: StrCpy $server_url $2 goto ok ok: IntOp $1 $1 + 1 Goto loop done: Pop $2 Pop $1 Pop $R1 FunctionEnd
ReadCSV and Trim functions
They used to be described in this Wiki, but somehow seem to be gone now. This is very unfortunate, as these are very convenient functions. So I just reproduce them here. I'm am NOT an author of these functions, and I would be glad to remove the code and replace it with links to them.
; Trim ; Removes leading & trailing whitespace from a string ; Usage: ; Push ; Call Trim ; Pop Function Trim Exch $R1 ; Original string Push $R2 Loop: StrCpy $R2 "$R1" 1 StrCmp "$R2" " " TrimLeft StrCmp "$R2" "$\r" TrimLeft StrCmp "$R2" "$\n" TrimLeft StrCmp "$R2" " " TrimLeft ; this is a tab. GoTo Loop2 TrimLeft: StrCpy $R1 "$R1" "" 1 Goto Loop Loop2: StrCpy $R2 "$R1" 1 -1 StrCmp "$R2" " " TrimRight StrCmp "$R2" "$\r" TrimRight StrCmp "$R2" "$\n" TrimRight StrCmp "$R2" " " TrimRight ; this is a tab GoTo Done TrimRight: StrCpy $R1 "$R1" -1 Goto Loop2 Done: Pop $R2 Exch $R1 FunctionEnd
Function ReadCSV Exch $1 # input string (csv) Push $2 # substring of $1: length 1, checked for commata Push $3 # substring of $1: single value, returned to stack (below $r2) Push $r1 # counter: length of $1, number letters to check Push $r2 # counter: values found, returned to top of stack Push $r3 # length: to determinate length of current value StrLen $r1 $1 StrCpy $r2 0 StrLen $r3 $1 loop: IntOp $r1 $r1 - 1 IntCmp $r1 -1 text done StrCpy $2 $1 1 $r1 StrCmp $2 ";" text Goto loop text: Push $r1 IntOp $r1 $r1 + 1 IntOp $r3 $r3 - $r1 StrCpy $3 $1 $r3 $r1 Pop $r1 StrCpy $r3 $r1 IntOp $r2 $r2 + 1 Push $3 Exch 6 Exch 5 Exch 4 Exch 3 Exch Goto loop done: StrCpy $1 $r2 Pop $r3 Pop $r2 Pop $r1 Pop $3 Pop $2 Exch $1 FunctionEnd