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
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 . 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
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
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}.
gVim is just a very powerful (if not terribly intuitive for those starting on it) text editor.
I've never tried doing anything which needed binary mode writing.
Up to you... doing serious code like that's a good way to learn NSIS.
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
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
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
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