We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Lutris takes extremely long time to parse very long string keys in registry
If user.reg contains very long (we a talking megabytes long here) string key it takes a very long time to start anything in this wineprefix with lutris. With ~15MB key lutris was taking ~30 minutes. It happens before starting wine, in "preparing to start" phase.

P.S. If you ask how does this happens - Battletech ny HBS writes some analytics into string key in registry. And it only adds to it, never cleans.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lutris/util/wine/registry.py`
Content:
```
1 import os
2 import re
3 from collections import OrderedDict
4 from datetime import datetime
5 from lutris.util.log import logger
6 from lutris.util import system
7 from lutris.util.wine.wine import WINE_DEFAULT_ARCH
8 
9 (
10     REG_NONE,
11     REG_SZ,
12     REG_EXPAND_SZ,
13     REG_BINARY,
14     REG_DWORD,
15     REG_DWORD_BIG_ENDIAN,
16     REG_LINK,
17     REG_MULTI_SZ,
18 ) = range(8)
19 
20 DATA_TYPES = {
21     '"': REG_SZ,
22     'str:"': REG_SZ,
23     'str(2):"': REG_EXPAND_SZ,
24     'str(7):"': REG_MULTI_SZ,
25     "hex": REG_BINARY,
26     "dword": REG_DWORD,
27 }
28 
29 
30 class WindowsFileTime:
31     """Utility class to deal with Windows FILETIME structures.
32 
33     See: https://msdn.microsoft.com/en-us/library/ms724284(v=vs.85).aspx
34     """
35 
36     ticks_per_seconds = 10000000  # 1 tick every 100 nanoseconds
37     epoch_delta = 11644473600  # 3600 * 24 * ((1970 - 1601) * 365 + 89)
38 
39     def __init__(self, timestamp=None):
40         self.timestamp = timestamp
41 
42     def __repr__(self):
43         return "<{}>: {}".format(self.__class__.__name__, self.timestamp)
44 
45     @classmethod
46     def from_hex(cls, hexvalue):
47         timestamp = int(hexvalue, 16)
48         return WindowsFileTime(timestamp)
49 
50     def to_hex(self):
51         return "{:x}".format(self.timestamp)
52 
53     @classmethod
54     def from_unix_timestamp(cls, timestamp):
55         timestamp = timestamp + cls.epoch_delta
56         timestamp = int(timestamp * cls.ticks_per_seconds)
57         return WindowsFileTime(timestamp)
58 
59     def to_unix_timestamp(self):
60         if not self.timestamp:
61             raise ValueError("No timestamp set")
62         unix_ts = self.timestamp / self.ticks_per_seconds
63         unix_ts = unix_ts - self.epoch_delta
64         return unix_ts
65 
66     def to_date_time(self):
67         return datetime.fromtimestamp(self.to_unix_timestamp())
68 
69 
70 class WineRegistry:
71     version_header = "WINE REGISTRY Version "
72     relative_to_header = ";; All keys relative to "
73 
74     def __init__(self, reg_filename=None):
75         self.arch = WINE_DEFAULT_ARCH
76         self.version = 2
77         self.relative_to = "\\\\User\\\\S-1-5-21-0-0-0-1000"
78         self.keys = OrderedDict()
79         self.reg_filename = reg_filename
80         if reg_filename:
81             if not system.path_exists(reg_filename):
82                 logger.error("Unexisting registry %s", reg_filename)
83             self.parse_reg_file(reg_filename)
84 
85     @property
86     def prefix_path(self):
87         """Return the Wine prefix path (where the .reg files are located)"""
88         if self.reg_filename:
89             return os.path.dirname(self.reg_filename)
90 
91     @staticmethod
92     def get_raw_registry(reg_filename):
93         """Return an array of the unprocessed contents of a registry file"""
94         if not system.path_exists(reg_filename):
95             return []
96         with open(reg_filename, "r") as reg_file:
97 
98             try:
99                 registry_content = reg_file.readlines()
100             except Exception:  # pylint: disable=broad-except
101                 logger.exception(
102                     "Failed to registry read %s, please send attach this file in a bug report",
103                     reg_filename
104                 )
105                 registry_content = []
106         return registry_content
107 
108     def parse_reg_file(self, reg_filename):
109         registry_lines = self.get_raw_registry(reg_filename)
110         current_key = None
111         add_next_to_value = False
112         for line in registry_lines:
113             line = line.rstrip("\n")  # Remove trailing newlines
114 
115             if line.startswith(self.version_header):
116                 self.version = int(line[len(self.version_header):])
117                 continue
118 
119             if line.startswith(self.relative_to_header):
120                 self.relative_to = line[len(self.relative_to_header):]
121                 continue
122 
123             if line.startswith("#arch"):
124                 self.arch = line.split("=")[1]
125                 continue
126 
127             if line.startswith("["):
128                 current_key = WineRegistryKey(key_def=line)
129                 self.keys[current_key.name] = current_key
130                 continue
131 
132             if current_key:
133                 if add_next_to_value:
134                     current_key.add_to_last(line)
135                 else:
136                     current_key.parse(line)
137                 add_next_to_value = line.endswith("\\")
138 
139     def render(self):
140         content = "{}{}\n".format(self.version_header, self.version)
141         content += "{}{}\n\n".format(self.relative_to_header, self.relative_to)
142         content += "#arch={}\n".format(self.arch)
143         for key in self.keys:
144             content += "\n"
145             content += self.keys[key].render()
146         return content
147 
148     def save(self, path=None):
149         """Write the registry to a file"""
150         if not path:
151             path = self.reg_filename
152         if not path:
153             raise OSError("No filename provided")
154         with open(path, "w") as registry_file:
155             registry_file.write(self.render())
156 
157     def query(self, path, subkey):
158         key = self.keys.get(path)
159         if key:
160             return key.get_subkey(subkey)
161 
162     def set_value(self, path, subkey, value):
163         key = self.keys.get(path)
164         if not key:
165             key = WineRegistryKey(path=path)
166             self.keys[key.name] = key
167         key.set_subkey(subkey, value)
168 
169     def clear_key(self, path):
170         """Removes all subkeys from a key"""
171         key = self.keys.get(path)
172         if not key:
173             return
174         key.subkeys.clear()
175 
176     def clear_subkeys(self, path, keys):
177         """Remove some subkeys from a key"""
178         key = self.keys.get(path)
179         if not key:
180             return
181         for subkey in list(key.subkeys.keys()):
182             if subkey not in keys:
183                 continue
184             key.subkeys.pop(subkey)
185 
186     def get_unix_path(self, windows_path):
187         windows_path = windows_path.replace("\\\\", "/")
188         if not self.prefix_path:
189             return
190         drives_path = os.path.join(self.prefix_path, "dosdevices")
191         if not system.path_exists(drives_path):
192             return
193         letter, relpath = windows_path.split(":", 1)
194         relpath = relpath.strip("/")
195         drive_link = os.path.join(drives_path, letter.lower() + ":")
196         try:
197             drive_path = os.readlink(drive_link)
198         except FileNotFoundError:
199             logger.error("Unable to read link for %s", drive_link)
200             return
201 
202         if not os.path.isabs(drive_path):
203             drive_path = os.path.join(drives_path, drive_path)
204         return os.path.join(drive_path, relpath)
205 
206 
207 class WineRegistryKey:
208     def __init__(self, key_def=None, path=None):
209 
210         self.subkeys = OrderedDict()
211         self.metas = OrderedDict()
212 
213         if path:
214             # Key is created by path, it's a new key
215             timestamp = datetime.now().timestamp()
216             self.name = path
217             self.raw_name = "[{}]".format(path.replace("/", "\\\\"))
218             self.raw_timestamp = " ".join(str(timestamp).split("."))
219 
220             windows_timestamp = WindowsFileTime.from_unix_timestamp(timestamp)
221             self.metas["time"] = windows_timestamp.to_hex()
222         else:
223             # Existing key loaded from file
224             self.raw_name, self.raw_timestamp = re.split(
225                 re.compile(r"(?<=[^\\]\]) "), key_def, maxsplit=1
226             )
227             self.name = self.raw_name.replace("\\\\", "/").strip("[]")
228 
229         # Parse timestamp either as int or float
230         ts_parts = self.raw_timestamp.strip().split()
231         if len(ts_parts) == 1:
232             self.timestamp = int(ts_parts[0])
233         else:
234             self.timestamp = float("{}.{}".format(ts_parts[0], ts_parts[1]))
235 
236     def __str__(self):
237         return "{0} {1}".format(self.raw_name, self.raw_timestamp)
238 
239     def parse(self, line):
240         """Parse a registry line, populating meta and subkeys"""
241         if len(line) < 4:
242             # Line is too short, nothing to parse
243             return
244 
245         if line.startswith("#"):
246             self.add_meta(line)
247         elif line.startswith('"'):
248             try:
249                 key, value = re.split(re.compile(r"(?<![^\\]\\\")="), line, maxsplit=1)
250             except ValueError as ex:
251                 logger.error("Unable to parse line %s", line)
252                 logger.exception(ex)
253                 return
254             key = key[1:-1]
255             self.subkeys[key] = value
256         elif line.startswith("@"):
257             key, value = line.split("=", 1)
258             self.subkeys["default"] = value
259 
260     def add_to_last(self, line):
261         last_subkey = list(self.subkeys.keys())[-1]
262         self.subkeys[last_subkey] += "\n{}".format(line)
263 
264     def render(self):
265         """Return the content of the key in the wine .reg format"""
266         content = self.raw_name + " " + self.raw_timestamp + "\n"
267         for key, value in self.metas.items():
268             if value is None:
269                 content += "#{}\n".format(key)
270             else:
271                 content += "#{}={}\n".format(key, value)
272         for key, value in self.subkeys.items():
273             if key == "default":
274                 key = "@"
275             else:
276                 key = '"{}"'.format(key)
277             content += "{}={}\n".format(key, value)
278         return content
279 
280     def render_value(self, value):
281         if isinstance(value, int):
282             return "dword:{:08x}".format(value)
283         if isinstance(value, str):
284             return '"{}"'.format(value)
285         raise NotImplementedError("TODO")
286 
287     def add_meta(self, meta_line):
288         if not meta_line.startswith("#"):
289             raise ValueError("Key metas should start with '#'")
290         meta_line = meta_line[1:]
291         parts = meta_line.split("=")
292         if len(parts) == 2:
293             key = parts[0]
294             value = parts[1]
295         elif len(parts) == 1:
296             key = parts[0]
297             value = None
298         else:
299             raise ValueError("Invalid meta line '{}'".format(meta_line))
300         self.metas[key] = value
301 
302     def get_meta(self, name):
303         return self.metas.get(name)
304 
305     def set_subkey(self, name, value):
306         self.subkeys[name] = self.render_value(value)
307 
308     def get_subkey(self, name):
309         if name not in self.subkeys:
310             return None
311         value = self.subkeys[name]
312         if value.startswith('"') and value.endswith('"'):
313             return value[1:-1]
314         if value.startswith("dword:"):
315             return int(value[6:], 16)
316         raise ValueError("Handle %s" % value)
317
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lutris/util/wine/registry.py b/lutris/util/wine/registry.py
--- a/lutris/util/wine/registry.py
+++ b/lutris/util/wine/registry.py
@@ -109,32 +109,29 @@
         registry_lines = self.get_raw_registry(reg_filename)
         current_key = None
         add_next_to_value = False
+        additional_values = []
         for line in registry_lines:
-            line = line.rstrip("\n")  # Remove trailing newlines
-
-            if line.startswith(self.version_header):
-                self.version = int(line[len(self.version_header):])
-                continue
-
-            if line.startswith(self.relative_to_header):
-                self.relative_to = line[len(self.relative_to_header):]
-                continue
-
-            if line.startswith("#arch"):
-                self.arch = line.split("=")[1]
-                continue
-
-            if line.startswith("["):
-                current_key = WineRegistryKey(key_def=line)
-                self.keys[current_key.name] = current_key
-                continue
+            line = line.rstrip("\n")
 
             if current_key:
                 if add_next_to_value:
-                    current_key.add_to_last(line)
-                else:
+                    additional_values.append(line)
+                elif not add_next_to_value:
+                    if additional_values:
+                        additional_values = '\n'.join(additional_values)
+                        current_key.add_to_last(additional_values)
+                        additional_values = []
                     current_key.parse(line)
                 add_next_to_value = line.endswith("\\")
+            elif line.startswith("["):
+                current_key = WineRegistryKey(key_def=line)
+                self.keys[current_key.name] = current_key
+            elif line.startswith(self.version_header):
+                self.version = int(line[len(self.version_header):])
+            elif line.startswith(self.relative_to_header):
+                self.relative_to = line[len(self.relative_to_header):]
+            elif line.startswith("#arch"):
+                self.arch = line.split("=")[1]
 
     def render(self):
         content = "{}{}\n".format(self.version_header, self.version)
@@ -258,7 +255,7 @@
             self.subkeys["default"] = value
 
     def add_to_last(self, line):
-        last_subkey = list(self.subkeys.keys())[-1]
+        last_subkey = next(reversed(self.subkeys))
         self.subkeys[last_subkey] += "\n{}".format(line)
 
     def render(self):

{"golden_diff": "diff --git a/lutris/util/wine/registry.py b/lutris/util/wine/registry.py\n--- a/lutris/util/wine/registry.py\n+++ b/lutris/util/wine/registry.py\n@@ -109,32 +109,29 @@\n         registry_lines = self.get_raw_registry(reg_filename)\n         current_key = None\n         add_next_to_value = False\n+        additional_values = []\n         for line in registry_lines:\n-            line = line.rstrip(\"\\n\")  # Remove trailing newlines\n-\n-            if line.startswith(self.version_header):\n-                self.version = int(line[len(self.version_header):])\n-                continue\n-\n-            if line.startswith(self.relative_to_header):\n-                self.relative_to = line[len(self.relative_to_header):]\n-                continue\n-\n-            if line.startswith(\"#arch\"):\n-                self.arch = line.split(\"=\")[1]\n-                continue\n-\n-            if line.startswith(\"[\"):\n-                current_key = WineRegistryKey(key_def=line)\n-                self.keys[current_key.name] = current_key\n-                continue\n+            line = line.rstrip(\"\\n\")\n \n             if current_key:\n                 if add_next_to_value:\n-                    current_key.add_to_last(line)\n-                else:\n+                    additional_values.append(line)\n+                elif not add_next_to_value:\n+                    if additional_values:\n+                        additional_values = '\\n'.join(additional_values)\n+                        current_key.add_to_last(additional_values)\n+                        additional_values = []\n                     current_key.parse(line)\n                 add_next_to_value = line.endswith(\"\\\\\")\n+            elif line.startswith(\"[\"):\n+                current_key = WineRegistryKey(key_def=line)\n+                self.keys[current_key.name] = current_key\n+            elif line.startswith(self.version_header):\n+                self.version = int(line[len(self.version_header):])\n+            elif line.startswith(self.relative_to_header):\n+                self.relative_to = line[len(self.relative_to_header):]\n+            elif line.startswith(\"#arch\"):\n+                self.arch = line.split(\"=\")[1]\n \n     def render(self):\n         content = \"{}{}\\n\".format(self.version_header, self.version)\n@@ -258,7 +255,7 @@\n             self.subkeys[\"default\"] = value\n \n     def add_to_last(self, line):\n-        last_subkey = list(self.subkeys.keys())[-1]\n+        last_subkey = next(reversed(self.subkeys))\n         self.subkeys[last_subkey] += \"\\n{}\".format(line)\n \n     def render(self):\n", "issue": "Lutris takes extremely long time to parse very long string keys in registry\nIf user.reg contains very long (we a talking megabytes long here) string key it takes a very long time to start anything in this wineprefix with lutris. With ~15MB key lutris was taking ~30 minutes. It happens before starting wine, in \"preparing to start\" phase.\r\n\r\nP.S. If you ask how does this happens - Battletech ny HBS writes some analytics into string key in registry. And it only adds to it, never cleans.\n", "before_files": [{"content": "import os\nimport re\nfrom collections import OrderedDict\nfrom datetime import datetime\nfrom lutris.util.log import logger\nfrom lutris.util import system\nfrom lutris.util.wine.wine import WINE_DEFAULT_ARCH\n\n(\n    REG_NONE,\n    REG_SZ,\n    REG_EXPAND_SZ,\n    REG_BINARY,\n    REG_DWORD,\n    REG_DWORD_BIG_ENDIAN,\n    REG_LINK,\n    REG_MULTI_SZ,\n) = range(8)\n\nDATA_TYPES = {\n    '\"': REG_SZ,\n    'str:\"': REG_SZ,\n    'str(2):\"': REG_EXPAND_SZ,\n    'str(7):\"': REG_MULTI_SZ,\n    \"hex\": REG_BINARY,\n    \"dword\": REG_DWORD,\n}\n\n\nclass WindowsFileTime:\n    \"\"\"Utility class to deal with Windows FILETIME structures.\n\n    See: https://msdn.microsoft.com/en-us/library/ms724284(v=vs.85).aspx\n    \"\"\"\n\n    ticks_per_seconds = 10000000  # 1 tick every 100 nanoseconds\n    epoch_delta = 11644473600  # 3600 * 24 * ((1970 - 1601) * 365 + 89)\n\n    def __init__(self, timestamp=None):\n        self.timestamp = timestamp\n\n    def __repr__(self):\n        return \"<{}>: {}\".format(self.__class__.__name__, self.timestamp)\n\n    @classmethod\n    def from_hex(cls, hexvalue):\n        timestamp = int(hexvalue, 16)\n        return WindowsFileTime(timestamp)\n\n    def to_hex(self):\n        return \"{:x}\".format(self.timestamp)\n\n    @classmethod\n    def from_unix_timestamp(cls, timestamp):\n        timestamp = timestamp + cls.epoch_delta\n        timestamp = int(timestamp * cls.ticks_per_seconds)\n        return WindowsFileTime(timestamp)\n\n    def to_unix_timestamp(self):\n        if not self.timestamp:\n            raise ValueError(\"No timestamp set\")\n        unix_ts = self.timestamp / self.ticks_per_seconds\n        unix_ts = unix_ts - self.epoch_delta\n        return unix_ts\n\n    def to_date_time(self):\n        return datetime.fromtimestamp(self.to_unix_timestamp())\n\n\nclass WineRegistry:\n    version_header = \"WINE REGISTRY Version \"\n    relative_to_header = \";; All keys relative to \"\n\n    def __init__(self, reg_filename=None):\n        self.arch = WINE_DEFAULT_ARCH\n        self.version = 2\n        self.relative_to = \"\\\\\\\\User\\\\\\\\S-1-5-21-0-0-0-1000\"\n        self.keys = OrderedDict()\n        self.reg_filename = reg_filename\n        if reg_filename:\n            if not system.path_exists(reg_filename):\n                logger.error(\"Unexisting registry %s\", reg_filename)\n            self.parse_reg_file(reg_filename)\n\n    @property\n    def prefix_path(self):\n        \"\"\"Return the Wine prefix path (where the .reg files are located)\"\"\"\n        if self.reg_filename:\n            return os.path.dirname(self.reg_filename)\n\n    @staticmethod\n    def get_raw_registry(reg_filename):\n        \"\"\"Return an array of the unprocessed contents of a registry file\"\"\"\n        if not system.path_exists(reg_filename):\n            return []\n        with open(reg_filename, \"r\") as reg_file:\n\n            try:\n                registry_content = reg_file.readlines()\n            except Exception:  # pylint: disable=broad-except\n                logger.exception(\n                    \"Failed to registry read %s, please send attach this file in a bug report\",\n                    reg_filename\n                )\n                registry_content = []\n        return registry_content\n\n    def parse_reg_file(self, reg_filename):\n        registry_lines = self.get_raw_registry(reg_filename)\n        current_key = None\n        add_next_to_value = False\n        for line in registry_lines:\n            line = line.rstrip(\"\\n\")  # Remove trailing newlines\n\n            if line.startswith(self.version_header):\n                self.version = int(line[len(self.version_header):])\n                continue\n\n            if line.startswith(self.relative_to_header):\n                self.relative_to = line[len(self.relative_to_header):]\n                continue\n\n            if line.startswith(\"#arch\"):\n                self.arch = line.split(\"=\")[1]\n                continue\n\n            if line.startswith(\"[\"):\n                current_key = WineRegistryKey(key_def=line)\n                self.keys[current_key.name] = current_key\n                continue\n\n            if current_key:\n                if add_next_to_value:\n                    current_key.add_to_last(line)\n                else:\n                    current_key.parse(line)\n                add_next_to_value = line.endswith(\"\\\\\")\n\n    def render(self):\n        content = \"{}{}\\n\".format(self.version_header, self.version)\n        content += \"{}{}\\n\\n\".format(self.relative_to_header, self.relative_to)\n        content += \"#arch={}\\n\".format(self.arch)\n        for key in self.keys:\n            content += \"\\n\"\n            content += self.keys[key].render()\n        return content\n\n    def save(self, path=None):\n        \"\"\"Write the registry to a file\"\"\"\n        if not path:\n            path = self.reg_filename\n        if not path:\n            raise OSError(\"No filename provided\")\n        with open(path, \"w\") as registry_file:\n            registry_file.write(self.render())\n\n    def query(self, path, subkey):\n        key = self.keys.get(path)\n        if key:\n            return key.get_subkey(subkey)\n\n    def set_value(self, path, subkey, value):\n        key = self.keys.get(path)\n        if not key:\n            key = WineRegistryKey(path=path)\n            self.keys[key.name] = key\n        key.set_subkey(subkey, value)\n\n    def clear_key(self, path):\n        \"\"\"Removes all subkeys from a key\"\"\"\n        key = self.keys.get(path)\n        if not key:\n            return\n        key.subkeys.clear()\n\n    def clear_subkeys(self, path, keys):\n        \"\"\"Remove some subkeys from a key\"\"\"\n        key = self.keys.get(path)\n        if not key:\n            return\n        for subkey in list(key.subkeys.keys()):\n            if subkey not in keys:\n                continue\n            key.subkeys.pop(subkey)\n\n    def get_unix_path(self, windows_path):\n        windows_path = windows_path.replace(\"\\\\\\\\\", \"/\")\n        if not self.prefix_path:\n            return\n        drives_path = os.path.join(self.prefix_path, \"dosdevices\")\n        if not system.path_exists(drives_path):\n            return\n        letter, relpath = windows_path.split(\":\", 1)\n        relpath = relpath.strip(\"/\")\n        drive_link = os.path.join(drives_path, letter.lower() + \":\")\n        try:\n            drive_path = os.readlink(drive_link)\n        except FileNotFoundError:\n            logger.error(\"Unable to read link for %s\", drive_link)\n            return\n\n        if not os.path.isabs(drive_path):\n            drive_path = os.path.join(drives_path, drive_path)\n        return os.path.join(drive_path, relpath)\n\n\nclass WineRegistryKey:\n    def __init__(self, key_def=None, path=None):\n\n        self.subkeys = OrderedDict()\n        self.metas = OrderedDict()\n\n        if path:\n            # Key is created by path, it's a new key\n            timestamp = datetime.now().timestamp()\n            self.name = path\n            self.raw_name = \"[{}]\".format(path.replace(\"/\", \"\\\\\\\\\"))\n            self.raw_timestamp = \" \".join(str(timestamp).split(\".\"))\n\n            windows_timestamp = WindowsFileTime.from_unix_timestamp(timestamp)\n            self.metas[\"time\"] = windows_timestamp.to_hex()\n        else:\n            # Existing key loaded from file\n            self.raw_name, self.raw_timestamp = re.split(\n                re.compile(r\"(?<=[^\\\\]\\]) \"), key_def, maxsplit=1\n            )\n            self.name = self.raw_name.replace(\"\\\\\\\\\", \"/\").strip(\"[]\")\n\n        # Parse timestamp either as int or float\n        ts_parts = self.raw_timestamp.strip().split()\n        if len(ts_parts) == 1:\n            self.timestamp = int(ts_parts[0])\n        else:\n            self.timestamp = float(\"{}.{}\".format(ts_parts[0], ts_parts[1]))\n\n    def __str__(self):\n        return \"{0} {1}\".format(self.raw_name, self.raw_timestamp)\n\n    def parse(self, line):\n        \"\"\"Parse a registry line, populating meta and subkeys\"\"\"\n        if len(line) < 4:\n            # Line is too short, nothing to parse\n            return\n\n        if line.startswith(\"#\"):\n            self.add_meta(line)\n        elif line.startswith('\"'):\n            try:\n                key, value = re.split(re.compile(r\"(?<![^\\\\]\\\\\\\")=\"), line, maxsplit=1)\n            except ValueError as ex:\n                logger.error(\"Unable to parse line %s\", line)\n                logger.exception(ex)\n                return\n            key = key[1:-1]\n            self.subkeys[key] = value\n        elif line.startswith(\"@\"):\n            key, value = line.split(\"=\", 1)\n            self.subkeys[\"default\"] = value\n\n    def add_to_last(self, line):\n        last_subkey = list(self.subkeys.keys())[-1]\n        self.subkeys[last_subkey] += \"\\n{}\".format(line)\n\n    def render(self):\n        \"\"\"Return the content of the key in the wine .reg format\"\"\"\n        content = self.raw_name + \" \" + self.raw_timestamp + \"\\n\"\n        for key, value in self.metas.items():\n            if value is None:\n                content += \"#{}\\n\".format(key)\n            else:\n                content += \"#{}={}\\n\".format(key, value)\n        for key, value in self.subkeys.items():\n            if key == \"default\":\n                key = \"@\"\n            else:\n                key = '\"{}\"'.format(key)\n            content += \"{}={}\\n\".format(key, value)\n        return content\n\n    def render_value(self, value):\n        if isinstance(value, int):\n            return \"dword:{:08x}\".format(value)\n        if isinstance(value, str):\n            return '\"{}\"'.format(value)\n        raise NotImplementedError(\"TODO\")\n\n    def add_meta(self, meta_line):\n        if not meta_line.startswith(\"#\"):\n            raise ValueError(\"Key metas should start with '#'\")\n        meta_line = meta_line[1:]\n        parts = meta_line.split(\"=\")\n        if len(parts) == 2:\n            key = parts[0]\n            value = parts[1]\n        elif len(parts) == 1:\n            key = parts[0]\n            value = None\n        else:\n            raise ValueError(\"Invalid meta line '{}'\".format(meta_line))\n        self.metas[key] = value\n\n    def get_meta(self, name):\n        return self.metas.get(name)\n\n    def set_subkey(self, name, value):\n        self.subkeys[name] = self.render_value(value)\n\n    def get_subkey(self, name):\n        if name not in self.subkeys:\n            return None\n        value = self.subkeys[name]\n        if value.startswith('\"') and value.endswith('\"'):\n            return value[1:-1]\n        if value.startswith(\"dword:\"):\n            return int(value[6:], 16)\n        raise ValueError(\"Handle %s\" % value)\n", "path": "lutris/util/wine/registry.py"}], "after_files": [{"content": "import os\nimport re\nfrom collections import OrderedDict\nfrom datetime import datetime\nfrom lutris.util.log import logger\nfrom lutris.util import system\nfrom lutris.util.wine.wine import WINE_DEFAULT_ARCH\n\n(\n    REG_NONE,\n    REG_SZ,\n    REG_EXPAND_SZ,\n    REG_BINARY,\n    REG_DWORD,\n    REG_DWORD_BIG_ENDIAN,\n    REG_LINK,\n    REG_MULTI_SZ,\n) = range(8)\n\nDATA_TYPES = {\n    '\"': REG_SZ,\n    'str:\"': REG_SZ,\n    'str(2):\"': REG_EXPAND_SZ,\n    'str(7):\"': REG_MULTI_SZ,\n    \"hex\": REG_BINARY,\n    \"dword\": REG_DWORD,\n}\n\n\nclass WindowsFileTime:\n    \"\"\"Utility class to deal with Windows FILETIME structures.\n\n    See: https://msdn.microsoft.com/en-us/library/ms724284(v=vs.85).aspx\n    \"\"\"\n\n    ticks_per_seconds = 10000000  # 1 tick every 100 nanoseconds\n    epoch_delta = 11644473600  # 3600 * 24 * ((1970 - 1601) * 365 + 89)\n\n    def __init__(self, timestamp=None):\n        self.timestamp = timestamp\n\n    def __repr__(self):\n        return \"<{}>: {}\".format(self.__class__.__name__, self.timestamp)\n\n    @classmethod\n    def from_hex(cls, hexvalue):\n        timestamp = int(hexvalue, 16)\n        return WindowsFileTime(timestamp)\n\n    def to_hex(self):\n        return \"{:x}\".format(self.timestamp)\n\n    @classmethod\n    def from_unix_timestamp(cls, timestamp):\n        timestamp = timestamp + cls.epoch_delta\n        timestamp = int(timestamp * cls.ticks_per_seconds)\n        return WindowsFileTime(timestamp)\n\n    def to_unix_timestamp(self):\n        if not self.timestamp:\n            raise ValueError(\"No timestamp set\")\n        unix_ts = self.timestamp / self.ticks_per_seconds\n        unix_ts = unix_ts - self.epoch_delta\n        return unix_ts\n\n    def to_date_time(self):\n        return datetime.fromtimestamp(self.to_unix_timestamp())\n\n\nclass WineRegistry:\n    version_header = \"WINE REGISTRY Version \"\n    relative_to_header = \";; All keys relative to \"\n\n    def __init__(self, reg_filename=None):\n        self.arch = WINE_DEFAULT_ARCH\n        self.version = 2\n        self.relative_to = \"\\\\\\\\User\\\\\\\\S-1-5-21-0-0-0-1000\"\n        self.keys = OrderedDict()\n        self.reg_filename = reg_filename\n        if reg_filename:\n            if not system.path_exists(reg_filename):\n                logger.error(\"Unexisting registry %s\", reg_filename)\n            self.parse_reg_file(reg_filename)\n\n    @property\n    def prefix_path(self):\n        \"\"\"Return the Wine prefix path (where the .reg files are located)\"\"\"\n        if self.reg_filename:\n            return os.path.dirname(self.reg_filename)\n\n    @staticmethod\n    def get_raw_registry(reg_filename):\n        \"\"\"Return an array of the unprocessed contents of a registry file\"\"\"\n        if not system.path_exists(reg_filename):\n            return []\n        with open(reg_filename, \"r\") as reg_file:\n\n            try:\n                registry_content = reg_file.readlines()\n            except Exception:  # pylint: disable=broad-except\n                logger.exception(\n                    \"Failed to registry read %s, please send attach this file in a bug report\",\n                    reg_filename\n                )\n                registry_content = []\n        return registry_content\n\n    def parse_reg_file(self, reg_filename):\n        registry_lines = self.get_raw_registry(reg_filename)\n        current_key = None\n        add_next_to_value = False\n        additional_values = []\n        for line in registry_lines:\n            line = line.rstrip(\"\\n\")\n\n            if current_key:\n                if add_next_to_value:\n                    additional_values.append(line)\n                elif not add_next_to_value:\n                    if additional_values:\n                        additional_values = '\\n'.join(additional_values)\n                        current_key.add_to_last(additional_values)\n                        additional_values = []\n                    current_key.parse(line)\n                add_next_to_value = line.endswith(\"\\\\\")\n            elif line.startswith(\"[\"):\n                current_key = WineRegistryKey(key_def=line)\n                self.keys[current_key.name] = current_key\n            elif line.startswith(self.version_header):\n                self.version = int(line[len(self.version_header):])\n            elif line.startswith(self.relative_to_header):\n                self.relative_to = line[len(self.relative_to_header):]\n            elif line.startswith(\"#arch\"):\n                self.arch = line.split(\"=\")[1]\n\n    def render(self):\n        content = \"{}{}\\n\".format(self.version_header, self.version)\n        content += \"{}{}\\n\\n\".format(self.relative_to_header, self.relative_to)\n        content += \"#arch={}\\n\".format(self.arch)\n        for key in self.keys:\n            content += \"\\n\"\n            content += self.keys[key].render()\n        return content\n\n    def save(self, path=None):\n        \"\"\"Write the registry to a file\"\"\"\n        if not path:\n            path = self.reg_filename\n        if not path:\n            raise OSError(\"No filename provided\")\n        with open(path, \"w\") as registry_file:\n            registry_file.write(self.render())\n\n    def query(self, path, subkey):\n        key = self.keys.get(path)\n        if key:\n            return key.get_subkey(subkey)\n\n    def set_value(self, path, subkey, value):\n        key = self.keys.get(path)\n        if not key:\n            key = WineRegistryKey(path=path)\n            self.keys[key.name] = key\n        key.set_subkey(subkey, value)\n\n    def clear_key(self, path):\n        \"\"\"Removes all subkeys from a key\"\"\"\n        key = self.keys.get(path)\n        if not key:\n            return\n        key.subkeys.clear()\n\n    def clear_subkeys(self, path, keys):\n        \"\"\"Remove some subkeys from a key\"\"\"\n        key = self.keys.get(path)\n        if not key:\n            return\n        for subkey in list(key.subkeys.keys()):\n            if subkey not in keys:\n                continue\n            key.subkeys.pop(subkey)\n\n    def get_unix_path(self, windows_path):\n        windows_path = windows_path.replace(\"\\\\\\\\\", \"/\")\n        if not self.prefix_path:\n            return\n        drives_path = os.path.join(self.prefix_path, \"dosdevices\")\n        if not system.path_exists(drives_path):\n            return\n        letter, relpath = windows_path.split(\":\", 1)\n        relpath = relpath.strip(\"/\")\n        drive_link = os.path.join(drives_path, letter.lower() + \":\")\n        try:\n            drive_path = os.readlink(drive_link)\n        except FileNotFoundError:\n            logger.error(\"Unable to read link for %s\", drive_link)\n            return\n\n        if not os.path.isabs(drive_path):\n            drive_path = os.path.join(drives_path, drive_path)\n        return os.path.join(drive_path, relpath)\n\n\nclass WineRegistryKey:\n    def __init__(self, key_def=None, path=None):\n\n        self.subkeys = OrderedDict()\n        self.metas = OrderedDict()\n\n        if path:\n            # Key is created by path, it's a new key\n            timestamp = datetime.now().timestamp()\n            self.name = path\n            self.raw_name = \"[{}]\".format(path.replace(\"/\", \"\\\\\\\\\"))\n            self.raw_timestamp = \" \".join(str(timestamp).split(\".\"))\n\n            windows_timestamp = WindowsFileTime.from_unix_timestamp(timestamp)\n            self.metas[\"time\"] = windows_timestamp.to_hex()\n        else:\n            # Existing key loaded from file\n            self.raw_name, self.raw_timestamp = re.split(\n                re.compile(r\"(?<=[^\\\\]\\]) \"), key_def, maxsplit=1\n            )\n            self.name = self.raw_name.replace(\"\\\\\\\\\", \"/\").strip(\"[]\")\n\n        # Parse timestamp either as int or float\n        ts_parts = self.raw_timestamp.strip().split()\n        if len(ts_parts) == 1:\n            self.timestamp = int(ts_parts[0])\n        else:\n            self.timestamp = float(\"{}.{}\".format(ts_parts[0], ts_parts[1]))\n\n    def __str__(self):\n        return \"{0} {1}\".format(self.raw_name, self.raw_timestamp)\n\n    def parse(self, line):\n        \"\"\"Parse a registry line, populating meta and subkeys\"\"\"\n        if len(line) < 4:\n            # Line is too short, nothing to parse\n            return\n\n        if line.startswith(\"#\"):\n            self.add_meta(line)\n        elif line.startswith('\"'):\n            try:\n                key, value = re.split(re.compile(r\"(?<![^\\\\]\\\\\\\")=\"), line, maxsplit=1)\n            except ValueError as ex:\n                logger.error(\"Unable to parse line %s\", line)\n                logger.exception(ex)\n                return\n            key = key[1:-1]\n            self.subkeys[key] = value\n        elif line.startswith(\"@\"):\n            key, value = line.split(\"=\", 1)\n            self.subkeys[\"default\"] = value\n\n    def add_to_last(self, line):\n        last_subkey = next(reversed(self.subkeys))\n        self.subkeys[last_subkey] += \"\\n{}\".format(line)\n\n    def render(self):\n        \"\"\"Return the content of the key in the wine .reg format\"\"\"\n        content = self.raw_name + \" \" + self.raw_timestamp + \"\\n\"\n        for key, value in self.metas.items():\n            if value is None:\n                content += \"#{}\\n\".format(key)\n            else:\n                content += \"#{}={}\\n\".format(key, value)\n        for key, value in self.subkeys.items():\n            if key == \"default\":\n                key = \"@\"\n            else:\n                key = '\"{}\"'.format(key)\n            content += \"{}={}\\n\".format(key, value)\n        return content\n\n    def render_value(self, value):\n        if isinstance(value, int):\n            return \"dword:{:08x}\".format(value)\n        if isinstance(value, str):\n            return '\"{}\"'.format(value)\n        raise NotImplementedError(\"TODO\")\n\n    def add_meta(self, meta_line):\n        if not meta_line.startswith(\"#\"):\n            raise ValueError(\"Key metas should start with '#'\")\n        meta_line = meta_line[1:]\n        parts = meta_line.split(\"=\")\n        if len(parts) == 2:\n            key = parts[0]\n            value = parts[1]\n        elif len(parts) == 1:\n            key = parts[0]\n            value = None\n        else:\n            raise ValueError(\"Invalid meta line '{}'\".format(meta_line))\n        self.metas[key] = value\n\n    def get_meta(self, name):\n        return self.metas.get(name)\n\n    def set_subkey(self, name, value):\n        self.subkeys[name] = self.render_value(value)\n\n    def get_subkey(self, name):\n        if name not in self.subkeys:\n            return None\n        value = self.subkeys[name]\n        if value.startswith('\"') and value.endswith('\"'):\n            return value[1:-1]\n        if value.startswith(\"dword:\"):\n            return int(value[6:], 16)\n        raise ValueError(\"Handle %s\" % value)\n", "path": "lutris/util/wine/registry.py"}]}