323 lines
11 KiB
C#
323 lines
11 KiB
C#
|
|
using System.Diagnostics;
|
|||
|
|
using System.IO;
|
|||
|
|
using UnityEditor;
|
|||
|
|
using UnityEngine;
|
|||
|
|
|
|||
|
|
namespace Kill.Editor
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// SHA-1 指纹获取工具
|
|||
|
|
/// </summary>
|
|||
|
|
public class SHA1Helper : EditorWindow
|
|||
|
|
{
|
|||
|
|
private string sha1Result = "";
|
|||
|
|
private Vector2 scrollPosition;
|
|||
|
|
|
|||
|
|
[MenuItem("Tools/SHA-1 指纹获取工具")]
|
|||
|
|
public static void ShowWindow()
|
|||
|
|
{
|
|||
|
|
GetWindow<SHA1Helper>("SHA-1 指纹获取");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private void OnGUI()
|
|||
|
|
{
|
|||
|
|
GUILayout.Label("SHA-1 指纹获取工具", EditorStyles.boldLabel);
|
|||
|
|
GUILayout.Space(10);
|
|||
|
|
|
|||
|
|
// 调试密钥库
|
|||
|
|
GUILayout.Label("调试版本 (Debug)", EditorStyles.boldLabel);
|
|||
|
|
if (GUILayout.Button("获取 Debug SHA-1", GUILayout.Height(30)))
|
|||
|
|
{
|
|||
|
|
GetDebugSHA1();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
GUILayout.Space(10);
|
|||
|
|
|
|||
|
|
// 发布密钥库
|
|||
|
|
GUILayout.Label("发布版本 (Release)", EditorStyles.boldLabel);
|
|||
|
|
EditorGUILayout.HelpBox(
|
|||
|
|
"请在 Player Settings > Publishing Settings 中配置密钥库路径和密码",
|
|||
|
|
MessageType.Info);
|
|||
|
|
|
|||
|
|
if (GUILayout.Button("获取 Release SHA-1", GUILayout.Height(30)))
|
|||
|
|
{
|
|||
|
|
GetReleaseSHA1();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
GUILayout.Space(20);
|
|||
|
|
|
|||
|
|
// 结果显示
|
|||
|
|
GUILayout.Label("结果:", EditorStyles.boldLabel);
|
|||
|
|
scrollPosition = GUILayout.BeginScrollView(scrollPosition, GUILayout.Height(150));
|
|||
|
|
EditorGUILayout.TextArea(sha1Result, GUILayout.ExpandHeight(true));
|
|||
|
|
GUILayout.EndScrollView();
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(sha1Result))
|
|||
|
|
{
|
|||
|
|
if (GUILayout.Button("复制到剪贴板"))
|
|||
|
|
{
|
|||
|
|
GUIUtility.systemCopyBuffer = sha1Result;
|
|||
|
|
UnityEngine.Debug.Log("已复制到剪贴板");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
GUILayout.Space(10);
|
|||
|
|
|
|||
|
|
// 使用说明
|
|||
|
|
EditorGUILayout.HelpBox(
|
|||
|
|
"使用说明:\n" +
|
|||
|
|
"1. 点击对应按钮获取 SHA-1\n" +
|
|||
|
|
"2. 复制结果\n" +
|
|||
|
|
"3. 在 Firebase 控制台 → 项目设置 → 应用 → SHA-1 中添加",
|
|||
|
|
MessageType.Info);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取 Debug SHA-1
|
|||
|
|
/// </summary>
|
|||
|
|
private void GetDebugSHA1()
|
|||
|
|
{
|
|||
|
|
string keytoolPath = GetKeytoolPath();
|
|||
|
|
if (string.IsNullOrEmpty(keytoolPath))
|
|||
|
|
{
|
|||
|
|
sha1Result = "错误: 找不到 keytool\n请确保已安装 JDK 并配置环境变量";
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
string keystorePath = Path.Combine(
|
|||
|
|
System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile),
|
|||
|
|
".android", "debug.keystore");
|
|||
|
|
|
|||
|
|
if (!File.Exists(keystorePath))
|
|||
|
|
{
|
|||
|
|
sha1Result = $"错误: 找不到 Debug 密钥库\n路径: {keystorePath}\n\n请先构建一次 Android 项目自动生成";
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RunKeytool(keytoolPath, keystorePath, "androiddebugkey", "android");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取 Release SHA-1
|
|||
|
|
/// </summary>
|
|||
|
|
private void GetReleaseSHA1()
|
|||
|
|
{
|
|||
|
|
string keytoolPath = GetKeytoolPath();
|
|||
|
|
if (string.IsNullOrEmpty(keytoolPath))
|
|||
|
|
{
|
|||
|
|
sha1Result = "错误: 找不到 keytool\n请确保已安装 JDK 并配置环境变量";
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 从 Player Settings 获取密钥库配置
|
|||
|
|
string keystorePath = PlayerSettings.Android.keystoreName;
|
|||
|
|
string keystorePass = PlayerSettings.Android.keystorePass;
|
|||
|
|
string keyaliasName = PlayerSettings.Android.keyaliasName;
|
|||
|
|
string keyaliasPass = PlayerSettings.Android.keyaliasPass;
|
|||
|
|
|
|||
|
|
if (string.IsNullOrEmpty(keystorePath))
|
|||
|
|
{
|
|||
|
|
sha1Result = "错误: 未配置发布密钥库\n\n请在 Player Settings > Publishing Settings 中配置";
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!File.Exists(keystorePath))
|
|||
|
|
{
|
|||
|
|
sha1Result = $"错误: 找不到密钥库文件\n路径: {keystorePath}";
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
RunKeytool(keytoolPath, keystorePath, keyaliasName, keyaliasPass);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 运行 keytool 命令
|
|||
|
|
/// </summary>
|
|||
|
|
private void RunKeytool(string keytoolPath, string keystorePath, string alias, string password)
|
|||
|
|
{
|
|||
|
|
ProcessStartInfo psi = new ProcessStartInfo
|
|||
|
|
{
|
|||
|
|
FileName = keytoolPath,
|
|||
|
|
Arguments = $"-list -v -alias {alias} -keystore \"{keystorePath}\" -storepass {password}",
|
|||
|
|
RedirectStandardOutput = true,
|
|||
|
|
RedirectStandardError = true,
|
|||
|
|
UseShellExecute = false,
|
|||
|
|
CreateNoWindow = true
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
using (Process process = Process.Start(psi))
|
|||
|
|
{
|
|||
|
|
// 读取字节流并尝试多种编码
|
|||
|
|
byte[] outputBytes = ReadAllBytes(process.StandardOutput.BaseStream);
|
|||
|
|
byte[] errorBytes = ReadAllBytes(process.StandardError.BaseStream);
|
|||
|
|
process.WaitForExit();
|
|||
|
|
|
|||
|
|
// 尝试多种编码解码
|
|||
|
|
string output = TryDecode(outputBytes);
|
|||
|
|
string error = TryDecode(errorBytes);
|
|||
|
|
|
|||
|
|
if (process.ExitCode == 0)
|
|||
|
|
{
|
|||
|
|
sha1Result = ExtractSHA1(output);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
sha1Result = $"错误:\n{error}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch (System.Exception ex)
|
|||
|
|
{
|
|||
|
|
sha1Result = $"执行失败: {ex.Message}";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 读取所有字节
|
|||
|
|
/// </summary>
|
|||
|
|
private byte[] ReadAllBytes(System.IO.Stream stream)
|
|||
|
|
{
|
|||
|
|
using (var ms = new System.IO.MemoryStream())
|
|||
|
|
{
|
|||
|
|
stream.CopyTo(ms);
|
|||
|
|
return ms.ToArray();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 尝试多种编码解码
|
|||
|
|
/// </summary>
|
|||
|
|
private string TryDecode(byte[] bytes)
|
|||
|
|
{
|
|||
|
|
// 尝试的编码列表
|
|||
|
|
System.Text.Encoding[] encodings = new System.Text.Encoding[]
|
|||
|
|
{
|
|||
|
|
System.Text.Encoding.UTF8,
|
|||
|
|
System.Text.Encoding.GetEncoding(936), // GBK
|
|||
|
|
System.Text.Encoding.GetEncoding(54936), // GB18030
|
|||
|
|
System.Text.Encoding.Default
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
foreach (var encoding in encodings)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
string result = encoding.GetString(bytes);
|
|||
|
|
// 检查是否包含乱码特征
|
|||
|
|
if (!ContainsGarbled(result))
|
|||
|
|
{
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 默认使用 UTF-8
|
|||
|
|
return System.Text.Encoding.UTF8.GetString(bytes);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查是否包含乱码
|
|||
|
|
/// </summary>
|
|||
|
|
private bool ContainsGarbled(string text)
|
|||
|
|
{
|
|||
|
|
// 检查是否包含常见的乱码字符
|
|||
|
|
foreach (char c in text)
|
|||
|
|
{
|
|||
|
|
if (c == '<27>' || c == '?' || (c >= 0x80 && c <= 0x9F))
|
|||
|
|
{
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 从输出中提取 SHA1
|
|||
|
|
/// </summary>
|
|||
|
|
private string ExtractSHA1(string output)
|
|||
|
|
{
|
|||
|
|
System.Text.StringBuilder result = new System.Text.StringBuilder();
|
|||
|
|
|
|||
|
|
// 提取证书指纹
|
|||
|
|
string[] lines = output.Split('\n');
|
|||
|
|
bool inCertificateFingerprints = false;
|
|||
|
|
|
|||
|
|
foreach (string line in lines)
|
|||
|
|
{
|
|||
|
|
if (line.Contains("Certificate fingerprints"))
|
|||
|
|
{
|
|||
|
|
inCertificateFingerprints = true;
|
|||
|
|
result.AppendLine("证书指纹:");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (inCertificateFingerprints)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(line))
|
|||
|
|
{
|
|||
|
|
inCertificateFingerprints = false;
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
result.AppendLine(line.Trim());
|
|||
|
|
|
|||
|
|
// 提取 SHA1 值(用于 Firebase)
|
|||
|
|
if (line.Contains("SHA1"))
|
|||
|
|
{
|
|||
|
|
string sha1 = line.Split(':')[1].Trim();
|
|||
|
|
result.AppendLine($"\n>>> Firebase 需要的 SHA-1: {sha1} <<<");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (result.Length == 0)
|
|||
|
|
{
|
|||
|
|
result.AppendLine("原始输出:");
|
|||
|
|
result.AppendLine(output);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return result.ToString();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取 keytool 路径
|
|||
|
|
/// </summary>
|
|||
|
|
private string GetKeytoolPath()
|
|||
|
|
{
|
|||
|
|
// 尝试从 Unity 的 JDK 路径获取
|
|||
|
|
string jdkPath = EditorApplication.applicationPath;
|
|||
|
|
|
|||
|
|
// macOS
|
|||
|
|
if (Application.platform == RuntimePlatform.OSXEditor)
|
|||
|
|
{
|
|||
|
|
string keytool = Path.Combine(jdkPath, "Contents", "PlaybackEngines", "AndroidPlayer", "OpenJDK", "bin", "keytool");
|
|||
|
|
if (File.Exists(keytool))
|
|||
|
|
return keytool;
|
|||
|
|
}
|
|||
|
|
// Windows
|
|||
|
|
else if (Application.platform == RuntimePlatform.WindowsEditor)
|
|||
|
|
{
|
|||
|
|
// 尝试 Unity 自带的 JDK
|
|||
|
|
string unityJdk = Path.Combine(Path.GetDirectoryName(jdkPath), "Data", "PlaybackEngines", "AndroidPlayer", "OpenJDK", "bin", "keytool.exe");
|
|||
|
|
if (File.Exists(unityJdk))
|
|||
|
|
return unityJdk;
|
|||
|
|
|
|||
|
|
// 尝试环境变量
|
|||
|
|
string envKeytool = System.Environment.GetEnvironmentVariable("JAVA_HOME");
|
|||
|
|
if (!string.IsNullOrEmpty(envKeytool))
|
|||
|
|
{
|
|||
|
|
string keytool = Path.Combine(envKeytool, "bin", "keytool.exe");
|
|||
|
|
if (File.Exists(keytool))
|
|||
|
|
return keytool;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 尝试系统 PATH
|
|||
|
|
return "keytool";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|