feat: modules moved and engine moved to submodule
This commit is contained in:
parent
dfb5e645cd
commit
c33d2130cc
5136 changed files with 225275 additions and 64485 deletions
|
|
@ -28,10 +28,7 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANDROID_EXPORT_H
|
||||
#define ANDROID_EXPORT_H
|
||||
#pragma once
|
||||
|
||||
void register_android_exporter_types();
|
||||
void register_android_exporter();
|
||||
|
||||
#endif // ANDROID_EXPORT_H
|
||||
|
|
|
|||
|
|
@ -30,11 +30,9 @@
|
|||
|
||||
#include "export_plugin.h"
|
||||
|
||||
#include "gradle_export_util.h"
|
||||
#include "logo_svg.gen.h"
|
||||
#include "run_icon_svg.gen.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/io/image_loader.h"
|
||||
|
|
@ -396,28 +394,27 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
|
|||
// its format is "[property]: [value]" so changed it as like build.prop
|
||||
String p = props[j];
|
||||
p = p.replace("]: ", "=");
|
||||
p = p.replace("[", "");
|
||||
p = p.replace("]", "");
|
||||
p = p.remove_chars("[]");
|
||||
|
||||
if (p.begins_with("ro.product.model=")) {
|
||||
device = p.get_slice("=", 1).strip_edges();
|
||||
device = p.get_slicec('=', 1).strip_edges();
|
||||
} else if (p.begins_with("ro.product.brand=")) {
|
||||
vendor = p.get_slice("=", 1).strip_edges().capitalize();
|
||||
vendor = p.get_slicec('=', 1).strip_edges().capitalize();
|
||||
} else if (p.begins_with("ro.build.display.id=")) {
|
||||
d.description += "Build: " + p.get_slice("=", 1).strip_edges() + "\n";
|
||||
d.description += "Build: " + p.get_slicec('=', 1).strip_edges() + "\n";
|
||||
} else if (p.begins_with("ro.build.version.release=")) {
|
||||
d.description += "Release: " + p.get_slice("=", 1).strip_edges() + "\n";
|
||||
d.description += "Release: " + p.get_slicec('=', 1).strip_edges() + "\n";
|
||||
} else if (p.begins_with("ro.build.version.sdk=")) {
|
||||
d.api_level = p.get_slice("=", 1).to_int();
|
||||
d.api_level = p.get_slicec('=', 1).to_int();
|
||||
} else if (p.begins_with("ro.product.cpu.abi=")) {
|
||||
d.architecture = p.get_slice("=", 1).strip_edges();
|
||||
d.architecture = p.get_slicec('=', 1).strip_edges();
|
||||
d.description += "CPU: " + d.architecture + "\n";
|
||||
} else if (p.begins_with("ro.product.manufacturer=")) {
|
||||
d.description += "Manufacturer: " + p.get_slice("=", 1).strip_edges() + "\n";
|
||||
d.description += "Manufacturer: " + p.get_slicec('=', 1).strip_edges() + "\n";
|
||||
} else if (p.begins_with("ro.board.platform=")) {
|
||||
d.description += "Chipset: " + p.get_slice("=", 1).strip_edges() + "\n";
|
||||
d.description += "Chipset: " + p.get_slicec('=', 1).strip_edges() + "\n";
|
||||
} else if (p.begins_with("ro.opengles.version=")) {
|
||||
uint32_t opengl = p.get_slice("=", 1).to_int();
|
||||
uint32_t opengl = p.get_slicec('=', 1).to_int();
|
||||
d.description += "OpenGL: " + itos(opengl >> 16) + "." + itos((opengl >> 8) & 0xFF) + "." + itos((opengl) & 0xFF) + "\n";
|
||||
}
|
||||
}
|
||||
|
|
@ -480,32 +477,32 @@ void EditorExportPlatformAndroid::_update_preset_status() {
|
|||
}
|
||||
#endif
|
||||
|
||||
String EditorExportPlatformAndroid::get_project_name(const String &p_name) const {
|
||||
String EditorExportPlatformAndroid::get_project_name(const Ref<EditorExportPreset> &p_preset, const String &p_name) const {
|
||||
String aname;
|
||||
if (!p_name.is_empty()) {
|
||||
aname = p_name;
|
||||
} else {
|
||||
aname = GLOBAL_GET("application/config/name");
|
||||
aname = get_project_setting(p_preset, "application/config/name");
|
||||
}
|
||||
|
||||
if (aname.is_empty()) {
|
||||
aname = VERSION_NAME;
|
||||
aname = GODOT_VERSION_NAME;
|
||||
}
|
||||
|
||||
return aname;
|
||||
}
|
||||
|
||||
String EditorExportPlatformAndroid::get_package_name(const String &p_package) const {
|
||||
String EditorExportPlatformAndroid::get_package_name(const Ref<EditorExportPreset> &p_preset, const String &p_package) const {
|
||||
String pname = p_package;
|
||||
String name = get_valid_basename();
|
||||
String name = get_valid_basename(p_preset);
|
||||
pname = pname.replace("$genname", name);
|
||||
return pname;
|
||||
}
|
||||
|
||||
// Returns the project name without invalid characters
|
||||
// or the "noname" string if all characters are invalid.
|
||||
String EditorExportPlatformAndroid::get_valid_basename() const {
|
||||
String basename = GLOBAL_GET("application/config/name");
|
||||
String EditorExportPlatformAndroid::get_valid_basename(const Ref<EditorExportPreset> &p_preset) const {
|
||||
String basename = get_project_setting(p_preset, "application/config/name");
|
||||
basename = basename.to_lower();
|
||||
|
||||
String name;
|
||||
|
|
@ -533,8 +530,8 @@ String EditorExportPlatformAndroid::get_assets_directory(const Ref<EditorExportP
|
|||
return gradle_build_directory.path_join(p_export_format == EXPORT_FORMAT_AAB ? AAB_ASSETS_DIRECTORY : APK_ASSETS_DIRECTORY);
|
||||
}
|
||||
|
||||
bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package, String *r_error) const {
|
||||
String pname = get_package_name(p_package);
|
||||
bool EditorExportPlatformAndroid::is_package_name_valid(const Ref<EditorExportPreset> &p_preset, const String &p_package, String *r_error) const {
|
||||
String pname = get_package_name(p_preset, p_package);
|
||||
|
||||
if (pname.length() == 0) {
|
||||
if (r_error) {
|
||||
|
|
@ -596,12 +593,12 @@ bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool EditorExportPlatformAndroid::is_project_name_valid() const {
|
||||
bool EditorExportPlatformAndroid::is_project_name_valid(const Ref<EditorExportPreset> &p_preset) const {
|
||||
// Get the original project name and convert to lowercase.
|
||||
String basename = GLOBAL_GET("application/config/name");
|
||||
String basename = get_project_setting(p_preset, "application/config/name");
|
||||
basename = basename.to_lower();
|
||||
// Check if there are invalid characters.
|
||||
if (basename != get_valid_basename()) {
|
||||
if (basename != get_valid_basename(p_preset)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -804,7 +801,12 @@ Error EditorExportPlatformAndroid::save_apk_so(void *p_userdata, const SharedObj
|
|||
|
||||
Error EditorExportPlatformAndroid::save_apk_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) {
|
||||
APKExportData *ed = static_cast<APKExportData *>(p_userdata);
|
||||
const String path = ResourceUID::ensure_path(p_path);
|
||||
|
||||
String path = p_path.simplify_path();
|
||||
if (path.begins_with("uid://")) {
|
||||
path = ResourceUID::uid_to_path(path).simplify_path();
|
||||
print_verbose(vformat(R"(UID referenced exported file name "%s" was replaced with "%s".)", p_path, path));
|
||||
}
|
||||
const String dst_path = path.replace_first("res://", "assets/");
|
||||
|
||||
store_in_apk(ed, dst_path, p_data, _should_compress_asset(path, p_data) ? Z_DEFLATED : 0);
|
||||
|
|
@ -855,9 +857,9 @@ bool EditorExportPlatformAndroid::_has_manage_external_storage_permission(const
|
|||
return p_permissions.has("android.permission.MANAGE_EXTERNAL_STORAGE");
|
||||
}
|
||||
|
||||
bool EditorExportPlatformAndroid::_uses_vulkan() {
|
||||
String rendering_method = GLOBAL_GET("rendering/renderer/rendering_method.mobile");
|
||||
String rendering_driver = GLOBAL_GET("rendering/rendering_device/driver.android");
|
||||
bool EditorExportPlatformAndroid::_uses_vulkan(const Ref<EditorExportPreset> &p_preset) const {
|
||||
String rendering_method = get_project_setting(p_preset, "rendering/renderer/rendering_method.mobile");
|
||||
String rendering_driver = get_project_setting(p_preset, "rendering/rendering_device/driver.android");
|
||||
return (rendering_method == "forward_plus" || rendering_method == "mobile") && rendering_driver == "vulkan";
|
||||
}
|
||||
|
||||
|
|
@ -939,7 +941,7 @@ void EditorExportPlatformAndroid::_create_editor_debug_keystore_if_needed() {
|
|||
print_verbose("Updated editor debug keystore to " + keystore_path);
|
||||
}
|
||||
|
||||
void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions) {
|
||||
void EditorExportPlatformAndroid::_get_manifest_info(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions, Vector<FeatureInfo> &r_features, Vector<MetadataInfo> &r_metadata) {
|
||||
const char **aperms = ANDROID_PERMS;
|
||||
while (*aperms) {
|
||||
bool enabled = p_preset->get("permissions/" + String(*aperms).to_lower());
|
||||
|
|
@ -960,6 +962,36 @@ void EditorExportPlatformAndroid::_get_permissions(const Ref<EditorExportPreset>
|
|||
r_permissions.push_back("android.permission.INTERNET");
|
||||
}
|
||||
}
|
||||
|
||||
if (_uses_vulkan(p_preset)) {
|
||||
// Require vulkan hardware level 1 support
|
||||
FeatureInfo vulkan_level = {
|
||||
"android.hardware.vulkan.level", // name
|
||||
false, // required
|
||||
"1" // version
|
||||
};
|
||||
r_features.append(vulkan_level);
|
||||
|
||||
// Require vulkan version 1.0
|
||||
FeatureInfo vulkan_version = {
|
||||
"android.hardware.vulkan.version", // name
|
||||
true, // required
|
||||
"0x400003" // version - Encoded value for api version 1.0
|
||||
};
|
||||
r_features.append(vulkan_version);
|
||||
}
|
||||
|
||||
MetadataInfo rendering_method_metadata = {
|
||||
"org.godotengine.rendering.method",
|
||||
p_preset->get_project_setting("rendering/renderer/rendering_method.mobile")
|
||||
};
|
||||
r_metadata.append(rendering_method_metadata);
|
||||
|
||||
MetadataInfo editor_version_metadata = {
|
||||
"org.godotengine.editor.version",
|
||||
String(GODOT_VERSION_FULL_CONFIG)
|
||||
};
|
||||
r_metadata.append(editor_version_metadata);
|
||||
}
|
||||
|
||||
void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) {
|
||||
|
|
@ -973,7 +1005,9 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
|
|||
manifest_text += _get_gles_tag();
|
||||
|
||||
Vector<String> perms;
|
||||
_get_permissions(p_preset, p_give_internet, perms);
|
||||
Vector<FeatureInfo> features;
|
||||
Vector<MetadataInfo> manifest_metadata;
|
||||
_get_manifest_info(p_preset, p_give_internet, perms, features, manifest_metadata);
|
||||
for (int i = 0; i < perms.size(); i++) {
|
||||
String permission = perms.get(i);
|
||||
if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || (permission == "android.permission.READ_EXTERNAL_STORAGE" && _has_manage_external_storage_permission(perms))) {
|
||||
|
|
@ -983,9 +1017,8 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
|
|||
}
|
||||
}
|
||||
|
||||
if (_uses_vulkan()) {
|
||||
manifest_text += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"false\" android:version=\"1\" />\n";
|
||||
manifest_text += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.version\" android:required=\"true\" android:version=\"0x400003\" />\n";
|
||||
for (int i = 0; i < features.size(); i++) {
|
||||
manifest_text += vformat(" <uses-feature tools:node=\"replace\" android:name=\"%s\" android:required=\"%s\" android:version=\"%s\" />\n", features[i].name, features[i].required, features[i].version);
|
||||
}
|
||||
|
||||
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
||||
|
|
@ -999,7 +1032,7 @@ void EditorExportPlatformAndroid::_write_tmp_manifest(const Ref<EditorExportPres
|
|||
}
|
||||
}
|
||||
|
||||
manifest_text += _get_application_tag(Ref<EditorExportPlatform>(this), p_preset, _has_read_write_storage_permission(perms), p_debug);
|
||||
manifest_text += _get_application_tag(Ref<EditorExportPlatform>(this), p_preset, _has_read_write_storage_permission(perms), p_debug, manifest_metadata);
|
||||
manifest_text += "</manifest>\n";
|
||||
String manifest_path = ExportTemplateManager::get_android_build_directory(p_preset).path_join(vformat("src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release")));
|
||||
|
||||
|
|
@ -1087,7 +1120,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
String package_name = p_preset->get("package/unique_name");
|
||||
|
||||
const int screen_orientation =
|
||||
_get_android_orientation_value(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))));
|
||||
_get_android_orientation_value(DisplayServer::ScreenOrientation(int(get_project_setting(p_preset, "display/window/handheld/orientation"))));
|
||||
|
||||
bool screen_support_small = p_preset->get("screen/support_small");
|
||||
bool screen_support_normal = p_preset->get("screen/support_normal");
|
||||
|
|
@ -1098,11 +1131,12 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
int app_category = p_preset->get("package/app_category");
|
||||
bool retain_data_on_uninstall = p_preset->get("package/retain_data_on_uninstall");
|
||||
bool exclude_from_recents = p_preset->get("package/exclude_from_recents");
|
||||
bool is_resizeable = bool(GLOBAL_GET("display/window/size/resizable"));
|
||||
bool is_resizeable = bool(get_project_setting(p_preset, "display/window/size/resizable"));
|
||||
|
||||
Vector<String> perms;
|
||||
// Write permissions into the perms variable.
|
||||
_get_permissions(p_preset, p_give_internet, perms);
|
||||
Vector<FeatureInfo> features;
|
||||
Vector<MetadataInfo> manifest_metadata;
|
||||
_get_manifest_info(p_preset, p_give_internet, perms, features, manifest_metadata);
|
||||
bool has_read_write_storage_permission = _has_read_write_storage_permission(perms);
|
||||
|
||||
while (ofs < (uint32_t)p_manifest.size()) {
|
||||
|
|
@ -1171,7 +1205,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
|
||||
//replace project information
|
||||
if (tname == "manifest" && attrname == "package") {
|
||||
string_table.write[attr_value] = get_package_name(package_name);
|
||||
string_table.write[attr_value] = get_package_name(p_preset, package_name);
|
||||
}
|
||||
|
||||
if (tname == "manifest" && attrname == "versionCode") {
|
||||
|
|
@ -1219,7 +1253,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
}
|
||||
|
||||
if (tname == "provider" && attrname == "authorities") {
|
||||
string_table.write[attr_value] = get_package_name(package_name) + String(".fileprovider");
|
||||
string_table.write[attr_value] = get_package_name(p_preset, package_name) + String(".fileprovider");
|
||||
}
|
||||
|
||||
if (tname == "supports-screens") {
|
||||
|
|
@ -1246,42 +1280,25 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
uint32_t name = decode_uint32(&p_manifest[iofs + 12]);
|
||||
String tname = string_table[name];
|
||||
|
||||
if (tname == "uses-feature") {
|
||||
Vector<String> feature_names;
|
||||
Vector<bool> feature_required_list;
|
||||
Vector<int> feature_versions;
|
||||
if (tname == "manifest" || tname == "application") {
|
||||
// save manifest ending so we can restore it
|
||||
Vector<uint8_t> manifest_end;
|
||||
uint32_t manifest_cur_size = p_manifest.size();
|
||||
|
||||
if (_uses_vulkan()) {
|
||||
// Require vulkan hardware level 1 support
|
||||
feature_names.push_back("android.hardware.vulkan.level");
|
||||
feature_required_list.push_back(false);
|
||||
feature_versions.push_back(1);
|
||||
manifest_end.resize(p_manifest.size() - ofs);
|
||||
memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size());
|
||||
|
||||
// Require vulkan version 1.0
|
||||
feature_names.push_back("android.hardware.vulkan.version");
|
||||
feature_required_list.push_back(true);
|
||||
feature_versions.push_back(0x400003); // Encoded value for api version 1.0
|
||||
int32_t attr_name_string = string_table.find("name");
|
||||
ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute.");
|
||||
|
||||
int32_t ns_android_string = string_table.find("http://schemas.android.com/apk/res/android");
|
||||
if (ns_android_string == -1) {
|
||||
string_table.push_back("http://schemas.android.com/apk/res/android");
|
||||
ns_android_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
if (feature_names.size() > 0) {
|
||||
ofs += 24; // skip over end tag
|
||||
|
||||
// save manifest ending so we can restore it
|
||||
Vector<uint8_t> manifest_end;
|
||||
uint32_t manifest_cur_size = p_manifest.size();
|
||||
|
||||
manifest_end.resize(p_manifest.size() - ofs);
|
||||
memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size());
|
||||
|
||||
int32_t attr_name_string = string_table.find("name");
|
||||
ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute.");
|
||||
|
||||
int32_t ns_android_string = string_table.find("http://schemas.android.com/apk/res/android");
|
||||
if (ns_android_string == -1) {
|
||||
string_table.push_back("http://schemas.android.com/apk/res/android");
|
||||
ns_android_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
if (tname == "manifest") {
|
||||
// Updating manifest features
|
||||
int32_t attr_uses_feature_string = string_table.find("uses-feature");
|
||||
if (attr_uses_feature_string == -1) {
|
||||
string_table.push_back("uses-feature");
|
||||
|
|
@ -1294,11 +1311,11 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
attr_required_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < feature_names.size(); i++) {
|
||||
const String &feature_name = feature_names[i];
|
||||
bool feature_required = feature_required_list[i];
|
||||
int feature_version = feature_versions[i];
|
||||
bool has_version_attribute = feature_version != -1;
|
||||
for (int i = 0; i < features.size(); i++) {
|
||||
const String &feature_name = features[i].name;
|
||||
bool feature_required = features[i].required;
|
||||
String feature_version = features[i].version;
|
||||
bool has_version_attribute = !feature_version.is_empty();
|
||||
|
||||
print_line("Adding feature " + feature_name);
|
||||
|
||||
|
|
@ -1326,9 +1343,9 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
attr_version_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
version_value = string_table.find(itos(feature_version));
|
||||
version_value = string_table.find(feature_version);
|
||||
if (version_value == -1) {
|
||||
string_table.push_back(itos(feature_version));
|
||||
string_table.push_back(feature_version);
|
||||
version_value = string_table.size() - 1;
|
||||
}
|
||||
|
||||
|
|
@ -1400,79 +1417,149 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
|
||||
ofs += 24;
|
||||
}
|
||||
memcpy(&p_manifest.write[ofs], manifest_end.ptr(), manifest_end.size());
|
||||
ofs -= 24; // go back over back end
|
||||
}
|
||||
}
|
||||
if (tname == "manifest") {
|
||||
// save manifest ending so we can restore it
|
||||
Vector<uint8_t> manifest_end;
|
||||
uint32_t manifest_cur_size = p_manifest.size();
|
||||
|
||||
manifest_end.resize(p_manifest.size() - ofs);
|
||||
memcpy(manifest_end.ptrw(), &p_manifest[ofs], manifest_end.size());
|
||||
|
||||
int32_t attr_name_string = string_table.find("name");
|
||||
ERR_FAIL_COND_MSG(attr_name_string == -1, "Template does not have 'name' attribute.");
|
||||
|
||||
int32_t ns_android_string = string_table.find("android");
|
||||
ERR_FAIL_COND_MSG(ns_android_string == -1, "Template does not have 'android' namespace.");
|
||||
|
||||
int32_t attr_uses_permission_string = string_table.find("uses-permission");
|
||||
if (attr_uses_permission_string == -1) {
|
||||
string_table.push_back("uses-permission");
|
||||
attr_uses_permission_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < perms.size(); ++i) {
|
||||
print_line("Adding permission " + perms[i]);
|
||||
|
||||
manifest_cur_size += 56 + 24; // node + end node
|
||||
p_manifest.resize(manifest_cur_size);
|
||||
|
||||
// Add permission to the string pool
|
||||
int32_t perm_string = string_table.find(perms[i]);
|
||||
if (perm_string == -1) {
|
||||
string_table.push_back(perms[i]);
|
||||
perm_string = string_table.size() - 1;
|
||||
// Updating manifest permissions
|
||||
int32_t attr_uses_permission_string = string_table.find("uses-permission");
|
||||
if (attr_uses_permission_string == -1) {
|
||||
string_table.push_back("uses-permission");
|
||||
attr_uses_permission_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
// start tag
|
||||
encode_uint16(0x102, &p_manifest.write[ofs]); // type
|
||||
encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
|
||||
encode_uint32(56, &p_manifest.write[ofs + 4]); // size
|
||||
encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
|
||||
encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
|
||||
encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start
|
||||
encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size
|
||||
encode_uint16(1, &p_manifest.write[ofs + 28]); // num_attrs
|
||||
encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index
|
||||
encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index
|
||||
encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index
|
||||
for (int i = 0; i < perms.size(); ++i) {
|
||||
print_line("Adding permission " + perms[i]);
|
||||
|
||||
// attribute
|
||||
encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns
|
||||
encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name'
|
||||
encode_uint32(perm_string, &p_manifest.write[ofs + 44]); // raw_value
|
||||
encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size
|
||||
p_manifest.write[ofs + 50] = 0; // typedvalue_always0
|
||||
p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string)
|
||||
encode_uint32(perm_string, &p_manifest.write[ofs + 52]); // typedvalue reference
|
||||
manifest_cur_size += 56 + 24; // node + end node
|
||||
p_manifest.resize(manifest_cur_size);
|
||||
|
||||
ofs += 56;
|
||||
// Add permission to the string pool
|
||||
int32_t perm_string = string_table.find(perms[i]);
|
||||
if (perm_string == -1) {
|
||||
string_table.push_back(perms[i]);
|
||||
perm_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
// end tag
|
||||
encode_uint16(0x103, &p_manifest.write[ofs]); // type
|
||||
encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
|
||||
encode_uint32(24, &p_manifest.write[ofs + 4]); // size
|
||||
encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
|
||||
encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
|
||||
// start tag
|
||||
encode_uint16(0x102, &p_manifest.write[ofs]); // type
|
||||
encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
|
||||
encode_uint32(56, &p_manifest.write[ofs + 4]); // size
|
||||
encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
|
||||
encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
|
||||
encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start
|
||||
encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size
|
||||
encode_uint16(1, &p_manifest.write[ofs + 28]); // num_attrs
|
||||
encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index
|
||||
encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index
|
||||
encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index
|
||||
|
||||
ofs += 24;
|
||||
// attribute
|
||||
encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns
|
||||
encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name'
|
||||
encode_uint32(perm_string, &p_manifest.write[ofs + 44]); // raw_value
|
||||
encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size
|
||||
p_manifest.write[ofs + 50] = 0; // typedvalue_always0
|
||||
p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string)
|
||||
encode_uint32(perm_string, &p_manifest.write[ofs + 52]); // typedvalue reference
|
||||
|
||||
ofs += 56;
|
||||
|
||||
// end tag
|
||||
encode_uint16(0x103, &p_manifest.write[ofs]); // type
|
||||
encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
|
||||
encode_uint32(24, &p_manifest.write[ofs + 4]); // size
|
||||
encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
|
||||
encode_uint32(attr_uses_permission_string, &p_manifest.write[ofs + 20]); // name
|
||||
|
||||
ofs += 24;
|
||||
}
|
||||
}
|
||||
|
||||
if (tname == "application") {
|
||||
// Updating application meta-data
|
||||
int32_t attr_meta_data_string = string_table.find("meta-data");
|
||||
if (attr_meta_data_string == -1) {
|
||||
string_table.push_back("meta-data");
|
||||
attr_meta_data_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
int32_t attr_value_string = string_table.find("value");
|
||||
if (attr_value_string == -1) {
|
||||
string_table.push_back("value");
|
||||
attr_value_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < manifest_metadata.size(); i++) {
|
||||
String meta_data_name = manifest_metadata[i].name;
|
||||
String meta_data_value = manifest_metadata[i].value;
|
||||
|
||||
print_line("Adding application metadata " + meta_data_name);
|
||||
|
||||
int32_t meta_data_name_string = string_table.find(meta_data_name);
|
||||
if (meta_data_name_string == -1) {
|
||||
string_table.push_back(meta_data_name);
|
||||
meta_data_name_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
int32_t meta_data_value_string = string_table.find(meta_data_value);
|
||||
if (meta_data_value_string == -1) {
|
||||
string_table.push_back(meta_data_value);
|
||||
meta_data_value_string = string_table.size() - 1;
|
||||
}
|
||||
|
||||
int tag_size = 76; // node and two attrs + end node
|
||||
int attr_count = 2;
|
||||
manifest_cur_size += tag_size + 24;
|
||||
p_manifest.resize(manifest_cur_size);
|
||||
|
||||
// start tag
|
||||
encode_uint16(0x102, &p_manifest.write[ofs]); // type
|
||||
encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
|
||||
encode_uint32(tag_size, &p_manifest.write[ofs + 4]); // size
|
||||
encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
|
||||
encode_uint32(attr_meta_data_string, &p_manifest.write[ofs + 20]); // name
|
||||
encode_uint16(20, &p_manifest.write[ofs + 24]); // attr_start
|
||||
encode_uint16(20, &p_manifest.write[ofs + 26]); // attr_size
|
||||
encode_uint16(attr_count, &p_manifest.write[ofs + 28]); // num_attrs
|
||||
encode_uint16(0, &p_manifest.write[ofs + 30]); // id_index
|
||||
encode_uint16(0, &p_manifest.write[ofs + 32]); // class_index
|
||||
encode_uint16(0, &p_manifest.write[ofs + 34]); // style_index
|
||||
|
||||
// android:name attribute
|
||||
encode_uint32(ns_android_string, &p_manifest.write[ofs + 36]); // ns
|
||||
encode_uint32(attr_name_string, &p_manifest.write[ofs + 40]); // 'name'
|
||||
encode_uint32(meta_data_name_string, &p_manifest.write[ofs + 44]); // raw_value
|
||||
encode_uint16(8, &p_manifest.write[ofs + 48]); // typedvalue_size
|
||||
p_manifest.write[ofs + 50] = 0; // typedvalue_always0
|
||||
p_manifest.write[ofs + 51] = 0x03; // typedvalue_type (string)
|
||||
encode_uint32(meta_data_name_string, &p_manifest.write[ofs + 52]); // typedvalue reference
|
||||
|
||||
// android:value attribute
|
||||
encode_uint32(ns_android_string, &p_manifest.write[ofs + 56]); // ns
|
||||
encode_uint32(attr_value_string, &p_manifest.write[ofs + 60]); // 'value'
|
||||
encode_uint32(meta_data_value_string, &p_manifest.write[ofs + 64]); // raw_value
|
||||
encode_uint16(8, &p_manifest.write[ofs + 68]); // typedvalue_size
|
||||
p_manifest.write[ofs + 70] = 0; // typedvalue_always0
|
||||
p_manifest.write[ofs + 71] = 0x03; // typedvalue_type (string)
|
||||
encode_uint32(meta_data_value_string, &p_manifest.write[ofs + 72]); // typedvalue reference
|
||||
|
||||
ofs += 76;
|
||||
|
||||
// end tag
|
||||
encode_uint16(0x103, &p_manifest.write[ofs]); // type
|
||||
encode_uint16(16, &p_manifest.write[ofs + 2]); // headersize
|
||||
encode_uint32(24, &p_manifest.write[ofs + 4]); // size
|
||||
encode_uint32(0, &p_manifest.write[ofs + 8]); // lineno
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 12]); // comment
|
||||
encode_uint32(-1, &p_manifest.write[ofs + 16]); // ns
|
||||
encode_uint32(attr_meta_data_string, &p_manifest.write[ofs + 20]); // name
|
||||
|
||||
ofs += 24;
|
||||
}
|
||||
}
|
||||
|
||||
// copy footer back in
|
||||
|
|
@ -1484,7 +1571,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
|
|||
ofs += size;
|
||||
}
|
||||
|
||||
//create new andriodmanifest binary
|
||||
// Create new android manifest binary.
|
||||
|
||||
Vector<uint8_t> ret;
|
||||
ret.resize(string_table_begins + string_table.size() * 4);
|
||||
|
|
@ -1588,9 +1675,7 @@ String EditorExportPlatformAndroid::_parse_string(const uint8_t *p_bytes, bool p
|
|||
str8.write[i] = p_bytes[offset + i];
|
||||
}
|
||||
str8.write[len] = 0;
|
||||
String str;
|
||||
str.parse_utf8((const char *)str8.ptr(), len);
|
||||
return str;
|
||||
return String::utf8((const char *)str8.ptr(), len);
|
||||
} else {
|
||||
String str;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
|
|
@ -1615,7 +1700,7 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> &
|
|||
Vector<String> string_table;
|
||||
|
||||
String package_name = p_preset->get("package/name");
|
||||
Dictionary appnames = GLOBAL_GET("application/config/name_localized");
|
||||
Dictionary appnames = get_project_setting(p_preset, "application/config/name_localized");
|
||||
|
||||
for (uint32_t i = 0; i < string_count; i++) {
|
||||
uint32_t offset = decode_uint32(&r_manifest[string_table_begins + i * 4]);
|
||||
|
|
@ -1626,14 +1711,14 @@ void EditorExportPlatformAndroid::_fix_resources(const Ref<EditorExportPreset> &
|
|||
if (str.begins_with("godot-project-name")) {
|
||||
if (str == "godot-project-name") {
|
||||
//project name
|
||||
str = get_project_name(package_name);
|
||||
str = get_project_name(p_preset, package_name);
|
||||
|
||||
} else {
|
||||
String lang = str.substr(str.rfind_char('-') + 1, str.length()).replace("-", "_");
|
||||
String lang = str.substr(str.rfind_char('-') + 1).replace_char('-', '_');
|
||||
if (appnames.has(lang)) {
|
||||
str = appnames[lang];
|
||||
} else {
|
||||
str = get_project_name(package_name);
|
||||
str = get_project_name(p_preset, package_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1725,7 +1810,7 @@ void EditorExportPlatformAndroid::_process_launcher_icons(const String &p_file_n
|
|||
}
|
||||
|
||||
void EditorExportPlatformAndroid::load_icon_refs(const Ref<EditorExportPreset> &p_preset, Ref<Image> &icon, Ref<Image> &foreground, Ref<Image> &background, Ref<Image> &monochrome) {
|
||||
String project_icon_path = GLOBAL_GET("application/config/icon");
|
||||
String project_icon_path = get_project_setting(p_preset, "application/config/icon");
|
||||
|
||||
Error err = OK;
|
||||
|
||||
|
|
@ -1854,7 +1939,7 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport
|
|||
String pn = p_preset->get("package/unique_name");
|
||||
String pn_err;
|
||||
|
||||
if (!is_package_name_valid(pn, &pn_err)) {
|
||||
if (!is_package_name_valid(Ref<EditorExportPreset>(p_preset), pn, &pn_err)) {
|
||||
return TTR("Invalid package name:") + " " + pn_err;
|
||||
}
|
||||
} else if (p_name == "gesture/swipe_to_dismiss") {
|
||||
|
|
@ -1864,16 +1949,10 @@ String EditorExportPlatformAndroid::get_export_option_warning(const EditorExport
|
|||
}
|
||||
} else if (p_name == "gradle_build/use_gradle_build") {
|
||||
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
|
||||
String enabled_plugins_names = _get_plugins_names(Ref<EditorExportPreset>(p_preset));
|
||||
if (!enabled_plugins_names.is_empty() && !gradle_build_enabled) {
|
||||
String enabled_deprecated_plugins_names = _get_deprecated_plugins_names(Ref<EditorExportPreset>(p_preset));
|
||||
if (!enabled_deprecated_plugins_names.is_empty() && !gradle_build_enabled) {
|
||||
return TTR("\"Use Gradle Build\" must be enabled to use the plugins.");
|
||||
}
|
||||
} else if (p_name == "xr_features/xr_mode") {
|
||||
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
|
||||
int xr_mode_index = p_preset->get("xr_features/xr_mode");
|
||||
if (xr_mode_index == XR_MODE_OPENXR && !gradle_build_enabled) {
|
||||
return TTR("OpenXR requires \"Use Gradle Build\" to be enabled");
|
||||
}
|
||||
} else if (p_name == "gradle_build/compress_native_libraries") {
|
||||
bool gradle_build_enabled = p_preset->get("gradle_build/use_gradle_build");
|
||||
if (bool(p_preset->get("gradle_build/compress_native_libraries")) && !gradle_build_enabled) {
|
||||
|
|
@ -2041,7 +2120,9 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor
|
|||
if (p_option == "graphics/opengl_debug" ||
|
||||
p_option == "command_line/extra_args" ||
|
||||
p_option == "permissions/custom_permissions" ||
|
||||
p_option == "gradle_build/compress_native_libraries" ||
|
||||
p_option == "keystore/debug" ||
|
||||
p_option == "keystore/debug_user" ||
|
||||
p_option == "keystore/debug_password" ||
|
||||
p_option == "package/retain_data_on_uninstall" ||
|
||||
p_option == "package/exclude_from_recents" ||
|
||||
p_option == "package/show_in_app_library" ||
|
||||
|
|
@ -2065,6 +2146,9 @@ bool EditorExportPlatformAndroid::get_export_option_visibility(const EditorExpor
|
|||
return false;
|
||||
}
|
||||
|
||||
if (p_option == "dotnet/android_use_linux_bionic") {
|
||||
return advanced_options_enabled;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2204,7 +2288,7 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
|
|||
args.push_back("--user");
|
||||
args.push_back("0");
|
||||
}
|
||||
args.push_back(get_package_name(package_name));
|
||||
args.push_back(get_package_name(p_preset, package_name));
|
||||
|
||||
output.clear();
|
||||
err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
|
||||
|
|
@ -2309,16 +2393,16 @@ Error EditorExportPlatformAndroid::run(const Ref<EditorExportPreset> &p_preset,
|
|||
// Going with implicit launch first based on the LAUNCHER category and the app's package.
|
||||
args.push_back("-c");
|
||||
args.push_back("android.intent.category.LAUNCHER");
|
||||
args.push_back(get_package_name(package_name));
|
||||
args.push_back(get_package_name(p_preset, package_name));
|
||||
|
||||
output.clear();
|
||||
err = OS::get_singleton()->execute(adb, args, &output, &rv, true);
|
||||
print_verbose(output);
|
||||
if (err || rv != 0 || output.contains("Error: Activity not started")) {
|
||||
// The implicit launch failed, let's try an explicit launch by specifying the component name before giving up.
|
||||
const String component_name = get_package_name(package_name) + "/com.godot.game.GodotApp";
|
||||
const String component_name = get_package_name(p_preset, package_name) + "/com.godot.game.GodotApp";
|
||||
print_line("Implicit launch failed.. Trying explicit launch using", component_name);
|
||||
args.erase(get_package_name(package_name));
|
||||
args.erase(get_package_name(p_preset, package_name));
|
||||
args.push_back("-n");
|
||||
args.push_back(component_name);
|
||||
|
||||
|
|
@ -2531,38 +2615,52 @@ bool EditorExportPlatformAndroid::has_valid_username_and_password(const Ref<Edit
|
|||
}
|
||||
|
||||
#ifdef MODULE_MONO_ENABLED
|
||||
static uint64_t _last_validate_tfm_time = 0;
|
||||
static String _last_validate_tfm = "";
|
||||
|
||||
bool _validate_dotnet_tfm(const String &required_tfm, String &r_error) {
|
||||
String assembly_name = path::get_csharp_project_name();
|
||||
String assembly_name = Path::get_csharp_project_name();
|
||||
String project_path = ProjectSettings::get_singleton()->globalize_path("res://" + assembly_name + ".csproj");
|
||||
|
||||
if (!FileAccess::exists(project_path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
String pipe;
|
||||
List<String> args;
|
||||
args.push_back("build");
|
||||
args.push_back(project_path);
|
||||
args.push_back("--getProperty:TargetFramework");
|
||||
uint64_t modified_time = FileAccess::get_modified_time(project_path);
|
||||
String tfm;
|
||||
|
||||
int exitcode;
|
||||
Error err = OS::get_singleton()->execute("dotnet", args, &pipe, &exitcode, true);
|
||||
if (err != OK || exitcode != 0) {
|
||||
if (err != OK) {
|
||||
WARN_PRINT("Failed to execute dotnet command. Error " + String(error_names[err]));
|
||||
} else if (exitcode != 0) {
|
||||
print_line(pipe);
|
||||
WARN_PRINT("dotnet command exited with code " + itos(exitcode) + ". See output above for more details.");
|
||||
}
|
||||
r_error += vformat(TTR("Unable to determine the C# project's TFM, it may be incompatible. The export template only supports '%s'. Make sure the project targets '%s' or consider using gradle builds instead."), required_tfm, required_tfm) + "\n";
|
||||
if (modified_time == _last_validate_tfm_time) {
|
||||
tfm = _last_validate_tfm;
|
||||
} else {
|
||||
String tfm = pipe.strip_edges();
|
||||
if (tfm != required_tfm) {
|
||||
r_error += vformat(TTR("C# project targets '%s' but the export template only supports '%s'. Consider using gradle builds instead."), tfm, required_tfm) + "\n";
|
||||
return false;
|
||||
String pipe;
|
||||
List<String> args;
|
||||
args.push_back("build");
|
||||
args.push_back(project_path);
|
||||
args.push_back("--getProperty:TargetFramework");
|
||||
|
||||
int exitcode;
|
||||
Error err = OS::get_singleton()->execute("dotnet", args, &pipe, &exitcode, true);
|
||||
if (err != OK || exitcode != 0) {
|
||||
if (err != OK) {
|
||||
WARN_PRINT("Failed to execute dotnet command. Error " + String(error_names[err]));
|
||||
} else if (exitcode != 0) {
|
||||
print_line(pipe);
|
||||
WARN_PRINT("dotnet command exited with code " + itos(exitcode) + ". See output above for more details.");
|
||||
}
|
||||
r_error += vformat(TTR("Unable to determine the C# project's TFM, it may be incompatible. The export template only supports '%s'. Make sure the project targets '%s' or consider using gradle builds instead."), required_tfm, required_tfm) + "\n";
|
||||
return true;
|
||||
} else {
|
||||
tfm = pipe.strip_edges();
|
||||
_last_validate_tfm_time = modified_time;
|
||||
_last_validate_tfm = tfm;
|
||||
}
|
||||
}
|
||||
|
||||
if (tfm != required_tfm) {
|
||||
r_error += vformat(TTR("C# project targets '%s' but the export template only supports '%s'. Consider using gradle builds instead."), tfm, required_tfm) + "\n";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -2599,6 +2697,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
|
|||
if (!dvalid) {
|
||||
template_err += TTR("Custom debug template not found.") + "\n";
|
||||
}
|
||||
has_export_templates |= dvalid;
|
||||
} else {
|
||||
has_export_templates |= exists_export_template("android_debug.apk", &template_err);
|
||||
}
|
||||
|
|
@ -2608,6 +2707,7 @@ bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<Edito
|
|||
if (!rvalid) {
|
||||
template_err += TTR("Custom release template not found.") + "\n";
|
||||
}
|
||||
has_export_templates |= rvalid;
|
||||
} else {
|
||||
has_export_templates |= exists_export_template("android_release.apk", &template_err);
|
||||
}
|
||||
|
|
@ -2819,23 +2919,23 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
|
|||
}
|
||||
}
|
||||
|
||||
String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile");
|
||||
String current_renderer = get_project_setting(p_preset, "rendering/renderer/rendering_method.mobile");
|
||||
if (current_renderer == "forward_plus") {
|
||||
// Warning only, so don't override `valid`.
|
||||
err += vformat(TTR("The \"%s\" renderer is designed for Desktop devices, and is not suitable for Android devices."), current_renderer);
|
||||
err += "\n";
|
||||
}
|
||||
|
||||
if (_uses_vulkan() && min_sdk_int < VULKAN_MIN_SDK_VERSION) {
|
||||
if (_uses_vulkan(p_preset) && min_sdk_int < VULKAN_MIN_SDK_VERSION) {
|
||||
// Warning only, so don't override `valid`.
|
||||
err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer);
|
||||
err += "\n";
|
||||
}
|
||||
|
||||
String package_name = p_preset->get("package/unique_name");
|
||||
if (package_name.contains("$genname") && !is_project_name_valid()) {
|
||||
if (package_name.contains("$genname") && !is_project_name_valid(p_preset)) {
|
||||
// Warning only, so don't override `valid`.
|
||||
err += vformat(TTR("The project name does not meet the requirement for the package name format and will be updated to \"%s\". Please explicitly specify the package name if needed."), get_valid_basename());
|
||||
err += vformat(TTR("The project name does not meet the requirement for the package name format and will be updated to \"%s\". Please explicitly specify the package name if needed."), get_valid_basename(p_preset));
|
||||
err += "\n";
|
||||
}
|
||||
|
||||
|
|
@ -2853,7 +2953,7 @@ List<String> EditorExportPlatformAndroid::get_binary_extensions(const Ref<Editor
|
|||
String EditorExportPlatformAndroid::get_apk_expansion_fullpath(const Ref<EditorExportPreset> &p_preset, const String &p_path) {
|
||||
int version_code = p_preset->get("version/code");
|
||||
String package_name = p_preset->get("package/unique_name");
|
||||
String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
|
||||
String apk_file_name = "main." + itos(version_code) + "." + get_package_name(p_preset, package_name) + ".obb";
|
||||
String fullpath = p_path.get_base_dir().path_join(apk_file_name);
|
||||
return fullpath;
|
||||
}
|
||||
|
|
@ -2965,7 +3065,11 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref<EditorExportPreset> &p_pre
|
|||
}
|
||||
|
||||
if (!FileAccess::exists(keystore)) {
|
||||
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not find keystore, unable to export."));
|
||||
if (p_debug) {
|
||||
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not find debug keystore, unable to export."));
|
||||
} else {
|
||||
add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not find release keystore, unable to export."));
|
||||
}
|
||||
return ERR_FILE_CANT_OPEN;
|
||||
}
|
||||
|
||||
|
|
@ -3151,6 +3255,17 @@ String EditorExportPlatformAndroid::join_abis(const Vector<EditorExportPlatformA
|
|||
return ret;
|
||||
}
|
||||
|
||||
String EditorExportPlatformAndroid::_get_deprecated_plugins_names(const Ref<EditorExportPreset> &p_preset) const {
|
||||
Vector<String> names;
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
PluginConfigAndroid::get_plugins_names(get_enabled_plugins(p_preset), names);
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
String plugins_names = String("|").join(names);
|
||||
return plugins_names;
|
||||
}
|
||||
|
||||
String EditorExportPlatformAndroid::_get_plugins_names(const Ref<EditorExportPreset> &p_preset) const {
|
||||
Vector<String> names;
|
||||
|
||||
|
|
@ -3320,8 +3435,8 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
|||
print_verbose("Android sdk path: " + sdk_path);
|
||||
|
||||
// TODO: should we use "package/name" or "application/config/name"?
|
||||
String project_name = get_project_name(p_preset->get("package/name"));
|
||||
err = _create_project_name_strings_files(p_preset, project_name, gradle_build_directory); //project name localization.
|
||||
String project_name = get_project_name(p_preset, p_preset->get("package/name"));
|
||||
err = _create_project_name_strings_files(p_preset, project_name, gradle_build_directory, get_project_setting(p_preset, "application/config/name_localized")); //project name localization.
|
||||
if (err != OK) {
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to overwrite res/*.xml files with project name."));
|
||||
}
|
||||
|
|
@ -3382,7 +3497,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
|||
String build_path = ProjectSettings::get_singleton()->globalize_path(gradle_build_directory);
|
||||
build_command = build_path.path_join(build_command);
|
||||
|
||||
String package_name = get_package_name(p_preset->get("package/unique_name"));
|
||||
String package_name = get_package_name(p_preset, p_preset->get("package/unique_name"));
|
||||
String version_code = itos(p_preset->get("version/code"));
|
||||
String version_name = p_preset->get_version("version/name");
|
||||
String min_sdk_version = p_preset->get("gradle_build/min_sdk");
|
||||
|
|
@ -3456,7 +3571,6 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
|||
}
|
||||
|
||||
String addons_directory = ProjectSettings::get_singleton()->globalize_path("res://addons");
|
||||
String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile");
|
||||
|
||||
cmdline.push_back("-p"); // argument to specify the start directory.
|
||||
cmdline.push_back(build_path); // start directory.
|
||||
|
|
@ -3473,8 +3587,6 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
|||
cmdline.push_back("-Pperform_zipalign=" + zipalign_flag); // argument to specify whether the build should be zipaligned.
|
||||
cmdline.push_back("-Pperform_signing=" + sign_flag); // argument to specify whether the build should be signed.
|
||||
cmdline.push_back("-Pcompress_native_libraries=" + compress_native_libraries_flag); // argument to specify whether the build should compress native libraries.
|
||||
cmdline.push_back("-Pgodot_editor_version=" + String(VERSION_FULL_CONFIG));
|
||||
cmdline.push_back("-Pgodot_rendering_method=" + current_renderer);
|
||||
|
||||
// NOTE: The release keystore is not included in the verbose logging
|
||||
// to avoid accidentally leaking sensitive information when sharing verbose logs for troubleshooting.
|
||||
|
|
@ -3497,7 +3609,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
|||
debug_keystore = OS::get_singleton()->get_resource_dir().path_join(debug_keystore).simplify_path();
|
||||
}
|
||||
if (!FileAccess::exists(debug_keystore)) {
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export."));
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find debug keystore, unable to export."));
|
||||
return ERR_FILE_CANT_OPEN;
|
||||
}
|
||||
|
||||
|
|
@ -3513,7 +3625,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
|||
release_keystore = OS::get_singleton()->get_resource_dir().path_join(release_keystore).simplify_path();
|
||||
}
|
||||
if (!FileAccess::exists(release_keystore)) {
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export."));
|
||||
add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find release keystore, unable to export."));
|
||||
return ERR_FILE_CANT_OPEN;
|
||||
}
|
||||
|
||||
|
|
@ -3655,6 +3767,17 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref<EditorExportP
|
|||
//write
|
||||
if (file == "AndroidManifest.xml") {
|
||||
_fix_manifest(p_preset, data, p_give_internet);
|
||||
|
||||
// Allow editor export plugins to update the prebuilt manifest as needed.
|
||||
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
||||
for (int i = 0; i < export_plugins.size(); i++) {
|
||||
if (export_plugins[i]->supports_platform(Ref<EditorExportPlatform>(this))) {
|
||||
PackedByteArray export_plugin_data = export_plugins[i]->update_android_prebuilt_manifest(Ref<EditorExportPlatform>(this), data);
|
||||
if (!export_plugin_data.is_empty()) {
|
||||
data = export_plugin_data;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (file == "resources.arsc") {
|
||||
_fix_resources(p_preset, data);
|
||||
|
|
|
|||
|
|
@ -28,13 +28,14 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANDROID_EXPORT_PLUGIN_H
|
||||
#define ANDROID_EXPORT_PLUGIN_H
|
||||
#pragma once
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
#include "godot_plugin_config.h"
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
#include "gradle_export_util.h"
|
||||
|
||||
#include "core/io/image.h"
|
||||
#include "core/io/zip_io.h"
|
||||
#include "core/os/os.h"
|
||||
|
|
@ -76,6 +77,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
|
|||
EditorProgress *ep = nullptr;
|
||||
};
|
||||
|
||||
struct FeatureInfo {
|
||||
String name;
|
||||
bool required;
|
||||
String version;
|
||||
};
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
mutable Vector<PluginConfigAndroid> android_plugins;
|
||||
mutable SafeFlag android_plugins_changed;
|
||||
|
|
@ -97,16 +104,16 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
|
|||
void _update_preset_status();
|
||||
#endif
|
||||
|
||||
String get_project_name(const String &p_name) const;
|
||||
String get_project_name(const Ref<EditorExportPreset> &p_preset, const String &p_name) const;
|
||||
|
||||
String get_package_name(const String &p_package) const;
|
||||
String get_package_name(const Ref<EditorExportPreset> &p_preset, const String &p_package) const;
|
||||
|
||||
String get_valid_basename() const;
|
||||
String get_valid_basename(const Ref<EditorExportPreset> &p_preset) const;
|
||||
|
||||
String get_assets_directory(const Ref<EditorExportPreset> &p_preset, int p_export_format) const;
|
||||
|
||||
bool is_package_name_valid(const String &p_package, String *r_error = nullptr) const;
|
||||
bool is_project_name_valid() const;
|
||||
bool is_package_name_valid(const Ref<EditorExportPreset> &p_preset, const String &p_package, String *r_error = nullptr) const;
|
||||
bool is_project_name_valid(const Ref<EditorExportPreset> &p_preset) const;
|
||||
|
||||
static bool _should_compress_asset(const String &p_path, const Vector<uint8_t> &p_data);
|
||||
|
||||
|
|
@ -152,7 +159,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
|
|||
|
||||
bool _has_manage_external_storage_permission(const Vector<String> &p_permissions);
|
||||
|
||||
void _get_permissions(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions);
|
||||
void _get_manifest_info(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, Vector<String> &r_permissions, Vector<FeatureInfo> &r_features, Vector<MetadataInfo> &r_metadata);
|
||||
|
||||
void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug);
|
||||
|
||||
|
|
@ -182,7 +189,7 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
|
|||
|
||||
static Vector<ABI> get_enabled_abis(const Ref<EditorExportPreset> &p_preset);
|
||||
|
||||
static bool _uses_vulkan();
|
||||
bool _uses_vulkan(const Ref<EditorExportPreset> &p_preset) const;
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
|
|
@ -236,6 +243,8 @@ public:
|
|||
|
||||
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
|
||||
|
||||
String _get_deprecated_plugins_names(const Ref<EditorExportPreset> &p_preset) const;
|
||||
|
||||
String _get_plugins_names(const Ref<EditorExportPreset> &p_preset) const;
|
||||
|
||||
String _resolve_export_plugin_android_library_path(const String &p_android_library_path) const;
|
||||
|
|
@ -269,5 +278,3 @@ public:
|
|||
|
||||
~EditorExportPlatformAndroid();
|
||||
};
|
||||
|
||||
#endif // ANDROID_EXPORT_PLUGIN_H
|
||||
|
|
|
|||
|
|
@ -28,13 +28,11 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANDROID_GODOT_PLUGIN_CONFIG_H
|
||||
#define ANDROID_GODOT_PLUGIN_CONFIG_H
|
||||
#pragma once
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/error/error_list.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/string/ustring.h"
|
||||
|
||||
|
|
@ -104,5 +102,3 @@ struct PluginConfigAndroid {
|
|||
};
|
||||
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
#endif // ANDROID_GODOT_PLUGIN_CONFIG_H
|
||||
|
|
|
|||
|
|
@ -171,8 +171,14 @@ Error store_string_at_path(const String &p_path, const String &p_data) {
|
|||
// This method will be called ONLY when gradle build is enabled.
|
||||
Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed) {
|
||||
CustomExportData *export_data = static_cast<CustomExportData *>(p_userdata);
|
||||
const String path = ResourceUID::ensure_path(p_path);
|
||||
|
||||
String path = p_path.simplify_path();
|
||||
if (path.begins_with("uid://")) {
|
||||
path = ResourceUID::uid_to_path(path).simplify_path();
|
||||
print_verbose(vformat(R"(UID referenced exported file name "%s" was replaced with "%s".)", p_path, path));
|
||||
}
|
||||
const String dst_path = path.replace_first("res://", export_data->assets_directory + "/");
|
||||
|
||||
print_verbose("Saving project files from " + path + " into " + dst_path);
|
||||
Error err = store_file_at_path(dst_path, p_data);
|
||||
return err;
|
||||
|
|
@ -194,10 +200,10 @@ String _android_xml_escape(const String &p_string) {
|
|||
}
|
||||
|
||||
// Creates strings.xml files inside the gradle project for different locales.
|
||||
Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name, const String &p_gradle_build_dir) {
|
||||
print_verbose("Creating strings resources for supported locales for project " + project_name);
|
||||
Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &p_project_name, const String &p_gradle_build_dir, const Dictionary &p_appnames) {
|
||||
print_verbose("Creating strings resources for supported locales for project " + p_project_name);
|
||||
// Stores the string into the default values directory.
|
||||
String processed_default_xml_string = vformat(GODOT_PROJECT_NAME_XML_STRING, _android_xml_escape(project_name));
|
||||
String processed_default_xml_string = vformat(GODOT_PROJECT_NAME_XML_STRING, _android_xml_escape(p_project_name));
|
||||
store_string_at_path(p_gradle_build_dir.path_join("res/values/godot_project_name_string.xml"), processed_default_xml_string);
|
||||
|
||||
// Searches the Gradle project res/ directory to find all supported locales
|
||||
|
|
@ -209,7 +215,6 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
|
|||
return ERR_CANT_OPEN;
|
||||
}
|
||||
da->list_dir_begin();
|
||||
Dictionary appnames = GLOBAL_GET("application/config/name_localized");
|
||||
while (true) {
|
||||
String file = da->get_next();
|
||||
if (file.is_empty()) {
|
||||
|
|
@ -221,8 +226,8 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
|
|||
}
|
||||
String locale = file.replace("values-", "").replace("-r", "_");
|
||||
String locale_directory = p_gradle_build_dir.path_join("res/" + file + "/godot_project_name_string.xml");
|
||||
if (appnames.has(locale)) {
|
||||
String locale_project_name = appnames[locale];
|
||||
if (p_appnames.has(locale)) {
|
||||
String locale_project_name = p_appnames[locale];
|
||||
String processed_xml_string = vformat(GODOT_PROJECT_NAME_XML_STRING, _android_xml_escape(locale_project_name));
|
||||
print_verbose("Storing project name for locale " + locale + " under " + locale_directory);
|
||||
store_string_at_path(locale_directory, processed_xml_string);
|
||||
|
|
@ -246,7 +251,7 @@ String _get_gles_tag() {
|
|||
String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
|
||||
String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\"";
|
||||
String sizes[] = { "small", "normal", "large", "xlarge" };
|
||||
size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]);
|
||||
constexpr size_t num_sizes = std::size(sizes);
|
||||
for (size_t i = 0; i < num_sizes; i++) {
|
||||
String feature_name = vformat("screen/support_%s", sizes[i]);
|
||||
String feature_support = bool_to_string(p_preset->get(feature_name));
|
||||
|
|
@ -258,7 +263,7 @@ String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
|
|||
}
|
||||
|
||||
String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug) {
|
||||
String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))));
|
||||
String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(p_export_platform->get_project_setting(p_preset, "display/window/handheld/orientation"))));
|
||||
String manifest_activity_text = vformat(
|
||||
" <activity android:name=\".GodotApp\" "
|
||||
"tools:replace=\"android:screenOrientation,android:excludeFromRecents,android:resizeableActivity\" "
|
||||
|
|
@ -268,7 +273,7 @@ String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, con
|
|||
"android:resizeableActivity=\"%s\">\n",
|
||||
bool_to_string(p_preset->get("package/exclude_from_recents")),
|
||||
orientation,
|
||||
bool_to_string(bool(GLOBAL_GET("display/window/size/resizable"))));
|
||||
bool_to_string(bool(p_export_platform->get_project_setting(p_preset, "display/window/size/resizable"))));
|
||||
|
||||
manifest_activity_text += " <intent-filter>\n"
|
||||
" <action android:name=\"android.intent.action.MAIN\" />\n"
|
||||
|
|
@ -306,7 +311,7 @@ String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, con
|
|||
return manifest_activity_text;
|
||||
}
|
||||
|
||||
String _get_application_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission, bool p_debug) {
|
||||
String _get_application_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission, bool p_debug, const Vector<MetadataInfo> &p_metadata) {
|
||||
int app_category_index = (int)(p_preset->get("package/app_category"));
|
||||
bool is_game = app_category_index == APP_CATEGORY_GAME;
|
||||
|
||||
|
|
@ -330,6 +335,10 @@ String _get_application_tag(const Ref<EditorExportPlatform> &p_export_platform,
|
|||
}
|
||||
manifest_application_text += " tools:ignore=\"GoogleAppIndexingWarning\">\n\n";
|
||||
|
||||
for (int i = 0; i < p_metadata.size(); i++) {
|
||||
manifest_application_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"%s\" android:value=\"%s\" />\n", p_metadata[i].name, p_metadata[i].value);
|
||||
}
|
||||
|
||||
Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins();
|
||||
for (int i = 0; i < export_plugins.size(); i++) {
|
||||
if (export_plugins[i]->supports_platform(p_export_platform)) {
|
||||
|
|
|
|||
|
|
@ -28,8 +28,7 @@
|
|||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#ifndef ANDROID_GRADLE_EXPORT_UTIL_H
|
||||
#define ANDROID_GRADLE_EXPORT_UTIL_H
|
||||
#pragma once
|
||||
|
||||
#include "core/io/dir_access.h"
|
||||
#include "core/io/file_access.h"
|
||||
|
|
@ -69,6 +68,11 @@ struct CustomExportData {
|
|||
Vector<String> libs;
|
||||
};
|
||||
|
||||
struct MetadataInfo {
|
||||
String name;
|
||||
String value;
|
||||
};
|
||||
|
||||
int _get_android_orientation_value(DisplayServer::ScreenOrientation screen_orientation);
|
||||
|
||||
String _get_android_orientation_label(DisplayServer::ScreenOrientation screen_orientation);
|
||||
|
|
@ -96,7 +100,7 @@ Error store_string_at_path(const String &p_path, const String &p_data);
|
|||
Error rename_and_store_file_in_gradle_project(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key, uint64_t p_seed);
|
||||
|
||||
// Creates strings.xml files inside the gradle project for different locales.
|
||||
Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &project_name, const String &p_gradle_build_dir);
|
||||
Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset, const String &p_project_name, const String &p_gradle_build_dir, const Dictionary &p_appnames);
|
||||
|
||||
String bool_to_string(bool v);
|
||||
|
||||
|
|
@ -106,6 +110,4 @@ String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset);
|
|||
|
||||
String _get_activity_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug);
|
||||
|
||||
String _get_application_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission, bool p_debug);
|
||||
|
||||
#endif // ANDROID_GRADLE_EXPORT_UTIL_H
|
||||
String _get_application_tag(const Ref<EditorExportPlatform> &p_export_platform, const Ref<EditorExportPreset> &p_preset, bool p_has_read_write_storage_permission, bool p_debug, const Vector<MetadataInfo> &p_metadata);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path fill="#56d881" d="M22.904 20.192a1.25 1.25 0 1 1 1.25-1.25 1.25 1.25 0 0 1-1.25 1.25m-13.808 0a1.25 1.25 0 1 1 1.25-1.25 1.25 1.25 0 0 1-1.25 1.25m14.256-7.525 2.496-4.323a.52.52 0 1 0-.899-.52l-2.528 4.378a15.69 15.69 0 0 0-12.842 0L7.051 7.823a.52.52 0 1 0-.9.521l2.497 4.323C4.361 15 1.43 19.34 1 24.467h30c-.43-5.128-3.361-9.468-7.648-11.8"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path fill="#56d881" d="M22.904 20.192a1.25 1.25 0 1 1 1.25-1.25 1.25 1.25 0 0 1-1.25 1.25m-13.808 0a1.25 1.25 0 1 1 1.25-1.25 1.25 1.25 0 0 1-1.25 1.25m14.256-7.525 2.496-4.323a.52.52 0 1 0-.899-.52l-2.528 4.378a15.69 15.69 0 0 0-12.842 0L7.051 7.823a.52.52 0 1 0-.9.521l2.497 4.323C4.361 15 1.43 19.34 1 24.467h30c-.43-5.128-3.361-9.468-7.648-11.8"/></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 422 B |
|
|
@ -1 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path fill="#e0e0e0" d="M11.187 9.936a.577.577 0 1 1 .578-.578.577.577 0 0 1-.578.578m-6.374 0a.577.577 0 1 1 .577-.578.577.577 0 0 1-.577.578m6.581-3.475 1.153-1.996a.24.24 0 1 0-.415-.24l-1.167 2.021a7.244 7.244 0 0 0-5.93 0L3.868 4.225a.24.24 0 1 0-.415.24l1.153 1.996a6.806 6.806 0 0 0-3.532 5.448h13.852a6.807 6.807 0 0 0-3.532-5.448"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="16" height="16"><path fill="#e0e0e0" d="M11.187 9.936a.577.577 0 1 1 .578-.578.577.577 0 0 1-.578.578m-6.374 0a.577.577 0 1 1 .577-.578.577.577 0 0 1-.577.578m6.581-3.475 1.153-1.996a.24.24 0 1 0-.415-.24l-1.167 2.021a7.244 7.244 0 0 0-5.93 0L3.868 4.225a.24.24 0 1 0-.415.24l1.153 1.996a6.806 6.806 0 0 0-3.532 5.448h13.852a6.807 6.807 0 0 0-3.532-5.448"/></svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 431 B After Width: | Height: | Size: 432 B |
Loading…
Add table
Add a link
Reference in a new issue