2014-02-10 02:10:30 +01:00
/*************************************************************************/
/* export.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
2017-08-27 14:16:55 +02:00
/* https://godotengine.org */
2014-02-10 02:10:30 +01:00
/*************************************************************************/
2021-01-01 20:13:46 +01:00
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
2014-02-10 02:10:30 +01:00
/* */
/* 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. */
/*************************************************************************/
2018-01-05 00:50:27 +01:00
2021-03-26 12:44:17 +01:00
# include "core/io/image_loader.h"
2021-03-11 22:40:30 +01:00
# include "core/io/stream_peer_ssl.h"
2019-08-24 17:27:38 +02:00
# include "core/io/tcp_server.h"
2018-09-11 18:13:45 +02:00
# include "core/io/zip_io.h"
2019-10-09 16:11:04 +02:00
# include "editor/editor_export.h"
2017-03-05 14:21:25 +01:00
# include "editor/editor_node.h"
2017-11-20 00:06:11 +01:00
# include "main/splash.gen.h"
2017-06-23 17:03:41 +02:00
# include "platform/javascript/logo.gen.h"
2017-05-25 20:57:13 +02:00
# include "platform/javascript/run_icon.gen.h"
2017-01-26 01:55:59 +01:00
2021-06-04 18:03:15 +02:00
class EditorHTTPServer : public RefCounted {
2019-08-24 17:27:38 +02:00
private :
2021-05-06 02:48:18 +02:00
Ref < TCPServer > server ;
2021-03-26 11:48:56 +01:00
Map < String , String > mimes ;
2021-03-11 22:40:30 +01:00
Ref < StreamPeerTCP > tcp ;
Ref < StreamPeerSSL > ssl ;
Ref < StreamPeer > peer ;
Ref < CryptoKey > key ;
Ref < X509Certificate > cert ;
bool use_ssl = false ;
2020-12-10 13:57:27 +01:00
uint64_t time = 0 ;
2019-08-24 17:27:38 +02:00
uint8_t req_buf [ 4096 ] ;
2020-12-10 13:57:27 +01:00
int req_pos = 0 ;
2019-08-24 17:27:38 +02:00
void _clear_client ( ) {
2021-03-11 22:40:30 +01:00
peer = Ref < StreamPeer > ( ) ;
ssl = Ref < StreamPeerSSL > ( ) ;
tcp = Ref < StreamPeerTCP > ( ) ;
2019-08-24 17:27:38 +02:00
memset ( req_buf , 0 , sizeof ( req_buf ) ) ;
time = 0 ;
req_pos = 0 ;
}
2021-03-11 22:40:30 +01:00
void _set_internal_certs ( Ref < Crypto > p_crypto ) {
2021-05-25 02:25:11 +02:00
const String cache_path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) ;
2021-03-11 22:40:30 +01:00
const String key_path = cache_path . plus_file ( " html5_server.key " ) ;
const String crt_path = cache_path . plus_file ( " html5_server.crt " ) ;
bool regen = ! FileAccess : : exists ( key_path ) | | ! FileAccess : : exists ( crt_path ) ;
if ( ! regen ) {
key = Ref < CryptoKey > ( CryptoKey : : create ( ) ) ;
cert = Ref < X509Certificate > ( X509Certificate : : create ( ) ) ;
if ( key - > load ( key_path ) ! = OK | | cert - > load ( crt_path ) ! = OK ) {
regen = true ;
}
}
if ( regen ) {
key = p_crypto - > generate_rsa ( 2048 ) ;
key - > save ( key_path ) ;
cert = p_crypto - > generate_self_signed_certificate ( key , " CN=godot-debug.local,O=A Game Dev,C=XXA " , " 20140101000000 " , " 20340101000000 " ) ;
cert - > save ( crt_path ) ;
}
}
2019-08-24 17:27:38 +02:00
public :
EditorHTTPServer ( ) {
2021-03-26 11:48:56 +01:00
mimes [ " html " ] = " text/html " ;
mimes [ " js " ] = " application/javascript " ;
mimes [ " json " ] = " application/json " ;
mimes [ " pck " ] = " application/octet-stream " ;
mimes [ " png " ] = " image/png " ;
mimes [ " svg " ] = " image/svg " ;
mimes [ " wasm " ] = " application/wasm " ;
2021-06-18 00:03:09 +02:00
server . instantiate ( ) ;
2019-08-24 17:27:38 +02:00
stop ( ) ;
}
void stop ( ) {
server - > stop ( ) ;
_clear_client ( ) ;
}
2021-05-06 02:48:18 +02:00
Error listen ( int p_port , IPAddress p_address , bool p_use_ssl , String p_ssl_key , String p_ssl_cert ) {
2021-03-11 22:40:30 +01:00
use_ssl = p_use_ssl ;
if ( use_ssl ) {
Ref < Crypto > crypto = Crypto : : create ( ) ;
if ( crypto . is_null ( ) ) {
return ERR_UNAVAILABLE ;
}
if ( ! p_ssl_key . is_empty ( ) & & ! p_ssl_cert . is_empty ( ) ) {
key = Ref < CryptoKey > ( CryptoKey : : create ( ) ) ;
Error err = key - > load ( p_ssl_key ) ;
ERR_FAIL_COND_V ( err ! = OK , err ) ;
cert = Ref < X509Certificate > ( X509Certificate : : create ( ) ) ;
err = cert - > load ( p_ssl_cert ) ;
ERR_FAIL_COND_V ( err ! = OK , err ) ;
} else {
_set_internal_certs ( crypto ) ;
}
}
2019-08-24 17:27:38 +02:00
return server - > listen ( p_port , p_address ) ;
}
bool is_listening ( ) const {
return server - > is_listening ( ) ;
}
void _send_response ( ) {
Vector < String > psa = String ( ( char * ) req_buf ) . split ( " \r \n " ) ;
int len = psa . size ( ) ;
ERR_FAIL_COND_MSG ( len < 4 , " Not enough response headers, got: " + itos ( len ) + " , expected >= 4. " ) ;
Vector < String > req = psa [ 0 ] . split ( " " , false ) ;
ERR_FAIL_COND_MSG ( req . size ( ) < 2 , " Invalid protocol or status code. " ) ;
// Wrong protocol
ERR_FAIL_COND_MSG ( req [ 0 ] ! = " GET " | | req [ 2 ] ! = " HTTP/1.1 " , " Invalid method or HTTP version. " ) ;
2021-06-24 18:20:28 +02:00
const int query_index = req [ 1 ] . find_char ( ' ? ' ) ;
const String path = ( query_index = = - 1 ) ? req [ 1 ] : req [ 1 ] . substr ( 0 , query_index ) ;
const String req_file = path . get_file ( ) ;
const String req_ext = path . get_extension ( ) ;
2021-05-25 02:25:11 +02:00
const String cache_path = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " web " ) ;
2021-03-26 11:48:56 +01:00
const String filepath = cache_path . plus_file ( req_file ) ;
if ( ! mimes . has ( req_ext ) | | ! FileAccess : : exists ( filepath ) ) {
2019-08-24 17:27:38 +02:00
String s = " HTTP/1.1 404 Not Found \r \n " ;
s + = " Connection: Close \r \n " ;
s + = " \r \n " ;
CharString cs = s . utf8 ( ) ;
2021-03-11 22:40:30 +01:00
peer - > put_data ( ( const uint8_t * ) cs . get_data ( ) , cs . size ( ) - 1 ) ;
2019-08-24 17:27:38 +02:00
return ;
}
2021-03-26 11:48:56 +01:00
const String ctype = mimes [ req_ext ] ;
2019-08-24 17:27:38 +02:00
FileAccess * f = FileAccess : : open ( filepath , FileAccess : : READ ) ;
ERR_FAIL_COND ( ! f ) ;
String s = " HTTP/1.1 200 OK \r \n " ;
s + = " Connection: Close \r \n " ;
2020-01-14 15:01:00 +01:00
s + = " Content-Type: " + ctype + " \r \n " ;
2020-09-30 19:12:36 +02:00
s + = " Access-Control-Allow-Origin: * \r \n " ;
s + = " Cross-Origin-Opener-Policy: same-origin \r \n " ;
s + = " Cross-Origin-Embedder-Policy: require-corp \r \n " ;
2021-01-25 14:41:23 +01:00
s + = " Cache-Control: no-store, max-age=0 \r \n " ;
2019-08-24 17:27:38 +02:00
s + = " \r \n " ;
CharString cs = s . utf8 ( ) ;
2021-03-11 22:40:30 +01:00
Error err = peer - > put_data ( ( const uint8_t * ) cs . get_data ( ) , cs . size ( ) - 1 ) ;
2020-01-14 15:06:53 +01:00
if ( err ! = OK ) {
memdelete ( f ) ;
ERR_FAIL ( ) ;
}
2019-08-24 17:27:38 +02:00
while ( true ) {
uint8_t bytes [ 4096 ] ;
2019-03-26 18:51:13 +01:00
uint64_t read = f - > get_buffer ( bytes , 4096 ) ;
if ( read = = 0 ) {
2019-08-24 17:27:38 +02:00
break ;
}
2021-03-11 22:40:30 +01:00
err = peer - > put_data ( bytes , read ) ;
2020-01-14 15:06:53 +01:00
if ( err ! = OK ) {
memdelete ( f ) ;
ERR_FAIL ( ) ;
}
2019-08-24 17:27:38 +02:00
}
2020-01-14 15:06:53 +01:00
memdelete ( f ) ;
2019-08-24 17:27:38 +02:00
}
void poll ( ) {
2020-05-14 16:41:43 +02:00
if ( ! server - > is_listening ( ) ) {
2019-08-24 17:27:38 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2021-03-11 22:40:30 +01:00
if ( tcp . is_null ( ) ) {
2020-05-14 16:41:43 +02:00
if ( ! server - > is_connection_available ( ) ) {
2019-08-24 17:27:38 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2021-03-11 22:40:30 +01:00
tcp = server - > take_connection ( ) ;
peer = tcp ;
2019-08-24 17:27:38 +02:00
time = OS : : get_singleton ( ) - > get_ticks_usec ( ) ;
}
if ( OS : : get_singleton ( ) - > get_ticks_usec ( ) - time > 1000000 ) {
_clear_client ( ) ;
return ;
}
2021-03-11 22:40:30 +01:00
if ( tcp - > get_status ( ) ! = StreamPeerTCP : : STATUS_CONNECTED ) {
2019-08-24 17:27:38 +02:00
return ;
2020-05-14 16:41:43 +02:00
}
2019-08-24 17:27:38 +02:00
2021-03-11 22:40:30 +01:00
if ( use_ssl ) {
if ( ssl . is_null ( ) ) {
ssl = Ref < StreamPeerSSL > ( StreamPeerSSL : : create ( ) ) ;
peer = ssl ;
ssl - > set_blocking_handshake_enabled ( false ) ;
if ( ssl - > accept_stream ( tcp , key , cert ) ! = OK ) {
_clear_client ( ) ;
return ;
}
}
ssl - > poll ( ) ;
if ( ssl - > get_status ( ) = = StreamPeerSSL : : STATUS_HANDSHAKING ) {
// Still handshaking, keep waiting.
return ;
}
if ( ssl - > get_status ( ) ! = StreamPeerSSL : : STATUS_CONNECTED ) {
_clear_client ( ) ;
return ;
}
}
2019-08-24 17:27:38 +02:00
while ( true ) {
char * r = ( char * ) req_buf ;
int l = req_pos - 1 ;
if ( l > 3 & & r [ l ] = = ' \n ' & & r [ l - 1 ] = = ' \r ' & & r [ l - 2 ] = = ' \n ' & & r [ l - 3 ] = = ' \r ' ) {
_send_response ( ) ;
_clear_client ( ) ;
return ;
}
int read = 0 ;
ERR_FAIL_COND ( req_pos > = 4096 ) ;
2021-03-11 22:40:30 +01:00
Error err = peer - > get_partial_data ( & req_buf [ req_pos ] , 1 , read ) ;
2019-08-24 17:27:38 +02:00
if ( err ! = OK ) {
// Got an error
_clear_client ( ) ;
return ;
} else if ( read ! = 1 ) {
// Busy, wait next poll
return ;
}
req_pos + = read ;
}
}
} ;
2017-03-28 03:21:21 +02:00
class EditorExportPlatformJavaScript : public EditorExportPlatform {
2019-03-19 19:35:57 +01:00
GDCLASS ( EditorExportPlatformJavaScript , EditorExportPlatform ) ;
2016-01-20 03:21:29 +01:00
2014-02-10 02:10:30 +01:00
Ref < ImageTexture > logo ;
2017-05-25 20:57:13 +02:00
Ref < ImageTexture > run_icon ;
2019-08-24 17:27:38 +02:00
Ref < ImageTexture > stop_icon ;
2020-12-10 13:57:27 +01:00
int menu_options = 0 ;
Ref < EditorHTTPServer > server ;
bool server_quit = false ;
Mutex server_lock ;
2021-01-19 13:29:41 +01:00
Thread server_thread ;
2014-02-10 02:10:30 +01:00
2020-12-03 14:42:25 +01:00
enum ExportMode {
EXPORT_MODE_NORMAL = 0 ,
EXPORT_MODE_THREADS = 1 ,
EXPORT_MODE_GDNATIVE = 2 ,
} ;
String _get_template_name ( ExportMode p_mode , bool p_debug ) const {
String name = " webassembly " ;
switch ( p_mode ) {
case EXPORT_MODE_THREADS :
name + = " _threads " ;
break ;
case EXPORT_MODE_GDNATIVE :
name + = " _gdnative " ;
break ;
default :
break ;
}
if ( p_debug ) {
name + = " _debug.zip " ;
} else {
name + = " _release.zip " ;
}
return name ;
}
2021-03-18 16:19:09 +01:00
Ref < Image > _get_project_icon ( ) const {
Ref < Image > icon ;
2021-06-18 00:03:09 +02:00
icon . instantiate ( ) ;
2021-03-18 16:19:09 +01:00
const String icon_path = String ( GLOBAL_GET ( " application/config/icon " ) ) . strip_edges ( ) ;
if ( icon_path . is_empty ( ) | | ImageLoader : : load_image ( icon_path , icon ) ! = OK ) {
return EditorNode : : get_singleton ( ) - > get_editor_theme ( ) - > get_icon ( " DefaultProjectIcon " , " EditorIcons " ) - > get_image ( ) ;
}
return icon ;
}
Ref < Image > _get_project_splash ( ) const {
Ref < Image > splash ;
2021-06-18 00:03:09 +02:00
splash . instantiate ( ) ;
2021-03-18 16:19:09 +01:00
const String splash_path = String ( GLOBAL_GET ( " application/boot_splash/image " ) ) . strip_edges ( ) ;
if ( splash_path . is_empty ( ) | | ImageLoader : : load_image ( splash_path , splash ) ! = OK ) {
return Ref < Image > ( memnew ( Image ( boot_splash_png ) ) ) ;
}
return splash ;
}
Error _extract_template ( const String & p_template , const String & p_dir , const String & p_name , bool pwa ) ;
void _replace_strings ( Map < String , String > p_replaces , Vector < uint8_t > & r_template ) ;
2021-03-01 17:53:56 +01:00
void _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-03-18 16:19:09 +01:00
Error _add_manifest_icon ( const String & p_path , const String & p_icon , int p_size , Array & r_arr ) ;
Error _build_pwa ( const Ref < EditorExportPreset > & p_preset , const String p_path , const Vector < SharedObject > & p_shared_objects ) ;
Error _write_or_error ( const uint8_t * p_content , int p_len , String p_path ) ;
2014-02-10 02:10:30 +01:00
2019-08-24 17:27:38 +02:00
static void _server_thread_poll ( void * data ) ;
2014-02-10 02:10:30 +01:00
public :
2020-07-10 12:34:39 +02:00
virtual void get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) override ;
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual void get_export_options ( List < ExportOption > * r_options ) override ;
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual String get_name ( ) const override ;
virtual String get_os_name ( ) const override ;
virtual Ref < Texture2D > get_logo ( ) const override ;
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual bool can_export ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates ) const override ;
virtual List < String > get_binary_extensions ( const Ref < EditorExportPreset > & p_preset ) const override ;
virtual Error export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags = 0 ) override ;
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual bool poll_export ( ) override ;
virtual int get_options_count ( ) const override ;
virtual String get_option_label ( int p_index ) const override { return p_index ? TTR ( " Stop HTTP Server " ) : TTR ( " Run in Browser " ) ; }
virtual String get_option_tooltip ( int p_index ) const override { return p_index ? TTR ( " Stop HTTP Server " ) : TTR ( " Run exported HTML in the system's default browser. " ) ; }
virtual Ref < ImageTexture > get_option_icon ( int p_index ) const override ;
virtual Error run ( const Ref < EditorExportPreset > & p_preset , int p_option , int p_debug_flags ) override ;
virtual Ref < Texture2D > get_run_icon ( ) const override ;
2014-02-10 02:10:30 +01:00
2020-07-10 12:34:39 +02:00
virtual void get_platform_features ( List < String > * r_features ) override {
2017-07-19 22:00:46 +02:00
r_features - > push_back ( " web " ) ;
2018-01-12 00:15:21 +01:00
r_features - > push_back ( get_os_name ( ) ) ;
2017-07-19 22:00:46 +02:00
}
2020-07-10 12:34:39 +02:00
virtual void resolve_platform_feature_priorities ( const Ref < EditorExportPreset > & p_preset , Set < String > & p_features ) override {
2018-08-22 04:56:04 +02:00
}
2020-07-10 12:34:39 +02:00
String get_debug_protocol ( ) const override { return " ws:// " ; }
2020-03-16 17:02:14 +01:00
2014-02-10 02:10:30 +01:00
EditorExportPlatformJavaScript ( ) ;
2019-08-24 17:27:38 +02:00
~ EditorExportPlatformJavaScript ( ) ;
2014-02-10 02:10:30 +01:00
} ;
2021-03-18 16:19:09 +01:00
Error EditorExportPlatformJavaScript : : _extract_template ( const String & p_template , const String & p_dir , const String & p_name , bool pwa ) {
2021-04-29 11:47:24 +02:00
FileAccess * src_f = nullptr ;
2021-03-18 16:19:09 +01:00
zlib_filefunc_def io = zipio_create_io_from_file ( & src_f ) ;
unzFile pkg = unzOpen2 ( p_template . utf8 ( ) . get_data ( ) , & io ) ;
if ( ! pkg ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not open template for export: " ) + " \n " + p_template ) ;
return ERR_FILE_NOT_FOUND ;
}
if ( unzGoToFirstFile ( pkg ) ! = UNZ_OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Invalid export template: " ) + " \n " + p_template ) ;
unzClose ( pkg ) ;
return ERR_FILE_CORRUPT ;
}
do {
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
2021-04-29 11:47:24 +02:00
unzGetCurrentFileInfo ( pkg , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
2021-03-18 16:19:09 +01:00
String file = fname ;
// 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
String dst = p_dir . plus_file ( file . replace ( " godot " , p_name ) ) ;
FileAccess * f = FileAccess : : open ( dst , FileAccess : : WRITE ) ;
if ( ! f ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + dst ) ;
unzClose ( pkg ) ;
return ERR_FILE_CANT_WRITE ;
}
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
memdelete ( f ) ;
} while ( unzGoToNextFile ( pkg ) = = UNZ_OK ) ;
unzClose ( pkg ) ;
return OK ;
}
Error EditorExportPlatformJavaScript : : _write_or_error ( const uint8_t * p_content , int p_size , String p_path ) {
FileAccess * f = FileAccess : : open ( p_path , FileAccess : : WRITE ) ;
if ( ! f ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + p_path ) ;
return ERR_FILE_CANT_WRITE ;
}
f - > store_buffer ( p_content , p_size ) ;
memdelete ( f ) ;
return OK ;
}
void EditorExportPlatformJavaScript : : _replace_strings ( Map < String , String > p_replaces , Vector < uint8_t > & r_template ) {
String str_template = String : : utf8 ( reinterpret_cast < const char * > ( r_template . ptr ( ) ) , r_template . size ( ) ) ;
String out ;
2017-03-28 03:21:21 +02:00
Vector < String > lines = str_template . split ( " \n " ) ;
2021-03-18 16:19:09 +01:00
for ( int i = 0 ; i < lines . size ( ) ; i + + ) {
String current_line = lines [ i ] ;
for ( Map < String , String > : : Element * E = p_replaces . front ( ) ; E ; E = E - > next ( ) ) {
current_line = current_line . replace ( E - > key ( ) , E - > get ( ) ) ;
}
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 ] ;
}
}
void EditorExportPlatformJavaScript : : _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 ) {
// Engine.js config
Dictionary config ;
2021-02-07 13:45:04 +01:00
Array libs ;
2020-12-05 00:37:41 +01:00
for ( int i = 0 ; i < p_shared_objects . size ( ) ; i + + ) {
2021-02-07 13:45:04 +01:00
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 ] ) ;
2020-12-05 00:37:41 +01:00
}
2021-01-30 11:35:03 +01:00
config [ " canvasResizePolicy " ] = p_preset - > get ( " html/canvas_resize_policy " ) ;
2021-03-08 23:16:51 +01:00
config [ " experimentalVK " ] = p_preset - > get ( " html/experimental_virtual_keyboard " ) ;
2021-06-25 19:07:17 +02:00
config [ " focusCanvas " ] = p_preset - > get ( " html/focus_canvas_on_start " ) ;
2021-02-07 13:45:04 +01:00
config [ " gdnativeLibs " ] = libs ;
config [ " executable " ] = p_name ;
config [ " args " ] = args ;
2021-03-01 17:53:56 +01:00
config [ " fileSizes " ] = p_file_sizes ;
2014-02-10 02:10:30 +01:00
2021-03-26 12:44:17 +01:00
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 " ;
2021-03-18 16:19:09 +01:00
head_include + = " <link rel='apple-touch-icon' href=' " + p_name + " .apple-touch-icon.png'/> \n " ;
2021-03-26 12:44:17 +01:00
}
2021-03-18 16:19:09 +01:00
if ( p_preset - > get ( " progressive_web_app/enabled " ) ) {
head_include + = " <link rel='manifest' href=' " + p_name + " .manifest.json'> \n " ;
head_include + = " <script type='application/javascript'>window.addEventListener('load', () => {if ('serviceWorker' in navigator) {navigator.serviceWorker.register(' " +
p_name + " .service.worker.js');}});</script> \n " ;
2017-03-28 03:21:21 +02:00
}
2014-02-10 02:10:30 +01:00
2021-03-18 16:19:09 +01:00
// Replaces HTML string
2020-12-29 19:12:33 +01:00
const String str_config = Variant ( config ) . to_json_string ( ) ;
2021-03-18 16:19:09 +01:00
const String custom_head_include = p_preset - > get ( " html/head_include " ) ;
Map < String , String > replaces ;
replaces [ " $GODOT_URL " ] = p_name + " .js " ;
replaces [ " $GODOT_PROJECT_NAME " ] = ProjectSettings : : get_singleton ( ) - > get_setting ( " application/config/name " ) ;
replaces [ " $GODOT_HEAD_INCLUDE " ] = head_include + custom_head_include ;
replaces [ " $GODOT_CONFIG " ] = str_config ;
_replace_strings ( replaces , p_html ) ;
}
Error EditorExportPlatformJavaScript : : _add_manifest_icon ( const String & p_path , const String & p_icon , int p_size , Array & r_arr ) {
const String name = p_path . get_file ( ) . get_basename ( ) ;
const String icon_name = vformat ( " %s.%dx%d.png " , name , p_size , p_size ) ;
const String icon_dest = p_path . get_base_dir ( ) . plus_file ( icon_name ) ;
Ref < Image > icon ;
if ( ! p_icon . is_empty ( ) ) {
2021-06-18 00:03:09 +02:00
icon . instantiate ( ) ;
2021-03-18 16:19:09 +01:00
const Error err = ImageLoader : : load_image ( p_icon , icon ) ;
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not read file: " ) + " \n " + p_icon ) ;
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 ) ;
2017-03-28 03:21:21 +02:00
}
2021-03-18 16:19:09 +01:00
const Error err = icon - > save_png ( icon_dest ) ;
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + icon_dest ) ;
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 ;
}
Error EditorExportPlatformJavaScript : : _build_pwa ( const Ref < EditorExportPreset > & p_preset , const String p_path , const Vector < SharedObject > & p_shared_objects ) {
// Service worker
const String dir = p_path . get_base_dir ( ) ;
const String name = p_path . get_file ( ) . get_basename ( ) ;
const ExportMode mode = ( ExportMode ) ( int ) p_preset - > get ( " variant/export_type " ) ;
Map < String , String > replaces ;
replaces [ " @GODOT_VERSION@ " ] = " 1 " ;
replaces [ " @GODOT_NAME@ " ] = name ;
replaces [ " @GODOT_OFFLINE_PAGE@ " ] = name + " .offline.html " ;
Array files ;
2020-12-29 19:12:33 +01:00
replaces [ " @GODOT_OPT_CACHE@ " ] = Variant ( files ) . to_json_string ( ) ;
2021-03-18 16:19:09 +01:00
files . push_back ( name + " .html " ) ;
files . push_back ( name + " .js " ) ;
files . push_back ( name + " .wasm " ) ;
files . push_back ( name + " .pck " ) ;
files . push_back ( name + " .offline.html " ) ;
if ( p_preset - > get ( " html/export_icon " ) ) {
files . push_back ( name + " .icon.png " ) ;
files . push_back ( name + " .apple-touch-icon.png " ) ;
}
if ( mode = = EXPORT_MODE_THREADS ) {
files . push_back ( name + " .worker.js " ) ;
files . push_back ( name + " .audio.worklet.js " ) ;
} else if ( mode = = EXPORT_MODE_GDNATIVE ) {
files . push_back ( name + " .side.wasm " ) ;
for ( int i = 0 ; i < p_shared_objects . size ( ) ; i + + ) {
files . push_back ( p_shared_objects [ i ] . path . get_file ( ) ) ;
}
}
2020-12-29 19:12:33 +01:00
replaces [ " @GODOT_CACHE@ " ] = Variant ( files ) . to_json_string ( ) ;
2021-03-18 16:19:09 +01:00
const String sw_path = dir . plus_file ( name + " .service.worker.js " ) ;
Vector < uint8_t > sw ;
{
FileAccess * f = FileAccess : : open ( sw_path , FileAccess : : READ ) ;
if ( ! f ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not read file: " ) + " \n " + sw_path ) ;
return ERR_FILE_CANT_READ ;
}
2021-05-25 08:58:49 +02:00
sw . resize ( f - > get_length ( ) ) ;
2021-03-18 16:19:09 +01:00
f - > get_buffer ( sw . ptrw ( ) , sw . size ( ) ) ;
memdelete ( f ) ;
f = nullptr ;
}
_replace_strings ( replaces , sw ) ;
Error err = _write_or_error ( sw . ptr ( ) , sw . size ( ) , dir . plus_file ( name + " .service.worker.js " ) ) ;
if ( err ! = OK ) {
return err ;
}
// Custom offline page
const String offline_page = p_preset - > get ( " progressive_web_app/offline_page " ) ;
if ( ! offline_page . is_empty ( ) ) {
DirAccess * da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
const String offline_dest = dir . plus_file ( name + " .offline.html " ) ;
err = da - > copy ( ProjectSettings : : get_singleton ( ) - > globalize_path ( offline_page ) , offline_dest ) ;
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not read file: " ) + " \n " + offline_dest ) ;
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 ;
String proj_name = ProjectSettings : : get_singleton ( ) - > get_setting ( " application/config/name " ) ;
if ( proj_name . is_empty ( ) ) {
proj_name = " Godot Game " ;
}
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 ) {
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 ) {
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 ) {
return err ;
}
manifest [ " icons " ] = icons_arr ;
2020-12-29 19:12:33 +01:00
CharString cs = Variant ( manifest ) . to_json_string ( ) . utf8 ( ) ;
2021-03-18 16:19:09 +01:00
err = _write_or_error ( ( const uint8_t * ) cs . get_data ( ) , cs . length ( ) , dir . plus_file ( name + " .manifest.json " ) ) ;
if ( err ! = OK ) {
return err ;
}
return OK ;
2014-02-10 02:10:30 +01:00
}
2017-03-28 03:21:21 +02:00
void EditorExportPlatformJavaScript : : get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) {
2019-03-03 11:52:53 +01:00
if ( p_preset - > get ( " vram_texture_compression/for_desktop " ) ) {
2017-03-28 03:21:21 +02:00
r_features - > push_back ( " s3tc " ) ;
2014-02-10 02:10:30 +01:00
}
2019-03-03 11:52:53 +01:00
if ( p_preset - > get ( " vram_texture_compression/for_mobile " ) ) {
2021-02-17 17:44:49 +01:00
String driver = ProjectSettings : : get_singleton ( ) - > get ( " rendering/driver/driver_name " ) ;
2019-03-03 11:52:53 +01:00
if ( driver = = " GLES2 " ) {
r_features - > push_back ( " etc " ) ;
2020-02-13 10:08:52 +01:00
} else if ( driver = = " Vulkan " ) {
// FIXME: Review if this is correct.
2019-03-03 11:52:53 +01:00
r_features - > push_back ( " etc2 " ) ;
}
2014-02-10 02:10:30 +01:00
}
2020-12-03 17:15:14 +01:00
ExportMode mode = ( ExportMode ) ( int ) p_preset - > get ( " variant/export_type " ) ;
if ( mode = = EXPORT_MODE_THREADS ) {
r_features - > push_back ( " threads " ) ;
} else if ( mode = = EXPORT_MODE_GDNATIVE ) {
r_features - > push_back ( " wasm32 " ) ;
}
2014-02-10 02:10:30 +01:00
}
2017-03-28 03:21:21 +02:00
void EditorExportPlatformJavaScript : : get_export_options ( List < ExportOption > * r_options ) {
2020-11-20 10:45:39 +01: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 " ) , " " ) ) ;
2020-12-03 14:42:25 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " variant/export_type " , PROPERTY_HINT_ENUM , " Regular,Threads,GDNative " ) , 0 ) ) ; // Export type.
2019-03-03 11:52:53 +01: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
2020-11-20 10:45:39 +01:00
2021-03-26 12:44:17 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/export_icon " ) , true ) ) ;
2018-08-23 21:45:48 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " html/custom_html_shell " , PROPERTY_HINT_FILE , " *.html " ) , " " ) ) ;
2017-03-28 03:21:21 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " html/head_include " , PROPERTY_HINT_MULTILINE_TEXT ) , " " ) ) ;
2021-01-30 11:35:03 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " html/canvas_resize_policy " , PROPERTY_HINT_ENUM , " None,Project,Adaptive " ) , 2 ) ) ;
2021-06-25 19:07:17 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/focus_canvas_on_start " ) , true ) ) ;
2021-03-08 23:16:51 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/experimental_virtual_keyboard " ) , false ) ) ;
2021-03-18 16:19:09 +01:00
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 " ) , " " ) ) ;
2021-05-22 04:30:58 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " progressive_web_app/display " , PROPERTY_HINT_ENUM , " Fullscreen,Standalone,Minimal UI,Browser " ) , 1 ) ) ;
2021-03-18 16:19:09 +01:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " progressive_web_app/orientation " , PROPERTY_HINT_ENUM , " Any,Landscape,Portrait " ) , 0 ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " progressive_web_app/icon_144x144 " , PROPERTY_HINT_FILE , " *.png,*.webp,*.svg,*.svgz " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " progressive_web_app/icon_180x180 " , PROPERTY_HINT_FILE , " *.png,*.webp,*.svg,*.svgz " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " progressive_web_app/icon_512x512 " , PROPERTY_HINT_FILE , " *.png,*.webp,*.svg,*.svgz " ) , " " ) ) ;
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : COLOR , " progressive_web_app/background_color " , PROPERTY_HINT_COLOR_NO_ALPHA ) , Color ( ) ) ) ;
2017-03-28 03:21:21 +02:00
}
2014-02-10 02:10:30 +01:00
2017-03-28 03:21:21 +02:00
String EditorExportPlatformJavaScript : : get_name ( ) const {
return " HTML5 " ;
2014-02-10 02:10:30 +01:00
}
2017-07-19 22:00:46 +02:00
String EditorExportPlatformJavaScript : : get_os_name ( ) const {
2018-01-12 00:15:21 +01:00
return " HTML5 " ;
2017-07-19 22:00:46 +02:00
}
2019-06-11 20:43:37 +02:00
Ref < Texture2D > EditorExportPlatformJavaScript : : get_logo ( ) const {
2017-03-28 03:21:21 +02:00
return logo ;
}
2014-02-10 02:10:30 +01:00
2017-03-28 03:21:21 +02:00
bool EditorExportPlatformJavaScript : : can_export ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates ) const {
2018-08-20 17:35:33 +02:00
String err ;
2020-01-07 13:29:02 +01:00
bool valid = false ;
2020-12-03 14:42:25 +01:00
ExportMode mode = ( ExportMode ) ( int ) p_preset - > get ( " variant/export_type " ) ;
2018-08-20 17:35:33 +02:00
2020-01-07 13:29:02 +01:00
// Look for export templates (first official, and if defined custom templates).
2020-12-03 14:42:25 +01:00
bool dvalid = exists_export_template ( _get_template_name ( mode , true ) , & err ) ;
bool rvalid = exists_export_template ( _get_template_name ( mode , false ) , & err ) ;
2018-08-20 17:35:33 +02:00
if ( p_preset - > get ( " custom_template/debug " ) ! = " " ) {
2020-01-07 13:29:02 +01:00
dvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/debug " ) ) ;
if ( ! dvalid ) {
2019-01-21 18:34:53 +01:00
err + = TTR ( " Custom debug template not found. " ) + " \n " ;
2018-08-20 17:35:33 +02:00
}
}
if ( p_preset - > get ( " custom_template/release " ) ! = " " ) {
2020-01-07 13:29:02 +01:00
rvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/release " ) ) ;
if ( ! rvalid ) {
2019-01-21 18:34:53 +01:00
err + = TTR ( " Custom release template not found. " ) + " \n " ;
2018-08-20 17:35:33 +02:00
}
}
2014-02-10 02:10:30 +01:00
2020-01-07 13:29:02 +01:00
valid = dvalid | | rvalid ;
2019-03-03 11:52:53 +01:00
r_missing_templates = ! valid ;
2020-01-07 13:29:02 +01:00
// Validate the rest of the configuration.
2019-03-03 11:52:53 +01:00
if ( p_preset - > get ( " vram_texture_compression/for_mobile " ) ) {
String etc_error = test_etc2 ( ) ;
if ( etc_error ! = String ( ) ) {
valid = false ;
err + = etc_error ;
}
2019-02-26 22:43:37 +01:00
}
2020-12-15 13:04:21 +01:00
if ( ! err . is_empty ( ) ) {
2018-08-20 17:35:33 +02:00
r_error = err ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
2018-08-20 17:35:33 +02:00
return valid ;
2017-03-28 03:21:21 +02:00
}
2014-02-10 02:10:30 +01:00
2018-10-29 22:18:49 +01:00
List < String > EditorExportPlatformJavaScript : : get_binary_extensions ( const Ref < EditorExportPreset > & p_preset ) const {
List < String > list ;
list . push_back ( " html " ) ;
return list ;
2017-03-28 03:21:21 +02:00
}
2014-02-10 02:10:30 +01:00
2017-03-28 03:21:21 +02:00
Error EditorExportPlatformJavaScript : : export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags ) {
2017-10-02 17:01:43 +02:00
ExportNotifier notifier ( * this , p_preset , p_debug , p_path , p_flags ) ;
2014-02-10 02:10:30 +01:00
2021-03-18 16:19:09 +01:00
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 " ) ;
2017-03-28 03:21:21 +02:00
2021-03-18 16:19:09 +01:00
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 ( ) ;
2014-02-10 02:10:30 +01:00
2021-03-18 16:19:09 +01:00
// Find the correct template
String template_path = p_debug ? custom_debug : custom_release ;
2017-03-28 03:21:21 +02:00
template_path = template_path . strip_edges ( ) ;
if ( template_path = = String ( ) ) {
2020-12-03 14:42:25 +01:00
ExportMode mode = ( ExportMode ) ( int ) p_preset - > get ( " variant/export_type " ) ;
template_path = find_export_template ( _get_template_name ( mode , p_debug ) ) ;
2014-02-10 02:10:30 +01:00
}
2021-03-18 16:19:09 +01:00
if ( ! DirAccess : : exists ( base_dir ) ) {
2019-03-05 08:52:45 +01:00
return ERR_FILE_BAD_PATH ;
}
2017-03-28 03:21:21 +02:00
if ( template_path ! = String ( ) & & ! FileAccess : : exists ( template_path ) ) {
2018-01-04 16:08:24 +01:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Template file not found: " ) + " \n " + template_path ) ;
2017-03-28 03:21:21 +02:00
return ERR_FILE_NOT_FOUND ;
}
2014-02-10 02:10:30 +01:00
2021-03-18 16:19:09 +01:00
// Export pck and shared objects
2020-12-05 00:37:41 +01:00
Vector < SharedObject > shared_objects ;
2021-03-18 16:19:09 +01:00
String pck_path = base_path + " .pck " ;
2020-12-05 00:37:41 +01:00
Error error = save_pack ( p_preset , pck_path , & shared_objects ) ;
2017-03-28 03:21:21 +02:00
if ( error ! = OK ) {
2018-01-04 16:08:24 +01:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + pck_path ) ;
2017-03-28 03:21:21 +02:00
return error ;
}
2020-12-05 00:37:41 +01:00
DirAccess * da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
for ( int i = 0 ; i < shared_objects . size ( ) ; i + + ) {
2021-03-18 16:19:09 +01:00
String dst = base_dir . plus_file ( shared_objects [ i ] . path . get_file ( ) ) ;
2020-12-05 00:37:41 +01:00
error = da - > copy ( shared_objects [ i ] . path , dst ) ;
if ( error ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + shared_objects [ i ] . path . get_file ( ) ) ;
memdelete ( da ) ;
return error ;
}
}
memdelete ( da ) ;
2021-03-18 16:19:09 +01:00
da = nullptr ;
2014-02-13 22:03:28 +01:00
2021-03-18 16:19:09 +01:00
// Extract templates.
error = _extract_template ( template_path , base_dir , base_name , pwa ) ;
if ( error ) {
return error ;
2017-11-20 00:06:11 +01:00
}
2014-02-10 02:10:30 +01:00
2021-03-18 16:19:09 +01:00
// Parse generated file sizes (pck and wasm, to help show a meaningful loading bar).
2021-03-01 17:53:56 +01:00
Dictionary file_sizes ;
2021-03-18 16:19:09 +01:00
FileAccess * f = nullptr ;
f = FileAccess : : open ( pck_path , FileAccess : : READ ) ;
if ( f ) {
2021-05-25 08:58:49 +02:00
file_sizes [ pck_path . get_file ( ) ] = ( uint64_t ) f - > get_length ( ) ;
2017-11-20 00:06:11 +01:00
memdelete ( f ) ;
2021-03-18 16:19:09 +01:00
f = nullptr ;
2021-03-01 17:53:56 +01:00
}
2021-03-18 16:19:09 +01:00
f = FileAccess : : open ( base_path + " .wasm " , FileAccess : : READ ) ;
if ( f ) {
2021-05-25 08:58:49 +02:00
file_sizes [ base_name + " .wasm " ] = ( uint64_t ) f - > get_length ( ) ;
2017-11-20 00:06:11 +01:00
memdelete ( f ) ;
2021-03-18 16:19:09 +01:00
f = nullptr ;
2014-02-10 02:10:30 +01:00
}
2021-03-18 16:19:09 +01:00
// 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 ) ;
if ( ! f ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not read HTML shell: " ) + " \n " + html_path ) ;
return ERR_FILE_CANT_READ ;
}
2021-05-25 08:58:49 +02:00
html . resize ( f - > get_length ( ) ) ;
2021-03-18 16:19:09 +01:00
f - > get_buffer ( html . ptrw ( ) , html . size ( ) ) ;
memdelete ( f ) ;
f = nullptr ;
// 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 ) {
return err ;
2017-11-20 00:06:11 +01:00
}
2021-03-18 16:19:09 +01:00
html . resize ( 0 ) ;
// Export splash (why?)
Ref < Image > splash = _get_project_splash ( ) ;
const String splash_png_path = base_path + " .png " ;
2020-01-20 21:46:42 +01:00
if ( splash - > save_png ( splash_png_path ) ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + splash_png_path ) ;
2017-11-20 00:06:11 +01:00
return ERR_FILE_CANT_WRITE ;
}
2020-01-20 21:46:42 +01:00
// 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.
2021-03-26 12:44:17 +01:00
if ( export_icon ) {
2021-03-18 16:19:09 +01:00
Ref < Image > favicon = _get_project_icon ( ) ;
const String favicon_png_path = base_path + " .icon.png " ;
2020-01-20 21:46:42 +01:00
if ( favicon - > save_png ( favicon_png_path ) ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + favicon_png_path ) ;
return ERR_FILE_CANT_WRITE ;
}
2021-03-18 16:19:09 +01:00
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 ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + apple_icon_png_path ) ;
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 ) {
return err ;
}
2020-01-20 21:46:42 +01:00
}
2014-02-10 02:10:30 +01:00
return OK ;
}
2019-10-11 13:47:28 +02:00
bool EditorExportPlatformJavaScript : : poll_export ( ) {
2017-06-27 03:13:36 +02: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 ;
}
}
2019-08-24 17:27:38 +02:00
int prev = menu_options ;
menu_options = preset . is_valid ( ) ;
if ( server - > is_listening ( ) ) {
if ( menu_options = = 0 ) {
2020-02-26 11:28:13 +01:00
MutexLock lock ( server_lock ) ;
2019-08-24 17:27:38 +02:00
server - > stop ( ) ;
} else {
menu_options + = 1 ;
}
}
return menu_options ! = prev ;
}
Ref < ImageTexture > EditorExportPlatformJavaScript : : get_option_icon ( int p_index ) const {
return p_index = = 1 ? stop_icon : EditorExportPlatform : : get_option_icon ( p_index ) ;
2017-06-27 03:13:36 +02:00
}
2019-10-11 13:47:28 +02:00
int EditorExportPlatformJavaScript : : get_options_count ( ) const {
2019-08-24 17:27:38 +02:00
return menu_options ;
2017-06-27 03:13:36 +02:00
}
2019-10-11 13:47:28 +02:00
Error EditorExportPlatformJavaScript : : run ( const Ref < EditorExportPreset > & p_preset , int p_option , int p_debug_flags ) {
2019-08-24 17:27:38 +02:00
if ( p_option = = 1 ) {
2020-02-26 11:28:13 +01:00
MutexLock lock ( server_lock ) ;
2019-08-24 17:27:38 +02:00
server - > stop ( ) ;
return OK ;
}
2021-05-25 02:25:11 +02:00
const String dest = EditorPaths : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " web " ) ;
2021-03-26 11:48:56 +01:00
DirAccessRef da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
if ( ! da - > dir_exists ( dest ) ) {
Error err = da - > make_dir_recursive ( dest ) ;
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not create HTTP server directory: " ) + " \n " + dest ) ;
return err ;
}
}
const String basepath = dest . plus_file ( " tmp_js_export " ) ;
2020-01-20 21:46:42 +01:00
Error err = export_project ( p_preset , true , basepath + " .html " , p_debug_flags ) ;
2019-08-09 13:45:30 +02:00
if ( err ! = OK ) {
// Export generates several files, clean them up on failure.
DirAccess : : remove_file_or_error ( basepath + " .html " ) ;
2021-03-18 16:19:09 +01:00
DirAccess : : remove_file_or_error ( basepath + " .offline.html " ) ;
2019-08-09 13:45:30 +02:00
DirAccess : : remove_file_or_error ( basepath + " .js " ) ;
2020-03-11 11:55:28 +01:00
DirAccess : : remove_file_or_error ( basepath + " .worker.js " ) ;
2020-11-10 11:05:22 +01:00
DirAccess : : remove_file_or_error ( basepath + " .audio.worklet.js " ) ;
2021-03-18 16:19:09 +01:00
DirAccess : : remove_file_or_error ( basepath + " .service.worker.js " ) ;
2019-08-09 13:45:30 +02:00
DirAccess : : remove_file_or_error ( basepath + " .pck " ) ;
DirAccess : : remove_file_or_error ( basepath + " .png " ) ;
2020-12-05 00:37:41 +01:00
DirAccess : : remove_file_or_error ( basepath + " .side.wasm " ) ;
2019-08-09 13:45:30 +02:00
DirAccess : : remove_file_or_error ( basepath + " .wasm " ) ;
2021-03-26 12:44:17 +01:00
DirAccess : : remove_file_or_error ( basepath + " .icon.png " ) ;
2021-03-18 16:19:09 +01:00
DirAccess : : remove_file_or_error ( basepath + " .apple-touch-icon.png " ) ;
2014-02-10 02:10:30 +01:00
return err ;
2017-03-28 03:21:21 +02:00
}
2019-08-24 17:27:38 +02:00
2020-01-20 21:46:42 +01:00
const uint16_t bind_port = EDITOR_GET ( " export/web/http_port " ) ;
2019-08-24 17:27:38 +02:00
// Resolve host if needed.
2020-01-20 21:46:42 +01:00
const String bind_host = EDITOR_GET ( " export/web/http_host " ) ;
2021-05-06 02:48:18 +02:00
IPAddress bind_ip ;
2019-08-24 17:27:38 +02:00
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'. " ) ;
2021-03-11 22:40:30 +01:00
const bool use_ssl = EDITOR_GET ( " export/web/use_ssl " ) ;
const String ssl_key = EDITOR_GET ( " export/web/ssl_key " ) ;
const String ssl_cert = EDITOR_GET ( " export/web/ssl_certificate " ) ;
2019-08-24 17:27:38 +02:00
// Restart server.
2020-02-26 11:28:13 +01:00
{
MutexLock lock ( server_lock ) ;
server - > stop ( ) ;
2021-03-11 22:40:30 +01:00
err = server - > listen ( bind_port , bind_ip , use_ssl , ssl_key , ssl_cert ) ;
2020-02-26 11:28:13 +01:00
}
2021-03-26 11:48:56 +01:00
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Error starting HTTP server: " ) + " \n " + itos ( err ) ) ;
return err ;
}
2019-08-24 17:27:38 +02:00
2021-03-11 22:40:30 +01:00
OS : : get_singleton ( ) - > shell_open ( String ( ( use_ssl ? " https:// " : " http:// " ) + bind_host + " : " + itos ( bind_port ) + " /tmp_js_export.html " ) ) ;
2019-08-09 13:45:30 +02:00
// FIXME: Find out how to clean up export files after running the successfully
// exported game. Might not be trivial.
2014-02-10 02:10:30 +01:00
return OK ;
}
2019-06-11 20:43:37 +02:00
Ref < Texture2D > EditorExportPlatformJavaScript : : get_run_icon ( ) const {
2017-05-25 20:57:13 +02:00
return run_icon ;
}
2019-08-24 17:27:38 +02:00
void EditorExportPlatformJavaScript : : _server_thread_poll ( void * data ) {
EditorExportPlatformJavaScript * ej = ( EditorExportPlatformJavaScript * ) data ;
while ( ! ej - > server_quit ) {
OS : : get_singleton ( ) - > delay_usec ( 1000 ) ;
2020-02-26 11:28:13 +01:00
{
MutexLock lock ( ej - > server_lock ) ;
ej - > server - > poll ( ) ;
}
2019-08-24 17:27:38 +02:00
}
}
2014-02-10 02:10:30 +01:00
EditorExportPlatformJavaScript : : EditorExportPlatformJavaScript ( ) {
2021-06-18 00:03:09 +02:00
server . instantiate ( ) ;
2021-01-19 13:29:41 +01:00
server_thread . start ( _server_thread_poll , this ) ;
2019-08-24 17:27:38 +02:00
2017-05-17 12:36:47 +02:00
Ref < Image > img = memnew ( Image ( _javascript_logo ) ) ;
2021-06-18 00:03:09 +02:00
logo . instantiate ( ) ;
2014-02-10 02:10:30 +01:00
logo - > create_from_image ( img ) ;
2017-05-25 20:57:13 +02:00
img = Ref < Image > ( memnew ( Image ( _javascript_run_icon ) ) ) ;
2021-06-18 00:03:09 +02:00
run_icon . instantiate ( ) ;
2017-05-25 20:57:13 +02:00
run_icon - > create_from_image ( img ) ;
2017-06-27 03:13:36 +02:00
2019-08-24 17:27:38 +02:00
Ref < Theme > theme = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) ;
2020-05-14 16:41:43 +02:00
if ( theme . is_valid ( ) ) {
2019-08-24 17:27:38 +02:00
stop_icon = theme - > get_icon ( " Stop " , " EditorIcons " ) ;
2020-05-14 16:41:43 +02:00
} else {
2021-06-18 00:03:09 +02:00
stop_icon . instantiate ( ) ;
2020-05-14 16:41:43 +02:00
}
2019-08-24 17:27:38 +02:00
}
EditorExportPlatformJavaScript : : ~ EditorExportPlatformJavaScript ( ) {
server - > stop ( ) ;
server_quit = true ;
2021-01-19 13:29:41 +01:00
server_thread . wait_to_finish ( ) ;
2014-02-10 02:10:30 +01:00
}
void register_javascript_exporter ( ) {
2019-08-24 17:27:38 +02:00
EDITOR_DEF ( " export/web/http_host " , " localhost " ) ;
EDITOR_DEF ( " export/web/http_port " , 8060 ) ;
2021-03-11 22:40:30 +01:00
EDITOR_DEF ( " export/web/use_ssl " , false ) ;
EDITOR_DEF ( " export/web/ssl_key " , " " ) ;
EDITOR_DEF ( " export/web/ssl_certificate " , " " ) ;
2019-08-24 17:27:38 +02:00
EditorSettings : : get_singleton ( ) - > add_property_hint ( PropertyInfo ( Variant : : INT , " export/web/http_port " , PROPERTY_HINT_RANGE , " 1,65535,1 " ) ) ;
2021-03-11 22:40:30 +01:00
EditorSettings : : get_singleton ( ) - > add_property_hint ( PropertyInfo ( Variant : : STRING , " export/web/ssl_key " , PROPERTY_HINT_GLOBAL_FILE , " *.key " ) ) ;
EditorSettings : : get_singleton ( ) - > add_property_hint ( PropertyInfo ( Variant : : STRING , " export/web/ssl_certificate " , PROPERTY_HINT_GLOBAL_FILE , " *.crt,*.pem " ) ) ;
2019-08-24 17:27:38 +02:00
2017-03-28 03:21:21 +02:00
Ref < EditorExportPlatformJavaScript > platform ;
2021-06-18 00:03:09 +02:00
platform . instantiate ( ) ;
2017-03-28 03:21:21 +02:00
EditorExport : : get_singleton ( ) - > add_export_platform ( platform ) ;
2014-02-10 02:10:30 +01:00
}