This repository has been archived by the owner on Nov 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
1,066 changed files
with
338,521 additions
and
22,851 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,159 +1,236 @@ | ||
#include <base/detect.h> | ||
|
||
#ifdef CONF_PLATFORM_ANDROID | ||
#include <sys/stat.h> | ||
#include <unistd.h> | ||
#include "android_main.h" | ||
|
||
#include <SDL.h> | ||
|
||
#include <base/hash.h> | ||
#include <base/log.h> | ||
#include <base/system.h> | ||
|
||
#include <engine/shared/linereader.h> | ||
|
||
#include <string> | ||
#include <vector> | ||
|
||
extern "C" __attribute__((visibility("default"))) void InitAndroid(); | ||
|
||
static int gs_AndroidStarted = false; | ||
|
||
void InitAndroid() | ||
static bool UnpackAsset(const char *pFilename) | ||
{ | ||
if(gs_AndroidStarted) | ||
char aAssetFilename[IO_MAX_PATH_LENGTH]; | ||
str_copy(aAssetFilename, "asset_integrity_files/"); | ||
str_append(aAssetFilename, pFilename); | ||
|
||
// This uses SDL_RWFromFile because it can read Android assets, | ||
// which are files stored in the app's APK file. All data files | ||
// are stored as assets and unpacked to the external storage. | ||
SDL_RWops *pAssetFile = SDL_RWFromFile(aAssetFilename, "rb"); | ||
if(!pAssetFile) | ||
{ | ||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DDNet", "The app was started, but not closed properly, this causes bugs. Please restart or manually delete this task.", SDL_GL_GetCurrentWindow()); | ||
std::exit(0); | ||
log_error("android", "Failed to open asset '%s' for reading", pFilename); | ||
return false; | ||
} | ||
|
||
gs_AndroidStarted = true; | ||
const long int FileLength = SDL_RWsize(pAssetFile); | ||
if(FileLength < 0) | ||
{ | ||
SDL_RWclose(pAssetFile); | ||
log_error("android", "Failed to determine length of asset '%s'", pFilename); | ||
return false; | ||
} | ||
|
||
// change current path to a writable directory | ||
const char *pPath = SDL_AndroidGetExternalStoragePath(); | ||
chdir(pPath); | ||
dbg_msg("client", "changed path to %s", pPath); | ||
char *pData = static_cast<char *>(malloc(FileLength)); | ||
const size_t ReadLength = SDL_RWread(pAssetFile, pData, 1, FileLength); | ||
SDL_RWclose(pAssetFile); | ||
|
||
// copy integrity files | ||
if(ReadLength != (size_t)FileLength) | ||
{ | ||
SDL_RWops *pF = SDL_RWFromFile("asset_integrity_files/integrity.txt", "rb"); | ||
if(!pF) | ||
{ | ||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "DDNet", "integrity.txt not found, consider reinstalling", SDL_GL_GetCurrentWindow()); | ||
std::exit(0); | ||
} | ||
free(pData); | ||
log_error("android", "Failed to read asset '%s' (read %" PRIzu ", wanted %ld)", pFilename, ReadLength, FileLength); | ||
return false; | ||
} | ||
|
||
IOHANDLE TargetFile = io_open(pFilename, IOFLAG_WRITE); | ||
if(!TargetFile) | ||
{ | ||
free(pData); | ||
log_error("android", "Failed to open '%s' for writing", pFilename); | ||
return false; | ||
} | ||
|
||
long int length; | ||
SDL_RWseek(pF, 0, RW_SEEK_END); | ||
length = SDL_RWtell(pF); | ||
SDL_RWseek(pF, 0, RW_SEEK_SET); | ||
const size_t WriteLength = io_write(TargetFile, pData, FileLength); | ||
io_close(TargetFile); | ||
free(pData); | ||
|
||
if(WriteLength != (size_t)FileLength) | ||
{ | ||
log_error("android", "Failed to write data to '%s' (wrote %" PRIzu ", wanted %ld)", pFilename, WriteLength, FileLength); | ||
return false; | ||
} | ||
|
||
char *pAl = (char *)malloc(length); | ||
SDL_RWread(pF, pAl, 1, length); | ||
return true; | ||
} | ||
|
||
SDL_RWclose(pF); | ||
constexpr const char *INTEGRITY_INDEX = "integrity.txt"; | ||
constexpr const char *INTEGRITY_INDEX_SAVE = "integrity_save.txt"; | ||
|
||
mkdir("data", 0755); | ||
// The first line of each integrity file contains the combined hash for all files, | ||
// if the hashes match then we assume that the unpacked data folder is up-to-date. | ||
static bool EqualIntegrityFiles(const char *pAssetFilename, const char *pStorageFilename) | ||
{ | ||
IOHANDLE StorageFile = io_open(pStorageFilename, IOFLAG_READ); | ||
if(!StorageFile) | ||
{ | ||
return false; | ||
} | ||
|
||
dbg_msg("integrity", "copying integrity.txt with size: %ld", length); | ||
char aStorageMainSha256[SHA256_MAXSTRSIZE]; | ||
const size_t StorageReadLength = io_read(StorageFile, aStorageMainSha256, sizeof(aStorageMainSha256) - 1); | ||
io_close(StorageFile); | ||
if(StorageReadLength != sizeof(aStorageMainSha256) - 1) | ||
{ | ||
return false; | ||
} | ||
aStorageMainSha256[sizeof(aStorageMainSha256) - 1] = '\0'; | ||
|
||
IOHANDLE pIO = io_open("integrity.txt", IOFLAG_WRITE); | ||
io_write(pIO, pAl, length); | ||
io_close(pIO); | ||
char aAssetFilename[IO_MAX_PATH_LENGTH]; | ||
str_copy(aAssetFilename, "asset_integrity_files/"); | ||
str_append(aAssetFilename, pAssetFilename); | ||
|
||
free(pAl); | ||
SDL_RWops *pAssetFile = SDL_RWFromFile(aAssetFilename, "rb"); | ||
if(!pAssetFile) | ||
{ | ||
return false; | ||
} | ||
|
||
IOHANDLE pIO = io_open("integrity.txt", IOFLAG_READ); | ||
CLineReader LineReader; | ||
LineReader.Init(pIO); | ||
const char *pReadLine = NULL; | ||
std::vector<std::string> vLines; | ||
while((pReadLine = LineReader.Get())) | ||
char aAssetMainSha256[SHA256_MAXSTRSIZE]; | ||
const size_t AssetReadLength = SDL_RWread(pAssetFile, aAssetMainSha256, 1, sizeof(aAssetMainSha256) - 1); | ||
SDL_RWclose(pAssetFile); | ||
if(AssetReadLength != sizeof(aAssetMainSha256) - 1) | ||
{ | ||
vLines.push_back(pReadLine); | ||
return false; | ||
} | ||
io_close(pIO); | ||
aAssetMainSha256[sizeof(aAssetMainSha256) - 1] = '\0'; | ||
|
||
return str_comp(aStorageMainSha256, aAssetMainSha256) == 0; | ||
} | ||
|
||
class CIntegrityFileLine | ||
{ | ||
public: | ||
char m_aFilename[IO_MAX_PATH_LENGTH]; | ||
SHA256_DIGEST m_Sha256; | ||
}; | ||
|
||
// first line is the whole hash | ||
std::string AllAsOne; | ||
for(size_t i = 1; i < vLines.size(); ++i) | ||
static std::vector<CIntegrityFileLine> ReadIntegrityFile(const char *pFilename) | ||
{ | ||
CLineReader LineReader; | ||
if(!LineReader.OpenFile(io_open(pFilename, IOFLAG_READ))) | ||
{ | ||
AllAsOne.append(vLines[i]); | ||
AllAsOne.append("\n"); | ||
return {}; | ||
} | ||
SHA256_DIGEST ShaAll; | ||
bool GotSHA = false; | ||
|
||
std::vector<CIntegrityFileLine> vLines; | ||
while(const char *pReadLine = LineReader.Get()) | ||
{ | ||
IOHANDLE pIOR = io_open("integrity_save.txt", IOFLAG_READ); | ||
if(pIOR != NULL) | ||
const char *pSpaceInLine = str_rchr(pReadLine, ' '); | ||
CIntegrityFileLine Line; | ||
char aSha256[SHA256_MAXSTRSIZE]; | ||
if(pSpaceInLine == nullptr) | ||
{ | ||
CLineReader LineReader; | ||
LineReader.Init(pIOR); | ||
const char *pLine = LineReader.Get(); | ||
if(pLine != NULL) | ||
if(!vLines.empty()) | ||
{ | ||
sha256_from_str(&ShaAll, pLine); | ||
GotSHA = true; | ||
// Only the first line is allowed to not contain a filename | ||
log_error("android", "Failed to parse line %" PRIzu " of '%s': line does not contain space", vLines.size() + 1, pFilename); | ||
return {}; | ||
} | ||
Line.m_aFilename[0] = '\0'; | ||
str_copy(aSha256, pReadLine); | ||
} | ||
else | ||
{ | ||
str_truncate(Line.m_aFilename, sizeof(Line.m_aFilename), pReadLine, pSpaceInLine - pReadLine); | ||
str_copy(aSha256, pSpaceInLine + 1); | ||
} | ||
if(sha256_from_str(&Line.m_Sha256, aSha256) != 0) | ||
{ | ||
log_error("android", "Failed to parse line %" PRIzu " of '%s': invalid SHA256 string", vLines.size() + 1, pFilename); | ||
return {}; | ||
} | ||
vLines.emplace_back(std::move(Line)); | ||
} | ||
|
||
SHA256_DIGEST ShaAllFile; | ||
sha256_from_str(&ShaAllFile, vLines[0].c_str()); | ||
return vLines; | ||
} | ||
|
||
// TODO: check files individually | ||
if(!GotSHA || ShaAllFile != ShaAll) | ||
const char *InitAndroid() | ||
{ | ||
// Change current working directory to our external storage location | ||
const char *pPath = SDL_AndroidGetExternalStoragePath(); | ||
if(pPath == nullptr) | ||
{ | ||
// then the files | ||
for(size_t i = 1; i < vLines.size(); ++i) | ||
{ | ||
std::string FileName, Hash; | ||
std::string::size_type n = 0; | ||
std::string::size_type c = 0; | ||
while((c = vLines[i].find(' ', n)) != std::string::npos) | ||
n = c + 1; | ||
FileName = vLines[i].substr(0, n - 1); | ||
Hash = vLines[i].substr(n + 1); | ||
|
||
std::string AssetFileName = std::string("asset_integrity_files/") + FileName; | ||
SDL_RWops *pF = SDL_RWFromFile(AssetFileName.c_str(), "rb"); | ||
|
||
dbg_msg("Integrity", "Copying from assets: %s", FileName.c_str()); | ||
|
||
std::string FileNamePath = FileName; | ||
std::string FileNamePathSub; | ||
c = 0; | ||
while((c = FileNamePath.find('/', c)) != std::string::npos) | ||
{ | ||
FileNamePathSub = FileNamePath.substr(0, c); | ||
fs_makedir(FileNamePathSub.c_str()); | ||
++c; | ||
} | ||
return "The external storage is not available."; | ||
} | ||
if(fs_chdir(pPath) != 0) | ||
{ | ||
return "Failed to change current directory to external storage."; | ||
} | ||
log_info("android", "Changed current directory to '%s'", pPath); | ||
|
||
if(fs_makedir("data") != 0 || fs_makedir("user") != 0) | ||
{ | ||
return "Failed to create 'data' and 'user' directories in external storage."; | ||
} | ||
|
||
long int length; | ||
SDL_RWseek(pF, 0, RW_SEEK_END); | ||
length = SDL_RWtell(pF); | ||
SDL_RWseek(pF, 0, RW_SEEK_SET); | ||
if(EqualIntegrityFiles(INTEGRITY_INDEX, INTEGRITY_INDEX_SAVE)) | ||
{ | ||
return nullptr; | ||
} | ||
|
||
if(!UnpackAsset(INTEGRITY_INDEX)) | ||
{ | ||
return "Failed to unpack the integrity index file. Consider reinstalling the app."; | ||
} | ||
|
||
char *pAl = (char *)malloc(length); | ||
SDL_RWread(pF, pAl, 1, length); | ||
std::vector<CIntegrityFileLine> vIntegrityLines = ReadIntegrityFile(INTEGRITY_INDEX); | ||
if(vIntegrityLines.empty()) | ||
{ | ||
return "Failed to load the integrity index file. Consider reinstalling the app."; | ||
} | ||
|
||
SDL_RWclose(pF); | ||
std::vector<CIntegrityFileLine> vIntegritySaveLines = ReadIntegrityFile(INTEGRITY_INDEX_SAVE); | ||
|
||
IOHANDLE pIO = io_open(FileName.c_str(), IOFLAG_WRITE); | ||
io_write(pIO, pAl, length); | ||
io_close(pIO); | ||
// The remaining lines of each integrity file list all assets and their hashes | ||
for(size_t i = 1; i < vIntegrityLines.size(); ++i) | ||
{ | ||
const CIntegrityFileLine &IntegrityLine = vIntegrityLines[i]; | ||
|
||
free(pAl); | ||
// Check if the asset is unchanged from the last unpacking | ||
const auto IntegritySaveLine = std::find_if(vIntegritySaveLines.begin(), vIntegritySaveLines.end(), [&](const CIntegrityFileLine &Line) { | ||
return str_comp(Line.m_aFilename, IntegrityLine.m_aFilename) == 0; | ||
}); | ||
if(IntegritySaveLine != vIntegritySaveLines.end() && IntegritySaveLine->m_Sha256 == IntegrityLine.m_Sha256) | ||
{ | ||
continue; | ||
} | ||
|
||
IOHANDLE pIOR = io_open("integrity_save.txt", IOFLAG_WRITE); | ||
if(pIOR != NULL) | ||
if(fs_makedir_rec_for(IntegrityLine.m_aFilename) != 0 || !UnpackAsset(IntegrityLine.m_aFilename)) | ||
{ | ||
char aFileSHA[SHA256_MAXSTRSIZE]; | ||
sha256_str(ShaAllFile, aFileSHA, sizeof(aFileSHA)); | ||
io_write(pIOR, aFileSHA, str_length(aFileSHA)); | ||
io_close(pIOR); | ||
return "Failed to unpack game assets, consider reinstalling the app."; | ||
} | ||
} | ||
|
||
// The integrity file will be unpacked every time when launching, | ||
// so we can simply rename it to update the saved integrity file. | ||
if((fs_is_file(INTEGRITY_INDEX_SAVE) && fs_remove(INTEGRITY_INDEX_SAVE) != 0) || fs_rename(INTEGRITY_INDEX, INTEGRITY_INDEX_SAVE) != 0) | ||
{ | ||
return "Failed to update the saved integrity index file."; | ||
} | ||
|
||
return nullptr; | ||
} | ||
|
||
#endif | ||
// See NativeMain.java | ||
constexpr uint32_t COMMAND_USER = 0x8000; | ||
constexpr uint32_t COMMAND_RESTART_APP = COMMAND_USER + 1; | ||
|
||
void RestartAndroidApp() | ||
{ | ||
SDL_AndroidSendMessage(COMMAND_RESTART_APP, 0); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
#ifndef ANDROID_ANDROID_MAIN_H | ||
#define ANDROID_ANDROID_MAIN_H | ||
|
||
#include <base/detect.h> | ||
#if !defined(CONF_PLATFORM_ANDROID) | ||
#error "This header should only be included when compiling for Android" | ||
#endif | ||
|
||
/** | ||
* Initializes the Android storage. Must be called on Android-systems | ||
* before using any of the I/O and storage functions. | ||
* | ||
* This will change the current working directory to the app specific external | ||
* storage location and unpack the assets from the APK file to the `data` folder. | ||
* The folder `user` is created in the external storage to store the user data. | ||
* | ||
* Failure must be handled by exiting the app. | ||
* | ||
* @return `nullptr` on success, error message on failure. | ||
*/ | ||
const char *InitAndroid(); | ||
|
||
/** | ||
* Sends an intent to the Android system to restart the app. | ||
* | ||
* This will restart the main activity in a new task. The current process | ||
* must immediately terminate after this function is called. | ||
*/ | ||
void RestartAndroidApp(); | ||
|
||
#endif // ANDROID_ANDROID_MAIN_H |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.