石器时代手游双端源码编译与打包全流程解析
简介StoneAgeMobileApp是一份面向安卓与iOS双平台的移动端游戏源码适合移动开发者和开源爱好者研究跨平台应用结构。既能帮助入门者理解App整体架构也能为进阶开发者提供模块级实现细节。项目围绕‘石器时代’主题覆盖Java/Kotlin与Swift/Objective-C两种原生技术栈能清晰看到Activity/Service等安卓组件、UIKit/SwiftUI界面搭建、网络请求与数据存储等典型模块同时整合SDL、FLAC等多媒体底层库演示了应用层与C/C库混编的系统级开源组织方式。资源包共10335个文件以C/C/Objective-C源码、头文件、工程配置vcproj/vcxproj/xcodeproj及HTML/TXT/MD说明文档为主总体积37.05MB目录同时包含安卓、iOS工程与第三方库源码便于按模块对照排查逻辑。已有1065人学习通过阅读可快速理清双平台架构掌握原生与底层库协同的写法也能为后续跨平台开发或功能移植提供可复用参考。1. 先看 StoneAgeMobileApp 是什么一套能跑的端游移植源码而不是个包做手游源码逆向或者接手老项目的人看到 StoneAgeMobileApp 这个名字时第一反应可能和我一样这是不是个编好的石器时代单机包实际上它是一份双端源代码工程Android 和 iOS 两个壳工程共享同一套 C 核心逻辑地图、战斗、宠物系统、资源加载全被编译进原生代码里。你拿到它能做的事情不是直接开玩而是在本机把两个端都编起来改 UI、换资源、接自己的游戏服务器最后产出一个能安装的 APK 和 IPA。下面按我自己的动手顺序把从源码到双端可运行包的完整链路讲清楚适合想快速验证这套源码、或者打算二次开发的团队。提前说一句老代码的坑不在功能逻辑而在工具链和包体配置后面你会反复遇到。2. 做编译前的目录体检先分清壳工程、核心代码与资源2.1 根目录结构Android、iOS 两个壳工程包着同一份 C 核心老牌端游移植到手机端最常见做法不是把游戏逻辑用 Java 或 Swift 重写一遍而是保留 C 核心只把平台相关的入口、生命周期、输入输出放到各自的壳工程里。StoneAgeMobileApp 这类源码如果结构正常顶层目录通常长这样StoneAgeMobileApp/ ├── android/ # Android 壳工程Gradle 构建 ├── ios/ # iOS 壳工程Xcode project workspace ├── src/ # C 核心战斗、地图、宠物、网络协议 ├── Resources/ # 美术、音频、地图配置 ├── Tools/ # 打包脚本、资源处理脚本 └── README.md拿到压缩包后先别急着开 IDE用命令行把目录摸一遍find . -maxdepth 2 -type d | sort ls -la android ios src Resources file src/*.cpp | head -20第一行命令看整体第二行确认双端工程是否存在第三行用file检查源码格式。这里有个容易误判的点有些打包者会把资源单独抽成加密包Resources目录下只有.pak或.dat看不到散落的.png。看到这种结构别慌继续找加载入口通常 C 代码里会有专门的解包类负责把资源包映射到内存。判断标准很简单android和ios目录都在且src下有大量.cpp/.h说明是完整源码如果只有assets和一堆二进制配置那只能算资源包不是标题里说的源代码。2.2 引擎与工具链版本决定你能不能编过的是这条线双端壳工程意味着你同时需要 Android 和 Apple 两套工具链。老项目最头疼的不是代码是当时用的 SDK 和 NDK 版本。Android 端如果工程写在 2018 年前后Gradle 插件版本会很低直接拿最新 Android Studio 打开大概率会报Minimum supported Gradle version之类的错。这时候强行升级 Gradle Plugin 不一定正确C 代码往往依赖旧版 NDK比如 armeabi 架构在 NDK r23 之后被移除老库直接编不过。我的建议是先看工程里声明的版本cat android/build.gradle | grep -E classpath|gradle cat android/gradle/wrapper/gradle-wrapper.propertiesgradle-wrapper.properties里的distributionUrl是这套工程最保守的构建版本。常见做法的第一步不是升级而是按工程声明去安装对应版本的 Gradle 和 NDK。iOS 端同样ios/Podfile里如果写了平台版本platform :ios, 8.0你拿 Xcode 15 打开会有一堆 deprecation 警告但一般还是能编真正卡人的是 C 标准库和 bitcode 开关这个后面在避坑章展开。2.3 最小环境配置JDK、NDK、Xcode 命令行一把查不要凭感觉装环境用命令确认缺失项。Android 端需要 JDK、Android SDK、NDKiOS 端需要 Xcode 和 Command Line Tools。我一般在终端里依次跑java -version xcodebuild -version sdkmanager --list | grep -E ndk|cmake adb devices参数说明sdkmanager --list | grep ndk能看到当前 SDK 已安装的 NDK 版本如果你需要 r17 而机器上只有 r23系统会绕过工程声明用新 NDK 去编译最后报一堆unknown type name uint32_t之类的奇怪错误。所以这条命令不是看一眼是要对照工程里的ndkVersion或者.cxx配置。adb devices确认测试机是否连上老项目很多逻辑只在真机上暴露问题模拟器反而跑得顺。如果缺 NDK用 Android Studio 的 SDK Manager 勾选历史版本或者用sdkmanager直接装sdkmanager ndk;21.4.7075529 cmake;3.10.2.4956307版本号这里不重要关键是版本要和build.gradle里的ndkVersion对齐。iOS 端如果xcodebuild -version报Xcode未安装说明你还需要先装 Command Line Tools真机调试还要确认手机上打开开发者模式这个放到 iOS 章节细说。3. Android 端从 Gradle 调整到出 APK完整编译链路3.1 先改 gradleABI 过滤、STL 选择、CMake 参数Android 壳工程的核心工作是把 C 核心编译成 so 库再包进 APK。最常见的编译失败都发生在externalNativeBuild配置上。我先给出一段能直接落到工程里的app/build.gradle片段android { compileSdkVersion 28 defaultConfig { applicationId com.stoneage.mobile minSdkVersion 19 targetSdkVersion 28 ndk { abiFilters armeabi-v7a, arm64-v8a } externalNativeBuild { cmake { cppFlags -stdc11 -fexceptions -frtti arguments -DANDROID_STLc_shared } } } externalNativeBuild { cmake { path src/main/cpp/CMakeLists.txt } } }几个参数我解释一下。abiFilters决定打入最终包里的 CPU 架构armeabi-v7a 覆盖绝大多数老 Android 手机arm64-v8a 是后续主流如果你要用模拟器跑 x86 镜像还得临时把x86加回去否则会报failed to load native library。ANDROID_STLc_shared是关键中的关键老工程经常写成c_static编完装到手机上一旦多个 so 库同时引用 STL启动或切场景时直接崩溃c_shared把 STL 做成共享库虽然包体会大几十 KB但稳定性好得多。cppFlags里的-fexceptions和-frtti要确认代码里是否用了try/catch和dynamic_cast老代码很多地方依赖这些特性不开就会编不过。改完 gradle 后先执行一次构建看后面的错误不要急着继续配资源cd android ./gradlew assembleDebug --stacktrace第一次构建会下依赖需要几分钟。看到BUILD SUCCESSFUL再往后走如果这里就失败八成是 NDK 版本和 CMakeLists 里的要求冲突先解决这个问题再继续改资源。3.2 资源合并assets 目录、启动进度条和包体驱动Android 端资源加载路径是个隐性坑。C 引擎读取资源时无外乎从assets读或从可写目录/storage/emulated/0/android/data/com.tencent.tmgp.sgame/...这类沙盒路径读现在 Google 限制了android/data的访问老代码最容易翻车。看工程里的资源管理器类多数是FileUtils::setSearchPath配置搜索路径。常见的做法是让引擎先查assets再查外部存储。把Resources下的内容压进assets目录时注意不要直接把整个Resources文件夹塞进去应该按引擎约定压缩成.zip或.pak因为 Android 资源包要避免几千个小散文件带来的 IO 损耗。命令行做法是cd android/app/src/main/assets cp -r ../../../../Resources/* . find . -type f | wc -l如果你看到文件数超过 5000建议用工具打包成单个资源文件否则安装包会巨大且构建超慢。另外启动进度条是个值得关注的点老游戏在启动时依赖引擎preload一段资源列表如果列表里的路径比实际资源多一个层级进度条会卡在 80% 左右不动看起来像死机实际是缺资源。遇到进度条卡住优先对照列表路径和 assets 里真实路径。3.3 命令行出包与签名assembleRelease 到 apksigner开发阶段直接用assembleDebug就行但要给别人装、或者模拟上架一定走 release 流程。release 流程由两步组成编译打包 签名。老项目常犯的错是只跑assembleRelease然后直接安装结果系统提示“应用未安装”因为 release 包默认是未签名的。cd android ./gradlew assembleRelease --info find app/build/outputs/apk -name *release*.apk--info会打印每次编译的完整记录适合定位资源压缩卡死、NDK 交叉编译警告这类黑匣子问题。生成的未签名包在app/build/outputs/apk/下名字类似app-release-unsigned.apk。接着用 Android SDK 自带的apksigner签名apksigner sign --ks my-release.keystore --ks-key-alias stoneage --ks-pass pass:changeit app-release-unsigned.apk zipalign -v 4 app-release-unsigned.apk stoneage-release.apk注意参数顺序zipalign要在签名之后跑建议直接拿apksigner verify --verbose stoneage-release.apk检查签名状态。很多环境装了旧版jarsigner对 APK 签名后也能装但在 Android 7.0 以上设备会因为签名方案过旧被拒绝所以统一用apksigner。如果你的工程里已经配了signingConfigs这条命令行可以省略但我建议手工走一次能看清整个包产物链路后面排查问题时你会感谢这个流程。4. iOS 端从 Xcode 工程到真机运行证书、签名与模拟器的取舍4.1 打开工程前的预处理pod install 和脚本修复iOS 壳工程一般以.xcodeproj或.xcworkspace形式存在。看到.xcworkspace说明用了 CocoaPods 管理第三方库必须先在ios目录下执行cd ios pod install --repo-updatepod install会依据Podfile拉取依赖库例如微信登录 SDK、崩溃统计库、加密库等并生成xcworkspace。这部分最容易出问题的是 Pod 仓库里的库版本和当前 Xcode 不兼容比如use_frameworks!导致导出成动态库后签名报错。我的做法是如果pod install失败先看报错是来自哪个 pod再回Podfile里锁定旧版本号。打开工程前还要检查有没有生成阶段脚本Build Phases - Run Script。老工程喜欢在编译前调用脚本拷贝资源或处理图标ls ios/*.sh cat ios/CMakeLists.txt 2/dev/null一些源码包会通过 CMake 先生成部分.a静态库再交给 Xcode 编译这类工程不能直接打开.xcodeproj得先把 CMake 步骤跑完否则编译期会提示找不到头文件。这一步的坑在于图片资源、info.plist路径、签名配置分散在多个目录脚本一旦失败错误信息又藏在 Xcode 日志的中间段新手特别容易被误导。4.2 签名与 bundle id真机运行绕不开的两个门槛iOS 与 Android 最大的区别是签名闭环。模拟器不需要签名但真机安装 IPA 必须有一对证书描述文件。如果你的 Apple 开发者账号还没配好最容易卡的地方是Signing Capabilities里显示 “Signing for ‘StoneAgeMobileApp’ requires a development team”此时需要做三件事在 Xcode 中选择自己的 Team修改 Bundle Identifier 为唯一值比如com.yourcompany.stoneage-mobile老工程默认的com.stoneage.mobile可能被占用在真机上打开“开发者模式”iOS 16 及以上设置里的 Developer Mode 条目否则 Xcode 会提示没有权限安装。真机调试前我习惯先用xcodebuild做一次无界面构建避免反复点击 Xcode 按钮浪费时间xcodebuild -workspace StoneAgeMobileApp.xcworkspace \ -scheme StoneAgeMobileApp \ -configuration Debug \ -destination generic/platformiOS \ CODE_SIGNING_ALLOWEDNO参数的落点是CODE_SIGNING_ALLOWEDNO先关闭签名看纯编译是否通过如果纯编译都过不了签名问题还没资格出现。编译通过后再用 Xcode 连接真机跑一次。真机首次连接时手机屏幕会弹出“信任此电脑”的提示没有点击信任就会一直卡在 “device locked” 状态这是新手最容易误判为编译失败的一环。4.3 模拟器与真机的差异资源路径、沙盒与 GPU模拟器能跑、真机闪退这类问题在 iOS 端比 Android 更常见。先看两处第一处是资源读取路径。模拟器文件系统对大小写不敏感真机敏感。老引擎在 Windows 或 mac 编译时生成的资源列表里路径可能是Texture/Pet/001.png而实际磁盘文件是Texture/pet/001.png模拟器能读到真机直接fopen失败黑屏或模型丢失。第二处是沙盒目录不同。模拟器的应用数据目录是 mac 本机路径真机上则是类似.../Library/Caches的受限目录。老代码常会把日志写到完全没有权限的路径导致启动时崩溃。排查时用真机连接 Xcode直接看 Console 输出比靠感觉插桩快得多。还可以用lldb在main.cpp启动入口打一个断点逐行看资源管理器初始化是否成功。我的习惯是在 iOS 端优先用真机验证功能用模拟器只做 UI 布局和连点测试。尤其是涉及 IAP、微信回调、推送这类系统能力的功能模拟器基本等于没有用真机走一遍才能确认代码里的回调真的触发了。5. 避坑编译失败、闪退和数据不通的 5 条排查记录5.1 现象链接时大量 undefined symbol原因NDK 的 STL 类型不一致Android 端编译到一半报错刷屏__cxa_begin_catch、std::__1::basic_string全是未定义符号。原因是工程的多个静态库.a用了不同 STL一个编译时选了c_static另一个选了c_shared链接阶段符号表对不上。解决方式是把所有模块统一的编译参数写进CMakeLists.txt并且让 gradle 里的arguments不要重复覆盖# CMakeLists.txt 里强制 STL 一致 set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -fexceptions -frtti)改完后重新./gradlew clean assembleDebug。这里要特别注意clean 必须执行旧缓存里的.a不会自动替换容易遇到“明明改了还不生效”的假象。5.2 现象启动后闪退回桌面没有任何报错原因C 异常没被系统捕获Android 端安装后点图标回到桌面logcat 没看到 Java 异常。这类崩溃九成发生在 native 层。老代码里try/catch覆盖不到所有 C 异常一个空指针就触发SIGSEGV系统强制杀掉进程。先用adb抓 tombstoneadb logcat -b crash -d adb pull /data/tombstones .//data/tombstones需要 root 才能拉在一台开发机上可以尽量用adb shell看tombstone_*文件里第一行的进程名和signal编号如果没 root更实际的办法是给 C 入口加全局信号捕获比如signal(SIGSEGV, handler)再配合addr2line解析 crash 时打印的返回地址。启动闪退还不只是代码问题资源加载失败同样会走到这条路径所以排查顺序应该是资源路径 → 系统库加载 → C 异常。5.3 现象地图黑块、宠物模型缺失原因资源路径大小写和打包层级Android 上地图读取正常进了战斗却黑块iOS 真机上头像显示一半。检查资源管理器搜索路径时发现代码写的是Resource/Pet/而实际资源在Resources/pet_001/。大多数引擎默认大小写敏感Windows 打包时复制错层级多套一层client/目录读不到就黑块。解决是通过日志把每次加载失败的路径打印出来对比磁盘路径。批量修正时用 Python 脚本统一归一化import os root Resources for dirpath, _, files in os.walk(root): for name in files: if name ! name.lower(): new_name name.lower() os.rename( os.path.join(dirpath, name), os.path.join(dirpath, new_name) )脚本只做文件名转小写不改扩展名。这样至少让 Android、iOS 两端路径保持一致。老代码里对路径的拼法千奇百怪有个预览版在 Windows 上不区分大小写到手机全暴露这就是典型的“开发期没被坑过上线期被玩家骂死”的问题。5.4 现象能进游戏但连不上服务器原因服务器地址写死在配置里App 装好后能启动进登录界面就转圈最后报“网络连接失败”。导出的 APK 里如果服务器 IP 被写死在 C 配置宏里翻代码找 IP 要费很大力气。常见情况是 IP 被拆成多段字符串拼起来甚至做了 base64 混淆。先全局搜索端口号grep -r 8080\|9001\|serverip android/src ios/src --include*.cpp -n找到后改成从外部配置读取而不是重新编译。老项目调试服务端时改一次 IP 编一次包太浪费时间。我的做法是加一个启动参数优先读取外部文件先查getExternalFilesDirAndroid 不受android/data目录限制或 iOS 的Documents目录下的server.cfg没有才用内置默认值。这样在测试机上改个文本文件就能切服务端环境不用重新出包。5.5 现象iOS 模拟器能跑真机闪退原因bitcode 或证书关联文件缺失模拟器正常运行真机装上去一开就闪退连启动页都没稳住。排查顺序先看 Xcode Console 的dyld报错基本是找不到动态库或签名失效再看 Build Settings 里的Enable BitcodeYES旧框架如果没开 bitcode在真机上会被桥接层拒绝。解决方式是关闭 Bitcode并检查项目里的.framework是否完整特别是有加密库时模拟器 x86_64 架构和真机 arm64 架构的 slice 不一样重新pod install拉取真机 slice 通常能解决。另一个隐藏点iOS 真机的资源文件如果被放到.gitignore里而模拟器从 mac 目录可见也会导致差异。做 iOS 验证前先把Resources文件夹拖进工程不要用 Create folder references 换掉真实目录否则路径关系会在真机上错位。6. 从能跑通到能上线验证清单、切服技巧和日志习惯APK 和 IPA 都能装上、能进游戏接下来才是真正拉开差距的部分。我的验证分三块启动、核心功能、稳定性。启动不只是看能不能打开要看冷启动耗时和资源加载进度条是否在目标机器上流畅核心功能按“登录→创建角色→进出地图→战斗→背包→宠物→保存/读取”排一条路径每个关键步骤做一次手动埋点稳定性则用连续切换场景 50 次、弱网 30 秒断线重连来压。切服这个需求最容易在临上线时爆发。把服务器地址外置到配置文件的技巧见 5.4这里补充一个细节配置文件名不要叫server.cfg容易被打包工具过滤或校验可以放到assets下叫local_config.ini引擎加载时先读它。内容保持极简[server] host192.168.1.10 port9001代码里注意用足够大的缓冲区老代码的 socket 缓冲区经常只有 128 字节塞一个 IPv6 地址就崩。最后讲一个我自己的血泪经验接手老项目时永远不要先删日志系统。石器时代这种老牌端游的日志看起来是最不重要的一层实际上它记录了资源加载的每一次失败路径、服务器回包的原始字节流、甚至崩溃前的操作序列。我见过有人嫌弃日志文件太大直接把log_info全注释掉结果上线后玩家反馈的场景丢失完全没法复现最后只能重新开日志、打热更、再等 48 小时回收日志耽误了一个发布周期。保留日志、配上按天滚动、压缩上传才是负责任的维护方式。我的习惯是每周跑一次双端构建并验证一条完整核心路径很多藏在老代码里的问题都是在这种机械重复中提前暴露的希望帮到你。本文还有配套的精品资源点击获取