[CLASS] RegFile - .reg file implementation

Post your working scripts, libraries and tools.
User avatar
cyruz
Posts: 345
Joined: 30 Sep 2013, 13:31

[CLASS] RegFile - .reg file implementation

Post by cyruz » 29 Jan 2023, 01:26

Hi guys,

the following class implements a .reg file as object, allowing for key/subkey population and string/file output.

Generated files try to be as close as possible to .regs exported by the Registry Editor and can be imported.

There is currently no ReadFile function, because I don't need a parser.

String manipulation is basic and kinda slow, please feel free to improve.

Code: Select all

; ----------------------------------------------------------------------------------------------------------------------
; Class ........: RegFile
; Description ..: Class representing a .reg file. Allows managing keys/subkeys and writing the object to string or file.
; Constructor ..: RegFile(sFileName)
; Description ..: Construct the RegFile instance object (referred next as "objRegFile").
; Parameters ...: sFileName - Full path to the desired file.
; Return .......: RegFile instance.
; Pub. Method ..: objRegFile.Add(sKey, sSubKey, sType, sVal)
; Description ..: Add a subkey to the specified key. A validity check will be performed, then Keys will be saved as
; ..............: items in the "RegRoot" Map instance variable and contain SubKeys defined as simple objects with Name,
; ..............: Type and Value properties. "Defs" properties (NameDef, TypeDef, ValueDef) are the definitions used in
; ..............: the .reg file and will be processed and assigned by the script.
; Parameters ...: sKey    - Full name of the Key (eg: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Edge).
; ..............: sSubKey - Name of the SubKey (eg: TaskbarAutoPin). "Default" SubKey must be assigned with "@" and use
; ..............:           the "REG_SZ" Type.
; ..............: sType   - One of the possible accepted value types:
; ..............:           * REG_SZ        = String value.
; ..............:           * REG_MULTI_SZ  = MultiString value. Strings must be newline (`n) separated. Trailing 
; ..............:                             newlines are omitted.
; ..............:           * REG_EXPAND_SZ = Expandable string. Can't contain newlines (`n).
; ..............:           * REG_DWORD     = 32 bit Integer.
; ..............:           * REG_QWORD     = 64 bit Integer.
; ..............:           * REG_BINARY    = Binary buffer. Must be an AutoHotkey Buffer object.
; ..............: sValue  - SubKey Value, reflecting the specified Type.
; Pub. Method ..: objRegFile.Del(sKey, sSubKey:="")
; Description ..: Delete a Key or a SubKey.
; Parameters ...: sKey    - Key to be deleted or where to locate the SubKey.
; ..............: sSubKey - SubKey to be deleted. Leave it empty to delete the Key..
; Pub. Method ..: objRegFile.WriteFile()
; Description ..: Write the object to file, creating a valid .reg file that can be imported by the registry editor.
; ..............: The file is UTF-16 with a Little Endian BOM. Every call to WriteFile() overrides the file in place.
; Pub. Method ..: objRegFile.ToString()
; Description ..: Write the object to string, with the same format used to create the .reg file.
; Return .......: A string representing the .reg file.
; AHK Version ..: AHK v2 x32/64 Unicode
; Author .......: cyruz - http://ciroprincipe.info
; License ......: WTFPL - http://www.wtfpl.net/txt/copying/
; Changelog ....: Jan. 28, 2023 - v0.0.1 - First version.
; ..............: Jan. 30, 2023 - v0.0.2 - Fixed variable wording (Def >> NameDef).
; ----------------------------------------------------------------------------------------------------------------------

class RegFile
{
    ; Map of registry types and their file definition counterpart.
    static TYPEDEFS := Map( "REG_SZ",        ""
                          , "REG_MULTI_SZ",  "hex(7):"
                          , "REG_EXPAND_SZ", "hex(2):"
                          , "REG_DWORD",     "dword:"
                          , "REG_QWORD",     "hex(b):"
                          , "REG_BINARY",    "hex:" )

    ; Array of registry types not requiring a column break in the file.
    static NOBREAKTYPES := [ "REG_SZ", "REG_DWORD", "REG_QWORD" ]
    
    ; Regular expression to validate keys.
    static REGVALIDATOR := "(HK(?:EY_CLASSES_ROOT|EY_LOCAL_MACHINE|EY_USERS|EY_CURRENT_(?:USER|CONFIG))(?:64)?)((?:\\[^\\]+)+)"
    
    ; Registry Version to be used as header in the file.
    static REGVERSION := "Windows Registry Editor Version 5.00"
 
    ; Newlines in the registry files are CRLF.
    static NEWLINE := "`r`n"
    
    regRoot := Map()
    
    __New(sFileName)
    {
        if !FileExist(RegExReplace(sFileName, "[^\\]+$"))
            throw Error("File path not valid: " sFileName)
    
        this.FileName := sFileName
        return this
    }
    
    Add(sKey, sSubKey, sType, sVal)
    {
        RegFile.__Validate(&sKey, &sSubKey, &sType, &sVal)
        
        if !this.regRoot.Has(sKey)
        {
            this.RegRoot[sKey]         := Map()
            this.RegRoot[sKey].Name    := sKey
            this.RegRoot[sKey].NameDef := "[" sKey "]"
        }
        
        ; Windows Registry doesn't allow for REG_MULTI_SZ values to contain empty strings
        ; and, due to the trailing newlines being treated as such, we remove all of them.
        (sType = "REG_MULTI_SZ") && RegFile.__StrRemTrailNewLines(&sVal) 
        
        this.regRoot[sKey][sSubKey] := { Name:     sSubKey
                                       , NameDef:  sSubKey = "@" ? sSubKey : "`"" sSubKey "`""
                                       , Type:     sType
                                       , TypeDef:  RegFile.TYPEDEFS[sType]
                                       , Value:    sVal
                                       , ValueDef: RegFile.__GetValueDef(&sType, &sVal) }
    }
    
    Del(sKey, sSubKey:="")
    {
        (sSubKey == "")
        ? this.regRoot.Delete(sKey)
        : this.regRoot[sKey].Delete(sSubKey)
    }
    
    WriteFile()
    {
        f := FileOpen(this.FileName, "w", "UTF-16")
        f.Write(RegFile.REGVERSION RegFile.NEWLINE)
        
        for ,okey in this.regRoot
        {
            f.Write(RegFile.NEWLINE okey.NameDef RegFile.NEWLINE)
            for ,osub in okey
                f.Write(RegFile.__StrPrintRecord(&osub) RegFile.NEWLINE)
        }
        
        f.Write(RegFile.NEWLINE)
        f.Close()
    }
    
    ToString()
    {
        str := RegFile.REGVERSION RegFile.NEWLINE
        
        for ,okey in this.regRoot
        {
            str .= RegFile.NEWLINE okey.NameDef RegFile.NEWLINE
            for ,osub in okey
                str .= RegFile.__StrPrintRecord(&osub) RegFile.NEWLINE
        }
        
        return str .= RegFile.NEWLINE
    }

    static __Validate(&sKey, &sSubKey, &sType, &sVal)
    {
        if !RegExMatch(sKey, RegFile.REGVALIDATOR)
            throw Error("Key not valid!")
            
        if !RegFile.TYPEDEFS.Has(sType)
            throw Error("sType must be REG_SZ | REG_MULTI_SZ | REG_EXPAND_SZ | REG_DWORD | REG_QWORD | REG_BINARY")
            
        if sSubKey = "@" && sType != "REG_SZ"
            throw Error("Default key must be of REG_SZ type.")
            
        switch sType
        {
            case "REG_SZ", "REG_MULTI_SZ", "REG_EXPAND_SZ":
            {
                if Type(sVal) != "String"
                    throw Error("REG_SZ/REG_MULTI_SZ/REG_EXPAND_SZ value must be a string.")
                if (sType = "REG_SZ" || sType = "REG_EXPAND_SZ") && InStr(sVal, "`n")
                    throw Error("REG_SZ/REG_EXPAND_SZ values can't contain newlines.")
            }
            case "REG_DWORD", "REG_QWORD":
            {
                if !IsInteger(sVal)
                    throw Error("REG_DWORD/REG_QWORD value must be an integer.")
            }
            case "REG_BINARY":
            {
                if !IsObject(sVal) || Type(sVal) != "Buffer"
                    throw Error("REG_BINARY value must be a buffer object.")
            }
            default: throw Error("Undefined type!")    
        }
    }
    
    ; Create a Value Definition for each Type, defining how the value will be saved in the .reg file.
    ; More specifically:
    ; REG_SZ        = Simple quoted strings.
    ; REG_MULTI_SZ  = MultiString, 1 per line. Saved in hex format, adding an additional final string terminator
    ;                 and replacing all newlines with specific string terminators.
    ; REG_EXPAND_SZ = String that can be expanded. Saved in hex format. Does not allow for newlines.
    ; REG_DWORD     = 32 bit integer, saved as a DWORD (UInt).
    ; REG_QWORD     = 64 bit integer, saved in hex format.
    ; REG_BINARY    = Binary buffer, saved in hex format.
    ; All hex data will be written in a comma separated list of bytes in hex format without the "0x" prefix.
    
    static __GetValueDef(&sType, &sVal)
    {
        switch sType
        {
            case "REG_SZ":        valueDef := "`"" sVal "`""
            case "REG_MULTI_SZ":  valueDef := RegFile.__StrToCsvHex(&sVal) ",00,00" ; +1 string terminator.
            case "REG_EXPAND_SZ": valueDef := RegFile.__StrToCsvHex(&sVal)
            case "REG_DWORD":     valueDef := Format("{:08x}", sVal)
            case "REG_QWORD":     valueDef := RegFile.__Int64ToCsvHex(&sVal)
            case "REG_BINARY":    valueDef := RegFile.__BufToCsvHex(sVal.Ptr, sVal.Size)           
        }
        
        return valueDef
    }
    
    static __StrToCsvHex(&sVal)
    {
        ; For REG_MULTI_SZ values, replace newlines with string terminators.
        return StrReplace(RegFile.__BufToCsvHex(StrPtr(sVal), StrLen(sVal)*2+2), "0a,00", "00,00")
    }
    
    static __Int64ToCsvHex(&sVal)
    {
        NumPut("UInt64", sVal, buf:=Buffer(8))
        return RegFile.__BufToCsvHex(buf.Ptr, buf.Size)
    }
    
    static __BufToCsvHex(pBuf, nSz)
    {
        VarSetStrCapacity(&str:="", nSz*4)
        loop nSz
            str .= Format("{:02x}", NumGet(pBuf, A_Index-1, "UChar")) ","
        
        return SubStr(str, 1, -1)
    }
    
    static __StrRemTrailNewLines(&sVal)
    {
        ptr := StrPtr(sVal), len := StrLen(sVal)
        loop
        {
            if InStr(RegFile.NEWLINE, SubStr(sVal, -(A_Index), 1)) && NumPut("UShort", 0, ptr, (len-A_Index)*2)
                continue
            else break
        }
        VarSetStrCapacity(&sVal, -1)
    }
    
    ; Print a full record (.reg line) in the following format:
    ; "SubKey"=TypeDef:ValueDef
    ; Apply a column break for the following types: REG_MULTI_SZ, REG_EXPAND_SZ, REG_BINARY.
    
    static __StrPrintRecord(&oSubKey, nColBreak:=77)
    {
        ; No column break for no break types.
        for ,v in RegFile.NOBREAKTYPES
            if RegFile.TYPEDEFS[v] = oSubKey.TypeDef
                return oSubKey.NameDef "=" oSubKey.TypeDef oSubKey.ValueDef
        
        str := ""
        row := oSubKey.NameDef "=" oSubKey.TypeDef
        idx := (len:=StrLen(row)) < nColBreak ? len : nColBreak
        cur := 1
        len := StrLen(oSubKey.ValueDef)
        
        ; Each token is "xx,", aside from last, being only "xx".
        while cur < len
        {
            if idx >= nColBreak
                 str .= row "\" RegFile.NEWLINE "  "
               , row := ""
               , idx := 2 ; The 2 spaces in the next line.
            else row .= (tkn:=SubStr(oSubKey.ValueDef, cur, 3))
               , idx += 3
               , cur += 3
        }
        
        return str .= row
    }
}

/* Test Code:

#SingleInstance force
#Include <Class_RegFile>

a := RegFile("c:\Users\itoff\Desktop\test.reg")

a.Add("HKEY_CURRENT_USER\Test", "@", "REG_SZ", "Ziocagnaccio")

a.Add("HKEY_CURRENT_USER\Test", "AString", "REG_SZ", "asdjhjka hkj jkh ajkhs jkdhajksh djkasdjkhaskjdha sjkda sd")

a.Add("HKEY_CURRENT_USER\Test", "ADwordVal", "REG_DWORD", 123)

a.Add("HKEY_CURRENT_USER\Test", "AQwordVal", "REG_QWORD", 10953338411770462354)

a.Add("HKEY_CURRENT_USER\Test", "AMultiString", "REG_MULTI_SZ", "jksfdka jksdjf kljas dkfljasjd fljk jsadflk;djsfljfd`nkljsl;kdfjklsdfj;lk ajkfka sjdfkjasdfj;kljkf`nkljask;dfjpowiopierpowiporiweopriweproweporwe")

a.Add("HKEY_CURRENT_USER\Test", "AExpandableString", "REG_EXPAND_SZ", "asdf;oipo iwipoerpowei poopi23p4832i4;l;dlgkk;l34wk;l543 536436 45   ")

bin := "81 39 80 13 09 81 03 21 73 98 76 19 78 39 87 12 39 81 27 3B 98 12 73 71 37 98 17 39 17 38 87 18 91 78 98 99 81 78 99 8A 98 A1 98 A7 23 98 71 A8 39 71 87 38 A1 E8 A1 A1 20 18 91 74 81 A9 84"
arr := StrSplit(bin, A_Space)
buf := Buffer(arr.Length)
for ,v in arr
   NumPut("UChar", "0x" v, buf.Ptr, A_Index-1)

a.Add("HKEY_CURRENT_USER\Test", "Binary", "REG_BINARY", buf)

a.WriteFile()

*/

Changelog:
  • Jan. 30, 2023 - v0.0.2 - Fixed variable wording (Def >> NameDef).

Cheers :beer:
ABCza on the old forum.
My GitHub.

Return to “Scripts and Functions (v2)”