460 lines
14 KiB
C#
460 lines
14 KiB
C#
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using System.IO;
|
|
using System.Text.RegularExpressions;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Runtime.InteropServices;
|
|
|
|
public class readDeviceInfo : MonoBehaviour
|
|
{
|
|
private const string LabelColorHex = "#2457c6";
|
|
private const string ValueColorHex = "#6b8acd";
|
|
private const float ColumnPaddingPixels = 32f;
|
|
private const int MinimumGapSpaces = 2;
|
|
|
|
public Button reread_button;
|
|
public Button export_button;
|
|
public Text deviceInfo_text;
|
|
|
|
void Start()
|
|
{
|
|
if (reread_button != null)
|
|
{
|
|
reread_button.onClick.AddListener(read_deviceInfo);
|
|
}
|
|
|
|
if (export_button != null)
|
|
{
|
|
export_button.onClick.AddListener(ExportToFile);
|
|
}
|
|
|
|
if (deviceInfo_text != null)
|
|
{
|
|
ConfigureDeviceInfoText();
|
|
}
|
|
|
|
read_deviceInfo();
|
|
}
|
|
|
|
void OnDestroy()
|
|
{
|
|
if (reread_button != null)
|
|
reread_button.onClick.RemoveListener(read_deviceInfo);
|
|
if (export_button != null)
|
|
export_button.onClick.RemoveListener(ExportToFile);
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
|
|
}
|
|
|
|
private void read_deviceInfo()
|
|
{
|
|
ConfigureDeviceInfoText();
|
|
var entries = new System.Collections.Generic.List<KeyValuePair<string, string>>();
|
|
|
|
void AddEntry(string title, object value)
|
|
{
|
|
string str;
|
|
if (value == null) str = "";
|
|
else if (value is bool b) str = b ? "是" : "否";
|
|
else str = value.ToString();
|
|
|
|
entries.Add(new KeyValuePair<string, string>(title ?? string.Empty, str ?? string.Empty));
|
|
}
|
|
|
|
AddEntry("平台", Application.platform.ToString());
|
|
AddEntry("数据目录", Application.dataPath);
|
|
AddEntry("persistent 数据路径", Application.persistentDataPath);
|
|
|
|
AddEntry("操作系统", SystemInfo.operatingSystem);
|
|
AddEntry("设备名称", SystemInfo.deviceName);
|
|
AddEntry("设备型号", SystemInfo.deviceModel);
|
|
AddEntry("设备唯一ID", SystemInfo.deviceUniqueIdentifier);
|
|
|
|
AddEntry("CPU", SystemInfo.processorType);
|
|
AddEntry("CPU核心数", SystemInfo.processorCount);
|
|
AddEntry("系统内存 (MB)", SystemInfo.systemMemorySize);
|
|
|
|
AddEntry("图形设备名称", SystemInfo.graphicsDeviceName);
|
|
AddEntry("图形设备供应商", SystemInfo.graphicsDeviceVendor);
|
|
AddEntry("DirectX", SystemInfo.graphicsDeviceType.ToString());
|
|
AddEntry("显存 (MB)", SystemInfo.graphicsMemorySize);
|
|
|
|
AddEntry("屏幕分辨率", $"{Screen.currentResolution.width} x {Screen.currentResolution.height} @ {Screen.currentResolution.refreshRateRatio.value:0.##}Hz");
|
|
AddEntry("屏幕DPI", Screen.dpi);
|
|
AddEntry("显示模式", Screen.fullScreenMode.ToString());
|
|
|
|
try
|
|
{
|
|
AddEntry("Battery Level", $"{SystemInfo.batteryLevel * 100:F0}%");
|
|
}
|
|
catch
|
|
{
|
|
AddEntry("Battery Status", "Not Supported");
|
|
}
|
|
|
|
AddEntry("主程序版本", "v1.1h02e");
|
|
AddEntry("内置小游戏版本", "v1.301");
|
|
AddEntry("游戏代码版本", "code::testingVersion1062");
|
|
AddEntry("登录渠道", "Steam Client");
|
|
|
|
AddEntry(".NET Framework", "8.0 +");
|
|
AddEntry("Max Texture Size", SystemInfo.maxTextureSize);
|
|
|
|
AddEntry("启用后台运行", Application.runInBackground);
|
|
AddEntry("启用多线程渲染", SystemInfo.graphicsMultiThreaded);
|
|
|
|
string output = BuildTwoColumnOutput(entries);
|
|
|
|
if (deviceInfo_text != null)
|
|
{
|
|
deviceInfo_text.text = output;
|
|
}
|
|
}
|
|
|
|
void ExportToFile()
|
|
{
|
|
if (deviceInfo_text == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string path = ShowSaveFileDialog();
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
string raw = deviceInfo_text.text;
|
|
string cleaned = Regex.Replace(raw, "<.*?>", string.Empty);
|
|
File.WriteAllText(path, cleaned);
|
|
}
|
|
catch (System.Exception)
|
|
{
|
|
}
|
|
}
|
|
|
|
private string BuildTwoColumnOutput(System.Collections.Generic.IReadOnlyList<KeyValuePair<string, string>> entries)
|
|
{
|
|
if (entries == null || entries.Count == 0)
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
float maxLabelWidth = 0f;
|
|
for (int i = 0; i < entries.Count; i++)
|
|
{
|
|
maxLabelWidth = Mathf.Max(maxLabelWidth, MeasurePlainTextWidth(entries[i].Key));
|
|
}
|
|
|
|
float availableWidth = GetAvailableTextWidth();
|
|
float targetLabelWidth = maxLabelWidth + ColumnPaddingPixels;
|
|
if (availableWidth > 0f)
|
|
{
|
|
targetLabelWidth = Mathf.Min(targetLabelWidth, availableWidth * 0.38f);
|
|
}
|
|
|
|
float spaceWidth = Mathf.Max(1f, MeasurePlainTextWidth(" "));
|
|
int continuationIndentSpaces = Mathf.Max(MinimumGapSpaces, Mathf.CeilToInt(targetLabelWidth / spaceWidth) + MinimumGapSpaces);
|
|
string continuationIndent = new string(' ', continuationIndentSpaces);
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
for (int i = 0; i < entries.Count; i++)
|
|
{
|
|
string label = entries[i].Key ?? string.Empty;
|
|
string value = entries[i].Value ?? string.Empty;
|
|
|
|
int gapSpaces = Mathf.Max(
|
|
MinimumGapSpaces,
|
|
Mathf.CeilToInt((targetLabelWidth - MeasurePlainTextWidth(label)) / spaceWidth) + MinimumGapSpaces);
|
|
|
|
string padding = new string(' ', gapSpaces);
|
|
float firstLineValueWidth = availableWidth > 0f
|
|
? Mathf.Max(32f, availableWidth - MeasurePlainTextWidth(label) - MeasurePlainTextWidth(padding))
|
|
: 0f;
|
|
float continuationValueWidth = availableWidth > 0f
|
|
? Mathf.Max(32f, availableWidth - MeasurePlainTextWidth(continuationIndent))
|
|
: 0f;
|
|
|
|
List<string> wrappedLines = WrapValueLines(value, firstLineValueWidth, continuationValueWidth);
|
|
|
|
sb.Append("<color=").Append(LabelColorHex).Append("><b>")
|
|
.Append(EscapeRichText(label))
|
|
.Append("</b></color>")
|
|
.Append(padding)
|
|
.Append("<color=").Append(ValueColorHex).Append(">")
|
|
.Append(EscapeRichText(wrappedLines[0]))
|
|
.Append("</color>");
|
|
|
|
for (int lineIndex = 1; lineIndex < wrappedLines.Count; lineIndex++)
|
|
{
|
|
sb.AppendLine();
|
|
sb.Append(continuationIndent)
|
|
.Append("<color=").Append(ValueColorHex).Append(">")
|
|
.Append(EscapeRichText(wrappedLines[lineIndex]))
|
|
.Append("</color>");
|
|
}
|
|
|
|
if (i < entries.Count - 1)
|
|
{
|
|
sb.AppendLine();
|
|
}
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private void ConfigureDeviceInfoText()
|
|
{
|
|
if (deviceInfo_text == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
deviceInfo_text.supportRichText = true;
|
|
deviceInfo_text.alignment = TextAnchor.UpperLeft;
|
|
deviceInfo_text.horizontalOverflow = HorizontalWrapMode.Wrap;
|
|
deviceInfo_text.verticalOverflow = VerticalWrapMode.Overflow;
|
|
deviceInfo_text.resizeTextForBestFit = false;
|
|
}
|
|
|
|
private float GetAvailableTextWidth()
|
|
{
|
|
if (deviceInfo_text == null)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
RectTransform rectTransform = deviceInfo_text.rectTransform;
|
|
if (rectTransform == null)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
float width = rectTransform.rect.width;
|
|
return width > 0f ? width : 0f;
|
|
}
|
|
|
|
private float MeasurePlainTextWidth(string text)
|
|
{
|
|
if (deviceInfo_text == null || string.IsNullOrEmpty(text))
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
TextGenerationSettings settings = deviceInfo_text.GetGenerationSettings(Vector2.zero);
|
|
settings.richText = false;
|
|
settings.generateOutOfBounds = true;
|
|
|
|
TextGenerator generator = new TextGenerator();
|
|
return generator.GetPreferredWidth(text, settings) / deviceInfo_text.pixelsPerUnit;
|
|
}
|
|
|
|
private List<string> WrapValueLines(string value, float firstLineWidth, float continuationLineWidth)
|
|
{
|
|
string normalized = NormalizeLineBreaks(value);
|
|
if (string.IsNullOrEmpty(normalized))
|
|
{
|
|
return new List<string> { string.Empty };
|
|
}
|
|
|
|
string[] rawLines = normalized.Split('\n');
|
|
List<string> wrapped = new List<string>();
|
|
for (int i = 0; i < rawLines.Length; i++)
|
|
{
|
|
string rawLine = rawLines[i];
|
|
List<string> lineParts = WrapSingleLine(rawLine, wrapped.Count == 0 ? firstLineWidth : continuationLineWidth);
|
|
if (lineParts.Count == 0)
|
|
{
|
|
lineParts.Add(string.Empty);
|
|
}
|
|
|
|
wrapped.Add(lineParts[0]);
|
|
for (int j = 1; j < lineParts.Count; j++)
|
|
{
|
|
wrapped.Add(lineParts[j]);
|
|
}
|
|
}
|
|
|
|
return wrapped.Count > 0 ? wrapped : new List<string> { string.Empty };
|
|
}
|
|
|
|
private List<string> WrapSingleLine(string text, float maxWidth)
|
|
{
|
|
List<string> lines = new List<string>();
|
|
if (string.IsNullOrEmpty(text) || maxWidth <= 0f)
|
|
{
|
|
lines.Add(text ?? string.Empty);
|
|
return lines;
|
|
}
|
|
|
|
string remaining = text;
|
|
while (!string.IsNullOrEmpty(remaining))
|
|
{
|
|
if (MeasurePlainTextWidth(remaining) <= maxWidth)
|
|
{
|
|
lines.Add(remaining);
|
|
break;
|
|
}
|
|
|
|
int splitIndex = FindSplitIndex(remaining, maxWidth);
|
|
if (splitIndex <= 0 || splitIndex >= remaining.Length)
|
|
{
|
|
lines.Add(remaining);
|
|
break;
|
|
}
|
|
|
|
string current = remaining.Substring(0, splitIndex).TrimEnd();
|
|
if (current.Length == 0)
|
|
{
|
|
current = remaining.Substring(0, Mathf.Min(1, remaining.Length));
|
|
splitIndex = current.Length;
|
|
}
|
|
|
|
lines.Add(current);
|
|
remaining = remaining.Substring(splitIndex).TrimStart();
|
|
}
|
|
|
|
return lines;
|
|
}
|
|
|
|
private int FindSplitIndex(string text, float maxWidth)
|
|
{
|
|
int lastBreakableIndex = -1;
|
|
int fallbackIndex = -1;
|
|
|
|
for (int i = 1; i <= text.Length; i++)
|
|
{
|
|
string candidate = text.Substring(0, i);
|
|
if (MeasurePlainTextWidth(candidate) <= maxWidth)
|
|
{
|
|
fallbackIndex = i;
|
|
if (IsBreakableCharacter(text[i - 1]))
|
|
{
|
|
lastBreakableIndex = i;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (lastBreakableIndex > 0)
|
|
{
|
|
return lastBreakableIndex;
|
|
}
|
|
|
|
return fallbackIndex;
|
|
}
|
|
|
|
private static bool IsBreakableCharacter(char c)
|
|
{
|
|
return char.IsWhiteSpace(c) || c == '/' || c == '\\' || c == '_' || c == '-' || c == '.' || c == ':' || c == ')' || c == ']';
|
|
}
|
|
|
|
private static string NormalizeLineBreaks(string input)
|
|
{
|
|
if (string.IsNullOrEmpty(input))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return input.Replace("\r\n", "\n").Replace('\r', '\n');
|
|
}
|
|
|
|
private static string EscapeRichText(string input)
|
|
{
|
|
if (string.IsNullOrEmpty(input))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return input.Replace("&", "&")
|
|
.Replace("<", "<")
|
|
.Replace(">", ">");
|
|
}
|
|
|
|
string ShowSaveFileDialog()
|
|
{
|
|
#if UNITY_EDITOR
|
|
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
|
string path = UnityEditor.EditorUtility.SaveFilePanel("保存设备信息", desktop, "device_info", "txt");
|
|
return string.IsNullOrEmpty(path) ? null : path;
|
|
#elif UNITY_STANDALONE_WIN
|
|
try
|
|
{
|
|
// Use native Windows SaveFile dialog via comdlg32 GetSaveFileName
|
|
OpenFileName ofn = new OpenFileName();
|
|
ofn.structSize = Marshal.SizeOf(ofn);
|
|
ofn.filter = "Text files (*.txt)\0*.txt\0All files (*.*)\0*.*\0";
|
|
ofn.file = new string('\0', 260);
|
|
ofn.maxFile = ofn.file.Length;
|
|
ofn.fileTitle = new string('\0', 260);
|
|
ofn.maxFileTitle = ofn.fileTitle.Length;
|
|
ofn.initialDir = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
|
|
ofn.title = "保存设备信息";
|
|
ofn.defExt = "txt";
|
|
ofn.flags = OpenFileNameFlags.OFN_OVERWRITEPROMPT;
|
|
|
|
if (GetSaveFileName(ofn))
|
|
{
|
|
// Trim any trailing nulls
|
|
return ofn.file.TrimEnd('\0');
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
return null;
|
|
#else
|
|
string defaultPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "device_info.txt");
|
|
return defaultPath;
|
|
#endif
|
|
}
|
|
|
|
#if UNITY_STANDALONE_WIN
|
|
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
|
|
private class OpenFileName
|
|
{
|
|
public int structSize = 0;
|
|
public IntPtr dlgOwner = IntPtr.Zero;
|
|
public IntPtr instance = IntPtr.Zero;
|
|
public string filter = null;
|
|
public string customFilter = null;
|
|
public int maxCustFilter = 0;
|
|
public int filterIndex = 0;
|
|
public string file = null;
|
|
public int maxFile = 0;
|
|
public string fileTitle = null;
|
|
public int maxFileTitle = 0;
|
|
public string initialDir = null;
|
|
public string title = null;
|
|
public int flags = 0;
|
|
public short fileOffset = 0;
|
|
public short fileExtension = 0;
|
|
public string defExt = null;
|
|
public IntPtr custData = IntPtr.Zero;
|
|
public IntPtr hook = IntPtr.Zero;
|
|
public string templateName = null;
|
|
public IntPtr reservedPtr = IntPtr.Zero;
|
|
public int reservedInt = 0;
|
|
public int flagsEx = 0;
|
|
}
|
|
|
|
private static class OpenFileNameFlags
|
|
{
|
|
public const int OFN_OVERWRITEPROMPT = 0x00000002;
|
|
}
|
|
|
|
[DllImport("comdlg32.dll", CharSet = CharSet.Auto, SetLastError = true)]
|
|
private static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
|
|
#endif
|
|
}
|