You are here

ReplaceInFile.nsh problems??? "ReplacePathInFile.nsh" works for all settings files (for what I have tested)

7 posts / 0 new
Last post
Mark Sikkema
Offline
Last seen: 10 years 11 months ago
Developer
Joined: 2009-07-20 14:55
ReplaceInFile.nsh problems??? "ReplacePathInFile.nsh" works for all settings files (for what I have tested)

First of all, I want to thank everybody involved making PortableApps.com possible!!!

Since I have been playing around with making my apps portable, I have had sleepless nights about replacing absolute paths!
With the `macro` ReplaceInFile` it is possible up to a certain extend. But with a couple of settings files, replace didn't work. When a file contains raw data, the settings became unusable. As well I came across some *.ini files which were in some diffrent format and made ReplaceInFile useless.

After trying to enhance the original macro, I ended up with basically a whole new piece of code. Which could do everything I needed so far.

It will be able to replace:
1. whole paths and roots in one go.
2. the file byte by byte, so all other data will stay as it used to be.
3. these *.ini files I came across, which have a NULL byte after each character.
4. paths in registry files which have the double backslash, automatically.
5. on top of this all, I believe it does all of this even faster then ReplaceInFile it self does. Probably because it doesn't call string replace (StrRep.nsh) anymore.

Anyway, test it, use it and give me feedback on what could be changed/added !!!

Ps.: Besides there isn`t much left of the original code, I hope I done right by leaving the names of John T.Haller and Datenbert in the script.

Usage: (the same as ReplaceInFile.nsh)
${ReplacePathInFile} SOURCE_FILE SEARCH_PATH REPLACEMENT_PATH

The code: (Sorry don't know yet how to include this in a better way. Anyone???)

; ReplacePathInFile
; Copyright: GringoLoco, (based on ReplaceInFile by Datenbert)
; License: BSD  (NSIS license)
; define added by John T. Haller of PortableApps.com

!include FileFunc.nsh ; add header for file manipulation
!insertmacro GetFileExt
!insertmacro WordReplace

Function RPIF

  ClearErrors  ; want to be a newborn

  Exch $0      ; REPLACEMENT_PATH
  Exch
  Exch $1      ; SEARCH_PATH
  Exch 2
  Exch $2      ; SOURCE_FILE

  Push $3     ;converted character to compare
  Push $4     ;useless var for to store NULL byte
  Push $5     ;lenght of search text
  Push $6     ;counted character of string
  Push $7     ;counter
  Push $8     ;driveletter
  Push $9     ;file extension
  Push $R0     ; SOURCE_FILE file handle
  Push $R1     ; temporary file handle
  Push $R2     ; unique temporary file name
  Push $R3     ; a byte to sar/save
  Push $R4     ; shift puffer

  StrCpy $9 ""                      ;reset "reg" flag
  Repeat:
  StrCpy $7 0                       ;reset counter
  StrLen $5 $1                      ;Set lenght of search text
  IfFileExists $2 +1 RPIF_Error     ;see if file exist
  StrCpy $8 $0 3                    ;get SEARCH_PATH drive letter
  FileOpen $R0 $2 r                 ;open file to read
  GetTempFileName $R2               ;get temp file name
  FileOpen $R1 $R2 w                ;open temp file to write
  FileReadByte $R0 $R3              ;read first byte to determen special ini file
  StrCpy $6 $1 1 $7                 ;get first character of search text allready, cause of SkipReadByte
  IntCmp $R3 0xff IniSpecial        ;could be special ini file
  Goto SkipReadByte                 ;normal file ReadLoop
                RPIF_Loop:
                StrCpy $6 $1 1 $7                                      ;get first character of search text
			ReadLoop:
				FileReadByte $R0 $R3                   ;read byte
                                IfErrors RPIF_End                      ;enough is enough
				SkipReadByte:
				IntFmt $3 "%c" $R3                     ;convert to character
				StrCmp $6 $3 FoundChar                ;found first character
				FileWriteByte $R1 $R3                  ;write non important byte
			Goto ReadLoop                                  ;read next character

			FoundLoop:
                                StrCpy $6 $1 1 $7                      ;get next character of search text
			        FileReadByte $R0 $R3                   ;read next byte
                                IfErrors WriteFinish                   ;error byte in the middle of found, do write last string
			        IntFmt $3 "%c" $R3                     ;convert to character
			        StrCmp $6 $3 FoundChar                 ;found next character / not found, do rewrite of saved text
                                IntCmp $7 2 Write Write DriveLetter    ;check for driveletter
			FoundChar:
				StrCpy $R4 "$R4$3"                     ;combine found characters into string
                                IntOp $7 $7 + 1                        ;increase counter
				StrCmp $7 $5 0 FoundLoop               ;Compare length of searchtext with counter(lenght of found text)
				StrCmp $R4 $1 +1 Write                 ;found whole word / not found at all, do rewrite of found text
				StrCpy $R4 $0                          ;copy replacement text, do write of replacement text

			Write:                                         ;write combined characters in to file
				FileWrite $R1 $R4                      ;write whole string
                                StrCmp $R4 $0 +2                       ;found whole word, so dont write last byte
				FileWriteByte $R1 $R3                  ;write last byte(found character), could be non-char code
				StrCpy $7 0                            ;reset counter
				StrCpy $R4 ""                          ;reset combined chars
		Goto RPIF_Loop

        DriveLetter:
                StrCpy $R4 $R4 "" 3                                    ;remove driveletter
                StrCpy $R4 $8$R4                                       ;add new driveletter
		Goto Write                                             ;continue write string

	WriteFinish:
                IntCmp $7 3 "" +3                               ;check for driveletter
                       StrCpy $R4 $R4 "" 3                      ;remove driveletter
                       StrCpy $R4 $8$R4                         ;add new driveletter
	        FileWrite $R1 $R4                               ;write last string (part of found text)
  Goto RPIF_End

	IniSpecial:
			FileWriteByte $R1 $R3                          ;write byte 0xff
                        FileReadByte $R0 $R3                           ;read second byte to make sure file is special ini
                        IntCmp $R3 0xfe "" SkipReadByte                ;sure is special ini file
                        FileWriteByte $R1 $R3                          ;write byte 0xfe
                IniSpecialLoop:
                                StrCpy $6 $1 1 $7                      ;get first character of search text
			IniSearchLoop:
				FileReadByte $R0 $R3                   ;read byte
			        FileReadByte $R0 $4                    ;read NULL byte
                                IfErrors RPIF_End                      ;enough is enough
				IntFmt $3 "%c" $R3                     ;convert to character
				StrCmp $6 $3 IniFoundChar              ;found first character
				FileWriteByte $R1 $R3                  ;write non important byte
				FileWriteByte $R1 0                    ;write NULL byte
			Goto IniSearchLoop                             ;read next character
			IniFoundLoop:
                                StrCpy $6 $1 1 $7                      ;get next character of search text
			        FileReadByte $R0 $R3                   ;read next byte
			        FileReadByte $R0 $4                    ;read NULL byte
                                IfErrors IniWriteFinish                ;error byte in the middle of found, do write last string
			        IntFmt $3 "%c" $R3                     ;convert to character
			        StrCmp $6 $3 IniFoundChar              ;found next character / not found, do rewrite of saved text
                                IntCmp $7 2 IniWrite IniWrite IniDriveLetter ;check for driveletter
			IniFoundChar:
				StrCpy $R4 "$R4$3"                     ;combine found characters into string
                                IntOp $7 $7 + 1                        ;increase counter
				StrCmp $7 $5 0 IniFoundLoop            ;Compare length of searchtext with counter(lenght of found text)
				StrCmp $R4 $1 +1 IniWrite              ;found whole word / not found at all, do rewrite of found text
				StrCpy $R4 $0                          ;copy replacement text, do write of replacement text

			IniWrite:                                      ;write combined characters in to file
				StrLen $7 $R4                          ;lenght of text, as counter for IniWriteLoop
				IniWriteLoop:
				        StrCpy $3 $R4 1 -$7            ;get first/next character
				        FileWrite $R1 $3               ;write character
				        FileWriteByte $R1 0            ;write NULL byte
				        IntOp $7 $7 - 1                ;decrease counter
                                        IntCmp $7 0 "" "" IniWriteLoop ;check if all characters are writen
                                StrCmp $R4 $0 +3                       ;if found whole word dont write last byte
				FileWriteByte $R1 $R3                  ;write last byte(found character), could be non-char code
				FileWriteByte $R1 0                    ;write NULL byte
				StrCpy $7 0                            ;reset counter
				StrCpy $R4 ""                          ;reset combined characters
		Goto IniSpecialLoop

        IniDriveLetter:
                StrCpy $R4 $R4 "" 3                                    ;remove driveletter
                StrCpy $R4 $8$R4                                       ;add new driveletter
		Goto IniWrite                                          ;continue write string

        IniWriteFinish:
                IntCmp $7 3 "" +3                               ;check for driveletter
                       StrCpy $R4 $R4 "" 3                      ;remove driveletter
                       StrCpy $R4 $8$R4                         ;add new driveletter
	StrLen $7 $R4                                           ;lenght of text, as counter for IniWriteFinishLoop
	IniWriteFinishLoop:
		StrCpy $3 $R4 1 -$7                             ;get first/next character
		FileWrite $R1 $3                                ;write character
		FileWriteByte $R1 0                             ;write NULL byte
		IntOp $7 $7 - 1                                 ;decrease counter
                IntCmp $7 0 "" "" IniWriteFinishLoop            ;check if all characters are writen

  RPIF_End:
    FileClose $R1                   ;close writen file
    FileClose $R0                   ;close read file
 
    Delete "$2.old"                 ;delete old file, if still exists after last replace
    Rename "$2" "$2.old"            ;rename original file
    Rename "$R2" "$2"               ;move/rename replaced file
    
    StrCmp $9 "reg" RPIF_Out                            ;if file hasn`t been checked for registry special paths yet
          ${GetFileExt} $2 $9                           ;get file extension
          StrCmp $9 "reg" +1 RPIF_Out                   ;see if file is registry file
                      ${WordReplace} $0 "\" "\\" "+" $0 ;change replacement path to registry special path format
                      ${WordReplace} $1 "\" "\\" "+" $1 ;change search path to registry special path format
                      StrCpy $8 $0 3                    ;get replacement path driveletter
                      StrCpy $1 $1 "" 3                 ;remove seach path driveletter
                      StrCpy $1 $8$1                    ;add new driveletter to search path (cause old drive letter has alreadybeen replaced)
                      Goto Repeat                       ;replace path in registry file again, this time with special path format
 
  RPIF_Error:
    SetErrors
 
  RPIF_Out:
  ClearErrors
  Pop $R4
  Pop $R3
  Pop $R2
  Pop $R1
  Pop $R0
  Pop $9
  Pop $8
  Pop $7
  Pop $6
  Pop $5
  Pop $4
  Pop $3
  Pop $2
  Pop $0
  Pop $1
FunctionEnd

!macro _ReplacePathInFile SOURCE_FILE SEARCH_TEXT REPLACEMENT
  Push "${SOURCE_FILE}"
  Push "${SEARCH_TEXT}"
  Push "${REPLACEMENT}"
  Call RPIF
!macroend

!define ReplacePathInFile '!insertmacro "_ReplacePathInFile"'

You can use the <pre> tag for blocks of code - mod CM

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 7 years 1 month ago
Joined: 2007-04-15 21:08
Some observations

Some observations on how we currently do things:
The way we generally do them is just as $CURRENTDRIVE:\, and thus \\ doesn't matter (but the generic launcher will cope with some extra situations where \\ is needed, and even with a special thing for Java where capitals have to be escaped :/)
Not sure about the null byte, never come across that in an INI file (but I've got a vague feeling that REGEDIT 5.00 files use a null byte in between every character... but gVim can automatically cope with that so that you don't see it but it still saves it as such Smile . From your brief overview though, that's the only spot where there may be improvements in functionality - but you'd still be able to insert the null byte inside the file (gVim at least you just type in Ctrl+V 000 in insert mode, or Ctrl+Q 000 if you've remapped Ctrl+V for paste or anything silly :P).

(I haven't looked very closely at your code yet.)

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

Mark Sikkema
Offline
Last seen: 10 years 11 months ago
Developer
Joined: 2009-07-20 14:55
I am not sure we understand each other?

Hi Chris, nice to meet you!

I am not sure we understand each other, I made this code so I am able to replace whole paths with a launcher just as roots could be replaced. So when there are any, lets say for example, recent files (which are in PA Data folder) stored in the settings ini file of an app, and I move the whole portable folder to a different location on my USB-drive, the settings won't get messed up.
So, cause I want to replace paths as well in registry files, \\ matters to me!
But probably I'm not supposed move a PortableApp to a different location!!!

Never came across gVim before, did download it today but not sure how I can make use of it within my launchers???

For me, it helped my launchers a lot to read-replace-write files byte by byte, so all binary code stays in tact, often enough came across .dat files with non ascii code, which got messed up after trying to replace roots.

Get the feeling I should have asked for advise first, before reinventing the wheel again. But it's in my personality to try and find out myself. Anyway, I`m learning a lot about NSIS this way.

For today I started on a new 'problem' I come across making launchers. And maybe this time I should ask help.

I want to try to make a launcher which (with the first launch), when I want to open/save a file standardly direct me to for example ?:\Documents or ?:\portableApps\?Portable\Data.
This seems often enough not possible by modifying settings files. I think I have to make a launcher that (temporary) modifies the registry key "LastVisitedMRU"?

Any ideas???

Formerly Gringoloco
Windows XP Pro sp3 x32

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 7 years 1 month ago
Joined: 2007-04-15 21:08
Gringoloco I am not sure we

Gringoloco I am not sure we understand each other, I made this code so I am able to replace whole paths with a launcher just as roots could be replaced. So when there are any, lets say for example, recent files (which are in PA Data folder) stored in the settings ini file of an app, and I move the whole portable folder to a different location on my USB-drive, the settings won't get messed up.
So, cause I want to replace paths as well in registry files, \\ matters to me!
But probably I'm not supposed move a PortableApp to a different location!!!

OK. I see what you're meaning. Generally speaking, we don't worry about folder-level portability, just drive-letter portability (in most of our apps it's incidental due to the way they set things up, but in some it isn't there, e.g. Songbird). In my experience it's very rare to need to change a folder location to update it - you either want to change the drive letter only, or if it's setting a location in Data or similar, it'll be a string in the executable parameters, or an environment variable, or something you can do with WriteINIStr or ${ConfigWrite}.

Gringoloco Never came across gVim before, did download it today but not sure how I can make use of it within my launchers???

gVim is just a very powerful (if not terribly intuitive for those starting on it) text editor.

Gringoloco For me, it helped my launchers a lot to read-replace-write files byte by byte, so all binary code stays in tact, often enough came across .dat files with non ascii code, which got messed up after trying to replace roots.

I've never tried doing anything which needed binary mode writing.

Gringoloco Get the feeling I should have asked for advise first, before reinventing the wheel again. But it's in my personality to try and find out myself. Anyway, I`m learning a lot about NSIS this way.

Up to you... doing serious code like that's a good way to learn NSIS.

Gringoloco I want to try to make a launcher which (with the first launch), when I want to open/save a file standardly direct me to for example ?:\Documents or ?:\portableApps\?Portable\Data.
This seems often enough not possible by modifying settings files. I think I have to make a launcher that (temporary) modifies the registry key "LastVisitedMRU"?

Forget about it... there's no safe, consistent way of doing it. We may reassess the issue later but for the moment leave it.

Sorry, these answers are rushed, got to go now.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

Mark Sikkema
Offline
Last seen: 10 years 11 months ago
Developer
Joined: 2009-07-20 14:55
Modifying LasVisitMRU not safe???

Thanx for your replies Chris, I know your coding of the ChrisLauncher and I`m sure I could still learn a lot of you!

But...., I wonder why you think modifying lastvisitMRU isn`t safe, all I can think about is that maybe on Vista it all works diferent???

I`ve been playing around to day with modifying the list
I made a code what:
1. searches for an existing App.exe hex-string in the LastVisitMRU key,and safes the value character
2. otherwise looks for the next free value character
3. then modifies/creates the new path to this value character
4. when the launcher finishes and cleans up, it will safe the last known path string for next use

There shouldn't be a need to change this to the original state afterwards, cause normally the standard PA's leave these traces anyway and isn't considered as a problem. But if the launcher should I can't think of a reason why it couldn't, it doesn`t even seem to matter to windows if a value character from the middle of the list is missing. So just store original hex-string at start of launch and replace (or remove if it didn`t used to exist before) at the end of the launcher

Anyway still have to debug the code, but seems to do what I want it to do, for now!
What i`m not sure about is what would happen when all characters (a-z) of the list are used??? (My computer is in Steady State so always is perfectly clean)

Formerly Gringoloco
Windows XP Pro sp3 x32

Chris Morgan
Chris Morgan's picture
Offline
Last seen: 7 years 1 month ago
Joined: 2007-04-15 21:08
From memory the MRU stuff

From memory the MRU stuff tends to vary a bit between operating systems, and also is shared to some degree between applications; I believe that you'd find you had to change it for a few different OS combinations, and if you do something wrong, then it causes a bit of trouble for the user.

I might go through and take a more extensive look at how it works on various different platforms. What you say you've got sounds OK.

I can't remember what happens when you get to Z, it might have been it drops it. It's all rather complex.

I am a Christian and a developer and moderator here.

“A soft answer turns away wrath, but a harsh word stirs up anger.” – Proverbs 15:1

Mark Sikkema
Offline
Last seen: 10 years 11 months ago
Developer
Joined: 2009-07-20 14:55
`how it works on various different platforms. `

I've been busy for a few days, having exams next week.

If you could have a look how it works on Vista or Windows 7, would be a great help. I don't know anybody who uses these systems.

On XP the key is:
HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\LastVisitedMRU

as well I wonder if the Classes system like is like it works on XP.

HKLM\SOFTWARE\Classes\regfile\shell

Ps.: Anyway, sorry I started new tread, i'm a bit chaotic lately.

Formerly Gringoloco
Windows XP Pro sp3 x32

Log in or register to post comments