Manipulating settings.dat File with Settings from Windows Store Apps

Settings in Windows Store Apps

Windows Store apps offer two containers for storing application settings as key-value pairs: ApplicationData.LocalSettings and ApplicationData.RoamingSettings. API for both is the same; the only difference between them is, how the settings are persisted: locally only or are they roamed across all user's devices.

If you've done any Windows Store development, you might already now that all application data from Windows Store apps is saved to Local\Packages inside user's AppData folder (usually C:\Users\Username\AppData). Each application has its own subfolder there; its name is concatenated from the application package name and a signing certificate based postfix. Of course, you can find the settings there as well: there's a settings.dat file inside the Settings subfolder.

Settings.dat File Format

A couple of times I've already wondered about the format of this file, but a recent question on Stack Overflow has encouraged me to finally get to the bottom of this mystery. The question already hinted at the file being a registry hive file and soon enough I found a blog post describing its contents in detail. You can read all the details there; here are just the most important bits for this blog post:

  • Settings are stored as values, grouped under two separate keys: LocalState and RoamingState.
  • The values are encoded as binary data; strings are null terminated and use UTF-16 encoding.
  • All values have an 8 bytes long postfix - a timestamp of some kind.
  • Value types are non-standard and include information about the data type.

Based on this information you can change the settings from outside the Windows Store app, as long as you have access to its settings.dat file:

  • Run Windows Registry Editor (regedit.exe).
  • Select HKEY_LOCAL_MACHINE or HKEY_USERS in the tree view, navigate to the File > Load Hive... menu item and select the settings.dat file you're interested in. Type in the name of the root key for the hive to be loaded.
  • For each setting you want to change, navigate to it, double click it and enter its new value.
  • Once you're done, select the root key of the hive you have loaded and navigate to the File > Unload Hive... menu item to unload it.

As already mentioned, the values are unfortunately binary encoded which makes them difficult to edit by hand. Having a way to do it programmatically would make it much easier.

Changing settings.dat Programmatically

There doesn't seem to be a way to modify a registry hive file, without having it loaded in the registry. This means the first step of the programmatic solution will be loading the file into the registry and the last step will be unloading it again. This can be done using the Reg command:

reg load HKLM\_Settings .\settings.dat
reg unload HKLM\_Settings

Both PowerShell and .NET Framework have APIs for reading and writing registry values. They probably both use the same underlying APIs as they both have problems with non-standard value types:

  • Trying to read the values fails; the method just returns the default value passed to it as a parameter.
  • Writing a value succeeds but changes the value type to REG_SZ, i.e. string. This breaks the code inside the Windows Store app.

Taking Advantage of .reg Files

The only way I've found to manipulate values of non-standard types, takes advantage of .reg registry files for exchanging registry settings in text format. To export our settings into a .reg file and to import the modified file back into registry, Reg command can be used again:

reg export HKLM\_Settings .\settings.reg
reg import .\settings.reg

If you take a look at the exported file, it looks very familiar to an .ini file:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\_TMP]

[HKEY_LOCAL_MACHINE\_TMP\LocalState]
"Test"=hex(5f5e10c):4f,00,6c,00,64,00,20,00,76,00,61,00,6c,00,75,00,65,00,00,\
  00,42,fe,6f,ec,38,32,d0,01

[HKEY_LOCAL_MACHINE\_TMP\RoamingState]

After a closer look it becomes obvious, this is not the case. There's a non-comment line at the top and values can span across multiple lines. Using existing APIs for handling .ini files is therefore not an option.

Quick and Dirty .reg File Parser

Creating a fully fledged parser for .reg files is not a trivial task, but if you only want to change a single value inside it, this can be done in only a few simple steps:

  • Read the file line by line until you get to the value you want to change. Copy them verbatim to the output.
  • The corresponding line will start with the value name in quotes, followed by an equality sign. Change the value in this line as required.
  • Copy all the remaining lines to the output unchanged.

Here's a simple PowerShell script to do this:

$fileContents = Get-Content .\settings.reg

$finalContents = @()
Foreach ($line in $fileContents)
{
    If ($line.StartsWith("""SettingName""="))
    {
        # set the new value
        $finalContents += """SettingName""=$newValue"
    }
    Else
    {
        $finalContents += $line
    }
}

$finalContents | Out-File .\settings.reg

Since a value can span multiple lines, this needs to be taken into account: all lines belonging to the value we're changing need to be replaced by a single line with the new value. There's no need to write a value in multiple lines even if it is long. To achieve this, the loop now needs to keep track whether the lines belong to the one we're changing or not (for brevity irrelevant parts of the script above are skipped):

$processing = $false
Foreach ($line in $fileContents)
{
    If (-Not ($processing))
    {
        # scanning for first line of the value
        If ($line.StartsWith("""SettingName""="))
        {
            # found - switch mode and start reading the old value
            $processing = $true
            $oldValue = $line.Replace("""SettingName""=", "")
        }
        Else
        {
            # not found yet - copy to output
            $finalContents += $line
        }
    }
    Else
    {
        # non-first lines have leading spaces
        $oldValue += $line.TrimStart(" ")
    }

    If ($processing)
    {
        # scanning for last line of the value
        If ($oldValue.EndsWith("\"))
        {
            # strip trailing backslash; the value continues
            $oldValue = $oldValue.TrimEnd("\")
        }
        Else
        {
            # no backslash; the value is complete
            $finalContents += """SettingName""=$newValue"
            $processing = $false
        }
    }
}

Serializing the New Value

The new serialized value will need to keep the value type and the last 8 bytes containing the timestamp; that's why we needed to read the old value:

$match = $oldValue -match '(.*:)(.*)'

$valueType = $matches[1]
$timestamp = $matches[2].Substring($matches[2].Length - 23)

# serialize the new value and compose it with the extracted prefix and postfix
$newValue = $valueType + $serializedValue + $timestamp

The only remaining part of the script is the serialization of the new value. Here's how to do it for strings; numeric data types are even simpler:

$utfEncoded = [System.Text.Encoding]::Unicode.GetBytes("New value")

Foreach ($byte in $utfEncoded)
{
    $serializedValue += [System.Convert]::ToString($byte, 16).PadLeft(2, "0") + ","
}

# append null terminator
$serializedValue += "00,00,"

Final Script for Download

I've wrapped all the code in an easy to use PowerShell function and published it as a Gist. It's still a bit rough and doesn't work in all cases, so feel free to fork it and improve it further. Don't forget to run it as administrator or reg load will fail due to insufficient privileges.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License