三国卡牌客户端基础资源仓库
hch
2026-10-07 1b6da6b04ba2beb49c3b23ab08a022f6026d5bac
0312 欢游硬核新SDK -  IOS打包;需要复测下 IOS包是否正常
2个文件已修改
452 ■■■■■ 已修改文件
Assets/Editor/Tool/ClientPackage.cs 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/XCodeProjectMod.cs 433 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
Assets/Editor/Tool/ClientPackage.cs
@@ -771,19 +771,16 @@
        {
            PlayerSettings.iOS.buildNumber = config.buildIndex.ToString();
            PlayerSettings.iOS.appleDeveloperTeamID = config.appleDeveloperTeamID;
            if (_replace)
            {
                XCodeProjectMod.BuildIOSProject_Replace();
            }
            else
            {
                XCodeProjectMod.BuildIOSProject_Append();
            }
        }).Forget();
#if UNITY_IOS
        if (_replace)
        {
            XCodeProjectMod.BuildIOSProject_Replace();
        }
        else
        {
            XCodeProjectMod.BuildIOSProject_Append();
        }
#endif
    }
    /// <summary>
Assets/Editor/XCodeProjectMod.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.iOS.Xcode.Custom;
using UnityEngine;
@@ -10,6 +11,34 @@
    // Channel 插件存放目录(BuildIpa 前由 ClientPackage.BuildIpa 从 _sdkPath 拷贝到 Assets/Plugins/iOS/Channel)
    private static readonly string iOSPluginPath = Application.dataPath + "/Plugins/iOS/Channel";
    // ===== 渠道标识:与 ClientPackage 中 VersionConfig.appId / sdkFileName 对应 =====
    private const string CHANNEL_SGHY = "sghy";   // 原有渠道(fimpfunSDK + Firebase/Facebook/Adjust)
    private const string CHANNEL_HYYH = "hyyh";   // 欢游硬核(HGameSDK)
    private static string s_Channel = null;
    /// <summary>当前打包渠道,未识别到时按 sghy 处理,保证原有打包行为不变</summary>
    private static string Channel
    {
        get
        {
            if (s_Channel == null)
            {
                s_Channel = DetectChannel();
            }
            return s_Channel;
        }
    }
    private static bool IsSghy { get { return Channel == CHANNEL_SGHY; } }
    private static bool IsHyyh { get { return Channel == CHANNEL_HYYH; } }
    private static string DetectChannel()
    {
        return VersionConfig.Get().appId;;
    }
    [UnityEditor.Callbacks.PostProcessBuild(999)]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
@@ -18,18 +47,19 @@
            return;
        }
        // 一次 Editor 会话可能连续打多个渠道的包,这里重新识别渠道
        s_Channel = null;
        Debug.Log("[XCodeProjectMod] 当前 iOS 渠道: " + Channel);
        DoPBXProject(path);
        BuildPlist(path);
        ModifyFile(path);
        // 生成 .xcworkspace 结构,方便开发者在 Xcode 中手动 Add Package Dependency
        // SPM 依赖必须通过 .xcworkspace 管理,.xcodeproj 单独无法正确 resolve
        // 开发者需在 Xcode 中手动 File → Add Package Dependency 添加以下仓库,并选择所需的产品包:
        //   - Firebase:  https://github.com/firebase/firebase-ios-sdk
        //   - Facebook:  https://github.com/facebook/facebook-ios-sdk
        //   - Adjust:    https://github.com/adjust/ios_sdk
        CreateWorkspaceWithSPM(path);
        // SPM 依赖(Firebase / Facebook / Adjust)为 sghy 渠道使用,需要通过 .xcworkspace 管理
        if (IsSghy)
        {
            CreateWorkspaceWithSPM(path);
        }
    }
    static void DoPBXProject(string path)
@@ -47,53 +77,86 @@
        project.SetBuildProperty(fwTargetGUID, "IPHONEOS_DEPLOYMENT_TARGET", "13.0");
        project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-ObjC");
        // Firebase 调试日志参数
        project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsDebugEnabled");
        project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRDebugEnabled");
        project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsVerboseLoggingEnabled");
        // WebKit(文档要求)
        project.AddFrameworkToProject(fwTargetGUID, "WebKit.framework", true);
        //UnityFramework
        project.AddBuildProperty(fwTargetGUID, "OTHER_LDFLAGS", "-ObjC");
        // 添加 GoogleService-Info.plist 和 funshine.plist 到工程
        string[] configPlists = { "GoogleService-Info.plist", "funshine.plist" };
        foreach (var plistName in configPlists)
        // sghy 渠道:Firebase 调试日志参数
        if (IsSghy)
        {
            string srcPath = iOSPluginPath + "/" + plistName;
            if (File.Exists(srcPath))
            project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsDebugEnabled");
            project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRDebugEnabled");
            project.AddBuildProperty(targetGUID, "OTHER_LDFLAGS", "-FIRAnalyticsVerboseLoggingEnabled");
            // WebKit(文档要求)
            project.AddFrameworkToProject(fwTargetGUID, "WebKit.framework", true);
            // 添加 GoogleService-Info.plist 和 funshine.plist 到工程
            string[] configPlists = { "GoogleService-Info.plist", "funshine.plist" };
            foreach (var plistName in configPlists)
            {
                string destPath = path + "/" + plistName;
                File.Copy(srcPath, destPath, true);
                string fileGuid = project.AddFile(plistName, plistName, PBXSourceTree.Source);
                string srcPath = iOSPluginPath + "/" + plistName;
                if (File.Exists(srcPath))
                {
                    string destPath = path + "/" + plistName;
                    File.Copy(srcPath, destPath, true);
                    string fileGuid = project.AddFile(plistName, plistName, PBXSourceTree.Source);
                    project.AddFileToBuild(targetGUID, fileGuid);
                }
                else
                {
                    Debug.LogWarning("[XCodeProjectMod] " + plistName + " 未找到: " + srcPath);
                }
            }
            // hfsdk.xcframework 添加到工程并设置为 Embed & Sign(sghy 渠道)
            string hfFrameworkPath = path + "/Frameworks/hfsdk.xcframework";
            if (Directory.Exists(hfFrameworkPath))
            {
                string fileGuid = project.AddFile(hfFrameworkPath, "Frameworks/hfsdk.xcframework", PBXSourceTree.Source);
                project.AddFileToBuild(fwTargetGUID, fileGuid);
                // Embed & Sign: 添加到 main target 的 Embed Frameworks
                project.AddFileToBuild(targetGUID, fileGuid);
            }
            else
            {
                Debug.LogWarning("[XCodeProjectMod] " + plistName + " 未找到: " + srcPath);
            }
            project.AddFrameworkToProject(fwTargetGUID, "AdServices.framework", true);
            project.AddFrameworkToProject(fwTargetGUID, "AdSupport.framework", true);
            project.AddFrameworkToProject(fwTargetGUID, "AppTrackingTransparency.framework", true);
            project.AddFrameworkToProject(fwTargetGUID, "StoreKit.framework", true);
        }
        // hfsdk.xcframework 添加到工程并设置为 Embed & Sign
        string hfFrameworkPath = path + "/Frameworks/hfsdk.xcframework";
        if (Directory.Exists(hfFrameworkPath))
        // hyyh 渠道:HGameSDK.xcframework / HGameChannelSdk.xcframework 设置为 Embed & Sign
        if (IsHyyh)
        {
            string fileGuid = project.AddFile(hfFrameworkPath, "Frameworks/hfsdk.xcframework", PBXSourceTree.Source);
            project.AddFileToBuild(fwTargetGUID, fileGuid);
            // Embed & Sign: 添加到 main target 的 Embed Frameworks
            project.AddFileToBuild(targetGUID, fileGuid);
            AddEmbedFramework(project, path, fwTargetGUID, targetGUID, "HGameSDK.xcframework");
            AddEmbedFramework(project, path, fwTargetGUID, targetGUID, "HGameChannelSdk.xcframework");
        }
        // Adjust SDK 依赖的系统框架(文档要求 Status 设为 Optional)
        project.AddFrameworkToProject(fwTargetGUID, "AdServices.framework", true);
        project.AddFrameworkToProject(fwTargetGUID, "AdSupport.framework", true);
        project.AddFrameworkToProject(fwTargetGUID, "AppTrackingTransparency.framework", true);
        project.AddFrameworkToProject(fwTargetGUID, "StoreKit.framework", true);
        File.WriteAllText(_projectPath, project.WriteToString());
    }
    /// <summary>
    /// 将导出工程 Frameworks 目录下的 xcframework 加入工程并设置 Embed & Sign。
    /// Unity 会把 Assets/Plugins/iOS 下的 .xcframework 拷贝到 &lt;导出工程&gt;/Frameworks 下。
    /// </summary>
    private static void AddEmbedFramework(PBXProject project, string projectPath, string fwTargetGUID, string targetGUID, string frameworkName)
    {
        string relPath = "Frameworks/" + frameworkName;
        string frameworkPath = projectPath + "/" + relPath;
        if (!Directory.Exists(frameworkPath))
        {
            // Unity 不同版本放置位置可能不同(Frameworks 下或插件子目录),这里递归兜底查找
            string[] found = Directory.GetDirectories(projectPath, frameworkName, SearchOption.AllDirectories);
            if (found == null || found.Length == 0)
            {
                Debug.LogWarning("[XCodeProjectMod] " + frameworkName + " 未找到: " + frameworkPath);
                return;
            }
            frameworkPath = found[0];
            relPath = frameworkPath.Substring(projectPath.Length).TrimStart('/', '\\').Replace("\\", "/");
        }
        string fileGuid = project.AddFile(frameworkPath, relPath, PBXSourceTree.Source);
        project.AddFileToBuild(fwTargetGUID, fileGuid);  // Link Binary With Libraries
        project.AddFileToBuild(targetGUID, fileGuid);    // Embed Frameworks
        Debug.Log("[XCodeProjectMod] 已添加(Embed & Sign): " + relPath);
    }
    /// <summary>
@@ -144,21 +207,40 @@
        PlistElementDict _rootDict = _plist.root;
        _rootDict.SetString("NSUserTrackingUsageDescription", "Please rest assured, granting permission will not access your private information on other websites. This permission is only used to identify the device and ensure service security and enhance browsing experience");
        // Facebook (funshine.plist: kFacebookAppID / kFacebookAppSecret)
        _rootDict.SetString("FacebookAppID", "1445747540428317");
        _rootDict.SetString("FacebookClientToken", "1103765897fb67ffc898e6a8ee2d9aec");
        _rootDict.SetString("FacebookDisplayName", "EZ PZ: Byte Heroes");
        // sghy 渠道:Facebook(funshine.plist: kFacebookAppID / kFacebookAppSecret)
        if (IsSghy)
        {
            _rootDict.SetString("FacebookAppID", "1445747540428317");
            _rootDict.SetString("FacebookClientToken", "1103765897fb67ffc898e6a8ee2d9aec");
            _rootDict.SetString("FacebookDisplayName", "EZ PZ: Byte Heroes");
        // 适配欧盟地区政策法规 - Google Analytics 默认允许
        _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE", true);
        _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE", true);
        _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA", true);
        _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS", true);
            PlistElementArray nsURLTypes = _rootDict.CreateArray("CFBundleURLTypes");
            var dict0 = nsURLTypes.AddDict();
            dict0.SetString("CFBundleTypeRole", "Editor");
            dict0.SetString("CFBundleURLName", "fb");
            var schemesArray = dict0.CreateArray("CFBundleURLSchemes");
            schemesArray.AddString("fb1445747540428317");
        // App 名称本地化
        _rootDict.SetBoolean("Application has localized display name", true);
        _rootDict.SetString("CFBundleDevelopmentRegion", "english");
        _rootDict.SetString("Localization native development region", "english");
            var array1 = _rootDict.CreateArray("LSApplicationQueriesSchemes");
            array1.AddString("fbapi");
            array1.AddString("fb-messenger-api");
            array1.AddString("fbauth2");
            array1.AddString("fbshareextension");
            // 适配欧盟地区政策法规 - Google Analytics 默认允许
            _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_ANALYTICS_STORAGE", true);
            _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_STORAGE", true);
            _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_USER_DATA", true);
            _rootDict.SetBoolean("GOOGLE_ANALYTICS_DEFAULT_ALLOW_AD_PERSONALIZATION_SIGNALS", true);
        }
        // App 名称本地化(需要 InfoPlist.strings 中存在 CFBundleDisplayName,见 CreateInfoPlistStrings)
        if (HasDisplayName())
        {
            _rootDict.SetBoolean("Application has localized display name", true);
            _rootDict.SetString("CFBundleDevelopmentRegion", "english");
            _rootDict.SetString("Localization native development region", "english");
        }
        // ===== 启动屏白底(Info.plist 方式:UILaunchScreen -> Background color)=====
        // iOS 13+ 支持用 Info.plist 的 UILaunchScreen 字典描述启动屏,无需 storyboard。
@@ -177,19 +259,6 @@
        PlistElementDict launchScreenDict = _rootDict.CreateDict("UILaunchScreen");
        launchScreenDict.SetString("UIColorName", launchScreenColorName);
        // =====================================================================
        PlistElementArray nsURLTypes = _rootDict.CreateArray("CFBundleURLTypes");
        var dict0 = nsURLTypes.AddDict();
        dict0.SetString("CFBundleTypeRole", "Editor");
        dict0.SetString("CFBundleURLName", "fb");
        var schemesArray = dict0.CreateArray("CFBundleURLSchemes");
        schemesArray.AddString("fb1445747540428317");
        var array1 = _rootDict.CreateArray("LSApplicationQueriesSchemes");
        array1.AddString("fbapi");
        array1.AddString("fb-messenger-api");
        array1.AddString("fbauth2");
        array1.AddString("fbshareextension");
        File.WriteAllText(_plistPath, _plist.WriteToString());
    }
@@ -247,6 +316,27 @@
    private static void ModifyFile(string projectPath)
    {
        if (IsHyyh)
        {
            ModifyFileForHyyh(projectPath);
        }
        else
        {
            ModifyFileForSghy(projectPath);
        }
        // 创建 InfoPlist.strings(App 名称本地化)
        CreateInfoPlistStrings(projectPath);
        // Sign In with Apple entitlements
        SetupEntitlements(projectPath);
    }
    /// <summary>
    /// sghy 渠道(fimpfunSDK + Firebase/Facebook/Adjust)的 AppController 注入
    /// </summary>
    private static void ModifyFileForSghy(string projectPath)
    {
        //修改UnityAppController.mm 文件
        var _xclass = new XClass(projectPath + "/Classes/UnityAppController.mm");
        //引入fimpfunSDK
@@ -289,9 +379,179 @@
        {
            Debug.LogWarning("[XCodeProjectMod] UnityViewControllerBase.mm 文件不存在,跳过 viewDidAppear 注入。此 Unity 版本可能已移除该文件。");
        }
    }
        // 创建 InfoPlist.strings(App 名称本地化)
        CreateInfoPlistStrings(projectPath);
    /// <summary>
    /// hyyh 渠道(HGameSDK)的 AppController 注入:
    /// 引入 UniversalSDK、暴露 IOSUniyMessageHandle 给 C#(AotSdkUtility)、初始化并转发 AppDelegate 生命周期。
    /// </summary>
    private static void ModifyFileForHyyh(string projectPath)
    {
        string mmPath = projectPath + "/Classes/UnityAppController.mm";
        string hPath = projectPath + "/Classes/UnityAppController.h";
        if (!File.Exists(mmPath) || !File.Exists(hPath))
        {
            Debug.LogError("[XCodeProjectMod][hyyh] UnityAppController 文件不存在: " + projectPath + "/Classes");
            return;
        }
        // 引入 UniversalSDK(渠道插件 wgyx/UniversalSDK.h)
        InsertBelowPattern(mmPath, @"#include\s+""PluginBase/AppDelegateListener\.h""",
                           "#import \"UniversalSDK.h\"", "import UniversalSDK");
        // C# 调用入口,与 AotSdkUtility.IOSUniyMessageHandle 对应
        string entryCode = "\n" +
                           "extern \"C\" void IOSUniyMessageHandle(const char* jsonString) {\n" +
                           "    [GetAppController().universalSDK HandleUnityMessage:[NSString stringWithUTF8String:jsonString]];\n" +
                           "}";
        InsertBelowPattern(mmPath, @"extern\s+""C""\s+ScreenOrientation\s+UnityCurrentOrientation\s*\(\s*\)",
                           entryCode, "IOSUniyMessageHandle");
        // 初始化 HGameSDK(必须在主线程、在 delegate 设置之后)
        string initCode = "\n" +
                          "    _universalSDK = [[UniversalSDK alloc] init];\n" +
                          "    [_universalSDK HGameSDKInit:application didFinishLaunchingWithOptions:launchOptions];";
        InsertBelowPattern(mmPath, @"::printf\(""-> applicationDidFinishLaunching\(\\n""\);",
                           initCode, "initSdk");
        // ===== UIApplicationDelegate 生命周期转发(HGameSDK 必接)=====
        InsertBelowPattern(mmPath, @"- \(BOOL\)application:[^\n]*openURL:[^\n]*\n\s*\{",
                           "    [_universalSDK application:[UIApplication sharedApplication] openURL:url options:options];", "openURL");
        InsertBelowPattern(mmPath, @"- \(void\)applicationWillResignActive:[^\n]*\n\s*\{",
                           "    [_universalSDK applicationWillResignActive:[UIApplication sharedApplication]];", "willResignActive");
        InsertBelowPattern(mmPath, @"- \(void\)applicationDidEnterBackground:[^\n]*\n\s*\{",
                           "    [_universalSDK applicationDidEnterBackground:[UIApplication sharedApplication]];", "didEnterBackground");
        InsertBelowPattern(mmPath, @"- \(void\)applicationWillEnterForeground:[^\n]*\n\s*\{",
                           "    [_universalSDK applicationWillEnterForeground:[UIApplication sharedApplication]];", "willEnterForeground");
        InsertBelowPattern(mmPath, @"- \(void\)applicationDidBecomeActive:[^\n]*\n\s*\{",
                           "    [_universalSDK applicationDidBecomeActive:[UIApplication sharedApplication]];", "didBecomeActive");
        InsertBelowPattern(mmPath, @"- \(void\)applicationWillTerminate:[^\n]*\n\s*\{",
                           "    [_universalSDK applicationWillTerminate:[UIApplication sharedApplication]];", "willTerminate");
        InsertBelowPattern(mmPath, @"- \(void\)application:[^\n]*didRegisterForRemoteNotificationsWithDeviceToken:[^\n]*\n\s*\{",
                           "    [_universalSDK application:[UIApplication sharedApplication] didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];", "didRegisterToken");
        InsertBelowPattern(mmPath, @"- \(void\)application:[^\n]*didFailToRegisterForRemoteNotificationsWithError:[^\n]*\n\s*\{",
                           "    [_universalSDK application:[UIApplication sharedApplication] didFailToRegisterForRemoteNotificationsWithError:error];", "didFailRegister");
        // 静默远程通知:优先复用 Unity 已有的现代回调;Unity 未声明该方法时补一个完整实现(文档必接)
        string remotePattern = @"- \(void\)application:[^\n]*didReceiveRemoteNotification:[^\n]*fetchCompletionHandler:[^\n]*\n\s*\{";
        string remoteForward = "    [_universalSDK application:[UIApplication sharedApplication] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];";
        if (!InsertBelowPattern(mmPath, remotePattern, remoteForward, "didReceiveRemoteNotification"))
        {
            AppendMethod(mmPath,
                "- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler\n{\n" +
                "    [_universalSDK application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];\n}",
                "didReceiveRemoteNotification");
        }
        // 屏幕方向:渠道返回 0 表示不接管,继续走 Unity 原有逻辑
        InsertBelowPattern(mmPath, @"- \(NSUInteger\)application:[^\n]*supportedInterfaceOrientationsForWindow:[^\n]*\n\s*\{",
                           "    NSUInteger sdkOrientations = [_universalSDK application:[UIApplication sharedApplication] supportedInterfaceOrientationsForWindow:window];\n" +
                           "    if (sdkOrientations != 0) {\n        return sdkOrientations;\n    }", "supportedInterfaceOrientations");
        // Universal Link:优先复用 Unity 已有回调;没有则补一个完整实现(文档必接)
        string continuePattern = @"- \(BOOL\)application:[^\n]*continueUserActivity:[^\n]*\n\s*\{";
        string continueForward = "    if ([_universalSDK application:[UIApplication sharedApplication] continueUserActivity:userActivity restorationHandler:restorationHandler]) {\n        return YES;\n    }";
        if (!InsertBelowPattern(mmPath, continuePattern, continueForward, "continueUserActivity"))
        {
            AppendMethod(mmPath,
                "- (BOOL)application:(UIApplication*)application continueUserActivity:(NSUserActivity*)userActivity restorationHandler:(void (^)(NSArray*))restorationHandler\n{\n" +
                "    if ([_universalSDK application:application continueUserActivity:userActivity restorationHandler:restorationHandler]) {\n        return YES;\n    }\n" +
                "    return NO;\n}",
                "continueUserActivity");
        }
        // 修改 UnityAppController.h
        var _xclass = new XClass(hPath);
        _xclass.WriteBelow("@class DisplayConnection;", "@class UniversalSDK;");
        _xclass.WriteBelow("DisplayConnection*  _mainDisplay;", "UniversalSDK* _universalSDK;");
        _xclass.WriteBelow("@property (readonly, copy, nonatomic) DisplayConnection*    mainDisplay;",
                           "@property (readonly, copy, nonatomic) UniversalSDK*  universalSDK;");
    }
    /// <summary>
    /// 按正则查找注入点,并在该行之后插入代码。找不到时只告警,不修改文件。
    /// </summary>
    private static bool InsertBelowPattern(string filePath, string pattern, string text, string tag)
    {
        if (!File.Exists(filePath))
        {
            Debug.LogWarning("[XCodeProjectMod] 文件不存在,跳过注入[" + tag + "]: " + filePath);
            return false;
        }
        string content = File.ReadAllText(filePath);
        Match match = Regex.Match(content, pattern);
        if (!match.Success)
        {
            Debug.LogWarning("[XCodeProjectMod] 未找到注入点[" + tag + "],跳过: " + pattern);
            return false;
        }
        int lineEnd = content.IndexOf('\n', match.Index + match.Length - 1);
        if (lineEnd < 0)
        {
            lineEnd = content.Length;
        }
        content = content.Substring(0, lineEnd) + "\n" + text + content.Substring(lineEnd);
        File.WriteAllText(filePath, content);
        return true;
    }
    /// <summary>
    /// 在 UnityAppController.mm 的 @implementation UnityAppController 块末尾追加一个完整的 delegate 方法。
    /// 用于 Unity 未声明该方法、但渠道 SDK 要求必须转发的场景(文档必接项)。
    /// </summary>
    private static bool AppendMethod(string filePath, string methodText, string tag)
    {
        if (!File.Exists(filePath))
        {
            Debug.LogWarning("[XCodeProjectMod] 文件不存在,跳过追加[" + tag + "]: " + filePath);
            return false;
        }
        string content = File.ReadAllText(filePath);
        int implIndex = content.IndexOf("@implementation UnityAppController", StringComparison.Ordinal);
        if (implIndex < 0)
        {
            Debug.LogError("[XCodeProjectMod] 未找到 @implementation UnityAppController,无法追加[" + tag + "]");
            return false;
        }
        int endIndex = content.IndexOf("@end", implIndex, StringComparison.Ordinal);
        if (endIndex < 0)
        {
            Debug.LogError("[XCodeProjectMod] 未找到 @end,无法追加[" + tag + "]");
            return false;
        }
        content = content.Substring(0, endIndex) + "\n" + methodText + "\n\n" + content.Substring(endIndex);
        File.WriteAllText(filePath, content);
        Debug.Log("[XCodeProjectMod] 已追加 delegate 方法[" + tag + "]");
        return true;
    }
    /// <summary>
    /// 各渠道的 App 显示名称:[渠道] = { 英文, 简体中文 }
    /// 未配置的渠道不会生成本地化名称,直接使用 PlayerSettings 中的 Product Name。
    /// </summary>
    private static readonly Dictionary<string, string[]> DisplayNames = new Dictionary<string, string[]>
    {
        { CHANNEL_SGHY, new[] { "EZ PZ", "懒人三国" } },
        // TODO hyyh 渠道确认 App 名称后在这里补充:{ CHANNEL_HYYH, new[] { "英文名", "中文名" } },
    };
    private static bool HasDisplayName()
    {
        return DisplayNames.ContainsKey(Channel);
    }
    /// <summary>
@@ -299,6 +559,14 @@
    /// </summary>
    private static void CreateInfoPlistStrings(string projectPath)
    {
        if (!HasDisplayName())
        {
            Debug.LogWarning("[XCodeProjectMod] 渠道 " + Channel + " 未配置 App 显示名称,跳过 InfoPlist.strings 本地化");
            return;
        }
        string[] names = DisplayNames[Channel];
        // 英文(默认)
        string enDir = projectPath + "/English.lproj";
        if (!Directory.Exists(enDir))
@@ -308,7 +576,7 @@
        string enStringsPath = enDir + "/InfoPlist.strings";
        if (!File.Exists(enStringsPath))
        {
            File.WriteAllText(enStringsPath, "CFBundleDisplayName = \"EZ PZ\";\n");
            File.WriteAllText(enStringsPath, "CFBundleDisplayName = \"" + names[0] + "\";\n");
        }
        // 中文
@@ -320,7 +588,7 @@
        string zhStringsPath = zhDir + "/InfoPlist.strings";
        if (!File.Exists(zhStringsPath))
        {
            File.WriteAllText(zhStringsPath, "CFBundleDisplayName = \"懒人三国\";\n");
            File.WriteAllText(zhStringsPath, "CFBundleDisplayName = \"" + names[1] + "\";\n");
        }
        // 将 InfoPlist.strings 添加到 Xcode 工程
@@ -341,14 +609,33 @@
            string zhFileGuid = project.AddFile(zhRelPath, zhRelPath, PBXSourceTree.Source);
            project.AddFileToBuild(targetGUID, zhFileGuid);
            // 添加 Sign In with Apple entitlements
            File.WriteAllText(_projectPath, project.WriteToString());
        }
        catch (System.Exception e)
        {
            Debug.LogError("[XCodeProjectMod] 将 InfoPlist.strings 添加到 Xcode 工程失败: " + e.Message);
        }
    }
    /// <summary>
    /// Sign In with Apple entitlements(与渠道无关,所有渠道都需要)
    /// </summary>
    private static void SetupEntitlements(string projectPath)
    {
        string _projectPath = PBXProject.GetPBXProjectPath(projectPath);
        try
        {
            PBXProject project = new PBXProject();
            project.ReadFromString(File.ReadAllText(_projectPath));
            string targetGUID = project.TargetGuidByName(PBXProject.GetUnityTargetName());
            AddEntitlements(projectPath, project, targetGUID);
            File.WriteAllText(_projectPath, project.WriteToString());
        }
        catch (System.Exception e)
        {
            Debug.LogError("[XCodeProjectMod] 将 InfoPlist.strings 添加到 Xcode 工程失败: " + e.Message);
            Debug.LogError("[XCodeProjectMod] 添加 entitlements 失败: " + e.Message);
        }
    }