2014-02-10 01:10:30 +00:00
/*************************************************************************/
/* export.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
2017-08-27 12:16:55 +00:00
/* https://godotengine.org */
2014-02-10 01:10:30 +00:00
/*************************************************************************/
2021-01-01 19:13:46 +00:00
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
2014-02-10 01:10:30 +00: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-04 23:50:27 +00:00
2021-03-26 11:44:17 +00:00
# include "core/io/image_loader.h"
2020-03-16 16:02:14 +00:00
# include "core/io/json.h"
2019-08-24 15:27:38 +00:00
# include "core/io/tcp_server.h"
2018-09-11 16:13:45 +00:00
# include "core/io/zip_io.h"
2019-10-09 14:11:04 +00:00
# include "editor/editor_export.h"
2017-03-05 13:21:25 +00:00
# include "editor/editor_node.h"
2017-11-19 23:06:11 +00:00
# include "main/splash.gen.h"
2017-06-23 15:03:41 +00:00
# include "platform/javascript/logo.gen.h"
2017-05-25 18:57:13 +00:00
# include "platform/javascript/run_icon.gen.h"
2017-01-26 00:55:59 +00:00
2019-08-24 15:27:38 +00:00
class EditorHTTPServer : public Reference {
private :
Ref < TCP_Server > server ;
Ref < StreamPeerTCP > connection ;
2021-03-26 10:48:56 +00:00
Map < String , String > mimes ;
2020-12-10 12:57:27 +00:00
uint64_t time = 0 ;
2019-08-24 15:27:38 +00:00
uint8_t req_buf [ 4096 ] ;
2020-12-10 12:57:27 +00:00
int req_pos = 0 ;
2019-08-24 15:27:38 +00:00
void _clear_client ( ) {
connection = Ref < StreamPeerTCP > ( ) ;
memset ( req_buf , 0 , sizeof ( req_buf ) ) ;
time = 0 ;
req_pos = 0 ;
}
public :
EditorHTTPServer ( ) {
2021-03-26 10:48:56 +00: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 " ;
2019-08-24 15:27:38 +00:00
server . instance ( ) ;
stop ( ) ;
}
void stop ( ) {
server - > stop ( ) ;
_clear_client ( ) ;
}
Error listen ( int p_port , IP_Address p_address ) {
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-03-26 10:48:56 +00:00
const String req_file = req [ 1 ] . get_file ( ) ;
const String req_ext = req [ 1 ] . get_extension ( ) ;
const String cache_path = EditorSettings : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " web " ) ;
const String filepath = cache_path . plus_file ( req_file ) ;
if ( ! mimes . has ( req_ext ) | | ! FileAccess : : exists ( filepath ) ) {
2019-08-24 15:27:38 +00:00
String s = " HTTP/1.1 404 Not Found \r \n " ;
s + = " Connection: Close \r \n " ;
s + = " \r \n " ;
CharString cs = s . utf8 ( ) ;
connection - > put_data ( ( const uint8_t * ) cs . get_data ( ) , cs . size ( ) - 1 ) ;
return ;
}
2021-03-26 10:48:56 +00:00
const String ctype = mimes [ req_ext ] ;
2019-08-24 15:27:38 +00: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 14:01:00 +00:00
s + = " Content-Type: " + ctype + " \r \n " ;
2020-09-30 17:12:36 +00: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 13:41:23 +00:00
s + = " Cache-Control: no-store, max-age=0 \r \n " ;
2019-08-24 15:27:38 +00:00
s + = " \r \n " ;
CharString cs = s . utf8 ( ) ;
Error err = connection - > put_data ( ( const uint8_t * ) cs . get_data ( ) , cs . size ( ) - 1 ) ;
2020-01-14 14:06:53 +00:00
if ( err ! = OK ) {
memdelete ( f ) ;
ERR_FAIL ( ) ;
}
2019-08-24 15:27:38 +00:00
while ( true ) {
uint8_t bytes [ 4096 ] ;
int read = f - > get_buffer ( bytes , 4096 ) ;
if ( read < 1 ) {
break ;
}
err = connection - > put_data ( bytes , read ) ;
2020-01-14 14:06:53 +00:00
if ( err ! = OK ) {
memdelete ( f ) ;
ERR_FAIL ( ) ;
}
2019-08-24 15:27:38 +00:00
}
2020-01-14 14:06:53 +00:00
memdelete ( f ) ;
2019-08-24 15:27:38 +00:00
}
void poll ( ) {
2020-05-14 14:41:43 +00:00
if ( ! server - > is_listening ( ) ) {
2019-08-24 15:27:38 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2019-08-24 15:27:38 +00:00
if ( connection . is_null ( ) ) {
2020-05-14 14:41:43 +00:00
if ( ! server - > is_connection_available ( ) ) {
2019-08-24 15:27:38 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2019-08-24 15:27:38 +00:00
connection = server - > take_connection ( ) ;
time = OS : : get_singleton ( ) - > get_ticks_usec ( ) ;
}
if ( OS : : get_singleton ( ) - > get_ticks_usec ( ) - time > 1000000 ) {
_clear_client ( ) ;
return ;
}
2020-05-14 14:41:43 +00:00
if ( connection - > get_status ( ) ! = StreamPeerTCP : : STATUS_CONNECTED ) {
2019-08-24 15:27:38 +00:00
return ;
2020-05-14 14:41:43 +00:00
}
2019-08-24 15:27:38 +00: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 ) ;
Error err = connection - > get_partial_data ( & req_buf [ req_pos ] , 1 , read ) ;
if ( err ! = OK ) {
// Got an error
_clear_client ( ) ;
return ;
} else if ( read ! = 1 ) {
// Busy, wait next poll
return ;
}
req_pos + = read ;
}
}
} ;
2017-03-28 01:21:21 +00:00
class EditorExportPlatformJavaScript : public EditorExportPlatform {
2019-03-19 18:35:57 +00:00
GDCLASS ( EditorExportPlatformJavaScript , EditorExportPlatform ) ;
2016-01-20 02:21:29 +00:00
2014-02-10 01:10:30 +00:00
Ref < ImageTexture > logo ;
2017-05-25 18:57:13 +00:00
Ref < ImageTexture > run_icon ;
2019-08-24 15:27:38 +00:00
Ref < ImageTexture > stop_icon ;
2020-12-10 12:57:27 +00:00
int menu_options = 0 ;
Ref < EditorHTTPServer > server ;
bool server_quit = false ;
Mutex server_lock ;
2021-01-19 12:29:41 +00:00
Thread server_thread ;
2014-02-10 01:10:30 +00:00
2020-12-03 13:42:25 +00: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-01 16:53:56 +00: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 ) ;
2014-02-10 01:10:30 +00:00
2019-08-24 15:27:38 +00:00
static void _server_thread_poll ( void * data ) ;
2014-02-10 01:10:30 +00:00
public :
2020-07-10 10:34:39 +00:00
virtual void get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) override ;
2014-02-10 01:10:30 +00:00
2020-07-10 10:34:39 +00:00
virtual void get_export_options ( List < ExportOption > * r_options ) override ;
2014-02-10 01:10:30 +00:00
2020-07-10 10:34:39 +00:00
virtual String get_name ( ) const override ;
virtual String get_os_name ( ) const override ;
virtual Ref < Texture2D > get_logo ( ) const override ;
2014-02-10 01:10:30 +00:00
2020-07-10 10:34:39 +00: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 01:10:30 +00:00
2020-07-10 10:34:39 +00: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 01:10:30 +00:00
2020-07-10 10:34:39 +00:00
virtual void get_platform_features ( List < String > * r_features ) override {
2017-07-19 20:00:46 +00:00
r_features - > push_back ( " web " ) ;
2018-01-11 23:15:21 +00:00
r_features - > push_back ( get_os_name ( ) ) ;
2017-07-19 20:00:46 +00:00
}
2020-07-10 10:34:39 +00:00
virtual void resolve_platform_feature_priorities ( const Ref < EditorExportPreset > & p_preset , Set < String > & p_features ) override {
2018-08-22 02:56:04 +00:00
}
2020-07-10 10:34:39 +00:00
String get_debug_protocol ( ) const override { return " ws:// " ; }
2020-03-16 16:02:14 +00:00
2014-02-10 01:10:30 +00:00
EditorExportPlatformJavaScript ( ) ;
2019-08-24 15:27:38 +00:00
~ EditorExportPlatformJavaScript ( ) ;
2014-02-10 01:10:30 +00:00
} ;
2021-03-01 16:53:56 +00:00
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 ) {
2017-03-28 01:21:21 +00:00
String str_template = String : : utf8 ( reinterpret_cast < const char * > ( p_html . ptr ( ) ) , p_html . size ( ) ) ;
String str_export ;
Vector < String > lines = str_template . split ( " \n " ) ;
2021-02-07 12:45:04 +00:00
Array libs ;
2020-12-04 23:37:41 +00:00
for ( int i = 0 ; i < p_shared_objects . size ( ) ; i + + ) {
2021-02-07 12:45:04 +00: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-04 23:37:41 +00:00
}
2021-02-07 12:45:04 +00:00
Dictionary config ;
2021-01-30 10:35:03 +00:00
config [ " canvasResizePolicy " ] = p_preset - > get ( " html/canvas_resize_policy " ) ;
2021-03-08 22:16:51 +00:00
config [ " experimentalVK " ] = p_preset - > get ( " html/experimental_virtual_keyboard " ) ;
2021-02-07 12:45:04 +00:00
config [ " gdnativeLibs " ] = libs ;
config [ " executable " ] = p_name ;
config [ " args " ] = args ;
2021-03-01 16:53:56 +00:00
config [ " fileSizes " ] = p_file_sizes ;
2021-02-07 12:45:04 +00:00
const String str_config = JSON : : print ( config ) ;
2014-02-10 01:10:30 +00:00
2021-03-26 11:44:17 +00: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 " ;
}
head_include + = static_cast < String > ( p_preset - > get ( " html/head_include " ) ) ;
2017-03-28 01:21:21 +00:00
for ( int i = 0 ; i < lines . size ( ) ; i + + ) {
String current_line = lines [ i ] ;
2021-02-07 12:45:04 +00:00
current_line = current_line . replace ( " $GODOT_URL " , p_name + " .js " ) ;
2020-01-20 21:35:36 +00:00
current_line = current_line . replace ( " $GODOT_PROJECT_NAME " , ProjectSettings : : get_singleton ( ) - > get_setting ( " application/config/name " ) ) ;
2021-03-26 11:44:17 +00:00
current_line = current_line . replace ( " $GODOT_HEAD_INCLUDE " , head_include ) ;
2021-02-07 12:45:04 +00:00
current_line = current_line . replace ( " $GODOT_CONFIG " , str_config ) ;
2017-03-28 01:21:21 +00:00
str_export + = current_line + " \n " ;
}
2014-02-10 01:10:30 +00:00
2017-03-28 01:21:21 +00:00
CharString cs = str_export . utf8 ( ) ;
p_html . resize ( cs . length ( ) ) ;
for ( int i = 0 ; i < cs . length ( ) ; i + + ) {
2018-07-25 01:11:03 +00:00
p_html . write [ i ] = cs [ i ] ;
2017-03-28 01:21:21 +00:00
}
2014-02-10 01:10:30 +00:00
}
2017-03-28 01:21:21 +00:00
void EditorExportPlatformJavaScript : : get_preset_features ( const Ref < EditorExportPreset > & p_preset , List < String > * r_features ) {
2019-03-03 10:52:53 +00:00
if ( p_preset - > get ( " vram_texture_compression/for_desktop " ) ) {
2017-03-28 01:21:21 +00:00
r_features - > push_back ( " s3tc " ) ;
2014-02-10 01:10:30 +00:00
}
2019-03-03 10:52:53 +00:00
if ( p_preset - > get ( " vram_texture_compression/for_mobile " ) ) {
2021-02-17 16:44:49 +00:00
String driver = ProjectSettings : : get_singleton ( ) - > get ( " rendering/driver/driver_name " ) ;
2019-03-03 10:52:53 +00:00
if ( driver = = " GLES2 " ) {
r_features - > push_back ( " etc " ) ;
2020-02-13 09:08:52 +00:00
} else if ( driver = = " Vulkan " ) {
// FIXME: Review if this is correct.
2019-03-03 10:52:53 +00:00
r_features - > push_back ( " etc2 " ) ;
}
2014-02-10 01:10:30 +00:00
}
2020-12-03 16:15:14 +00: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 01:10:30 +00:00
}
2017-03-28 01:21:21 +00:00
void EditorExportPlatformJavaScript : : get_export_options ( List < ExportOption > * r_options ) {
2020-11-20 09:45:39 +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 " ) , " " ) ) ;
2020-12-03 13:42:25 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " variant/export_type " , PROPERTY_HINT_ENUM , " Regular,Threads,GDNative " ) , 0 ) ) ; // Export type.
2019-03-03 10:52:53 +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
2020-11-20 09:45:39 +00:00
2021-03-26 11:44:17 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/export_icon " ) , true ) ) ;
2018-08-23 19:45:48 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " html/custom_html_shell " , PROPERTY_HINT_FILE , " *.html " ) , " " ) ) ;
2017-03-28 01:21:21 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : STRING , " html/head_include " , PROPERTY_HINT_MULTILINE_TEXT ) , " " ) ) ;
2021-01-30 10:35:03 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : INT , " html/canvas_resize_policy " , PROPERTY_HINT_ENUM , " None,Project,Adaptive " ) , 2 ) ) ;
2021-03-08 22:16:51 +00:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/experimental_virtual_keyboard " ) , false ) ) ;
2017-03-28 01:21:21 +00:00
}
2014-02-10 01:10:30 +00:00
2017-03-28 01:21:21 +00:00
String EditorExportPlatformJavaScript : : get_name ( ) const {
return " HTML5 " ;
2014-02-10 01:10:30 +00:00
}
2017-07-19 20:00:46 +00:00
String EditorExportPlatformJavaScript : : get_os_name ( ) const {
2018-01-11 23:15:21 +00:00
return " HTML5 " ;
2017-07-19 20:00:46 +00:00
}
2019-06-11 18:43:37 +00:00
Ref < Texture2D > EditorExportPlatformJavaScript : : get_logo ( ) const {
2017-03-28 01:21:21 +00:00
return logo ;
}
2014-02-10 01:10:30 +00:00
2017-03-28 01:21:21 +00:00
bool EditorExportPlatformJavaScript : : can_export ( const Ref < EditorExportPreset > & p_preset , String & r_error , bool & r_missing_templates ) const {
2018-08-20 15:35:33 +00:00
String err ;
2020-01-07 12:29:02 +00:00
bool valid = false ;
2020-12-03 13:42:25 +00:00
ExportMode mode = ( ExportMode ) ( int ) p_preset - > get ( " variant/export_type " ) ;
2018-08-20 15:35:33 +00:00
2020-01-07 12:29:02 +00:00
// Look for export templates (first official, and if defined custom templates).
2020-12-03 13:42:25 +00: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 15:35:33 +00:00
if ( p_preset - > get ( " custom_template/debug " ) ! = " " ) {
2020-01-07 12:29:02 +00:00
dvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/debug " ) ) ;
if ( ! dvalid ) {
2019-01-21 17:34:53 +00:00
err + = TTR ( " Custom debug template not found. " ) + " \n " ;
2018-08-20 15:35:33 +00:00
}
}
if ( p_preset - > get ( " custom_template/release " ) ! = " " ) {
2020-01-07 12:29:02 +00:00
rvalid = FileAccess : : exists ( p_preset - > get ( " custom_template/release " ) ) ;
if ( ! rvalid ) {
2019-01-21 17:34:53 +00:00
err + = TTR ( " Custom release template not found. " ) + " \n " ;
2018-08-20 15:35:33 +00:00
}
}
2014-02-10 01:10:30 +00:00
2020-01-07 12:29:02 +00:00
valid = dvalid | | rvalid ;
2019-03-03 10:52:53 +00:00
r_missing_templates = ! valid ;
2020-01-07 12:29:02 +00:00
// Validate the rest of the configuration.
2019-03-03 10:52:53 +00: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 21:43:37 +00:00
}
2020-12-15 12:04:21 +00:00
if ( ! err . is_empty ( ) ) {
2018-08-20 15:35:33 +00:00
r_error = err ;
2020-05-14 14:41:43 +00:00
}
2014-02-10 01:10:30 +00:00
2018-08-20 15:35:33 +00:00
return valid ;
2017-03-28 01:21:21 +00:00
}
2014-02-10 01:10:30 +00:00
2018-10-29 21:18:49 +00: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 01:21:21 +00:00
}
2014-02-10 01:10:30 +00:00
2017-03-28 01:21:21 +00:00
Error EditorExportPlatformJavaScript : : export_project ( const Ref < EditorExportPreset > & p_preset , bool p_debug , const String & p_path , int p_flags ) {
2017-10-02 15:01:43 +00:00
ExportNotifier notifier ( * this , p_preset , p_debug , p_path , p_flags ) ;
2014-02-10 01:10:30 +00:00
2017-03-28 01:21:21 +00:00
String custom_debug = p_preset - > get ( " custom_template/debug " ) ;
String custom_release = p_preset - > get ( " custom_template/release " ) ;
2017-11-19 23:06:11 +00:00
String custom_html = p_preset - > get ( " html/custom_html_shell " ) ;
2021-03-26 11:44:17 +00:00
bool export_icon = p_preset - > get ( " html/export_icon " ) ;
2017-03-28 01:21:21 +00:00
String template_path = p_debug ? custom_debug : custom_release ;
2014-02-10 01:10:30 +00:00
2017-03-28 01:21:21 +00:00
template_path = template_path . strip_edges ( ) ;
if ( template_path = = String ( ) ) {
2020-12-03 13:42:25 +00: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 01:10:30 +00:00
}
2019-03-06 12:20:18 +00:00
if ( ! DirAccess : : exists ( p_path . get_base_dir ( ) ) ) {
2019-03-05 07:52:45 +00:00
return ERR_FILE_BAD_PATH ;
}
2017-03-28 01:21:21 +00:00
if ( template_path ! = String ( ) & & ! FileAccess : : exists ( template_path ) ) {
2018-01-04 15:08:24 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Template file not found: " ) + " \n " + template_path ) ;
2017-03-28 01:21:21 +00:00
return ERR_FILE_NOT_FOUND ;
}
2014-02-10 01:10:30 +00:00
2020-12-04 23:37:41 +00:00
Vector < SharedObject > shared_objects ;
2017-03-28 01:21:21 +00:00
String pck_path = p_path . get_basename ( ) + " .pck " ;
2020-12-04 23:37:41 +00:00
Error error = save_pack ( p_preset , pck_path , & shared_objects ) ;
2017-03-28 01:21:21 +00:00
if ( error ! = OK ) {
2018-01-04 15:08:24 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + pck_path ) ;
2017-03-28 01:21:21 +00:00
return error ;
}
2020-12-04 23:37:41 +00:00
DirAccess * da = DirAccess : : create ( DirAccess : : ACCESS_FILESYSTEM ) ;
for ( int i = 0 ; i < shared_objects . size ( ) ; i + + ) {
String dst = p_path . get_base_dir ( ) . plus_file ( shared_objects [ i ] . path . get_file ( ) ) ;
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 ) ;
2014-02-13 21:03:28 +00:00
2020-04-01 23:20:12 +00:00
FileAccess * src_f = nullptr ;
2017-03-28 01:21:21 +00:00
zlib_filefunc_def io = zipio_create_io_from_file ( & src_f ) ;
unzFile pkg = unzOpen2 ( template_path . utf8 ( ) . get_data ( ) , & io ) ;
2014-02-13 21:03:28 +00:00
2014-02-10 01:10:30 +00:00
if ( ! pkg ) {
2018-01-04 15:08:24 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not open template for export: " ) + " \n " + template_path ) ;
2014-02-10 01:10:30 +00:00
return ERR_FILE_NOT_FOUND ;
}
2017-11-19 23:06:11 +00:00
if ( unzGoToFirstFile ( pkg ) ! = UNZ_OK ) {
2018-01-04 15:08:24 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Invalid export template: " ) + " \n " + template_path ) ;
2017-11-19 23:06:11 +00:00
unzClose ( pkg ) ;
return ERR_FILE_CORRUPT ;
}
2014-02-10 01:10:30 +00:00
2021-03-01 16:53:56 +00:00
Vector < uint8_t > html ;
Dictionary file_sizes ;
2017-11-19 23:06:11 +00:00
do {
2014-02-10 01:10:30 +00:00
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
2020-04-01 23:20:12 +00:00
unzGetCurrentFileInfo ( pkg , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
2014-02-10 01:10:30 +00:00
2017-03-28 01:21:21 +00:00
String file = fname ;
2014-02-10 01:10:30 +00:00
2021-03-01 16:53:56 +00:00
// HTML is handled later
if ( file = = " godot.html " ) {
if ( custom_html . is_empty ( ) ) {
html . resize ( info . uncompressed_size ) ;
unzOpenCurrentFile ( pkg ) ;
unzReadCurrentFile ( pkg , html . ptrw ( ) , html . size ( ) ) ;
unzCloseCurrentFile ( pkg ) ;
}
continue ;
}
2014-02-10 01:10:30 +00:00
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
//read
unzOpenCurrentFile ( pkg ) ;
2017-11-25 03:07:54 +00:00
unzReadCurrentFile ( pkg , data . ptrw ( ) , data . size ( ) ) ;
2014-02-10 01:10:30 +00:00
unzCloseCurrentFile ( pkg ) ;
//write
2021-03-01 16:53:56 +00:00
if ( file = = " godot.js " ) {
2017-03-28 01:21:21 +00:00
file = p_path . get_file ( ) . get_basename ( ) + " .js " ;
2020-12-04 23:37:41 +00:00
2020-03-11 10:55:28 +00:00
} else if ( file = = " godot.worker.js " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .worker.js " ;
2020-12-04 23:37:41 +00:00
} else if ( file = = " godot.side.wasm " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .side.wasm " ;
2020-11-10 10:05:22 +00:00
} else if ( file = = " godot.audio.worklet.js " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .audio.worklet.js " ;
2017-03-28 01:21:21 +00:00
} else if ( file = = " godot.wasm " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .wasm " ;
2021-03-01 16:53:56 +00:00
file_sizes [ file . get_file ( ) ] = ( uint64_t ) info . uncompressed_size ;
2016-10-30 22:10:17 +00:00
}
2014-02-10 01:10:30 +00:00
String dst = p_path . get_base_dir ( ) . plus_file ( file ) ;
2017-03-28 01:21:21 +00:00
FileAccess * f = FileAccess : : open ( dst , FileAccess : : WRITE ) ;
2014-02-10 01:10:30 +00:00
if ( ! f ) {
2018-01-04 15:08:24 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + dst ) ;
2014-02-10 01:10:30 +00:00
unzClose ( pkg ) ;
return ERR_FILE_CANT_WRITE ;
}
2017-03-28 01:21:21 +00:00
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
2014-02-10 01:10:30 +00:00
memdelete ( f ) ;
2017-11-19 23:06:11 +00:00
} while ( unzGoToNextFile ( pkg ) = = UNZ_OK ) ;
unzClose ( pkg ) ;
2020-12-15 12:04:21 +00:00
if ( ! custom_html . is_empty ( ) ) {
2017-11-19 23:06:11 +00:00
FileAccess * f = FileAccess : : open ( custom_html , FileAccess : : READ ) ;
if ( ! f ) {
2018-01-04 15:08:24 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not read custom HTML shell: " ) + " \n " + custom_html ) ;
2017-11-19 23:06:11 +00:00
return ERR_FILE_CANT_READ ;
}
2021-03-01 16:53:56 +00:00
html . resize ( f - > get_len ( ) ) ;
f - > get_buffer ( html . ptrw ( ) , html . size ( ) ) ;
2017-11-19 23:06:11 +00:00
memdelete ( f ) ;
2021-03-01 16:53:56 +00:00
}
{
FileAccess * f = FileAccess : : open ( pck_path , FileAccess : : READ ) ;
if ( f ) {
file_sizes [ pck_path . get_file ( ) ] = ( uint64_t ) f - > get_len ( ) ;
memdelete ( f ) ;
2021-04-05 12:02:50 +00:00
f = nullptr ;
2021-03-01 16:53:56 +00:00
}
_fix_html ( html , p_preset , p_path . get_file ( ) . get_basename ( ) , p_debug , p_flags , shared_objects , file_sizes ) ;
2017-11-19 23:06:11 +00:00
f = FileAccess : : open ( p_path , FileAccess : : WRITE ) ;
if ( ! f ) {
2018-01-04 15:08:24 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + p_path ) ;
2017-11-19 23:06:11 +00:00
return ERR_FILE_CANT_WRITE ;
}
2021-03-01 16:53:56 +00:00
f - > store_buffer ( html . ptr ( ) , html . size ( ) ) ;
2017-11-19 23:06:11 +00:00
memdelete ( f ) ;
2021-03-01 16:53:56 +00:00
html . resize ( 0 ) ;
2014-02-10 01:10:30 +00:00
}
2017-11-19 23:06:11 +00:00
Ref < Image > splash ;
2020-01-20 20:46:42 +00:00
const String splash_path = String ( GLOBAL_GET ( " application/boot_splash/image " ) ) . strip_edges ( ) ;
2020-12-15 12:04:21 +00:00
if ( ! splash_path . is_empty ( ) ) {
2017-11-19 23:06:11 +00:00
splash . instance ( ) ;
2021-03-26 11:44:17 +00:00
const Error err = ImageLoader : : load_image ( splash_path , splash ) ;
2017-11-19 23:06:11 +00:00
if ( err ) {
2018-01-04 16:31:35 +00:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not read boot splash image file: " ) + " \n " + splash_path + " \n " + TTR ( " Using default boot splash image. " ) ) ;
2017-11-19 23:06:11 +00:00
splash . unref ( ) ;
}
}
if ( splash . is_null ( ) ) {
splash = Ref < Image > ( memnew ( Image ( boot_splash_png ) ) ) ;
}
2020-01-20 20:46:42 +00:00
const String splash_png_path = p_path . get_base_dir ( ) . plus_file ( p_path . get_file ( ) . get_basename ( ) + " .png " ) ;
if ( splash - > save_png ( splash_png_path ) ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + splash_png_path ) ;
2017-11-19 23:06:11 +00:00
return ERR_FILE_CANT_WRITE ;
}
2020-01-20 20:46:42 +00: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 11:44:17 +00:00
if ( export_icon ) {
Ref < Image > favicon ;
const String favicon_path = String ( GLOBAL_GET ( " application/config/icon " ) ) . strip_edges ( ) ;
if ( ! favicon_path . is_empty ( ) ) {
favicon . instance ( ) ;
const Error err = ImageLoader : : load_image ( favicon_path , favicon ) ;
if ( err ) {
favicon . unref ( ) ;
}
2020-01-20 20:46:42 +00:00
}
2021-03-26 11:44:17 +00:00
if ( favicon . is_null ( ) ) {
favicon = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) - > get_icon ( " DefaultProjectIcon " , " EditorIcons " ) - > get_image ( ) ;
}
const String favicon_png_path = p_path . get_base_dir ( ) . plus_file ( p_path . get_file ( ) . get_basename ( ) + " .icon.png " ) ;
2020-01-20 20:46:42 +00: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 ;
}
}
2014-02-10 01:10:30 +00:00
return OK ;
}
2019-10-11 11:47:28 +00:00
bool EditorExportPlatformJavaScript : : poll_export ( ) {
2017-06-27 01:13:36 +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 ;
}
}
2019-08-24 15:27:38 +00:00
int prev = menu_options ;
menu_options = preset . is_valid ( ) ;
if ( server - > is_listening ( ) ) {
if ( menu_options = = 0 ) {
2020-02-26 10:28:13 +00:00
MutexLock lock ( server_lock ) ;
2019-08-24 15:27:38 +00: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 01:13:36 +00:00
}
2019-10-11 11:47:28 +00:00
int EditorExportPlatformJavaScript : : get_options_count ( ) const {
2019-08-24 15:27:38 +00:00
return menu_options ;
2017-06-27 01:13:36 +00:00
}
2019-10-11 11:47:28 +00:00
Error EditorExportPlatformJavaScript : : run ( const Ref < EditorExportPreset > & p_preset , int p_option , int p_debug_flags ) {
2019-08-24 15:27:38 +00:00
if ( p_option = = 1 ) {
2020-02-26 10:28:13 +00:00
MutexLock lock ( server_lock ) ;
2019-08-24 15:27:38 +00:00
server - > stop ( ) ;
return OK ;
}
2021-03-26 10:48:56 +00:00
const String dest = EditorSettings : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " web " ) ;
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 20:46:42 +00:00
Error err = export_project ( p_preset , true , basepath + " .html " , p_debug_flags ) ;
2019-08-09 11:45:30 +00:00
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 + " .js " ) ;
2020-03-11 10:55:28 +00:00
DirAccess : : remove_file_or_error ( basepath + " .worker.js " ) ;
2020-11-10 10:05:22 +00:00
DirAccess : : remove_file_or_error ( basepath + " .audio.worklet.js " ) ;
2019-08-09 11:45:30 +00:00
DirAccess : : remove_file_or_error ( basepath + " .pck " ) ;
DirAccess : : remove_file_or_error ( basepath + " .png " ) ;
2020-12-04 23:37:41 +00:00
DirAccess : : remove_file_or_error ( basepath + " .side.wasm " ) ;
2019-08-09 11:45:30 +00:00
DirAccess : : remove_file_or_error ( basepath + " .wasm " ) ;
2021-03-26 11:44:17 +00:00
DirAccess : : remove_file_or_error ( basepath + " .icon.png " ) ;
2014-02-10 01:10:30 +00:00
return err ;
2017-03-28 01:21:21 +00:00
}
2019-08-24 15:27:38 +00:00
2020-01-20 20:46:42 +00:00
const uint16_t bind_port = EDITOR_GET ( " export/web/http_port " ) ;
2019-08-24 15:27:38 +00:00
// Resolve host if needed.
2020-01-20 20:46:42 +00:00
const String bind_host = EDITOR_GET ( " export/web/http_host " ) ;
IP_Address bind_ip ;
2019-08-24 15:27:38 +00: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'. " ) ;
// Restart server.
2020-02-26 10:28:13 +00:00
{
MutexLock lock ( server_lock ) ;
server - > stop ( ) ;
err = server - > listen ( bind_port , bind_ip ) ;
}
2021-03-26 10:48:56 +00:00
if ( err ! = OK ) {
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Error starting HTTP server: " ) + " \n " + itos ( err ) ) ;
return err ;
}
2019-08-24 15:27:38 +00:00
OS : : get_singleton ( ) - > shell_open ( String ( " http:// " + bind_host + " : " + itos ( bind_port ) + " /tmp_js_export.html " ) ) ;
2019-08-09 11:45:30 +00:00
// FIXME: Find out how to clean up export files after running the successfully
// exported game. Might not be trivial.
2014-02-10 01:10:30 +00:00
return OK ;
}
2019-06-11 18:43:37 +00:00
Ref < Texture2D > EditorExportPlatformJavaScript : : get_run_icon ( ) const {
2017-05-25 18:57:13 +00:00
return run_icon ;
}
2019-08-24 15:27:38 +00: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 10:28:13 +00:00
{
MutexLock lock ( ej - > server_lock ) ;
ej - > server - > poll ( ) ;
}
2019-08-24 15:27:38 +00:00
}
}
2014-02-10 01:10:30 +00:00
EditorExportPlatformJavaScript : : EditorExportPlatformJavaScript ( ) {
2019-08-24 15:27:38 +00:00
server . instance ( ) ;
2021-01-19 12:29:41 +00:00
server_thread . start ( _server_thread_poll , this ) ;
2019-08-24 15:27:38 +00:00
2017-05-17 10:36:47 +00:00
Ref < Image > img = memnew ( Image ( _javascript_logo ) ) ;
2017-03-28 01:21:21 +00:00
logo . instance ( ) ;
2014-02-10 01:10:30 +00:00
logo - > create_from_image ( img ) ;
2017-05-25 18:57:13 +00:00
img = Ref < Image > ( memnew ( Image ( _javascript_run_icon ) ) ) ;
run_icon . instance ( ) ;
run_icon - > create_from_image ( img ) ;
2017-06-27 01:13:36 +00:00
2019-08-24 15:27:38 +00:00
Ref < Theme > theme = EditorNode : : get_singleton ( ) - > get_editor_theme ( ) ;
2020-05-14 14:41:43 +00:00
if ( theme . is_valid ( ) ) {
2019-08-24 15:27:38 +00:00
stop_icon = theme - > get_icon ( " Stop " , " EditorIcons " ) ;
2020-05-14 14:41:43 +00:00
} else {
2019-08-24 15:27:38 +00:00
stop_icon . instance ( ) ;
2020-05-14 14:41:43 +00:00
}
2019-08-24 15:27:38 +00:00
}
EditorExportPlatformJavaScript : : ~ EditorExportPlatformJavaScript ( ) {
server - > stop ( ) ;
server_quit = true ;
2021-01-19 12:29:41 +00:00
server_thread . wait_to_finish ( ) ;
2014-02-10 01:10:30 +00:00
}
void register_javascript_exporter ( ) {
2019-08-24 15:27:38 +00:00
EDITOR_DEF ( " export/web/http_host " , " localhost " ) ;
EDITOR_DEF ( " export/web/http_port " , 8060 ) ;
EditorSettings : : get_singleton ( ) - > add_property_hint ( PropertyInfo ( Variant : : INT , " export/web/http_port " , PROPERTY_HINT_RANGE , " 1,65535,1 " ) ) ;
2017-03-28 01:21:21 +00:00
Ref < EditorExportPlatformJavaScript > platform ;
platform . instance ( ) ;
EditorExport : : get_singleton ( ) - > add_export_platform ( platform ) ;
2014-02-10 01:10:30 +00:00
}