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:
. 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
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
Ctrl+V 000in 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