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
/*************************************************************************/
2020-01-01 11:16:22 +01:00
/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2020 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
2020-03-16 17:02:14 +01:00
# include "core/io/json.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
2017-03-28 03:21:21 +02:00
# define EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE "webassembly_release.zip"
# define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip"
2014-02-10 02:10:30 +01:00
2019-08-24 17:27:38 +02:00
class EditorHTTPServer : public Reference {
private :
Ref < TCP_Server > server ;
Ref < StreamPeerTCP > connection ;
uint64_t time ;
uint8_t req_buf [ 4096 ] ;
int req_pos ;
void _clear_client ( ) {
connection = Ref < StreamPeerTCP > ( ) ;
memset ( req_buf , 0 , sizeof ( req_buf ) ) ;
time = 0 ;
req_pos = 0 ;
}
public :
EditorHTTPServer ( ) {
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. " ) ;
2020-12-05 00:37:41 +01:00
const String cache_path = EditorSettings : : get_singleton ( ) - > get_cache_dir ( ) ;
2020-01-20 21:46:42 +01:00
const String basereq = " /tmp_js_export " ;
2020-12-05 00:37:41 +01:00
String filepath ;
String ctype ;
2019-08-24 17:27:38 +02:00
if ( req [ 1 ] = = basereq + " .html " ) {
2020-12-05 00:37:41 +01:00
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ;
2020-01-14 15:01:00 +01:00
ctype = " text/html " ;
2019-08-24 17:27:38 +02:00
} else if ( req [ 1 ] = = basereq + " .js " ) {
2020-12-05 00:37:41 +01:00
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ;
2020-01-14 15:01:00 +01:00
ctype = " application/javascript " ;
2020-11-10 11:05:22 +01:00
} else if ( req [ 1 ] = = basereq + " .audio.worklet.js " ) {
2020-12-05 00:37:41 +01:00
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ;
2020-11-10 11:05:22 +01:00
ctype = " application/javascript " ;
2020-03-11 11:55:28 +01:00
} else if ( req [ 1 ] = = basereq + " .worker.js " ) {
2020-12-05 00:37:41 +01:00
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ;
2020-03-11 11:55:28 +01:00
ctype = " application/javascript " ;
2019-08-24 17:27:38 +02:00
} else if ( req [ 1 ] = = basereq + " .pck " ) {
2020-12-05 00:37:41 +01:00
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ;
2020-01-14 15:01:00 +01:00
ctype = " application/octet-stream " ;
2020-01-20 21:46:42 +01:00
} else if ( req [ 1 ] = = basereq + " .png " | | req [ 1 ] = = " /favicon.png " ) {
// Also allow serving the generated favicon for a smoother loading experience.
if ( req [ 1 ] = = " /favicon.png " ) {
filepath = EditorSettings : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " favicon.png " ) ;
} else {
2020-12-05 00:37:41 +01:00
filepath = basereq + " .png " ;
2020-01-20 21:46:42 +01:00
}
2020-01-14 15:01:00 +01:00
ctype = " image/png " ;
2020-12-05 00:37:41 +01:00
} else if ( req [ 1 ] = = basereq + " .side.wasm " ) {
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ;
ctype = " application/wasm " ;
2019-08-24 17:27:38 +02:00
} else if ( req [ 1 ] = = basereq + " .wasm " ) {
2020-12-05 00:37:41 +01:00
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ;
2020-01-14 15:01:00 +01:00
ctype = " application/wasm " ;
2020-12-05 00:37:41 +01:00
} else if ( req [ 1 ] . ends_with ( " .wasm " ) ) {
filepath = cache_path . plus_file ( req [ 1 ] . get_file ( ) ) ; // TODO dangerous?
ctype = " application/wasm " ;
}
if ( filepath . empty ( ) | | ! 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 ( ) ;
connection - > put_data ( ( const uint8_t * ) cs . get_data ( ) , cs . size ( ) - 1 ) ;
return ;
}
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 " ;
2019-08-24 17:27:38 +02: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 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 ] ;
int read = f - > get_buffer ( bytes , 4096 ) ;
if ( read < 1 ) {
break ;
}
err = connection - > 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
}
2019-08-24 17:27:38 +02:00
if ( connection . 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
}
2019-08-24 17:27:38 +02: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 16:41:43 +02:00
if ( connection - > 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
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 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 ;
int menu_options ;
2014-02-10 02:10:30 +01:00
2020-12-05 00:37:41 +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 ) ;
2014-02-10 02:10:30 +01:00
2019-08-24 17:27:38 +02:00
private :
Ref < EditorHTTPServer > server ;
bool server_quit ;
2020-02-26 11:28:13 +01:00
Mutex server_lock ;
2019-08-24 17:27:38 +02:00
Thread * server_thread ;
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
} ;
2020-12-05 00:37:41 +01: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 ) {
2017-03-28 03:21:21 +02: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 " ) ;
2020-03-16 17:02:14 +01:00
Vector < String > flags ;
String flags_json ;
gen_export_flags ( flags , p_flags ) ;
flags_json = JSON : : print ( flags ) ;
2020-12-05 00:37:41 +01:00
String libs ;
for ( int i = 0 ; i < p_shared_objects . size ( ) ; i + + ) {
libs + = " \" " + p_shared_objects [ i ] . path . get_file ( ) + " \" , " ;
}
2014-02-10 02:10:30 +01:00
2017-03-28 03:21:21 +02:00
for ( int i = 0 ; i < lines . size ( ) ; i + + ) {
String current_line = lines [ i ] ;
2017-08-13 13:10:04 +02:00
current_line = current_line . replace ( " $GODOT_BASENAME " , p_name ) ;
2020-01-20 22:35:36 +01:00
current_line = current_line . replace ( " $GODOT_PROJECT_NAME " , ProjectSettings : : get_singleton ( ) - > get_setting ( " application/config/name " ) ) ;
2017-03-28 03:21:21 +02:00
current_line = current_line . replace ( " $GODOT_HEAD_INCLUDE " , p_preset - > get ( " html/head_include " ) ) ;
2020-07-30 17:31:35 +02:00
current_line = current_line . replace ( " $GODOT_FULL_WINDOW " , p_preset - > get ( " html/full_window_size " ) ? " true " : " false " ) ;
2020-12-05 00:37:41 +01:00
current_line = current_line . replace ( " $GODOT_GDNATIVE_LIBS " , libs ) ;
2017-03-28 03:21:21 +02:00
current_line = current_line . replace ( " $GODOT_DEBUG_ENABLED " , p_debug ? " true " : " false " ) ;
2020-03-16 17:02:14 +01:00
current_line = current_line . replace ( " $GODOT_ARGS " , flags_json ) ;
2017-03-28 03:21:21 +02:00
str_export + = current_line + " \n " ;
}
2014-02-10 02:10:30 +01:00
2017-03-28 03:21:21 +02:00
CharString cs = str_export . utf8 ( ) ;
p_html . resize ( cs . length ( ) ) ;
for ( int i = 0 ; i < cs . length ( ) ; i + + ) {
2018-07-25 03:11:03 +02:00
p_html . write [ i ] = cs [ i ] ;
2017-03-28 03:21:21 +02:00
}
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 " ) ) {
String driver = ProjectSettings : : get_singleton ( ) - > get ( " rendering/quality/driver/driver_name " ) ;
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
}
}
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 " ) , " " ) ) ;
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
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 ) , " " ) ) ;
2020-07-30 17:31:35 +02:00
r_options - > push_back ( ExportOption ( PropertyInfo ( Variant : : BOOL , " html/full_window_size " ) , true ) ) ;
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 ;
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).
bool dvalid = exists_export_template ( EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG , & err ) ;
bool rvalid = exists_export_template ( EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE , & 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-05-14 16:41:43 +02:00
if ( ! err . 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
2017-03-28 03:21:21 +02:00
String custom_debug = p_preset - > get ( " custom_template/debug " ) ;
String custom_release = p_preset - > get ( " custom_template/release " ) ;
2017-11-20 00:06:11 +01:00
String custom_html = p_preset - > get ( " html/custom_html_shell " ) ;
2017-03-28 03:21:21 +02:00
String template_path = p_debug ? custom_debug : custom_release ;
2014-02-10 02:10:30 +01:00
2017-03-28 03:21:21 +02:00
template_path = template_path . strip_edges ( ) ;
if ( template_path = = String ( ) ) {
2020-05-14 16:41:43 +02:00
if ( p_debug ) {
2017-11-18 05:50:26 +01:00
template_path = find_export_template ( EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG ) ;
2020-05-14 16:41:43 +02:00
} else {
2017-11-18 05:50:26 +01:00
template_path = find_export_template ( EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE ) ;
2020-05-14 16:41:43 +02:00
}
2014-02-10 02:10:30 +01:00
}
2019-03-06 13:20:18 +01:00
if ( ! DirAccess : : exists ( p_path . get_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
2020-12-05 00:37:41 +01:00
Vector < SharedObject > shared_objects ;
2017-03-28 03:21:21 +02:00
String pck_path = p_path . get_basename ( ) + " .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 + + ) {
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 22:03:28 +01:00
2020-04-02 01:20:12 +02:00
FileAccess * src_f = nullptr ;
2017-03-28 03:21:21 +02:00
zlib_filefunc_def io = zipio_create_io_from_file ( & src_f ) ;
unzFile pkg = unzOpen2 ( template_path . utf8 ( ) . get_data ( ) , & io ) ;
2014-02-13 22:03:28 +01:00
2014-02-10 02:10:30 +01:00
if ( ! pkg ) {
2018-01-04 16:08:24 +01:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not open template for export: " ) + " \n " + template_path ) ;
2014-02-10 02:10:30 +01:00
return ERR_FILE_NOT_FOUND ;
}
2017-11-20 00:06:11 +01:00
if ( unzGoToFirstFile ( pkg ) ! = UNZ_OK ) {
2018-01-04 16:08:24 +01:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Invalid export template: " ) + " \n " + template_path ) ;
2017-11-20 00:06:11 +01:00
unzClose ( pkg ) ;
return ERR_FILE_CORRUPT ;
}
2014-02-10 02:10:30 +01:00
2017-11-20 00:06:11 +01:00
do {
2014-02-10 02:10:30 +01:00
//get filename
unz_file_info info ;
char fname [ 16384 ] ;
2020-04-02 01:20:12 +02:00
unzGetCurrentFileInfo ( pkg , & info , fname , 16384 , nullptr , 0 , nullptr , 0 ) ;
2014-02-10 02:10:30 +01:00
2017-03-28 03:21:21 +02:00
String file = fname ;
2014-02-10 02:10:30 +01:00
Vector < uint8_t > data ;
data . resize ( info . uncompressed_size ) ;
//read
unzOpenCurrentFile ( pkg ) ;
2017-11-25 04:07:54 +01:00
unzReadCurrentFile ( pkg , data . ptrw ( ) , data . size ( ) ) ;
2014-02-10 02:10:30 +01:00
unzCloseCurrentFile ( pkg ) ;
//write
2017-03-28 03:21:21 +02:00
if ( file = = " godot.html " ) {
2017-11-20 00:06:11 +01:00
if ( ! custom_html . empty ( ) ) {
continue ;
}
2020-12-05 00:37:41 +01:00
_fix_html ( data , p_preset , p_path . get_file ( ) . get_basename ( ) , p_debug , p_flags , shared_objects ) ;
2017-03-28 03:21:21 +02:00
file = p_path . get_file ( ) ;
2017-11-20 00:06:11 +01:00
2017-03-28 03:21:21 +02:00
} else if ( file = = " godot.js " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .js " ;
2020-12-05 00:37:41 +01:00
2020-03-11 11:55:28 +01:00
} else if ( file = = " godot.worker.js " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .worker.js " ;
2020-12-05 00:37:41 +01:00
} else if ( file = = " godot.side.wasm " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .side.wasm " ;
2020-11-10 11:05:22 +01:00
} else if ( file = = " godot.audio.worklet.js " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .audio.worklet.js " ;
2017-03-28 03:21:21 +02:00
} else if ( file = = " godot.wasm " ) {
file = p_path . get_file ( ) . get_basename ( ) + " .wasm " ;
2016-10-30 23:10:17 +01:00
}
2014-02-10 02:10:30 +01:00
String dst = p_path . get_base_dir ( ) . plus_file ( file ) ;
2017-03-28 03:21:21 +02:00
FileAccess * f = FileAccess : : open ( dst , FileAccess : : WRITE ) ;
2014-02-10 02:10:30 +01:00
if ( ! f ) {
2018-01-04 16:08:24 +01:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + dst ) ;
2014-02-10 02:10:30 +01:00
unzClose ( pkg ) ;
return ERR_FILE_CANT_WRITE ;
}
2017-03-28 03:21:21 +02:00
f - > store_buffer ( data . ptr ( ) , data . size ( ) ) ;
2014-02-10 02:10:30 +01:00
memdelete ( f ) ;
2017-11-20 00:06:11 +01:00
} while ( unzGoToNextFile ( pkg ) = = UNZ_OK ) ;
unzClose ( pkg ) ;
if ( ! custom_html . empty ( ) ) {
FileAccess * f = FileAccess : : open ( custom_html , FileAccess : : READ ) ;
if ( ! f ) {
2018-01-04 16:08:24 +01:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not read custom HTML shell: " ) + " \n " + custom_html ) ;
2017-11-20 00:06:11 +01:00
return ERR_FILE_CANT_READ ;
}
Vector < uint8_t > buf ;
buf . resize ( f - > get_len ( ) ) ;
2017-11-25 04:07:54 +01:00
f - > get_buffer ( buf . ptrw ( ) , buf . size ( ) ) ;
2017-11-20 00:06:11 +01:00
memdelete ( f ) ;
2020-12-05 00:37:41 +01:00
_fix_html ( buf , p_preset , p_path . get_file ( ) . get_basename ( ) , p_debug , p_flags , shared_objects ) ;
2017-11-20 00:06:11 +01:00
f = FileAccess : : open ( p_path , FileAccess : : WRITE ) ;
if ( ! f ) {
2018-01-04 16:08:24 +01:00
EditorNode : : get_singleton ( ) - > show_warning ( TTR ( " Could not write file: " ) + " \n " + p_path ) ;
2017-11-20 00:06:11 +01:00
return ERR_FILE_CANT_WRITE ;
}
f - > store_buffer ( buf . ptr ( ) , buf . size ( ) ) ;
memdelete ( f ) ;
2014-02-10 02:10:30 +01:00
}
2017-11-20 00:06:11 +01:00
Ref < Image > splash ;
2020-01-20 21:46:42 +01:00
const String splash_path = String ( GLOBAL_GET ( " application/boot_splash/image " ) ) . strip_edges ( ) ;
2017-11-20 00:06:11 +01:00
if ( ! splash_path . empty ( ) ) {
splash . instance ( ) ;
2020-01-20 21:46:42 +01:00
const Error err = splash - > load ( splash_path ) ;
2017-11-20 00:06:11 +01:00
if ( err ) {
2018-01-04 17:31:35 +01: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-20 00:06:11 +01:00
splash . unref ( ) ;
}
}
if ( splash . is_null ( ) ) {
splash = Ref < Image > ( memnew ( Image ( boot_splash_png ) ) ) ;
}
2020-01-20 21:46:42 +01: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-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.
Ref < Image > favicon ;
const String favicon_path = String ( GLOBAL_GET ( " application/config/icon " ) ) . strip_edges ( ) ;
if ( ! favicon_path . empty ( ) ) {
favicon . instance ( ) ;
const Error err = favicon - > load ( favicon_path ) ;
if ( err ) {
favicon . unref ( ) ;
}
}
if ( favicon . is_valid ( ) ) {
const String favicon_png_path = p_path . get_base_dir ( ) . plus_file ( " favicon.png " ) ;
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 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 ;
}
2020-01-20 21:46:42 +01:00
const String basepath = EditorSettings : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " tmp_js_export " ) ;
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 " ) ;
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 " ) ;
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 " ) ;
2020-01-20 21:46:42 +01:00
DirAccess : : remove_file_or_error ( EditorSettings : : get_singleton ( ) - > get_cache_dir ( ) . plus_file ( " favicon.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 " ) ;
IP_Address 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'. " ) ;
// Restart server.
2020-02-26 11:28:13 +01:00
{
MutexLock lock ( server_lock ) ;
server - > stop ( ) ;
err = server - > listen ( bind_port , bind_ip ) ;
}
2019-08-24 17:27:38 +02:00
ERR_FAIL_COND_V_MSG ( err ! = OK , err , " Unable to start HTTP server. " ) ;
OS : : get_singleton ( ) - > shell_open ( String ( " 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 ( ) {
2019-08-24 17:27:38 +02:00
server . instance ( ) ;
server_quit = false ;
server_thread = Thread : : create ( _server_thread_poll , this ) ;
2017-05-17 12:36:47 +02:00
Ref < Image > img = memnew ( Image ( _javascript_logo ) ) ;
2017-03-28 03:21:21 +02:00
logo . instance ( ) ;
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 ) ) ) ;
run_icon . instance ( ) ;
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 {
2019-08-24 17:27:38 +02:00
stop_icon . instance ( ) ;
2020-05-14 16:41:43 +02:00
}
2019-08-24 17:27:38 +02:00
menu_options = 0 ;
}
EditorExportPlatformJavaScript : : ~ EditorExportPlatformJavaScript ( ) {
server - > stop ( ) ;
server_quit = true ;
Thread : : wait_to_finish ( server_thread ) ;
memdelete ( server_thread ) ;
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 ) ;
EditorSettings : : get_singleton ( ) - > add_property_hint ( PropertyInfo ( Variant : : INT , " export/web/http_port " , PROPERTY_HINT_RANGE , " 1,65535,1 " ) ) ;
2017-03-28 03:21:21 +02:00
Ref < EditorExportPlatformJavaScript > platform ;
platform . instance ( ) ;
EditorExport : : get_singleton ( ) - > add_export_platform ( platform ) ;
2014-02-10 02:10:30 +01:00
}