diff options
Diffstat (limited to 'modules')
47 files changed, 1689 insertions, 1413 deletions
diff --git a/modules/SCsub b/modules/SCsub index 67f5893db4..36c2472c42 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -13,7 +13,7 @@ env.modules_sources = [ for x in env.module_list: if (x in env.disabled_modules): continue - env_modules.Append(CPPFLAGS=["-DMODULE_" + x.upper() + "_ENABLED"]) + env_modules.Append(CPPDEFINES=["MODULE_" + x.upper() + "_ENABLED"]) SConscript(x + "/SCsub") if env.split_modules: diff --git a/modules/assimp/SCsub b/modules/assimp/SCsub index 0da7e432e2..8a77e4f803 100644 --- a/modules/assimp/SCsub +++ b/modules/assimp/SCsub @@ -17,60 +17,60 @@ env_assimp.Prepend(CPPPATH=['#thirdparty/zlib/']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/contrib/openddlparser/include']) env_assimp.Prepend(CPPPATH=['#thirdparty/assimp/contrib/rapidjson/include']) env_assimp.Prepend(CPPPATH=['.']) -#env_assimp.Append(CPPFLAGS=['-DASSIMP_DOUBLE_PRECISION']) # TODO default to what godot is compiled with for future double support -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_BOOST_WORKAROUND']) -env_assimp.Append(CPPFLAGS=['-DOPENDDLPARSER_BUILD']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OWN_ZLIB']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_EXPORT']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_X_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_AMF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3DS_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD3_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD5_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MDL_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD2_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_PLY_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_ASE_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OBJ_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_HMP_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_SMD_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MDC_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD5_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_STL_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_LWO_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_DXF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_NFF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_RAW_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_SIB_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OFF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_AC_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_BVH_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IRRMESH_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IRR_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_Q3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_B3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_COLLADA_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_TERRAGEN_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_CSM_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_LWS_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OGRE_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OPENGEX_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MS3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_COB_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_BLEND_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_Q3BSP_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_NDO_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_STEP_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IFC_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_XGL_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_ASSBIN_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_GLTF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_C4D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3MF_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_X3D_IMPORTER']) +#env_assimp.Append(CPPDEFINES=['ASSIMP_DOUBLE_PRECISION']) # TODO default to what godot is compiled with for future double support +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_BOOST_WORKAROUND']) +env_assimp.Append(CPPDEFINES=['OPENDDLPARSER_BUILD']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OWN_ZLIB']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_EXPORT']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_X_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_AMF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_3DS_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD3_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD5_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MDL_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD2_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_PLY_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_ASE_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OBJ_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_HMP_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_SMD_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MDC_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MD5_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_STL_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_LWO_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_DXF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_NFF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_RAW_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_SIB_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OFF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_AC_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_BVH_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_IRRMESH_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_IRR_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_Q3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_B3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_COLLADA_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_TERRAGEN_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_CSM_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_LWS_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OGRE_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_OPENGEX_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_MS3D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_COB_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_BLEND_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_Q3BSP_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_NDO_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_STEP_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_IFC_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_XGL_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_ASSBIN_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_GLTF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_C4D_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_3MF_IMPORTER']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_NO_X3D_IMPORTER']) -env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_SINGLETHREADED']) +env_assimp.Append(CPPDEFINES=['ASSIMP_BUILD_SINGLETHREADED']) if (not env.msvc): env_assimp.Append(CXXFLAGS=['-std=c++11']) @@ -78,14 +78,14 @@ elif (env.msvc == False and env['platform'] == 'windows'): env_assimp.Append(LDFLAGS=['-pthread']) if(env['platform'] == 'windows'): - env_assimp.Append(CPPFLAGS=['-DPLATFORM_WINDOWS']) - env_assimp.Append(CPPFLAGS=['-DPLATFORM=WINDOWS']) + env_assimp.Append(CPPDEFINES=['PLATFORM_WINDOWS']) + env_assimp.Append(CPPDEFINES=[('PLATFORM', 'WINDOWS')]) elif(env['platform'] == 'x11'): - env_assimp.Append(CPPFLAGS=['-DPLATFORM_LINUX']) - env_assimp.Append(CPPFLAGS=['-DPLATFORM=LINUX']) + env_assimp.Append(CPPDEFINES=['PLATFORM_LINUX']) + env_assimp.Append(CPPDEFINES=[('PLATFORM', 'LINUX')]) elif(env['platform'] == 'osx'): - env_assimp.Append(CPPFLAGS=['-DPLATFORM_DARWIN']) - env_assimp.Append(CPPFLAGS=['-DPLATFORM=DARWIN']) + env_assimp.Append(CPPDEFINES=['PLATFORM_DARWIN']) + env_assimp.Append(CPPDEFINES=[('PLATFORM', 'DARWIN')]) env_thirdparty = env_assimp.Clone() env_thirdparty.disable_warnings() diff --git a/modules/bullet/SCsub b/modules/bullet/SCsub index 2fe7a1b4c0..ecc8a9b481 100644 --- a/modules/bullet/SCsub +++ b/modules/bullet/SCsub @@ -192,7 +192,7 @@ if env['builtin_bullet']: else: env_bullet.Prepend(CPPPATH=[thirdparty_dir]) # if env['target'] == "debug" or env['target'] == "release_debug": - # env_bullet.Append(CPPFLAGS=['-DBT_DEBUG']) + # env_bullet.Append(CPPDEFINES=['BT_DEBUG']) env_thirdparty = env_bullet.Clone() env_thirdparty.disable_warnings() diff --git a/modules/enet/SCsub b/modules/enet/SCsub index 78d8d43414..485c33b1a8 100644 --- a/modules/enet/SCsub +++ b/modules/enet/SCsub @@ -22,7 +22,7 @@ if env['builtin_enet']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_enet.Prepend(CPPPATH=[thirdparty_dir]) - env_enet.Append(CPPFLAGS=["-DGODOT_ENET"]) + env_enet.Append(CPPDEFINES=["GODOT_ENET"]) env_thirdparty = env_enet.Clone() env_thirdparty.disable_warnings() diff --git a/modules/freetype/SCsub b/modules/freetype/SCsub index 0ea581220e..b47377cbc4 100644 --- a/modules/freetype/SCsub +++ b/modules/freetype/SCsub @@ -65,16 +65,16 @@ if env['builtin_freetype']: # Also needed in main env for scene/ env.Prepend(CPPPATH=[thirdparty_dir + "/include"]) - env_freetype.Append(CPPFLAGS=['-DFT2_BUILD_LIBRARY', '-DFT_CONFIG_OPTION_USE_PNG']) + env_freetype.Append(CPPDEFINES=['FT2_BUILD_LIBRARY', 'FT_CONFIG_OPTION_USE_PNG']) if (env['target'] != 'release'): - env_freetype.Append(CPPFLAGS=['-DZLIB_DEBUG']) + env_freetype.Append(CPPDEFINES=['ZLIB_DEBUG']) # Also requires libpng headers if env['builtin_libpng']: env_freetype.Prepend(CPPPATH=["#thirdparty/libpng"]) sfnt = thirdparty_dir + 'src/sfnt/sfnt.c' - # Must be done after all CPPFLAGS are being set so we can copy them. + # Must be done after all CPPDEFINES are being set so we can copy them. if env['platform'] == 'javascript': # Forcibly undefine this macro so SIMD is not used in this file, # since currently unsupported in WASM @@ -103,4 +103,4 @@ if env['builtin_freetype']: # Godot source files env_freetype.add_source_files(env.modules_sources, "*.cpp") # Used in scene/, needs to be in main env -env.Append(CPPFLAGS=['-DFREETYPE_ENABLED']) +env.Append(CPPDEFINES=['FREETYPE_ENABLED']) diff --git a/modules/gdnative/nativescript/SCsub b/modules/gdnative/nativescript/SCsub index 5841ad5531..92c9d6630d 100644 --- a/modules/gdnative/nativescript/SCsub +++ b/modules/gdnative/nativescript/SCsub @@ -4,7 +4,6 @@ Import('env') Import('env_gdnative') env_gdnative.add_source_files(env.modules_sources, '*.cpp') -env_gdnative.Append(CPPFLAGS=['-DGDAPI_BUILT_IN']) if "platform" in env and env["platform"] in ["x11", "iphone"]: env.Append(LINKFLAGS=["-rdynamic"]) diff --git a/modules/gdnative/pluginscript/pluginscript_language.cpp b/modules/gdnative/pluginscript/pluginscript_language.cpp index 4bbadc62e7..9de073fc8e 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.cpp +++ b/modules/gdnative/pluginscript/pluginscript_language.cpp @@ -159,7 +159,7 @@ String PluginScriptLanguage::make_function(const String &p_class, const String & return String(); } -Error PluginScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_force, String &r_call_hint) { +Error PluginScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { if (_desc.complete_code) { Array options; godot_error tmp = _desc.complete_code( @@ -171,7 +171,8 @@ Error PluginScriptLanguage::complete_code(const String &p_code, const String &p_ &r_force, (godot_string *)&r_call_hint); for (int i = 0; i < options.size(); i++) { - r_options->push_back(String(options[i])); + ScriptCodeCompletionOption option(options[i], ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_options->push_back(option); } return (Error)tmp; } diff --git a/modules/gdnative/pluginscript/pluginscript_language.h b/modules/gdnative/pluginscript/pluginscript_language.h index a11e916975..7b3844d0b0 100644 --- a/modules/gdnative/pluginscript/pluginscript_language.h +++ b/modules/gdnative/pluginscript/pluginscript_language.h @@ -81,7 +81,7 @@ public: virtual bool can_inherit_from_file() { return true; } virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const; - virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_force, String &r_call_hint); + virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint); virtual void auto_indent_code(String &p_code, int p_from_line, int p_to_line) const; virtual void add_global_constant(const StringName &p_variable, const Variant &p_value); diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 1756f6eabc..a5ad23c75d 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -458,7 +458,7 @@ public: virtual bool can_inherit_from_file() { return true; } virtual int find_function(const String &p_function, const String &p_code) const; virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const; - virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint); + virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint); #ifdef TOOLS_ENABLED virtual Error lookup_code(const String &p_code, const String &p_symbol, const String &p_path, Object *p_owner, LookupResult &r_result); #endif diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 26b14a6148..78a1bfc99b 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -509,12 +509,14 @@ struct GDScriptCompletionIdentifier { assigned_expression(NULL) {} }; -static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Set<String> &r_list) { +static void _get_directory_contents(EditorFileSystemDirectory *p_dir, Map<String, ScriptCodeCompletionOption> &r_list) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; for (int i = 0; i < p_dir->get_file_count(); i++) { - r_list.insert(quote_style + p_dir->get_file_path(i) + quote_style); + ScriptCodeCompletionOption option(p_dir->get_file_path(i), ScriptCodeCompletionOption::KIND_FILE_PATH); + option.insert_text = quote_style + option.display + quote_style; + r_list.insert(option.display, option); } for (int i = 0; i < p_dir->get_subdir_count(); i++) { @@ -1807,14 +1809,15 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio return arghint; } -static void _find_enumeration_candidates(const String p_enum_hint, Set<String> &r_result) { +static void _find_enumeration_candidates(const String p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) { if (p_enum_hint.find(".") == -1) { // Global constant StringName current_enum = p_enum_hint; for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) { if (GlobalConstants::get_global_constant_enum(i) == current_enum) { - r_result.insert(GlobalConstants::get_global_constant_name(i)); + ScriptCodeCompletionOption option(GlobalConstants::get_global_constant_name(i), ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); } } } else { @@ -1829,15 +1832,17 @@ static void _find_enumeration_candidates(const String p_enum_hint, Set<String> & ClassDB::get_enum_constants(class_name, enum_name, &enum_constants); for (List<StringName>::Element *E = enum_constants.front(); E; E = E->next()) { String candidate = class_name + "." + E->get(); - r_result.insert(candidate); + ScriptCodeCompletionOption option(candidate, ScriptCodeCompletionOption::KIND_ENUM); + r_result.insert(option.display, option); } } } -static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Set<String> &r_result) { +static void _find_identifiers_in_block(const GDScriptCompletionContext &p_context, Map<String, ScriptCodeCompletionOption> &r_result) { for (Map<StringName, GDScriptParser::LocalVarNode *>::Element *E = p_context.block->variables.front(); E; E = E->next()) { if (E->get()->line < p_context.line) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_VARIABLE); + r_result.insert(option.display, option); } } if (p_context.block->parent_block) { @@ -1847,40 +1852,47 @@ static void _find_identifiers_in_block(const GDScriptCompletionContext &p_contex } } -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result); +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result); -static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Set<String> &r_result) { +static void _find_identifiers_in_class(const GDScriptCompletionContext &p_context, bool p_static, bool p_only_functions, bool p_parent_only, Map<String, ScriptCodeCompletionOption> &r_result) { if (!p_parent_only) { if (!p_static && !p_only_functions) { for (int i = 0; i < p_context._class->variables.size(); i++) { - r_result.insert(p_context._class->variables[i].identifier); + ScriptCodeCompletionOption option(p_context._class->variables[i].identifier, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { for (Map<StringName, GDScriptParser::ClassNode::Constant>::Element *E = p_context._class->constant_expressions.front(); E; E = E->next()) { - r_result.insert(E->key()); + ScriptCodeCompletionOption option(E->key(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } for (int i = 0; i < p_context._class->subclasses.size(); i++) { - r_result.insert(p_context._class->subclasses[i]->name); + ScriptCodeCompletionOption option(p_context._class->subclasses[i]->name, ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } } for (int i = 0; i < p_context._class->static_functions.size(); i++) { + ScriptCodeCompletionOption option(p_context._class->static_functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); if (p_context._class->static_functions[i]->arguments.size()) { - r_result.insert(p_context._class->static_functions[i]->name.operator String() + "("); + option.insert_text += "("; } else { - r_result.insert(p_context._class->static_functions[i]->name.operator String() + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } if (!p_static) { for (int i = 0; i < p_context._class->functions.size(); i++) { + ScriptCodeCompletionOption option(p_context._class->functions[i]->name.operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); if (p_context._class->functions[i]->arguments.size()) { - r_result.insert(p_context._class->functions[i]->name.operator String() + "("); + option.insert_text += "("; } else { - r_result.insert(p_context._class->functions[i]->name.operator String() + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } } } @@ -1898,12 +1910,14 @@ static void _find_identifiers_in_class(const GDScriptCompletionContext &p_contex _find_identifiers_in_base(c, base_type, p_only_functions, r_result); } -static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Set<String> &r_result) { +static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { GDScriptParser::DataType base_type = p_base.type; bool _static = base_type.is_meta_type; if (_static && base_type.kind != GDScriptParser::DataType::BUILTIN) { - r_result.insert("new("); + ScriptCodeCompletionOption option("new", ScriptCodeCompletionOption::KIND_FUNCTION); + option.insert_text += "("; + r_result.insert(option.display, option); } while (base_type.has_type) { @@ -1921,26 +1935,31 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context if (script.is_valid()) { if (!_static && !p_only_functions) { for (const Set<StringName>::Element *E = script->get_members().front(); E; E = E->next()) { - r_result.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { for (const Map<StringName, Variant>::Element *E = script->get_constants().front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } } for (const Map<StringName, GDScriptFunction *>::Element *E = script->get_member_functions().front(); E; E = E->next()) { if (!_static || E->get()->is_static()) { + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get()->get_argument_count()) { - r_result.insert(E->key().operator String() + "("); + option.insert_text += "("; } else { - r_result.insert(E->key().operator String() + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } } if (!p_only_functions) { for (const Map<StringName, Ref<GDScript> >::Element *E = script->get_subclasses().front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } } base_type = GDScriptParser::DataType(); @@ -1964,25 +1983,29 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context List<PropertyInfo> members; scr->get_script_property_list(&members); for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { - r_result.insert(E->get().name); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } if (!p_only_functions) { Map<StringName, Variant> constants; scr->get_constants(&constants); for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } } List<MethodInfo> methods; scr->get_script_method_list(&methods); for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get().arguments.size()) { - r_result.insert(E->get().name + "("); + option.insert_text += "("; } else { - r_result.insert(E->get().name + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } Ref<Script> base_script = scr->get_base_script(); @@ -2009,7 +2032,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context List<String> constants; ClassDB::get_integer_constant_list(type, &constants); for (List<String>::Element *E = constants.front(); E; E = E->next()) { - r_result.insert(E->get()); + ScriptCodeCompletionOption option(E->get(), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } if (!_static) { @@ -2022,7 +2046,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context if (E->get().name.find("/") != -1) { continue; } - r_result.insert(E->get().name); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } } @@ -2035,11 +2060,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context if (E->get().name.begins_with("_")) { continue; } + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get().arguments.size()) { - r_result.insert(E->get().name + "("); + option.insert_text += "("; } else { - r_result.insert(E->get().name + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } } @@ -2058,7 +2085,8 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context for (List<PropertyInfo>::Element *E = members.front(); E; E = E->next()) { if (String(E->get().name).find("/") == -1) { - r_result.insert(E->get().name); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_MEMBER); + r_result.insert(option.display, option); } } } @@ -2066,11 +2094,13 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context List<MethodInfo> methods; tmp.get_method_list(&methods); for (List<MethodInfo>::Element *E = methods.front(); E; E = E->next()) { + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_FUNCTION); if (E->get().arguments.size()) { - r_result.insert(E->get().name + "("); + option.insert_text += "("; } else { - r_result.insert(E->get().name + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } return; @@ -2082,7 +2112,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionContext &p_context } } -static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Set<String> &r_result) { +static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p_only_functions, Map<String, ScriptCodeCompletionOption> &r_result) { const GDScriptParser::BlockNode *block = p_context.block; @@ -2091,7 +2121,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p const GDScriptParser::FunctionNode *f = p_context.function; for (int i = 0; i < f->arguments.size(); i++) { - r_result.insert(f->arguments[i].operator String()); + ScriptCodeCompletionOption option(f->arguments[i].operator String(), ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_result.insert(option.display, option); } } @@ -2116,11 +2147,13 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p for (int i = 0; i < GDScriptFunctions::FUNC_MAX; i++) { MethodInfo mi = GDScriptFunctions::get_info(GDScriptFunctions::Function(i)); + ScriptCodeCompletionOption option(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))), ScriptCodeCompletionOption::KIND_FUNCTION); if (mi.arguments.size() || (mi.flags & METHOD_FLAG_VARARG)) { - r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "("); + option.insert_text += "("; } else { - r_result.insert(String(GDScriptFunctions::get_func_name(GDScriptFunctions::Function(i))) + "()"); + option.insert_text += "()"; } + r_result.insert(option.display, option); } static const char *_type_names[Variant::VARIANT_MAX] = { @@ -2130,7 +2163,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p }; for (int i = 0; i < Variant::VARIANT_MAX; i++) { - r_result.insert(_type_names[i]); + ScriptCodeCompletionOption option(_type_names[i], ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } static const char *_keywords[] = { @@ -2144,7 +2178,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p const char **kw = _keywords; while (*kw) { - r_result.insert(*kw); + ScriptCodeCompletionOption option(*kw, ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + r_result.insert(option.display, option); kw++; } @@ -2158,7 +2193,8 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p } String path = ProjectSettings::get_singleton()->get(s); if (path.begins_with("*")) { - r_result.insert(s.get_slice("/", 1)); + ScriptCodeCompletionOption option(s.get_slice("/", 1), ScriptCodeCompletionOption::KIND_CONSTANT); + r_result.insert(option.display, option); } } @@ -2166,16 +2202,18 @@ static void _find_identifiers(const GDScriptCompletionContext &p_context, bool p List<StringName> named_scripts; ScriptServer::get_global_class_list(&named_scripts); for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { - r_result.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } // Native classes for (const Map<StringName, int>::Element *E = GDScriptLanguage::get_singleton()->get_global_map().front(); E; E = E->next()) { - r_result.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + r_result.insert(option.display, option); } } -static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Set<String> &r_result, String &r_arghint) { +static void _find_call_arguments(const GDScriptCompletionContext &p_context, const GDScriptCompletionIdentifier &p_base, const StringName &p_method, int p_argidx, bool p_static, Map<String, ScriptCodeCompletionOption> &r_result, String &r_arghint) { Variant base = p_base.value; GDScriptParser::DataType base_type = p_base.type; @@ -2199,7 +2237,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con if ((p_method == "connect" || p_method == "emit_signal") && p_argidx == 0) { for (int i = 0; i < base_type.class_type->_signals.size(); i++) { - r_result.insert(quote_style + base_type.class_type->_signals[i].name.operator String() + quote_style); + ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2212,7 +2252,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con List<MethodInfo> signals; gds->get_script_signal_list(&signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - r_result.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } Ref<GDScript> base_script = gds->get_base_script(); @@ -2250,7 +2292,8 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con List<String> options; obj->get_argument_options(p_method, p_argidx, &options); for (List<String>::Element *F = options.front(); F; F = F->next()) { - r_result.insert(F->get()); + ScriptCodeCompletionOption option(F->get(), ScriptCodeCompletionOption::KIND_FUNCTION); + r_result.insert(option.display, option); } } } @@ -2271,7 +2314,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con List<MethodInfo> signals; ClassDB::get_signal_list(class_name, &signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - r_result.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(E->get().name, ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2286,7 +2331,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con continue; } String name = s.get_slice("/", 1); - r_result.insert(quote_style + "/root/" + name + quote_style); + ScriptCodeCompletionOption option("/root/" + name, ScriptCodeCompletionOption::KIND_NODE_PATH); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2300,7 +2347,9 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con continue; } String name = s.get_slice("/", 1); - r_result.insert(quote_style + name + quote_style); + ScriptCodeCompletionOption option(name, ScriptCodeCompletionOption::KIND_CONSTANT); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } @@ -2333,7 +2382,7 @@ static void _find_call_arguments(const GDScriptCompletionContext &p_context, con } } -static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Set<String> &r_result, bool &r_forced, String &r_arghint) { +static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDScriptParser::Node *p_node, int p_argidx, Map<String, ScriptCodeCompletionOption> &r_result, bool &r_forced, String &r_arghint) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; @@ -2451,17 +2500,19 @@ static void _find_call_arguments(GDScriptCompletionContext &p_context, const GDS _find_call_arguments(p_context, ci, function, p_argidx, _static, r_result, r_arghint); if (function == "connect" && p_argidx == 2) { - Set<String> methods; + Map<String, ScriptCodeCompletionOption> methods; _find_identifiers_in_base(p_context, connect_base, true, methods); - for (Set<String>::Element *E = methods.front(); E; E = E->next()) { - r_result.insert(quote_style + E->get().replace("(", "").replace(")", "") + quote_style); + for (Map<String, ScriptCodeCompletionOption>::Element *E = methods.front(); E; E = E->next()) { + ScriptCodeCompletionOption &option = E->value(); + option.insert_text = quote_style + option.display + quote_style; + r_result.insert(option.display, option); } } r_forced = r_result.size() > 0; } -Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) { +Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { const String quote_style = EDITOR_DEF("text_editor/completion/use_single_quotes", false) ? "'" : "\""; @@ -2469,7 +2520,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path parser.parse(p_code, p_path.get_base_dir(), false, p_path, true); r_forced = false; - Set<String> options; + Map<String, ScriptCodeCompletionOption> options; GDScriptCompletionContext context; context._class = parser.get_completion_class(); context.block = parser.get_completion_block(); @@ -2490,7 +2541,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<StringName> constants; Variant::get_constants_for_type(parser.get_completion_built_in_constant(), &constants); for (List<StringName>::Element *E = constants.front(); E; E = E->next()) { - options.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CONSTANT); + options.insert(option.display, option); } } break; case GDScriptParser::COMPLETION_PARENT_FUNCTION: { @@ -2515,9 +2567,11 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path r_forced = true; String idopt = opt.unquote(); if (idopt.replace("/", "_").is_valid_identifier()) { - options.insert(idopt); + ScriptCodeCompletionOption option(idopt, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } else { - options.insert(opt); + ScriptCodeCompletionOption option(opt, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } } } @@ -2532,7 +2586,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path continue; } String name = s.get_slice("/", 1); - options.insert(quote_style + "/root/" + name + quote_style); + ScriptCodeCompletionOption option(quote_style + "/root/" + name + quote_style, ScriptCodeCompletionOption::KIND_NODE_PATH); + options.insert(option.display, option); } } } break; @@ -2655,7 +2710,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path } method_hint += ":"; - options.insert(method_hint); + ScriptCodeCompletionOption option(method_hint, ScriptCodeCompletionOption::KIND_FUNCTION); + options.insert(option.display, option); } } break; case GDScriptParser::COMPLETION_YIELD: { @@ -2673,7 +2729,9 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path switch (base_type.kind) { case GDScriptParser::DataType::CLASS: { for (int i = 0; i < base_type.class_type->_signals.size(); i++) { - options.insert(quote_style + base_type.class_type->_signals[i].name.operator String() + quote_style); + ScriptCodeCompletionOption option(base_type.class_type->_signals[i].name.operator String(), ScriptCodeCompletionOption::KIND_SIGNAL); + option.insert_text = quote_style + option.display + quote_style; + options.insert(option.display, option); } base_type = base_type.class_type->base_type; } break; @@ -2684,7 +2742,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<MethodInfo> signals; scr->get_script_signal_list(&signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - options.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL); + options.insert(option.display, option); } Ref<Script> base_script = scr->get_base_script(); if (base_script.is_valid()) { @@ -2711,7 +2770,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path List<MethodInfo> signals; ClassDB::get_signal_list(class_name, &signals); for (List<MethodInfo>::Element *E = signals.front(); E; E = E->next()) { - options.insert(quote_style + E->get().name + quote_style); + ScriptCodeCompletionOption option(quote_style + E->get().name + quote_style, ScriptCodeCompletionOption::KIND_SIGNAL); + options.insert(option.display, option); } } break; default: { @@ -2748,18 +2808,21 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path c.line = E->value().expression->line; if (_guess_expression_type(c, E->value().expression, constant)) { if (constant.type.has_type && constant.type.is_meta_type) { - options.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } } for (int i = 0; i < clss->subclasses.size(); i++) { if (clss->subclasses[i]->name != StringName()) { - options.insert(clss->subclasses[i]->name.operator String()); + ScriptCodeCompletionOption option(clss->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } clss = clss->owner; for (int i = 0; i < Variant::VARIANT_MAX; i++) { - options.insert(Variant::get_type_name((Variant::Type)i)); + ScriptCodeCompletionOption option(Variant::get_type_name((Variant::Type)i), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } @@ -2773,18 +2836,21 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path if (Engine::get_singleton()->has_singleton(class_name)) { continue; } - options.insert(class_name); + ScriptCodeCompletionOption option(class_name, ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } // Named scripts List<StringName> named_scripts; ScriptServer::get_global_class_list(&named_scripts); for (List<StringName>::Element *E = named_scripts.front(); E; E = E->next()) { - options.insert(E->get().operator String()); + ScriptCodeCompletionOption option(E->get().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } if (parser.get_completion_identifier_is_function()) { - options.insert("void"); + ScriptCodeCompletionOption option("void", ScriptCodeCompletionOption::KIND_PLAIN_TEXT); + options.insert(option.display, option); } r_forced = true; } break; @@ -2831,13 +2897,15 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path c2.line = E->value().expression->line; if (_guess_expression_type(c2, E->value().expression, constant)) { if (constant.type.has_type && constant.type.is_meta_type) { - options.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } } for (int i = 0; i < base_type.class_type->subclasses.size(); i++) { if (base_type.class_type->subclasses[i]->name != StringName()) { - options.insert(base_type.class_type->subclasses[i]->name.operator String()); + ScriptCodeCompletionOption option(base_type.class_type->subclasses[i]->name.operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } @@ -2855,7 +2923,8 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path for (Map<StringName, Variant>::Element *E = constants.front(); E; E = E->next()) { Ref<Script> const_scr = E->value(); if (const_scr.is_valid()) { - options.insert(E->key().operator String()); + ScriptCodeCompletionOption option(E->key().operator String(), ScriptCodeCompletionOption::KIND_CLASS); + options.insert(option.display, option); } } Ref<Script> base_script = scr->get_base_script(); @@ -2877,7 +2946,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path } break; } - for (Set<String>::Element *E = options.front(); E; E = E->next()) { + for (Map<String, ScriptCodeCompletionOption>::Element *E = options.front(); E; E = E->next()) { r_options->push_back(E->get()); } @@ -2886,7 +2955,7 @@ Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path #else -Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<String> *r_options, bool &r_forced, String &r_call_hint) { +Error GDScriptLanguage::complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_forced, String &r_call_hint) { return OK; } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 9ab86a5459..c5c86fda0a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -125,7 +125,7 @@ bool GDScriptParser::_enter_indent_block(BlockNode *p_block) { } } -bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete) { +bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete, bool p_parsing_constant) { if (tokenizer->get_token() == GDScriptTokenizer::TK_PARENTHESIS_CLOSE) { tokenizer->advance(); @@ -149,7 +149,7 @@ bool GDScriptParser::_parse_arguments(Node *p_parent, Vector<Node *> &p_args, bo return false; } - Node *arg = _parse_expression(p_parent, p_static); + Node *arg = _parse_expression(p_parent, p_static, false, p_parsing_constant); if (!arg) { return false; } @@ -639,7 +639,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s id->name = identifier; op->arguments.push_back(id); - if (!_parse_arguments(op, op->arguments, p_static, true)) + if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; @@ -731,7 +731,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s completion_node = op; } if (!replaced) { - if (!_parse_arguments(op, op->arguments, p_static, true)) + if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; } @@ -826,11 +826,12 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } // Check parents for the constant - if (!bfn && cln->extends_file != StringName()) { - Ref<GDScript> parent = ResourceLoader::load(cln->extends_file); - if (parent.is_valid() && parent->is_valid()) { + if (!bfn) { + // Using current_class instead of cln here, since cln is const* + _determine_inheritance(current_class, false); + if (cln->base_type.has_type && cln->base_type.kind == DataType::GDSCRIPT && cln->base_type.script_type->is_valid()) { Map<StringName, Variant> parent_constants; - parent->get_constants(&parent_constants); + current_class->base_type.script_type->get_constants(&parent_constants); if (parent_constants.has(identifier)) { ConstantNode *constant = alloc_node<ConstantNode>(); constant->value = parent_constants[identifier]; @@ -1112,7 +1113,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s } } else { tokenizer->advance(); - if (!_parse_arguments(op, op->arguments, p_static)) { + if (!_parse_arguments(op, op->arguments, p_static, false, p_parsing_constant)) { return NULL; } } @@ -1164,22 +1165,14 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s tokenizer->advance(); IdentifierNode *id = alloc_node<IdentifierNode>(); - if (tokenizer->get_token() == GDScriptTokenizer::TK_BUILT_IN_FUNC) { - //small hack so built in funcs don't obfuscate methods - - id->name = GDScriptFunctions::get_func_name(tokenizer->get_token_built_in_func()); - tokenizer->advance(); - - } else { - StringName identifier; - if (_get_completable_identifier(COMPLETION_METHOD, identifier)) { - completion_node = op; - //indexing stuff - } - - id->name = identifier; + StringName identifier; + if (_get_completable_identifier(COMPLETION_METHOD, identifier)) { + completion_node = op; + //indexing stuff } + id->name = identifier; + op->arguments.push_back(expr); // call what op->arguments.push_back(id); // call func //get arguments @@ -1188,7 +1181,7 @@ GDScriptParser::Node *GDScriptParser::_parse_expression(Node *p_parent, bool p_s _make_completable_call(0); completion_node = op; } - if (!_parse_arguments(op, op->arguments, p_static, true)) + if (!_parse_arguments(op, op->arguments, p_static, true, p_parsing_constant)) return NULL; expr = op; @@ -5158,9 +5151,11 @@ void GDScriptParser::_parse_class(ClassNode *p_class) { } } -void GDScriptParser::_determine_inheritance(ClassNode *p_class) { +void GDScriptParser::_determine_inheritance(ClassNode *p_class, bool p_recursive) { - if (p_class->extends_used) { + if (p_class->base_type.has_type) { + // Already determined + } else if (p_class->extends_used) { //do inheritance String path = p_class->extends_file; @@ -5355,9 +5350,11 @@ void GDScriptParser::_determine_inheritance(ClassNode *p_class) { p_class->base_type.native_type = "Reference"; } - // Recursively determine subclasses - for (int i = 0; i < p_class->subclasses.size(); i++) { - _determine_inheritance(p_class->subclasses[i]); + if (p_recursive) { + // Recursively determine subclasses + for (int i = 0; i < p_class->subclasses.size(); i++) { + _determine_inheritance(p_class->subclasses[i], p_recursive); + } } } diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h index 5e4de11357..62d7bdb393 100644 --- a/modules/gdscript/gdscript_parser.h +++ b/modules/gdscript/gdscript_parser.h @@ -582,7 +582,7 @@ private: #endif // DEBUG_ENABLED bool _recover_from_completion(); - bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false); + bool _parse_arguments(Node *p_parent, Vector<Node *> &p_args, bool p_static, bool p_can_codecomplete = false, bool p_parsing_constant = false); bool _enter_indent_block(BlockNode *p_block = NULL); bool _parse_newline(); Node *_parse_expression(Node *p_parent, bool p_static, bool p_allow_assign = false, bool p_parsing_constant = false); @@ -599,7 +599,7 @@ private: void _parse_class(ClassNode *p_class); bool _end_statement(); - void _determine_inheritance(ClassNode *p_class); + void _determine_inheritance(ClassNode *p_class, bool p_recursive = true); bool _parse_type(DataType &r_type, bool p_can_be_void = false); DataType _resolve_type(const DataType &p_source, int p_line); DataType _type_from_variant(const Variant &p_value) const; diff --git a/modules/mono/build_scripts/make_android_mono_config.py b/modules/mono/build_scripts/make_android_mono_config.py new file mode 100644 index 0000000000..cd9210897d --- /dev/null +++ b/modules/mono/build_scripts/make_android_mono_config.py @@ -0,0 +1,69 @@ + +def generate_compressed_config(config_src, output_dir): + import os.path + from compat import byte_to_str + + # Header file + with open(os.path.join(output_dir, 'android_mono_config.gen.h'), 'w') as header: + header.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ +#ifndef ANDROID_MONO_CONFIG_GEN_H +#define ANDROID_MONO_CONFIG_GEN_H + +#ifdef ANDROID_ENABLED + +#include "core/ustring.h" + +String get_godot_android_mono_config(); + +#endif // ANDROID_ENABLED + +#endif // ANDROID_MONO_CONFIG_GEN_H +''') + + # Source file + with open(os.path.join(output_dir, 'android_mono_config.gen.cpp'), 'w') as cpp: + with open(config_src, 'rb') as f: + buf = f.read() + decompr_size = len(buf) + import zlib + buf = zlib.compress(buf) + compr_size = len(buf) + + bytes_seq_str = '' + for i, buf_idx in enumerate(range(compr_size)): + if i > 0: + bytes_seq_str += ', ' + bytes_seq_str += byte_to_str(buf[buf_idx]) + + cpp.write('''/* THIS FILE IS GENERATED DO NOT EDIT */ +#include "android_mono_config.gen.h" + +#ifdef ANDROID_ENABLED + +#include "core/io/compression.h" +#include "core/pool_vector.h" + +namespace { + +// config +static const int config_compressed_size = %d; +static const int config_uncompressed_size = %d; +static const unsigned char config_compressed_data[] = { %s }; + +} // namespace + +String get_godot_android_mono_config() { + PoolVector<uint8_t> data; + data.resize(config_uncompressed_size); + PoolVector<uint8_t>::Write w = data.write(); + Compression::decompress(w.ptr(), config_uncompressed_size, config_compressed_data, + config_compressed_size, Compression::MODE_DEFLATE); + String s; + if (s.parse_utf8((const char *)w.ptr(), data.size())) { + ERR_FAIL_V(String()); + } + return s; +} + +#endif // ANDROID_ENABLED +''' % (compr_size, decompr_size, bytes_seq_str)) diff --git a/modules/mono/build_scripts/make_cs_compressed_header.py b/modules/mono/build_scripts/make_cs_compressed_header.py index 1f9177cef8..ed49db5bb2 100644 --- a/modules/mono/build_scripts/make_cs_compressed_header.py +++ b/modules/mono/build_scripts/make_cs_compressed_header.py @@ -23,30 +23,31 @@ def generate_header(src, dst, version_dst): latest_mtime = mtime if mtime > latest_mtime else latest_mtime with open(filepath, 'rb') as f: buf = f.read() - decomp_size = len(buf) + decompr_size = len(buf) import zlib buf = zlib.compress(buf) + compr_size = len(buf) name = str(cs_file_count) header.write('\n') header.write('// ' + filepath_src_rel + '\n') - header.write('static const int _cs_' + name + '_compressed_size = ' + str(len(buf)) + ';\n') - header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decomp_size) + ';\n') + header.write('static const int _cs_' + name + '_compressed_size = ' + str(compr_size) + ';\n') + header.write('static const int _cs_' + name + '_uncompressed_size = ' + str(decompr_size) + ';\n') header.write('static const unsigned char _cs_' + name + '_compressed[] = { ') - for i, buf_idx in enumerate(range(len(buf))): + for i, buf_idx in enumerate(range(compr_size)): if i > 0: header.write(', ') header.write(byte_to_str(buf[buf_idx])) + header.write(' };\n') inserted_files += '\tr_files.insert("' + filepath_src_rel.replace('\\', '\\\\') + '", ' \ - 'CompressedFile(_cs_' + name + '_compressed_size, ' \ + 'GodotCsCompressedFile(_cs_' + name + '_compressed_size, ' \ '_cs_' + name + '_uncompressed_size, ' \ '_cs_' + name + '_compressed));\n' - header.write(' };\n') - header.write('\nstruct CompressedFile\n' '{\n' + header.write('\nstruct GodotCsCompressedFile\n' '{\n' '\tint compressed_size;\n' '\tint uncompressed_size;\n' '\tconst unsigned char* data;\n' - '\n\tCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' + '\n\tGodotCsCompressedFile(int p_comp_size, int p_uncomp_size, const unsigned char* p_data)\n' '\t{\n' '\t\tcompressed_size = p_comp_size;\n' '\t\tuncompressed_size = p_uncomp_size;\n' - '\t\tdata = p_data;\n' '\t}\n' '\n\tCompressedFile() {}\n' '};\n' - '\nvoid get_compressed_files(Map<String, CompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n' + '\t\tdata = p_data;\n' '\t}\n' '\n\tGodotCsCompressedFile() {}\n' '};\n' + '\nvoid get_compressed_files(Map<String, GodotCsCompressedFile>& r_files)\n' '{\n' + inserted_files + '}\n' ) header.write('\n#endif // TOOLS_ENABLED\n') header.write('\n#endif // CS_COMPRESSED_H\n') diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py index c549640d61..3b15f722a3 100644 --- a/modules/mono/build_scripts/mono_configure.py +++ b/modules/mono/build_scripts/mono_configure.py @@ -41,7 +41,7 @@ def copy_file(src_dir, dst_dir, name): dst_dir = Dir(dst_dir).abspath if not os.path.isdir(dst_dir): - os.mkdir(dst_dir) + os.makedirs(dst_dir) copy(src_path, dst_dir) @@ -65,6 +65,10 @@ def configure(env, env_mono): # TODO: Implement this. We have to add the data directory to the apk, concretely the Api and Tools folders. raise RuntimeError('This module does not currently support building for android with tools enabled') + if is_android and mono_static: + # When static linking and doing something that requires libmono-native, we get a dlopen error as libmono-native seems to depend on libmonosgen-2.0 + raise RuntimeError('Linking Mono statically is not currently supported on Android') + if (os.getenv('MONO32_PREFIX') or os.getenv('MONO64_PREFIX')) and not mono_prefix: print("WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead") @@ -173,7 +177,7 @@ def configure(env, env_mono): if not mono_lib: raise RuntimeError('Could not find mono library in: ' + mono_lib_path) - env_mono.Append(CPPFLAGS=['-D_REENTRANT']) + env_mono.Append(CPPDEFINES=['_REENTRANT']) if mono_static: mono_lib_file = os.path.join(mono_lib_path, 'lib' + mono_lib + '.a') @@ -188,7 +192,7 @@ def configure(env, env_mono): if is_apple: env.Append(LIBS=['iconv', 'pthread']) elif is_android: - env.Append(LIBS=['m', 'dl']) + pass # Nothing else: env.Append(LIBS=['m', 'rt', 'dl', 'pthread']) @@ -236,6 +240,14 @@ def configure(env, env_mono): mono_root = subprocess.check_output(['pkg-config', 'mono-2', '--variable=prefix']).decode('utf8').strip() make_template_dir(env, mono_root) + elif not tools_enabled and is_android: + # Compress Android Mono Config + from . import make_android_mono_config + config_file_path = os.path.join(mono_root, 'etc', 'mono', 'config') + make_android_mono_config.generate_compressed_config(config_file_path, 'mono_gd/') + + # Copy the required shared libraries + copy_mono_shared_libs(env, mono_root, None) if copy_mono_root: if not mono_root: @@ -270,9 +282,8 @@ def make_template_dir(env, mono_root): # Copy etc/mono/ - if platform != 'android': - template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono') - copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform']) + template_mono_config_dir = os.path.join(template_mono_root_dir, 'etc', 'mono') + copy_mono_etc_dir(mono_root, template_mono_config_dir, env['platform']) # Copy the required shared libraries @@ -398,7 +409,7 @@ def configure_for_mono_version(env, mono_version): raise RuntimeError("Mono JIT compiler version not found; specify one manually with the 'MONO_VERSION' environment variable") print('Found Mono JIT compiler version: ' + str(mono_version)) if mono_version >= LooseVersion('5.12.0'): - env.Append(CPPFLAGS=['-DHAS_PENDING_EXCEPTIONS']) + env.Append(CPPDEFINES=['HAS_PENDING_EXCEPTIONS']) def pkgconfig_try_find_mono_root(mono_lib_names, sharedlib_ext): diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 9522eaee77..cdec10eaba 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -682,19 +682,20 @@ bool CSharpLanguage::is_assembly_reloading_needed() { GDMonoAssembly *proj_assembly = gdmono->get_project_assembly(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } - name += ".dll"; + appname_safe += ".dll"; if (proj_assembly) { String proj_asm_path = proj_assembly->get_path(); if (!FileAccess::exists(proj_assembly->get_path())) { // Maybe it wasn't loaded from the default path, so check this as well - proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name); + proj_asm_path = GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe); if (!FileAccess::exists(proj_asm_path)) return false; // No assembly to load } @@ -702,7 +703,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { if (FileAccess::get_modified_time(proj_asm_path) <= proj_assembly->get_modified_time()) return false; // Already up to date } else { - if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(name))) + if (!FileAccess::exists(GodotSharpDirs::get_res_temp_assemblies_dir().plus_file(appname_safe))) return false; // No assembly to load } diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index 2d618f7891..5e0ce3554a 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -914,12 +914,12 @@ Error BindingsGenerator::generate_cs_core_project(const String &p_solution_dir, // Generate sources from compressed files - Map<String, CompressedFile> compressed_files; + Map<String, GodotCsCompressedFile> compressed_files; get_compressed_files(compressed_files); - for (Map<String, CompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) { + for (Map<String, GodotCsCompressedFile>::Element *E = compressed_files.front(); E; E = E->next()) { const String &file_name = E->key(); - const CompressedFile &file_data = E->value(); + const GodotCsCompressedFile &file_data = E->value(); String output_file = path_join(core_dir, file_name); diff --git a/modules/mono/editor/godotsharp_editor.cpp b/modules/mono/editor/godotsharp_editor.cpp index 9d42528927..8dc5ced29f 100644 --- a/modules/mono/editor/godotsharp_editor.cpp +++ b/modules/mono/editor/godotsharp_editor.cpp @@ -63,16 +63,17 @@ bool GodotSharpEditor::_create_project_solution() { pr.step(TTR("Generating C# project...")); String path = OS::get_singleton()->get_resource_dir(); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } - String guid = CSharpProject::generate_game_project(path, name); + String guid = CSharpProject::generate_game_project(path, appname_safe); if (guid.length()) { - DotNetSolution solution(name); + DotNetSolution solution(appname_safe); if (!solution.set_path(path)) { show_error_dialog(TTR("Failed to create solution.")); @@ -81,12 +82,12 @@ bool GodotSharpEditor::_create_project_solution() { DotNetSolution::ProjectInfo proj_info; proj_info.guid = guid; - proj_info.relpath = name + ".csproj"; + proj_info.relpath = appname_safe + ".csproj"; proj_info.configs.push_back("Debug"); proj_info.configs.push_back("Release"); proj_info.configs.push_back("Tools"); - solution.add_new_project(name, proj_info); + solution.add_new_project(appname_safe, proj_info); Error sln_error = solution.save(); diff --git a/modules/mono/editor/godotsharp_export.cpp b/modules/mono/editor/godotsharp_export.cpp index 126178125f..590ec007ad 100644 --- a/modules/mono/editor/godotsharp_export.cpp +++ b/modules/mono/editor/godotsharp_export.cpp @@ -106,13 +106,14 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug Map<String, String> dependencies; String project_dll_name = ProjectSettings::get_singleton()->get("application/config/name"); - if (project_dll_name.empty()) { - project_dll_name = "UnnamedProject"; + String project_dll_name_safe = OS::get_singleton()->get_safe_dir_name(project_dll_name); + if (project_dll_name_safe.empty()) { + project_dll_name_safe = "UnnamedProject"; } String project_dll_src_dir = GodotSharpDirs::get_res_temp_assemblies_base_dir().plus_file(build_config); - String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name + ".dll"); - dependencies.insert(project_dll_name, project_dll_src_path); + String project_dll_src_path = project_dll_src_dir.plus_file(project_dll_name_safe + ".dll"); + dependencies.insert(project_dll_name_safe, project_dll_src_path); { MonoDomain *export_domain = GDMonoUtils::create_domain("GodotEngine.ProjectExportDomain"); @@ -122,10 +123,10 @@ void GodotSharpExport::_export_begin(const Set<String> &p_features, bool p_debug _GDMONO_SCOPE_DOMAIN_(export_domain); GDMonoAssembly *scripts_assembly = NULL; - bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name, + bool load_success = GDMono::get_singleton()->load_assembly_from(project_dll_name_safe, project_dll_src_path, &scripts_assembly, /* refonly: */ true); - ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name); + ERR_EXPLAIN("Cannot load assembly (refonly): " + project_dll_name_safe); ERR_FAIL_COND(!load_success); Vector<String> search_dirs; diff --git a/modules/mono/glue/collections_glue.cpp b/modules/mono/glue/collections_glue.cpp index 47239f1260..e67c8b9ad9 100644 --- a/modules/mono/glue/collections_glue.cpp +++ b/modules/mono/glue/collections_glue.cpp @@ -46,7 +46,7 @@ void godot_icall_Array_Dtor(Array *ptr) { } MonoObject *godot_icall_Array_At(Array *ptr, int index) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return NULL; } @@ -54,7 +54,7 @@ MonoObject *godot_icall_Array_At(Array *ptr, int index) { } MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_encoding, GDMonoClass *type_class) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return NULL; } @@ -62,7 +62,7 @@ MonoObject *godot_icall_Array_At_Generic(Array *ptr, int index, uint32_t type_en } void godot_icall_Array_SetAt(Array *ptr, int index, MonoObject *value) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; } @@ -124,7 +124,7 @@ MonoBoolean godot_icall_Array_Remove(Array *ptr, MonoObject *item) { } void godot_icall_Array_RemoveAt(Array *ptr, int index) { - if (index < 0 || index > ptr->size()) { + if (index < 0 || index >= ptr->size()) { GDMonoUtils::set_pending_exception(mono_get_exception_index_out_of_range()); return; } diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 09a1fc6fbc..6445742899 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -39,6 +39,10 @@ #include "editor/editor_settings.h" #endif +#ifdef __ANDROID__ +#include "utils/android_utils.h" +#endif + namespace GodotSharpDirs { String _get_expected_build_config() { @@ -129,15 +133,16 @@ private: mono_solutions_dir = mono_user_dir.plus_file("solutions"); build_logs_dir = mono_user_dir.plus_file("build_logs"); - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } String base_path = ProjectSettings::get_singleton()->globalize_path("res://"); - sln_filepath = base_path.plus_file(name + ".sln"); - csproj_filepath = base_path.plus_file(name + ".csproj"); + sln_filepath = base_path.plus_file(appname_safe + ".sln"); + csproj_filepath = base_path.plus_file(appname_safe + ".csproj"); #endif String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); @@ -150,7 +155,12 @@ private: String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); + +#if __ANDROID__ + data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir(); +#else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); +#endif #ifdef WINDOWS_ENABLED data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); @@ -173,15 +183,21 @@ private: #else - String appname = OS::get_singleton()->get_safe_dir_name(ProjectSettings::get_singleton()->get("application/config/name")); - String data_dir_root = exe_dir.plus_file("data_" + appname); + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + String data_dir_root = exe_dir.plus_file("data_" + appname_safe); if (!DirAccess::exists(data_dir_root)) { data_dir_root = exe_dir.plus_file("data_Godot"); } String data_mono_root_dir = data_dir_root.plus_file("Mono"); data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); + +#if __ANDROID__ + data_mono_lib_dir = GDMonoUtils::Android::get_app_native_lib_dir(); +#else data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); +#endif #ifdef WINDOWS_ENABLED data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index 7699e0d0cd..c90da31f3a 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -56,6 +56,10 @@ #include "main/main.h" #endif +#ifdef ANDROID_ENABLED +#include "android_mono_config.gen.h" +#endif + #define OUT_OF_SYNC_ERR_MESSAGE(m_assembly_name) "The assembly '" m_assembly_name "' is out of sync. " \ "This error is expected if you just upgraded to a newer Godot version. " \ "Building the project will update the assembly to the correct version." @@ -287,7 +291,11 @@ void GDMono::initialize() { gdmono_debug_init(); #endif +#ifdef ANDROID_ENABLED + mono_config_parse_memory(get_godot_android_mono_config().utf8().get_data()); +#else mono_config_parse(NULL); +#endif mono_install_unhandled_exception_hook(&unhandled_exception_hook, NULL); @@ -651,12 +659,13 @@ bool GDMono::_load_project_assembly() { if (project_assembly) return true; - String name = ProjectSettings::get_singleton()->get("application/config/name"); - if (name.empty()) { - name = "UnnamedProject"; + String appname = ProjectSettings::get_singleton()->get("application/config/name"); + String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); + if (appname_safe.empty()) { + appname_safe = "UnnamedProject"; } - bool success = load_assembly(name, &project_assembly); + bool success = load_assembly(appname_safe, &project_assembly); if (success) { mono_assembly_set_main(project_assembly->get_assembly()); diff --git a/modules/mono/mono_gd/gd_mono_assembly.cpp b/modules/mono/mono_gd/gd_mono_assembly.cpp index f1f0015ac9..1b765a09ff 100644 --- a/modules/mono/mono_gd/gd_mono_assembly.cpp +++ b/modules/mono/mono_gd/gd_mono_assembly.cpp @@ -270,7 +270,18 @@ Error GDMonoAssembly::load(bool p_refonly) { Vector<uint8_t> data = FileAccess::get_file_as_array(path); ERR_FAIL_COND_V(data.empty(), ERR_FILE_CANT_READ); - String image_filename = ProjectSettings::get_singleton()->globalize_path(path); + String image_filename; + +#ifdef ANDROID_ENABLED + if (path.begins_with("res://")) { + image_filename = path.substr(6, path.length()); + } else { + image_filename = ProjectSettings::get_singleton()->globalize_path(path); + } +#else + // FIXME: globalize_path does not work on exported games + image_filename = ProjectSettings::get_singleton()->globalize_path(path); +#endif MonoImageOpenStatus status = MONO_IMAGE_OK; diff --git a/modules/mono/utils/android_utils.cpp b/modules/mono/utils/android_utils.cpp new file mode 100644 index 0000000000..7dd67e3b8e --- /dev/null +++ b/modules/mono/utils/android_utils.cpp @@ -0,0 +1,68 @@ +/*************************************************************************/ +/* android_utils.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "android_utils.h" + +#ifdef __ANDROID__ + +#include "platform/android/thread_jandroid.h" + +namespace GDMonoUtils { +namespace Android { + +String get_app_native_lib_dir() { + JNIEnv *env = ThreadAndroid::get_env(); + + jclass activityThreadClass = env->FindClass("android/app/ActivityThread"); + jmethodID currentActivityThread = env->GetStaticMethodID(activityThreadClass, "currentActivityThread", "()Landroid/app/ActivityThread;"); + jobject activityThread = env->CallStaticObjectMethod(activityThreadClass, currentActivityThread); + jmethodID getApplication = env->GetMethodID(activityThreadClass, "getApplication", "()Landroid/app/Application;"); + jobject ctx = env->CallObjectMethod(activityThread, getApplication); + + jmethodID getApplicationInfo = env->GetMethodID(env->GetObjectClass(ctx), "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;"); + jobject applicationInfo = env->CallObjectMethod(ctx, getApplicationInfo); + jfieldID nativeLibraryDirField = env->GetFieldID(env->GetObjectClass(applicationInfo), "nativeLibraryDir", "Ljava/lang/String;"); + jstring nativeLibraryDir = (jstring)env->GetObjectField(applicationInfo, nativeLibraryDirField); + + String result; + + const char *const nativeLibraryDir_utf8 = env->GetStringUTFChars(nativeLibraryDir, NULL); + if (nativeLibraryDir_utf8) { + result.parse_utf8(nativeLibraryDir_utf8); + env->ReleaseStringUTFChars(nativeLibraryDir, nativeLibraryDir_utf8); + } + + return result; +} + +} // namespace Android +} // namespace GDMonoUtils + +#endif // __ANDROID__ diff --git a/modules/mono/utils/android_utils.h b/modules/mono/utils/android_utils.h new file mode 100644 index 0000000000..f911c3fdfe --- /dev/null +++ b/modules/mono/utils/android_utils.h @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* android_utils.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef ANDROID_UTILS_H +#define ANDROID_UTILS_H + +#ifdef __ANDROID__ + +#include "core/ustring.h" + +namespace GDMonoUtils { +namespace Android { + +String get_app_native_lib_dir(); + +} // namespace Android +} // namespace GDMonoUtils + +#endif // __ANDROID__ + +#endif // ANDROID_UTILS_H diff --git a/modules/opus/SCsub b/modules/opus/SCsub index a4a431bab7..1db5b0987e 100644 --- a/modules/opus/SCsub +++ b/modules/opus/SCsub @@ -139,7 +139,7 @@ if env['builtin_opus']: opus_sources_silk = [] if env["platform"] in ["android", "iphone", "javascript"]: - env_opus.Append(CPPFLAGS=["-DFIXED_POINT"]) + env_opus.Append(CPPDEFINES=["FIXED_POINT"]) opus_sources_silk = [ "silk/fixed/LTP_analysis_filter_FIX.c", "silk/fixed/LTP_scale_ctrl_FIX.c", @@ -208,7 +208,7 @@ if env['builtin_opus']: if env['builtin_libogg']: env_opus.Prepend(CPPPATH=["#thirdparty/libogg"]) - env_opus.Append(CPPFLAGS=["-DHAVE_CONFIG_H"]) + env_opus.Append(CPPDEFINES=["HAVE_CONFIG_H"]) thirdparty_include_paths = [ "", @@ -222,14 +222,14 @@ if env['builtin_opus']: if env["platform"] == "android": if ("android_arch" in env and env["android_arch"] == "armv7"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM_OPT"]) elif ("android_arch" in env and env["android_arch"] == "arm64v8"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM64_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM64_OPT"]) elif env["platform"] == "iphone": if ("arch" in env and env["arch"] == "arm"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM_OPT"]) elif ("arch" in env and env["arch"] == "arm64"): - env_opus.Append(CPPFLAGS=["-DOPUS_ARM64_OPT"]) + env_opus.Append(CPPDEFINES=["OPUS_ARM64_OPT"]) env_thirdparty = env_opus.Clone() env_thirdparty.disable_warnings() diff --git a/modules/regex/SCsub b/modules/regex/SCsub index 65f354ffa7..acbe4a5a01 100644 --- a/modules/regex/SCsub +++ b/modules/regex/SCsub @@ -9,10 +9,10 @@ if env['builtin_pcre2']: jit_blacklist = ['javascript', 'uwp'] thirdparty_dir = '#thirdparty/pcre2/src/' - thirdparty_flags = ['-DPCRE2_STATIC', '-DHAVE_CONFIG_H'] + thirdparty_flags = ['PCRE2_STATIC', 'HAVE_CONFIG_H'] if 'platform' in env and env['platform'] not in jit_blacklist: - thirdparty_flags.append('-DSUPPORT_JIT') + thirdparty_flags.append('SUPPORT_JIT') thirdparty_sources = [ "pcre2_auto_possess.c", @@ -47,17 +47,17 @@ if env['builtin_pcre2']: thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_regex.Prepend(CPPPATH=[thirdparty_dir]) - env_regex.Append(CPPFLAGS=thirdparty_flags) + env_regex.Append(CPPDEFINES=thirdparty_flags) def pcre2_builtin(width): env_pcre2 = env_regex.Clone() env_pcre2.disable_warnings() env_pcre2["OBJSUFFIX"] = "_" + width + env_pcre2["OBJSUFFIX"] env_pcre2.add_source_files(env.modules_sources, thirdparty_sources) - env_pcre2.Append(CPPFLAGS=["-DPCRE2_CODE_UNIT_WIDTH=" + width]) + env_pcre2.Append(CPPDEFINES=[("PCRE2_CODE_UNIT_WIDTH", width)]) pcre2_builtin("16") pcre2_builtin("32") -env_regex.Append(CPPFLAGS=["-DPCRE2_CODE_UNIT_WIDTH=0"]) +env_regex.Append(CPPDEFINES=[("PCRE2_CODE_UNIT_WIDTH", 0)]) env_regex.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/svg/SCsub b/modules/svg/SCsub index 90bfe22abb..9324c1634b 100644 --- a/modules/svg/SCsub +++ b/modules/svg/SCsub @@ -16,7 +16,7 @@ env_svg.Prepend(CPPPATH=[thirdparty_dir]) # FIXME: Needed in editor/editor_themes.cpp for now, but ideally there # shouldn't be a dependency on modules/ and its own 3rd party deps. env.Prepend(CPPPATH=[thirdparty_dir]) -env.Append(CPPFLAGS=["-DSVG_ENABLED"]) +env.Append(CPPDEFINES=["SVG_ENABLED"]) env_thirdparty = env_svg.Clone() env_thirdparty.disable_warnings() diff --git a/modules/theora/SCsub b/modules/theora/SCsub index 785eca4c41..ff65d2f8ec 100644 --- a/modules/theora/SCsub +++ b/modules/theora/SCsub @@ -66,7 +66,7 @@ if env['builtin_libtheora']: thirdparty_sources += thirdparty_sources_x86_vc if (env["x86_libtheora_opt_gcc"] or env["x86_libtheora_opt_vc"]): - env_theora.Append(CPPFLAGS=["-DOC_X86_ASM"]) + env_theora.Append(CPPDEFINES=["OC_X86_ASM"]) thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] diff --git a/modules/upnp/SCsub b/modules/upnp/SCsub index ec1d7f44d5..3f56a69594 100644 --- a/modules/upnp/SCsub +++ b/modules/upnp/SCsub @@ -26,8 +26,8 @@ if env['builtin_miniupnpc']: thirdparty_sources = [thirdparty_dir + "miniupnpc/" + file for file in thirdparty_sources] env_upnp.Prepend(CPPPATH=[thirdparty_dir]) - env_upnp.Append(CPPFLAGS=["-DMINIUPNP_STATICLIB"]) - env_upnp.Append(CPPFLAGS=["-DMINIUPNPC_SET_SOCKET_TIMEOUT"]) + env_upnp.Append(CPPDEFINES=["MINIUPNP_STATICLIB"]) + env_upnp.Append(CPPDEFINES=["MINIUPNPC_SET_SOCKET_TIMEOUT"]) env_thirdparty = env_upnp.Clone() env_thirdparty.disable_warnings() diff --git a/modules/vhacd/SCsub b/modules/vhacd/SCsub index 161aed2eb9..e581fb7bb2 100644 --- a/modules/vhacd/SCsub +++ b/modules/vhacd/SCsub @@ -10,22 +10,21 @@ env_vhacd = env_modules.Clone() thirdparty_dir = "#thirdparty/vhacd/" thirdparty_sources = [ -"src/vhacdManifoldMesh.cpp", -"src/FloatMath.cpp", -"src/vhacdMesh.cpp", -"src/vhacdICHull.cpp", -"src/vhacdVolume.cpp", -"src/VHACD-ASYNC.cpp", -"src/btAlignedAllocator.cpp", -"src/vhacdRaycastMesh.cpp", -"src/VHACD.cpp", -"src/btConvexHullComputer.cpp" + "src/vhacdManifoldMesh.cpp", + "src/FloatMath.cpp", + "src/vhacdMesh.cpp", + "src/vhacdICHull.cpp", + "src/vhacdVolume.cpp", + "src/VHACD-ASYNC.cpp", + "src/btAlignedAllocator.cpp", + "src/vhacdRaycastMesh.cpp", + "src/VHACD.cpp", + "src/btConvexHullComputer.cpp" ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_vhacd.Prepend(CPPPATH=[thirdparty_dir + "/inc"]) -env_vhacd.Append(CPPFLAGS=["-DGODOT_ENET"]) # upstream uses c++11 if not env.msvc: diff --git a/modules/webm/libvpx/SCsub b/modules/webm/libvpx/SCsub index c76585013c..14fa6c1268 100644 --- a/modules/webm/libvpx/SCsub +++ b/modules/webm/libvpx/SCsub @@ -323,7 +323,7 @@ if webm_cpu_x86: elif cpu_bits == '64': env_libvpx["ASCPU"] = 'X86_64' - env_libvpx.Append(CPPFLAGS=['-DWEBM_X86ASM']) + env_libvpx.Append(CPPDEFINES=['WEBM_X86ASM']) webm_simd_optimizations = True @@ -337,7 +337,7 @@ if webm_cpu_arm: env_libvpx["ASFLAGS"] = '' env_libvpx["ASCOM"] = '$AS $ASFLAGS -o $TARGET $SOURCES' - env_libvpx.Append(CPPFLAGS=['-DWEBM_ARMASM']) + env_libvpx.Append(CPPDEFINES=['WEBM_ARMASM']) webm_simd_optimizations = True diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub index d9e60eb6f1..033169411f 100644 --- a/modules/websocket/SCsub +++ b/modules/websocket/SCsub @@ -5,89 +5,26 @@ Import('env_modules') # Thirdparty source files -env_lws = env_modules.Clone() - -if env['builtin_libwebsockets'] and not env["platform"] == "javascript": # already builtin for javascript - thirdparty_dir = "#thirdparty/libwebsockets/" - helper_dir = "win32helpers/" - thirdparty_sources = [ - - "core/alloc.c", - "core/context.c", - "core/libwebsockets.c", - "core/output.c", - "core/pollfd.c", - "core/service.c", - - "event-libs/poll/poll.c", - - "misc/base64-decode.c", - "misc/lejp.c", - "misc/sha-1.c", - - "roles/h1/ops-h1.c", - "roles/http/header.c", - "roles/http/client/client.c", - "roles/http/client/client-handshake.c", - "roles/http/server/fops-zip.c", - "roles/http/server/lejp-conf.c", - "roles/http/server/parsers.c", - "roles/http/server/server.c", - "roles/listen/ops-listen.c", - "roles/pipe/ops-pipe.c", - "roles/raw/ops-raw.c", - - "roles/ws/client-ws.c", - "roles/ws/client-parser-ws.c", - "roles/ws/ops-ws.c", - "roles/ws/server-ws.c", - - "tls/tls.c", - "tls/tls-client.c", - "tls/tls-server.c", - - "tls/mbedtls/wrapper/library/ssl_cert.c", - "tls/mbedtls/wrapper/library/ssl_pkey.c", - "tls/mbedtls/wrapper/library/ssl_stack.c", - "tls/mbedtls/wrapper/library/ssl_methods.c", - "tls/mbedtls/wrapper/library/ssl_lib.c", - "tls/mbedtls/wrapper/library/ssl_x509.c", - "tls/mbedtls/wrapper/platform/ssl_port.c", - "tls/mbedtls/wrapper/platform/ssl_pm.c", - "tls/mbedtls/lws-genhash.c", - "tls/mbedtls/mbedtls-client.c", - "tls/mbedtls/lws-genrsa.c", - "tls/mbedtls/ssl.c", - "tls/mbedtls/mbedtls-server.c" +env_ws = env_modules.Clone() + +if env['builtin_wslay'] and not env["platform"] == "javascript": # already builtin for javascript + wslay_dir = "#thirdparty/wslay/" + wslay_sources = [ + "wslay_net.c", + "wslay_event.c", + "wslay_queue.c", + "wslay_stack.c", + "wslay_frame.c", ] - - if env["platform"] == "android": # Builtin getifaddrs - thirdparty_sources += ["misc/getifaddrs.c"] - - if env["platform"] == "windows" or env["platform"] == "uwp": # Winsock - thirdparty_sources += ["plat/lws-plat-win.c", helper_dir + "getopt.c", helper_dir + "getopt_long.c", helper_dir + "gettimeofday.c"] - else: # Unix socket - thirdparty_sources += ["plat/lws-plat-unix.c"] - - thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] - - env_lws.Prepend(CPPPATH=[thirdparty_dir]) - - if env['builtin_mbedtls']: - mbedtls_includes = "#thirdparty/mbedtls/include" - env_lws.Prepend(CPPPATH=[mbedtls_includes]) - - wrapper_includes = ["#thirdparty/libwebsockets/tls/mbedtls/wrapper/include/" + inc for inc in ["internal", "openssl", "platform", ""]] - env_lws.Prepend(CPPPATH=wrapper_includes) - + wslay_sources = [wslay_dir + s for s in wslay_sources] + env_ws.Prepend(CPPPATH=[wslay_dir + "includes/"]) + env_ws.Append(CPPDEFINES=["HAVE_CONFIG_H"]) if env["platform"] == "windows" or env["platform"] == "uwp": - env_lws.Prepend(CPPPATH=[thirdparty_dir + helper_dir]) - - if env["platform"] == "uwp": - env_lws.Append(CPPFLAGS=["/DLWS_MINGW_SUPPORT"]) - - env_thirdparty = env_lws.Clone() - env_thirdparty.disable_warnings() - env_thirdparty.add_source_files(env.modules_sources, thirdparty_sources) - -env_lws.add_source_files(env.modules_sources, "*.cpp") + env_ws.Append(CPPDEFINES=["HAVE_WINSOCK2_H"]) + else: + env_ws.Append(CPPDEFINES=["HAVE_NETINET_IN_H"]) + env_wslay = env_ws.Clone() + env_wslay.disable_warnings() + env_wslay.add_source_files(env.modules_sources, wslay_sources) + +env_ws.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/websocket/doc_classes/WebSocketClient.xml b/modules/websocket/doc_classes/WebSocketClient.xml index 0d425ad1dd..c3baf9de83 100644 --- a/modules/websocket/doc_classes/WebSocketClient.xml +++ b/modules/websocket/doc_classes/WebSocketClient.xml @@ -22,7 +22,7 @@ <argument index="2" name="gd_mp_api" type="bool" default="false"> </argument> <description> - Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. + Connects to the given URL requesting one of the given [code]protocols[/code] as sub-protocol. If the list empty (default), no sub-protocol will be requested. If [code]true[/code] is passed as [code]gd_mp_api[/code], the client will behave like a network peer for the [MultiplayerAPI], connections to non-Godot servers will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.) on the [WebSocketPeer] returned via [code]get_peer(1)[/code] and not on this object directly (e.g. [code]get_peer(1).put_packet(data)[/code]). </description> diff --git a/modules/websocket/doc_classes/WebSocketServer.xml b/modules/websocket/doc_classes/WebSocketServer.xml index 1af5e403e6..63318e5874 100644 --- a/modules/websocket/doc_classes/WebSocketServer.xml +++ b/modules/websocket/doc_classes/WebSocketServer.xml @@ -69,7 +69,7 @@ </argument> <description> Starts listening on the given port. - You can specify the desired subprotocols via the "protocols" array. If the list empty (default), "binary" will be used. + You can specify the desired subprotocols via the "protocols" array. If the list empty (default), no sub-protocol will be requested. If [code]true[/code] is passed as [code]gd_mp_api[/code], the server will behave like a network peer for the [MultiplayerAPI], connections from non-Godot clients will not work, and [signal data_received] will not be emitted. If [code]false[/code] is passed instead (default), you must call [PacketPeer] functions ([code]put_packet[/code], [code]get_packet[/code], etc.), on the [WebSocketPeer] returned via [code]get_peer(id)[/code] to communicate with the peer with given [code]id[/code] (e.g. [code]get_peer(id).get_available_packet_count[/code]). </description> diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp deleted file mode 100644 index f139a4ef66..0000000000 --- a/modules/websocket/lws_client.cpp +++ /dev/null @@ -1,248 +0,0 @@ -/*************************************************************************/ -/* lws_client.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_client.h" -#include "core/io/ip.h" -#include "core/project_settings.h" -#if defined(LWS_OPENSSL_SUPPORT) -#include "core/io/stream_peer_ssl.h" -#include "tls/mbedtls/wrapper/include/openssl/ssl.h" -#endif - -Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { - - ERR_FAIL_COND_V(context != NULL, FAILED); - - IP_Address addr; - - if (!p_host.is_valid_ip_address()) { - addr = IP::get_singleton()->resolve_hostname(p_host); - } else { - addr = p_host; - } - - ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); - - // Prepare protocols - _lws_make_protocols(this, &LWSClient::_lws_gd_callback, p_protocols, &_lws_ref); - - // Init lws client - struct lws_context_creation_info info; - struct lws_client_connect_info i; - - memset(&i, 0, sizeof i); - memset(&info, 0, sizeof info); - - info.port = CONTEXT_PORT_NO_LISTEN; - info.protocols = _lws_ref->lws_structs; - info.gid = -1; - info.uid = -1; - //info.ws_ping_pong_interval = 5; - info.user = _lws_ref; -#if defined(LWS_OPENSSL_SUPPORT) - info.options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; -#endif - context = lws_create_context(&info); - - if (context == NULL) { - _lws_free_ref(_lws_ref); - _lws_ref = NULL; - ERR_EXPLAIN("Unable to create lws context"); - ERR_FAIL_V(FAILED); - } - - i.context = context; - if (p_protocols.size() > 0) - i.protocol = _lws_ref->lws_names; - else - i.protocol = NULL; - - if (p_ssl) { - i.ssl_connection = LCCSCF_USE_SSL; - if (!verify_ssl) - i.ssl_connection |= LCCSCF_ALLOW_SELFSIGNED; - } else { - i.ssl_connection = 0; - } - - // These CharStrings needs to survive till we call lws_client_connect_via_info - CharString addr_ch = ((String)addr).ascii(); - CharString host_ch = p_host.utf8(); - CharString path_ch = p_path.utf8(); - i.address = addr_ch.get_data(); - i.host = host_ch.get_data(); - i.path = path_ch.get_data(); - i.port = p_port; - - lws_client_connect_via_info(&i); - - return OK; -}; - -int LWSClient::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -void LWSClient::poll() { - - _lws_poll(); -} - -int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - - Ref<LWSPeer> peer = static_cast<Ref<LWSPeer> >(_peer); - LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; - - switch (reason) { -#if defined(LWS_OPENSSL_SUPPORT) - case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS: { - PoolByteArray arr = StreamPeerSSL::get_project_cert_array(); - if (arr.size() > 0) - SSL_CTX_add_client_CA((SSL_CTX *)user, d2i_X509(NULL, &arr.read()[0], arr.size())); - else if (verify_ssl) - WARN_PRINTS("No CA cert specified in project settings, SSL will not work"); - } break; -#endif - case LWS_CALLBACK_CLIENT_ESTABLISHED: - peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - peer_data->peer_id = 0; - peer_data->force_close = false; - peer_data->clean_close = false; - _on_connect(lws_get_protocol(wsi)->name); - break; - - case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: - _on_error(); - destroy_context(); - return -1; // We should close the connection (would probably happen anyway) - - case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { - int code; - String reason2 = peer->get_close_reason(in, len, code); - peer_data->clean_close = true; - _on_close_request(code, reason2); - return 0; - } - - case LWS_CALLBACK_CLIENT_CLOSED: - peer->close(); - destroy_context(); - _on_disconnect(peer_data->clean_close); - return 0; // We can end here - - case LWS_CALLBACK_CLIENT_RECEIVE: - peer->read_wsi(in, len); - if (peer->get_available_packet_count() > 0) - _on_peer_packet(); - break; - - case LWS_CALLBACK_CLIENT_WRITEABLE: - if (peer_data->force_close) { - peer->send_close_status(wsi); - return -1; - } - - peer->write_wsi(); - break; - - default: - break; - } - - return 0; -} - -Ref<WebSocketPeer> LWSClient::get_peer(int p_peer_id) const { - - return _peer; -} - -NetworkedMultiplayerPeer::ConnectionStatus LWSClient::get_connection_status() const { - - if (context == NULL) - return CONNECTION_DISCONNECTED; - - if (_peer->is_connected_to_host()) - return CONNECTION_CONNECTED; - - return CONNECTION_CONNECTING; -} - -void LWSClient::disconnect_from_host(int p_code, String p_reason) { - - if (context == NULL) - return; - - _peer->close(p_code, p_reason); -}; - -IP_Address LWSClient::get_connected_host() const { - - return IP_Address(); -}; - -uint16_t LWSClient::get_connected_port() const { - - return 1025; -}; - -Error LWSClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(context != NULL, FAILED); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -LWSClient::LWSClient() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); - - context = NULL; - _lws_ref = NULL; - _peer = Ref<LWSPeer>(memnew(LWSPeer)); -}; - -LWSClient::~LWSClient() { - - invalidate_lws_ref(); // We do not want any more callback - disconnect_from_host(); - destroy_context(); - _peer = Ref<LWSPeer>(); -}; - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_helper.cpp b/modules/websocket/lws_helper.cpp deleted file mode 100644 index a652779960..0000000000 --- a/modules/websocket/lws_helper.cpp +++ /dev/null @@ -1,157 +0,0 @@ -/*************************************************************************/ -/* lws_helper.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#if !defined(JAVASCRIPT_ENABLED) - -#include "lws_helper.h" - -_LWSRef *_lws_create_ref(void *obj) { - - _LWSRef *out = (_LWSRef *)memalloc(sizeof(_LWSRef)); - out->is_destroying = false; - out->free_context = false; - out->is_polling = false; - out->obj = obj; - out->is_valid = true; - out->lws_structs = NULL; - out->lws_names = NULL; - return out; -} - -void _lws_free_ref(_LWSRef *ref) { - // Free strings and structs - memfree(ref->lws_structs); - memfree(ref->lws_names); - // Free ref - memfree(ref); -} - -bool _lws_destroy(struct lws_context *context, _LWSRef *ref) { - if (context == NULL || ref->is_destroying) - return false; - - if (ref->is_polling) { - ref->free_context = true; - return false; - } - - ref->is_destroying = true; - lws_context_destroy(context); - _lws_free_ref(ref); - return true; -} - -bool _lws_poll(struct lws_context *context, _LWSRef *ref) { - - ERR_FAIL_COND_V(context == NULL, false); - ERR_FAIL_COND_V(ref == NULL, false); - - ref->is_polling = true; - lws_service(context, 0); - ref->is_polling = false; - - if (!ref->free_context) - return false; // Nothing to do - - bool is_valid = ref->is_valid; // Might have been destroyed by poll - - _lws_destroy(context, ref); // Will destroy context and ref - - return is_valid; // If the object should NULL its context and ref -} - -/* - * Prepare the protocol_structs to be fed to context. - * Also prepare the protocol string used by the client. - */ -void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref) { - // The input strings might go away after this call, we need to copy them. - // We will clear them when destroying the context. - int i; - int len = p_names.size(); - size_t data_size = sizeof(struct LWSPeer::PeerData); - PoolVector<String>::Read pnr = p_names.read(); - - // This is a reference connecting the object with lws keep track of status, mallocs, etc. - // Must survive as long the context. - // Must be freed manually when context creation fails. - _LWSRef *ref = _lws_create_ref(p_obj); - - // LWS protocol structs. - ref->lws_structs = (struct lws_protocols *)memalloc(sizeof(struct lws_protocols) * (len + 2)); - memset(ref->lws_structs, 0, sizeof(struct lws_protocols) * (len + 2)); - - CharString strings = p_names.join(",").ascii(); - int str_len = strings.length(); - - // Joined string of protocols, double the size: comma separated first, NULL separated last - ref->lws_names = (char *)memalloc((str_len + 1) * 2); // Plus the terminator - - char *names_ptr = ref->lws_names; - struct lws_protocols *structs_ptr = ref->lws_structs; - - // Comma separated protocols string to be used in client Sec-WebSocket-Protocol header - if (str_len > 0) - copymem(names_ptr, strings.get_data(), str_len); - names_ptr[str_len] = '\0'; // NULL terminator - - // NULL terminated protocol strings to be used in protocol structs - if (str_len > 0) - copymem(&names_ptr[str_len + 1], strings.get_data(), str_len); - names_ptr[(str_len * 2) + 1] = '\0'; // NULL terminator - int pos = str_len + 1; - - // The first protocol is the default for any http request (before upgrade). - // It is also used as the websocket protocol when no subprotocol is specified. - structs_ptr[0].name = "default"; - structs_ptr[0].callback = p_callback; - structs_ptr[0].per_session_data_size = data_size; - structs_ptr[0].rx_buffer_size = LWS_BUF_SIZE; - structs_ptr[0].tx_packet_size = LWS_PACKET_SIZE; - // Add user defined protocols - for (i = 0; i < len; i++) { - structs_ptr[i + 1].name = (const char *)&names_ptr[pos]; - structs_ptr[i + 1].callback = p_callback; - structs_ptr[i + 1].per_session_data_size = data_size; - structs_ptr[i + 1].rx_buffer_size = LWS_BUF_SIZE; - structs_ptr[i + 1].tx_packet_size = LWS_PACKET_SIZE; - pos += pnr[i].ascii().length() + 1; - names_ptr[pos - 1] = '\0'; - } - // Add protocols terminator - structs_ptr[len + 1].name = NULL; - structs_ptr[len + 1].callback = NULL; - structs_ptr[len + 1].per_session_data_size = 0; - structs_ptr[len + 1].rx_buffer_size = 0; - - *r_lws_ref = ref; -} - -#endif diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h deleted file mode 100644 index 265dc4e6ad..0000000000 --- a/modules/websocket/lws_helper.h +++ /dev/null @@ -1,111 +0,0 @@ -/*************************************************************************/ -/* lws_helper.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifndef LWS_HELPER_H -#define LWS_HELPER_H - -#define LWS_BUF_SIZE 65536 -#define LWS_PACKET_SIZE LWS_BUF_SIZE - -#include "core/io/stream_peer.h" -#include "core/os/os.h" -#include "core/reference.h" -#include "core/ring_buffer.h" -#include "lws_peer.h" - -struct _LWSRef { - bool free_context; - bool is_polling; - bool is_valid; - bool is_destroying; - void *obj; - struct lws_protocols *lws_structs; - char *lws_names; -}; - -_LWSRef *_lws_create_ref(void *obj); -void _lws_free_ref(_LWSRef *ref); -bool _lws_destroy(struct lws_context *context, _LWSRef *ref); -bool _lws_poll(struct lws_context *context, _LWSRef *ref); -void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref); - -/* clang-format off */ -#define LWS_HELPER(CNAME) \ -protected: \ - struct _LWSRef *_lws_ref; \ - struct lws_context *context; \ - bool _keep_servicing; \ - \ - static int _lws_gd_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { \ - \ - if (wsi == NULL) { \ - return 0; \ - } \ - \ - struct _LWSRef *ref = (struct _LWSRef *)lws_context_user(lws_get_context(wsi)); \ - if (!ref->is_valid) \ - return 0; \ - CNAME *helper = (CNAME *)ref->obj; \ - helper->_keep_servicing = true; \ - return helper->_handle_cb(wsi, reason, user, in, len); \ - } \ - \ - void invalidate_lws_ref() { \ - if (_lws_ref != NULL) \ - _lws_ref->is_valid = false; \ - } \ - \ - void destroy_context() { \ - if (_lws_destroy(context, _lws_ref)) { \ - context = NULL; \ - _lws_ref = NULL; \ - } \ - } \ - \ -public: \ - virtual int _handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); \ - \ - void _lws_poll() { \ - ERR_FAIL_COND(context == NULL); \ - do { \ - _keep_servicing = false; \ - if (::_lws_poll(context, _lws_ref)) { \ - context = NULL; \ - _lws_ref = NULL; \ - break; \ - } \ - } while (_keep_servicing); \ - } \ - \ -protected: - -/* clang-format on */ - -#endif // LWS_HELPER_H diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp deleted file mode 100644 index a7c85450fa..0000000000 --- a/modules/websocket/lws_peer.cpp +++ /dev/null @@ -1,270 +0,0 @@ -/*************************************************************************/ -/* lws_peer.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_peer.h" - -#include "core/io/ip.h" - -// Needed for socket_helpers on Android at least. UNIXes has it, just include if not windows -#if !defined(WINDOWS_ENABLED) -#include <netinet/in.h> -#include <sys/socket.h> -#endif - -#include "drivers/unix/net_socket_posix.h" - -void LWSPeer::set_wsi(struct lws *p_wsi, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { - ERR_FAIL_COND(wsi != NULL); - - _in_buffer.resize(p_in_pkt_size, p_in_buf_size); - _out_buffer.resize(p_out_pkt_size, p_out_buf_size); - _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size)) + LWS_PRE); - wsi = p_wsi; -}; - -void LWSPeer::set_write_mode(WriteMode p_mode) { - write_mode = p_mode; -} - -LWSPeer::WriteMode LWSPeer::get_write_mode() const { - return write_mode; -} - -Error LWSPeer::read_wsi(void *in, size_t len) { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - if (lws_is_first_fragment(wsi)) - _in_size = 0; - else if (_in_size == -1) // Trash this frame - return ERR_FILE_CORRUPT; - - Error err = _in_buffer.write_packet((const uint8_t *)in, len, NULL); - - if (err != OK) { - _in_buffer.discard_payload(_in_size); - _in_size = -1; - ERR_FAIL_V(err); - } - - _in_size += len; - - if (lws_is_final_fragment(wsi)) { - uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; - err = _in_buffer.write_packet(NULL, _in_size, &is_string); - if (err != OK) { - _in_buffer.discard_payload(_in_size); - _in_size = -1; - ERR_FAIL_V(err); - } - } - - return OK; -} - -Error LWSPeer::write_wsi() { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - PoolVector<uint8_t> tmp; - int count = _out_buffer.packets_left(); - - if (count == 0) - return OK; - - int read = 0; - uint8_t is_string = 0; - PoolVector<uint8_t>::Write rw = _packet_buffer.write(); - _out_buffer.read_packet(&(rw[LWS_PRE]), _packet_buffer.size() - LWS_PRE, &is_string, read); - - enum lws_write_protocol mode = is_string ? LWS_WRITE_TEXT : LWS_WRITE_BINARY; - lws_write(wsi, &(rw[LWS_PRE]), read, mode); - - if (count > 1) - lws_callback_on_writable(wsi); // we want to write more! - - return OK; -} - -Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - uint8_t is_string = write_mode == WRITE_MODE_TEXT; - _out_buffer.write_packet(p_buffer, p_buffer_size, &is_string); - lws_callback_on_writable(wsi); // notify that we want to write - return OK; -}; - -Error LWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { - - r_buffer_size = 0; - - ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); - - if (_in_buffer.packets_left() == 0) - return ERR_UNAVAILABLE; - - int read = 0; - PoolVector<uint8_t>::Write rw = _packet_buffer.write(); - _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); - - *r_buffer = rw.ptr(); - r_buffer_size = read; - - return OK; -}; - -int LWSPeer::get_available_packet_count() const { - - if (!is_connected_to_host()) - return 0; - - return _in_buffer.packets_left(); -}; - -bool LWSPeer::was_string_packet() const { - - return _is_string; -}; - -bool LWSPeer::is_connected_to_host() const { - - return wsi != NULL; -}; - -String LWSPeer::get_close_reason(void *in, size_t len, int &r_code) { - String s; - r_code = 0; - if (len < 2) // From docs this should not happen - return s; - - const uint8_t *b = (const uint8_t *)in; - r_code = b[0] << 8 | b[1]; - - if (len > 2) { - const char *utf8 = (const char *)&b[2]; - s.parse_utf8(utf8, len - 2); - } - return s; -} - -void LWSPeer::send_close_status(struct lws *p_wsi) { - if (close_code == -1) - return; - - int len = close_reason.size(); - ERR_FAIL_COND(len > 123); // Maximum allowed reason size in bytes - - lws_close_status code = (lws_close_status)close_code; - unsigned char *reason = len > 0 ? (unsigned char *)close_reason.utf8().ptrw() : NULL; - - lws_close_reason(p_wsi, code, reason, len); - - close_code = -1; - close_reason = ""; -} - -void LWSPeer::close(int p_code, String p_reason) { - if (wsi != NULL) { - close_code = p_code; - close_reason = p_reason; - PeerData *data = ((PeerData *)lws_wsi_user(wsi)); - data->force_close = true; - data->clean_close = true; - lws_callback_on_writable(wsi); // Notify that we want to disconnect - } else { - close_code = -1; - close_reason = ""; - } - wsi = NULL; - _in_buffer.clear(); - _out_buffer.clear(); - _in_size = 0; - _is_string = 0; - _packet_buffer.resize(0); -}; - -IP_Address LWSPeer::get_connected_host() const { - - ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); - - IP_Address ip; - uint16_t port = 0; - - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - int fd = lws_get_socket_fd(wsi); - ERR_FAIL_COND_V(fd == -1, IP_Address()); - - int ret = getpeername(fd, (struct sockaddr *)&addr, &len); - ERR_FAIL_COND_V(ret != 0, IP_Address()); - - NetSocketPosix::_set_ip_port(&addr, ip, port); - - return ip; -}; - -uint16_t LWSPeer::get_connected_port() const { - - ERR_FAIL_COND_V(!is_connected_to_host(), 0); - - IP_Address ip; - uint16_t port = 0; - - struct sockaddr_storage addr; - socklen_t len = sizeof(addr); - - int fd = lws_get_socket_fd(wsi); - ERR_FAIL_COND_V(fd == -1, 0); - - int ret = getpeername(fd, (struct sockaddr *)&addr, &len); - ERR_FAIL_COND_V(ret != 0, 0); - - NetSocketPosix::_set_ip_port(&addr, ip, port); - - return port; -}; - -LWSPeer::LWSPeer() { - wsi = NULL; - write_mode = WRITE_MODE_BINARY; - close(); -}; - -LWSPeer::~LWSPeer() { - - close(); -}; - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp deleted file mode 100644 index 482c02dc60..0000000000 --- a/modules/websocket/lws_server.cpp +++ /dev/null @@ -1,225 +0,0 @@ -/*************************************************************************/ -/* lws_server.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ -/* */ -/* 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. */ -/*************************************************************************/ - -#ifndef JAVASCRIPT_ENABLED - -#include "lws_server.h" -#include "core/os/os.h" -#include "core/project_settings.h" - -Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { - - ERR_FAIL_COND_V(context != NULL, FAILED); - - _is_multiplayer = gd_mp_api; - - struct lws_context_creation_info info; - memset(&info, 0, sizeof info); - - // Prepare lws protocol structs - _lws_make_protocols(this, &LWSServer::_lws_gd_callback, p_protocols, &_lws_ref); - - info.port = p_port; - info.user = _lws_ref; - info.protocols = _lws_ref->lws_structs; - info.gid = -1; - info.uid = -1; - //info.ws_ping_pong_interval = 5; - - context = lws_create_context(&info); - - if (context == NULL) { - _lws_free_ref(_lws_ref); - _lws_ref = NULL; - ERR_EXPLAIN("Unable to create LWS context"); - ERR_FAIL_V(FAILED); - } - - return OK; -} - -bool LWSServer::is_listening() const { - return context != NULL; -} - -int LWSServer::get_max_packet_size() const { - return (1 << _out_buf_size) - PROTO_SIZE; -} - -int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { - - LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; - - switch (reason) { - case LWS_CALLBACK_HTTP: - // no http for now - // closing immediately returning -1; - return -1; - - case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: - // check header here? - break; - - case LWS_CALLBACK_ESTABLISHED: { - int32_t id = _gen_unique_id(); - - Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer)); - peer->set_wsi(wsi, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); - _peer_map[id] = peer; - - peer_data->peer_id = id; - peer_data->force_close = false; - peer_data->clean_close = false; - _on_connect(id, lws_get_protocol(wsi)->name); - break; - } - - case LWS_CALLBACK_WS_PEER_INITIATED_CLOSE: { - if (peer_data == NULL) - return 0; - - int32_t id = peer_data->peer_id; - if (_peer_map.has(id)) { - int code; - Ref<LWSPeer> peer = _peer_map[id]; - String reason2 = peer->get_close_reason(in, len, code); - peer_data->clean_close = true; - _on_close_request(id, code, reason2); - } - return 0; - } - - case LWS_CALLBACK_CLOSED: { - if (peer_data == NULL) - return 0; - int32_t id = peer_data->peer_id; - bool clean = peer_data->clean_close; - if (_peer_map.has(id)) { - _peer_map[id]->close(); - _peer_map.erase(id); - } - _on_disconnect(id, clean); - return 0; // we can end here - } - - case LWS_CALLBACK_RECEIVE: { - int32_t id = peer_data->peer_id; - if (_peer_map.has(id)) { - static_cast<Ref<LWSPeer> >(_peer_map[id])->read_wsi(in, len); - if (_peer_map[id]->get_available_packet_count() > 0) - _on_peer_packet(id); - } - break; - } - - case LWS_CALLBACK_SERVER_WRITEABLE: { - int id = peer_data->peer_id; - if (peer_data->force_close) { - if (_peer_map.has(id)) { - Ref<LWSPeer> peer = _peer_map[id]; - peer->send_close_status(wsi); - } - return -1; - } - - if (_peer_map.has(id)) - static_cast<Ref<LWSPeer> >(_peer_map[id])->write_wsi(); - break; - } - - default: - break; - } - - return 0; -} - -void LWSServer::stop() { - if (context == NULL) - return; - - _peer_map.clear(); - destroy_context(); - context = NULL; -} - -bool LWSServer::has_peer(int p_id) const { - return _peer_map.has(p_id); -} - -Ref<WebSocketPeer> LWSServer::get_peer(int p_id) const { - ERR_FAIL_COND_V(!has_peer(p_id), NULL); - return _peer_map[p_id]; -} - -IP_Address LWSServer::get_peer_address(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), IP_Address()); - - return _peer_map[p_peer_id]->get_connected_host(); -} - -int LWSServer::get_peer_port(int p_peer_id) const { - ERR_FAIL_COND_V(!has_peer(p_peer_id), 0); - - return _peer_map[p_peer_id]->get_connected_port(); -} - -void LWSServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { - ERR_FAIL_COND(!has_peer(p_peer_id)); - - get_peer(p_peer_id)->close(p_code, p_reason); -} - -Error LWSServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { - ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); - ERR_FAIL_COND_V(context != NULL, FAILED); - - _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; - _in_pkt_size = nearest_shift(p_in_packets - 1); - _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; - _out_pkt_size = nearest_shift(p_out_packets - 1); - return OK; -} - -LWSServer::LWSServer() { - _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; - _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); - _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; - _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); - context = NULL; - _lws_ref = NULL; -} - -LWSServer::~LWSServer() { - invalidate_lws_ref(); // we do not want any more callbacks - stop(); -} - -#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp index 39bf3de982..1c808f0d5c 100644 --- a/modules/websocket/register_types.cpp +++ b/modules/websocket/register_types.cpp @@ -37,9 +37,8 @@ #include "emws_peer.h" #include "emws_server.h" #else -#include "lws_client.h" -#include "lws_peer.h" -#include "lws_server.h" +#include "wsl_client.h" +#include "wsl_server.h" #endif void register_websocket_types() { @@ -64,9 +63,9 @@ void register_websocket_types() { EMWSClient::make_default(); EMWSServer::make_default(); #else - LWSPeer::make_default(); - LWSClient::make_default(); - LWSServer::make_default(); + WSLPeer::make_default(); + WSLClient::make_default(); + WSLServer::make_default(); #endif ClassDB::register_virtual_class<WebSocketMultiplayerPeer>(); diff --git a/modules/websocket/wsl_client.cpp b/modules/websocket/wsl_client.cpp new file mode 100644 index 0000000000..b2d865a58f --- /dev/null +++ b/modules/websocket/wsl_client.cpp @@ -0,0 +1,362 @@ +/*************************************************************************/ +/* wsl_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_client.h" +#include "core/io/ip.h" +#include "core/project_settings.h" + +void WSLClient::_do_handshake() { + if (_requested < _request.size() - 1) { + int sent = 0; + Error err = _connection->put_partial_data(((const uint8_t *)_request.get_data() + _requested), _request.size() - _requested - 1, sent); + // Sending handshake failed + if (err != OK) { + disconnect_from_host(); + _on_error(); + return; + } + _requested += sent; + + } else { + int read = 0; + while (true) { + if (_resp_pos >= WSL_MAX_HEADER_SIZE) { + // Header is too big + disconnect_from_host(); + _on_error(); + ERR_EXPLAIN("Response headers too big"); + ERR_FAIL(); + } + Error err = _connection->get_partial_data(&_resp_buf[_resp_pos], 1, read); + if (err == ERR_FILE_EOF) { + // We got a disconnect. + disconnect_from_host(); + _on_error(); + return; + } else if (err != OK) { + // Got some error. + disconnect_from_host(); + _on_error(); + return; + } else if (read != 1) { + // Busy, wait next poll. + break; + } + // Check "\r\n\r\n" header terminator + char *r = (char *)_resp_buf; + int l = _resp_pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; + String protocol; + // Response is over, verify headers and create peer. + if (!_verify_headers(protocol)) { + disconnect_from_host(); + _on_error(); + ERR_EXPLAIN("Invalid response headers"); + ERR_FAIL(); + } + // Create peer. + WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); + data->obj = this; + data->conn = _connection; + data->is_server = false; + data->id = 1; + _peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); + _on_connect(protocol); + } + _resp_pos += 1; + } + } +} + +bool WSLClient::_verify_headers(String &r_protocol) { + String s = (char *)_resp_buf; + Vector<String> psa = s.split("\r\n"); + int len = psa.size(); + if (len < 4) { + ERR_EXPLAIN("Not enough response headers."); + ERR_FAIL_V(false); + } + + Vector<String> req = psa[0].split(" ", false); + if (req.size() < 2) { + ERR_EXPLAIN("Invalid protocol or status code."); + ERR_FAIL_V(false); + } + // Wrong protocol + if (req[0] != "HTTP/1.1" || req[1] != "101") { + ERR_EXPLAIN("Invalid protocol or status code."); + ERR_FAIL_V(false); + } + + Map<String, String> headers; + for (int i = 1; i < len; i++) { + Vector<String> header = psa[i].split(":", false, 1); + if (header.size() != 2) { + ERR_EXPLAIN("Invalid header -> " + psa[i]); + ERR_FAIL_V(false); + } + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) + headers[name] += "," + value; + else + headers[name] = value; + } + +#define _WLS_EXPLAIN(NAME, VALUE) \ + ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'"); +#define _WLS_CHECK(NAME, VALUE) \ + _WLS_EXPLAIN(NAME, VALUE); \ + ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false); +#define _WLS_CHECK_NC(NAME, VALUE) \ + _WLS_EXPLAIN(NAME, VALUE); \ + ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME] != VALUE, false); + _WLS_CHECK("connection", "upgrade"); + _WLS_CHECK("upgrade", "websocket"); + _WLS_CHECK_NC("sec-websocket-accept", WSLPeer::compute_key_response(_key)); + if (_protocols.size() == 0) { + // We didn't request a custom protocol + ERR_FAIL_COND_V(headers.has("sec-websocket-protocol"), false); + } else { + ERR_FAIL_COND_V(!headers.has("sec-websocket-protocol"), false); + r_protocol = headers["sec-websocket-protocol"]; + bool valid = false; + for (int i = 0; i < _protocols.size(); i++) { + if (_protocols[i] != r_protocol) + continue; + valid = true; + break; + } + if (!valid) + return false; + } +#undef _WLS_CHECK_NC +#undef _WLS_CHECK +#undef _WLS_EXPLAIN + + return true; +} + +Error WSLClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + + ERR_FAIL_COND_V(_connection.is_valid(), ERR_ALREADY_IN_USE); + + _peer = Ref<WSLPeer>(memnew(WSLPeer)); + IP_Address addr; + + if (!p_host.is_valid_ip_address()) { + addr = IP::get_singleton()->resolve_hostname(p_host); + } else { + addr = p_host; + } + + ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); + + String port = ""; + if ((p_port != 80 && !p_ssl) || (p_port != 443 && p_ssl)) { + port = ":" + itos(p_port); + } + + Error err = _tcp->connect_to_host(addr, p_port); + if (err != OK) { + _on_error(); + _tcp->disconnect_from_host(); + return err; + } + _connection = _tcp; + _use_ssl = p_ssl; + _host = p_host; + _protocols = p_protocols; + + _key = WSLPeer::generate_key(); + // TODO custom extra headers (allow overriding this too?) + String request = "GET " + p_path + " HTTP/1.1\r\n"; + request += "Host: " + p_host + port + "\r\n"; + request += "Upgrade: websocket\r\n"; + request += "Connection: Upgrade\r\n"; + request += "Sec-WebSocket-Key: " + _key + "\r\n"; + request += "Sec-WebSocket-Version: 13\r\n"; + if (p_protocols.size() > 0) { + request += "Sec-WebSocket-Protocol: "; + for (int i = 0; i < p_protocols.size(); i++) { + if (i != 0) + request += ","; + request += p_protocols[i]; + } + request += "\r\n"; + } + request += "\r\n"; + _request = request.utf8(); + + return OK; +} + +int WSLClient::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + +void WSLClient::poll() { + if (_peer->is_connected_to_host()) { + _peer->poll(); + if (!_peer->is_connected_to_host()) { + _on_disconnect(_peer->close_code != -1); + disconnect_from_host(); + } + return; + } + + if (_connection.is_null()) + return; // Not connected. + + switch (_tcp->get_status()) { + case StreamPeerTCP::STATUS_NONE: + // Clean close + _on_error(); + disconnect_from_host(); + break; + case StreamPeerTCP::STATUS_CONNECTED: { + Ref<StreamPeerSSL> ssl; + if (_use_ssl) { + if (_connection == _tcp) { + // Start SSL handshake + ssl = Ref<StreamPeerSSL>(StreamPeerSSL::create()); + ERR_EXPLAIN("SSL is not available in this build"); + ERR_FAIL_COND(ssl.is_null()); + ssl->set_blocking_handshake_enabled(false); + if (ssl->connect_to_stream(_tcp, verify_ssl, _host) != OK) { + _on_error(); + disconnect_from_host(); + return; + } + _connection = ssl; + } else { + ssl = static_cast<Ref<StreamPeerSSL> >(_connection); + ERR_FAIL_COND(ssl.is_null()); // Bug? + ssl->poll(); + } + if (ssl->get_status() == StreamPeerSSL::STATUS_HANDSHAKING) + return; // Need more polling. + else if (ssl->get_status() != StreamPeerSSL::STATUS_CONNECTED) { + _on_error(); + disconnect_from_host(); + return; // Error. + } + } + // Do websocket handshake. + _do_handshake(); + } break; + case StreamPeerTCP::STATUS_ERROR: + _on_error(); + disconnect_from_host(); + break; + case StreamPeerTCP::STATUS_CONNECTING: + break; // Wait for connection + } +} + +Ref<WebSocketPeer> WSLClient::get_peer(int p_peer_id) const { + + ERR_FAIL_COND_V(p_peer_id != 1, NULL); + + return _peer; +} + +NetworkedMultiplayerPeer::ConnectionStatus WSLClient::get_connection_status() const { + + if (_peer->is_connected_to_host()) + return CONNECTION_CONNECTED; + + if (_tcp->is_connected_to_host()) + return CONNECTION_CONNECTING; + + return CONNECTION_DISCONNECTED; +} + +void WSLClient::disconnect_from_host(int p_code, String p_reason) { + + _peer->close(p_code, p_reason); + _connection = Ref<StreamPeer>(NULL); + _tcp = Ref<StreamPeerTCP>(memnew(StreamPeerTCP)); + + _key = ""; + _host = ""; + _protocols.resize(0); + _use_ssl = false; + + _request = ""; + _requested = 0; + + memset(_resp_buf, 0, sizeof(_resp_buf)); + _resp_pos = 0; +} + +IP_Address WSLClient::get_connected_host() const { + + return IP_Address(); +} + +uint16_t WSLClient::get_connected_port() const { + + return 1025; +} + +Error WSLClient::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { + ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); + ERR_FAIL_COND_V(_connection.is_valid(), FAILED); + + _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; + _in_pkt_size = nearest_shift(p_in_packets - 1); + _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; + _out_pkt_size = nearest_shift(p_out_packets - 1); + return OK; +} + +WSLClient::WSLClient() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSC_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSC_OUT_PKT) - 1); + + _peer.instance(); + _tcp.instance(); + disconnect_from_host(); +} + +WSLClient::~WSLClient() { + + _peer->close_now(); + _peer->invalidate(); + disconnect_from_host(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_client.h b/modules/websocket/wsl_client.h index df4aabec7f..57dfd635b7 100644 --- a/modules/websocket/lws_client.h +++ b/modules/websocket/wsl_client.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_client.h */ +/* wsl_client.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,21 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSCLIENT_H -#define LWSCLIENT_H +#ifndef WSLCLIENT_H +#define WSLCLIENT_H #ifndef JAVASCRIPT_ENABLED #include "core/error_list.h" -#include "lws_helper.h" -#include "lws_peer.h" +#include "core/io/stream_peer_ssl.h" +#include "core/io/stream_peer_tcp.h" #include "websocket_client.h" +#include "wsl_peer.h" +#include "wslay/wslay.h" -class LWSClient : public WebSocketClient { +class WSLClient : public WebSocketClient { - GDCIIMPL(LWSClient, WebSocketClient); - - LWS_HELPER(LWSClient); + GDCIIMPL(WSLClient, WebSocketClient); private: int _in_buf_size; @@ -50,6 +50,26 @@ private: int _out_buf_size; int _out_pkt_size; + Ref<WSLPeer> _peer; + Ref<StreamPeerTCP> _tcp; + Ref<StreamPeer> _connection; + + CharString _request; + int _requested; + + uint8_t _resp_buf[WSL_MAX_HEADER_SIZE]; + int _resp_pos; + + String _response; + + String _key; + String _host; + PoolVector<String> _protocols; + bool _use_ssl; + + void _do_handshake(); + bool _verify_headers(String &r_protocol); + public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); @@ -61,10 +81,10 @@ public: virtual ConnectionStatus get_connection_status() const; virtual void poll(); - LWSClient(); - ~LWSClient(); + WSLClient(); + ~WSLClient(); }; #endif // JAVASCRIPT_ENABLED -#endif // LWSCLIENT_H +#endif // WSLCLIENT_H diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp new file mode 100644 index 0000000000..b11bd2b70f --- /dev/null +++ b/modules/websocket/wsl_peer.cpp @@ -0,0 +1,339 @@ +/*************************************************************************/ +/* lws_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_peer.h" + +#include "wsl_client.h" +#include "wsl_server.h" + +#include "core/math/crypto_core.h" +#include "core/math/random_number_generator.h" +#include "core/os/os.h" + +String WSLPeer::generate_key() { + // Random key + RandomNumberGenerator rng; + rng.set_seed(OS::get_singleton()->get_unix_time()); + PoolVector<uint8_t> bkey; + int len = 16; // 16 bytes, as per RFC + bkey.resize(len); + PoolVector<uint8_t>::Write w = bkey.write(); + for (int i = 0; i < len; i++) { + w[i] = (uint8_t)rng.randi_range(0, 255); + } + return CryptoCore::b64_encode_str(&w[0], len); +} + +String WSLPeer::compute_key_response(String p_key) { + String key = p_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // Magic UUID as per RFC + Vector<uint8_t> sha = key.sha1_buffer(); + return CryptoCore::b64_encode_str(sha.ptr(), sha.size()); +} + +void WSLPeer::_wsl_destroy(struct PeerData **p_data) { + if (!p_data || !(*p_data)) + return; + struct PeerData *data = *p_data; + if (data->polling) { + data->destroy = true; + return; + } + wslay_event_context_free(data->ctx); + memdelete(data); + *p_data = NULL; +} + +bool WSLPeer::_wsl_poll(struct PeerData *p_data) { + p_data->polling = true; + int err = 0; + if ((err = wslay_event_recv(p_data->ctx)) != 0 || (err = wslay_event_send(p_data->ctx)) != 0) { + print_verbose("Websocket (wslay) poll error: " + itos(err)); + p_data->destroy = true; + } + p_data->polling = false; + + if (p_data->destroy || (wslay_event_get_close_sent(p_data->ctx) && wslay_event_get_close_received(p_data->ctx))) { + bool valid = p_data->valid; + _wsl_destroy(&p_data); + return valid; + } + return false; +} + +ssize_t wsl_recv_callback(wslay_event_context_ptr ctx, uint8_t *data, size_t len, int flags, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + Ref<StreamPeer> conn = peer_data->conn; + int read = 0; + Error err = conn->get_partial_data(data, len, read); + if (err != OK) { + print_verbose("Websocket get data error: " + itos(err) + ", read (should be 0!): " + itos(read)); + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + if (read == 0) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + return read; +} + +ssize_t wsl_send_callback(wslay_event_context_ptr ctx, const uint8_t *data, size_t len, int flags, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + Ref<StreamPeer> conn = peer_data->conn; + int sent = 0; + Error err = conn->put_partial_data(data, len, sent); + if (err != OK) { + wslay_event_set_error(ctx, WSLAY_ERR_CALLBACK_FAILURE); + return -1; + } + if (sent == 0) { + wslay_event_set_error(ctx, WSLAY_ERR_WOULDBLOCK); + return -1; + } + return sent; +} + +int wsl_genmask_callback(wslay_event_context_ptr ctx, uint8_t *buf, size_t len, void *user_data) { + RandomNumberGenerator rng; + // TODO maybe use crypto in the future? + rng.set_seed(OS::get_singleton()->get_unix_time()); + for (unsigned int i = 0; i < len; i++) { + buf[i] = (uint8_t)rng.randi_range(0, 255); + } + return 0; +} + +void wsl_msg_recv_callback(wslay_event_context_ptr ctx, const struct wslay_event_on_msg_recv_arg *arg, void *user_data) { + struct WSLPeer::PeerData *peer_data = (struct WSLPeer::PeerData *)user_data; + if (!peer_data->valid) { + return; + } + WSLPeer *peer = (WSLPeer *)peer_data->peer; + + if (peer->parse_message(arg) != OK) + return; + + if (peer_data->is_server) { + WSLServer *helper = (WSLServer *)peer_data->obj; + helper->_on_peer_packet(peer_data->id); + } else { + WSLClient *helper = (WSLClient *)peer_data->obj; + helper->_on_peer_packet(); + } +} + +wslay_event_callbacks wsl_callbacks = { + wsl_recv_callback, + wsl_send_callback, + wsl_genmask_callback, + NULL, /* on_frame_recv_start_callback */ + NULL, /* on_frame_recv_callback */ + NULL, /* on_frame_recv_end_callback */ + wsl_msg_recv_callback +}; + +Error WSLPeer::parse_message(const wslay_event_on_msg_recv_arg *arg) { + uint8_t is_string = 0; + if (arg->opcode == WSLAY_TEXT_FRAME) { + is_string = 1; + } else if (arg->opcode == WSLAY_CONNECTION_CLOSE) { + close_code = arg->status_code; + size_t len = arg->msg_length; + close_reason = ""; + if (len > 2 /* first 2 bytes = close code */) { + close_reason.parse_utf8((char *)arg->msg + 2, len - 2); + } + if (!wslay_event_get_close_sent(_data->ctx)) { + if (_data->is_server) { + WSLServer *helper = (WSLServer *)_data->obj; + helper->_on_close_request(_data->id, close_code, close_reason); + } else { + WSLClient *helper = (WSLClient *)_data->obj; + helper->_on_close_request(close_code, close_reason); + } + } + return ERR_FILE_EOF; + } else if (arg->opcode != WSLAY_BINARY_FRAME) { + // Ping or pong + return ERR_SKIP; + } + _in_buffer.write_packet(arg->msg, arg->msg_length, &is_string); + return OK; +} + +void WSLPeer::make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size) { + ERR_FAIL_COND(_data != NULL); + ERR_FAIL_COND(p_data == NULL); + + _in_buffer.resize(p_in_pkt_size, p_in_buf_size); + _packet_buffer.resize((1 << MAX(p_in_buf_size, p_out_buf_size))); + + _data = p_data; + _data->peer = this; + _data->valid = true; + _connection = Ref<StreamPeer>(_data->conn); + + if (_data->is_server) + wslay_event_context_server_init(&(_data->ctx), &wsl_callbacks, _data); + else + wslay_event_context_client_init(&(_data->ctx), &wsl_callbacks, _data); + wslay_event_config_set_max_recv_msg_length(_data->ctx, (1 << p_in_buf_size)); +} + +void WSLPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +WSLPeer::WriteMode WSLPeer::get_write_mode() const { + return write_mode; +} + +void WSLPeer::poll() { + if (!_data) + return; + + if (_wsl_poll(_data)) { + _data = NULL; + } +} + +Error WSLPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + struct wslay_event_msg msg; // Should I use fragmented? + msg.opcode = write_mode == WRITE_MODE_TEXT ? WSLAY_TEXT_FRAME : WSLAY_BINARY_FRAME; + msg.msg = p_buffer; + msg.msg_length = p_buffer_size; + + wslay_event_queue_msg(_data->ctx, &msg); + return OK; +} + +Error WSLPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + r_buffer_size = 0; + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + if (_in_buffer.packets_left() == 0) + return ERR_UNAVAILABLE; + + int read = 0; + PoolVector<uint8_t>::Write rw = _packet_buffer.write(); + _in_buffer.read_packet(rw.ptr(), _packet_buffer.size(), &_is_string, read); + + *r_buffer = rw.ptr(); + r_buffer_size = read; + + return OK; +} + +int WSLPeer::get_available_packet_count() const { + + if (!is_connected_to_host()) + return 0; + + return _in_buffer.packets_left(); +} + +bool WSLPeer::was_string_packet() const { + + return _is_string; +} + +bool WSLPeer::is_connected_to_host() const { + + return _data != NULL; +} + +void WSLPeer::close_now() { + close(1000, ""); + _wsl_destroy(&_data); +} + +void WSLPeer::close(int p_code, String p_reason) { + if (_data && !wslay_event_get_close_sent(_data->ctx)) { + CharString cs = p_reason.utf8(); + wslay_event_queue_close(_data->ctx, p_code, (uint8_t *)cs.ptr(), cs.size()); + wslay_event_send(_data->ctx); + } + + _in_buffer.clear(); + _packet_buffer.resize(0); +} + +IP_Address WSLPeer::get_connected_host() const { + + ERR_FAIL_COND_V(!is_connected_to_host(), IP_Address()); + + IP_Address ip; + return ip; +} + +uint16_t WSLPeer::get_connected_port() const { + + ERR_FAIL_COND_V(!is_connected_to_host(), 0); + + uint16_t port = 0; + return port; +} + +void WSLPeer::invalidate() { + if (_data) + _data->valid = false; +} + +WSLPeer::WSLPeer() { + _data = NULL; + _is_string = 0; + close_code = -1; + write_mode = WRITE_MODE_BINARY; +} + +WSLPeer::~WSLPeer() { + + close(); + invalidate(); + _wsl_destroy(&_data); + _data = NULL; +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_peer.h b/modules/websocket/wsl_peer.h index 6771c13094..d51b304fe1 100644 --- a/modules/websocket/lws_peer.h +++ b/modules/websocket/wsl_peer.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_peer.h */ +/* wsl_peer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,50 +28,76 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSPEER_H -#define LWSPEER_H +#ifndef WSLPEER_H +#define WSLPEER_H #ifndef JAVASCRIPT_ENABLED #include "core/error_list.h" #include "core/io/packet_peer.h" #include "core/ring_buffer.h" -#include "libwebsockets.h" -#include "lws_config.h" #include "packet_buffer.h" #include "websocket_peer.h" +#include "wslay/wslay.h" -class LWSPeer : public WebSocketPeer { +#define WSL_MAX_HEADER_SIZE 4096 - GDCIIMPL(LWSPeer, WebSocketPeer); +class WSLPeer : public WebSocketPeer { + + GDCIIMPL(WSLPeer, WebSocketPeer); + +public: + struct PeerData { + bool polling; + bool destroy; + bool valid; + bool is_server; + void *obj; + void *peer; + Ref<StreamPeer> conn; + int id; + wslay_event_context_ptr ctx; + + PeerData() { + polling = false; + destroy = false; + valid = false; + is_server = false; + id = 1; + ctx = NULL; + obj = NULL; + peer = NULL; + } + }; + + static String compute_key_response(String p_key); + static String generate_key(); private: - int _in_size; + static bool _wsl_poll(struct PeerData *p_data); + static void _wsl_destroy(struct PeerData **p_data); + + Ref<StreamPeer> _connection; + struct PeerData *_data; uint8_t _is_string; // Our packet info is just a boolean (is_string), using uint8_t for it. PacketBuffer<uint8_t> _in_buffer; - PacketBuffer<uint8_t> _out_buffer; PoolVector<uint8_t> _packet_buffer; - struct lws *wsi; WriteMode write_mode; +public: int close_code; String close_reason; - -public: - struct PeerData { - uint32_t peer_id; - bool force_close; - bool clean_close; - }; + void poll(); // Used by client and server. virtual int get_available_packet_count() const; virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); virtual int get_max_packet_size() const { return _packet_buffer.size(); }; + virtual void close_now(); virtual void close(int p_code = 1000, String p_reason = ""); virtual bool is_connected_to_host() const; virtual IP_Address get_connected_host() const; @@ -81,14 +107,12 @@ public: virtual void set_write_mode(WriteMode p_mode); virtual bool was_string_packet() const; - void set_wsi(struct lws *wsi, unsigned int _in_buf_size, unsigned int _in_pkt_size, unsigned int _out_buf_size, unsigned int _out_pkt_size); - Error read_wsi(void *in, size_t len); - Error write_wsi(); - void send_close_status(struct lws *wsi); - String get_close_reason(void *in, size_t len, int &r_code); + void make_context(PeerData *p_data, unsigned int p_in_buf_size, unsigned int p_in_pkt_size, unsigned int p_out_buf_size, unsigned int p_out_pkt_size); + Error parse_message(const wslay_event_on_msg_recv_arg *arg); + void invalidate(); - LWSPeer(); - ~LWSPeer(); + WSLPeer(); + ~WSLPeer(); }; #endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/wsl_server.cpp b/modules/websocket/wsl_server.cpp new file mode 100644 index 0000000000..1e140a716f --- /dev/null +++ b/modules/websocket/wsl_server.cpp @@ -0,0 +1,281 @@ +/*************************************************************************/ +/* lws_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* 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. */ +/*************************************************************************/ + +#ifndef JAVASCRIPT_ENABLED + +#include "wsl_server.h" +#include "core/os/os.h" +#include "core/project_settings.h" + +WSLServer::PendingPeer::PendingPeer() { + time = 0; + has_request = false; + response_sent = 0; + req_pos = 0; + memset(req_buf, 0, sizeof(req_buf)); +} + +bool WSLServer::PendingPeer::_parse_request(String &r_key) { + Vector<String> psa = String((char *)req_buf).split("\r\n"); + int len = psa.size(); + if (len < 4) { + ERR_EXPLAIN("Not enough response headers."); + ERR_FAIL_V(false); + } + + Vector<String> req = psa[0].split(" ", false); + if (req.size() < 2) { + ERR_EXPLAIN("Invalid protocol or status code."); + ERR_FAIL_V(false); + } + // Wrong protocol + if (req[0] != "GET" || req[2] != "HTTP/1.1") { + ERR_EXPLAIN("Invalid method or HTTP version."); + ERR_FAIL_V(false); + } + + Map<String, String> headers; + for (int i = 1; i < len; i++) { + Vector<String> header = psa[i].split(":", false, 1); + if (header.size() != 2) { + ERR_EXPLAIN("Invalid header -> " + psa[i]); + ERR_FAIL_V(false); + } + String name = header[0].to_lower(); + String value = header[1].strip_edges(); + if (headers.has(name)) + headers[name] += "," + value; + else + headers[name] = value; + } +#define _WLS_CHECK(NAME, VALUE) \ + ERR_EXPLAIN("Missing or invalid header '" + String(NAME) + "'. Expected value '" + VALUE + "'"); \ + ERR_FAIL_COND_V(!headers.has(NAME) || headers[NAME].to_lower() != VALUE, false); +#define _WLS_CHECK_EX(NAME) \ + ERR_EXPLAIN("Missing header '" + String(NAME) + "'."); \ + ERR_FAIL_COND_V(!headers.has(NAME), false); + _WLS_CHECK("upgrade", "websocket"); + _WLS_CHECK("sec-websocket-version", "13"); + _WLS_CHECK_EX("sec-websocket-key"); + _WLS_CHECK_EX("connection"); +#undef _WLS_CHECK_EX +#undef _WLS_CHECK + r_key = headers["sec-websocket-key"]; + return true; +} + +Error WSLServer::PendingPeer::do_handshake() { + if (OS::get_singleton()->get_ticks_msec() - time > WSL_SERVER_TIMEOUT) + return ERR_TIMEOUT; + if (!has_request) { + int read = 0; + while (true) { + if (req_pos >= WSL_MAX_HEADER_SIZE) { + // Header is too big + ERR_EXPLAIN("Response headers too big"); + ERR_FAIL_V(ERR_OUT_OF_MEMORY); + } + Error err = connection->get_partial_data(&req_buf[req_pos], 1, read); + if (err != OK) // Got an error + return FAILED; + else if (read != 1) // Busy, wait next poll + return ERR_BUSY; + char *r = (char *)req_buf; + int l = req_pos; + if (l > 3 && r[l] == '\n' && r[l - 1] == '\r' && r[l - 2] == '\n' && r[l - 3] == '\r') { + r[l - 3] = '\0'; + if (!_parse_request(key)) { + return FAILED; + } + String s = "HTTP/1.1 101 Switching Protocols\r\n"; + s += "Upgrade: websocket\r\n"; + s += "Connection: Upgrade\r\n"; + s += "Sec-WebSocket-Accept: " + WSLPeer::compute_key_response(key) + "\r\n"; + s += "\r\n"; + response = s.utf8(); + has_request = true; + break; + } + req_pos += 1; + } + } + if (has_request && response_sent < response.size() - 1) { + int sent = 0; + Error err = connection->put_partial_data((const uint8_t *)response.get_data() + response_sent, response.size() - response_sent - 1, sent); + if (err != OK) { + return err; + } + response_sent += sent; + } + if (response_sent < response.size() - 1) + return ERR_BUSY; + return OK; +} + +Error WSLServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { + ERR_FAIL_COND_V(is_listening(), ERR_ALREADY_IN_USE); + + _is_multiplayer = gd_mp_api; + _server->listen(p_port); + + return OK; +} + +void WSLServer::poll() { + + List<int> remove_ids; + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + Ref<WSLPeer> peer = (WSLPeer *)E->get().ptr(); + peer->poll(); + if (!peer->is_connected_to_host()) { + _on_disconnect(E->key(), peer->close_code != -1); + remove_ids.push_back(E->key()); + } + } + for (List<int>::Element *E = remove_ids.front(); E; E = E->next()) { + _peer_map.erase(E->get()); + } + remove_ids.clear(); + + List<Ref<PendingPeer> > remove_peers; + for (List<Ref<PendingPeer> >::Element *E = _pending.front(); E; E = E->next()) { + Ref<PendingPeer> ppeer = E->get(); + Error err = ppeer->do_handshake(); + if (err == ERR_BUSY) { + continue; + } else if (err != OK) { + remove_peers.push_back(ppeer); + continue; + } + // Creating new peer + int32_t id = _gen_unique_id(); + + WSLPeer::PeerData *data = memnew(struct WSLPeer::PeerData); + data->obj = this; + data->conn = ppeer->connection; + data->is_server = true; + data->id = id; + + Ref<WSLPeer> ws_peer = memnew(WSLPeer); + ws_peer->make_context(data, _in_buf_size, _in_pkt_size, _out_buf_size, _out_pkt_size); + + _peer_map[id] = ws_peer; + remove_peers.push_back(ppeer); + _on_connect(id, ""); + } + for (List<Ref<PendingPeer> >::Element *E = remove_peers.front(); E; E = E->next()) { + _pending.erase(E->get()); + } + remove_peers.clear(); + + if (!_server->is_listening()) + return; + + while (_server->is_connection_available()) { + Ref<StreamPeer> conn = _server->take_connection(); + if (is_refusing_new_connections()) + continue; // Conn will go out-of-scope and be closed. + + Ref<PendingPeer> peer = memnew(PendingPeer); + peer->connection = conn; + peer->time = OS::get_singleton()->get_ticks_msec(); + _pending.push_back(peer); + } +} + +bool WSLServer::is_listening() const { + return _server->is_listening(); +} + +int WSLServer::get_max_packet_size() const { + return (1 << _out_buf_size) - PROTO_SIZE; +} + +void WSLServer::stop() { + _server->stop(); + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + Ref<WSLPeer> peer = (WSLPeer *)E->get().ptr(); + peer->close_now(); + } + _pending.clear(); + _peer_map.clear(); +} + +bool WSLServer::has_peer(int p_id) const { + return _peer_map.has(p_id); +} + +Ref<WebSocketPeer> WSLServer::get_peer(int p_id) const { + ERR_FAIL_COND_V(!has_peer(p_id), NULL); + return _peer_map[p_id]; +} + +IP_Address WSLServer::get_peer_address(int p_peer_id) const { + ERR_FAIL_COND_V(!has_peer(p_peer_id), IP_Address()); + + return _peer_map[p_peer_id]->get_connected_host(); +} + +int WSLServer::get_peer_port(int p_peer_id) const { + ERR_FAIL_COND_V(!has_peer(p_peer_id), 0); + + return _peer_map[p_peer_id]->get_connected_port(); +} + +void WSLServer::disconnect_peer(int p_peer_id, int p_code, String p_reason) { + ERR_FAIL_COND(!has_peer(p_peer_id)); + + get_peer(p_peer_id)->close(p_code, p_reason); +} + +Error WSLServer::set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets) { + ERR_EXPLAIN("Buffers sizes can only be set before listening or connecting"); + ERR_FAIL_COND_V(_server->is_listening(), FAILED); + + _in_buf_size = nearest_shift(p_in_buffer - 1) + 10; + _in_pkt_size = nearest_shift(p_in_packets - 1); + _out_buf_size = nearest_shift(p_out_buffer - 1) + 10; + _out_pkt_size = nearest_shift(p_out_packets - 1); + return OK; +} + +WSLServer::WSLServer() { + _in_buf_size = nearest_shift((int)GLOBAL_GET(WSS_IN_BUF) - 1) + 10; + _in_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_IN_PKT) - 1); + _out_buf_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_BUF) - 1) + 10; + _out_pkt_size = nearest_shift((int)GLOBAL_GET(WSS_OUT_PKT) - 1); + _server.instance(); +} + +WSLServer::~WSLServer() { + stop(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.h b/modules/websocket/wsl_server.h index b331852d26..b0520bd731 100644 --- a/modules/websocket/lws_server.h +++ b/modules/websocket/wsl_server.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* lws_server.h */ +/* wsl_server.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,29 +28,53 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef LWSSERVER_H -#define LWSSERVER_H +#ifndef WSLSERVER_H +#define WSLSERVER_H #ifndef JAVASCRIPT_ENABLED -#include "core/reference.h" -#include "lws_helper.h" -#include "lws_peer.h" #include "websocket_server.h" +#include "wsl_peer.h" -class LWSServer : public WebSocketServer { +#include "core/io/stream_peer_tcp.h" +#include "core/io/tcp_server.h" - GDCIIMPL(LWSServer, WebSocketServer); +#define WSL_SERVER_TIMEOUT 1000 - LWS_HELPER(LWSServer); +class WSLServer : public WebSocketServer { + + GDCIIMPL(WSLServer, WebSocketServer); private: - Map<int, Ref<LWSPeer> > peer_map; + class PendingPeer : public Reference { + + private: + bool _parse_request(String &r_key); + + public: + Ref<StreamPeer> connection; + + int time; + uint8_t req_buf[WSL_MAX_HEADER_SIZE]; + int req_pos; + String key; + bool has_request; + CharString response; + int response_sent; + + PendingPeer(); + + Error do_handshake(); + }; + int _in_buf_size; int _in_pkt_size; int _out_buf_size; int _out_pkt_size; + List<Ref<PendingPeer> > _pending; + Ref<TCP_Server> _server; + public: Error set_buffers(int p_in_buffer, int p_in_packets, int p_out_buffer, int p_out_packets); Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); @@ -62,12 +86,12 @@ public: IP_Address get_peer_address(int p_peer_id) const; int get_peer_port(int p_peer_id) const; void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = ""); - virtual void poll() { _lws_poll(); } + virtual void poll(); - LWSServer(); - ~LWSServer(); + WSLServer(); + ~WSLServer(); }; #endif // JAVASCRIPT_ENABLED -#endif // LWSSERVER_H +#endif // WSLSERVER_H |