diff --git a/DeobfuscateStackTrace/DeobfuscateStackTrace.csproj b/DeobfuscateStackTrace/DeobfuscateStackTrace.csproj
new file mode 100644
index 0000000..c20cf7e
--- /dev/null
+++ b/DeobfuscateStackTrace/DeobfuscateStackTrace.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net8.0
+ enable
+ disable
+ 1.0.0
+
+
+
+
+
+
+
diff --git a/DeobfuscateStackTrace/DeobfuscateStackTrace.sln b/DeobfuscateStackTrace/DeobfuscateStackTrace.sln
new file mode 100644
index 0000000..98fcbcc
--- /dev/null
+++ b/DeobfuscateStackTrace/DeobfuscateStackTrace.sln
@@ -0,0 +1,24 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35931.197
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DeobfuscateStackTrace", "DeobfuscateStackTrace.csproj", "{B7192F39-1EEA-4F31-885B-B606D700FC79}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {B7192F39-1EEA-4F31-885B-B606D700FC79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B7192F39-1EEA-4F31-885B-B606D700FC79}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B7192F39-1EEA-4F31-885B-B606D700FC79}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B7192F39-1EEA-4F31-885B-B606D700FC79}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {9F39E3ED-EF31-43DE-B085-0F7BF60844E8}
+ EndGlobalSection
+EndGlobal
diff --git a/DeobfuscateStackTrace/Program.cs b/DeobfuscateStackTrace/Program.cs
new file mode 100644
index 0000000..9cda94b
--- /dev/null
+++ b/DeobfuscateStackTrace/Program.cs
@@ -0,0 +1,57 @@
+using CommandLine;
+
+namespace DeobfuscateStackTrace
+{
+ internal class Program
+ {
+ private class CommandOptions
+ {
+
+ [Option('m', "mappingFile", Required = true, HelpText = "mapping xml file")]
+ public string MappingFile { get; set; }
+
+ [Option('i', "input", Required = true, HelpText = "input obfuscated log file")]
+ public string InputFile { get; set; }
+
+ [Option('o', "output", Required = true, HelpText = "output deobfuscated log file")]
+ public string OutputFile { get; set; }
+
+ }
+
+ static void Main(string[] args)
+ {
+ CommandOptions opt = ParseArgs(args);
+
+ if (!File.Exists(opt.MappingFile))
+ {
+ Console.Error.WriteLine($"Mapping file {opt.MappingFile} not found");
+ Environment.Exit(1);
+ }
+ if (!File.Exists(opt.InputFile))
+ {
+ Console.Error.WriteLine($"Input file {opt.InputFile} not found");
+ Environment.Exit(1);
+ }
+ var reader = new SymbolMappingReader(opt.MappingFile);
+ StackTraceDeObfuscator.Convert(reader, opt.InputFile, opt.OutputFile);
+ }
+
+ private static CommandOptions ParseArgs(string[] args)
+ {
+ var helpWriter = new StringWriter();
+ var parser = new Parser(settings =>
+ {
+ settings.AllowMultiInstance = true;
+ settings.HelpWriter = helpWriter;
+ });
+
+ var result = parser.ParseArguments(args);
+ if (result.Tag == ParserResultType.NotParsed)
+ {
+ Console.Error.WriteLine(helpWriter.ToString());
+ Environment.Exit(1);
+ }
+ return ((Parsed)result).Value;
+ }
+ }
+}
diff --git a/DeobfuscateStackTrace/Properties/launchSettings.json b/DeobfuscateStackTrace/Properties/launchSettings.json
new file mode 100644
index 0000000..de0ffc0
--- /dev/null
+++ b/DeobfuscateStackTrace/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "DeobfuscateStackTrace": {
+ "commandName": "Project",
+ "commandLineArgs": "-m ../../../mapping.xml -i ../../../obfuscated.log -o ../../../deobfuscated.log"
+ }
+ }
+}
\ No newline at end of file
diff --git a/DeobfuscateStackTrace/StackTraceDeObfuscator.cs b/DeobfuscateStackTrace/StackTraceDeObfuscator.cs
new file mode 100644
index 0000000..2a13ab0
--- /dev/null
+++ b/DeobfuscateStackTrace/StackTraceDeObfuscator.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace DeobfuscateStackTrace
+{
+ public class StackTraceDeObfuscator
+ {
+ public static void Convert(SymbolMappingReader reader, string oldLogFile, string newLogFile)
+ {
+ var obfuscatedLines = File.ReadAllLines(oldLogFile, Encoding.UTF8);
+ var deObfuscatedLines = new List();
+
+ bool logLineFound = false;
+ foreach (string line in obfuscatedLines)
+ {
+ if (TryConvertLine(line, reader, ref logLineFound, out var newLine))
+ {
+ deObfuscatedLines.Add(newLine);
+ }
+ else
+ {
+ deObfuscatedLines.Add(line);
+ }
+ }
+ File.WriteAllLines(newLogFile, deObfuscatedLines, Encoding.UTF8);
+ }
+
+ private static bool TryConvertLine(string line, SymbolMappingReader reader, ref bool logLineFound, out string deObfuscatedStackTrace)
+ {
+ deObfuscatedStackTrace = line;
+ if (string.IsNullOrEmpty(line))
+ {
+ logLineFound = false;
+ return false;
+ }
+ if (!logLineFound)
+ {
+ logLineFound = line.StartsWith("UnityEngine.DebugLogHandler:Internal_Log")
+ || line.StartsWith("UnityEngine.DebugLogHandler:LogFormat")
+ || line.StartsWith("UnityEngine.Logger:Log");
+ return false;
+ }
+ return reader.TryDeObfuscateStackTrace(line, out deObfuscatedStackTrace);
+ }
+ }
+}
diff --git a/DeobfuscateStackTrace/SymbolMappingReader.cs b/DeobfuscateStackTrace/SymbolMappingReader.cs
new file mode 100644
index 0000000..5d103c4
--- /dev/null
+++ b/DeobfuscateStackTrace/SymbolMappingReader.cs
@@ -0,0 +1,204 @@
+
+using System.Xml;
+
+namespace DeobfuscateStackTrace
+{
+
+ public class SymbolMappingReader
+ {
+
+ private readonly Dictionary> _fullSignatureMapper = new Dictionary>();
+ private readonly Dictionary> _signatureWithParamsMapper = new Dictionary>();
+
+
+ private enum RenameStatus
+ {
+ NotRenamed,
+ Renamed,
+ }
+
+ private class RenameRecord
+ {
+ public RenameStatus status;
+ public string signature;
+ public string oldName;
+ public string newName;
+ public string oldStackTraceSignature; // only for MethodDef
+ public object renameMappingData;
+ }
+
+ private class RenameMappingField
+ {
+ public RenameStatus status;
+ public string signature;
+ public string newName;
+ }
+
+ private class RenameMappingMethod
+ {
+ public RenameStatus status;
+ public string signature;
+ public string newName;
+
+ public List parameters = new List();
+ }
+
+ private class RenameMappingMethodParam
+ {
+ public RenameStatus status;
+ public int index;
+ public string newName;
+ }
+
+ private class RenameMappingProperty
+ {
+ public RenameStatus status;
+ public string signature;
+ public string newName;
+ }
+
+ private class RenameMappingEvent
+ {
+ public RenameStatus status;
+ public string signature;
+ public string newName;
+ }
+
+ private class RenameMappingType
+ {
+ public RenameStatus status;
+ public string oldFullName;
+ public string newFullName;
+
+ public Dictionary fields = new Dictionary();
+ public Dictionary methods = new Dictionary();
+ public Dictionary properties = new Dictionary();
+ public Dictionary events = new Dictionary();
+ }
+
+ private class RenameMappingAssembly
+ {
+ public RenameStatus status;
+ public string oldAssName;
+ public string newAssName;
+
+ public Dictionary types = new Dictionary();
+ }
+
+
+ public SymbolMappingReader(string mappingFile)
+ {
+ LoadXmlMappingFile(mappingFile);
+ }
+
+ private void LoadXmlMappingFile(string mappingFile)
+ {
+ var doc = new XmlDocument();
+ doc.Load(mappingFile);
+ var root = doc.DocumentElement;
+ foreach (XmlNode node in root.ChildNodes)
+ {
+ if (!(node is XmlElement element))
+ {
+ continue;
+ }
+ LoadAssemblyMapping(element);
+ }
+ }
+
+ private void LoadAssemblyMapping(XmlElement ele)
+ {
+ if (ele.Name != "assembly")
+ {
+ throw new System.Exception($"Invalid node name: {ele.Name}. Expected 'assembly'.");
+ }
+ foreach (XmlNode node in ele.ChildNodes)
+ {
+ if (!(node is XmlElement element))
+ {
+ continue;
+ }
+ if (element.Name == "type")
+ {
+ LoadTypeMapping(element);
+ }
+ }
+ }
+
+ private void LoadTypeMapping(XmlElement ele)
+ {
+ foreach (XmlNode node in ele.ChildNodes)
+ {
+ if (!(node is XmlElement c))
+ {
+ continue;
+ }
+ if (node.Name == "method")
+ {
+ LoadMethodMapping(c);
+ }
+ }
+ }
+
+
+ private string GetMethodSignatureWithoutParams(string signature)
+ {
+ int index = signature.IndexOf('(');
+ if (index < 0)
+ {
+ return signature;
+ }
+ return signature.Substring(0, index);
+ }
+
+ private void LoadMethodMapping(XmlElement ele)
+ {
+ if (!ele.HasAttribute("oldStackTraceSignature"))
+ {
+ throw new System.Exception($"Invalid node name: {ele.Name}. attribute 'oldStackTraceSignature' missing.");
+ }
+ if (!ele.HasAttribute("newStackTraceSignature"))
+ {
+ throw new System.Exception($"Invalid node name: {ele.Name}. attribute 'newStackTraceSignature' missing.");
+ }
+ string oldStackTraceSignature = ele.Attributes["oldStackTraceSignature"].Value;
+ string newStackTraceSignature = ele.Attributes["newStackTraceSignature"].Value;
+
+ if (!_fullSignatureMapper.TryGetValue(newStackTraceSignature, out var oldFullSignatures))
+ {
+ oldFullSignatures = new List();
+ _fullSignatureMapper[newStackTraceSignature] = oldFullSignatures;
+ }
+ oldFullSignatures.Add(oldStackTraceSignature);
+
+ string oldStackTraceSignatureWithoutParams = GetMethodSignatureWithoutParams(oldStackTraceSignature);
+ string newStackTraceSignatureWithoutParams = GetMethodSignatureWithoutParams(newStackTraceSignature);
+ if (!_signatureWithParamsMapper.TryGetValue(newStackTraceSignatureWithoutParams, out var oldSignaturesWithoutParams))
+ {
+ oldSignaturesWithoutParams = new List();
+ _signatureWithParamsMapper[newStackTraceSignatureWithoutParams] = oldSignaturesWithoutParams;
+ }
+ oldSignaturesWithoutParams.Add(oldStackTraceSignatureWithoutParams);
+ }
+
+
+ public bool TryDeObfuscateStackTrace(string obfuscatedStackTraceLog, out string deObfuscatedStackTrace)
+ {
+ obfuscatedStackTraceLog = obfuscatedStackTraceLog.Trim();
+ if (_fullSignatureMapper.TryGetValue(obfuscatedStackTraceLog, out var oldFullSignatures))
+ {
+ deObfuscatedStackTrace = string.Join("|", oldFullSignatures);
+ return true;
+ }
+
+ string obfuscatedStackTraceSignatureWithoutParams = GetMethodSignatureWithoutParams(obfuscatedStackTraceLog);
+ if (_signatureWithParamsMapper.TryGetValue(obfuscatedStackTraceSignatureWithoutParams, out var oldSignaturesWithoutParams))
+ {
+ deObfuscatedStackTrace = obfuscatedStackTraceLog.Replace(obfuscatedStackTraceSignatureWithoutParams, string.Join("|", oldSignaturesWithoutParams));
+ return true;
+ }
+ deObfuscatedStackTrace = null;
+ return false;
+ }
+ }
+}
diff --git a/DeobfuscateStackTrace/mapping.xml b/DeobfuscateStackTrace/mapping.xml
new file mode 100644
index 0000000..06d298c
--- /dev/null
+++ b/DeobfuscateStackTrace/mapping.xml
@@ -0,0 +1,3614 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file