2021-07-14 20:16:48 +00:00
/**************************************************************************/
/* export_plugin.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_plugin.h"
2023-06-19 09:28:22 +00:00
# include "logo_svg.gen.h"
# include "run_icon_svg.gen.h"
2023-06-08 12:51:32 +00:00
2022-02-12 01:46:22 +00:00
# include "core/config/project_settings.h"
2022-07-31 18:14:15 +00:00
# include "editor/editor_settings.h"
2023-08-13 00:33:39 +00:00
# include "editor/editor_string_names.h"
2023-04-12 19:02:28 +00:00
# include "editor/export/editor_export.h"
2023-06-20 02:50:09 +00:00
# include "editor/import/resource_importer_texture_settings.h"
2024-01-15 12:14:55 +00:00
# include "editor/themes/editor_scale.h"
2023-07-11 20:29:09 +00:00
# include "scene/resources/image_texture.h"
2022-05-08 07:46:53 +00:00
2023-06-23 22:01:53 +00:00
# include "modules/modules_enabled.gen.h" // For mono and svg.
2022-05-08 07:46:53 +00:00
# ifdef MODULE_SVG_ENABLED
# include "modules/svg/image_loader_svg.h"
# endif
2022-02-12 01:46:22 +00:00
2022-08-28 18:27:45 +00:00
Error EditorExportPlatformWeb : : _extract_template ( const String & p_template , const String & p_dir , const String & p_name , bool pwa ) {
2022-05-11 12:15:58 +00:00
Ref < FileAccess > io_fa ;
zlib_filefunc_def io = zipio_create_io ( & io_fa ) ;
2021-07-14 20:16:48 +00:00
unzFile pkg = unzOpen2 ( p_template . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! pkg ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , vformat ( TTR ( " Could not open template for export: \" %s \" . " ) , p_template ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_NOT_FOUND ;
}
if ( unzGoToFirstFile ( pkg ) ! = UNZ_OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , vformat ( TTR ( " Invalid export template: \" %s \" . " ) , p_template ) ) ;
2021-07-14 20:16:48 +00:00
unzClose ( pkg ) ;
return ERR_FILE_CORRUPT ;
}
do {
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
unzGetCurrentFileInfo ( pkg , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
2022-01-05 12:27:11 +00:00
String file = String : : utf8 ( fname ) ;
2021-07-14 20:16:48 +00:00
2022-06-03 15:34:24 +00:00
// Skip folders.
if ( file . ends_with ( " / " ) ) {
continue ;
}
2021-07-14 20:16:48 +00:00
// Skip service worker and offline page if not exporting pwa.
if ( ! pwa & & ( file = = " godot.service.worker.js " | | file = = " godot.offline.html " ) ) {
continue ;
}
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
//read
unzOpenCurrentFile ( pkg ) ;
unzReadCurrentFile ( pkg , data . ptrw ( ) , data . size ( ) ) ;
unzCloseCurrentFile ( pkg ) ;
//write
2022-08-30 00:34:01 +00:00
String dst = p_dir . path_join ( file . replace ( " godot " , p_name ) ) ;
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( dst , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , dst ) ) ;
2021-07-14 20:16:48 +00:00
unzClose ( pkg ) ;
return ERR_FILE_CANT_WRITE ;
}
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
} while ( unzGoToNextFile ( pkg ) = = UNZ_OK ) ;
unzClose ( pkg ) ;
return OK ;
}
2022-08-28 18:27:45 +00:00
Error EditorExportPlatformWeb : : _write_or_error ( const uint8_t * p_content , int p_size , String p_path ) {
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( p_path , FileAccess : : WRITE ) ;
if ( f . is_null ( ) ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , p_path ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_CANT_WRITE ;
}
f - > store_buffer ( p_content , p_size ) ;
return OK ;
}
2021-08-01 19:47:20 +00:00
void EditorExportPlatformWeb : : _replace_strings ( const HashMap < String , String > & p_replaces , Vector < uint8_t > & r_template ) {
2021-07-14 20:16:48 +00:00
String str_template = String : : utf8 ( reinterpret_cast < const char * > ( r_template . ptr ( ) ) , r_template . size ( ) ) ;
String out ;
Vector < String > lines = str_template . split ( " \n " ) ;
for ( int i = 0 ; i < lines . size ( ) ; i + + ) {
String current_line = lines [ i ] ;
2021-08-09 20:13:42 +00:00
for ( const KeyValue < String , String > & E : p_replaces ) {
current_line = current_line . replace ( E . key , E . value ) ;
2021-07-14 20:16:48 +00:00
}
out + = current_line + " \n " ;
}
CharString cs = out . utf8 ( ) ;
r_template . resize ( cs . length ( ) ) ;
for ( int i = 0 ; i < cs . length ( ) ; i + + ) {
r_template . write [ i ] = cs [ i ] ;
}
}
2022-08-28 18:27:45 +00:00
void EditorExportPlatformWeb : : _fix_html ( Vector < uint8_t > & p_html , const Ref < EditorExportPreset > & p_preset , const String & p_name , bool p_debug , int p_flags , const Vector < SharedObject > p_shared_objects , const Dictionary & p_file_sizes ) {
2021-07-14 20:16:48 +00:00
// Engine.js config
Dictionary config ;
Array libs ;
for ( int i = 0 ; i < p_shared_objects . size ( ) ; i + + ) {
libs . push_back ( p_shared_objects [ i ] . path . get_file ( ) ) ;
}
Vector < String > flags ;
gen_export_flags ( flags , p_flags & ( ~ DEBUG_FLAG_DUMB_CLIENT ) ) ;
Array args ;
for ( int i = 0 ; i < flags . size ( ) ; i + + ) {
args . push_back ( flags [ i ] ) ;
}
config [ " canvasResizePolicy " ] = p_preset - > get ( " html/canvas_resize_policy " ) ;
config [ " experimentalVK " ] = p_preset - > get ( " html/experimental_virtual_keyboard " ) ;
config [ " focusCanvas " ] = p_preset - > get ( " html/focus_canvas_on_start " ) ;
2022-12-07 11:11:28 +00:00
config [ " gdextensionLibs " ] = libs ;
2021-07-14 20:16:48 +00:00
config [ " executable " ] = p_name ;
config [ " args " ] = args ;
config [ " fileSizes " ] = p_file_sizes ;
String head_include ;
if ( p_preset - > get ( " html/export_icon " ) ) {
head_include + = " <link id='-gd-engine-icon' rel='icon' type='image/png' href=' " + p_name + " .icon.png' /> \n " ;
head_include + = " <link rel='apple-touch-icon' href=' " + p_name + " .apple-touch-icon.png'/> \n " ;
}
if ( p_preset - > get ( " progressive_web_app/enabled " ) ) {
head_include + = " <link rel='manifest' href=' " + p_name + " .manifest.json'> \n " ;
2021-12-21 13:41:26 +00:00
config [ " serviceWorker " ] = p_name + " .service.worker.js " ;
2021-07-14 20:16:48 +00:00
}
// Replaces HTML string
const String str_config = Variant ( config ) . to_json_string ( ) ;
const String custom_head_include = p_preset - > get ( " html/head_include " ) ;
2022-05-13 13:04:37 +00:00
HashMap < String , String > replaces ;
2021-07-14 20:16:48 +00:00
replaces [ " $GODOT_URL " ] = p_name + " .js " ;
2023-01-13 12:16:49 +00:00
replaces [ " $GODOT_PROJECT_NAME " ] = GLOBAL_GET ( " application/config/name " ) ;
2021-07-14 20:16:48 +00:00
replaces [ " $GODOT_HEAD_INCLUDE " ] = head_include + custom_head_include ;
replaces [ " $GODOT_CONFIG " ] = str_config ;
2023-12-01 18:39:09 +00:00
if ( p_preset - > get ( " variant/thread_support " ) ) {
replaces [ " $GODOT_THREADS_ENABLED " ] = " true " ;
} else {
replaces [ " $GODOT_THREADS_ENABLED " ] = " false " ;
}
2021-07-14 20:16:48 +00:00
_replace_strings ( replaces , p_html ) ;
}
2022-08-28 18:27:45 +00:00
Error EditorExportPlatformWeb : : _add_manifest_icon ( const String & p_path , const String & p_icon , int p_size , Array & r_arr ) {
2021-07-14 20:16:48 +00:00
const String name = p_path . get_file ( ) . get_basename ( ) ;
const String icon_name = vformat ( " %s.%dx%d.png " , name , p_size , p_size ) ;
2022-08-30 00:34:01 +00:00
const String icon_dest = p_path . get_base_dir ( ) . path_join ( icon_name ) ;
2021-07-14 20:16:48 +00:00
Ref < Image > icon ;
if ( ! p_icon . is_empty ( ) ) {
icon . instantiate ( ) ;
const Error err = ImageLoader : : load_image ( p_icon , icon ) ;
if ( err ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Icon Creation " ) , vformat ( TTR ( " Could not read file: \" %s \" . " ) , p_icon ) ) ;
2021-07-14 20:16:48 +00:00
return err ;
}
if ( icon - > get_width ( ) ! = p_size | | icon - > get_height ( ) ! = p_size ) {
icon - > resize ( p_size , p_size ) ;
}
} else {
icon = _get_project_icon ( ) ;
icon - > resize ( p_size , p_size ) ;
}
const Error err = icon - > save_png ( icon_dest ) ;
if ( err ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Icon Creation " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , icon_dest ) ) ;
2021-07-14 20:16:48 +00:00
return err ;
}
Dictionary icon_dict ;
icon_dict [ " sizes " ] = vformat ( " %dx%d " , p_size , p_size ) ;
icon_dict [ " type " ] = " image/png " ;
icon_dict [ " src " ] = icon_name ;
r_arr . push_back ( icon_dict ) ;
return err ;
}
2022-08-28 18:27:45 +00:00
Error EditorExportPlatformWeb : : _build_pwa ( const Ref < EditorExportPreset > & p_preset , const String p_path , const Vector < SharedObject > & p_shared_objects ) {
2023-01-13 12:16:49 +00:00
String proj_name = GLOBAL_GET ( " application/config/name " ) ;
2021-12-21 13:41:26 +00:00
if ( proj_name . is_empty ( ) ) {
proj_name = " Godot Game " ;
}
2021-07-14 20:16:48 +00:00
// Service worker
const String dir = p_path . get_base_dir ( ) ;
const String name = p_path . get_file ( ) . get_basename ( ) ;
2022-08-29 13:56:28 +00:00
bool extensions = ( bool ) p_preset - > get ( " variant/extensions_support " ) ;
2022-05-13 13:04:37 +00:00
HashMap < String , String > replaces ;
2023-12-01 18:39:09 +00:00
replaces [ " ___GODOT_VERSION___ " ] = String : : num_int64 ( OS : : get_singleton ( ) - > get_unix_time ( ) ) + " | " + String : : num_int64 ( OS : : get_singleton ( ) - > get_ticks_usec ( ) ) ;
replaces [ " ___GODOT_NAME___ " ] = proj_name . substr ( 0 , 16 ) ;
replaces [ " ___GODOT_OFFLINE_PAGE___ " ] = name + " .offline.html " ;
2021-12-21 13:41:26 +00:00
// Files cached during worker install.
Array cache_files ;
cache_files . push_back ( name + " .html " ) ;
cache_files . push_back ( name + " .js " ) ;
cache_files . push_back ( name + " .offline.html " ) ;
2021-07-14 20:16:48 +00:00
if ( p_preset - > get ( " html/export_icon " ) ) {
2021-12-21 13:41:26 +00:00
cache_files . push_back ( name + " .icon.png " ) ;
cache_files . push_back ( name + " .apple-touch-icon.png " ) ;
2021-07-14 20:16:48 +00:00
}
2022-08-29 13:56:28 +00:00
cache_files . push_back ( name + " .worker.js " ) ;
cache_files . push_back ( name + " .audio.worklet.js " ) ;
2023-12-01 18:39:09 +00:00
replaces [ " ___GODOT_CACHE___ " ] = Variant ( cache_files ) . to_json_string ( ) ;
2021-12-21 13:41:26 +00:00
// Heavy files that are cached on demand.
Array opt_cache_files ;
opt_cache_files . push_back ( name + " .wasm " ) ;
opt_cache_files . push_back ( name + " .pck " ) ;
2022-08-29 13:56:28 +00:00
if ( extensions ) {
2021-12-21 13:41:26 +00:00
opt_cache_files . push_back ( name + " .side.wasm " ) ;
2021-07-14 20:16:48 +00:00
for ( int i = 0 ; i < p_shared_objects . size ( ) ; i + + ) {
2021-12-21 13:41:26 +00:00
opt_cache_files . push_back ( p_shared_objects [ i ] . path . get_file ( ) ) ;
2021-07-14 20:16:48 +00:00
}
}
2023-12-01 18:39:09 +00:00
replaces [ " ___GODOT_OPT_CACHE___ " ] = Variant ( opt_cache_files ) . to_json_string ( ) ;
2021-07-14 20:16:48 +00:00
2022-08-30 00:34:01 +00:00
const String sw_path = dir . path_join ( name + " .service.worker.js " ) ;
2021-07-14 20:16:48 +00:00
Vector < uint8_t > sw ;
{
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( sw_path , FileAccess : : READ ) ;
if ( f . is_null ( ) ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " PWA " ) , vformat ( TTR ( " Could not read file: \" %s \" . " ) , sw_path ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_CANT_READ ;
}
sw . resize ( f - > get_length ( ) ) ;
f - > get_buffer ( sw . ptrw ( ) , sw . size ( ) ) ;
}
_replace_strings ( replaces , sw ) ;
2022-08-30 00:34:01 +00:00
Error err = _write_or_error ( sw . ptr ( ) , sw . size ( ) , dir . path_join ( name + " .service.worker.js " ) ) ;
2021-07-14 20:16:48 +00:00
if ( err ! = OK ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return err ;
}
// Custom offline page
const String offline_page = p_preset - > get ( " progressive_web_app/offline_page " ) ;
if ( ! offline_page . is_empty ( ) ) {
2022-03-23 09:08:58 +00:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2022-08-30 00:34:01 +00:00
const String offline_dest = dir . path_join ( name + " .offline.html " ) ;
2021-07-14 20:16:48 +00:00
err = da - > copy ( ProjectSettings : : get_singleton ( ) - > globalize_path ( offline_page ) , offline_dest ) ;
if ( err ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " PWA " ) , vformat ( TTR ( " Could not read file: \" %s \" . " ) , offline_dest ) ) ;
2021-07-14 20:16:48 +00:00
return err ;
}
}
// Manifest
const char * modes [ 4 ] = { " fullscreen " , " standalone " , " minimal-ui " , " browser " } ;
const char * orientations [ 3 ] = { " any " , " landscape " , " portrait " } ;
const int display = CLAMP ( int ( p_preset - > get ( " progressive_web_app/display " ) ) , 0 , 4 ) ;
const int orientation = CLAMP ( int ( p_preset - > get ( " progressive_web_app/orientation " ) ) , 0 , 3 ) ;
Dictionary manifest ;
manifest [ " name " ] = proj_name ;
manifest [ " start_url " ] = " ./ " + name + " .html " ;
manifest [ " display " ] = String : : utf8 ( modes [ display ] ) ;
manifest [ " orientation " ] = String : : utf8 ( orientations [ orientation ] ) ;
manifest [ " background_color " ] = " # " + p_preset - > get ( " progressive_web_app/background_color " ) . operator Color ( ) . to_html ( false ) ;
Array icons_arr ;
const String icon144_path = p_preset - > get ( " progressive_web_app/icon_144x144 " ) ;
err = _add_manifest_icon ( p_path , icon144_path , 144 , icons_arr ) ;
if ( err ! = OK ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return err ;
}
const String icon180_path = p_preset - > get ( " progressive_web_app/icon_180x180 " ) ;
err = _add_manifest_icon ( p_path , icon180_path , 180 , icons_arr ) ;
if ( err ! = OK ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return err ;
}
const String icon512_path = p_preset - > get ( " progressive_web_app/icon_512x512 " ) ;
err = _add_manifest_icon ( p_path , icon512_path , 512 , icons_arr ) ;
if ( err ! = OK ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return err ;
}
manifest [ " icons " ] = icons_arr ;
CharString cs = Variant ( manifest ) . to_json_string ( ) . utf8 ( ) ;
2022-08-30 00:34:01 +00:00
err = _write_or_error ( ( const uint8_t * ) cs . get_data ( ) , cs . length ( ) , dir . path_join ( name + " .manifest.json " ) ) ;
2021-07-14 20:16:48 +00:00
if ( err ! = OK ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return err ;
}
return OK ;
}
2022-08-28 18:27:45 +00:00
void EditorExportPlatformWeb : : get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) const {
2021-07-14 20:16:48 +00:00
if ( p_preset - > get ( " vram_texture_compression/for_desktop " ) ) {
r_features - > push_back ( " s3tc " ) ;
}
if ( p_preset - > get ( " vram_texture_compression/for_mobile " ) ) {
2022-09-08 00:44:36 +00:00
r_features - > push_back ( " etc2 " ) ;
2021-07-14 20:16:48 +00:00
}
2022-08-29 13:56:28 +00:00
r_features - > push_back ( " wasm32 " ) ;
2021-07-14 20:16:48 +00:00
}
2023-03-09 08:41:52 +00:00
void EditorExportPlatformWeb : : get_export_options ( List < ExportOption > * r_options ) const {
2021-07-14 20:16:48 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_template/debug " , PROPERTY_HINT_GLOBAL_FILE , " *.zip " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " custom_template/release " , PROPERTY_HINT_GLOBAL_FILE , " *.zip " ) , " " ) ) ;
2022-08-29 13:56:28 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " variant/extensions_support " ) , false ) ) ; // Export type.
2023-12-01 18:39:09 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " variant/thread_support " ) , true ) ) ; // Thread support (i.e. run with or without COEP/COOP headers).
2021-07-14 20:16:48 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " vram_texture_compression/for_desktop " ) , true ) ) ; // S3TC
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " vram_texture_compression/for_mobile " ) , false ) ) ; // ETC or ETC2, depending on renderer
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/export_icon " ) , true ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " html/custom_html_shell " , PROPERTY_HINT_FILE , " *.html " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " html/head_include " , PROPERTY_HINT_MULTILINE_TEXT ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " html/canvas_resize_policy " , PROPERTY_HINT_ENUM , " None,Project,Adaptive " ) , 2 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/focus_canvas_on_start " ) , true ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/experimental_virtual_keyboard " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " progressive_web_app/enabled " ) , false ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " progressive_web_app/offline_page " , PROPERTY_HINT_FILE , " *.html " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " progressive_web_app/display " , PROPERTY_HINT_ENUM , " Fullscreen,Standalone,Minimal UI,Browser " ) , 1 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " progressive_web_app/orientation " , PROPERTY_HINT_ENUM , " Any,Landscape,Portrait " ) , 0 ) ) ;
2022-01-17 11:39:57 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " progressive_web_app/icon_144x144 " , PROPERTY_HINT_FILE , " *.png,*.webp,*.svg " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " progressive_web_app/icon_180x180 " , PROPERTY_HINT_FILE , " *.png,*.webp,*.svg " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " progressive_web_app/icon_512x512 " , PROPERTY_HINT_FILE , " *.png,*.webp,*.svg " ) , " " ) ) ;
2021-07-14 20:16:48 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : COLOR , " progressive_web_app/background_color " , PROPERTY_HINT_COLOR_NO_ALPHA ) , Color ( ) ) ) ;
}
2022-08-28 18:27:45 +00:00
String EditorExportPlatformWeb : : get_name ( ) const {
return " Web " ;
2021-07-14 20:16:48 +00:00
}
2022-08-28 18:27:45 +00:00
String EditorExportPlatformWeb : : get_os_name ( ) const {
return " Web " ;
2021-07-14 20:16:48 +00:00
}
2022-08-28 18:27:45 +00:00
Ref < Texture2D > EditorExportPlatformWeb : : get_logo ( ) const {
2021-07-14 20:16:48 +00:00
return logo ;
}
2023-06-11 17:01:39 +00:00
bool EditorExportPlatformWeb : : has_valid_export_configuration ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates , bool p_debug ) const {
2023-06-23 22:01:53 +00:00
# ifdef MODULE_MONO_ENABLED
// Don't check for additional errors, as this particular error cannot be resolved.
2023-07-11 20:11:51 +00:00
r_error + = TTR ( " Exporting to Web is currently not supported in Godot 4 when using C#/.NET. Use Godot 3 to target Web with C#/Mono instead. " ) + " \n " ;
r_error + = TTR ( " If this project does not use C#, use a non-C# editor build to export the project. " ) + " \n " ;
2023-06-23 22:01:53 +00:00
return false ;
2023-07-11 20:11:51 +00:00
# else
String err ;
bool valid = false ;
bool extensions = ( bool ) p_preset - > get ( " variant/extensions_support " ) ;
2023-12-01 18:39:09 +00:00
bool thread_support = ( bool ) p_preset - > get ( " variant/thread_support " ) ;
2023-06-23 22:01:53 +00:00
2021-07-14 20:16:48 +00:00
// Look for export templates (first official, and if defined custom templates).
2023-12-01 18:39:09 +00:00
bool dvalid = exists_export_template ( _get_template_name ( extensions , thread_support , true ) , & err ) ;
bool rvalid = exists_export_template ( _get_template_name ( extensions , thread_support , false ) , & err ) ;
2021-07-14 20:16:48 +00:00
if ( p_preset - > get ( " custom_template/debug " ) ! = " " ) {
dvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/debug " ) ) ;
if ( ! dvalid ) {
err + = TTR ( " Custom debug template not found. " ) + " \n " ;
}
}
if ( p_preset - > get ( " custom_template/release " ) ! = " " ) {
rvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/release " ) ) ;
if ( ! rvalid ) {
err + = TTR ( " Custom release template not found. " ) + " \n " ;
}
}
valid = dvalid | | rvalid ;
r_missing_templates = ! valid ;
2022-07-17 19:26:03 +00:00
if ( ! err . is_empty ( ) ) {
r_error = err ;
}
return valid ;
2023-07-11 20:11:51 +00:00
# endif // !MODULE_MONO_ENABLED
2022-07-17 19:26:03 +00:00
}
2022-08-28 18:27:45 +00:00
bool EditorExportPlatformWeb : : has_valid_project_configuration ( const Ref < EditorExportPreset > & p_preset , String & r_error ) const {
2022-07-17 19:26:03 +00:00
String err ;
bool valid = true ;
// Validate the project configuration.
2021-07-14 20:16:48 +00:00
if ( p_preset - > get ( " vram_texture_compression/for_mobile " ) ) {
2023-06-20 02:50:09 +00:00
if ( ! ResourceImporterTextureSettings : : should_import_etc2_astc ( ) ) {
2021-07-14 20:16:48 +00:00
valid = false ;
}
}
if ( ! err . is_empty ( ) ) {
r_error = err ;
}
return valid ;
}
2022-08-28 18:27:45 +00:00
List < String > EditorExportPlatformWeb : : get_binary_extensions ( const Ref < EditorExportPreset > & p_preset ) const {
2021-07-14 20:16:48 +00:00
List < String > list ;
list . push_back ( " html " ) ;
return list ;
}
2022-08-28 18:27:45 +00:00
Error EditorExportPlatformWeb : : export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags ) {
2021-07-14 20:16:48 +00:00
ExportNotifier notifier ( * this , p_preset , p_debug , p_path , p_flags ) ;
const String custom_debug = p_preset - > get ( " custom_template/debug " ) ;
const String custom_release = p_preset - > get ( " custom_template/release " ) ;
const String custom_html = p_preset - > get ( " html/custom_html_shell " ) ;
const bool export_icon = p_preset - > get ( " html/export_icon " ) ;
const bool pwa = p_preset - > get ( " progressive_web_app/enabled " ) ;
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 ( ) ;
2023-12-06 14:18:35 +00:00
if ( ! DirAccess : : exists ( base_dir ) ) {
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Target folder does not exist or is inaccessible: \" %s \" " ) , base_dir ) ) ;
return ERR_FILE_BAD_PATH ;
}
2021-07-14 20:16:48 +00:00
// Find the correct template
String template_path = p_debug ? custom_debug : custom_release ;
template_path = template_path . strip_edges ( ) ;
2021-12-09 09:42:46 +00:00
if ( template_path . is_empty ( ) ) {
2022-08-29 13:56:28 +00:00
bool extensions = ( bool ) p_preset - > get ( " variant/extensions_support " ) ;
2023-12-01 18:39:09 +00:00
bool thread_support = ( bool ) p_preset - > get ( " variant/thread_support " ) ;
template_path = find_export_template ( _get_template_name ( extensions , thread_support , p_debug ) ) ;
2021-07-14 20:16:48 +00:00
}
2021-12-09 09:42:46 +00:00
if ( ! template_path . is_empty ( ) & & ! FileAccess : : exists ( template_path ) ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Prepare Templates " ) , vformat ( TTR ( " Template file not found: \" %s \" . " ) , template_path ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_NOT_FOUND ;
}
// Export pck and shared objects
Vector < SharedObject > shared_objects ;
String pck_path = base_path + " .pck " ;
2022-03-10 07:48:25 +00:00
Error error = save_pack ( p_preset , p_debug , pck_path , & shared_objects ) ;
2021-07-14 20:16:48 +00:00
if ( error ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , pck_path ) ) ;
2021-07-14 20:16:48 +00:00
return error ;
}
2022-03-10 14:27:09 +00:00
{
2022-03-23 09:08:58 +00:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2022-03-10 14:27:09 +00:00
for ( int i = 0 ; i < shared_objects . size ( ) ; i + + ) {
2022-08-30 00:34:01 +00:00
String dst = base_dir . path_join ( shared_objects [ i ] . path . get_file ( ) ) ;
2022-03-10 14:27:09 +00:00
error = da - > copy ( shared_objects [ i ] . path , dst ) ;
if ( error ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , shared_objects [ i ] . path . get_file ( ) ) ) ;
2022-03-10 14:27:09 +00:00
return error ;
}
2021-07-14 20:16:48 +00:00
}
}
// Extract templates.
error = _extract_template ( template_path , base_dir , base_name , pwa ) ;
if ( error ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return error ;
}
// Parse generated file sizes (pck and wasm, to help show a meaningful loading bar).
Dictionary file_sizes ;
2022-03-23 09:08:58 +00:00
Ref < FileAccess > f = FileAccess : : open ( pck_path , FileAccess : : READ ) ;
if ( f . is_valid ( ) ) {
2021-07-14 20:16:48 +00:00
file_sizes [ pck_path . get_file ( ) ] = ( uint64_t ) f - > get_length ( ) ;
}
f = FileAccess : : open ( base_path + " .wasm " , FileAccess : : READ ) ;
2022-03-23 09:08:58 +00:00
if ( f . is_valid ( ) ) {
2021-07-14 20:16:48 +00:00
file_sizes [ base_name + " .wasm " ] = ( uint64_t ) f - > get_length ( ) ;
}
// Read the HTML shell file (custom or from template).
const String html_path = custom_html . is_empty ( ) ? base_path + " .html " : custom_html ;
Vector < uint8_t > html ;
f = FileAccess : : open ( html_path , FileAccess : : READ ) ;
2022-03-23 09:08:58 +00:00
if ( f . is_null ( ) ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not read HTML shell: \" %s \" . " ) , html_path ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_CANT_READ ;
}
html . resize ( f - > get_length ( ) ) ;
f - > get_buffer ( html . ptrw ( ) , html . size ( ) ) ;
2022-10-11 12:49:51 +00:00
f . unref ( ) ; // close file.
2021-07-14 20:16:48 +00:00
// Generate HTML file with replaced strings.
_fix_html ( html , p_preset , base_name , p_debug , p_flags , shared_objects , file_sizes ) ;
Error err = _write_or_error ( html . ptr ( ) , html . size ( ) , p_path ) ;
if ( err ! = OK ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return err ;
}
html . resize ( 0 ) ;
// Export splash (why?)
Ref < Image > splash = _get_project_splash ( ) ;
const String splash_png_path = base_path + " .png " ;
if ( splash - > save_png ( splash_png_path ) ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , splash_png_path ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_CANT_WRITE ;
}
// Save a favicon that can be accessed without waiting for the project to finish loading.
// This way, the favicon can be displayed immediately when loading the page.
if ( export_icon ) {
Ref < Image > favicon = _get_project_icon ( ) ;
const String favicon_png_path = base_path + " .icon.png " ;
if ( favicon - > save_png ( favicon_png_path ) ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , favicon_png_path ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_CANT_WRITE ;
}
favicon - > resize ( 180 , 180 ) ;
const String apple_icon_png_path = base_path + " .apple-touch-icon.png " ;
if ( favicon - > save_png ( apple_icon_png_path ) ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Export " ) , vformat ( TTR ( " Could not write file: \" %s \" . " ) , apple_icon_png_path ) ) ;
2021-07-14 20:16:48 +00:00
return ERR_FILE_CANT_WRITE ;
}
}
// Generate the PWA worker and manifest
if ( pwa ) {
err = _build_pwa ( p_preset , p_path , shared_objects ) ;
if ( err ! = OK ) {
2023-12-06 14:18:35 +00:00
// Message is supplied by the subroutine method.
2021-07-14 20:16:48 +00:00
return err ;
}
}
return OK ;
}
2022-08-28 18:27:45 +00:00
bool EditorExportPlatformWeb : : poll_export ( ) {
2021-07-14 20:16:48 +00:00
Ref < EditorExportPreset > preset ;
for ( int i = 0 ; i < EditorExport : : get_singleton ( ) - > get_export_preset_count ( ) ; i + + ) {
Ref < EditorExportPreset > ep = EditorExport : : get_singleton ( ) - > get_export_preset ( i ) ;
if ( ep - > is_runnable ( ) & & ep - > get_platform ( ) = = this ) {
preset = ep ;
break ;
}
}
int prev = menu_options ;
menu_options = preset . is_valid ( ) ;
if ( server - > is_listening ( ) ) {
if ( menu_options = = 0 ) {
MutexLock lock ( server_lock ) ;
server - > stop ( ) ;
} else {
menu_options + = 1 ;
}
}
return menu_options ! = prev ;
}
2022-08-28 18:27:45 +00:00
Ref < ImageTexture > EditorExportPlatformWeb : : get_option_icon ( int p_index ) const {
2021-07-14 20:16:48 +00:00
return p_index = = 1 ? stop_icon : EditorExportPlatform : : get_option_icon ( p_index ) ;
}
2022-08-28 18:27:45 +00:00
int EditorExportPlatformWeb : : get_options_count ( ) const {
2021-07-14 20:16:48 +00:00
return menu_options ;
}
2022-08-28 18:27:45 +00:00
Error EditorExportPlatformWeb : : run ( const Ref < EditorExportPreset > & p_preset , int p_option , int p_debug_flags ) {
2021-07-14 20:16:48 +00:00
if ( p_option = = 1 ) {
MutexLock lock ( server_lock ) ;
server - > stop ( ) ;
return OK ;
}
2022-08-30 00:34:01 +00:00
const String dest = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . path_join ( " web " ) ;
2022-03-23 09:08:58 +00:00
Ref < DirAccess > da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
2021-07-14 20:16:48 +00:00
if ( ! da - > dir_exists ( dest ) ) {
Error err = da - > make_dir_recursive ( dest ) ;
if ( err ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , vformat ( TTR ( " Could not create HTTP server directory: %s. " ) , dest ) ) ;
2021-07-14 20:16:48 +00:00
return err ;
}
}
2022-06-03 15:34:24 +00:00
2022-08-30 00:34:01 +00:00
const String basepath = dest . path_join ( " tmp_js_export " ) ;
2021-07-14 20:16:48 +00:00
Error err = export_project ( p_preset , true , basepath + " .html " , p_debug_flags ) ;
if ( err ! = OK ) {
// Export generates several files, clean them up on failure.
DirAccess : : remove_file_or_error ( basepath + " .html " ) ;
DirAccess : : remove_file_or_error ( basepath + " .offline.html " ) ;
DirAccess : : remove_file_or_error ( basepath + " .js " ) ;
DirAccess : : remove_file_or_error ( basepath + " .worker.js " ) ;
DirAccess : : remove_file_or_error ( basepath + " .audio.worklet.js " ) ;
DirAccess : : remove_file_or_error ( basepath + " .service.worker.js " ) ;
DirAccess : : remove_file_or_error ( basepath + " .pck " ) ;
DirAccess : : remove_file_or_error ( basepath + " .png " ) ;
DirAccess : : remove_file_or_error ( basepath + " .side.wasm " ) ;
DirAccess : : remove_file_or_error ( basepath + " .wasm " ) ;
DirAccess : : remove_file_or_error ( basepath + " .icon.png " ) ;
DirAccess : : remove_file_or_error ( basepath + " .apple-touch-icon.png " ) ;
return err ;
}
const uint16_t bind_port = EDITOR_GET ( " export/web/http_port " ) ;
// Resolve host if needed.
const String bind_host = EDITOR_GET ( " export/web/http_host " ) ;
IPAddress bind_ip ;
if ( bind_host . is_valid_ip_address ( ) ) {
bind_ip = bind_host ;
} else {
bind_ip = IP : : get_singleton ( ) - > resolve_hostname ( bind_host ) ;
}
ERR_FAIL_COND_V_MSG ( ! bind_ip . is_valid ( ) , ERR_INVALID_PARAMETER , " Invalid editor setting 'export/web/http_host': ' " + bind_host + " '. Try using '127.0.0.1'. " ) ;
2022-09-07 06:25:47 +00:00
const bool use_tls = EDITOR_GET ( " export/web/use_tls " ) ;
const String tls_key = EDITOR_GET ( " export/web/tls_key " ) ;
const String tls_cert = EDITOR_GET ( " export/web/tls_certificate " ) ;
2021-07-14 20:16:48 +00:00
// Restart server.
{
MutexLock lock ( server_lock ) ;
server - > stop ( ) ;
2022-09-07 06:25:47 +00:00
err = server - > listen ( bind_port , bind_ip , use_tls , tls_key , tls_cert ) ;
2021-07-14 20:16:48 +00:00
}
if ( err ! = OK ) {
2022-06-03 15:34:24 +00:00
add_message ( EXPORT_MESSAGE_ERROR , TTR ( " Run " ) , vformat ( TTR ( " Error starting HTTP server: %d. " ) , err ) ) ;
2021-07-14 20:16:48 +00:00
return err ;
}
2022-09-07 06:25:47 +00:00
OS : : get_singleton ( ) - > shell_open ( String ( ( use_tls ? " https:// " : " http:// " ) + bind_host + " : " + itos ( bind_port ) + " /tmp_js_export.html " ) ) ;
2021-07-14 20:16:48 +00:00
// FIXME: Find out how to clean up export files after running the successfully
// exported game. Might not be trivial.
return OK ;
}
2022-08-28 18:27:45 +00:00
Ref < Texture2D > EditorExportPlatformWeb : : get_run_icon ( ) const {
2021-07-14 20:16:48 +00:00
return run_icon ;
}
2022-08-28 18:27:45 +00:00
void EditorExportPlatformWeb : : _server_thread_poll ( void * data ) {
EditorExportPlatformWeb * ej = static_cast < EditorExportPlatformWeb * > ( data ) ;
2024-02-07 03:27:38 +00:00
while ( ! ej - > server_quit . get ( ) ) {
2022-02-14 07:59:02 +00:00
OS : : get_singleton ( ) - > delay_usec ( 6900 ) ;
2021-07-14 20:16:48 +00:00
{
MutexLock lock ( ej - > server_lock ) ;
ej - > server - > poll ( ) ;
}
}
}
2022-08-28 18:27:45 +00:00
EditorExportPlatformWeb : : EditorExportPlatformWeb ( ) {
2023-03-09 08:41:52 +00:00
if ( EditorNode : : get_singleton ( ) ) {
server . instantiate ( ) ;
server_thread . start ( _server_thread_poll , this ) ;
2021-07-14 20:16:48 +00:00
2022-05-08 07:46:53 +00:00
# ifdef MODULE_SVG_ENABLED
2023-03-09 08:41:52 +00:00
Ref < Image > img = memnew ( Image ) ;
const bool upsample = ! Math : : is_equal_approx ( Math : : round ( EDSCALE ) , EDSCALE ) ;
2022-05-08 07:46:53 +00:00
2023-06-14 23:27:56 +00:00
ImageLoaderSVG : : create_image_from_string ( img , _web_logo_svg , EDSCALE , upsample , false ) ;
2023-03-09 08:41:52 +00:00
logo = ImageTexture : : create_from_image ( img ) ;
2022-05-08 07:46:53 +00:00
2023-06-14 23:27:56 +00:00
ImageLoaderSVG : : create_image_from_string ( img , _web_run_icon_svg , EDSCALE , upsample , false ) ;
2023-03-09 08:41:52 +00:00
run_icon = ImageTexture : : create_from_image ( img ) ;
2022-05-08 07:46:53 +00:00
# endif
2021-07-14 20:16:48 +00:00
2023-03-09 08:41:52 +00:00
Ref < Theme > theme = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) ;
if ( theme . is_valid ( ) ) {
2023-08-13 00:33:39 +00:00
stop_icon = theme - > get_icon ( SNAME ( " Stop " ) , EditorStringName ( EditorIcons ) ) ;
2023-03-09 08:41:52 +00:00
} else {
stop_icon . instantiate ( ) ;
}
2021-07-14 20:16:48 +00:00
}
}
2022-08-28 18:27:45 +00:00
EditorExportPlatformWeb : : ~ EditorExportPlatformWeb ( ) {
2023-03-09 08:41:52 +00:00
if ( server . is_valid ( ) ) {
server - > stop ( ) ;
}
2024-02-07 03:27:38 +00:00
server_quit . set ( true ) ;
2023-04-27 16:34:30 +00:00
if ( server_thread . is_started ( ) ) {
server_thread . wait_to_finish ( ) ;
}
2021-07-14 20:16:48 +00:00
}