summary refs log tree commit diff stats
path: root/tools/niminst
diff options
context:
space:
mode:
Diffstat (limited to 'tools/niminst')
-rw-r--r--tools/niminst/buildbat.tmpl6
-rw-r--r--tools/niminst/buildsh.tmpl6
-rw-r--r--tools/niminst/debcreation.nim4
-rw-r--r--tools/niminst/deinstall.tmpl2
-rw-r--r--tools/niminst/install.tmpl2
-rw-r--r--tools/niminst/makefile.tmpl4
-rw-r--r--tools/niminst/niminst.nim95
-rw-r--r--tools/niminst/nsis.tmpl219
8 files changed, 271 insertions, 67 deletions
diff --git a/tools/niminst/buildbat.tmpl b/tools/niminst/buildbat.tmpl
index 2a8da144d..278b6caea 100644
--- a/tools/niminst/buildbat.tmpl
+++ b/tools/niminst/buildbat.tmpl
@@ -1,5 +1,5 @@
 #? stdtmpl(subsChar='?') | standard
-#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex: int): string = 
+#proc generateBuildBatchScript(c: ConfigData, winIndex, cpuIndex: int): string =
 #  result = "@echo off\nREM Generated by niminst\n"
 SET CC=gcc
 SET LINKER=gcc
@@ -23,8 +23,8 @@ CALL %CC% %COMP_FLAGS% -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
 IF ERRORLEVEL 1 (GOTO:END)
 #    end for
 
-ECHO %LINKER% -o ?{"%BIN_DIR%"\toLower(c.name)}.exe ?linkCmd %LINK_FLAGS%
-CALL %LINKER% -o ?{"%BIN_DIR%"\toLower(c.name)}.exe ?linkCmd %LINK_FLAGS%
+ECHO %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
+CALL %LINKER% -o ?{"%BIN_DIR%"\toLowerAscii(c.name)}.exe ?linkCmd %LINK_FLAGS%
 
 #  end block
 
diff --git a/tools/niminst/buildsh.tmpl b/tools/niminst/buildsh.tmpl
index 13dfe5226..f82fc0fee 100644
--- a/tools/niminst/buildsh.tmpl
+++ b/tools/niminst/buildsh.tmpl
@@ -92,6 +92,10 @@ case $uos in
     ;;
   *haiku* )
     myos="haiku"
+    LINK_FLAGS="$LINK_FLAGS -lroot -lnetwork"
+    ;;
+  *mingw* )
+    myos="windows"
     ;;
   *)
     echo 2>&1 "Error: unknown operating system: $uos"
@@ -143,7 +147,7 @@ case $myos in
     $CC $COMP_FLAGS -Ic_code -c ?{f} -o ?{changeFileExt(f, "o")}
 #        add(linkCmd, " \\\n" & changeFileExt(f, "o"))
 #      end for
-    $LINKER -o ?{"$binDir/" & toLower(c.name)} ?linkCmd $LINK_FLAGS
+    $LINKER -o ?{"$binDir/" & toLowerAscii(c.name)} ?linkCmd $LINK_FLAGS
     ;;
 #    end for
   *)
diff --git a/tools/niminst/debcreation.nim b/tools/niminst/debcreation.nim
index 36b2a29ec..0ecea132f 100644
--- a/tools/niminst/debcreation.nim
+++ b/tools/niminst/debcreation.nim
@@ -148,7 +148,7 @@ proc prepDeb*(packName, version, mtnName, mtnEmail, shortDesc, desc: string,
   ## binaries/config/docs/lib: files relative to nim's root, that need to
   ##   be installed.
 
-  let pkgName = packName.toLower()
+  let pkgName = packName.toLowerAscii()
 
   var workingDir = getTempDir() / "niminst" / "deb"
   var upstreamSource = (pkgName & "-" & version)
@@ -168,7 +168,7 @@ proc prepDeb*(packName, version, mtnName, mtnEmail, shortDesc, desc: string,
   echo("Creating necessary files in debian/")
   createDir(workingDir / upstreamSource / "debian")
 
-  template writeDebian(f, s: string): expr =
+  template writeDebian(f, s: string) =
     writeFile(workingDir / upstreamSource / "debian" / f, s)
 
   var controlFile = createControl(pkgName, makeMtn(mtnName, mtnEmail),
diff --git a/tools/niminst/deinstall.tmpl b/tools/niminst/deinstall.tmpl
index 3cdfbf45d..bba310f76 100644
--- a/tools/niminst/deinstall.tmpl
+++ b/tools/niminst/deinstall.tmpl
@@ -1,7 +1,7 @@
 #? stdtmpl(subsChar='?') | standard
 #proc generateDeinstallScript(c: ConfigData): string =
 #  result = "#! /bin/sh\n# Generated by niminst\n"
-#  var proj = c.name.toLower
+#  var proj = c.name.toLowerAscii
 
 if [ $# -eq 1 ] ; then
   case $1 in
diff --git a/tools/niminst/install.tmpl b/tools/niminst/install.tmpl
index 3f17840a8..d72b132ef 100644
--- a/tools/niminst/install.tmpl
+++ b/tools/niminst/install.tmpl
@@ -1,7 +1,7 @@
 #? stdtmpl(subsChar = '?') | standard
 #proc generateInstallScript(c: ConfigData): string =
 #  result = "#! /bin/sh\n# Generated by niminst\n"
-#  var proj = c.name.toLower
+#  var proj = c.name.toLowerAscii
 
 ## Current directory you start script from
 BASE_DIR=$(pwd)
diff --git a/tools/niminst/makefile.tmpl b/tools/niminst/makefile.tmpl
index 5c95ccda9..ce2db1c48 100644
--- a/tools/niminst/makefile.tmpl
+++ b/tools/niminst/makefile.tmpl
@@ -157,7 +157,7 @@ endif
 %.o: %.c
 	$(CC) $(COMP_FLAGS) -Ic_code -c $< -o $@
 
-?{"$(binDir)/" & toLower(c.name)}: $(oFiles)
+?{"$(binDir)/" & toLowerAscii(c.name)}: $(oFiles)
 	@mkdir -p $(binDir)
 	$(LINKER) -o $@ $^ $(LINK_FLAGS)
 	@echo "SUCCESS"
@@ -165,4 +165,4 @@ endif
 .PHONY: clean
 
 clean:
-	rm -f $(oFiles) ?{"$(binDir)/" & toLower(c.name)}
+	rm -f $(oFiles) ?{"$(binDir)/" & toLowerAscii(c.name)}
diff --git a/tools/niminst/niminst.nim b/tools/niminst/niminst.nim
index 4c8dfcddf..e0b8ad9b3 100644
--- a/tools/niminst/niminst.nim
+++ b/tools/niminst/niminst.nim
@@ -313,7 +313,7 @@ proc parseIniFile(c: var ConfigData) =
       of cfgSectionStart:
         section = normalize(k.section)
       of cfgKeyValuePair:
-        var v = k.value % c.vars
+        var v = `%`(k.value, c.vars, {useEnvironment, useEmpty})
         c.vars[k.key] = v
 
         case section
@@ -447,7 +447,8 @@ proc readCFiles(c: var ConfigData, osA, cpuA: int) =
           # HACK: we conditionally add ``-lm -ldl``, so remove them from the
           # linker flags:
           c.linker.flags = c.linker.flags.replaceWord("-lm").replaceWord(
-                           "-ldl").strip
+                           "-ldl").replaceWord("-lroot").replaceWord(
+                           "-lnetwork").strip
         else:
           if cmpIgnoreStyle(k.key, "libpath") == 0:
             c.libpath = k.value
@@ -551,7 +552,7 @@ proc srcdist(c: var ConfigData) =
 # --------------------- generate inno setup -----------------------------------
 proc setupDist(c: var ConfigData) =
   let scrpt = generateInnoSetup(c)
-  let n = "build" / "install_$#_$#.iss" % [toLower(c.name), c.version]
+  let n = "build" / "install_$#_$#.iss" % [toLowerAscii(c.name), c.version]
   writeFile(n, scrpt, "\13\10")
   when defined(windows):
     if c.innosetup.path.len == 0:
@@ -568,7 +569,7 @@ proc setupDist(c: var ConfigData) =
 # --------------------- generate NSIS setup -----------------------------------
 proc setupDist2(c: var ConfigData) =
   let scrpt = generateNsisSetup(c)
-  let n = "build" / "install_$#_$#.nsi" % [toLower(c.name), c.version]
+  let n = "build" / "install_$#_$#.nsi" % [toLowerAscii(c.name), c.version]
   writeFile(n, scrpt, "\13\10")
   when defined(windows):
     if c.nsisSetup.path.len == 0:
@@ -585,7 +586,7 @@ proc setupDist2(c: var ConfigData) =
 # ------------------ generate ZIP file ---------------------------------------
 when haveZipLib:
   proc zipDist(c: var ConfigData) =
-    var proj = toLower(c.name) & "-" & c.version
+    var proj = toLowerAscii(c.name) & "-" & c.version
     var n = "$#.zip" % proj
     if c.outdir.len == 0: n = "build" / n
     else: n = c.outdir / n
@@ -616,49 +617,64 @@ when haveZipLib:
     else:
       quit("Cannot open for writing: " & n)
 
-proc xzDist(c: var ConfigData) =
-  let proj = toLower(c.name) & "-" & c.version
-  var n = "$#.tar.xz" % proj
+proc xzDist(c: var ConfigData; windowsZip=false) =
+  let proj = toLowerAscii(c.name) & "-" & c.version
   let tmpDir = if c.outdir.len == 0: "build" else: c.outdir
 
-  template processFile(z, dest, src) =
-    let s = src
-    let d = dest
-    echo "Copying ", s, " to ", tmpDir / d
-    let destdir = tmpdir / d.splitFile.dir
-    if not dirExists(destdir): createDir(destdir)
-    copyFileWithPermissions(s, tmpDir / d)
-
-  processFile(z, proj / buildBatFile32, "build" / buildBatFile32)
-  processFile(z, proj / buildBatFile64, "build" / buildBatFile64)
-  processFile(z, proj / buildShFile, "build" / buildShFile)
-  processFile(z, proj / makeFile, "build" / makeFile)
-  processFile(z, proj / installShFile, installShFile)
-  processFile(z, proj / deinstallShFile, deinstallShFile)
-  for f in walkFiles(c.libpath / "lib/*.h"):
-    processFile(z, proj / "c_code" / extractFilename(f), f)
-  for osA in 1..c.oses.len:
-    for cpuA in 1..c.cpus.len:
-      var dir = buildDir(osA, cpuA)
-      for k, f in walkDir("build" / dir):
-        if k == pcFile: processFile(z, proj / dir / extractFilename(f), f)
+  proc processFile(destFile, src: string) =
+    let dest = tmpDir / destFile
+    echo "Copying ", src, " to ", dest
+    if not existsFile(src):
+      echo "[Warning] Source file doesn't exist: ", src
+    let destDir = dest.splitFile.dir
+    if not dirExists(destDir): createDir(destDir)
+    copyFileWithPermissions(src, dest)
+
+  if not windowsZip and not existsFile("build" / buildBatFile32):
+    quit("No C sources found in ./build/, please build by running " &
+         "./koch csource -d:release.")
+
+  if not windowsZip:
+    processFile(proj / buildBatFile32, "build" / buildBatFile32)
+    processFile(proj / buildBatFile64, "build" / buildBatFile64)
+    processFile(proj / buildShFile, "build" / buildShFile)
+    processFile(proj / makeFile, "build" / makeFile)
+    processFile(proj / installShFile, installShFile)
+    processFile(proj / deinstallShFile, deinstallShFile)
+    for f in walkFiles(c.libpath / "lib/*.h"):
+      processFile(proj / "c_code" / extractFilename(f), f)
+    for osA in 1..c.oses.len:
+      for cpuA in 1..c.cpus.len:
+        var dir = buildDir(osA, cpuA)
+        for k, f in walkDir("build" / dir):
+          if k == pcFile: processFile(proj / dir / extractFilename(f), f)
+  else:
+    for f in items(c.cat[fcWinBin]):
+      let filename = f.extractFilename
+      processFile(proj / "bin" / filename, f)
 
-  for cat in items({fcConfig..fcOther, fcUnix, fcNimble}):
+  let osSpecific = if windowsZip: fcWindows else: fcUnix
+  for cat in items({fcConfig..fcOther, osSpecific, fcNimble}):
     echo("Current category: ", cat)
-    for f in items(c.cat[cat]): processFile(z, proj / f, f)
+    for f in items(c.cat[cat]): processFile(proj / f, f)
 
   # Copy the .nimble file over
   let nimbleFile = c.nimblePkgName & ".nimble"
-  processFile(z, proj / nimbleFile, nimbleFile)
+  processFile(proj / nimbleFile, nimbleFile)
 
   when true:
     let oldDir = getCurrentDir()
     setCurrentDir(tmpDir)
     try:
-      if execShellCmd("XZ_OPT=-9 gtar Jcf $1.tar.xz $1 --exclude=.DS_Store" % proj) != 0:
-        # try old 'tar' without --exclude feature:
-        if execShellCmd("XZ_OPT=-9 tar Jcf $1.tar.xz $1" % proj) != 0:
+      if windowsZip:
+        if execShellCmd("7z a -tzip $1.zip $1" % proj) != 0:
           echo("External program failed")
+      else:
+        if execShellCmd("XZ_OPT=-9 gtar Jcf $1.tar.xz $1 --exclude=.DS_Store" %
+                        proj) != 0:
+          # try old 'tar' without --exclude feature:
+          if execShellCmd("XZ_OPT=-9 tar Jcf $1.tar.xz $1" % proj) != 0:
+            echo("External program failed")
     finally:
       setCurrentDir(oldDir)
 
@@ -676,11 +692,11 @@ proc debDist(c: var ConfigData) =
   echo("Copying source to tmp/niminst/deb/")
   var currentSource = getCurrentDir()
   var workingDir = getTempDir() / "niminst" / "deb"
-  var upstreamSource = (c.name.toLower() & "-" & c.version)
+  var upstreamSource = (c.name.toLowerAscii() & "-" & c.version)
 
   createDir(workingDir / upstreamSource)
 
-  template copyNimDist(f, dest: string): stmt =
+  template copyNimDist(f, dest: string) =
     createDir((workingDir / upstreamSource / dest).splitFile.dir)
     copyFile(currentSource / f, workingDir / upstreamSource / dest)
 
@@ -724,10 +740,7 @@ if actionCSource in c.actions:
 if actionScripts in c.actions:
   writeInstallScripts(c)
 if actionZip in c.actions:
-  when haveZipLib:
-    zipDist(c)
-  else:
-    quit("libzip is not installed")
+  xzDist(c, true)
 if actionXz in c.actions:
   xzDist(c)
 if actionDeb in c.actions:
diff --git a/tools/niminst/nsis.tmpl b/tools/niminst/nsis.tmpl
index 0897d36a8..95d4652e3 100644
--- a/tools/niminst/nsis.tmpl
+++ b/tools/niminst/nsis.tmpl
@@ -11,10 +11,6 @@
   ; File Functions Header, used to get the current drive root.
   !include "FileFunc.nsh"
 
-  ; *Patched* Environment Variable Manipulation Header, used to add
-  ; tools to the user's PATH environment variable.
-  !include "EnvVarUpdate.nsh"
-
 ;--------------------------------
 ; Global variables and defines
   !define PRODUCT_NAME "?c.displayName"
@@ -35,7 +31,7 @@
 
   ; Default installation folder
   ; This is changed later (in .onInit) to the root directory, if possible.
-  InstallDir "$PROGRAMFILES?{when sizeof(int) == 8: "64" else: ""}\?{c.name}-?{c.version}"
+  InstallDir "$PROGRAMFILES?{when sizeof(int) == 8: "64" else: ""}\?{c.name}-?{c.version}\"
 
   ; Get installation folder from registry if available
   InstallDirRegKey HKCU "Software\c.name\c.version" ""
@@ -44,7 +40,7 @@
   RequestExecutionLevel user
 
   ; Allow installation to the root drive directory.
-  AllowRootDirInstall true
+  AllowRootDirInstall false
 
   ; Maximum compression!
   SetCompressor /SOLID /FINAL lzma
@@ -156,10 +152,14 @@
 
   ; Section for adding tools to the PATH variable
   Section "Setup Path Environment" PathSection
-     ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw"
-     ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\mingw\bin"
-     ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\bin"
-     ${EnvVarUpdate} $R0 "PATH" "A" "HKCU" "$INSTDIR\dist\babel"
+    Push "$INSTDIR\bin"
+    Call AddToPath
+    Push "$INSTDIR\dist\mingw"
+    Call AddToPath
+    Push "$INSTDIR\dist\mingw\bin"
+    Call AddToPath
+    Push "$PROFILE\.nimble\bin"
+    Call AddToPath
   SectionEnd
 
   ; The downloadable sections. These sections are automatically generated by
@@ -202,7 +202,7 @@
     ; Shortcuts
     #  if d.len >= 6:
     #    let startMenuEntry = d[5]
-    #    let e = splitFile(startMenuEntry).name.capitalize
+    #    let e = splitFile(startMenuEntry).name.capitalizeAscii
       !insertmacro MUI_STARTMENU_WRITE_BEGIN Application
         CreateShortCut "$SMPROGRAMS\$ICONS_GROUP\?{e}.lnk" "$INSTDIR\?dir\?{startMenuEntry.toWin}"
       !insertmacro MUI_STARTMENU_WRITE_END
@@ -243,10 +243,14 @@
     SetAutoClose true
 
     ; Remove entries from the PATH environment variable
-    ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw"
-    ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\mingw\bin"
-    ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\bin"
-    ${un.EnvVarUpdate} $R0 "PATH" "R" "HKCU" "$INSTDIR\dist\babel"
+    Push "$INSTDIR\bin"
+    Call un.RemoveFromPath
+    Push "$INSTDIR\dist\mingw"
+    Call un.RemoveFromPath
+    Push "$INSTDIR\dist\mingw\bin"
+    Call un.RemoveFromPath
+    Push "$PROFILE\.nimble\bin"
+    Call un.RemoveFromPath
   SectionEnd
 
 ;--------------------------------
@@ -254,5 +258,188 @@
 
   Function .onInit
     ${GetRoot} "$EXEDIR" $R0
-    strCpy $INSTDIR "$R0\?{c.name}"
+    ;strCpy $INSTDIR "$R0\?{c.name}"
   FunctionEnd
+
+
+;--------------------------------------------------------------------
+; Path functions
+;
+; Based on example from:
+; http://nsis.sourceforge.net/Path_Manipulation
+;
+; Actually based on:
+; https://www.smartmontools.org/browser/trunk/smartmontools/os_win32/installer.nsi#L636
+
+
+!include "WinMessages.nsh"
+
+; Registry Entry for environment (NT4,2000,XP)
+; All users:
+;!define Environ 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"'
+; Current user only:
+!define Environ 'HKCU "Environment"'
+
+
+; AddToPath - Appends dir to PATH
+;   (does not work on Win9x/ME)
+;
+; Usage:
+;   Push "dir"
+;   Call AddToPath
+
+Function AddToPath
+  Exch $0
+  Push $1
+  Push $2
+  Push $3
+  Push $4
+
+  ; NSIS ReadRegStr returns empty string on string overflow
+  ; Native calls are used here to check actual length of PATH
+
+  ; $4 = RegOpenKey(HKEY_CURRENT_USER, "Environment", &$3)
+  System::Call "advapi32::RegOpenKey(i 0x80000001, t'Environment', *i.r3) i.r4"
+  IntCmp $4 0 0 done done
+  ; $4 = RegQueryValueEx($3, "PATH", (DWORD*)0, (DWORD*)0, &$1, ($2=NSIS_MAX_STRLEN, &$2))
+  ; RegCloseKey($3)
+  System::Call "advapi32::RegQueryValueEx(i $3, t'PATH', i 0, i 0, t.r1, *i ${NSIS_MAX_STRLEN} r2) i.r4"
+  System::Call "advapi32::RegCloseKey(i $3)"
+
+  IntCmp $4 234 0 +4 +4 ; $4 == ERROR_MORE_DATA
+    DetailPrint "AddToPath: original length $2 > ${NSIS_MAX_STRLEN}"
+    MessageBox MB_OK "PATH not updated, original length $2 > ${NSIS_MAX_STRLEN}"
+    Goto done
+
+  IntCmp $4 0 +5 ; $4 != NO_ERROR
+    IntCmp $4 2 +3 ; $4 != ERROR_FILE_NOT_FOUND
+      DetailPrint "AddToPath: unexpected error code $4"
+      Goto done
+    StrCpy $1 ""
+
+  ; Check if already in PATH
+  Push "$1;"
+  Push "$0;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" 0 done
+  Push "$1;"
+  Push "$0\;"
+  Call StrStr
+  Pop $2
+  StrCmp $2 "" 0 done
+
+  ; Prevent NSIS string overflow
+  StrLen $2 $0
+  StrLen $3 $1
+  IntOp $2 $2 + $3
+  IntOp $2 $2 + 2 ; $2 = strlen(dir) + strlen(PATH) + sizeof(";")
+  IntCmp $2 ${NSIS_MAX_STRLEN} +4 +4 0
+    DetailPrint "AddToPath: new length $2 > ${NSIS_MAX_STRLEN}"
+    MessageBox MB_OK "PATH not updated, new length $2 > ${NSIS_MAX_STRLEN}."
+    Goto done
+
+  ; Append dir to PATH
+  DetailPrint "Add to PATH: $0"
+  StrCpy $2 $1 1 -1
+  StrCmp $2 ";" 0 +2
+    StrCpy $1 $1 -1 ; remove trailing ';'
+  StrCmp $1 "" +2   ; no leading ';'
+    StrCpy $0 "$1;$0"
+  WriteRegExpandStr ${Environ} "PATH" $0
+  SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+done:
+  Pop $4
+  Pop $3
+  Pop $2
+  Pop $1
+  Pop $0
+FunctionEnd
+
+
+; RemoveFromPath - Removes dir from PATH
+;
+; Usage:
+;   Push "dir"
+;   Call RemoveFromPath
+
+Function un.RemoveFromPath
+  Exch $0
+  Push $1
+  Push $2
+  Push $3
+  Push $4
+  Push $5
+  Push $6
+
+  ReadRegStr $1 ${Environ} "PATH"
+  StrCpy $5 $1 1 -1
+  StrCmp $5 ";" +2
+    StrCpy $1 "$1;" ; ensure trailing ';'
+  Push $1
+  Push "$0;"
+  Call un.StrStr
+  Pop $2 ; pos of our dir
+  StrCmp $2 "" done
+
+  DetailPrint "Remove from PATH: $0"
+  StrLen $3 "$0;"
+  StrLen $4 $2
+  StrCpy $5 $1 -$4 ; $5 is now the part before the path to remove
+  StrCpy $6 $2 "" $3 ; $6 is now the part after the path to remove
+  StrCpy $3 "$5$6"
+  StrCpy $5 $3 1 -1
+  StrCmp $5 ";" 0 +2
+    StrCpy $3 $3 -1 ; remove trailing ';'
+  WriteRegExpandStr ${Environ} "PATH" $3
+  SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000
+
+done:
+  Pop $6
+  Pop $5
+  Pop $4
+  Pop $3
+  Pop $2
+  Pop $1
+  Pop $0
+FunctionEnd
+
+
+; StrStr - find substring in a string
+;
+; Usage:
+;   Push "this is some string"
+;   Push "some"
+;   Call StrStr
+;   Pop $0 ; "some string"
+
+!macro StrStr un
+Function ${un}StrStr
+  Exch $R1 ; $R1=substring, stack=[old$R1,string,...]
+  Exch     ;                stack=[string,old$R1,...]
+  Exch $R2 ; $R2=string,    stack=[old$R2,old$R1,...]
+  Push $R3
+  Push $R4
+  Push $R5
+  StrLen $R3 $R1
+  StrCpy $R4 0
+  ; $R1=substring, $R2=string, $R3=strlen(substring)
+  ; $R4=count, $R5=tmp
+  loop:
+    StrCpy $R5 $R2 $R3 $R4
+    StrCmp $R5 $R1 done
+    StrCmp $R5 "" done
+    IntOp $R4 $R4 + 1
+    Goto loop
+done:
+  StrCpy $R1 $R2 "" $R4
+  Pop $R5
+  Pop $R4
+  Pop $R3
+  Pop $R2
+  Exch $R1 ; $R1=old$R1, stack=[result,...]
+FunctionEnd
+!macroend
+!insertmacro StrStr ""
+!insertmacro StrStr "un."