├── .gitignore ├── key ├── testkey.pk8 └── testkey.x509.pem ├── images ├── 4399.svg ├── meizu.svg ├── yingyongbao.svg ├── xiaomi.svg ├── 360.svg ├── oppo.svg ├── jiuyou.svg └── vivo.svg ├── .github └── workflows │ ├── xapk.yml │ └── main.yml ├── README.md └── merge_build.sh /.gitignore: -------------------------------------------------------------------------------- 1 | *.keystore 2 | *.apk -------------------------------------------------------------------------------- /key/testkey.pk8: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Chtholly344/Azurlane-Build/HEAD/key/testkey.pk8 -------------------------------------------------------------------------------- /images/4399.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/meizu.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /key/testkey.x509.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEqDCCA5CgAwIBAgIJAJNurL4H8gHfMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD 3 | VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4g 4 | VmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UE 5 | AxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAe 6 | Fw0wODAyMjkwMTMzNDZaFw0zNTA3MTcwMTMzNDZaMIGUMQswCQYDVQQGEwJVUzET 7 | MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4G 8 | A1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9p 9 | ZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZI 10 | hvcNAQEBBQADggENADCCAQgCggEBANaTGQTexgskse3HYuDZ2CU+Ps1s6x3i/waM 11 | qOi8qM1r03hupwqnbOYOuw+ZNVn/2T53qUPn6D1LZLjk/qLT5lbx4meoG7+yMLV4 12 | wgRDvkxyGLhG9SEVhvA4oU6Jwr44f46+z4/Kw9oe4zDJ6pPQp8PcSvNQIg1QCAcy 13 | 4ICXF+5qBTNZ5qaU7Cyz8oSgpGbIepTYOzEJOmc3Li9kEsBubULxWBjf/gOBzAzU 14 | RNps3cO4JFgZSAGzJWQTT7/emMkod0jb9WdqVA2BVMi7yge54kdVMxHEa5r3b97s 15 | zI5p58ii0I54JiCUP5lyfTwE/nKZHZnfm644oLIXf6MdW2r+6R8CAQOjgfwwgfkw 16 | HQYDVR0OBBYEFEhZAFY9JyxGrhGGBaR0GawJyowRMIHJBgNVHSMEgcEwgb6AFEhZ 17 | AFY9JyxGrhGGBaR0GawJyowRoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UE 18 | CBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMH 19 | QW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAG 20 | CSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJAJNurL4H8gHfMAwGA1Ud 21 | EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAHqvlozrUMRBBVEY0NqrrwFbinZa 22 | J6cVosK0TyIUFf/azgMJWr+kLfcHCHJsIGnlw27drgQAvilFLAhLwn62oX6snb4Y 23 | LCBOsVMR9FXYJLZW2+TcIkCRLXWG/oiVHQGo/rWuWkJgU134NDEFJCJGjDbiLCpe 24 | +ZTWHdcwauTJ9pUbo8EvHRkU3cYfGmLaLfgn9gP+pWA7LFQNvXwBnDa6sppCccEX 25 | 31I828XzgXpJ4O+mDL1/dBd+ek8ZPUP0IgdyZm5MTYPhvVqGCHzzTy3sIeJFymwr 26 | sBbmg2OAUNLEMO6nwmocSdN2ClirfxqCzJOLSDE4QyS9BAH6EhY6UFcOaE0= 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /images/yingyongbao.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/xiaomi.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/360.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/oppo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/workflows/xapk.yml: -------------------------------------------------------------------------------- 1 | name: XAPK AzurLane JMBQ Build Work 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | region: 7 | description: 'Select Game Server' 8 | required: true 9 | type: choice 10 | options: 11 | - 'TW' 12 | - 'EN' 13 | - 'JP' 14 | - 'KR' 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | permissions: 19 | contents: write 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | 25 | - name: Setup Java JDK 26 | uses: actions/setup-java@v3.3.0 27 | with: 28 | java-version: "17" 29 | distribution: "adopt" 30 | 31 | - name: Set up Android SDK 32 | uses: android-actions/setup-android@v3 33 | 34 | - name: Install build-tools 35 | run: | 36 | yes | sdkmanager --install "build-tools;35.0.0" 37 | 38 | - name: Build APK 39 | run: | 40 | chmod +x merge_build.sh 41 | ./merge_build.sh "${{ github.event.inputs.region }}" 42 | 43 | - name: Release and Upload Assets 44 | uses: ncipollo/release-action@v1 45 | with: 46 | tag: "${{ env.JMBQ_VERSION }}-XAPK" 47 | name: "AzurLane JMBQ ${{ env.JMBQ_VERSION }} Build XAPK" 48 | artifacts: "${{ github.event.inputs.region }}-V.${{ env.VERSION }}.7z.*" 49 | allowUpdates: true 50 | body: | 51 |
52 | 中文安装说明 - 点击展开 53 | 1. 在下方列表中找到你需要的版本前缀,下载该服务器名称前缀的所有以 7z.00* 结尾的文件
54 | 2. 解压时只需要选择"XXX.7z.001"解压即可
55 | 3. 在解压后的文件夹中找到xapk文件并安装,安装方法请自行查询相关教程 56 |
57 | 58 |
59 | English Installation Instructions - Click to expand 60 | 1. Find the version prefix you need in the list below, and download all files ending with 7z.00* that have that server name prefix
61 | 2. When extracting, simply select the "XXX.7z.001" file to decompress
62 | 3. Find the XAPK file in the extracted folder and install it, please refer to the relevant tutorials for installation methods 63 |
64 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: APK AzurLane JMBQ Build Work 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | server_name: 6 | description: '区服名称-必须(必须是英文字母!例如 Bilibili,台服和外服请使用XAPK AzurLane JMBQ Build Work)' 7 | required: true 8 | type: string 9 | default: '' 10 | download_url: 11 | description: 'APK下载链接(只能填入直链,最好使用国外云盘的直链,例如Google云盘)' 12 | required: true 13 | type: string 14 | default: '' 15 | 16 | jobs: 17 | build: 18 | runs-on: ubuntu-latest 19 | permissions: 20 | contents: write 21 | 22 | steps: 23 | # 步骤1:检出仓库代码 24 | - name: Checkout code 25 | uses: actions/checkout@v4 26 | 27 | # 步骤2:设置 Java JDK 28 | - name: Setup Java JDK 29 | uses: actions/setup-java@v3.3.0 30 | with: 31 | java-version: "17" 32 | distribution: "adopt" 33 | 34 | # 步骤3:设置 Android SDK 35 | - name: Set up Android SDK 36 | uses: android-actions/setup-android@v3 37 | 38 | # 步骤4:安装 build-tools 39 | - name: Install build-tools 40 | run: | 41 | yes | sdkmanager --install "build-tools;35.0.0" 42 | 43 | # 步骤5:执行构建流程 44 | - name: Build APK 45 | run: | 46 | chmod +x merge_build.sh 47 | ./merge_build.sh "${{ github.event.inputs.server_name }}" "${{ github.event.inputs.download_url }}" 48 | 49 | # 步骤6:创建 Release 并上传所有分卷文件 50 | - name: Release and Upload Assets 51 | uses: ncipollo/release-action@v1 52 | with: 53 | tag: "${{ env.JMBQ_VERSION }}-APK" 54 | name: "AzurLane JMBQ ${{ env.JMBQ_VERSION }} Build APK" 55 | artifacts: "${{ github.event.inputs.server_name }}-V.${{ env.VERSION }}.7z.*" 56 | allowUpdates: true 57 | body: | 58 |
59 | 中文安装说明 - 点击展开 60 | 1. 在下方列表中找到你需要的版本前缀,下载该服务器名称前缀的所有以 7z.00* 结尾的文件
61 | 2. 解压时只需要选择"XXX.7z.001"解压即可
62 | 3. 在解压后的文件夹中找到apk文件并安装 63 |
64 | 65 |
66 | English Installation Instructions - Click to expand 67 | 1. Find the version prefix you need in the list below, and download all files ending with 7z.00* that have that server name prefix
68 | 2. When extracting, simply select the "XXX.7z.001" file to decompress
69 | 3. Find the APK file in the extracted folder and install it 70 |
71 | -------------------------------------------------------------------------------- /images/jiuyou.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/vivo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 访问计数 3 |

Azurlane Build

4 | 5 | ![forks](https://img.shields.io/github/forks/Chtholly344/Azurlane-Build.svg?style=flat&label=分支数) 6 | ![stars](https://img.shields.io/github/stars/Chtholly344/Azurlane-Build?style=flat&label=星标数) 7 | ![issues](https://img.shields.io/github/issues/Chtholly344/Azurlane-Build) 8 |

9 | 使用Github Workflow自动构建对应区服的APK/XAPK 10 |
11 |
12 | 发现问题?提交 13 | Issue 14 |

15 | 16 |
17 | 18 | --- 19 | 20 | ## ⚠️ 重要提示 21 | 本项目仅用于学习和研究,请在遵守相关法律法规的前提下使用本项目,若您违规使用使用本项目,那么所导致的一切后果将由您本人承担。 22 | 23 | - **风险警告**:使用mod可能涉及未知风险,如果您坚持使用,那么您将承担可能会造成的任何后果,包括但不限于您的游戏账号被封禁 24 | - **登录问题**:重新打包的APK签名与官方版本不同,可能导致第三方授权登录失败。请优先使用二维码或验证码登录。 25 | 26 | --- 27 | 28 | ## 各渠道服安装包官方下载地址 29 | 若您有一定的动手能力,请自行fork本仓库,手动运行工作流来获取对应的APK 30 | 31 | ⚠️⚠️⚠️ 注意!!! 32 | - **OPPO**和**4399**渠道服请自行下载安装包,上传网盘后获取直链下载 33 | 34 |
35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 | 45 | --- 46 | ## 项目目录 47 | ``` 48 | 49 | ├── 📁 .github 50 | │└── 📁 workflows 51 | │├── ⚙️ main.yml 52 | │└── ⚙️ xapk.yml 53 | ├──📁 key 54 | │├── 📄 testkey.pk8 55 | │└── 📄 testkey.x509.pem 56 | ├──⚙️ .gitignore 57 | ├──📝 README.md 58 | └──📄 merge_build.sh # 构建脚本 59 | 60 | ``` 61 | 62 | --- 63 | ## 预览 64 | ![Screenshot_2025-11-23-09-17-23-266_com bilibili azurlane](https://github.com/user-attachments/assets/3d57e120-2444-4a6d-8e0c-afb44f76a9b8) 65 | 66 | --- 67 | ## 🚧 已知问题 68 | 以下问题将不会被解决: 69 | 70 | - **韩服**:启动无响应,可能触发反作弊机制。 71 | - **华为服**:启动界面卡顿,疑似由于签名验证问题导致HMS Core初始化失败。 72 | 73 | --- 74 | 75 | ## 📚 致谢 76 | 77 | 1. [JMBQ/azurlane](https://github.com/JMBQ/azurlane) 78 | 2. [n0k0m3/PerseusCI](https://github.com/n0k0m3/PerseusCI) 79 | 80 | --- 81 | 82 | ## 📊 Star历史 83 | 84 | [![Star History Chart](https://starchart.cc/Chtholly344/Azurlane-Build.svg?variant=adaptive)](https://starchart.cc/Chtholly344/Azurlane-Build) 85 | -------------------------------------------------------------------------------- /merge_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 合并后的构建脚本,支持APK和XAPK格式 3 | # 公共变量参数 部分从Github Action中传入 4 | BUILD_TOOLS_DIR=$(find ${ANDROID_HOME}/build-tools -maxdepth 1 -type d | sort -V | tail -n 1) 5 | AAPT_PATH="${BUILD_TOOLS_DIR}/aapt" 6 | DOWNLOAD_DIR="." 7 | GAME_SERVER=$1 8 | APK_URL=$2 9 | BUILD_TYPE="APK" # 默认构建类型 10 | 11 | # 检查参数 12 | CHECK_PARAM() { 13 | if [ -z "${GAME_SERVER}" ]; then 14 | echo "服务器名称不能为空" 15 | exit 1 16 | fi 17 | 18 | if ! echo "${GAME_SERVER}" | grep -q "^[a-zA-Z0-9]*$"; then 19 | echo "服务器参数包含非英文数字字符,请重新输入" 20 | exit 1 21 | fi 22 | 23 | # 检测是否需要使用XAPK构建模式 24 | # 对于国际服务器(EN、JP、KR)和TW服务器,使用XAPK模式 25 | # 这些服务器通常通过apkeep直接下载,不需要APK_URL参数 26 | case "${GAME_SERVER}" in 27 | "TW" | "EN" | "JP" | "KR") 28 | BUILD_TYPE="XAPK" 29 | echo "检测到需要使用XAPK构建模式: ${GAME_SERVER}" 30 | ;; 31 | *) 32 | # 其他服务器需要提供APK下载链接 33 | if [ -z "${APK_URL}" ]; then 34 | echo "APK下载链接不能为空" 35 | exit 1 36 | fi 37 | BUILD_TYPE="APK" 38 | echo "使用标准APK构建模式: ${GAME_SERVER}" 39 | ;; 40 | esac 41 | } 42 | 43 | # 设置包名和文件名(XAPK模式使用) 44 | SET_BUNDLE_ID() { 45 | case "$GAME_SERVER" in 46 | "TW") 47 | GAME_BUNDLE_ID="com.hkmanjuu.azurlane.gp" 48 | ;; 49 | "EN") 50 | GAME_BUNDLE_ID="com.YoStarEN.AzurLane" 51 | ;; 52 | "JP") 53 | GAME_BUNDLE_ID="com.YoStarJP.AzurLane" 54 | ;; 55 | "KR") 56 | GAME_BUNDLE_ID="kr.txwy.and.blhx" 57 | ;; 58 | esac 59 | APK_FILENAME="${GAME_BUNDLE_ID}.apk" 60 | echo "已设置包名为: ${GAME_BUNDLE_ID}" 61 | } 62 | 63 | # 下载apkeep(XAPK模式使用) 64 | DOWNLOAD_APKEEP() { 65 | local OWNER="EFForg" 66 | local REPO="apkeep" 67 | local LIB_PLATFORM="x86_64-unknown-linux-gnu" 68 | local FILENAME="apkeep" 69 | 70 | echo "正在下载apkeep工具..." 71 | local API_RESPONSE=$(curl -s "https://api.github.com/repos/${OWNER}/${REPO}/releases/latest") 72 | local DOWNLOAD_LINK=$(echo "${API_RESPONSE}" | jq -r ".assets[] | select(.name | contains(\"${LIB_PLATFORM}\")) | .browser_download_url" | head -n 1) 73 | if [ -z "${DOWNLOAD_LINK}" ] || [ "${DOWNLOAD_LINK}" == "null" ]; then 74 | echo "无法找到Apkeep下载链接" 75 | exit 1 76 | fi 77 | 78 | curl -L -o "${DOWNLOAD_DIR}/${FILENAME}" "${DOWNLOAD_LINK}" 79 | if [ $? -eq 0 ]; then 80 | echo "Apkeep下载成功!文件保存至:${DOWNLOAD_DIR}/${FILENAME}" 81 | chmod +x "${DOWNLOAD_DIR}/${FILENAME}" 82 | else 83 | echo "Apkeep下载失败,请重试" 84 | exit 1 85 | fi 86 | } 87 | 88 | # 下载ApkTool 89 | DOWNLOAD_APKTOOL() { 90 | local OWNER="iBotPeaches" 91 | local REPO="Apktool" 92 | local FILENAME="apktool.jar" 93 | 94 | echo "正在下载Apktool..." 95 | local API_RESPONSE=$(curl -s "https://api.github.com/repos/${OWNER}/${REPO}/releases/latest") 96 | local DOWNLOAD_LINK=$(echo "${API_RESPONSE}" | jq -r '.assets[] | select(.name | endswith(".jar")) | .browser_download_url' | head -n 1) 97 | if [ -z "${DOWNLOAD_LINK}" ] || [ "${DOWNLOAD_LINK}" == "null" ]; then 98 | echo "无法找到Apktool下载链接" 99 | exit 1 100 | fi 101 | 102 | curl -L -o "${DOWNLOAD_DIR}/${FILENAME}" "${DOWNLOAD_LINK}" 103 | if [ $? -eq 0 ]; then 104 | echo "Apktool下载成功!文件保存至:${DOWNLOAD_DIR}/${FILENAME}" 105 | else 106 | echo "Apktool下载失败,请重试" 107 | exit 1 108 | fi 109 | } 110 | 111 | # 下载 Mod Patch 文件并解压 112 | DOWNLOAD_MOD_MENU() { 113 | local OWNER="JMBQ" 114 | local REPO="azurlane" 115 | local FILENAME="MOD_MENU.rar" 116 | 117 | echo "正在下载MOD补丁..." 118 | local API_RESPONSE=$(curl -s "https://api.github.com/repos/${OWNER}/${REPO}/releases/latest") 119 | local JMBQ_VERSION=$(echo "${API_RESPONSE}" | jq -r '.tag_name') 120 | # 修改:查找name中含有.rar的文件,而不是直接使用第一个assets 121 | local DOWNLOAD_LINK=$(echo "${API_RESPONSE}" | jq -r '.assets[] | select(.name | contains(".rar")) | .browser_download_url' | head -n 1) 122 | 123 | if [ -z "${DOWNLOAD_LINK}" ] || [ "${DOWNLOAD_LINK}" == "null" ]; then 124 | # 修改:查找name中含有.zip的文件,避免后缀不一致导致的无法获取链接 125 | local FILENAME="MOD_MENU.zip" 126 | local DOWNLOAD_LINK=$(echo "${API_RESPONSE}" | jq -r '.assets[] | select(.name | contains(".zip")) | .browser_download_url' | head -n 1) 127 | if [ -z "${DOWNLOAD_LINK}" ] || [ "${DOWNLOAD_LINK}" == "null" ]; then 128 | echo "无法获取MOD Patch文件下载链接" 129 | exit 1 130 | fi 131 | fi 132 | 133 | curl -L -o "${DOWNLOAD_DIR}/${FILENAME}" "${DOWNLOAD_LINK}" 134 | if [ $? -eq 0 ]; then 135 | echo "补丁下载成功!文件保存至:${DOWNLOAD_DIR}/${FILENAME}" 136 | else 137 | echo "补丁下载失败,请重试" 138 | exit 1 139 | fi 140 | 141 | if command -v 7z &> /dev/null; then 142 | 7z x -y "${DOWNLOAD_DIR}/${FILENAME}" -o"${DOWNLOAD_DIR}/JMBQ" 143 | else 144 | echo "错误: 未找到7z工具,无法解压!" 145 | exit 1 146 | fi 147 | 148 | if [ $? -ne 0 ]; then 149 | echo "错误: 解压 ${FILENAME} 失败!" 150 | exit 1 151 | fi 152 | echo "JMBQ目录内容:" 153 | ls -la "${DOWNLOAD_DIR}/JMBQ" 2>/dev/null || echo "无法列出目录内容" 154 | 155 | echo "JMBQ_VERSION=${JMBQ_VERSION}" >> "${GITHUB_ENV}" 156 | } 157 | 158 | # 下载APK(通用函数,根据构建类型执行不同的下载逻辑) 159 | DOWNLOAD_APK() { 160 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 161 | # XAPK模式下载逻辑 162 | echo "正在使用apkeep下载XAPK..." 163 | "${DOWNLOAD_DIR}/apkeep" -a "${GAME_BUNDLE_ID}" "${DOWNLOAD_DIR}/" 164 | if [ $? -ne 0 ]; then 165 | echo "XAPK 下载失败!" 166 | exit 1 167 | fi 168 | echo "XAPK [${GAME_BUNDLE_ID}.xapk] 下载成功!" 169 | echo "正在从 XAPK 中提取文件..." 170 | unzip -o "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.xapk" -d "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}" 171 | if [ $? -ne 0 ]; then 172 | echo "错误: 解压失败!" 173 | exit 1 174 | fi 175 | mv "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}/${GAME_BUNDLE_ID}.apk" "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" 176 | else 177 | # 普通APK模式下载逻辑 178 | APK_FILENAME="${GAME_SERVER}.apk" 179 | echo "正在下载APK..." 180 | curl -L -o "${DOWNLOAD_DIR}/${APK_FILENAME}" "${APK_URL}" 181 | if [ $? -ne 0 ]; then 182 | echo "APK下载失败" 183 | exit 1 184 | fi 185 | echo "APK [${APK_FILENAME}] 下载完成" 186 | fi 187 | } 188 | 189 | # 删除原始XAPK(XAPK模式使用) 190 | DELETE_ORGINAL_XAPK() { 191 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 192 | echo "删除原始XAPK文件..." 193 | rm -rf "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.xapk" 194 | fi 195 | } 196 | 197 | # 验证APK 198 | VERIFY_APK() { 199 | local APK_TO_VERIFY 200 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 201 | APK_TO_VERIFY="${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" 202 | else 203 | APK_TO_VERIFY="${DOWNLOAD_DIR}/${GAME_SERVER}.apk" 204 | fi 205 | 206 | echo "正在验证APK: ${APK_TO_VERIFY}" 207 | [ ! -f "${APK_TO_VERIFY}" ] && { echo "APK文件未找到"; exit 1; } 208 | 209 | local FILE_SIZE=$(stat -f%z "${APK_TO_VERIFY}" 2>/dev/null || stat -c%s "${APK_TO_VERIFY}" 2>/dev/null) 210 | [ "${FILE_SIZE}" -lt 1024 ] && { echo "APK文件大小异常"; exit 1; } 211 | unzip -t "${APK_TO_VERIFY}" >/dev/null 2>&1 || { echo "APK文件损坏"; exit 1; } 212 | echo "APK验证通过" 213 | } 214 | 215 | # APK 解包 216 | DECODE_APK() { 217 | local APK_TO_DECODE 218 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 219 | APK_TO_DECODE="${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" 220 | else 221 | APK_TO_DECODE="${DOWNLOAD_DIR}/${GAME_SERVER}.apk" 222 | fi 223 | 224 | echo "APK反编译: ${APK_TO_DECODE}" 225 | java -jar "${DOWNLOAD_DIR}/apktool.jar" d -f "${APK_TO_DECODE}" -o "${DOWNLOAD_DIR}/DECODE_Output" 226 | if [ $? -ne 0 ]; then 227 | echo "错误: APK 反编译失败!" 228 | exit 1 229 | fi 230 | echo "反编译完成。" 231 | } 232 | 233 | # 删除源APK 234 | DELETE_ORGINAL_APK() { 235 | local APK_TO_DELETE 236 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 237 | APK_TO_DELETE="${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" 238 | else 239 | APK_TO_DELETE="${DOWNLOAD_DIR}/${GAME_SERVER}.apk" 240 | fi 241 | 242 | echo "删除原始APK文件..." 243 | rm -rf "${APK_TO_DELETE}" 244 | } 245 | 246 | # 合入MOD 247 | PATCH_APK() { 248 | echo "正在合入MOD补丁..." 249 | cp -r "${DOWNLOAD_DIR}/JMBQ/assets/." "${DOWNLOAD_DIR}/DECODE_Output/assets/" 250 | if [ $? -ne 0 ]; then 251 | echo "错误: 复制资源文件失败!" 252 | exit 1 253 | fi 254 | echo "复制资源文件完成" 255 | 256 | local MAX_CLASS_NUM=$(find "${DOWNLOAD_DIR}/DECODE_Output/" -maxdepth 1 -type d -name "smali_classes*" 2>/dev/null | sed 's/.*smali_classes//' | sort -n | tail -1) 257 | MAX_CLASS_NUM=${MAX_CLASS_NUM:-3} 258 | local NEW_CLASS_NUM=$((MAX_CLASS_NUM + 1)) 259 | local NEW_SMALI_DIR="smali_classes${NEW_CLASS_NUM}" 260 | 261 | # 移除maxdepth限制,确保能找到所有smali_classes目录 262 | local SRC_DIR=$(find "${DOWNLOAD_DIR}/JMBQ" -type d -name "smali_classes*" 2>/dev/null | head -1) 263 | 264 | if [ -z "${SRC_DIR}" ]; then 265 | # 添加详细的错误信息,显示JMBQ目录结构 266 | echo "错误: MOD 补丁目录中未找到 smali_classes 目录!" 267 | echo "JMBQ目录内容:" 268 | ls -la "${DOWNLOAD_DIR}/JMBQ" 2>/dev/null || echo "无法列出目录内容" 269 | exit 1 270 | fi 271 | 272 | echo "找到MOD补丁目录: ${SRC_DIR}" 273 | cp -r "${SRC_DIR}" "${DOWNLOAD_DIR}/DECODE_Output/${NEW_SMALI_DIR}" || { 274 | echo "错误: 复制 smali 文件失败!" 275 | exit 1 276 | } 277 | echo "smali文件复制完成" 278 | 279 | local SMALI_FILE=$(find "${DOWNLOAD_DIR}/DECODE_Output" -type f -name "UnityPlayerActivity.smali") 280 | if [ -z "${SMALI_FILE}" ]; then 281 | echo "错误: UnityPlayerActivity.smali 文件未找到!" 282 | exit 1 283 | fi 284 | echo "已找到 UnityPlayerActivity.smali 文件,路径为: ${SMALI_FILE}" 285 | 286 | local LINE_NUM=$(grep -n ".method public constructor ()V" "${SMALI_FILE}" | cut -d: -f1) 287 | [ -z "${LINE_NUM}" ] && { 288 | echo "未找到构造函数" 289 | exit 1 290 | } 291 | 292 | echo "正在修改 ${SMALI_FILE} 文件..." 293 | sed -i -e "/\.method public constructor ()V/,/\.end method/{" \ 294 | -e "/\.locals 0/a\ invoke-static {}, Lcom/android/support/Main;->Start()V" \ 295 | -e "}" "${SMALI_FILE}" || { 296 | echo "错误:添加smali代码失败,请检查文件路径、权限或文件内容格式。" 297 | exit 1 298 | } 299 | echo "smali代码添加成功!" 300 | 301 | echo "正在修改 AndroidManifest.xml 文件..." 302 | local MANIFEST_FILE="${DOWNLOAD_DIR}/DECODE_Output/AndroidManifest.xml" 303 | sed -i 's## \n \n #' "${MANIFEST_FILE}" || { 304 | echo "错误:修改 AndroidManifest.xml 文件失败,请检查文件路径、权限或文件内容格式。" 305 | exit 1 306 | } 307 | echo "修改成功!" 308 | echo "补丁完成。" 309 | } 310 | 311 | # 打包APK 312 | BUILD_APK() { 313 | local OUTPUT_APK 314 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 315 | OUTPUT_APK="${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" 316 | else 317 | OUTPUT_APK="${DOWNLOAD_DIR}/${GAME_SERVER}.apk" 318 | fi 319 | 320 | echo "正在重新构建已打补丁的 APK 文件: ${OUTPUT_APK}" 321 | java -jar "${DOWNLOAD_DIR}/apktool.jar" b -f "${DOWNLOAD_DIR}/DECODE_Output" -o "${OUTPUT_APK}" 322 | if [ $? -ne 0 ]; then 323 | echo "错误: APK 构建失败!" 324 | exit 1 325 | fi 326 | echo "APK 构建成功" 327 | } 328 | 329 | # 优化并签名APK 330 | OPTIMIZE_AND_SIGN_APK() { 331 | export PATH=${PATH}:${BUILD_TOOLS_DIR} 332 | local KEY_DIR="${DOWNLOAD_DIR}/key/" 333 | local PRIVATE_KEY="${KEY_DIR}testkey.pk8" 334 | local CERTIFICATE="${KEY_DIR}testkey.x509.pem" 335 | local INPUT_APK 336 | local UNSIGNED_APK 337 | local FINAL_APK 338 | 339 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 340 | INPUT_APK="${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" 341 | UNSIGNED_APK="${GAME_BUNDLE_ID}.unsigned.apk" 342 | else 343 | INPUT_APK="${DOWNLOAD_DIR}/${GAME_SERVER}.apk" 344 | UNSIGNED_APK="${GAME_SERVER}.unsigned.apk" 345 | fi 346 | 347 | local OUTPUT_APK="${DOWNLOAD_DIR}/${UNSIGNED_APK}" 348 | local FINAL_APK="${INPUT_APK}" 349 | 350 | if [ ! -f "${INPUT_APK}" ]; then 351 | echo "错误:找不到输入APK文件: ${INPUT_APK}" 352 | exit 1 353 | fi 354 | 355 | if [ ! -f "${PRIVATE_KEY}" ] || [ ! -f "${CERTIFICATE}" ]; then 356 | echo "错误:找不到签名密钥文件" 357 | echo "请确保以下文件存在:" 358 | echo " - ${PRIVATE_KEY}" 359 | echo " - ${CERTIFICATE}" 360 | exit 1 361 | else 362 | echo "已找到签名密钥:" 363 | echo " - ${PRIVATE_KEY}" 364 | echo " - ${CERTIFICATE}" 365 | fi 366 | 367 | echo "正在优化APK..." 368 | if zipalign -f 4 "${INPUT_APK}" "${OUTPUT_APK}"; then 369 | echo "优化成功" 370 | rm "${INPUT_APK}" 371 | 372 | echo "正在签名APK..." 373 | if apksigner sign --key "${PRIVATE_KEY}" --cert "${CERTIFICATE}" "${OUTPUT_APK}"; then 374 | echo "签名成功" 375 | mv "${OUTPUT_APK}" "${FINAL_APK}" 376 | else 377 | echo "签名失败" 378 | exit 1 379 | fi 380 | else 381 | echo "优化失败" 382 | exit 1 383 | fi 384 | } 385 | 386 | # 获取并传回游戏版本 387 | GET_GAME_VERSION() { 388 | local APK_TO_CHECK 389 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 390 | APK_TO_CHECK="${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" 391 | else 392 | APK_TO_CHECK="${DOWNLOAD_DIR}/${GAME_SERVER}.apk" 393 | fi 394 | 395 | if [ -f "${APK_TO_CHECK}" ]; then 396 | if [ -f "${AAPT_PATH}" ]; then 397 | GAME_VERSION=$("${AAPT_PATH}" dump badging "${APK_TO_CHECK}" | grep "versionName" | sed "s/.*versionName='\([^']*\)'.*/\1/" | head -1) 398 | if [ -z "${GAME_VERSION}" ] || [ "${GAME_VERSION}" = "''" ]; then 399 | GAME_VERSION="未知" 400 | echo "警告:无法从APK提取版本信息" 401 | fi 402 | else 403 | echo "错误:找不到aapt工具: ${AAPT_PATH}" 404 | fi 405 | else 406 | echo "错误:APK文件不存在: ${APK_TO_CHECK}" 407 | fi 408 | echo "VERSION=${GAME_VERSION}" >> "${GITHUB_ENV}" 409 | echo "游戏版本: ${GAME_VERSION}" 410 | } 411 | 412 | # 重命名APK(APK模式使用) 413 | RENAME_APK() { 414 | if [ "${BUILD_TYPE}" = "APK" ] && [ -f "${DOWNLOAD_DIR}/${GAME_SERVER}.apk" ]; then 415 | if [ -f "${AAPT_PATH}" ]; then 416 | PACKAGE_NAME=$("${AAPT_PATH}" dump badging "${DOWNLOAD_DIR}/${GAME_SERVER}.apk" | grep "package: name=" | cut -d"'" -f2 | head -1) 417 | if [ -z "${PACKAGE_NAME}" ] || [ "${PACKAGE_NAME}" = "''" ]; then 418 | PACKAGE_NAME="${GAME_SERVER}" 419 | echo "警告:无法从APK提取包名,使用服务器名称作为包名" 420 | fi 421 | mv "${DOWNLOAD_DIR}/${GAME_SERVER}.apk" "${DOWNLOAD_DIR}/${PACKAGE_NAME}.apk" 422 | echo "重命名成功 [${PACKAGE_NAME}.apk]" 423 | else 424 | echo "错误:找不到aapt工具: ${AAPT_PATH}" 425 | fi 426 | fi 427 | } 428 | 429 | # 移动修改后的APK到源目录并重新打包XAPK(XAPK模式使用) 430 | REPACK_XAPK() { 431 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 432 | echo "正在重新打包XAPK..." 433 | mkdir -p "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}" 434 | mv -f "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.apk" "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}/${GAME_BUNDLE_ID}.apk" 435 | cd "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}" && zip -r "${GAME_BUNDLE_ID}.xapk" * 436 | cd - > /dev/null 437 | mv "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}/${GAME_BUNDLE_ID}.xapk" "${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.xapk" 438 | echo "XAPK重新打包完成" 439 | fi 440 | } 441 | 442 | # 生成7z分卷压缩包 443 | CREATE_SPLIT_ARCHIVES() { 444 | local FINAL_FILE 445 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 446 | FINAL_FILE="${DOWNLOAD_DIR}/${GAME_BUNDLE_ID}.xapk" 447 | else 448 | if [ -f "${DOWNLOAD_DIR}/${PACKAGE_NAME}.apk" ]; then 449 | FINAL_FILE="${DOWNLOAD_DIR}/${PACKAGE_NAME}.apk" 450 | else 451 | FINAL_FILE="${DOWNLOAD_DIR}/${GAME_SERVER}.apk" 452 | fi 453 | fi 454 | 455 | if [ ! -f "${FINAL_FILE}" ]; then 456 | echo "错误: 最终文件未找到: ${FINAL_FILE}" 457 | exit 1 458 | fi 459 | echo "正在压缩 ${FINAL_FILE}" 460 | 7z a -v800M "${GAME_SERVER}-V.${GAME_VERSION}.7z" "${FINAL_FILE}" || { 461 | echo "错误: 7z 压缩失败!" 462 | exit 1 463 | } 464 | echo "分卷压缩完成: ${GAME_SERVER}-V.${GAME_VERSION}.7z" 465 | } 466 | 467 | # 打印Logo 468 | PRINT_LOGO() { 469 | cat << "EOF" 470 | 471 | ________ ________ ___ ___ ________ ___ ________ ________ _______ ___ _____ ______ ________ ________ 472 | |\ __ \|\_____ \|\ \|\ \|\ __ \|\ \ |\ __ \|\ ___ \|\ ___ \ |\ \|\ _ \ _ \|\ __ \|\ __ \ 473 | \ \ \|\ \\___/ /\ \ \\ \ \ \|\ \ \ \ \ \ \|\ \ \ \\ \ \ \ __/| \ \ \ \ \\__\ \ \ \ \|\ /\ \ \|\ \ 474 | \ \ __ \ / / /\ \ \\ \ \ _ _\ \ \ \ \ __ \ \ \\ \ \ \ \_|/__ __ \ \ \ \ \\|__| \ \ \ __ \ \ \\ \\ 475 | \ \ \ \ \ / /_/__\ \ \\ \ \ \\ \\ \ \____\ \ \ \ \ \ \\ \ \ \ \_|\ \ |\ \\\_\ \ \ \ \ \ \ \ \|\ \ \ \\ \\ 476 | \ \__\ \__\\________\ \_______\ \__\\ _\\ \_______\ \__\ \__\ \__\\ \__\ \_______\ \ \________\ \__\ \ \__\ \_______\ \_____ \ 477 | \|__|\|__\|\|_______|\|_______|\|__|\|__\|_______|\|__|\|__|\|__| \|__\|_______| \|________|\|__| \|__\|_______|\|___| \__\ 478 | \|__| 479 | 480 | 481 | EOF 482 | } 483 | 484 | # 主执行函数 485 | main() { 486 | PRINT_LOGO 487 | CHECK_PARAM 488 | 489 | # 根据构建类型执行不同的流程 490 | if [ "${BUILD_TYPE}" = "XAPK" ]; then 491 | # XAPK构建流程 492 | SET_BUNDLE_ID 493 | DOWNLOAD_APKEEP 494 | DOWNLOAD_APKTOOL 495 | DOWNLOAD_MOD_MENU 496 | DOWNLOAD_APK 497 | DELETE_ORGINAL_XAPK 498 | VERIFY_APK 499 | DECODE_APK 500 | DELETE_ORGINAL_APK 501 | PATCH_APK 502 | BUILD_APK 503 | OPTIMIZE_AND_SIGN_APK 504 | GET_GAME_VERSION 505 | REPACK_XAPK 506 | else 507 | # APK构建流程 508 | DOWNLOAD_APKTOOL 509 | DOWNLOAD_MOD_MENU 510 | DOWNLOAD_APK 511 | VERIFY_APK 512 | DECODE_APK 513 | DELETE_ORGINAL_APK 514 | PATCH_APK 515 | BUILD_APK 516 | OPTIMIZE_AND_SIGN_APK 517 | GET_GAME_VERSION 518 | RENAME_APK 519 | fi 520 | 521 | # 共同的后续步骤 522 | CREATE_SPLIT_ARCHIVES 523 | echo "构建完成!构建类型: ${BUILD_TYPE}" 524 | } 525 | 526 | # 执行主函数 527 | main 528 | --------------------------------------------------------------------------------