三国卡牌客户端基础资源仓库
macos
2026-06-16 970df5ba2dd630f709daa8bc9753cb9bca073eaf
1
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.iOS.Xcode.Custom;
using UnityEngine;
 
public class XCodeProjectMod
{
    // Channel 插件存放目录(BuildIpa 前由 ClientPackage.BuildIpa 从 _sdkPath 拷贝到 Assets/Plugins/iOS/Channel)
    private static readonly string iOSPluginPath = Application.dataPath + "/Plugins/iOS/Channel";
 
    [UnityEditor.Callbacks.PostProcessBuild(999)]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
        if (buildTarget != BuildTarget.iOS)
        {
            return;
        }
 
 
        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);
    }
 
    static void DoPBXProject(string path)
    {
        string _projectPath = PBXProject.GetPBXProjectPath(path);
 
        PBXProject project = new PBXProject();
        project.ReadFromString(File.ReadAllText(_projectPath));
 
        string targetGUID = project.TargetGuidByName(PBXProject.GetUnityTargetName());
        string fwTargetGUID = project.TargetGuidByName("UnityFramework");
 
        // 1.1 环境要求:iOS 13.0 及以上
        project.SetBuildProperty(targetGUID, "IPHONEOS_DEPLOYMENT_TARGET", "13.0");
        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)
        {
            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);
            }
        }
 
        // 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>
    /// 生成 Unity-iPhone.xcworkspace 结构,仅包含 contents.xcworkspacedata。
    /// SPM 依赖由开发者在 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
    /// Package.resolved 由 Xcode 在 resolve 后自动生成,代码不做处理。
    /// </summary>
    private static void CreateWorkspaceWithSPM(string projectPath)
    {
        string workspacePath = projectPath + "/Unity-iPhone.xcworkspace";
        string xcodeprojAbsPath = projectPath + "/Unity-iPhone.xcodeproj";
 
        if (!Directory.Exists(xcodeprojAbsPath))
        {
            Debug.LogWarning("[XCodeProjectMod] xcodeproj not found, skip creating workspace: " + xcodeprojAbsPath);
            return;
        }
 
        Directory.CreateDirectory(workspacePath);
 
        string contentsXml = @"<?xml version=""1.0"" encoding=""UTF-8""?>
<Workspace
   version = ""1.0"">
   <FileRef
      location = ""group:Unity-iPhone.xcodeproj"">
   </FileRef>
</Workspace>
";
        File.WriteAllText(workspacePath + "/contents.xcworkspacedata", contentsXml);
 
        Debug.Log("[XCodeProjectMod] Unity-iPhone.xcworkspace created at: " + workspacePath);
        Debug.Log("[XCodeProjectMod] 请在 Xcode 中打开 .xcworkspace,手动 File → Add Package Dependency 添加 SPM 仓库:");
        Debug.Log("[XCodeProjectMod]   - Firebase:  https://github.com/firebase/firebase-ios-sdk");
        Debug.Log("[XCodeProjectMod]   - Facebook:  https://github.com/facebook/facebook-ios-sdk");
        Debug.Log("[XCodeProjectMod]   - Adjust:    https://github.com/adjust/ios_sdk");
    }
 
    private static void BuildPlist(string path)
    {
        string _plistPath = path + "/Info.plist";
 
        PlistDocument _plist = new PlistDocument();
        _plist.ReadFromString(File.ReadAllText(_plistPath));
 
        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");
 
        // 适配欧盟地区政策法规 - 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 名称本地化
        _rootDict.SetBoolean("Application has localized display name", true);
        _rootDict.SetString("CFBundleDevelopmentRegion", "english");
        _rootDict.SetString("Localization native development region", "english");
 
        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());
    }
 
 
 
    private static void ModifyFile(string projectPath)
    {
        //修改UnityAppController.mm 文件
        var _xclass = new XClass(projectPath + "/Classes/UnityAppController.mm");
        //引入fimpfunSDK
        _xclass.WriteBelow("#include \"PluginBase/AppDelegateListener.h\"", "#import \"fimpfunSDK.h\"");
        //unity交互调用的方法
        string newCode = "\n" +
                 "extern \"C\" void IOSUniyMessageHandle(const char* jsonString) {\n" +
                 "    [GetAppController().fimpfunBridge HandleGameMessage:[NSString stringWithUTF8String:jsonString]];\n" +
                 "}";
        _xclass.WriteBelow("extern \"C\" ScreenOrientation    UnityCurrentOrientation()   { return GetAppController().unityView.contentOrientation; }", newCode);
        //初始化fimpfunSDK
        newCode = "\n" +
                "    _fimpfunBridge = [[FimpfunBridge alloc] init];\n    [_fimpfunBridge YngameSDKInit:application didFinishLaunchingWithOptions:launchOptions];";
        _xclass.WriteBelow("::printf(\"-> applicationDidFinishLaunching()\\n\");", newCode);
 
 
        // fimpfunSDK 生命周期
        _xclass.WriteBelow("- (BOOL)application:(UIApplication*)app openURL:(NSURL*)url options:(NSDictionary<NSString*, id>*)options\n{", "\n    [_fimpfunBridge application:app openURL:url options:options];");
 
        _xclass.WriteBelow("::printf(\"-> applicationDidBecomeActive()\\n\");", "[_fimpfunBridge applicationDidBecomeActive:application];");
 
 
        //修改UnityAppController.h 文件
        _xclass = new XClass(projectPath + "/Classes/UnityAppController.h");
        _xclass.WriteBelow("@class DisplayConnection;", "@class FimpfunBridge;");
        _xclass.WriteBelow("DisplayConnection*  _mainDisplay;", "FimpfunBridge* _fimpfunBridge;");
        _xclass.WriteBelow("@property (readonly, copy, nonatomic) DisplayConnection*    mainDisplay;",
                           "@property (readonly, copy, nonatomic) FimpfunBridge*  fimpfunBridge;");
 
        // 3.1 适配欧盟政策:在 viewDidAppear 中调用 adaptingEUGDPR(文档要求)
        // UnityViewControllerBase.mm 在较新 Unity 版本(如 2022.3+)中可能不存在
        string viewControllerPath = projectPath + "/Classes/UnityViewControllerBase.mm";
        if (System.IO.File.Exists(viewControllerPath))
        {
            var _viewController = new XClass(viewControllerPath);
            _viewController.WriteBelow("@implementation UnityViewControllerBase",
                "\n- (void)viewDidAppear:(BOOL)animated {\n    [super viewDidAppear:animated];\n    [GetAppController().fimpfunBridge viewDidAppear];\n}\n");
        }
        else
        {
            Debug.LogWarning("[XCodeProjectMod] UnityViewControllerBase.mm 文件不存在,跳过 viewDidAppear 注入。此 Unity 版本可能已移除该文件。");
        }
 
        // 创建 InfoPlist.strings(App 名称本地化)
        CreateInfoPlistStrings(projectPath);
    }
 
    /// <summary>
    /// 创建 InfoPlist.strings 文件用于 App 名称本地化
    /// </summary>
    private static void CreateInfoPlistStrings(string projectPath)
    {
        // 英文(默认)
        string enDir = projectPath + "/English.lproj";
        if (!Directory.Exists(enDir))
        {
            Directory.CreateDirectory(enDir);
        }
        string enStringsPath = enDir + "/InfoPlist.strings";
        if (!File.Exists(enStringsPath))
        {
            File.WriteAllText(enStringsPath, "CFBundleDisplayName = \"EZ PZ\";\n");
        }
 
        // 中文
        string zhDir = projectPath + "/zh-Hans.lproj";
        if (!Directory.Exists(zhDir))
        {
            Directory.CreateDirectory(zhDir);
        }
        string zhStringsPath = zhDir + "/InfoPlist.strings";
        if (!File.Exists(zhStringsPath))
        {
            File.WriteAllText(zhStringsPath, "CFBundleDisplayName = \"懒人三国\";\n");
        }
 
        // 将 InfoPlist.strings 添加到 Xcode 工程
        string _projectPath = PBXProject.GetPBXProjectPath(projectPath);
        try
        {
            PBXProject project = new PBXProject();
            project.ReadFromString(File.ReadAllText(_projectPath));
            string targetGUID = project.TargetGuidByName(PBXProject.GetUnityTargetName());
 
            // 英文
            string enRelPath = "English.lproj/InfoPlist.strings";
            string enFileGuid = project.AddFile(enRelPath, enRelPath, PBXSourceTree.Source);
            project.AddFileToBuild(targetGUID, enFileGuid);
 
            // 中文
            string zhRelPath = "zh-Hans.lproj/InfoPlist.strings";
            string zhFileGuid = project.AddFile(zhRelPath, zhRelPath, PBXSourceTree.Source);
            project.AddFileToBuild(targetGUID, zhFileGuid);
 
            // 添加 Sign In with Apple entitlements
            AddEntitlements(projectPath, project, targetGUID);
 
            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 AddEntitlements(string projectPath, PBXProject project, string targetGUID)
    {
        string entPath = projectPath + "/" + PBXProject.GetUnityTargetName() + ".entitlements";
 
        // 如果 entitlements 文件不存在,创建
        if (!File.Exists(entPath))
        {
            string entContent = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
                "<plist version=\"1.0\">\n" +
                "<dict>\n" +
                "\t<key>com.apple.developer.applesignin</key>\n" +
                "\t<array>\n" +
                "\t\t<string>Default</string>\n" +
                "\t</array>\n" +
                "</dict>\n" +
                "</plist>\n";
            File.WriteAllText(entPath, entContent);
        }
        else
        {
            // 已有 entitlements 文件,检查是否已有 Sign In with Apple
            string existingContent = File.ReadAllText(entPath);
            if (!existingContent.Contains("com.apple.developer.applesignin"))
            {
                existingContent = existingContent.Replace("</dict>",
                    "\t<key>com.apple.developer.applesignin</key>\n" +
                    "\t<array>\n" +
                    "\t\t<string>Default</string>\n" +
                    "\t</array>\n" +
                    "</dict>");
                File.WriteAllText(entPath, existingContent);
            }
        }
 
        // 设置 CODE_SIGN_ENTITLEMENTS build property(不加入 Copy Bundle Resources)
        // entitlements 文件只需要在 Build Settings 中引用,不需要加入任何 Build Phase
        string entFileName = PBXProject.GetUnityTargetName() + ".entitlements";
        project.SetBuildProperty(targetGUID, "CODE_SIGN_ENTITLEMENTS", entFileName);
    }
 
    private static string GetBuildPath()
    {
        string _buildPath = Application.dataPath + "/../IOS_BUILD";
 
        if (!Directory.Exists(_buildPath))
        {
            Directory.CreateDirectory(_buildPath);
        }
 
        return new DirectoryInfo(_buildPath).FullName;
    }
 
    public static string[] GetBuildLevels()
    {
        List<string> _sceneNames = new List<string>();
        foreach (var _scene in EditorBuildSettings.scenes)
        {
            if (_scene == null)
            {
                continue;
            }
 
            if (_scene.enabled)
            {
                _sceneNames.Add(_scene.path);
            }
        }
        return _sceneNames.ToArray();
    }
 
    private static void CopyDirectory(string sourceDir, string destDir)
    {
        Directory.CreateDirectory(destDir);
        foreach (var file in Directory.GetFiles(sourceDir))
        {
            string destFile = Path.Combine(destDir, Path.GetFileName(file));
            File.Copy(file, destFile, true);
        }
        foreach (var dir in Directory.GetDirectories(sourceDir))
        {
            string destSubDir = Path.Combine(destDir, Path.GetFileName(dir));
            CopyDirectory(dir, destSubDir);
        }
    }
 
    private static bool s_IsAppend = false;
 
    public static void BuildIOSProject_Append()
    {
        string _buildPath = GetBuildPath();
        UnityEngine.Debug.Log(_buildPath);
        if (string.IsNullOrEmpty(_buildPath))
        {
            return;
        }
 
        s_IsAppend = true;
        PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP);
        BuildPipeline.BuildPlayer(ClientPackage.baseLevels, _buildPath, BuildTarget.iOS, BuildOptions.AcceptExternalModificationsToPlayer);
    }
 
    public static void BuildIOSProject_Replace()
    {
        s_IsAppend = false;
        PlayerSettings.SetScriptingBackend(BuildTargetGroup.iOS, ScriptingImplementation.IL2CPP);
        BuildPipeline.BuildPlayer(ClientPackage.baseLevels, GetBuildPath(), BuildTarget.iOS, BuildOptions.None);
    }
 
    [MenuItem("Build/ipa")]
    public static void BuildIPA()
    {
        var p = new System.Diagnostics.Process();
        p.StartInfo.FileName = "osascript";
        p.StartInfo.Arguments = string.Format("-e 'tell application \"Terminal\" \n activate \n do script \"cd {0} && sh {1} {2}\" \n end tell'",
                                               Application.dataPath + "/../",
                                              "buildipa.sh",
                                               GetBuildPath());
        p.StartInfo.UseShellExecute = false;
        p.StartInfo.RedirectStandardOutput = false;
        p.Start();
        p.WaitForExit();
    }
}