| | |
| | | using System.Collections; |
| | | using System; |
| | | using System.Collections.Generic; |
| | | using System.IO; |
| | | using System.Reflection; |
| | | using System.Runtime.Serialization; |
| | | using System.Text; |
| | | using UnityEngine; |
| | | using UnityEditor; |
| | | |
| | | public class PackageDetailsWindow : ScriptableWizard |
| | | { |
| | | private class PrintNode |
| | | { |
| | | public object value; |
| | | public bool isArray; |
| | | public string[] childNames; |
| | | public int[] childIndexes; |
| | | public string content; |
| | | } |
| | | |
| | | private struct DetailSearchMatch |
| | | { |
| | | public int detailIndex; |
| | | public int characterIndex; |
| | | |
| | | public DetailSearchMatch(int detailIndex, int characterIndex) |
| | | { |
| | | this.detailIndex = detailIndex; |
| | | this.characterIndex = characterIndex; |
| | | } |
| | | } |
| | | |
| | | static PackageDetailsWindow window; |
| | | public static string packageTime; |
| | | public static string packageName; |
| | | public static string packageBytes; |
| | | public static List<string> packageDetails; |
| | | private static string[] formattedPackageDetails; |
| | | private static bool[] detailFormattedStates; |
| | | private static bool clearDetailSelectionOnNextGUI; |
| | | private static string detailSearchText = string.Empty; |
| | | private static string activeDetailSearchText = string.Empty; |
| | | private static readonly List<DetailSearchMatch> detailSearchMatches = |
| | | new List<DetailSearchMatch>(); |
| | | private static int currentDetailSearchMatch = -1; |
| | | private static int pendingScrollSearchMatch = -1; |
| | | private static float[] detailRowPositions; |
| | | private static float[] detailSearchMatchPositions; |
| | | private static float detailScrollViewHeight; |
| | | |
| | | private Vector2 scrollPosition; |
| | | |
| | | public static void SetPackage(NetPackage package) |
| | | { |
| | | packageTime = package.time; |
| | | packageName = package.name; |
| | | packageBytes = package.content; |
| | | packageDetails = BuildPackageDetails(package); |
| | | ResetDetailFormatState(); |
| | | ResetDetailSearchState(); |
| | | RequestDetailDisplayRefresh(); |
| | | } |
| | | |
| | | private void OnGUI() |
| | | { |
| | | window = this; |
| | | ClearDetailSelectionIfNeeded(); |
| | | |
| | | var fieldDetailStyle = new GUIStyle(EditorStyles.textArea); |
| | | fieldDetailStyle.wordWrap = true; |
| | | |
| | | EditorGUILayout.BeginVertical(); |
| | | EditorGUILayout.Space(); |
| | | EditorGUILayout.LabelField("名称:", !string.IsNullOrEmpty(packageName) ? packageName : ""); |
| | |
| | | EditorGUILayout.Space(); |
| | | |
| | | EditorGUILayout.LabelField("字段详情:"); |
| | | ApplyPendingSearchScroll(); |
| | | scrollPosition = GUILayout.BeginScrollView(scrollPosition, false, true); |
| | | |
| | | if (packageDetails != null) |
| | | { |
| | | for (int i = 0; i < packageDetails.Count; i++) |
| | | { |
| | | EditorGUILayout.TextField(packageDetails[i]); |
| | | using (new GUILayout.HorizontalScope()) |
| | | { |
| | | var detail = GetDisplayDetail(i); |
| | | var detailWidth = Mathf.Max(100f, position.width - 145f); |
| | | var detailHeight = Mathf.Max(EditorGUIUtility.singleLineHeight + 4f, |
| | | fieldDetailStyle.CalcHeight(new GUIContent(detail), detailWidth)); |
| | | |
| | | var backgroundColor = GUI.backgroundColor; |
| | | if (IsCurrentSearchMatch(i)) |
| | | { |
| | | GUI.backgroundColor = new Color(0.45f, 0.75f, 1f, 1f); |
| | | } |
| | | var detailRect = EditorGUILayout.GetControlRect(false, detailHeight, |
| | | GUILayout.ExpandWidth(true)); |
| | | EditorGUI.SelectableLabel(detailRect, detail, fieldDetailStyle); |
| | | GUI.backgroundColor = backgroundColor; |
| | | DrawDetailSearchHighlights(i, detailRect, detail, fieldDetailStyle); |
| | | |
| | | if (GUILayout.Button("格式化", GUILayout.Width(55f), |
| | | GUILayout.Height(EditorGUIUtility.singleLineHeight + 4f))) |
| | | { |
| | | SetDetailFormatted(i, true); |
| | | } |
| | | |
| | | if (GUILayout.Button("还原", GUILayout.Width(45f), |
| | | GUILayout.Height(EditorGUIUtility.singleLineHeight + 4f))) |
| | | { |
| | | SetDetailFormatted(i, false); |
| | | } |
| | | } |
| | | |
| | | if (Event.current.type == EventType.Repaint |
| | | && detailRowPositions != null |
| | | && i < detailRowPositions.Length) |
| | | { |
| | | detailRowPositions[i] = GUILayoutUtility.GetLastRect().y; |
| | | } |
| | | } |
| | | } |
| | | |
| | | GUILayout.EndScrollView(); |
| | | if (Event.current.type == EventType.Repaint) |
| | | { |
| | | detailScrollViewHeight = GUILayoutUtility.GetLastRect().height; |
| | | if (pendingScrollSearchMatch >= 0) |
| | | { |
| | | Repaint(); |
| | | } |
| | | } |
| | | |
| | | using (new GUILayout.HorizontalScope()) |
| | | { |
| | | detailSearchText = EditorGUILayout.TextField(detailSearchText); |
| | | |
| | | if (GUILayout.Button("搜索", GUILayout.Width(50f))) |
| | | { |
| | | SearchDetails(); |
| | | } |
| | | |
| | | if (GUILayout.Button("清除", GUILayout.Width(50f))) |
| | | { |
| | | ClearDetailSearch(); |
| | | } |
| | | |
| | | using (new EditorGUI.DisabledScope(detailSearchMatches.Count == 0)) |
| | | { |
| | | if (GUILayout.Button("上一条", GUILayout.Width(55f))) |
| | | { |
| | | MoveToDetailSearchMatch(-1); |
| | | } |
| | | |
| | | if (GUILayout.Button("下一条", GUILayout.Width(55f))) |
| | | { |
| | | MoveToDetailSearchMatch(1); |
| | | } |
| | | } |
| | | |
| | | GUILayout.Label(GetDetailSearchProgress(), GUILayout.Width(55f)); |
| | | } |
| | | |
| | | using (new GUILayout.HorizontalScope()) |
| | | { |
| | | if (GUILayout.Button("格式化全部")) |
| | | { |
| | | SetAllDetailsFormatted(true); |
| | | } |
| | | |
| | | if (GUILayout.Button("还原全部")) |
| | | { |
| | | SetAllDetailsFormatted(false); |
| | | } |
| | | } |
| | | |
| | | using (new GUILayout.HorizontalScope()) |
| | | { |
| | | if (GUILayout.Button("导出 TXT")) |
| | | { |
| | | ExportToTxt(); |
| | | } |
| | | |
| | | if (GUILayout.Button("复制")) |
| | | { |
| | | CopyToClipboard(); |
| | | } |
| | | } |
| | | |
| | | EditorGUILayout.EndVertical(); |
| | | } |
| | | |
| | | private static void ExportToTxt() |
| | | { |
| | | var fileName = GetExportFileName() + ".txt"; |
| | | var path = Path.Combine(Application.dataPath, fileName); |
| | | File.WriteAllText(path, BuildTextContent(), new UTF8Encoding(true)); |
| | | } |
| | | |
| | | public static void WriteAllPackageDetails() |
| | | { |
| | | #if !UNITY_WEBGL |
| | | var packages = NetPkgCtl.GetPackages(); |
| | | if (packages == null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | var count = 0; |
| | | var lines = new List<string>(); |
| | | for (int i = packages.Count - 1; i >= 0; i--) |
| | | { |
| | | if (count > 20000) |
| | | { |
| | | break; |
| | | } |
| | | |
| | | var package = packages[i]; |
| | | var line = StringUtility.Concat( |
| | | package.type == NetPackagetType.Client ? "【发送】\t" : "【接收】\t", |
| | | package.time, ":", package.name, "\r\n"); |
| | | var details = BuildPackageDetails(package); |
| | | if (details != null) |
| | | { |
| | | for (int j = 0; j < details.Count; j++) |
| | | { |
| | | line = StringUtility.Concat(line, "\t\t\t", details[j], "\r\n"); |
| | | } |
| | | } |
| | | |
| | | lines.Add(line); |
| | | count++; |
| | | } |
| | | |
| | | var path = Application.dataPath + "/PackageLogs_details_" |
| | | + DateTime.Now.ToString("HH_mm_ss") + ".txt"; |
| | | File.WriteAllLines(path, lines.ToArray()); |
| | | #endif |
| | | } |
| | | |
| | | private static void CopyToClipboard() |
| | | { |
| | | EditorGUIUtility.systemCopyBuffer = BuildTextContent(); |
| | | } |
| | | |
| | | private static string BuildTextContent() |
| | | { |
| | | var content = new StringBuilder(); |
| | | content.Append("名称:").AppendLine(string.IsNullOrEmpty(packageName) ? string.Empty : packageName); |
| | | content.Append("时间:").AppendLine(string.IsNullOrEmpty(packageTime) ? string.Empty : packageTime); |
| | | content.AppendLine("字节流:"); |
| | | content.AppendLine(string.IsNullOrEmpty(packageBytes) ? string.Empty : packageBytes); |
| | | content.AppendLine("字段详情:"); |
| | | |
| | | if (packageDetails != null) |
| | | { |
| | | for (int i = 0; i < packageDetails.Count; i++) |
| | | { |
| | | content.AppendLine(GetDisplayDetail(i)); |
| | | } |
| | | } |
| | | |
| | | return content.ToString(); |
| | | } |
| | | |
| | | private static string GetExportFileName() |
| | | { |
| | | var safePackageName = string.IsNullOrEmpty(packageName) ? "PackageDetails" : packageName; |
| | | var invalidFileNameChars = Path.GetInvalidFileNameChars(); |
| | | for (int i = 0; i < invalidFileNameChars.Length; i++) |
| | | { |
| | | safePackageName = safePackageName.Replace(invalidFileNameChars[i], '_'); |
| | | } |
| | | |
| | | var safePackageTime = string.IsNullOrEmpty(packageTime) ? string.Empty : "_" + packageTime.Replace(':', '_'); |
| | | return safePackageName + safePackageTime; |
| | | } |
| | | |
| | | private static void ResetDetailFormatState() |
| | | { |
| | | if (packageDetails == null) |
| | | { |
| | | formattedPackageDetails = null; |
| | | detailFormattedStates = null; |
| | | return; |
| | | } |
| | | |
| | | formattedPackageDetails = new string[packageDetails.Count]; |
| | | detailFormattedStates = new bool[packageDetails.Count]; |
| | | for (int i = 0; i < packageDetails.Count; i++) |
| | | { |
| | | formattedPackageDetails[i] = FormatDetail(packageDetails[i]); |
| | | } |
| | | } |
| | | |
| | | private static void ResetDetailSearchState() |
| | | { |
| | | detailSearchText = string.Empty; |
| | | activeDetailSearchText = string.Empty; |
| | | detailSearchMatches.Clear(); |
| | | currentDetailSearchMatch = -1; |
| | | pendingScrollSearchMatch = -1; |
| | | detailRowPositions = packageDetails == null ? null : new float[packageDetails.Count]; |
| | | detailSearchMatchPositions = null; |
| | | detailScrollViewHeight = 0f; |
| | | } |
| | | |
| | | private static void ClearDetailSearch() |
| | | { |
| | | ResetDetailSearchState(); |
| | | if (window != null) |
| | | { |
| | | window.scrollPosition = Vector2.zero; |
| | | } |
| | | RequestDetailDisplayRefresh(); |
| | | } |
| | | |
| | | private void ApplyPendingSearchScroll() |
| | | { |
| | | if (pendingScrollSearchMatch < 0 |
| | | || pendingScrollSearchMatch >= detailSearchMatches.Count) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | var match = detailSearchMatches[pendingScrollSearchMatch]; |
| | | var targetY = -1f; |
| | | if (detailSearchMatchPositions != null |
| | | && pendingScrollSearchMatch < detailSearchMatchPositions.Length) |
| | | { |
| | | targetY = detailSearchMatchPositions[pendingScrollSearchMatch]; |
| | | } |
| | | |
| | | if (targetY >= 0f) |
| | | { |
| | | scrollPosition.y = Mathf.Max(0f, targetY - detailScrollViewHeight * 0.45f); |
| | | pendingScrollSearchMatch = -1; |
| | | return; |
| | | } |
| | | |
| | | if (detailRowPositions != null |
| | | && match.detailIndex >= 0 |
| | | && match.detailIndex < detailRowPositions.Length) |
| | | { |
| | | scrollPosition.y = Mathf.Max(0f, detailRowPositions[match.detailIndex] - 4f); |
| | | } |
| | | } |
| | | |
| | | private static bool IsCurrentSearchMatch(int detailIndex) |
| | | { |
| | | return currentDetailSearchMatch >= 0 |
| | | && currentDetailSearchMatch < detailSearchMatches.Count |
| | | && detailSearchMatches[currentDetailSearchMatch].detailIndex == detailIndex; |
| | | } |
| | | |
| | | private static void DrawDetailSearchHighlights(int detailIndex, Rect detailRect, string detail, |
| | | GUIStyle style) |
| | | { |
| | | if (Event.current.type != EventType.Repaint |
| | | || string.IsNullOrEmpty(detail) |
| | | || string.IsNullOrEmpty(activeDetailSearchText)) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | var content = new GUIContent(detail); |
| | | var searchLength = activeDetailSearchText.Length; |
| | | for (int matchListIndex = 0; matchListIndex < detailSearchMatches.Count; matchListIndex++) |
| | | { |
| | | var match = detailSearchMatches[matchListIndex]; |
| | | if (match.detailIndex != detailIndex |
| | | || match.characterIndex < 0 |
| | | || match.characterIndex + searchLength > detail.Length) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | if (detailSearchMatchPositions != null |
| | | && matchListIndex < detailSearchMatchPositions.Length) |
| | | { |
| | | detailSearchMatchPositions[matchListIndex] = |
| | | style.GetCursorPixelPosition(detailRect, content, match.characterIndex).y; |
| | | } |
| | | |
| | | var highlightColor = matchListIndex == currentDetailSearchMatch |
| | | ? new Color(1f, 0.55f, 0.02f, 0.68f) |
| | | : new Color(1f, 0.72f, 0.05f, 0.42f); |
| | | DrawDetailSearchHighlightRange(detailRect, content, style, match.characterIndex, |
| | | match.characterIndex + searchLength, highlightColor); |
| | | } |
| | | } |
| | | |
| | | private static void DrawDetailSearchHighlightRange(Rect detailRect, GUIContent content, |
| | | GUIStyle style, int startIndex, int endIndex, Color highlightColor) |
| | | { |
| | | for (int cursorIndex = startIndex; cursorIndex < endIndex; cursorIndex++) |
| | | { |
| | | var startCursor = style.GetCursorPixelPosition(detailRect, content, cursorIndex); |
| | | var endCursor = style.GetCursorPixelPosition(detailRect, content, cursorIndex + 1); |
| | | var endX = endCursor.x; |
| | | if (Mathf.Abs(endCursor.y - startCursor.y) > 0.1f || endX <= startCursor.x) |
| | | { |
| | | var characterWidth = style.CalcSize( |
| | | new GUIContent(content.text[cursorIndex].ToString())).x; |
| | | endX = Mathf.Min(detailRect.xMax - style.padding.right, |
| | | startCursor.x + characterWidth); |
| | | } |
| | | |
| | | DrawDetailSearchHighlightRect(startCursor.x, startCursor.y, endX, style, |
| | | highlightColor); |
| | | } |
| | | } |
| | | |
| | | private static void DrawDetailSearchHighlightRect(float startX, float y, float endX, |
| | | GUIStyle style, Color highlightColor) |
| | | { |
| | | var width = endX - startX; |
| | | if (width <= 0f) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | var lineHeight = Mathf.Max(style.lineHeight, EditorGUIUtility.singleLineHeight); |
| | | var highlightRect = new Rect(startX, y + 1f, width, Mathf.Max(1f, lineHeight - 2f)); |
| | | EditorGUI.DrawRect(highlightRect, highlightColor); |
| | | } |
| | | |
| | | private static void SearchDetails() |
| | | { |
| | | RebuildDetailSearchMatches(); |
| | | if (detailSearchMatches.Count == 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | currentDetailSearchMatch = 0; |
| | | JumpToCurrentDetailSearchMatch(); |
| | | } |
| | | |
| | | private static void MoveToDetailSearchMatch(int direction) |
| | | { |
| | | if (!string.Equals(activeDetailSearchText, detailSearchText, StringComparison.Ordinal)) |
| | | { |
| | | RebuildDetailSearchMatches(); |
| | | if (detailSearchMatches.Count == 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | currentDetailSearchMatch = direction < 0 ? detailSearchMatches.Count - 1 : 0; |
| | | JumpToCurrentDetailSearchMatch(); |
| | | return; |
| | | } |
| | | |
| | | if (detailSearchMatches.Count == 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | currentDetailSearchMatch += direction; |
| | | if (currentDetailSearchMatch < 0) |
| | | { |
| | | currentDetailSearchMatch = detailSearchMatches.Count - 1; |
| | | } |
| | | else if (currentDetailSearchMatch >= detailSearchMatches.Count) |
| | | { |
| | | currentDetailSearchMatch = 0; |
| | | } |
| | | |
| | | JumpToCurrentDetailSearchMatch(); |
| | | } |
| | | |
| | | private static void RebuildDetailSearchMatches() |
| | | { |
| | | activeDetailSearchText = detailSearchText ?? string.Empty; |
| | | detailSearchMatches.Clear(); |
| | | currentDetailSearchMatch = -1; |
| | | pendingScrollSearchMatch = -1; |
| | | detailSearchMatchPositions = null; |
| | | |
| | | if (packageDetails == null || string.IsNullOrWhiteSpace(activeDetailSearchText)) |
| | | { |
| | | RequestDetailDisplayRefresh(); |
| | | return; |
| | | } |
| | | |
| | | for (int i = 0; i < packageDetails.Count; i++) |
| | | { |
| | | var detail = GetDisplayDetail(i); |
| | | if (string.IsNullOrEmpty(detail)) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | var searchLength = activeDetailSearchText.Length; |
| | | for (int searchStart = 0; searchStart <= detail.Length - searchLength;) |
| | | { |
| | | var matchIndex = detail.IndexOf(activeDetailSearchText, searchStart, |
| | | StringComparison.OrdinalIgnoreCase); |
| | | if (matchIndex < 0) |
| | | { |
| | | break; |
| | | } |
| | | |
| | | detailSearchMatches.Add(new DetailSearchMatch(i, matchIndex)); |
| | | searchStart = matchIndex + searchLength; |
| | | } |
| | | } |
| | | |
| | | detailSearchMatchPositions = new float[detailSearchMatches.Count]; |
| | | for (int i = 0; i < detailSearchMatchPositions.Length; i++) |
| | | { |
| | | detailSearchMatchPositions[i] = -1f; |
| | | } |
| | | |
| | | RequestDetailDisplayRefresh(); |
| | | } |
| | | |
| | | private static void JumpToCurrentDetailSearchMatch() |
| | | { |
| | | if (currentDetailSearchMatch < 0 || currentDetailSearchMatch >= detailSearchMatches.Count) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | pendingScrollSearchMatch = currentDetailSearchMatch; |
| | | RequestDetailDisplayRefresh(); |
| | | } |
| | | |
| | | private static string GetDetailSearchProgress() |
| | | { |
| | | if (detailSearchMatches.Count == 0 || currentDetailSearchMatch < 0) |
| | | { |
| | | return "0/0"; |
| | | } |
| | | |
| | | return (currentDetailSearchMatch + 1) + "/" + detailSearchMatches.Count; |
| | | } |
| | | |
| | | private static string GetDisplayDetail(int index) |
| | | { |
| | | if (packageDetails == null || index < 0 || index >= packageDetails.Count) |
| | | { |
| | | return string.Empty; |
| | | } |
| | | |
| | | if (detailFormattedStates != null |
| | | && formattedPackageDetails != null |
| | | && index < detailFormattedStates.Length |
| | | && index < formattedPackageDetails.Length |
| | | && detailFormattedStates[index]) |
| | | { |
| | | return formattedPackageDetails[index]; |
| | | } |
| | | |
| | | return packageDetails[index] ?? string.Empty; |
| | | } |
| | | |
| | | private static void SetDetailFormatted(int index, bool formatted) |
| | | { |
| | | var states = detailFormattedStates; |
| | | if (states == null || index < 0 || index >= states.Length) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | EditorApplication.delayCall += () => |
| | | { |
| | | if (detailFormattedStates != states || index < 0 || index >= states.Length) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | states[index] = formatted; |
| | | RefreshDetailSearchAfterFormatChange(); |
| | | }; |
| | | } |
| | | |
| | | private static void SetAllDetailsFormatted(bool formatted) |
| | | { |
| | | var states = detailFormattedStates; |
| | | if (states == null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | EditorApplication.delayCall += () => |
| | | { |
| | | if (detailFormattedStates != states) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | for (int i = 0; i < states.Length; i++) |
| | | { |
| | | states[i] = formatted; |
| | | } |
| | | RefreshDetailSearchAfterFormatChange(); |
| | | }; |
| | | } |
| | | |
| | | private static void RefreshDetailSearchAfterFormatChange() |
| | | { |
| | | if (detailSearchMatches.Count == 0 || string.IsNullOrEmpty(activeDetailSearchText)) |
| | | { |
| | | RequestDetailDisplayRefresh(); |
| | | return; |
| | | } |
| | | |
| | | var previousMatch = currentDetailSearchMatch; |
| | | RebuildDetailSearchMatches(); |
| | | if (detailSearchMatches.Count == 0) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | currentDetailSearchMatch = Mathf.Clamp(previousMatch, 0, detailSearchMatches.Count - 1); |
| | | JumpToCurrentDetailSearchMatch(); |
| | | } |
| | | |
| | | private static void RequestDetailDisplayRefresh() |
| | | { |
| | | clearDetailSelectionOnNextGUI = true; |
| | | if (window != null) |
| | | { |
| | | window.Repaint(); |
| | | } |
| | | } |
| | | |
| | | private static void ClearDetailSelectionIfNeeded() |
| | | { |
| | | if (!clearDetailSelectionOnNextGUI) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | GUI.FocusControl(null); |
| | | GUIUtility.keyboardControl = 0; |
| | | GUIUtility.hotControl = 0; |
| | | EditorGUIUtility.editingTextField = false; |
| | | clearDetailSelectionOnNextGUI = false; |
| | | } |
| | | |
| | | private static string FormatDetail(string detail) |
| | | { |
| | | if (string.IsNullOrEmpty(detail)) |
| | | { |
| | | return string.Empty; |
| | | } |
| | | |
| | | var valueStart = detail.IndexOf("-->", StringComparison.Ordinal); |
| | | if (valueStart < 0) |
| | | { |
| | | return detail; |
| | | } |
| | | valueStart += 3; |
| | | |
| | | var hasContainer = false; |
| | | for (int i = valueStart; i < detail.Length; i++) |
| | | { |
| | | if (detail[i] == '{' || detail[i] == '[') |
| | | { |
| | | hasContainer = true; |
| | | break; |
| | | } |
| | | } |
| | | |
| | | if (!hasContainer) |
| | | { |
| | | return detail; |
| | | } |
| | | |
| | | var content = new StringBuilder(detail.Length * 2); |
| | | content.Append(detail, 0, valueStart); |
| | | |
| | | var indent = 0; |
| | | var inQuotes = false; |
| | | var escaped = false; |
| | | var skipWhitespace = false; |
| | | for (int i = valueStart; i < detail.Length; i++) |
| | | { |
| | | var character = detail[i]; |
| | | if (inQuotes) |
| | | { |
| | | content.Append(character); |
| | | if (escaped) |
| | | { |
| | | escaped = false; |
| | | } |
| | | else if (character == '\\') |
| | | { |
| | | escaped = true; |
| | | } |
| | | else if (character == '"') |
| | | { |
| | | inQuotes = false; |
| | | } |
| | | continue; |
| | | } |
| | | |
| | | if (skipWhitespace && char.IsWhiteSpace(character)) |
| | | { |
| | | continue; |
| | | } |
| | | skipWhitespace = false; |
| | | |
| | | if (character == '"') |
| | | { |
| | | inQuotes = true; |
| | | content.Append(character); |
| | | continue; |
| | | } |
| | | |
| | | if (character == '{' || character == '[') |
| | | { |
| | | content.Append(character); |
| | | indent++; |
| | | if (!IsEmptyContainer(detail, i, character == '{' ? '}' : ']')) |
| | | { |
| | | AppendNewLineAndIndent(content, indent); |
| | | } |
| | | skipWhitespace = true; |
| | | continue; |
| | | } |
| | | |
| | | if (character == '}' || character == ']') |
| | | { |
| | | indent = Mathf.Max(0, indent - 1); |
| | | var openingCharacter = character == '}' ? '{' : '['; |
| | | if (content.Length == 0 || content[content.Length - 1] != openingCharacter) |
| | | { |
| | | AppendNewLineAndIndent(content, indent); |
| | | } |
| | | content.Append(character); |
| | | continue; |
| | | } |
| | | |
| | | if (character == ';' || character == ',') |
| | | { |
| | | content.Append(character); |
| | | AppendNewLineAndIndent(content, indent); |
| | | skipWhitespace = true; |
| | | continue; |
| | | } |
| | | |
| | | content.Append(character); |
| | | } |
| | | |
| | | return content.ToString(); |
| | | } |
| | | |
| | | private static bool IsEmptyContainer(string content, int openingIndex, char closingCharacter) |
| | | { |
| | | for (int i = openingIndex + 1; i < content.Length; i++) |
| | | { |
| | | if (char.IsWhiteSpace(content[i])) |
| | | { |
| | | continue; |
| | | } |
| | | return content[i] == closingCharacter; |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | private static void AppendNewLineAndIndent(StringBuilder content, int indent) |
| | | { |
| | | content.AppendLine(); |
| | | content.Append(' ', indent * 4); |
| | | } |
| | | |
| | | public static List<string> BuildPackageDetails(NetPackage package) |
| | | { |
| | | if (package.type != NetPackagetType.Server |
| | | || string.IsNullOrEmpty(package.name) |
| | | || string.IsNullOrEmpty(package.content)) |
| | | { |
| | | return package.fieldDetails; |
| | | } |
| | | |
| | | var packageType = typeof(GameNetPackBasic).Assembly.GetType(package.name); |
| | | if (packageType == null || !typeof(GameNetPackBasic).IsAssignableFrom(packageType)) |
| | | { |
| | | return package.fieldDetails; |
| | | } |
| | | |
| | | var readMethod = packageType.GetMethod("ReadFromBytes", BindingFlags.Instance | BindingFlags.Public); |
| | | if (readMethod == null || readMethod.DeclaringType == typeof(GameNetPackBasic)) |
| | | { |
| | | return package.fieldDetails; |
| | | } |
| | | |
| | | try |
| | | { |
| | | var packageObject = FormatterServices.GetUninitializedObject(packageType) as GameNetPackBasic; |
| | | var readIndexField = typeof(GameNetPackBasic).GetField("_readIndex", |
| | | BindingFlags.Instance | BindingFlags.NonPublic); |
| | | readIndexField.SetValue(packageObject, 2); |
| | | packageObject.ReadFromBytes(ParsePackageBytes(package.content)); |
| | | return BuildExpandedFieldDetails(packageObject, package.fieldDetails); |
| | | } |
| | | catch (Exception ex) |
| | | { |
| | | Debug.LogError(string.Format("[PackageDetailsWindow] 完整展开封包 {0} 失败:{1}", |
| | | package.name, ex)); |
| | | return package.fieldDetails; |
| | | } |
| | | } |
| | | |
| | | private static byte[] ParsePackageBytes(string content) |
| | | { |
| | | var bytesText = content.Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries); |
| | | var bytes = new byte[bytesText.Length]; |
| | | for (int i = 0; i < bytesText.Length; i++) |
| | | { |
| | | bytes[i] = Convert.ToByte(bytesText[i], 16); |
| | | } |
| | | return bytes; |
| | | } |
| | | |
| | | private static List<string> BuildExpandedFieldDetails(object packageObject, List<string> recordedDetails) |
| | | { |
| | | var fields = packageObject.GetType().GetFields(); |
| | | var contents = new List<string>(); |
| | | for (int i = 0; i < fields.Length; i++) |
| | | { |
| | | var fieldContent = fields[i].GetValue(packageObject); |
| | | var array = fieldContent as Array; |
| | | if (array != null) |
| | | { |
| | | contents.AddRange(FormatArray(fields[i].Name, array)); |
| | | } |
| | | else |
| | | { |
| | | var recordedDetail = FindRecordedFieldDetail(recordedDetails, fields[i].Name); |
| | | contents.Add(recordedDetail ?? string.Format("\"{0}\":{1}", fields[i].Name, fieldContent)); |
| | | } |
| | | } |
| | | return contents; |
| | | } |
| | | |
| | | private static string FindRecordedFieldDetail(List<string> recordedDetails, string fieldName) |
| | | { |
| | | if (recordedDetails == null) |
| | | { |
| | | return null; |
| | | } |
| | | |
| | | var prefix = string.Format("\"{0}\":", fieldName); |
| | | for (int i = 0; i < recordedDetails.Count; i++) |
| | | { |
| | | if (recordedDetails[i].StartsWith(prefix, StringComparison.Ordinal)) |
| | | { |
| | | return recordedDetails[i]; |
| | | } |
| | | } |
| | | return null; |
| | | } |
| | | |
| | | private static string[] FormatArray(string fieldName, Array array) |
| | | { |
| | | var contents = new string[array.Length]; |
| | | for (int i = 0; i < array.Length; i++) |
| | | { |
| | | var value = array.GetValue(i); |
| | | if (value == null) |
| | | { |
| | | contents[i] = string.Format("\"{0}\"[{1}]-->null", fieldName, i); |
| | | continue; |
| | | } |
| | | |
| | | var type = value.GetType(); |
| | | contents[i] = IsSimpleType(type) |
| | | ? string.Format("\"{0}\"[{1}]-->({2}){3}", fieldName, i, type.Name, FormatSimpleValue(value)) |
| | | : string.Format("\"{0}\"[{1}]-->{2}", fieldName, i, FormatValue(value)); |
| | | } |
| | | return contents; |
| | | } |
| | | |
| | | private static string FormatValue(object value) |
| | | { |
| | | var nodes = new List<PrintNode>(); |
| | | nodes.Add(new PrintNode { value = value }); |
| | | |
| | | for (int nodeIndex = 0; nodeIndex < nodes.Count; nodeIndex++) |
| | | { |
| | | var node = nodes[nodeIndex]; |
| | | if (node.value == null) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | var type = node.value.GetType(); |
| | | if (IsSimpleType(type)) |
| | | { |
| | | continue; |
| | | } |
| | | |
| | | var array = node.value as Array; |
| | | if (array != null) |
| | | { |
| | | node.isArray = true; |
| | | node.childIndexes = new int[array.Length]; |
| | | for (int i = 0; i < array.Length; i++) |
| | | { |
| | | node.childIndexes[i] = nodes.Count; |
| | | nodes.Add(new PrintNode { value = array.GetValue(i) }); |
| | | } |
| | | continue; |
| | | } |
| | | |
| | | var fields = type.GetFields(); |
| | | node.childNames = new string[fields.Length]; |
| | | node.childIndexes = new int[fields.Length]; |
| | | for (int i = 0; i < fields.Length; i++) |
| | | { |
| | | node.childNames[i] = fields[i].Name; |
| | | node.childIndexes[i] = nodes.Count; |
| | | nodes.Add(new PrintNode { value = fields[i].GetValue(node.value) }); |
| | | } |
| | | } |
| | | |
| | | for (int nodeIndex = nodes.Count - 1; nodeIndex >= 0; nodeIndex--) |
| | | { |
| | | var node = nodes[nodeIndex]; |
| | | if (node.value == null) |
| | | { |
| | | node.content = "null"; |
| | | continue; |
| | | } |
| | | |
| | | var type = node.value.GetType(); |
| | | if (IsSimpleType(type)) |
| | | { |
| | | node.content = FormatSimpleValue(node.value); |
| | | continue; |
| | | } |
| | | |
| | | var content = new StringBuilder(); |
| | | if (node.isArray) |
| | | { |
| | | content.Append("["); |
| | | for (int i = 0; i < node.childIndexes.Length; i++) |
| | | { |
| | | if (i > 0) |
| | | { |
| | | content.Append(", "); |
| | | } |
| | | content.Append(nodes[node.childIndexes[i]].content); |
| | | } |
| | | content.Append("]"); |
| | | } |
| | | else |
| | | { |
| | | content.Append("{"); |
| | | for (int i = 0; i < node.childIndexes.Length; i++) |
| | | { |
| | | if (i > 0) |
| | | { |
| | | content.Append("; "); |
| | | } |
| | | content.Append("\"").Append(node.childNames[i]).Append("\":") |
| | | .Append(nodes[node.childIndexes[i]].content); |
| | | } |
| | | content.Append("}"); |
| | | } |
| | | node.content = content.ToString(); |
| | | } |
| | | |
| | | return nodes[0].content; |
| | | } |
| | | |
| | | private static bool IsSimpleType(Type type) |
| | | { |
| | | return type.IsPrimitive |
| | | || type.IsEnum |
| | | || type == typeof(string) |
| | | || type == typeof(decimal) |
| | | || type == typeof(DateTime); |
| | | } |
| | | |
| | | private static string FormatSimpleValue(object value) |
| | | { |
| | | return value == null ? "null" : value.ToString(); |
| | | } |
| | | |
| | | [UnityEditor.MenuItem("程序/封包详情 &2")] |
| | | public static void CreatePackageDetailsWindow() |
| | | { |