├── .gitignore
├── CHANGELOG.MD
├── CHANGELOG.MD.meta
├── LICENSE
├── LICENSE.meta
├── README.md
├── README.md.meta
├── Scripts.meta
├── Scripts
├── Editor.meta
└── Editor
│ ├── BrunoMikoski.SmartSymbolicate.Editor.asmdef
│ ├── BrunoMikoski.SmartSymbolicate.Editor.asmdef.meta
│ ├── HyperlinkInterceptor.cs
│ ├── HyperlinkInterceptor.cs.meta
│ ├── SmartSymbolicateWindow.cs
│ └── SmartSymbolicateWindow.cs.meta
├── package.json
└── package.json.meta
/.gitignore:
--------------------------------------------------------------------------------
1 | # This .gitignore file should be placed at the root of your Unity project directory
2 | #
3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
4 | #
5 | /[Ll]ibrary/
6 | /[Tt]emp/
7 | /[Oo]bj/
8 | /[Bb]uild/
9 | /[Bb]uilds/
10 | /[Ll]ogs/
11 | /[Mm]emoryCaptures/
12 |
13 | # Asset meta data should only be ignored when the corresponding asset is also ignored
14 | !/[Aa]ssets/**/*.meta
15 |
16 | # Uncomment this line if you wish to ignore the asset store tools plugin
17 | # /[Aa]ssets/AssetStoreTools*
18 |
19 | # Autogenerated Jetbrains Rider plugin
20 | [Aa]ssets/Plugins/Editor/JetBrains*
21 |
22 | # Visual Studio cache directory
23 | .vs/
24 |
25 | # Gradle cache directory
26 | .gradle/
27 |
28 | # Autogenerated VS/MD/Consulo solution and project files
29 | ExportedObj/
30 | .consulo/
31 | *.csproj
32 | *.unityproj
33 | *.sln
34 | *.suo
35 | *.tmp
36 | *.user
37 | *.userprefs
38 | *.pidb
39 | *.booproj
40 | *.svd
41 | *.pdb
42 | *.mdb
43 | *.opendb
44 | *.VC.db
45 |
46 | # Unity3D generated meta files
47 | *.pidb.meta
48 | *.pdb.meta
49 | *.mdb.meta
50 |
51 | # Unity3D generated file on crash reports
52 | sysinfo.txt
53 |
54 | # Builds
55 | *.apk
56 | *.unitypackage
57 |
58 | # Crashlytics generated file
59 | crashlytics-build.properties
60 |
61 |
--------------------------------------------------------------------------------
/CHANGELOG.MD:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ## [1.1.1]
10 | ### Changed
11 | - Fixed formatting to expose all previous information as well, since can hide valueable information
12 | - Improved Hyperlink generator to find target classes
13 |
14 | ## [1.1.0]
15 | ### Changed
16 | - Better output format
17 |
18 | ### Add
19 | - Added new parser that will try to find that file and line number of `libil2cpp` errors
20 |
21 | ## [1.0.3]
22 | ### Changed
23 | - Allowed custom symbols to be used besides the know ones
24 |
25 | ### Fix
26 | - Fixed an issue that when parsing crashes would still use the unity symbols from unity installation instead of the project ones when Strip Engine Code is enabled
27 |
28 |
29 | ## [1.0.2]
30 | ### Changed
31 | - Moved the Menu to `Window/Analysis/Smart Symbolicate`
32 |
33 | ## [1.0.1]
34 | ### Add
35 | - Added a symbol selection option
36 |
37 | ## [1.0.0]
38 | ### Add
39 | - Initial implementation of the Smart Symbolicate
40 |
41 | [1.1.1]: https://github.com/brunomikoski/UnitySmartSymbolicate/releases/tag/v1.1.1
42 | [1.1.0]: https://github.com/brunomikoski/UnitySmartSymbolicate/releases/tag/v1.1.0
43 | [1.0.3]: https://github.com/brunomikoski/UnitySmartSymbolicate/releases/tag/v1.0.3
44 | [1.0.2]: https://github.com/brunomikoski/UnitySmartSymbolicate/releases/tag/v1.0.2
45 | [1.0.1]: https://github.com/brunomikoski/UnitySmartSymbolicate/releases/tag/v1.0.1
46 | [1.0.0]: https://github.com/brunomikoski/UnitySmartSymbolicate/releases/tag/v1.0.0
47 |
--------------------------------------------------------------------------------
/CHANGELOG.MD.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2cc98d63797d66245bb7867fcb950530
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Bruno Mikoski
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ec045a84b366e304b80921485115eba0
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Unity Smart Symbolicate
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | ## Features
45 | - Automatically identify Google Play Crash reports information and settings
46 |
47 | ## How to use?
48 | - Define unity installation folder (It should be the folder where all your unity installations are)
49 | - Define your project [symbols](https://docs.unity3d.com/2020.3/Documentation/Manual/android-symbols.html) folder
50 | - Paste google play crash rates on the input
51 | - Validate information
52 | - Press Parse
53 |
54 | ## FAQ
55 |
56 | ## System Requirements
57 | Unity 2018.4.0 or later versions
58 |
59 |
60 | ## How to install
61 |
62 |
63 |
64 |
65 | Add from OpenUPM | via scoped registry, recommended
66 |
67 | This package is available on OpenUPM: https://openupm.com/packages/com.brunomikoski.animationsequencer
68 |
69 | To add it the package to your project:
70 |
71 | - open `Edit/Project Settings/Package Manager`
72 | - add a new Scoped Registry:
73 | ```
74 | Name: OpenUPM
75 | URL: https://package.openupm.com/
76 | Scope(s): com.brunomikoski
77 | ```
78 | - click Save
79 | - open Package Manager
80 | - click +
81 | - select Add from Git URL
82 | - paste `com.brunomikoski.unitysmartsymbolicate`
83 | - click Add
84 |
85 |
86 |
87 | Add from GitHub | not recommended, no updates :(
88 |
89 | You can also add it directly from GitHub on Unity 2019.4+. Note that you won't be able to receive updates through Package Manager this way, you'll have to update manually.
90 |
91 | - open Package Manager
92 | - click +
93 | - select Add from Git URL
94 | - paste `https://github.com/brunomikoski/UnitySmartSymbolicate.git`
95 | - click Add
96 |
97 |
98 |
99 |
--------------------------------------------------------------------------------
/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7cf8fddf6294fdd4fbcd08c342a569a9
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 10cd6ef00afd26641b0c86de5f4b1015
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/Editor.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ff3ad93ba63dab447b53541b2c32ca26
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Scripts/Editor/BrunoMikoski.SmartSymbolicate.Editor.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "BrunoMikoski.SmartSymbolicate.Editor",
3 | "rootNamespace": "",
4 | "references": [],
5 | "includePlatforms": [
6 | "Editor"
7 | ],
8 | "excludePlatforms": [],
9 | "allowUnsafeCode": false,
10 | "overrideReferences": false,
11 | "precompiledReferences": [],
12 | "autoReferenced": true,
13 | "defineConstraints": [],
14 | "versionDefines": [],
15 | "noEngineReferences": false
16 | }
--------------------------------------------------------------------------------
/Scripts/Editor/BrunoMikoski.SmartSymbolicate.Editor.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bc1e7b1da4c62a54091335e9a9be11d8
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Scripts/Editor/HyperlinkInterceptor.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Reflection;
5 | using Unity.CodeEditor;
6 | using UnityEditor;
7 |
8 | namespace BrunoMikoski.SmartSymbolicate
9 | {
10 | public static class HyperlinkInterceptor
11 | {
12 | private static EventInfo cachedHyperLinkClickedEvent;
13 | private static EventInfo HyperLinkClickedEvent
14 | {
15 | get
16 | {
17 | if (cachedHyperLinkClickedEvent == null)
18 | {
19 | cachedHyperLinkClickedEvent = typeof(EditorGUI).GetEvent("hyperLinkClicked", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
20 | }
21 | return cachedHyperLinkClickedEvent;
22 | }
23 | }
24 |
25 | private static Delegate cachedHyperLinkClickedHandler;
26 | private static Delegate HyperLinkClickedHandler
27 | {
28 | get
29 | {
30 | if (cachedHyperLinkClickedHandler == null)
31 | {
32 | #if UNITY_2021_2_OR_NEWER
33 | Action handler = (_, args) => OnProcessClickData(args.hyperLinkData);
34 | cachedHyperLinkClickedHandler = handler;
35 | #else
36 | if (HyperLinkClickedEvent != null)
37 | {
38 | var method = typeof(HyperlinkInterceptor).GetMethod("OnClicked", BindingFlags.Static | BindingFlags.NonPublic| BindingFlags.Public);
39 | if (method != null)
40 | {
41 | cachedHyperLinkClickedHandler = Delegate.CreateDelegate(HyperLinkClickedEvent.EventHandlerType, method);
42 | }
43 | }
44 | #endif
45 | }
46 | return cachedHyperLinkClickedHandler;
47 | }
48 | }
49 |
50 | public static void Enable()
51 | {
52 | if (HyperLinkClickedHandler == null)
53 | throw new NotSupportedException();
54 |
55 | #if UNITY_2021_2_OR_NEWER
56 | EditorGUI.hyperLinkClicked += (Action)HyperLinkClickedHandler;
57 | #else
58 | HyperLinkClickedEvent.AddMethod.Invoke(null, new object[] { HyperLinkClickedHandler });
59 | #endif
60 | }
61 | public static void Disable()
62 | {
63 | if (HyperLinkClickedHandler == null)
64 | throw new NotSupportedException();
65 |
66 | #if UNITY_2021_2_OR_NEWER
67 | EditorGUI.hyperLinkClicked -= (Action)HyperLinkClickedHandler;
68 | #else
69 | HyperLinkClickedEvent.RemoveMethod.Invoke(null, new object[] { HyperLinkClickedHandler });
70 | #endif
71 | }
72 |
73 | static PropertyInfo property;
74 | static void OnClicked(object sender, EventArgs args)
75 | {
76 | if (!EditorWindow.HasOpenInstances())
77 | return;
78 |
79 | if (property == null)
80 | {
81 | property = args.GetType().GetProperty("hyperlinkInfos", BindingFlags.Instance | BindingFlags.Public);
82 | if (property == null) return;
83 | }
84 | var infos = property.GetValue(args);
85 | if (infos is Dictionary)
86 | {
87 | OnProcessClickData((Dictionary)infos);
88 | }
89 | }
90 |
91 | static void OnProcessClickData(Dictionary infos)
92 | {
93 | if (infos == null) return;
94 | if (!infos.TryGetValue("href", out var path)) return;
95 | infos.TryGetValue("line", out var line);
96 | infos.TryGetValue("column", out var column);
97 |
98 | if (File.Exists(path))
99 | {
100 | int.TryParse(line, out var _line);
101 | int.TryParse(column, out var _column);
102 | OpenFileInIDE(path, _line, _column);
103 | }
104 | }
105 |
106 | public static bool OpenFileInIDE(string filepath, int line, int column)
107 | {
108 | return CodeEditor.CurrentEditor.OpenProject(filepath, line, column);
109 | }
110 | }
111 | }
--------------------------------------------------------------------------------
/Scripts/Editor/HyperlinkInterceptor.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 27f5b855b2e24cd0a4e0f9981a6b9b1e
3 | timeCreated: 1652950101
--------------------------------------------------------------------------------
/Scripts/Editor/SmartSymbolicateWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Text.RegularExpressions;
8 | using UnityEditor;
9 | using UnityEngine;
10 | using Debug = UnityEngine.Debug;
11 |
12 | namespace BrunoMikoski.SmartSymbolicate
13 | {
14 | public class SmartSymbolicateWindow : EditorWindow
15 | {
16 | private const string UNITY_PATH_STORAGE_KEY = "SmartSymbolicate.UnityPathStorageKey";
17 | private const string PROJECT_SYMBOLS_PATH_STORAGE_KEY = "SmartSymbolicate.ProjectSymbolsStorageKey";
18 |
19 | private const string LIB_IL2CPP_NAME = "libil2cpp";
20 | private const string LIB_UNITY_NAME = "libunity";
21 |
22 | private const string DEBUG_EXTENSION = "dbg.so";
23 | private const string SYM_EXTENSION = "sym.so";
24 | private const string DEFAULT_EXTENSION = "so";
25 | private const string SMART_SYMBOLICATE_TITLE_WINDOW = "Smart Symbolicate";
26 |
27 | private string Addr2Line32Path
28 | {
29 | get
30 | {
31 | if (NDKVersion >= new Version(23, 1))
32 | {
33 | if (Application.platform == RuntimePlatform.WindowsEditor)
34 | return @"Editor\Data\PlaybackEngines\AndroidPlayer\NDK\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-addr2line.exe";
35 | return @"PlaybackEngines/AndroidPlayer/NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-addr2line";
36 | }
37 | else
38 | {
39 | if (Application.platform == RuntimePlatform.WindowsEditor)
40 | return @"Editor\Data\PlaybackEngines\AndroidPlayer\NDK\toolchains\arm-linux-androideabi-4.9\prebuilt\windows-x86_64\bin\arm-linux-androideabi-addr2line.exe";
41 | return @"PlaybackEngines/AndroidPlayer/NDK/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line";
42 | }
43 | }
44 | }
45 |
46 | private string Addr2Line64Path
47 | {
48 | get
49 | {
50 | if (NDKVersion >= new Version(23, 1))
51 | {
52 | if (Application.platform == RuntimePlatform.WindowsEditor)
53 | return @"Editor\Data\PlaybackEngines\AndroidPlayer\NDK\toolchains\llvm\prebuilt\windows-x86_64\bin\llvm-addr2line.exe";
54 | return @"PlaybackEngines/AndroidPlayer/NDK/toolchains/llvm/prebuilt/darwin-x86_64/bin/llvm-addr2line";
55 | }
56 | else
57 | {
58 | if (Application.platform == RuntimePlatform.WindowsEditor)
59 | return @"Editor\Data\PlaybackEngines\AndroidPlayer\NDK\toolchains\aarch64-linux-android-4.9\prebuilt\windows-x86_64\bin\aarch64-linux-android-addr2line.exe";
60 | return @"PlaybackEngines/AndroidPlayer/NDK/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/aarch64-linux-android-addr2line";
61 | }
62 | }
63 | }
64 |
65 | private Version NDKVersion
66 | {
67 | get
68 | {
69 | var ndkPath = Application.platform == RuntimePlatform.WindowsEditor ? @"Editor\Data\PlaybackEngines\AndroidPlayer\NDK" : @"PlaybackEngines/AndroidPlayer/NDK";
70 | var sourceProperties = Path.Combine(unityHubPath, unityVersion, ndkPath, "source.properties");
71 | if (!File.Exists(sourceProperties))
72 | throw new Exception($"Couldn't acquire NDK version, '{sourceProperties}' was not found");
73 |
74 | var contents = File.ReadAllText(sourceProperties);
75 | var regex = new Regex(@"Pkg\.Revision\s*=\s*(?\S+)");
76 | var match = regex.Match(contents);
77 | if (match.Success)
78 | {
79 | var versionText = match.Groups["version"].Value;
80 | if (!Version.TryParse(versionText, out var version))
81 | throw new Exception($"Couldn't resolve version from '{sourceProperties}', the value was '{versionText}'");
82 | return version;
83 | }
84 | else
85 | {
86 | throw new Exception($"Couldn't not find NDK version inside '{sourceProperties}', file contents\n:{contents}");
87 | }
88 | }
89 | }
90 |
91 | private static string DefaultUnityInstallationFolder
92 | {
93 | get
94 | {
95 | if (Application.platform == RuntimePlatform.OSXEditor)
96 | return @"/Applications/Unity/Hub/Editor";
97 | return @"C:\Program Files\Unity\Hub\Editor";
98 | }
99 | }
100 |
101 | private static string UnityVariationsPath
102 | {
103 | get
104 | {
105 | if (Application.platform == RuntimePlatform.WindowsEditor)
106 | return @"Editor\Data\PlaybackEngines\AndroidPlayer\Variations";
107 | return @"PlaybackEngines/AndroidPlayer/Variations";
108 | }
109 | }
110 |
111 |
112 | private enum ReleaseType
113 | {
114 | Release = 0,
115 | Development = 1
116 | }
117 |
118 | private enum ScriptingBackendType
119 | {
120 | il2cpp = 0,
121 | mono = 1
122 | }
123 |
124 | private enum CPUType
125 | {
126 | arm64_v8a = 0,
127 | armeabi_v7a = 1
128 | }
129 |
130 | private enum SymbolsType
131 | {
132 | Auto,
133 | libil2cpp,
134 | libunity
135 | }
136 |
137 | private string unityHubPath
138 | {
139 | get => EditorPrefs.GetString(UNITY_PATH_STORAGE_KEY, DefaultUnityInstallationFolder);
140 | set => EditorPrefs.SetString(UNITY_PATH_STORAGE_KEY, value);
141 | }
142 |
143 | private string projectSymbolsPath
144 | {
145 | get => EditorPrefs.GetString(PROJECT_SYMBOLS_PATH_STORAGE_KEY, string.Empty);
146 | set => EditorPrefs.SetString(PROJECT_SYMBOLS_PATH_STORAGE_KEY, value);
147 | }
148 |
149 | private string crashInput;
150 | private string unityVersion;
151 | private string output;
152 |
153 | private ReleaseType releaseType = ReleaseType.Release;
154 | private ScriptingBackendType scriptingBackendType = ScriptingBackendType.il2cpp;
155 | private CPUType cpuType = CPUType.arm64_v8a;
156 | private SymbolsType symbolsType = SymbolsType.Auto;
157 |
158 | private string[] releaseTypeDisplayNames = Array.Empty();
159 | private string[] scriptingBackendTypeNames = Array.Empty();
160 | private string[] symbolTypeNames = Array.Empty();
161 | private string[] cpuTypeNames = Array.Empty();
162 | private string[] availableUnityVersions = Array.Empty();
163 |
164 | private bool validUnityHubFolder;
165 | private bool isMissingUnityVersion;
166 | private string desiredUnityVersion;
167 | private bool isMissingCPUType;
168 | private string desiredCPUType;
169 |
170 | private Vector2 outputScrollView;
171 | private GUIStyle outputTextFieldStyle;
172 | private Vector2 inputScrollView;
173 | private bool printCommands;
174 |
175 | private static HashSet unknowLibs = new HashSet();
176 |
177 | [MenuItem("Window/Analysis/Smart Symbolicate", false, 1000)]
178 | public static void ShowExample()
179 | {
180 | SmartSymbolicateWindow wnd = GetWindow();
181 | wnd.titleContent = new GUIContent(SMART_SYMBOLICATE_TITLE_WINDOW);
182 | }
183 |
184 | private void OnEnable()
185 | {
186 | GenerateDisplayNames();
187 | ValidateUnityHubPath(unityHubPath);
188 | HyperlinkInterceptor.Enable();
189 | }
190 |
191 | private void OnDisable()
192 | {
193 | HyperlinkInterceptor.Disable();
194 | }
195 |
196 | private void GenerateDisplayNames()
197 | {
198 | releaseTypeDisplayNames = Enum.GetNames(typeof(ReleaseType));
199 | scriptingBackendTypeNames = Enum.GetNames(typeof(ScriptingBackendType));
200 | cpuTypeNames = Enum.GetNames(typeof(CPUType));
201 | symbolTypeNames = Enum.GetNames(typeof(SymbolsType));
202 |
203 | for (int i = 0; i < cpuTypeNames.Length; i++)
204 | cpuTypeNames[i] = cpuTypeNames[i].Replace("_", "-");
205 | }
206 |
207 | private void OnGUI()
208 | {
209 | if (outputTextFieldStyle == null)
210 | outputTextFieldStyle = new GUIStyle(EditorStyles.textArea) { wordWrap = true, richText = true };
211 |
212 | DrawPaths();
213 | DrawInput();
214 | DrawSettings();
215 | DrawOutput();
216 | }
217 |
218 | private void DrawOutput()
219 | {
220 | EditorGUILayout.BeginVertical("HelpBox");
221 | EditorGUILayout.LabelField("Output", EditorStyles.foldoutHeader);
222 | EditorGUILayout.Separator();
223 |
224 | outputScrollView = EditorGUILayout.BeginScrollView(outputScrollView, false, true, GUILayout.ExpandHeight(true));
225 | EditorGUILayout.TextArea(output, outputTextFieldStyle, GUILayout.ExpandHeight(true),
226 | GUILayout.ExpandWidth(true));
227 | EditorGUILayout.EndScrollView();
228 |
229 | EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(crashInput) || string.IsNullOrEmpty(unityHubPath) || string.IsNullOrEmpty(projectSymbolsPath));
230 | if (GUILayout.Button("Parse"))
231 | {
232 | ParseInput();
233 | }
234 |
235 | EditorGUI.EndDisabledGroup();
236 |
237 | EditorGUILayout.EndVertical();
238 | }
239 |
240 | private void ParseInput()
241 | {
242 | unknowLibs.Clear();
243 |
244 | string targetADDR2 = GetTargetAddr2line();
245 | if (!File.Exists(targetADDR2))
246 | {
247 | Debug.LogError($"Failed to find ADDR2Line at path {targetADDR2}");
248 | return;
249 | }
250 |
251 | StringBuilder parsedResults = new StringBuilder();
252 | string[] lines = crashInput.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
253 |
254 | for (int i = 0; i < lines.Length; i++)
255 | {
256 | string line = lines[i];
257 |
258 | Match memoryAddress = Regex.Match(line, @"0[xX][0-9a-fA-F]+");
259 | if (!memoryAddress.Success)
260 | {
261 | Regex removeEmptyLines = new Regex(@"\s+");
262 | if (removeEmptyLines.Replace(line, "").Length < 3)
263 | continue;
264 |
265 | parsedResults.AppendLine(line);
266 | continue;
267 | }
268 |
269 | string targetLib = string.Empty;
270 | if (symbolsType == SymbolsType.Auto)
271 | {
272 | Match libMatch = Regex.Match(line, @"(?<=at )(.*)(?=\.)");
273 | if (!libMatch.Success)
274 | continue;
275 |
276 | targetLib = libMatch.Value;
277 | }
278 | else if (symbolsType == SymbolsType.libunity)
279 | {
280 | targetLib = Path.GetFileNameWithoutExtension(LIB_UNITY_NAME);
281 | }
282 | else if (symbolsType == SymbolsType.libil2cpp)
283 | {
284 | targetLib = Path.GetFileNameWithoutExtension(LIB_IL2CPP_NAME);
285 | }
286 |
287 | string memoryAddressValue = memoryAddress.Value;
288 | if (!TryGetLibPath(targetLib, out string knowPath))
289 | {
290 | parsedResults.AppendLine($"Unknow lib named {targetLib} :: {memoryAddressValue}");
291 | continue;
292 | }
293 |
294 | if (!File.Exists(knowPath))
295 | {
296 | parsedResults.AppendLine($"Failed to find lib {targetLib} at Path: {knowPath}");
297 | continue;
298 | }
299 |
300 | if (printCommands)
301 | {
302 | parsedResults.AppendLine($"Executing Command: {targetADDR2} -f -C -e \"{knowPath}\" {memoryAddressValue}");
303 | }
304 |
305 | EditorUtility.DisplayProgressBar("Processing Symbols", $"Processing {memoryAddressValue} against {targetLib}", (float)i / lines.Length);
306 |
307 | using (Process process = new Process())
308 | {
309 | process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
310 | process.StartInfo.FileName = targetADDR2;
311 | process.StartInfo.Arguments = $"-f -C -e \"{knowPath}\" {memoryAddressValue}";
312 | process.StartInfo.UseShellExecute = false;
313 | process.StartInfo.CreateNoWindow = true;
314 | process.StartInfo.RedirectStandardOutput = true;
315 | process.StartInfo.RedirectStandardInput = true;
316 | process.StartInfo.RedirectStandardError = true;
317 | process.Start();
318 |
319 | string processOutput = process.StandardOutput.ReadToEnd().Replace("??:?", string.Empty);
320 |
321 | if (targetLib.IndexOf(LIB_IL2CPP_NAME, StringComparison.Ordinal) > -1)
322 | {
323 | TryGenerateCodeHyperlinkForOutput(ref processOutput);
324 | }
325 |
326 | parsedResults.AppendLine($" at {GetLibraryDisplay(targetLib)}.{memoryAddressValue} => {processOutput}");
327 |
328 | string error = process.StandardError.ReadToEnd();
329 | if (!string.IsNullOrEmpty(error))
330 | parsedResults.AppendLine($"[Error] {memoryAddressValue} => {error}");
331 |
332 | process.WaitForExit();
333 | }
334 | }
335 |
336 | output = Regex.Replace(parsedResults.ToString(), @"^\s+$[\r\n]*", string.Empty, RegexOptions.Multiline);
337 |
338 | EditorUtility.ClearProgressBar();
339 | }
340 |
341 | private string GetLibraryDisplay(string targetLib)
342 | {
343 | string color = "green";
344 | if (string.Equals(targetLib, "libunity", StringComparison.Ordinal))
345 | color = "yellow";
346 | if (unknowLibs.Contains(targetLib))
347 | color = "red";
348 |
349 | return $"{targetLib}";
350 | }
351 |
352 | private void TryGenerateCodeHyperlinkForOutput(ref string outputLine)
353 | {
354 | try
355 | {
356 | if (string.IsNullOrEmpty(outputLine))
357 | return;
358 |
359 | string[] splitResults = outputLine.Split(new[] { "_" }, StringSplitOptions.RemoveEmptyEntries);
360 | if (splitResults.Length < 2)
361 | return;
362 |
363 | string className = splitResults[0];
364 | string methodName = splitResults[1];
365 |
366 | string[] classGUIDs = AssetDatabase.FindAssets($"{className} t:TextAsset");
367 | if (classGUIDs.Length == 0)
368 | return;
369 |
370 | string classPath = string.Empty;
371 |
372 | for (int i = 0; i < classGUIDs.Length; i++)
373 | {
374 | string possibleClass = AssetDatabase.GUIDToAssetPath(classGUIDs[i]);
375 | string possibleClassName = Path.GetFileNameWithoutExtension(possibleClass);
376 | if (string.Equals(possibleClassName, className, StringComparison.Ordinal))
377 | {
378 | classPath = possibleClass;
379 | break;
380 | }
381 | }
382 |
383 | if (string.IsNullOrEmpty(classPath))
384 | return;
385 |
386 | string[] lines = File.ReadAllLines(Path.GetFullPath(classPath));
387 |
388 | int hitCounts = 0;
389 | int targetLine = -1;
390 |
391 | for (int i = 0; i < lines.Length; i++)
392 | {
393 | string line = lines[i];
394 |
395 | if (line.IndexOf(methodName, StringComparison.Ordinal) == -1)
396 | continue;
397 |
398 | //If is assigning the value somewhere, probably its not the method declaration
399 | if (line.IndexOf("=", StringComparison.OrdinalIgnoreCase) > -1)
400 | continue;
401 |
402 | //If has less than 1 space, probably is a method invocation
403 | if (line.Split(' ').Length < 1)
404 | continue;
405 |
406 | //Ignore .
407 | if (line.IndexOf(".", StringComparison.OrdinalIgnoreCase) > -1)
408 | continue;
409 |
410 | hitCounts++;
411 | targetLine = i + 1;
412 | }
413 |
414 | if (hitCounts <= 0)
415 | return;
416 |
417 | outputLine = outputLine.Replace("\n", string.Empty);
418 | if (hitCounts == 1)
419 | outputLine = $"{outputLine} (at {classPath}:{targetLine} This is a guess :) ) ";
420 | else
421 | outputLine = $"{outputLine} (at {classPath} This is a guess :) ) ";
422 | }
423 | catch (Exception e)
424 | {
425 | Debug.LogException(e);
426 | }
427 | }
428 |
429 | private bool TryGetLibPath(string targetLibName, out string libKnowPath)
430 | {
431 | string targetProjectSymbolsPath = projectSymbolsPath;
432 | targetProjectSymbolsPath = Path.Combine(targetProjectSymbolsPath, cpuTypeNames[(int)cpuType]);
433 |
434 | DirectoryInfo projectPathDirectory = new DirectoryInfo(targetProjectSymbolsPath);
435 |
436 | FileInfo[] targetLibFiles = projectPathDirectory.GetFiles($"{targetLibName}*", SearchOption.AllDirectories);
437 | SortedDictionary prioritySymbols = new SortedDictionary();
438 | for (int i = 0; i < targetLibFiles.Length; i++)
439 | {
440 | FileInfo fileInfo = targetLibFiles[i];
441 |
442 | if (!prioritySymbols.ContainsKey(DEBUG_EXTENSION))
443 | {
444 | if (fileInfo.FullName.IndexOf(DEBUG_EXTENSION, StringComparison.OrdinalIgnoreCase) > -1)
445 | {
446 | prioritySymbols.Add(DEBUG_EXTENSION, fileInfo);
447 | continue;
448 | }
449 | }
450 |
451 | if (!prioritySymbols.ContainsKey(SYM_EXTENSION))
452 | {
453 | if (fileInfo.FullName.IndexOf(SYM_EXTENSION, StringComparison.OrdinalIgnoreCase) > -1)
454 | {
455 | prioritySymbols.Add(SYM_EXTENSION, fileInfo);
456 | continue;
457 | }
458 | }
459 |
460 | if (!prioritySymbols.ContainsKey(DEFAULT_EXTENSION))
461 | {
462 | if (fileInfo.FullName.IndexOf(DEFAULT_EXTENSION, StringComparison.OrdinalIgnoreCase) > -1)
463 | {
464 | prioritySymbols.Add(DEFAULT_EXTENSION, fileInfo);
465 | }
466 | }
467 | }
468 |
469 | if (prioritySymbols.TryGetValue(DEBUG_EXTENSION, out FileInfo resultFileInfo))
470 | {
471 | libKnowPath = resultFileInfo.FullName;
472 | return true;
473 | }
474 |
475 | if (prioritySymbols.TryGetValue(SYM_EXTENSION, out resultFileInfo))
476 | {
477 | libKnowPath = resultFileInfo.FullName;
478 | return true;
479 | }
480 |
481 | if (prioritySymbols.TryGetValue(DEFAULT_EXTENSION, out resultFileInfo))
482 | {
483 | libKnowPath = resultFileInfo.FullName;
484 | return true;
485 | }
486 |
487 |
488 | if (targetLibName.Equals(LIB_UNITY_NAME, StringComparison.Ordinal))
489 | {
490 | string targetPath = Path.Combine(unityHubPath, unityVersion);
491 | targetPath = Path.Combine(targetPath, UnityVariationsPath);
492 |
493 | targetPath = Path.Combine(targetPath, scriptingBackendTypeNames[(int)releaseType]);
494 | targetPath = Path.Combine(targetPath, releaseTypeDisplayNames[(int)releaseType]);
495 | targetPath = Path.Combine(targetPath, "Symbols");
496 | targetPath = Path.Combine(targetPath, cpuTypeNames[(int)cpuType]);
497 |
498 |
499 | targetPath = Path.Combine(targetPath, $"{LIB_UNITY_NAME}.{SYM_EXTENSION}");
500 | libKnowPath = targetPath;
501 | return true;
502 | }
503 |
504 |
505 | libKnowPath = string.Empty;
506 | unknowLibs.Add(targetLibName);
507 | return false;
508 | }
509 |
510 | private string GetTargetAddr2line()
511 | {
512 | if (cpuType == CPUType.arm64_v8a)
513 | return Path.Combine(Path.Combine(unityHubPath, unityVersion), Addr2Line64Path);
514 |
515 | return Path.Combine(Path.Combine(unityHubPath, unityVersion), Addr2Line32Path);
516 | }
517 |
518 | private void DrawSettings()
519 | {
520 | EditorGUILayout.BeginVertical("HelpBox");
521 | EditorGUILayout.LabelField("Settings", EditorStyles.foldoutHeader);
522 | EditorGUILayout.Separator();
523 | EditorGUI.indentLevel++;
524 |
525 | EditorGUILayout.BeginHorizontal();
526 |
527 | EditorGUILayout.BeginVertical("HelpBox", GUILayout.Width(100), GUILayout.Height(68));
528 | EditorGUILayout.LabelField("Unity Version", EditorStyles.boldLabel);
529 | EditorGUI.BeginChangeCheck();
530 |
531 | if (validUnityHubFolder)
532 | {
533 | if (isMissingUnityVersion)
534 | {
535 | EditorGUILayout.HelpBox($"Crash Reports needs Unity version {desiredUnityVersion}",
536 | MessageType.Error);
537 | }
538 | else
539 | {
540 | int selectedUnityVersion = Mathf.Clamp(Array.IndexOf(availableUnityVersions, unityVersion), 0,
541 | availableUnityVersions.Length);
542 | unityVersion = availableUnityVersions[EditorGUILayout.Popup(selectedUnityVersion, availableUnityVersions)];
543 | printCommands = EditorGUILayout.ToggleLeft("Print Commands", printCommands);
544 | }
545 | }
546 | else
547 | {
548 | EditorGUILayout.HelpBox("Invalid Unity Hub Folder", MessageType.Error);
549 | }
550 |
551 | EditorGUILayout.EndVertical();
552 |
553 | EditorGUILayout.BeginVertical("HelpBox", GUILayout.Width(100));
554 | EditorGUILayout.LabelField("Release Type", EditorStyles.boldLabel);
555 | releaseType = (ReleaseType)GUILayout.SelectionGrid((int)releaseType, releaseTypeDisplayNames, 1, EditorStyles.radioButton);
556 | EditorGUILayout.EndVertical();
557 |
558 | EditorGUILayout.BeginVertical("HelpBox", GUILayout.Width(100));
559 | EditorGUILayout.LabelField("Scripting Backend", EditorStyles.boldLabel);
560 | scriptingBackendType = (ScriptingBackendType)GUILayout.SelectionGrid((int)scriptingBackendType, scriptingBackendTypeNames, 1, EditorStyles.radioButton);
561 | EditorGUILayout.EndVertical();
562 |
563 | EditorGUILayout.BeginVertical("HelpBox", GUILayout.Width(100));
564 | EditorGUILayout.LabelField("CPU", EditorStyles.boldLabel);
565 | if (!isMissingCPUType)
566 | {
567 | cpuType = (CPUType)GUILayout.SelectionGrid((int)cpuType, cpuTypeNames, 1, EditorStyles.radioButton);
568 | }
569 | else
570 | {
571 | EditorGUILayout.HelpBox($"Missing CPU Type {desiredCPUType}", MessageType.Error);
572 | }
573 | EditorGUILayout.EndVertical();
574 |
575 |
576 | EditorGUILayout.BeginVertical("HelpBox", GUILayout.Width(100));
577 | EditorGUILayout.LabelField("Symbols", EditorStyles.boldLabel);
578 | symbolsType = (SymbolsType)GUILayout.SelectionGrid((int)symbolsType, symbolTypeNames, 2, EditorStyles.radioButton);
579 | EditorGUILayout.EndVertical();
580 |
581 | EditorGUILayout.EndHorizontal();
582 |
583 |
584 | EditorGUI.indentLevel--;
585 | EditorGUILayout.EndVertical();
586 | }
587 |
588 | private void DrawInput()
589 | {
590 | EditorGUILayout.BeginVertical("HelpBox");
591 | EditorGUILayout.LabelField("Crash Input", EditorStyles.foldoutHeader);
592 | EditorGUILayout.Separator();
593 |
594 | inputScrollView = EditorGUILayout.BeginScrollView(inputScrollView, false, true, GUILayout.Height(200));
595 | EditorGUI.BeginChangeCheck();
596 | crashInput = EditorGUILayout.TextArea(crashInput, GUILayout.ExpandHeight(true));
597 | if (EditorGUI.EndChangeCheck())
598 | TryGetInformationFromCrashInput();
599 |
600 | EditorGUILayout.EndScrollView();
601 |
602 | EditorGUILayout.EndVertical();
603 | }
604 |
605 | private void TryGetInformationFromCrashInput()
606 | {
607 | if (string.IsNullOrEmpty(crashInput))
608 | {
609 | isMissingCPUType = false;
610 | isMissingUnityVersion = false;
611 | return;
612 | }
613 |
614 | TryGetScriptingBackendFromCrashInput();
615 | TryGetUnityVersionFromCrashInput();
616 | TryGetCPUFromCrashInput();
617 | TryGetBuildType();
618 | }
619 |
620 | private void TryGetBuildType()
621 | {
622 | Match buildTypeMatch = Regex.Match(crashInput, pattern: @"(?<=Build type ')(.*?)[^']*");
623 | if (buildTypeMatch.Success)
624 | {
625 | int targetBuildType = Array.IndexOf(releaseTypeDisplayNames, buildTypeMatch.Value);
626 | if (targetBuildType != -1)
627 | {
628 | releaseType = (ReleaseType)targetBuildType;
629 | }
630 | }
631 | }
632 |
633 | private void TryGetCPUFromCrashInput()
634 | {
635 | Match cpuMatch = Regex.Match(crashInput, pattern: @"(?<=CPU ')(.*?)[^']*");
636 | if (cpuMatch.Success)
637 | {
638 | int targetCPUIndex = Array.IndexOf(cpuTypeNames, cpuMatch.Value);
639 | if (targetCPUIndex == -1)
640 | {
641 | isMissingCPUType = true;
642 | desiredCPUType = cpuMatch.Value;
643 | }
644 | else
645 | {
646 | cpuType = (CPUType)targetCPUIndex;
647 | isMissingCPUType = false;
648 | }
649 | }
650 | else
651 | {
652 | isMissingCPUType = false;
653 | }
654 | }
655 |
656 | private void TryGetUnityVersionFromCrashInput()
657 | {
658 | Match unityVersionMatch = Regex.Match(crashInput, pattern: @"(?<=Version ')(.*?)[^ ']*");
659 | if (unityVersionMatch.Success)
660 | {
661 | int targetUnityVersionIndex = Array.IndexOf(availableUnityVersions, unityVersionMatch.Value);
662 | if (targetUnityVersionIndex == -1)
663 | {
664 | isMissingUnityVersion = true;
665 | desiredUnityVersion = unityVersionMatch.Value;
666 | }
667 | else
668 | {
669 | isMissingUnityVersion = false;
670 | unityVersion = availableUnityVersions[targetUnityVersionIndex];
671 | }
672 | }
673 | else
674 | {
675 | isMissingUnityVersion = false;
676 | desiredUnityVersion = "Unknow";
677 | }
678 | }
679 |
680 | private void TryGetScriptingBackendFromCrashInput()
681 | {
682 | Match scriptingBackendNameMatch = Regex.Match(crashInput, @"(?<=\Scripting Backend ')(.*?)[^']*");
683 | if (scriptingBackendNameMatch.Success)
684 | {
685 | int newScriptingBackend = Array.IndexOf(scriptingBackendTypeNames, scriptingBackendNameMatch.Value);
686 | if (newScriptingBackend != -1)
687 | scriptingBackendType = (ScriptingBackendType)newScriptingBackend;
688 | }
689 | }
690 |
691 |
692 | private void DrawPaths()
693 | {
694 | EditorGUILayout.BeginVertical("HelpBox");
695 | EditorGUILayout.LabelField("Paths", EditorStyles.foldoutHeader);
696 | EditorGUILayout.Separator();
697 |
698 | EditorGUI.indentLevel++;
699 |
700 | EditorGUILayout.BeginHorizontal();
701 | EditorGUI.BeginChangeCheck();
702 | string newUnityPath = EditorGUILayout.TextField("Unity Hub Path", unityHubPath);
703 | if (EditorGUI.EndChangeCheck())
704 | ValidateUnityHubPath(newUnityPath);
705 |
706 | if (GUILayout.Button("Browse", GUILayout.Width(70)))
707 | {
708 | newUnityPath = EditorUtility.OpenFolderPanel("Select Unity Hub Root Folder", unityHubPath, "");
709 | if (!string.Equals(newUnityPath, unityHubPath))
710 | ValidateUnityHubPath(newUnityPath);
711 | }
712 |
713 | EditorGUILayout.EndHorizontal();
714 |
715 | EditorGUILayout.BeginHorizontal();
716 | projectSymbolsPath = EditorGUILayout.TextField("Project Symbols", projectSymbolsPath);
717 |
718 | if (GUILayout.Button("Browse", GUILayout.Width(70)))
719 | {
720 | projectSymbolsPath = EditorUtility.OpenFolderPanel("Select Unity Hub Root Folder", projectSymbolsPath, "");
721 | }
722 |
723 | EditorGUILayout.EndHorizontal();
724 |
725 |
726 | EditorGUI.indentLevel--;
727 | EditorGUILayout.EndVertical();
728 | }
729 |
730 | private void ValidateUnityHubPath(string targetUnityPath)
731 | {
732 | unityHubPath = targetUnityPath;
733 |
734 | string[] childDirectories = Directory.GetDirectories(targetUnityPath);
735 |
736 | List validUnityPaths = new List();
737 |
738 | for (int i = 0; i < childDirectories.Length; i++)
739 | {
740 | string folderName = Path.GetFileName(childDirectories[i]);
741 | if (!Regex.Match(folderName, @"^[1-9]\d*(\.[1-9]\d*)*[a-z]*[1-9]").Success)
742 | continue;
743 |
744 | validUnityPaths.Add(folderName);
745 | }
746 | availableUnityVersions = validUnityPaths.ToArray();
747 | validUnityHubFolder = availableUnityVersions.Length > 0;
748 | }
749 | }
750 | }
751 |
--------------------------------------------------------------------------------
/Scripts/Editor/SmartSymbolicateWindow.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6c8efce964139ee4191926b9eb7aa475
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.brunomikoski.unitysmartsymbolicate",
3 | "displayName": "Smart Symbolicate",
4 | "version": "1.1.1",
5 | "unity": "2018.4",
6 | "description": "A tool to help you symbolicate android crashes",
7 | "keywords": [
8 | "symbolicate",
9 | "google play",
10 | "mobile",
11 | "crash",
12 | "il2cpp"
13 | ],
14 | "category": "editor extensions",
15 | "homepage": "https://github.com/brunomikoski/UnitySmartSymbolicate",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/brunomikoski/UnitySmartSymbolicate.git"
19 | },
20 | "bugs": "https://github.com/brunomikoski/UnitySmartSymbolicate/issues",
21 | "author": {
22 | "name": "Bruno Mikoski",
23 | "url": "https://www.brunomikoski.com"
24 | },
25 | "type": "library"
26 | }
--------------------------------------------------------------------------------
/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fc8b54ed970180344a6acbd1c796e468
3 | PackageManifestImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------