/*************************************************************************/ /* export.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ /* "Software"), to deal in the Software without restriction, including */ /* without limitation the rights to use, copy, modify, merge, publish, */ /* distribute, sublicense, and/or sell copies of the Software, and to */ /* permit persons to whom the Software is furnished to do so, subject to */ /* the following conditions: */ /* */ /* The above copyright notice and this permission notice shall be */ /* included in all copies or substantial portions of the Software. */ /* */ /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ #include "export.h" #include "editor/editor_import_export.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "globals.h" #include "io/marshalls.h" #include "io/xml_parser.h" #include "io/zip_io.h" #include "os/file_access.h" #include "os/os.h" #include "platform/bb10/logo.gen.h" #include "version.h" #define MAX_DEVICES 5 class EditorExportPlatformBB10 : public EditorExportPlatform { OBJ_TYPE(EditorExportPlatformBB10, EditorExportPlatform); String custom_package; int version_code; String version_name; String package; String name; String category; String description; String author_name; String author_id; String icon; struct Device { int index; String name; String description; }; Vector devices; bool devices_changed; Mutex *device_lock; Thread *device_thread; Ref logo; volatile bool quit_request; static void _device_poll_thread(void *ud); void _fix_descriptor(Vector &p_manifest); protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List *p_list) const; public: virtual String get_name() const { return "BlackBerry 10"; } virtual ImageCompression get_image_compression() const { return IMAGE_COMPRESSION_ETC1; } virtual Ref get_logo() const { return logo; } virtual bool poll_devices(); virtual int get_device_count() const; virtual String get_device_name(int p_device) const; virtual String get_device_info(int p_device) const; virtual Error run(int p_device, int p_flags = 0); virtual bool requires_password(bool p_debug) const { return !p_debug; } virtual String get_binary_extension() const { return "bar"; } virtual Error export_project(const String &p_path, bool p_debug, int p_flags = 0); virtual bool can_export(String *r_error = NULL) const; EditorExportPlatformBB10(); ~EditorExportPlatformBB10(); }; bool EditorExportPlatformBB10::_set(const StringName &p_name, const Variant &p_value) { String n = p_name; if (n == "version/code") version_code = p_value; else if (n == "version/name") version_name = p_value; else if (n == "package/unique_name") package = p_value; else if (n == "package/category") category = p_value; else if (n == "package/name") name = p_value; else if (n == "package/description") description = p_value; else if (n == "package/icon") icon = p_value; else if (n == "package/custom_template") custom_package = p_value; else if (n == "release/author") author_name = p_value; else if (n == "release/author_id") author_id = p_value; else return false; return true; } bool EditorExportPlatformBB10::_get(const StringName &p_name, Variant &r_ret) const { String n = p_name; if (n == "version/code") r_ret = version_code; else if (n == "version/name") r_ret = version_name; else if (n == "package/unique_name") r_ret = package; else if (n == "package/category") r_ret = category; else if (n == "package/name") r_ret = name; else if (n == "package/description") r_ret = description; else if (n == "package/icon") r_ret = icon; else if (n == "package/custom_template") r_ret = custom_package; else if (n == "release/author") r_ret = author_name; else if (n == "release/author_id") r_ret = author_id; else return false; return true; } void EditorExportPlatformBB10::_get_property_list(List *p_list) const { p_list->push_back(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,65535,1")); p_list->push_back(PropertyInfo(Variant::STRING, "version/name")); p_list->push_back(PropertyInfo(Variant::STRING, "package/unique_name")); p_list->push_back(PropertyInfo(Variant::STRING, "package/category")); p_list->push_back(PropertyInfo(Variant::STRING, "package/name")); p_list->push_back(PropertyInfo(Variant::STRING, "package/description", PROPERTY_HINT_MULTILINE_TEXT)); p_list->push_back(PropertyInfo(Variant::STRING, "package/icon", PROPERTY_HINT_FILE, "png")); p_list->push_back(PropertyInfo(Variant::STRING, "package/custom_template", PROPERTY_HINT_GLOBAL_FILE, "zip")); p_list->push_back(PropertyInfo(Variant::STRING, "release/author")); p_list->push_back(PropertyInfo(Variant::STRING, "release/author_id")); //p_list->push_back( PropertyInfo( Variant::INT, "resources/pack_mode", PROPERTY_HINT_ENUM,"Copy,Single Exec.,Pack (.pck),Bundles (Optical)")); } void EditorExportPlatformBB10::_fix_descriptor(Vector &p_descriptor) { String fpath = EditorSettings::get_singleton()->get_settings_path().plus_file("tmp_bar-settings.xml"); { FileAccessRef f = FileAccess::open(fpath, FileAccess::WRITE); f->store_buffer(p_descriptor.ptr(), p_descriptor.size()); } Ref parser = memnew(XMLParser); Error err = parser->open(fpath); ERR_FAIL_COND(err != OK); String txt; err = parser->read(); Vector depth; while (err != ERR_FILE_EOF) { ERR_FAIL_COND(err != OK); switch (parser->get_node_type()) { case XMLParser::NODE_NONE: { print_line("???"); } break; case XMLParser::NODE_ELEMENT: { String e = "<"; e += parser->get_node_name(); for (int i = 0; i < parser->get_attribute_count(); i++) { e += " "; e += parser->get_attribute_name(i) + "=\""; e += parser->get_attribute_value(i) + "\" "; } if (parser->is_empty()) { e += "/"; } else { depth.push_back(parser->get_node_name()); } e += ">"; txt += e; } break; case XMLParser::NODE_ELEMENT_END: { txt += "get_node_name() + ">"; if (depth.size() && depth[depth.size() - 1] == parser->get_node_name()) { depth.resize(depth.size() - 1); } } break; case XMLParser::NODE_TEXT: { if (depth.size() == 2 && depth[0] == "qnx" && depth[1] == "id") { txt += package; } else if (depth.size() == 2 && depth[0] == "qnx" && depth[1] == "name") { String aname; if (this->name != "") { aname = this->name; } else { aname = Globals::get_singleton()->get("application/name"); } if (aname == "") { aname = _MKSTR(VERSION_NAME); } txt += aname; } else if (depth.size() == 2 && depth[0] == "qnx" && depth[1] == "versionNumber") { txt += itos(version_code); } else if (depth.size() == 2 && depth[0] == "qnx" && depth[1] == "description") { txt += description; } else if (depth.size() == 2 && depth[0] == "qnx" && depth[1] == "author") { txt += author_name; } else if (depth.size() == 2 && depth[0] == "qnx" && depth[1] == "authorId") { txt += author_id; } else if (depth.size() == 2 && depth[0] == "qnx" && depth[1] == "category") { txt += category; } else { txt += parser->get_node_data(); } } break; case XMLParser::NODE_COMMENT: { txt += ""; } break; case XMLParser::NODE_CDATA: { //ignore //print_line("cdata"); } break; case XMLParser::NODE_UNKNOWN: { //ignore txt += "<" + parser->get_node_name() + ">"; } break; } err = parser->read(); } CharString cs = txt.utf8(); p_descriptor.resize(cs.length()); for (int i = 0; i < cs.length(); i++) p_descriptor[i] = cs[i]; } Error EditorExportPlatformBB10::export_project(const String &p_path, bool p_debug, int p_flags) { EditorProgress ep("export", "Exporting for BlackBerry 10", 104); String src_template = custom_package; if (src_template == "") { String err; src_template = find_export_template("bb10.zip", &err); if (src_template == "") { EditorNode::add_io_error(err); return ERR_FILE_NOT_FOUND; } } FileAccess *src_f = NULL; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); ep.step("Creating FileSystem for BAR", 0); unzFile pkg = unzOpen2(src_template.utf8().get_data(), &io); if (!pkg) { EditorNode::add_io_error("Could not find template zip to export:\n" + src_template); return ERR_FILE_NOT_FOUND; } DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); da->change_dir(EditorSettings::get_singleton()->get_settings_path()); if (da->change_dir("tmp") != OK) { da->make_dir("tmp"); if (da->change_dir("tmp") != OK) return ERR_CANT_CREATE; } if (da->change_dir("bb10_export") != OK) { da->make_dir("bb10_export"); if (da->change_dir("bb10_export") != OK) { return ERR_CANT_CREATE; } } String bar_dir = da->get_current_dir(); if (bar_dir.ends_with("/")) { bar_dir = bar_dir.substr(0, bar_dir.length() - 1); } //THIS IS SUPER, SUPER DANGEROUS!!!! //CAREFUL WITH THIS CODE, MIGHT DELETE USERS HARD DRIVE OR HOME DIR //EXTRA CHECKS ARE IN PLACE EVERYWERE TO MAKE SURE NOTHING BAD HAPPENS BUT STILL.... //BE SUPER CAREFUL WITH THIS PLEASE!!! //BLACKBERRY THIS IS YOUR FAULT FOR NOT MAKING A BETTER WAY!! bool berr = bar_dir.ends_with("bb10_export"); if (berr) { if (da->list_dir_begin()) { EditorNode::add_io_error("Can't ensure that dir is empty:\n" + bar_dir); ERR_FAIL_COND_V(berr, FAILED); }; String f = da->get_next(); while (f != "") { if (f == "." || f == "..") { f = da->get_next(); continue; }; Error err = da->remove(bar_dir + "/" + f); if (err != OK) { EditorNode::add_io_error("Can't ensure that dir is empty:\n" + bar_dir); ERR_FAIL_COND_V(err != OK, err); }; f = da->get_next(); }; da->list_dir_end(); } else { print_line("ARE YOU CRAZY??? THIS IS A SERIOUS BUG HERE!!!"); ERR_FAIL_V(ERR_OMFG_THIS_IS_VERY_VERY_BAD); } ERR_FAIL_COND_V(!pkg, ERR_CANT_OPEN); int ret = unzGoToFirstFile(pkg); while (ret == UNZ_OK) { //get filename unz_file_info info; char fname[16384]; ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0); String file = fname; Vector data; data.resize(info.uncompressed_size); //read unzOpenCurrentFile(pkg); unzReadCurrentFile(pkg, data.ptr(), data.size()); unzCloseCurrentFile(pkg); //write if (file == "bar-descriptor.xml") { _fix_descriptor(data); } if (file == "icon.png") { bool found = false; if (this->icon != "" && this->icon.ends_with(".png")) { FileAccess *f = FileAccess::open(this->icon, FileAccess::READ); if (f) { data.resize(f->get_len()); f->get_buffer(data.ptr(), data.size()); memdelete(f); found = true; } } if (!found) { String appicon = Globals::get_singleton()->get("application/icon"); if (appicon != "" && appicon.ends_with(".png")) { FileAccess *f = FileAccess::open(appicon, FileAccess::READ); if (f) { data.resize(f->get_len()); f->get_buffer(data.ptr(), data.size()); memdelete(f); } } } } if (file.find("/")) { da->make_dir_recursive(file.get_base_dir()); } FileAccessRef wf = FileAccess::open(bar_dir.plus_file(file), FileAccess::WRITE); wf->store_buffer(data.ptr(), data.size()); ret = unzGoToNextFile(pkg); } ep.step("Adding Files..", 2); FileAccess *dst = FileAccess::open(bar_dir + "/data.pck", FileAccess::WRITE); if (!dst) { EditorNode::add_io_error("Can't copy executable file to:\n " + p_path); return ERR_FILE_CANT_WRITE; } save_pack(dst, false, 1024); dst->close(); memdelete(dst); ep.step("Creating BAR Package..", 104); String bb_packager = EditorSettings::get_singleton()->get("blackberry/host_tools"); bb_packager = bb_packager.plus_file("blackberry-nativepackager"); if (OS::get_singleton()->get_name() == "Windows") bb_packager += ".bat"; if (!FileAccess::exists(bb_packager)) { EditorNode::add_io_error("Can't find packager:\n" + bb_packager); return ERR_CANT_OPEN; } List args; args.push_back("-package"); args.push_back(p_path); if (p_debug) { String debug_token = EditorSettings::get_singleton()->get("blackberry/debug_token"); if (!FileAccess::exists(debug_token)) { EditorNode::add_io_error("Debug token not found!"); } else { args.push_back("-debugToken"); args.push_back(debug_token); } args.push_back("-devMode"); args.push_back("-configuration"); args.push_back("Device-Debug"); } else { args.push_back("-configuration"); args.push_back("Device-Release"); } args.push_back(bar_dir.plus_file("bar-descriptor.xml")); int ec; Error err = OS::get_singleton()->execute(bb_packager, args, true, NULL, NULL, &ec); if (err != OK) return err; if (ec != 0) return ERR_CANT_CREATE; return OK; } bool EditorExportPlatformBB10::poll_devices() { bool dc = devices_changed; devices_changed = false; return dc; } int EditorExportPlatformBB10::get_device_count() const { device_lock->lock(); int dc = devices.size(); device_lock->unlock(); return dc; } String EditorExportPlatformBB10::get_device_name(int p_device) const { ERR_FAIL_INDEX_V(p_device, devices.size(), ""); device_lock->lock(); String s = devices[p_device].name; device_lock->unlock(); return s; } String EditorExportPlatformBB10::get_device_info(int p_device) const { ERR_FAIL_INDEX_V(p_device, devices.size(), ""); device_lock->lock(); String s = devices[p_device].description; device_lock->unlock(); return s; } void EditorExportPlatformBB10::_device_poll_thread(void *ud) { EditorExportPlatformBB10 *ea = (EditorExportPlatformBB10 *)ud; while (!ea->quit_request) { String bb_deploy = EditorSettings::get_singleton()->get("blackberry/host_tools"); bb_deploy = bb_deploy.plus_file("blackberry-deploy"); bool windows = OS::get_singleton()->get_name() == "Windows"; if (windows) bb_deploy += ".bat"; if (FileAccess::exists(bb_deploy)) { Vector devices; for (int i = 0; i < MAX_DEVICES; i++) { String host = EditorSettings::get_singleton()->get("blackberry/device_" + itos(i + 1) + "/host"); if (host == String()) continue; String pass = EditorSettings::get_singleton()->get("blackberry/device_" + itos(i + 1) + "/password"); if (pass == String()) continue; List args; args.push_back("-listDeviceInfo"); args.push_back(host); args.push_back("-password"); args.push_back(pass); int ec; String dp; Error err = OS::get_singleton()->execute(bb_deploy, args, true, NULL, &dp, &ec); if (err == OK && ec == 0) { Device dev; dev.index = i; String descr; Vector ls = dp.split("\n"); for (int i = 0; i < ls.size(); i++) { String l = ls[i].strip_edges(); if (l.begins_with("modelfullname::")) { dev.name = l.get_slice("::", 1); descr += "Model: " + dev.name + "\n"; } if (l.begins_with("modelnumber::")) { String s = l.get_slice("::", 1); dev.name += " (" + s + ")"; descr += "Model Number: " + s + "\n"; } if (l.begins_with("scmbundle::")) descr += "OS Version: " + l.get_slice("::", 1) + "\n"; if (l.begins_with("[n]debug_token_expiration::")) descr += "Debug Token Expires:: " + l.get_slice("::", 1) + "\n"; } dev.description = descr; devices.push_back(dev); } } bool changed = false; ea->device_lock->lock(); if (ea->devices.size() != devices.size()) { changed = true; } else { for (int i = 0; i < ea->devices.size(); i++) { if (ea->devices[i].index != devices[i].index) { changed = true; break; } } } if (changed) { ea->devices = devices; ea->devices_changed = true; } ea->device_lock->unlock(); } uint64_t wait = 3000000; uint64_t time = OS::get_singleton()->get_ticks_usec(); while (OS::get_singleton()->get_ticks_usec() - time < wait) { OS::get_singleton()->delay_usec(1000); if (ea->quit_request) break; } } } Error EditorExportPlatformBB10::run(int p_device, int p_flags) { ERR_FAIL_INDEX_V(p_device, devices.size(), ERR_INVALID_PARAMETER); String bb_deploy = EditorSettings::get_singleton()->get("blackberry/host_tools"); bb_deploy = bb_deploy.plus_file("blackberry-deploy"); if (OS::get_singleton()->get_name() == "Windows") bb_deploy += ".bat"; if (!FileAccess::exists(bb_deploy)) { EditorNode::add_io_error("Blackberry Deploy not found:\n" + bb_deploy); return ERR_FILE_NOT_FOUND; } device_lock->lock(); EditorProgress ep("run", "Running on " + devices[p_device].name, 3); //export_temp ep.step("Exporting APK", 0); String export_to = EditorSettings::get_singleton()->get_settings_path().plus_file("/tmp/tmpexport.bar"); Error err = export_project(export_to, true, p_flags); if (err) { device_lock->unlock(); return err; } #if 0 ep.step("Uninstalling..",1); print_line("Uninstalling previous version: "+devices[p_device].name); List args; args.push_back("-s"); args.push_back(devices[p_device].id); args.push_back("uninstall"); args.push_back(package); int rv; err = OS::get_singleton()->execute(adb,args,true,NULL,NULL,&rv); if (err || rv!=0) { EditorNode::add_io_error("Could not install to device."); device_lock->unlock(); return ERR_CANT_CREATE; } print_line("Installing into device (please wait..): "+devices[p_device].name); #endif ep.step("Installing to Device (please wait..)..", 2); List args; args.clear(); args.push_back("-installApp"); args.push_back("-launchApp"); args.push_back("-device"); String host = EditorSettings::get_singleton()->get("blackberry/device_" + itos(p_device + 1) + "/host"); String pass = EditorSettings::get_singleton()->get("blackberry/device_" + itos(p_device + 1) + "/password"); args.push_back(host); args.push_back("-password"); args.push_back(pass); args.push_back(export_to); int rv; err = OS::get_singleton()->execute(bb_deploy, args, true, NULL, NULL, &rv); if (err || rv != 0) { EditorNode::add_io_error("Could not install to device."); device_lock->unlock(); return ERR_CANT_CREATE; } device_lock->unlock(); return OK; } EditorExportPlatformBB10::EditorExportPlatformBB10() { version_code = 1; version_name = "1.0"; package = "com.godot.noname"; category = "core.games"; name = ""; author_name = "Cert. Name"; author_id = "Cert. ID"; description = "Game made with Godot Engine"; device_lock = Mutex::create(); quit_request = false; // Don't start polling devices if we can't export anyway. Means that an engine restart is needed to enable it though, // but it feels better than querying the editor settings all the time in the thread. if (can_export()) { device_thread = Thread::create(_device_poll_thread, this); devices_changed = true; } else { device_thread = NULL; } Image img(_bb10_logo); logo = Ref(memnew(ImageTexture)); logo->create_from_image(img); } bool EditorExportPlatformBB10::can_export(String *r_error) const { bool valid = true; String bb_deploy = EditorSettings::get_singleton()->get("blackberry/host_tools"); String err; if (!FileAccess::exists(bb_deploy.plus_file("blackberry-deploy"))) { valid = false; err += "Blackberry host tools not configured in editor settings.\n"; } if (!exists_export_template("bb10.zip")) { valid = false; err += "No export template found.\nDownload and install export templates.\n"; } String debug_token = EditorSettings::get_singleton()->get("blackberry/debug_token"); if (!FileAccess::exists(debug_token)) { valid = false; err += "No debug token set, will not be able to test on device.\n"; } if (custom_package != "" && !FileAccess::exists(custom_package)) { valid = false; err += "Custom release package not found.\n"; } if (r_error) *r_error = err; return valid; } EditorExportPlatformBB10::~EditorExportPlatformBB10() { quit_request = true; if (device_thread) { Thread::wait_to_finish(device_thread); memdelete(device_thread); device_thread = NULL; } if (device_lock) { memdelete(device_lock); device_lock = NULL; } } void register_bb10_exporter() { EDITOR_DEF("blackberry/host_tools", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "blackberry/host_tools", PROPERTY_HINT_GLOBAL_DIR)); EDITOR_DEF("blackberry/debug_token", ""); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "blackberry/debug_token", PROPERTY_HINT_GLOBAL_FILE, "bar")); EDITOR_DEF("blackberry/device_1/host", ""); EDITOR_DEF("blackberry/device_1/password", ""); EDITOR_DEF("blackberry/device_2/host", ""); EDITOR_DEF("blackberry/device_2/password", ""); EDITOR_DEF("blackberry/device_3/host", ""); EDITOR_DEF("blackberry/device_3/password", ""); EDITOR_DEF("blackberry/device_4/host", ""); EDITOR_DEF("blackberry/device_4/password", ""); EDITOR_DEF("blackberry/device_5/host", ""); EDITOR_DEF("blackberry/device_5/password", ""); Ref exporter = Ref(memnew(EditorExportPlatformBB10)); EditorImportExport::get_singleton()->add_export_platform(exporter); }