/**************************************************************************/ /* export.cpp */ /**************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /**************************************************************************/ /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ /* */ /* 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 "core/io/zip_io.h" #include "core/version.h" #include "core/config/project_settings.h" #include "editor/editor_settings.h" #define TEMPLATE_RELEASE "vita_release.zip" class ExportPluginVita : public EditorExportPlugin { public: Vector editor_id_vec; protected: virtual void _export_begin(const RBSet &p_features, bool p_debug, const String &p_path, int p_flags) { if (editor_id_vec.size() != 0) { add_file("custom_editor_id", editor_id_vec, false); } } }; class EditorExportPlatformVita : public EditorExportPlatform { GDCLASS(EditorExportPlatformVita, EditorExportPlatform) Ref logo; ExportPluginVita *export_plugin; public: virtual void get_preset_features(const Ref &p_preset, List *r_features) { String driver = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name"); if ((driver == "GLES2") || (driver == "GLES3" && ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2"))) { if (p_preset->get("texture_format/etc")) { r_features->push_back("etc"); } if (p_preset->get("texture_format/pvrtc")) { r_features->push_back("pvrtc"); } } } virtual void get_export_options(List *r_options) { String title = ProjectSettings::get_singleton()->get("application/config/name"); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/pvrtc"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "param_sfo/title", PROPERTY_HINT_PLACEHOLDER_TEXT, title), title)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "param_sfo/title_id", PROPERTY_HINT_PLACEHOLDER_TEXT, "GDOT00001 (Make sure it's CAPITALIZED and 9 characters MAX"), "GDOT00001")); r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "param_sfo/parental_level", PROPERTY_HINT_MAX, "11"), 1)); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "param_sfo/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Version XX.YY"), "01.00")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "assets/bubble_icon_128x128", PROPERTY_HINT_GLOBAL_FILE, "*.png"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "assets/app_splash_960x544", PROPERTY_HINT_GLOBAL_FILE, "*.png"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "assets/livearea_bg_840x500", PROPERTY_HINT_GLOBAL_FILE, "*.png"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "assets/livearea_startup_button_280x158", PROPERTY_HINT_GLOBAL_FILE, "*.png"), "")); } virtual String get_name() const { return "PlayStation Vita"; } virtual String get_os_name() const { return "Vita"; } virtual Ref get_logo() const { return logo; } virtual Ref get_run_icon() const { return logo; } virtual bool poll_devices() { return false; } virtual int get_device_count() const { return 2; } virtual Error run(const Ref &p_preset, int p_device, int p_debug_flags) { return OK; } virtual bool has_valid_export_configuration(const Ref &p_preset, String &r_error, bool &r_missing_templates) const { String err; r_missing_templates = find_export_template(TEMPLATE_RELEASE) == String(); bool valid = !r_missing_templates; if (!err.empty()) { r_error = err; } return valid; } virtual bool has_valid_project_configuration(const Ref &p_preset, String &r_error) const { return true; } virtual List get_binary_extensions(const Ref &p_preset) const { List list; list.push_back("vpk"); return list; } void _zip_folder_recursive(zipFile &p_zip, const String &p_root_path, const String &p_folder) { String dir = p_root_path.plus_file(p_folder); DirAccess *da = DirAccess::open(dir); da->list_dir_begin(); String f = da->get_next(); while (!f.empty()) { if (f == "." || f == "..") { f = da->get_next(); continue; } if (da->is_link(f)) { OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); zip_fileinfo zipfi; zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.min; zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Pandemonium month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ zipfi.tmz_date.tm_sec = time.sec; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; // 0120000: symbolic link type // 0000755: permissions rwxr-xr-x // 0000644: permissions rw-r--r-- uint32_t _mode = 0100777; zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); zipfi.internal_fa = 0; zipOpenNewFileInZip4(p_zip, p_folder.plus_file(f).utf8().get_data(), &zipfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, nullptr, 0, 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions 0); String target = da->read_link(f); zipWriteInFileInZip(p_zip, target.utf8().get_data(), target.utf8().size()); zipCloseFileInZip(p_zip); } else if (da->current_is_dir()) { _zip_folder_recursive(p_zip, p_root_path, p_folder.plus_file(f)); } else { OS::Time time = OS::get_singleton()->get_time(); OS::Date date = OS::get_singleton()->get_date(); zip_fileinfo zipfi; zipfi.tmz_date.tm_hour = time.hour; zipfi.tmz_date.tm_mday = date.day; zipfi.tmz_date.tm_min = time.min; zipfi.tmz_date.tm_mon = date.month - 1; // Note: "tm" month range - 0..11, Godot month range - 1..12, http://www.cplusplus.com/reference/ctime/tm/ zipfi.tmz_date.tm_sec = time.sec; zipfi.tmz_date.tm_year = date.year; zipfi.dosDate = 0; // 0100000: regular file type // 0000755: permissions rwxr-xr-x // 0000644: permissions rw-r--r-- uint32_t _mode = 0100777; zipfi.external_fa = (_mode << 16L) | !(_mode & 0200); zipfi.internal_fa = 0; zipOpenNewFileInZip4(p_zip, p_folder.plus_file(f).utf8().get_data(), &zipfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION, 0, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY, nullptr, 0, 0x0314, // "version made by", 0x03 - Unix, 0x14 - ZIP specification version 2.0, required to store Unix file permissions 0); FileAccessRef fa = FileAccess::open(dir.plus_file(f), FileAccess::READ); if (!fa) { add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f))); return; } const int bufsize = 16384; uint8_t buf[bufsize]; while (true) { uint64_t got = fa->get_buffer(buf, bufsize); if (got == 0) { break; } zipWriteInFileInZip(p_zip, buf, got); } zipCloseFileInZip(p_zip); } f = da->get_next(); } da->list_dir_end(); memdelete(da); } void create_vpk(String outVpk, String dir) { FileAccess *dst_f = nullptr; zlib_filefunc_def io_dst = zipio_create_io_from_file(&dst_f); zipFile zip = zipOpen2(outVpk.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io_dst); _zip_folder_recursive(zip, dir, ""); zipClose(zip, NULL); } virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0) { ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); const String base_dir = p_path.get_base_dir(); const String base_path = p_path.get_basename(); const String base_name = p_path.get_file().get_basename(); if (!DirAccess::exists(base_dir)) { return ERR_FILE_BAD_PATH; } String template_path = find_export_template(TEMPLATE_RELEASE); if (template_path != String() && !FileAccess::exists(template_path)) { add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Template file not found: \"%s\"."), template_path)); return ERR_FILE_NOT_FOUND; } DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); Error err; // update nro icon/title/author/version ParamSFOStruct *sfo = memnew(ParamSFOStruct); sfo->title = p_preset->get("param_sfo/title"); sfo->title_id = p_preset->get("param_sfo/title_id"); sfo->version = p_preset->get("param_sfo/version"); sfo->parental_level = p_preset->get("param_sfo/parental_level"); String icon = p_preset->get("assets/bubble_icon_128x128"); String splash = p_preset->get("assets/app_splash_960x544"); String livearea_bg = p_preset->get("assets/livearea_bg_840x500"); String livearea_startup_button = p_preset->get("assets/livearea_startup_button_280x158"); String cache = EditorSettings::get_singleton()->get_cache_dir(); String app_dir = cache.plus_file("app"); da->make_dir(app_dir); String game_data_dir = app_dir.plus_file("game_data"); da->make_dir(game_data_dir); da->make_dir(app_dir.plus_file("module")); da->make_dir_recursive(app_dir.plus_file("sce_sys/livearea/contents")); FileAccess *src_f = nullptr; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); unzFile pkg = unzOpen2(template_path.utf8().get_data(), &io); if (!pkg) { EditorNode::add_io_error("Could not find vita template for exporting:\n" + template_path); return ERR_FILE_NOT_FOUND; } int ret = unzGoToFirstFile(pkg); while (ret == UNZ_OK) { // get file name unz_file_info info; char fname[16384]; ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0); String path = String::utf8(fname); if (path.ends_with("/")) { // Ignore directories ret = unzGoToNextFile(pkg); continue; } Vector data; bool do_read = true; //read if (do_read) { data.resize(info.uncompressed_size); unzOpenCurrentFile(pkg); unzReadCurrentFile(pkg, data.ptrw(), data.size()); unzCloseCurrentFile(pkg); } print_line("ADDING: " + path); FileAccess *fa = FileAccess::open(app_dir.plus_file(path), FileAccess::WRITE); fa->store_buffer(data.ptr(), data.size()); fa->flush(); fa->close(); ret = unzGoToNextFile(pkg); } unzClose(pkg); //String current_version = VERSION_FULL_CONFIG; //String template_path = EditorSettings::get_singleton()->get_templates_dir().plus_file(current_version); Vector shared_objects; err = save_pack(p_preset, game_data_dir.plus_file("game.pck"), &shared_objects); mksfoex(sfo, app_dir.plus_file("sce_sys")); if (err == OK) { if (icon != String() && FileAccess::exists(icon)) { da->copy(icon, app_dir.plus_file("sce_sys/icon0.png")); } if (splash != String() && FileAccess::exists(splash)) { da->copy(splash, app_dir.plus_file("sce_sys/pic0.png")); } if (livearea_bg != String() && FileAccess::exists(livearea_bg)) { da->copy(livearea_bg, app_dir.plus_file("sce_sys/livearea/contents/bg.png")); } if (livearea_startup_button != String() && FileAccess::exists(livearea_startup_button)) { da->copy(livearea_startup_button, app_dir.plus_file("sce_sys/livearea/contents/startup.png")); } } create_vpk(base_path + ".vpk", app_dir); memdelete(sfo); memdelete(da); da = nullptr; return OK; } virtual void get_platform_features(List *r_features) { r_features->push_back("mobile"); r_features->push_back(get_os_name()); } virtual void resolve_platform_feature_priorities(const Ref &p_preset, RBSet &p_features) { } EditorExportPlatformVita() { Ref img = memnew(Image(_vita_logo)); logo.instance(); logo->create_from_image(img); export_plugin = memnew(ExportPluginVita); EditorExport::get_singleton()->add_export_plugin(export_plugin); } ~EditorExportPlatformVita() { } }; void register_vita_exporter() { Ref exporter; exporter.instance(); EditorExport::get_singleton()->add_export_platform(exporter); }