VERSION 2.00
Begin Form Form1 
   BackColor       =   &H00C0C0C0&
   BorderStyle     =   1  'Fixed Single
   Caption         =   "INIBAK"
   ClientHeight    =   3060
   ClientLeft      =   510
   ClientTop       =   1350
   ClientWidth     =   4935
   Height          =   3990
   Icon            =   INIBAK.FRX:0000
   Left            =   450
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   ScaleHeight     =   3060
   ScaleWidth      =   4935
   Top             =   480
   Width           =   5055
   Begin FileListBox File1 
      Height          =   225
      Left            =   360
      TabIndex        =   1
      Top             =   2280
      Visible         =   0   'False
      Width           =   1815
   End
   Begin CommonDialog CMDialog1 
      CancelError     =   -1  'True
      DefaultExt      =   "INI"
      DialogTitle     =   "Select INI File"
      Filter          =   "INI Files|*.INI"
      Left            =   3960
      Top             =   1320
   End
   Begin ListBox INIList 
      BackColor       =   &H00C0C0C0&
      Height          =   1980
      HelpContextID   =   9
      Left            =   240
      Sorted          =   -1  'True
      TabIndex        =   0
      Top             =   840
      Width           =   4455
   End
   Begin Shape Shape1 
      BackColor       =   &H00000000&
      BorderColor     =   &H00808080&
      Height          =   2820
      Index           =   1
      Left            =   120
      Top             =   120
      Width           =   4695
   End
   Begin Shape Shape1 
      BackColor       =   &H00000000&
      BorderColor     =   &H00FFFFFF&
      Height          =   2820
      Index           =   0
      Left            =   135
      Top             =   135
      Width           =   4695
   End
   Begin Label Label1 
      BackColor       =   &H00C0C0C0&
      Caption         =   "Protected INI Files:"
      FontBold        =   -1  'True
      FontItalic      =   0   'False
      FontName        =   "MS Sans Serif"
      FontSize        =   15
      FontStrikethru  =   0   'False
      FontUnderline   =   0   'False
      ForeColor       =   &H00000000&
      Height          =   375
      Left            =   240
      TabIndex        =   2
      Top             =   240
      Width           =   4455
   End
   Begin Menu mnu_Main 
      Caption         =   "&File"
      HelpContextID   =   2
      Index           =   0
      WindowList      =   -1  'True
      Begin Menu mnu_File 
         Caption         =   "&Add INI File to List"
         HelpContextID   =   4
         Index           =   0
      End
      Begin Menu mnu_File 
         Caption         =   "&Delete INI File from List"
         HelpContextID   =   5
         Index           =   1
      End
      Begin Menu mnu_File 
         Caption         =   "&Restore INI File from Backup"
         HelpContextID   =   6
         Index           =   2
      End
      Begin Menu mnu_File 
         Caption         =   "&Check INI Files Now"
         HelpContextID   =   7
         Index           =   3
      End
      Begin Menu mnu_File 
         Caption         =   "-"
         Index           =   4
      End
      Begin Menu mnu_File 
         Caption         =   "E&xit"
         HelpContextID   =   2
         Index           =   5
      End
   End
   Begin Menu mnu_Main 
      Caption         =   "&Levels"
      HelpContextID   =   3
      Index           =   1
      Begin Menu mnu_Level 
         Caption         =   "&3"
         HelpContextID   =   3
         Index           =   0
      End
      Begin Menu mnu_Level 
         Caption         =   "&4"
         HelpContextID   =   3
         Index           =   1
      End
      Begin Menu mnu_Level 
         Caption         =   "&5"
         HelpContextID   =   3
         Index           =   2
      End
      Begin Menu mnu_Level 
         Caption         =   "&6"
         HelpContextID   =   3
         Index           =   3
      End
      Begin Menu mnu_Level 
         Caption         =   "&7"
         HelpContextID   =   3
         Index           =   4
      End
      Begin Menu mnu_Level 
         Caption         =   "&8"
         HelpContextID   =   3
         Index           =   5
      End
      Begin Menu mnu_Level 
         Caption         =   "&9"
         HelpContextID   =   3
         Index           =   6
      End
      Begin Menu mnu_Level 
         Caption         =   "1&0"
         HelpContextID   =   3
         Index           =   7
      End
      Begin Menu mnu_Level 
         Caption         =   "1&1"
         HelpContextID   =   3
         Index           =   8
      End
      Begin Menu mnu_Level 
         Caption         =   "1&2"
         HelpContextID   =   3
         Index           =   9
      End
   End
   Begin Menu mnu_Main 
      Caption         =   "&Help"
      HelpContextID   =   8
      Index           =   2
      Begin Menu mnu_Help 
         Caption         =   "&Contents"
         HelpContextID   =   8
         Index           =   0
      End
      Begin Menu mnu_Help 
         Caption         =   "&Search for Help On..."
         HelpContextID   =   8
         Index           =   1
      End
      Begin Menu mnu_Help 
         Caption         =   "&How to Use Help"
         HelpContextID   =   8
         Index           =   2
      End
      Begin Menu mnu_Help 
         Caption         =   "-"
         Index           =   3
      End
      Begin Menu mnu_Help 
         Caption         =   "&About INIBAK..."
         HelpContextID   =   8
         Index           =   4
      End
   End
End
Option Explicit
' INI files don't care about capitalization, so neither does INIBAK.
' The next line causes comparisons to default to Text mode.
Option Compare Text

Function AddFirstBackup% (ByVal FullPath$)
  ' Add a new INI file to the list of files protected, and make an
  ' initial backup copy of that file.
  Dim Path$, fName$, fExt$
  FileSplit FullPath, Path, fName, fExt
  ' Clear out any leftover backups - these SHOULD not exist
  File1.Pattern = fName + ".0*"
  File1.Refresh
  If File1.ListCount > 0 Then
    Dim Result%, Msg$, MsgType%
    Msg = "Old backups of " + fName + ".INI exist.  Delete?"
    MsgType = MB_ICONQUESTION Or MB_YESNOCANCEL
    Result = MsgBox(Msg, MsgType, MsgTitle)
    Select Case Result
      Case IDCANCEL
        ' Cancel means cancel the whole add file operation
        AddFirstBackup = False
        Exit Function
      Case IDYES
        ' Yes, delete the old backup files
        Dim N%
        For N = 0 To File1.ListCount - 1
          KillSafe IniBakDir + File1.List(N)
        Next N
      Case IDNO
        ' No, leave the old files, put new copy in the .000 slot
        MakeRoom fName
    End Select
  End If
  FileCopy FullPath, IniBakDir + fName + ".000"
  AddFirstBackup = True
End Function

Sub FileSplit (ByVal FullPath$, fPath$, fName$, fExt$)
' Given a full pathname in FullPath, e.g. d:\path\filename.ext,
' FileSplit puts path (d:\path) in fPath, name (filename) in fName,
' and extension (ext) in fExt
  Dim Start%, P, Posn%
  Start = 1
  P = Start
  Do Until P = 0
    P = InStr(Start, FullPath, "\")
    If P <> 0 Then Posn = P
    Start = Posn + 1
  Loop
  fPath$ = Left$(FullPath, Posn)
  fName = Mid$(FullPath, Len(fPath) + 1)
  Posn = InStr(fName, ".")
  If Posn = 0 Then
    fExt = ""
  Else
    fExt = Mid$(fName, Posn + 1)
    fName = Left(fName, Posn - 1)
  End If
End Sub

Sub Form_Load ()
  ' Get the Windows directory and backup directory
  WindowsDir = String$(145, 0)
  Dim Success%
  Success = GetWindowsDirectory(WindowsDir, 144)
  WindowsDir = Left$(WindowsDir, InStr(WindowsDir, Chr$(0)) - 1)
  ' Allow for possibility that WindowsDir might be a root directory
  If Right$(WindowsDir, 1) = "\" Then
    IniBakDir = WindowsDir
  Else
    IniBakDir = WindowsDir + "\"
  End If
  ' ANSI Character 162 is the cent sign: "INIBAK."
  IniBakDir = IniBakDir + "INIBAK" + String$(2, 162) + "." + String$(3, 162)
  ' Create the INI backup directory, if it doesn't already exist.
  On Error Resume Next
  MkDir IniBakDir
  On Error GoTo 0
  IniBakDir = IniBakDir + "\"
  ' Get and verify the number of levels
  NumLevels = GetPrivateProfileInt("Levels", "Levels", 5, "INIBAK.INI")
  If NumLevels < MinLevels Then NumLevels = MinLevels
  If NumLevels > MaxLevels Then NumLevels = MaxLevels
  mnu_Level(NumLevels - MinLevels).Checked = True
  ' Initialize the common dialog to start in the Windows directory
  CmDialog1.InitDir = WindowsDir
  CmDialog1.Flags = OFN_PATHMUSTEXIST Or OFN_NOCHANGEDIR
  ' Initialize the invisible file list to stay in the backup directory
  File1.Path = IniBakDir
  ' Read up to ten INI names from INIBAK.INI
  Dim N%, OneIni$
  For N = 1 To 10
    ' GPPS is a "wrapper" for GetPrivateProfileString
    Success = GPPS("INI Files", "File" + Str$(N), "", OneIni, 144, "INIBAK.INI")
    If OneIni <> "" Then IniList.AddItem OneIni
  Next N
  Form1.Show
  If GlobalFindAtom(IniBakDir) = 0 Then
    Dim TheAtom%
    TheAtom = GlobalAddAtom(IniBakDir)
    menuCheck
  End If
  If IniList.ListCount > 0 Then IniList.ListIndex = 0
End Sub

Function GPPS% (ByVal lpAppName$, ByVal lpKeyName$, ByVal lpDefault$, lpRetString$, ByVal nSize%, ByVal lpFileName$)
' A "wrapper" for GetPrivateProfileString.  It handles pre-allocating
' the buffer string and chopping off the extra 0 bytes afterward.  Use
' *only* for getting one key, NOT for getting all keys in a section.
  lpRetString = String$(nSize + 1, 0)
  GPPS = GetPrivateProfileString(lpAppName, lpKeyName, lpDefault, lpRetString, nSize, lpFileName)
  lpRetString = Left$(lpRetString, InStr(lpRetString, Chr$(0)) - 1)
End Function

Function IniChanged% (ByVal OrigName$)
  ' Compare the INI file named in the OrigName parameter with its most
  ' recent backup.
  Dim xPath$, xName$, xExt$
  FileSplit OrigName, xPath, xName, xExt
  ' In case the user has deleted one or more of the backup files, the
  ' TakeUpSlack function renames them in order to *.000, *.001, etc.
  TakeUpSlack xName
  ' If the user has *deleted* all backups for this file, *make* a
  ' backup and get out.
  Dim BakName$
  BakName = xName + ".000"
  If File1.ListCount = 0 Then
    Msg = "No backup exists for " + OrigName + ".  Creating backup."
    MsgType = MB_ICONINFORMATION
    MsgBox Msg, MsgType, MsgTitle
    FileCopy OrigName, IniBakDir + BakName
    Exit Function
  End If
  ' The Normalize subroutine puts an INI file's lines in order, so it
  ' can be easily compared with another INI file.
  Dim NowNorm$, BakNorm$
  NowNorm = IniBakDir + "TEMP_NOW.$$$"
  BakNorm = IniBakDir + "TEMP_BAK.$$$"
  Normalize OrigName, NowNorm
  Normalize IniBakDir + BakName, BakNorm
  Dim FNumNow%, FNumBak%
  FNumNow = FreeFile
  Open NowNorm For Input Access Read Shared As FNumNow
  FNumBak = FreeFile
  Open BakNorm For Input Access Read Shared As FNumBak
  Dim nowLine$, bakLine$, NoLine$, Comp%
  nowLine = ""
  bakLine = ""
  NoLine = Chr$(253) ' greater than any possible string in the file.
  ' The two list boxes in Form2 serve to hold lines that DIFFER between
  ' the two normalized files.  List1 holds files that are in the
  ' current version but not in the backup (added lines) and List2 holds
  ' lines that are in the backup but not in the current version
  ' (deleted lines).  If the value of a key is CHANGED, the old line
  ' appears as deleted and the new line as added.
  Form2.List1.Clear
  Form2.List2.Clear
  Do While (bakLine <> NoLine) Or (nowLine <> NoLine)
    Comp = StrComp(nowLine, bakLine, 1)
    Select Case Comp
      Case Is < 0
        ' line in current file is < line in bak file - added
        Form2.List1.AddItem nowLine
        ' get another line from current file
        nowLine = NoLine
        If Not EOF(FNumNow) Then Line Input #FNumNow, nowLine
      Case 0
        ' get another line from both files
        nowLine = NoLine
        If Not EOF(FNumNow) Then Line Input #FNumNow, nowLine
        bakLine = NoLine
        If Not EOF(FNumBak) Then Line Input #FNumBak, bakLine
      Case Is > 0
        ' line in current file is > line in bak file - deleted
        Form2.List2.AddItem bakLine
        ' get another line from bak file
        bakLine = NoLine
        If Not EOF(FNumBak) Then Line Input #FNumBak, bakLine
    End Select
  Loop
  Close FNumBak, FNumNow
  KillSafe NowNorm
  KillSafe BakNorm
  IniChanged = Form2.List1.ListCount + Form2.List2.ListCount > 0
End Function

Sub KillSafe (ByVal FileName$)
  ' If a file has been deleted by another process before INIBAK gets
  ' around to deleting it, KILL alone would cause an error.
  On Error Resume Next
  Kill FileName$
  On Error GoTo 0
End Sub

Sub MakeRoom (ByVal fName$)
  TakeUpSlack fName
  ' Make room for new .000 file by pushing others down the stack.
  File1.Pattern = fName + ".0*"
  File1.Refresh
  ' If there are too many, delete until there's room for one more.
  ' Since the file list is sorted by name, the last item in the list
  ' is the one with the greatest (i.e. oldest) extension.
  Dim N%
  For N = NumLevels - 1 To File1.ListCount - 1
    KillSafe IniBakDir + File1.List(N)
  Next N
  File1.Pattern = fName + ".0*"
  File1.Refresh
  ' Now rename them to one number higher
  Dim OldName$, NewName$ ' variables to keep line from getting too long
  For N = File1.ListCount - 1 To 0 Step -1
    OldName = IniBakDir + File1.List(N)
    NewName = IniBakDir + fName + "."
    NewName = NewName + Right$("000" + Format$(N + 1), 3)
    Name OldName As NewName
  Next N
End Sub

Function MatchFileNum% (ByVal fName$)
  ' Given an INI file name, return the number of the "File#=" key
  ' holding that name.  Returns 11 if not found.  Pass an empty
  ' string and it returns the first unused file number.
  Dim N%, Success%, IniName$
  N = 0
  ' Start IniName with a value that CAN'T be a filename
  IniName = fName + "./."
  Do Until (IniName = fName) Or (N > 10)
    N = N + 1
    ' GPPS is a "wrapper" for GetPrivateProfileString
    Success = GPPS("INI Files", "File" + Str$(N), "", IniName, 144, "INIBAK.INI")
  Loop
  MatchFileNum = N
End Function

Sub menuAdd ()
  ' Add a new INI file to the list of those protected.
  ' Because CmDialog1 has its CancelError field set to TRUE, it will
  ' generate an error if the user picks Cancel.  This allows the
  ' subroutine to skip to the end if the user cancels.
  On Error GoTo Cancelled
  CmDialog1.Action = 1 ' this displays the Open File dialog
  If CmDialog1.Flags And OFN_EXTENSIONDIFFERENT Then
    Msg = CmDialog1.Filename + " is the wrong file type. "
    Msg = Msg + "INIBAK protects INI files only."
    MsgType = MB_ICONSTOP
    MsgBox Msg, MsgType, MsgTitle
    Exit Sub
  End If
  ' Make sure file isn't already under our protection
  If MatchFileNum(CmDialog1.Filename) < 11 Then
    Msg = "File " + CmDialog1.Filename + " is already protected "
    Msg = Msg + "by INIBAK."
    MsgType = MB_ICONINFORMATION
    MsgBox Msg, MsgType, MsgTitle
    Exit Sub
  End If
  Dim N%
  ' Find a vacant slot in INIBAK.INI
  N = MatchFileNum("")
  If N > 10 Then
    Msg = "INIBAK is already protecting ten files.  Please delete "
    Msg = Msg + "one before adding another."
    MsgType = MB_ICONSTOP
    MsgBox Msg, MsgType, MsgTitle
    Exit Sub
  End If
  ' Add the first backup, add the file to the list, and write the
  ' file's full pathname to INIBAK.INI
  If AddFirstBackup(CmDialog1.Filename) Then
    IniList.AddItem CmDialog1.Filename
    Dim Success%
    Success = WritePrivateProfileString("INI Files", "File" + Str$(N), CmDialog1.Filename, "INIBAK.INI")
  End If
  Exit Sub
Cancelled:
  Exit Sub
End Sub

Sub menuCheck ()
  ' Compare each protected INI file with its most recent backup.  If
  ' changed, display differences and offer to store the new version,
  ' restore the old, or do nothing.  Called from menu choice *or*
  ' called automatically the FIRST time INIBAK runs during a given
  ' Windows session
  Load Form2
  DoEvents
  Dim N%
  Screen.MousePointer = 11 ' hourglass cursor
  For N = 0 To IniList.ListCount - 1
    ' Visibly select the Nth item on-screen
    IniList.ListIndex = N
    Label1.ForeColor = QBColor(15) ' white
    Label1.BackColor = QBColor(4) ' red
    Dim xPath$, xName$, xExt$
    FileSplit IniList.List(N), xPath, xName, xExt
    Label1.Caption = "Checking " + xName + "." + xExt + "..."
    Label1.Refresh
    ' The IniChanged procedure stores added and deleted lines in
    ' list boxes belonging to Form2.  That makes it easy to display
    ' the changes for the user.
    If IniChanged(IniList.List(N)) Then
      ' Restore the arrow cursor for use with Warning form
      Screen.MousePointer = 0
      Form2.Label1.Caption = IniList.List(N)
      If Form2.List1.ListCount > 0 Then Form2.List1.ListIndex = 0
      If Form2.List2.ListCount > 0 Then Form2.List2.ListIndex = 0
      Form2.Tag = 0
      Form2.Show MODAL
      Select Case Form2.Tag
        Case R_SAVENEW
          MakeRoom xName
          FileCopy IniList.List(N), IniBakDir + xName + ".000"
        Case R_OVERWRITE
          TakeUpSlack xName
          FileCopy IniList.List(N), IniBakDir + xName + ".000"
        Case R_RESTORE
          menuRestore IniList.List(N)
        Case R_IGNORE
          ' do nothing
      End Select
      ' Call DoEvents to allow main form to redisplay
      DoEvents
      Screen.MousePointer = 11
    End If
  Next N
  Unload Form2
  Label1.Caption = "Protected INI Files:"
  Label1.ForeColor = 0
  Label1.BackColor = &HC0C0C0
  Screen.MousePointer = 0
End Sub

Sub menuDelete (ByVal FileName$)
  ' Delete the selected file from the list of those protected
  Msg = "Are you sure you want to remove INIBAK's protection from "
  Msg = Msg + FileName + "?"
  MsgType = MB_ICONQUESTION Or MB_YESNO
  If MsgBox(Msg, MsgType, MsgTitle) = IDNO Then
    Exit Sub
  End If
  Dim IniName$, N%, Success%
  ' Locate the INIBAK.INI entry for this file
  N = MatchFileNum(FileName)
  If N > 10 Then
    Msg = "The file " + FileName + " is not present in INIBAK.INI. "
    Msg = Msg + "INIBAK.INI has been deleted or modified. Please do "
    Msg = Msg + "not make changes to INIBAK.INI."
    MsgBox Msg, MB_ICONINFORMATION, MsgTitle
  Else
    Success = WritePrivateProfileString("INI Files", "File" + Str$(N), "", "INIBAK.INI")
  End If
  ' Delete associated backup files
  Dim xPath$, xName$, xExt$
  FileSplit FileName, xPath$, xName$, xExt$
  File1.Pattern = xName + ".0*"
  If File1.ListCount > 0 Then
    For N = File1.ListCount - 1 To 0 Step -1
      KillSafe IniBakDir + File1.List(N)
    Next N
  End If
  Dim NewIndex%
  NewIndex = IniList.ListIndex
  IniList.RemoveItem IniList.ListIndex
  If NewIndex > IniList.ListCount - 1 Then NewIndex = NewIndex - 1
  IniList.ListIndex = NewIndex
End Sub

Sub menuRestore (ByVal OrigName$)
  ' Form3 displays the list of backups for the selected file.  The user
  ' can choose to copy one of them over the file.
  Load Form3
  Form3.File1.Path = IniBakDir
  Dim xPath$, xName$, xExt$
  FileSplit OrigName, xPath, xName, xExt
  Form3.Label1.Caption = "Restoring " + OrigName + " from backup"
  Form3.File1.Pattern = xName + ".0*"
  Form3.Tag = 0
  Form3.Show MODAL
  If Form3.Tag = R_RESTORE Then
    Dim BakName$
    BakName = IniBakDir + Form3.File1.FileName
    FileSplit BakName, xPath, xName, xExt
    FileCopy BakName, OrigName
    ' If the selected backup was not the most recent, "promote" it
    ' into the 000 spot.
    If xExt <> "000" Then
      ' Get rid of the temporary file with $$$ extension, if it exists
      KillSafe xPath + xName + ".$$$"
      ' Rename the selected backup to the temporary $$$ extension
      Name BakName As xPath + xName + ".$$$"
      ' Make room for new 000 file
      MakeRoom xName
      ' Rename the selected backup to the 000 extension.
      Name xPath + xName + ".$$$" As xPath + xName + ".000"
    End If
    Msg = "Changes to INI files do not always take effect until you "
    Msg = Msg + "restart Windows.  Restart Windows now?"
    MsgType = MB_ICONQUESTION Or MB_YESNO
    If MsgBox(Msg, MsgType, MsgTitle) = IDYES Then
      Dim Success%
      Success = ExitWindows(EW_RESTARTWINDOWS, 0)
    End If
  End If
  Unload Form3
End Sub

Sub mnu_File_Click (Index As Integer)
  Msg = "First, select an INI file from the list"
  MsgType = MB_ICONINFORMATION
  Select Case Index
    Case 0
      menuAdd
    Case 1
      If IniList.ListIndex >= 0 Then
        menuDelete IniList.Text
      Else
        MsgBox Msg, MsgType, MsgTitle
      End If
    Case 2
      If IniList.ListIndex >= 0 Then
        menuRestore IniList.Text 'IniList.List(IniList.ListIndex)
      Else
        MsgBox Msg, MsgType, MsgTitle
      End If
    Case 3
      menuCheck
    Case 5
      Unload Form1
  End Select
End Sub

Sub mnu_Help_Click (Index As Integer)
  ' WinHelp and WinHelpByNum are declared in INIBAK.BAS.  VB programs
  ' have automatic context-sensitive help, but they need these API
  ' functions for menu-selected help.
  Dim Success%
  Select Case Index
    Case 0
      Success = WinHelpByNum(Form1.hWnd, App.HelpFile, HELP_CONTENTS, 0)
    Case 1
      Success = WinHelp(Form1.hWnd, App.HelpFile, HELP_PARTIALKEY, "")
    Case 2
      Success = WinHelpByNum(Form1.hWnd, App.HelpFile, HELP_HELPONHELP, 0)
    Case 4
      ' The generic About Box is implemented in the files ABOUTBOX.TXT
      ' and ABOUTBOX.BAS.  Function DisplayAboutBox handles it all.
      DisplayAboutBox Form1, "INIBAK", 1#, 1994, "Neil J. Rubenking", "First Published in PC Magazine", "May 17, 1994, U.S. Edition", 0, False, 0, &HC0C0C0
  End Select
End Sub

Sub mnu_Level_Click (Index As Integer)
  ' Change the number of levels of backup
  Dim NuLevel%
  NuLevel = Index + 3
  ' Clicked the SAME number?  Do nothing!
  If NuLevel = NumLevels Then Exit Sub
  ' Check the existing backups for any beyond the new level
  File1.Pattern = "*.0*"
  File1.Refresh
  Dim N%, OldCount%
  OldCount = 0
  For N = File1.ListCount - 1 To 0 Step -1
    If Right(File1.List(N), 3) >= NuLevel Then OldCount = OldCount + 1
  Next N
  If OldCount > 0 Then
    Dim Msg$, MsgType%
    Msg = "Delete existing backups older than " + Str$(NuLevel)
    Msg = Msg + " levels?"
    MsgType = MB_ICONQUESTION Or MB_YESNOCANCEL
    Select Case MsgBox(Msg, MsgType, MsgTitle)
      Case IDYES
        For N = File1.ListCount - 1 To 0 Step -1
          If Right(File1.List(N), 3) >= NuLevel Then
            KillSafe IniBakDir + File1.List(N)
          End If
        Next N
      Case IDNO
        ' do nothing; old backups will get eliminated as used
      Case IDCANCEL
        ' Exit, DON'T change level
        Exit Sub
    End Select
  End If
  ' Un-check the old menu item, checkmark the new
  mnu_Level(NumLevels - MinLevels).Checked = False
  NumLevels = NuLevel
  mnu_Level(NumLevels - MinLevels).Checked = True
  ' Write the new level value to INIBAK.INI
  Dim Success%
  Success = WritePrivateProfileString("Levels", "Levels", Str$(NumLevels), "INIBAK.INI")
End Sub

Sub Normalize (ByVal InName$, ByVal OutName$)
  ' An INI file is composed of sections and keys.  A section heading is
  ' enclosed in [] square brackets, and a key takes the form
  ' keyname=value.  Each key belongs to the section whose heading is
  ' above it.  The order of the sections and the order of keys within
  ' sections does not matter.  This procedure takes an INI file and
  ' generates a "normalized" file in which each line has the form
  ' "[section]key", and the lines are in sorted order.
  Dim FNumIn%, FNumOut%
  FNumIn = FreeFile
  Open InName For Input Access Read Shared As FNumIn
  FNumOut = FreeFile
  Open OutName For Output Access Write As FNumOut
  Dim aLine$, Locn&
  ' Get the section headings and store them in List1.  The list has
  ' the sorted property, so they're automatically sorted.  Along with
  ' each section heading, store its *location* in the INI file.
  Form2.List1.Clear
  Do While Not EOF(FNumIn)
    Locn = Seek(FNumIn)
    Line Input #FNumIn, aLine
    aLine = Trim$(aLine)
    If (Left$(aLine, 1) = "[") And (Right$(aLine, 1) = "]") Then
      Form2.List1.AddItem aLine + Str$(Locn)
    End If
  Loop
  Dim N%, M%, bLine$
  ' Stepping through List1 in order, seek to the start of each section
  ' heading and store the keys from that section into List2.  Again
  ' the list is automatically sorted.  Comments and blank lines are
  ' ignored.
  For N = 0 To Form2.List1.ListCount - 1
    aLine = Form2.List1.List(N)
    Form2.List2.Clear
    Locn = Val(Mid$(aLine, InStr(aLine, "]") + 1))
    Seek FNumIn, Locn
    ' Re-read the section title line
    Line Input #FNumIn, aLine
    aLine = Trim$(aLine)
    Do
      If EOF(FNumIn) Then Exit Do
      Line Input #FNumIn, bLine
      bLine = Trim$(bLine)
      ' If we've reached the next section header, exit the loop
      If Left$(bLine, 1) = "[" Then Exit Do
      If (Left$(bLine, 1) <> ";") And (bLine <> "") Then
        Form2.List2.AddItem bLine
      End If
    Loop
    ' Write out the sorted list of keys, prefixing each with the
    ' section name.
    For M% = 0 To Form2.List2.ListCount - 1
      Print #FNumOut, aLine; " "; Form2.List2.List(M)
    Next M
  Next N
  Close FNumIn, FNumOut
End Sub

Sub SaveLevel ()
  ' Save the new value of NumLevels to INIBAK.INI
  Dim Success%
  Success% = WritePrivateProfileString("Levels", "Levels", Str$(NumLevels), "INIBAK.INI")
End Sub

Sub TakeUpSlack (ByVal fName$)
  ' It's possible that the user has deleted one or more of the backup
  ' files.  Here we get a list of them in order and check that the
  ' extensions are 000, 001, and so on.  If not, we rename them so
  ' they are.
  Dim N%, NextName$
  File1.Pattern = fName + ".0*"
  File1.Refresh
  For N = 0 To File1.ListCount - 1
    NextName = fName + "." + Right$("000" + Format$(N), 3)
    If StrComp(File1.List(N), NextName, 1) <> 0 Then
      Name IniBakDir + File1.List(N) As IniBakDir + NextName
    End If
  Next N
End Sub

