diff --git a/editor/export_template_manager.cpp b/editor/export_template_manager.cpp index 1447a143d40..536cfaa1dd2 100644 --- a/editor/export_template_manager.cpp +++ b/editor/export_template_manager.cpp @@ -542,7 +542,6 @@ void ExportTemplateManager::_notification(int p_what) { template_list_state->set_text(status); if (errored) { set_process(false); - ; } } @@ -555,25 +554,33 @@ void ExportTemplateManager::_notification(int p_what) { bool ExportTemplateManager::can_install_android_template() { - return FileAccess::exists(EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG).plus_file("android_source.zip")); + const String templates_dir = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + return FileAccess::exists(templates_dir.plus_file("android_source.zip")) && + FileAccess::exists(templates_dir.plus_file("android_release.apk")) && + FileAccess::exists(templates_dir.plus_file("android_debug.apk")); } Error ExportTemplateManager::install_android_template() { + // To support custom Android builds, we install various things to the project's res://android folder. + // First is the Java source code and buildsystem from android_source.zip. + // Then we extract the Godot Android libraries from pre-build android_release.apk + // and android_debug.apk, to place them in the libs folder. + DirAccessRef da = DirAccess::open("res://"); ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); - //make android dir (if it does not exist) + // Make res://android dir (if it does not exist). da->make_dir("android"); { - //add an empty .gdignore file to avoid scan + // Add an empty .gdignore file to avoid scan. FileAccessRef f = FileAccess::open("res://android/.gdignore", FileAccess::WRITE); ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); f->store_line(""); f->close(); } { - //add version, to ensure building won't work if template and Godot version don't match + // Add version, to ensure building won't work if template and Godot version don't match. FileAccessRef f = FileAccess::open("res://android/.build_version", FileAccess::WRITE); ERR_FAIL_COND_V(!f, ERR_CANT_CREATE); f->store_line(VERSION_FULL_CONFIG); @@ -583,7 +590,10 @@ Error ExportTemplateManager::install_android_template() { Error err = da->make_dir_recursive("android/build"); ERR_FAIL_COND_V(err != OK, err); - String source_zip = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG).plus_file("android_source.zip"); + // Uncompress source template. + + const String &templates_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + const String &source_zip = templates_path.plus_file("android_source.zip"); ERR_FAIL_COND_V(!FileAccess::exists(source_zip), ERR_CANT_OPEN); FileAccess *src_f = NULL; @@ -593,37 +603,33 @@ Error ExportTemplateManager::install_android_template() { ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android sources not in ZIP format."); int ret = unzGoToFirstFile(pkg); - int total_files = 0; - //count files + // Count files to unzip. while (ret == UNZ_OK) { total_files++; ret = unzGoToNextFile(pkg); } - ret = unzGoToFirstFile(pkg); - //decompress files - ProgressDialog::get_singleton()->add_task("uncompress", TTR("Uncompressing Android Build Sources"), total_files); + + ProgressDialog::get_singleton()->add_task("uncompress_src", TTR("Uncompressing Android Build Sources"), total_files); Set dirs_tested; - int idx = 0; while (ret == UNZ_OK) { - //get filename + // Get file path. unz_file_info info; - char fname[16384]; - ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0); + char fpath[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fpath, 16384, NULL, 0, NULL, 0); - String name = fname; + String path = fpath; + String base_dir = path.get_base_dir(); - String base_dir = name.get_base_dir(); - - if (!name.ends_with("/")) { + if (!path.ends_with("/")) { Vector data; data.resize(info.uncompressed_size); - //read + // Read. unzOpenCurrentFile(pkg); unzReadCurrentFile(pkg, data.ptrw(), data.size()); unzCloseCurrentFile(pkg); @@ -633,7 +639,7 @@ Error ExportTemplateManager::install_android_template() { dirs_tested.insert(base_dir); } - String to_write = String("res://android/build").plus_file(name); + String to_write = String("res://android/build").plus_file(path); FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE); if (f) { f->store_buffer(data.ptr(), data.size()); @@ -646,13 +652,96 @@ Error ExportTemplateManager::install_android_template() { } } - ProgressDialog::get_singleton()->task_step("uncompress", name, idx); + ProgressDialog::get_singleton()->task_step("uncompress_src", path, idx); idx++; ret = unzGoToNextFile(pkg); } - ProgressDialog::get_singleton()->end_task("uncompress"); + ProgressDialog::get_singleton()->end_task("uncompress_src"); + unzClose(pkg); + + // Extract libs from pre-built APKs. + err = _extract_libs_from_apk("release"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't extract Android libs from android_release.apk."); + err = _extract_libs_from_apk("debug"); + ERR_FAIL_COND_V_MSG(err != OK, err, "Can't extract Android libs from android_debug.apk."); + + return OK; +} + +Error ExportTemplateManager::_extract_libs_from_apk(const String &p_target_name) { + + const String &templates_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(VERSION_FULL_CONFIG); + const String &apk_file = templates_path.plus_file("android_" + p_target_name + ".apk"); + ERR_FAIL_COND_V(!FileAccess::exists(apk_file), ERR_CANT_OPEN); + + FileAccess *src_f = NULL; + zlib_filefunc_def io = zipio_create_io_from_file(&src_f); + + unzFile pkg = unzOpen2(apk_file.utf8().get_data(), &io); + ERR_FAIL_COND_V_MSG(!pkg, ERR_CANT_OPEN, "Android APK can't be extracted."); + + DirAccessRef da = DirAccess::open("res://"); + ERR_FAIL_COND_V(!da, ERR_CANT_CREATE); + + // 8 steps because 4 arches, 2 libs per arch. + ProgressDialog::get_singleton()->add_task("extract_libs_from_apk", TTR("Extracting Android Libraries From APKs"), 8); + + int ret = unzGoToFirstFile(pkg); + Set dirs_tested; + int idx = 0; + while (ret == UNZ_OK) { + // Get file path. + unz_file_info info; + char fpath[16384]; + ret = unzGetCurrentFileInfo(pkg, &info, fpath, 16384, NULL, 0, NULL, 0); + + String path = fpath; + String base_dir = path.get_base_dir(); + String file = path.get_file(); + + if (!base_dir.begins_with("lib") || path.ends_with("/")) { + ret = unzGoToNextFile(pkg); + continue; + } + + Vector data; + data.resize(info.uncompressed_size); + + // Read. + unzOpenCurrentFile(pkg); + unzReadCurrentFile(pkg, data.ptrw(), data.size()); + unzCloseCurrentFile(pkg); + + // We have a "lib" folder in the APK, but it should be "libs/{release,debug}" in the source dir. + String target_base_dir = base_dir.replace_first("lib", String("libs").plus_file(p_target_name)); + + if (!dirs_tested.has(base_dir)) { + da->make_dir_recursive(String("android/build").plus_file(target_base_dir)); + dirs_tested.insert(base_dir); + } + + String to_write = String("res://android/build").plus_file(target_base_dir.plus_file(path.get_file())); + FileAccess *f = FileAccess::open(to_write, FileAccess::WRITE); + if (f) { + f->store_buffer(data.ptr(), data.size()); + memdelete(f); +#ifndef WINDOWS_ENABLED + // We can't retrieve Unix permissions from the APK it seems, so simply set 0755 as should be. + FileAccess::set_unix_permissions(to_write, 0755); +#endif + } else { + ERR_PRINTS("Can't uncompress file: " + to_write); + } + + ProgressDialog::get_singleton()->task_step("extract_libs_from_apk", path, idx); + + idx++; + ret = unzGoToNextFile(pkg); + } + + ProgressDialog::get_singleton()->end_task("extract_libs_from_apk"); unzClose(pkg); return OK; diff --git a/editor/export_template_manager.h b/editor/export_template_manager.h index ad3ab507b38..ecb8e85b218 100644 --- a/editor/export_template_manager.h +++ b/editor/export_template_manager.h @@ -72,6 +72,8 @@ class ExportTemplateManager : public ConfirmationDialog { virtual void ok_pressed(); bool _install_from_file(const String &p_file, bool p_use_progress = true); + Error _extract_libs_from_apk(const String &p_target_name); + void _http_download_mirror_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data); void _http_download_templates_completed(int p_status, int p_code, const PoolStringArray &headers, const PoolByteArray &p_data);