소스 검색

Merge pull request #79708 from YuriSizov/4.0-cherrypicks

Cherry-picks for the 4.0 branch (future 4.0.4) - 3rd batch
Yuri Sizov 2 년 전
부모
커밋
cfedb0a7a6
50개의 변경된 파일779개의 추가작업 그리고 325개의 파일을 삭제
  1. 202 0
      CHANGELOG.md
  2. 3 2
      core/input/input.cpp
  3. 1 1
      core/math/basis.cpp
  4. 2 2
      doc/classes/EditorImportPlugin.xml
  5. 1 1
      doc/classes/Mutex.xml
  6. 4 0
      doc/classes/ShaderInclude.xml
  7. 5 5
      doc/classes/String.xml
  8. 20 0
      drivers/unix/net_socket_posix.cpp
  9. 1 0
      drivers/unix/net_socket_posix.h
  10. 1 0
      drivers/vulkan/vulkan_context.cpp
  11. 2 2
      editor/animation_track_editor.cpp
  12. 1 1
      editor/code_editor.cpp
  13. 2 1
      editor/editor_file_system.cpp
  14. 40 50
      editor/import/post_import_plugin_skeleton_rest_fixer.cpp
  15. 1 1
      editor/import/resource_importer_wav.cpp
  16. 41 2
      editor/plugins/tiles/tile_atlas_view.cpp
  17. 5 0
      editor/plugins/tiles/tile_atlas_view.h
  18. 5 3
      editor/plugins/tiles/tiles_editor_plugin.cpp
  19. 1 1
      modules/text_server_adv/thorvg_svg_in_ot.cpp
  20. 1 1
      modules/text_server_fb/thorvg_svg_in_ot.cpp
  21. 2 1
      platform/android/audio_driver_opensl.cpp
  22. 2 0
      platform/android/java/editor/src/main/AndroidManifest.xml
  23. 16 1
      platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt
  24. 5 0
      platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt
  25. 6 1
      platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt
  26. 8 2
      platform/android/java/lib/src/org/godotengine/godot/Godot.java
  27. 56 14
      platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java
  28. 6 4
      scene/2d/gpu_particles_2d.cpp
  29. 11 9
      scene/3d/gpu_particles_3d.cpp
  30. 1 21
      scene/3d/skeleton_ik_3d.cpp
  31. 16 0
      scene/gui/line_edit.cpp
  32. 1 0
      scene/gui/line_edit.h
  33. 7 2
      scene/gui/popup_menu.cpp
  34. 21 2
      scene/gui/range.cpp
  35. 2 0
      scene/gui/range.h
  36. 1 1
      scene/gui/slider.cpp
  37. 12 9
      scene/gui/spin_box.cpp
  38. 1 1
      scene/gui/spin_box.h
  39. 3 1
      scene/gui/text_edit.cpp
  40. 12 12
      scene/main/node.cpp
  41. 1 1
      scene/main/node.h
  42. 8 2
      scene/resources/particle_process_material.cpp
  43. 1 0
      scene/resources/tile_set.cpp
  44. 1 1
      thirdparty/README.md
  45. 10 6
      thirdparty/enet/enet/enet.h
  46. 4 0
      thirdparty/enet/godot.cpp
  47. 14 2
      thirdparty/enet/host.c
  48. 35 42
      thirdparty/enet/packet.c
  49. 54 30
      thirdparty/enet/peer.c
  50. 123 87
      thirdparty/enet/protocol.c

+ 202 - 0
CHANGELOG.md

@@ -4,6 +4,207 @@ All notable changes to this project will be documented in this file.
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## [4.0.4] - TBD
+
+See the [release announcement](https://godotengine.org/article/maintenance-release-godot-4-0-4) for details.
+
+### Added
+
+#### C#/.NET
+
+- Add version defines to help users deal with breaking changes ([GH-78270](https://github.com/godotengine/godot/pull/78270)).
+
+#### Documentation
+
+- Document GI techniques ignoring VisualInstance3D and Camera3D layers ([GH-74688](https://github.com/godotengine/godot/pull/74688)).
+- Document how to use the global animation library in GDScript ([GH-74894](https://github.com/godotengine/godot/pull/74894)).
+- Add more info on the nature of NAN ([GH-75614](https://github.com/godotengine/godot/pull/75614)).
+- Add Stretch Mode description to ProjectSettings.xml ([GH-76272](https://github.com/godotengine/godot/pull/76272)).
+- Document seamless caveats on small textures in NoiseTexture2D and NoiseTexture3D ([GH-77017](https://github.com/godotengine/godot/pull/77017)).
+- Document the database for `Input.get_joy_name()` and `Input.get_joy_guid()` ([GH-77768](https://github.com/godotengine/godot/pull/77768)).
+- Document the InitialAction enum in RenderingDevice ([GH-77882](https://github.com/godotengine/godot/pull/77882)).
+- Document ShaderInclude ([GH-78562](https://github.com/godotengine/godot/pull/78562)).
+
+#### Editor
+
+- Add an editor option to copy system info to clipboard ([GH-65902](https://github.com/godotengine/godot/pull/65902)).
+
+#### Input
+
+- Add support for DPAD Center key of Android TV remote controller ([GH-77115](https://github.com/godotengine/godot/pull/77115)).
+
+### Changed
+
+#### 2D
+
+- Make tile atlas merge dialog use filter nearest on both sides ([GH-77385](https://github.com/godotengine/godot/pull/77385)).
+- Don't create bones from empty scene ([GH-77473](https://github.com/godotengine/godot/pull/77473)).
+- Don't disable `Material` and `PlaceholderMaterial` when `disable_3d=yes` ([GH-77654](https://github.com/godotengine/godot/pull/77654)).
+- Draw materials in tile atlas view ([GH-77909](https://github.com/godotengine/godot/pull/77909)).
+
+#### Animation
+
+- Hide Animation Frames section when there are no animations ([GH-77221](https://github.com/godotengine/godot/pull/77221)).
+- Improve `Skeleton3D::find_bone()` performance ([GH-77307](https://github.com/godotengine/godot/pull/77307)).
+- Avoid mutating the same Skin multiple times ([GH-77505](https://github.com/godotengine/godot/pull/77505)).
+
+#### C#/.NET
+
+- Always decode `dotnet` output as UTF-8 ([GH-74065](https://github.com/godotengine/godot/pull/74065)).
+- Link the right build property to REAL_T_IS_DOUBLE ([GH-77198](https://github.com/godotengine/godot/pull/77198)).
+
+#### Editor
+
+- Use nearest with mipmaps texture filter in SpriteFrames editor plugin ([GH-74341](https://github.com/godotengine/godot/pull/74341)).
+- Make sure script cache is created after reimport ([GH-75798](https://github.com/godotengine/godot/pull/75798)).
+- Make SpriteFrames editor toolbar a `FlowContainer` ([GH-77034](https://github.com/godotengine/godot/pull/77034)).
+- Prevent selecting unselectable `EditorProperty` with RMB ([GH-77148](https://github.com/godotengine/godot/pull/77148)).
+- Do not translate node name when assigned to an exported field ([GH-77217](https://github.com/godotengine/godot/pull/77217)).
+- Allow up to INT32_MAX max size in Array/Dictionary editor ([GH-77225](https://github.com/godotengine/godot/pull/77225)).
+- Avoid error spam on first opening of a not yet imported project ([GH-77276](https://github.com/godotengine/godot/pull/77276)).
+- Ensure quotes are escaped when converting built-in scripts ([GH-77399](https://github.com/godotengine/godot/pull/77399)).
+- Ignore the `project_settings_override` file when in editor ([GH-77459](https://github.com/godotengine/godot/pull/77459)).
+
+#### GDScript
+
+- Treat `BitField<Enum>` as `int` (not `Enum`) ([GH-77579](https://github.com/godotengine/godot/pull/77579)).
+
+#### GUI
+
+- Stop dragging when `Slider` changes editability ([GH-77242](https://github.com/godotengine/godot/pull/77242)).
+- Use defined key mapping for closing popups and dialogs ([GH-77297](https://github.com/godotengine/godot/pull/77297)).
+- TextServer: Prevent duplicate line breaks on virtual spaces when line width is significantly smaller than character width ([GH-77514](https://github.com/godotengine/godot/pull/77514)).
+- Cancel tooltip when mouse leaves viewport ([GH-77933](https://github.com/godotengine/godot/pull/77933)).
+- Preserve selection when focusing SpinBox ([GH-78092](https://github.com/godotengine/godot/pull/78092)).
+
+#### Input
+
+- Improve touchpad and mouse support for the Android editor ([GH-77498](https://github.com/godotengine/godot/pull/77498)).
+- Skip error messages for buttons that don't exist ([GH-77748](https://github.com/godotengine/godot/pull/77748)).
+
+#### Rendering
+
+- Disable AMD switchable graphics on Windows with Vulkan to fix driver issue ([GH-73450](https://github.com/godotengine/godot/pull/73450)).
+- Take 3D resolution scaling into account for mesh LOD ([GH-77294](https://github.com/godotengine/godot/pull/77294)).
+
+#### Thirdparty
+
+- brotli updated to version ed1995b6b.
+- msdfgen updated to version 1.10.
+- recast updated to version 1.6.0.
+- tinyexr updated to version 1.0.5.
+- wslay updated to version 0e7d106ff.
+- zstd updated to version 1.5.5.
+- CA root certificates updated to 2023-06-02 bundle from Mozilla.
+
+### Fixed
+
+#### 2D
+
+- Fix crash when opening a TileSet with invalid tiles ([GH-78165](https://github.com/godotengine/godot/pull/78165)).
+- Fix crash with failed compatibility tiles ([GH-78796](https://github.com/godotengine/godot/pull/78796)).
+
+#### 3D
+
+- Fix CSGPolygon3D in path mode disappearing at runtime ([GH-77118](https://github.com/godotengine/godot/pull/77118)).
+
+#### Animation
+
+- Fix type check in AnimationTrackKeyEdit for methods ([GH-74948](https://github.com/godotengine/godot/pull/74948)).
+- Fix `AnimatedSprite3D` autoplay warning ([GH-77028](https://github.com/godotengine/godot/pull/77028)).
+- Adjust BoneAttachment3D children/meshes during rest fixer ([GH-77123](https://github.com/godotengine/godot/pull/77123)).
+- Fix `get_bone_pose_global_no_override()` returning incorrect values ([GH-77194](https://github.com/godotengine/godot/pull/77194)).
+- Fix for SkeletonIK3D interpolation and bone roll ([GH-77469](https://github.com/godotengine/godot/pull/77469)).
+- Fix AnimationPlayer cumulative `speed_scale` ([GH-77500](https://github.com/godotengine/godot/pull/77500)).
+- Fix adding bones with the same name after calling `Skeleton3D.clear_bones()` ([GH-77874](https://github.com/godotengine/godot/pull/77874)).
+
+#### Audio
+
+- Fix trim when importing WAV ([GH-75261](https://github.com/godotengine/godot/pull/75261)).
+- Fix 2D audio in multiple viewports ([GH-76713](https://github.com/godotengine/godot/pull/76713)).
+- Fix crash in AudioStream preview ([GH-77664](https://github.com/godotengine/godot/pull/77664)).
+- Fix issue causing the Android editor to crash when creating a new AudioStreamMicrophone ([GH-77686](https://github.com/godotengine/godot/pull/77686)).
+
+#### Buildsystem
+
+- CI: Fix running the unit tests on windows ([GH-76887](https://github.com/godotengine/godot/pull/76887)).
+- Linux: Fix udev fallback logic with `use_sowrap=no` ([GH-79111](https://github.com/godotengine/godot/pull/79111)).
+
+#### C#/.NET
+
+- Fix C# glue generation for enums with negative values ([GH-77018](https://github.com/godotengine/godot/pull/77018)).
+- Fix `SendToScriptDebugger` crash ([GH-77377](https://github.com/godotengine/godot/pull/77377)).
+
+#### Core
+
+- Fix `StringName` comparison ([GH-77197](https://github.com/godotengine/godot/pull/77197)).
+- Fix calling `TextureStorage::texture_3d_update()` could cause a crash ([GH-77266](https://github.com/godotengine/godot/pull/77266)).
+
+#### Editor
+
+- Fix theme of editor VCS dialogs ([GH-75983](https://github.com/godotengine/godot/pull/75983)).
+- Fix calculation bug with `TextEdit::get_line_height()` ([GH-76605](https://github.com/godotengine/godot/pull/76605)).
+- Fix Input Map key assignments missing after project conversion ([GH-77134](https://github.com/godotengine/godot/pull/77134)).
+- Fix `Window` derived nodes being unselectable for `ViewportTexture` `NodePath` ([GH-77312](https://github.com/godotengine/godot/pull/77312)).
+- Fix crash when using tile atlas merge with recreated alt tile ([GH-77382](https://github.com/godotengine/godot/pull/77382)).
+- Fix filesystem cache split error ([GH-78324](https://github.com/godotengine/godot/pull/78324)).
+- Fix saving size in `CreateDialog` ([GH-78403](https://github.com/godotengine/godot/pull/78403)).
+- Shaders: Exclude incorrect completion options for `render_mode` in shaders ([GH-77086](https://github.com/godotengine/godot/pull/77086)).
+
+#### GDScript
+
+- Add missing `script_type` `nullptr` check ([GH-75943](https://github.com/godotengine/godot/pull/75943)).
+- Fix warning ignoring for member variables ([GH-76203](https://github.com/godotengine/godot/pull/76203)).
+- Fix `validate_call_arg()` for unresolved datatype ([GH-77091](https://github.com/godotengine/godot/pull/77091)).
+- Fix extraction of chained `tr()` calls ([GH-77538](https://github.com/godotengine/godot/pull/77538)).
+
+#### GUI
+
+- Fix `Range`-derived nodes not redrawing after `set_value_no_signal` ([GH-70834](https://github.com/godotengine/godot/pull/70834)).
+- Fix adding colors to swatches not updating in previous ColorPickers ([GH-76751](https://github.com/godotengine/godot/pull/76751)).
+- Fix crash when changing node type from PopupMenu to ItemList ([GH-76854](https://github.com/godotengine/godot/pull/76854)).
+- Fix `ItemList` item text positions in RTL mode ([GH-77166](https://github.com/godotengine/godot/pull/77166)).
+- Fix crash when selecting lines in text edit ([GH-77667](https://github.com/godotengine/godot/pull/77667)).
+- Fix SVG font rendering after ThorVG update ([GH-77942](https://github.com/godotengine/godot/pull/77942)).
+- Fix disabled slider highlighting ([GH-78776](https://github.com/godotengine/godot/pull/78776)).
+
+#### Input
+
+- Fix errors that appear while reordering input map entries ([GH-77009](https://github.com/godotengine/godot/pull/77009)).
+- Fix spatial viewport multitouch detection support ([GH-78083](https://github.com/godotengine/godot/pull/78083)).
+
+#### Navigation
+
+- Fix agent avoidance position not updated when entering SceneTree ([GH-77110](https://github.com/godotengine/godot/pull/77110)).
+
+#### Networking
+
+- Fix HTTPClient `_request` using wrong size ([GH-75867](https://github.com/godotengine/godot/pull/75867)).
+- ENet: Better handle truncated socket messages ([GH-79699](https://github.com/godotengine/godot/pull/79699)).
+
+#### Particles
+
+- Correctly reset particle size and rotation in ParticlesProcessMaterial ([GH-78021](https://github.com/godotengine/godot/pull/78021)).
+- Avoid error spam when (un)pausing GPUParticles out of tree ([GH-78143](https://github.com/godotengine/godot/pull/78143)).
+
+#### Physics
+
+- Fix width and center position of `CapsuleShape2D::get_rect` ([GH-77065](https://github.com/godotengine/godot/pull/77065)).
+
+#### Porting
+
+- Android: Set pending intent flag to stop insta-crash ([GH-78175](https://github.com/godotengine/godot/pull/78175)).
+- Windows: Fix for Win+M crashing the editor ([GH-78235](https://github.com/godotengine/godot/pull/78235)).
+
+#### Rendering
+
+- Fix typo in FinalAction `switch` statement in RenderingDevice ([GH-75945](https://github.com/godotengine/godot/pull/75945)).
+- Fix modulation propagation for Y-sorted CanvasItems ([GH-77079](https://github.com/godotengine/godot/pull/77079)).
+- Fix LightmapGI dynamic object lighting ([GH-77089](https://github.com/godotengine/godot/pull/77089)).
+- Fix calculation of skinned AABB for unused bones ([GH-77265](https://github.com/godotengine/godot/pull/77265)).
+- Fix uninitialized Y-sort modulate for CanvasItems ([GH-78134](https://github.com/godotengine/godot/pull/78134)).
+
+
 ## [4.0.3] - 2023-05-19
 
 See the [release announcement](https://godotengine.org/article/maintenance-release-godot-4-0-3) for details.
@@ -3106,6 +3307,7 @@ See the [release announcement](https://godotengine.org/article/godot-3-3-has-arr
   - Only WebAssembly is supported now, since all browsers supporting WebGL 2.0 also support WebAssembly.
 
 
+[4.0.4]: https://github.com/godotengine/godot/compare/4.0.3-stable...4.0.4-stable
 [4.0.3]: https://github.com/godotengine/godot/compare/4.0.2-stable...4.0.3-stable
 [4.0.2]: https://github.com/godotengine/godot/compare/4.0.1-stable...4.0.2-stable
 [4.0.1]: https://github.com/godotengine/godot/compare/4.0-stable...4.0.1-stable

+ 3 - 2
core/input/input.cpp

@@ -1354,8 +1354,9 @@ void Input::parse_mapping(String p_mapping) {
 
 		String output = entry[idx].get_slice(":", 0).replace(" ", "");
 		String input = entry[idx].get_slice(":", 1).replace(" ", "");
-		ERR_CONTINUE_MSG(output.length() < 1 || input.length() < 2,
-				vformat("Invalid device mapping entry \"%s\" in mapping:\n%s", entry[idx], p_mapping));
+		if (output.length() < 1 || input.length() < 2) {
+			continue;
+		}
 
 		if (output == "platform" || output == "hint") {
 			continue;

+ 1 - 1
core/math/basis.cpp

@@ -397,7 +397,7 @@ void Basis::rotate_to_align(Vector3 p_start_direction, Vector3 p_end_direction)
 		real_t dot = p_start_direction.dot(p_end_direction);
 		dot = CLAMP(dot, -1.0f, 1.0f);
 		const real_t angle_rads = Math::acos(dot);
-		set_axis_angle(axis, angle_rads);
+		*this = Basis(axis, angle_rads) * (*this);
 	}
 }
 

+ 2 - 2
doc/classes/EditorImportPlugin.xml

@@ -30,10 +30,10 @@
 		func _get_preset_count():
 		    return 1
 
-		func _get_preset_name(i):
+		func _get_preset_name(preset_index):
 		    return "Default"
 
-		func _get_import_options(i):
+		func _get_import_options(path, preset_index):
 		    return [{"name": "my_option", "default_value": false}]
 
 		func _import(source_file, save_path, options, platform_variants, gen_files):

+ 1 - 1
doc/classes/Mutex.xml

@@ -22,7 +22,7 @@
 			<return type="bool" />
 			<description>
 				Tries locking this [Mutex], but does not block. Returns [code]true[/code] on success, [code]false[/code] otherwise.
-				[b]Note:[/b] This function returns [constant OK] if the thread already has ownership of the mutex.
+				[b]Note:[/b] This function returns [code]true[/code] if the thread already has ownership of the mutex.
 			</description>
 		</method>
 		<method name="unlock">

+ 4 - 0
doc/classes/ShaderInclude.xml

@@ -1,13 +1,17 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 <class name="ShaderInclude" inherits="Resource" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
 	<brief_description>
+		A snippet of shader code to be included in a [Shader] with [code]#include[/code].
 	</brief_description>
 	<description>
+		A shader include file, saved with the [code].gdshaderinc[/code] extension. This class allows you to define a custom shader snippet that can be included in a [Shader] by using the preprocessor directive [code]#include[/code], followed by the file path (e.g. [code]#include "res://shader_lib.gdshaderinc"[/code]). The snippet doesn't have to be a valid shader on its own.
 	</description>
 	<tutorials>
+		<link title="Shader preprocessor">$DOCS_URL/tutorials/shaders/shader_reference/shader_preprocessor.html</link>
 	</tutorials>
 	<members>
 		<member name="code" type="String" setter="set_code" getter="get_code" default="&quot;&quot;">
+			Returns the code of the shader include file. The returned text is what the user has written, not the full generated code used internally.
 		</member>
 	</members>
 </class>

+ 5 - 5
doc/classes/String.xml

@@ -884,11 +884,11 @@
 			<description>
 				Converts the string representing a decimal number into a [float]. This method stops on the first non-number character, except the first decimal point ([code].[/code]) and the exponent letter ([code]e[/code]). See also [method is_valid_float].
 				[codeblock]
-				var a = "12.35".to_float() # a is 12.35
-				var b = "1.2.3".to_float() # b is 1.2
-				var c = "12xy3".to_float() # c is 12.0
-				var d = "1e3".to_float()   # d is 1000.0
-				var e = "Hello!".to_int()  # e is 0.0
+				var a = "12.35".to_float()  # a is 12.35
+				var b = "1.2.3".to_float()  # b is 1.2
+				var c = "12xy3".to_float()  # c is 12.0
+				var d = "1e3".to_float()    # d is 1000.0
+				var e = "Hello!".to_float() # e is 0.0
 				[/codeblock]
 			</description>
 		</method>

+ 20 - 0
drivers/unix/net_socket_posix.cpp

@@ -204,6 +204,9 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const {
 	if (err == WSAEACCES) {
 		return ERR_NET_UNAUTHORIZED;
 	}
+	if (err == WSAEMSGSIZE || err == WSAENOBUFS) {
+		return ERR_NET_BUFFER_TOO_SMALL;
+	}
 	print_verbose("Socket error: " + itos(err));
 	return ERR_NET_OTHER;
 #else
@@ -222,6 +225,9 @@ NetSocketPosix::NetError NetSocketPosix::_get_socket_error() const {
 	if (errno == EACCES) {
 		return ERR_NET_UNAUTHORIZED;
 	}
+	if (errno == ENOBUFS) {
+		return ERR_NET_BUFFER_TOO_SMALL;
+	}
 	print_verbose("Socket error: " + itos(errno));
 	return ERR_NET_OTHER;
 #endif
@@ -550,6 +556,10 @@ Error NetSocketPosix::recv(uint8_t *p_buffer, int p_len, int &r_read) {
 			return ERR_BUSY;
 		}
 
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
+
 		return FAILED;
 	}
 
@@ -571,6 +581,10 @@ Error NetSocketPosix::recvfrom(uint8_t *p_buffer, int p_len, int &r_read, IPAddr
 			return ERR_BUSY;
 		}
 
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
+
 		return FAILED;
 	}
 
@@ -606,6 +620,9 @@ Error NetSocketPosix::send(const uint8_t *p_buffer, int p_len, int &r_sent) {
 		if (err == ERR_NET_WOULD_BLOCK) {
 			return ERR_BUSY;
 		}
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
 
 		return FAILED;
 	}
@@ -625,6 +642,9 @@ Error NetSocketPosix::sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IP
 		if (err == ERR_NET_WOULD_BLOCK) {
 			return ERR_BUSY;
 		}
+		if (err == ERR_NET_BUFFER_TOO_SMALL) {
+			return ERR_OUT_OF_MEMORY;
+		}
 
 		return FAILED;
 	}

+ 1 - 0
drivers/unix/net_socket_posix.h

@@ -56,6 +56,7 @@ private:
 		ERR_NET_IN_PROGRESS,
 		ERR_NET_ADDRESS_INVALID_OR_UNAVAILABLE,
 		ERR_NET_UNAUTHORIZED,
+		ERR_NET_BUFFER_TOO_SMALL,
 		ERR_NET_OTHER,
 	};
 

+ 1 - 0
drivers/vulkan/vulkan_context.cpp

@@ -2276,6 +2276,7 @@ Error VulkanContext::prepare_buffers() {
 				// presentation engine will still present the image correctly.
 				print_verbose("Vulkan: Early suboptimal swapchain, recreating.");
 				_update_swap_chain(w);
+				break;
 			} else if (err != VK_SUCCESS) {
 				ERR_BREAK_MSG(err != VK_SUCCESS, "Vulkan: Did not create swapchain successfully. Error code: " + String(string_VkResult(err)));
 			} else {

+ 2 - 2
editor/animation_track_editor.cpp

@@ -201,7 +201,7 @@ bool AnimationTrackKeyEdit::_set(const StringName &p_name, const Variant &p_valu
 
 					if (t != args[idx].get_type()) {
 						Callable::CallError err;
-						if (Variant::can_convert(args[idx].get_type(), t)) {
+						if (Variant::can_convert_strict(args[idx].get_type(), t)) {
 							Variant old = args[idx];
 							Variant *ptrs[1] = { &old };
 							Variant::construct(t, args.write[idx], (const Variant **)ptrs, 1, err);
@@ -786,7 +786,7 @@ bool AnimationMultiTrackKeyEdit::_set(const StringName &p_name, const Variant &p
 
 							if (t != args[idx].get_type()) {
 								Callable::CallError err;
-								if (Variant::can_convert(args[idx].get_type(), t)) {
+								if (Variant::can_convert_strict(args[idx].get_type(), t)) {
 									Variant old = args[idx];
 									Variant *ptrs[1] = { &old };
 									Variant::construct(t, args.write[idx], (const Variant **)ptrs, 1, err);

+ 1 - 1
editor/code_editor.cpp

@@ -408,7 +408,7 @@ void FindReplaceBar::_update_matches_label() {
 		matches_label->add_theme_color_override("font_color", results_count > 0 ? get_theme_color(SNAME("font_color"), SNAME("Label")) : get_theme_color(SNAME("error_color"), SNAME("Editor")));
 
 		if (results_count == 0) {
-			matches_label->set_text("No match");
+			matches_label->set_text(TTR("No match"));
 		} else if (results_count_to_current == -1) {
 			matches_label->set_text(vformat(TTRN("%d match", "%d matches", results_count), results_count));
 		} else {

+ 2 - 1
editor/editor_file_system.cpp

@@ -262,7 +262,7 @@ void EditorFileSystem::_scan_filesystem() {
 
 				} else {
 					Vector<String> split = l.split("::");
-					ERR_CONTINUE(split.size() != 9);
+					ERR_CONTINUE(split.size() < 9);
 					String name = split[0];
 					String file;
 
@@ -2328,6 +2328,7 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
 	ResourceUID::get_singleton()->update_cache(); // After reimporting, update the cache.
 
 	_save_filesystem_cache();
+	_update_pending_script_classes();
 	importing = false;
 	if (!is_scanning()) {
 		emit_signal(SNAME("filesystem_changed"));

+ 40 - 50
editor/import/post_import_plugin_skeleton_rest_fixer.cpp

@@ -31,6 +31,7 @@
 #include "post_import_plugin_skeleton_rest_fixer.h"
 
 #include "editor/import/scene_import_settings.h"
+#include "scene/3d/bone_attachment_3d.h"
 #include "scene/3d/importer_mesh_instance_3d.h"
 #include "scene/3d/skeleton_3d.h"
 #include "scene/animation/animation_player.h"
@@ -105,42 +106,6 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
 			global_transform.origin = Vector3(); // Translation by a Node is not a bone animation, so the retargeted model should be at the origin.
 		}
 
-		// Calc IBM difference.
-		LocalVector<Vector<Transform3D>> ibm_diffs;
-		{
-			TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
-			while (nodes.size()) {
-				ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
-				ERR_CONTINUE(!mi);
-
-				Ref<Skin> skin = mi->get_skin();
-				ERR_CONTINUE(!skin.is_valid());
-
-				Node *node = mi->get_node(mi->get_skeleton_path());
-				ERR_CONTINUE(!node);
-
-				Skeleton3D *mesh_skeleton = Object::cast_to<Skeleton3D>(node);
-				if (!mesh_skeleton || mesh_skeleton != src_skeleton) {
-					continue;
-				}
-
-				Vector<Transform3D> ibm_diff;
-				ibm_diff.resize(src_skeleton->get_bone_count());
-				Transform3D *ibm_diff_w = ibm_diff.ptrw();
-
-				int skin_len = skin->get_bind_count();
-				for (int i = 0; i < skin_len; i++) {
-					StringName bn = skin->get_bind_name(i);
-					int bone_idx = src_skeleton->find_bone(bn);
-					if (bone_idx >= 0) {
-						ibm_diff_w[bone_idx] = global_transform * src_skeleton->get_bone_global_rest(bone_idx) * skin->get_bind_pose(i);
-					}
-				}
-
-				ibm_diffs.push_back(ibm_diff);
-			}
-		}
-
 		// Apply node transforms.
 		if (bool(p_options["retarget/rest_fixer/apply_node_transforms"])) {
 			Vector3 scl = global_transform.basis.get_scale_local();
@@ -288,12 +253,11 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
 		Vector<Transform3D> silhouette_diff; // Transform values to be ignored when overwrite axis.
 		silhouette_diff.resize(src_skeleton->get_bone_count());
 		Transform3D *silhouette_diff_w = silhouette_diff.ptrw();
+		LocalVector<Transform3D> pre_silhouette_skeleton_global_rest;
+		for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
+			pre_silhouette_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
+		}
 		if (bool(p_options["retarget/rest_fixer/fix_silhouette/enable"])) {
-			LocalVector<Transform3D> old_skeleton_global_rest;
-			for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
-				old_skeleton_global_rest.push_back(src_skeleton->get_bone_global_rest(i));
-			}
-
 			Vector<int> bones_to_process = prof_skeleton->get_parentless_bones();
 			while (bones_to_process.size() > 0) {
 				int prof_idx = bones_to_process[0];
@@ -450,7 +414,7 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
 
 			// For skin modification in overwrite rest.
 			for (int i = 0; i < src_skeleton->get_bone_count(); i++) {
-				silhouette_diff_w[i] = old_skeleton_global_rest[i] * src_skeleton->get_bone_global_rest(i).inverse();
+				silhouette_diff_w[i] = pre_silhouette_skeleton_global_rest[i] * src_skeleton->get_bone_global_rest(i).affine_inverse();
 			}
 
 			is_rest_changed = true;
@@ -645,14 +609,20 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
 		if (is_rest_changed) {
 			// Fix skin.
 			{
+				HashSet<Ref<Skin>> mutated_skins;
 				TypedArray<Node> nodes = p_base_scene->find_children("*", "ImporterMeshInstance3D");
-				int skin_idx = 0;
 				while (nodes.size()) {
 					ImporterMeshInstance3D *mi = Object::cast_to<ImporterMeshInstance3D>(nodes.pop_back());
 					ERR_CONTINUE(!mi);
 
 					Ref<Skin> skin = mi->get_skin();
-					ERR_CONTINUE(!skin.is_valid());
+					if (skin.is_null()) {
+						continue;
+					}
+					if (mutated_skins.has(skin)) {
+						continue;
+					}
+					mutated_skins.insert(skin);
 
 					Node *node = mi->get_node(mi->get_skeleton_path());
 					ERR_CONTINUE(!node);
@@ -662,19 +632,39 @@ void PostImportPluginSkeletonRestFixer::internal_process(InternalImportCategory
 						continue;
 					}
 
-					Vector<Transform3D> ibm_diff = ibm_diffs[skin_idx];
-
 					int skin_len = skin->get_bind_count();
 					for (int i = 0; i < skin_len; i++) {
 						StringName bn = skin->get_bind_name(i);
 						int bone_idx = src_skeleton->find_bone(bn);
 						if (bone_idx >= 0) {
-							Transform3D new_rest = silhouette_diff[bone_idx] * src_skeleton->get_bone_global_rest(bone_idx);
-							skin->set_bind_pose(i, new_rest.inverse() * ibm_diff[bone_idx]);
+							Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
+							adjust_transform.scale(global_transform.basis.get_scale_local());
+							skin->set_bind_pose(i, adjust_transform * skin->get_bind_pose(i));
 						}
 					}
-
-					skin_idx++;
+				}
+				nodes = src_skeleton->get_children();
+				while (nodes.size()) {
+					BoneAttachment3D *attachment = Object::cast_to<BoneAttachment3D>(nodes.pop_back());
+					if (attachment == nullptr) {
+						continue;
+					}
+					int bone_idx = attachment->get_bone_idx();
+					if (bone_idx == -1) {
+						bone_idx = src_skeleton->find_bone(attachment->get_bone_name());
+					}
+					ERR_CONTINUE(bone_idx < 0 || bone_idx >= src_skeleton->get_bone_count());
+					Transform3D adjust_transform = src_skeleton->get_bone_global_rest(bone_idx).affine_inverse() * silhouette_diff[bone_idx].affine_inverse() * pre_silhouette_skeleton_global_rest[bone_idx];
+					adjust_transform.scale(global_transform.basis.get_scale_local());
+
+					TypedArray<Node> child_nodes = attachment->get_children();
+					while (child_nodes.size()) {
+						Node3D *child = Object::cast_to<Node3D>(child_nodes.pop_back());
+						if (child == nullptr) {
+							continue;
+						}
+						child->set_transform(adjust_transform * child->get_transform());
+					}
 				}
 			}
 

+ 1 - 1
editor/import/resource_importer_wav.cpp

@@ -386,7 +386,7 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
 
 	bool trim = p_options["edit/trim"];
 
-	if (trim && (loop_mode != AudioStreamWAV::LOOP_DISABLED) && format_channels > 0) {
+	if (trim && (loop_mode == AudioStreamWAV::LOOP_DISABLED) && format_channels > 0) {
 		int first = 0;
 		int last = (frames / format_channels) - 1;
 		bool found = false;

+ 41 - 2
editor/plugins/tiles/tile_atlas_view.cpp

@@ -244,13 +244,16 @@ void TileAtlasView::_draw_base_tiles() {
 		for (int i = 0; i < tile_set_atlas_source->get_tiles_count(); i++) {
 			Vector2i atlas_coords = tile_set_atlas_source->get_tile_id(i);
 
+			// Different materials need to be drawn with different CanvasItems.
+			RID ci_rid = _get_canvas_item_to_draw(tile_set_atlas_source->get_tile_data(atlas_coords, 0), base_tiles_draw, material_tiles_draw);
+
 			for (int frame = 0; frame < tile_set_atlas_source->get_tile_animation_frames_count(atlas_coords); frame++) {
 				// Update the y to max value.
 				Rect2i base_frame_rect = tile_set_atlas_source->get_tile_texture_region(atlas_coords, frame);
 				Vector2 offset_pos = Rect2(base_frame_rect).get_center() + Vector2(tile_set_atlas_source->get_tile_data(atlas_coords, 0)->get_texture_origin());
 
 				// Draw the tile.
-				TileMap::draw_tile(base_tiles_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, 0, frame);
+				TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, 0, frame);
 			}
 		}
 
@@ -286,6 +289,33 @@ void TileAtlasView::_draw_base_tiles() {
 	}
 }
 
+RID TileAtlasView::_get_canvas_item_to_draw(const TileData *p_for_data, const CanvasItem *p_base_item, HashMap<Ref<Material>, RID> &p_material_map) {
+	Ref<Material> mat = p_for_data->get_material();
+	if (mat.is_null()) {
+		return p_base_item->get_canvas_item();
+	} else if (p_material_map.has(mat)) {
+		return p_material_map[mat];
+	} else {
+		RID ci_rid = RS::get_singleton()->canvas_item_create();
+		RS::get_singleton()->canvas_item_set_parent(ci_rid, p_base_item->get_canvas_item());
+		RS::get_singleton()->canvas_item_set_material(ci_rid, mat->get_rid());
+		p_material_map[mat] = ci_rid;
+		return ci_rid;
+	}
+}
+
+void TileAtlasView::_clear_material_canvas_items() {
+	for (KeyValue<Ref<Material>, RID> kv : material_tiles_draw) {
+		RS::get_singleton()->free(kv.value);
+	}
+	material_tiles_draw.clear();
+
+	for (KeyValue<Ref<Material>, RID> kv : material_alternatives_draw) {
+		RS::get_singleton()->free(kv.value);
+	}
+	material_alternatives_draw.clear();
+}
+
 void TileAtlasView::_draw_base_tiles_texture_grid() {
 	Ref<Texture2D> texture = tile_set_atlas_source->get_texture();
 	if (texture.is_valid()) {
@@ -370,6 +400,9 @@ void TileAtlasView::_draw_alternatives() {
 				TileData *tile_data = tile_set_atlas_source->get_tile_data(atlas_coords, alternative_id);
 				bool transposed = tile_data->get_transpose();
 
+				// Different materials need to be drawn with different CanvasItems.
+				RID ci_rid = _get_canvas_item_to_draw(tile_data, alternatives_draw, material_alternatives_draw);
+
 				// Update the y to max value.
 				Vector2i offset_pos;
 				if (transposed) {
@@ -381,7 +414,7 @@ void TileAtlasView::_draw_alternatives() {
 				}
 
 				// Draw the tile.
-				TileMap::draw_tile(alternatives_draw->get_canvas_item(), offset_pos, tile_set, source_id, atlas_coords, alternative_id);
+				TileMap::draw_tile(ci_rid, offset_pos, tile_set, source_id, atlas_coords, alternative_id);
 
 				// Increment the x position.
 				current_pos.x += transposed ? texture_region_size.y : texture_region_size.x;
@@ -407,6 +440,8 @@ void TileAtlasView::set_atlas_source(TileSet *p_tile_set, TileSetAtlasSource *p_
 	tile_set = p_tile_set;
 	tile_set_atlas_source = p_tile_set_atlas_source;
 
+	_clear_material_canvas_items();
+
 	if (!tile_set) {
 		return;
 	}
@@ -690,3 +725,7 @@ TileAtlasView::TileAtlasView() {
 	alternatives_draw->connect("draw", callable_mp(this, &TileAtlasView::_draw_alternatives));
 	alternative_tiles_drawing_root->add_child(alternatives_draw);
 }
+
+TileAtlasView::~TileAtlasView() {
+	_clear_material_canvas_items();
+}

+ 5 - 0
editor/plugins/tiles/tile_atlas_view.h

@@ -89,7 +89,11 @@ private:
 	Control *base_tiles_drawing_root = nullptr;
 
 	Control *base_tiles_draw = nullptr;
+	HashMap<Ref<Material>, RID> material_tiles_draw;
+	HashMap<Ref<Material>, RID> material_alternatives_draw;
 	void _draw_base_tiles();
+	RID _get_canvas_item_to_draw(const TileData *p_for_data, const CanvasItem *p_base_item, HashMap<Ref<Material>, RID> &p_material_map);
+	void _clear_material_canvas_items();
 
 	Control *base_tiles_texture_grid = nullptr;
 	void _draw_base_tiles_texture_grid();
@@ -157,6 +161,7 @@ public:
 	void queue_redraw();
 
 	TileAtlasView();
+	~TileAtlasView();
 };
 
 #endif // TILE_ATLAS_VIEW_H

+ 5 - 3
editor/plugins/tiles/tiles_editor_plugin.cpp

@@ -106,9 +106,11 @@ void TilesEditorPlugin::_thread() {
 						Vector2i coords = tile_map->get_cell_atlas_coords(0, cell);
 						int alternative = tile_map->get_cell_alternative_tile(0, cell);
 
-						Vector2 center = world_pos - atlas_source->get_tile_data(coords, alternative)->get_texture_origin();
-						encompassing_rect.expand_to(center - atlas_source->get_tile_texture_region(coords).size / 2);
-						encompassing_rect.expand_to(center + atlas_source->get_tile_texture_region(coords).size / 2);
+						if (atlas_source->has_tile(coords) && atlas_source->has_alternative_tile(coords, alternative)) {
+							Vector2 center = world_pos - atlas_source->get_tile_data(coords, alternative)->get_texture_origin();
+							encompassing_rect.expand_to(center - atlas_source->get_tile_texture_region(coords).size / 2);
+							encompassing_rect.expand_to(center + atlas_source->get_tile_texture_region(coords).size / 2);
+						}
 					}
 				}
 

+ 1 - 1
modules/text_server_adv/thorvg_svg_in_ot.cpp

@@ -126,7 +126,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
 				xml_body += vformat("</%s>", parser->get_node_name());
 			}
 		}
-		String temp_xml_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body;
+		String temp_xml_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1 1\">" + xml_body;
 		CharString temp_xml = temp_xml_str.utf8();
 
 		std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();

+ 1 - 1
modules/text_server_fb/thorvg_svg_in_ot.cpp

@@ -126,7 +126,7 @@ FT_Error tvg_svg_in_ot_preset_slot(FT_GlyphSlot p_slot, FT_Bool p_cache, FT_Poin
 				xml_body += vformat("</%s>", parser->get_node_name());
 			}
 		}
-		String temp_xml_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 0 0\">" + xml_body;
+		String temp_xml_str = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1 1\">" + xml_body;
 		CharString temp_xml = temp_xml_str.utf8();
 
 		std::unique_ptr<tvg::Picture> picture = tvg::Picture::gen();

+ 2 - 1
platform/android/audio_driver_opensl.cpp

@@ -272,7 +272,8 @@ Error AudioDriverOpenSL::input_start() {
 		return init_input_device();
 	}
 
-	return OK;
+	WARN_PRINT("Unable to start audio capture - No RECORD_AUDIO permission");
+	return ERR_UNAUTHORIZED;
 }
 
 Error AudioDriverOpenSL::input_stop() {

+ 2 - 0
platform/android/java/editor/src/main/AndroidManifest.xml

@@ -21,6 +21,8 @@
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
         android:maxSdkVersion="29"/>
     <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.VIBRATE" />
 
     <application
         android:allowBackup="false"

+ 16 - 1
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotEditor.kt

@@ -81,7 +81,9 @@ open class GodotEditor : FullScreenGodotApp() {
 	private val commandLineParams = ArrayList<String>()
 
 	override fun onCreate(savedInstanceState: Bundle?) {
-		PermissionsUtil.requestManifestPermissions(this)
+		// We exclude certain permissions from the set we request at startup, as they'll be
+		// requested on demand based on use-cases.
+		PermissionsUtil.requestManifestPermissions(this, setOf(Manifest.permission.RECORD_AUDIO))
 
 		val params = intent.getStringArrayExtra(COMMAND_LINE_PARAMS)
 		updateCommandLineParams(params)
@@ -98,6 +100,8 @@ open class GodotEditor : FullScreenGodotApp() {
 		val longPressEnabled = enableLongPressGestures()
 		val panScaleEnabled = enablePanAndScaleGestures()
 
+		checkForProjectPermissionsToEnable()
+
 		runOnUiThread {
 			// Enable long press, panning and scaling gestures
 			godotFragment?.renderView?.inputHandler?.apply {
@@ -107,6 +111,17 @@ open class GodotEditor : FullScreenGodotApp() {
 		}
 	}
 
+	/**
+	 * Check for project permissions to enable
+	 */
+	protected open fun checkForProjectPermissionsToEnable() {
+		// Check for RECORD_AUDIO permission
+		val audioInputEnabled = java.lang.Boolean.parseBoolean(GodotLib.getGlobal("audio/driver/enable_input"));
+		if (audioInputEnabled) {
+			PermissionsUtil.requestPermission(Manifest.permission.RECORD_AUDIO, this)
+		}
+	}
+
 	private fun updateCommandLineParams(args: Array<String>?) {
 		// Update the list of command line params with the new args
 		commandLineParams.clear()

+ 5 - 0
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotGame.kt

@@ -39,4 +39,9 @@ class GodotGame : GodotEditor() {
 	override fun enableLongPressGestures() = false
 
 	override fun enablePanAndScaleGestures() = false
+
+	override fun checkForProjectPermissionsToEnable() {
+		// Nothing to do.. by the time we get here, the project permissions will have already
+		// been requested by the Editor window.
+	}
 }

+ 6 - 1
platform/android/java/editor/src/main/java/org/godotengine/editor/GodotProjectManager.kt

@@ -37,4 +37,9 @@ package org.godotengine.editor
  * Upon selection of a project, this activity (via its parent logic) starts the
  * [GodotEditor] activity.
  */
-class GodotProjectManager : GodotEditor()
+class GodotProjectManager : GodotEditor() {
+	override fun checkForProjectPermissionsToEnable() {
+		// Nothing to do here.. we have yet to select a project to load.
+	}
+}
+

+ 8 - 2
platform/android/java/lib/src/org/godotengine/godot/Godot.java

@@ -685,8 +685,14 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
 				Intent notifierIntent = new Intent(activity, activity.getClass());
 				notifierIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
-				PendingIntent pendingIntent = PendingIntent.getActivity(activity, 0,
-						notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+				PendingIntent pendingIntent;
+				if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+					pendingIntent = PendingIntent.getActivity(activity, 0,
+							notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+				} else {
+					pendingIntent = PendingIntent.getActivity(activity, 0,
+							notifierIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+				}
 
 				int startResult;
 				try {

+ 56 - 14
platform/android/java/lib/src/org/godotengine/godot/utils/PermissionsUtil.java

@@ -42,10 +42,12 @@ import android.os.Environment;
 import android.provider.Settings;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.core.content.ContextCompat;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
 
 /**
  * This class includes utility functions for Android permissions related operations.
@@ -58,6 +60,7 @@ public final class PermissionsUtil {
 	static final int REQUEST_CAMERA_PERMISSION = 2;
 	static final int REQUEST_VIBRATE_PERMISSION = 3;
 	public static final int REQUEST_ALL_PERMISSION_REQ_CODE = 1001;
+	public static final int REQUEST_SINGLE_PERMISSION_REQ_CODE = 1002;
 	public static final int REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE = 2002;
 
 	private PermissionsUtil() {
@@ -65,31 +68,57 @@ public final class PermissionsUtil {
 
 	/**
 	 * Request a dangerous permission. name must be specified in <a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/res/AndroidManifest.xml">this</a>
-	 * @param name the name of the requested permission.
+	 * @param permissionName the name of the requested permission.
 	 * @param activity the caller activity for this method.
 	 * @return true/false. "true" if permission was granted otherwise returns "false".
 	 */
-	public static boolean requestPermission(String name, Activity activity) {
+	public static boolean requestPermission(String permissionName, Activity activity) {
 		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 			// Not necessary, asked on install already
 			return true;
 		}
 
-		if (name.equals("RECORD_AUDIO") && ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
-			activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION);
-			return false;
-		}
+		switch (permissionName) {
+			case "RECORD_AUDIO":
+			case Manifest.permission.RECORD_AUDIO:
+				if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+					activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION);
+					return false;
+				}
+				return true;
 
-		if (name.equals("CAMERA") && ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
-			activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION);
-			return false;
-		}
+			case "CAMERA":
+			case Manifest.permission.CAMERA:
+				if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+					activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION);
+					return false;
+				}
+				return true;
 
-		if (name.equals("VIBRATE") && ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
-			activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION);
-			return false;
+			case "VIBRATE":
+			case Manifest.permission.VIBRATE:
+				if (ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) {
+					activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION);
+					return false;
+				}
+				return true;
+
+			default:
+				// Check if the given permission is a dangerous permission
+				try {
+					PermissionInfo permissionInfo = getPermissionInfo(activity, permissionName);
+					int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
+					if (protectionLevel == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permissionName) != PackageManager.PERMISSION_GRANTED) {
+						activity.requestPermissions(new String[] { permissionName }, REQUEST_SINGLE_PERMISSION_REQ_CODE);
+						return false;
+					}
+				} catch (PackageManager.NameNotFoundException e) {
+					// Unknown permission - return false as it can't be granted.
+					Log.w(TAG, "Unable to identify permission " + permissionName, e);
+					return false;
+				}
+				return true;
 		}
-		return true;
 	}
 
 	/**
@@ -98,6 +127,16 @@ public final class PermissionsUtil {
 	 * @return true/false. "true" if all permissions were granted otherwise returns "false".
 	 */
 	public static boolean requestManifestPermissions(Activity activity) {
+		return requestManifestPermissions(activity, null);
+	}
+
+	/**
+	 * Request dangerous permissions which are defined in the Android manifest file from the user.
+	 * @param activity the caller activity for this method.
+	 * @param excludes Set of permissions to exclude from the request
+	 * @return true/false. "true" if all permissions were granted otherwise returns "false".
+	 */
+	public static boolean requestManifestPermissions(Activity activity, @Nullable Set<String> excludes) {
 		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
 			return true;
 		}
@@ -115,6 +154,9 @@ public final class PermissionsUtil {
 
 		List<String> requestedPermissions = new ArrayList<>();
 		for (String manifestPermission : manifestPermissions) {
+			if (excludes != null && excludes.contains(manifestPermission)) {
+				continue;
+			}
 			try {
 				if (manifestPermission.equals(Manifest.permission.MANAGE_EXTERNAL_STORAGE)) {
 					if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Environment.isExternalStorageManager()) {

+ 6 - 4
scene/2d/gpu_particles_2d.cpp

@@ -551,10 +551,12 @@ void GPUParticles2D::_notification(int p_what) {
 
 		case NOTIFICATION_PAUSED:
 		case NOTIFICATION_UNPAUSED: {
-			if (can_process()) {
-				RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
-			} else {
-				RS::get_singleton()->particles_set_speed_scale(particles, 0);
+			if (is_inside_tree()) {
+				if (can_process()) {
+					RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
+				} else {
+					RS::get_singleton()->particles_set_speed_scale(particles, 0);
+				}
 			}
 		} break;
 

+ 11 - 9
scene/3d/gpu_particles_3d.cpp

@@ -417,15 +417,6 @@ NodePath GPUParticles3D::get_sub_emitter() const {
 
 void GPUParticles3D::_notification(int p_what) {
 	switch (p_what) {
-		case NOTIFICATION_PAUSED:
-		case NOTIFICATION_UNPAUSED: {
-			if (can_process()) {
-				RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
-			} else {
-				RS::get_singleton()->particles_set_speed_scale(particles, 0);
-			}
-		} break;
-
 		// Use internal process when emitting and one_shot is on so that when
 		// the shot ends the editor can properly update.
 		case NOTIFICATION_INTERNAL_PROCESS: {
@@ -450,6 +441,17 @@ void GPUParticles3D::_notification(int p_what) {
 			RS::get_singleton()->particles_set_subemitter(particles, RID());
 		} break;
 
+		case NOTIFICATION_PAUSED:
+		case NOTIFICATION_UNPAUSED: {
+			if (is_inside_tree()) {
+				if (can_process()) {
+					RS::get_singleton()->particles_set_speed_scale(particles, speed_scale);
+				} else {
+					RS::get_singleton()->particles_set_speed_scale(particles, 0);
+				}
+			}
+		} break;
+
 		case NOTIFICATION_VISIBILITY_CHANGED: {
 			// Make sure particles are updated before rendering occurs if they were active before.
 			if (is_visible_in_tree() && !RS::get_singleton()->particles_is_inactive(particles)) {

+ 1 - 21
scene/3d/skeleton_ik_3d.cpp

@@ -249,26 +249,6 @@ void FabrikInverseKinematic::make_goal(Task *p_task, const Transform3D &p_invers
 	}
 }
 
-static Vector3 get_bone_axis_forward_vector(Skeleton3D *skeleton, int p_bone) {
-	// If it is a child/leaf bone...
-	if (skeleton->get_bone_parent(p_bone) > 0) {
-		return skeleton->get_bone_rest(p_bone).origin.normalized();
-	}
-	// If it has children...
-	Vector<int> child_bones = skeleton->get_bone_children(p_bone);
-	if (child_bones.size() == 0) {
-		WARN_PRINT_ONCE("Cannot calculate forward direction for bone " + itos(p_bone));
-		WARN_PRINT_ONCE("Assuming direction of (0, 1, 0) for bone");
-		return Vector3(0, 1, 0);
-	}
-	Vector3 combined_child_dir = Vector3(0, 0, 0);
-	for (int i = 0; i < child_bones.size(); i++) {
-		combined_child_dir += skeleton->get_bone_rest(child_bones[i]).origin.normalized();
-	}
-	combined_child_dir = combined_child_dir / child_bones.size();
-	return combined_child_dir.normalized();
-}
-
 void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool override_tip_basis, bool p_use_magnet, const Vector3 &p_magnet_position) {
 	if (blending_delta <= 0.01f) {
 		// Before skipping, make sure we undo the global pose overrides
@@ -307,7 +287,7 @@ void FabrikInverseKinematic::solve(Task *p_task, real_t blending_delta, bool ove
 		new_bone_pose.origin = ci->current_pos;
 
 		if (!ci->children.is_empty()) {
-			Vector3 forward_vector = get_bone_axis_forward_vector(p_task->skeleton, ci->bone);
+			Vector3 forward_vector = (ci->children[0].initial_transform.origin - ci->initial_transform.origin).normalized();
 			// Rotate the bone towards the next bone in the chain:
 			new_bone_pose.basis.rotate_to_align(forward_vector, new_bone_pose.origin.direction_to(ci->children[0].current_pos));
 

+ 16 - 0
scene/gui/line_edit.cpp

@@ -1527,6 +1527,22 @@ void LineEdit::set_text(String p_text) {
 	scroll_offset = 0.0;
 }
 
+void LineEdit::set_text_with_selection(const String &p_text) {
+	Selection selection_copy = selection;
+
+	clear_internal();
+	insert_text_at_caret(p_text);
+	_create_undo_state();
+
+	int tlen = text.length();
+	selection = selection_copy;
+	selection.begin = MIN(selection.begin, tlen);
+	selection.end = MIN(selection.end, tlen);
+	selection.start_column = MIN(selection.start_column, tlen);
+
+	queue_redraw();
+}
+
 void LineEdit::set_text_direction(Control::TextDirection p_text_direction) {
 	ERR_FAIL_COND((int)p_text_direction < -1 || (int)p_text_direction > 3);
 	if (text_direction != p_text_direction) {

+ 1 - 0
scene/gui/line_edit.h

@@ -282,6 +282,7 @@ public:
 
 	void set_text(String p_text);
 	String get_text() const;
+	void set_text_with_selection(const String &p_text); // Set text, while preserving selection.
 
 	void set_text_direction(TextDirection p_text_direction);
 	TextDirection get_text_direction() const;

+ 7 - 2
scene/gui/popup_menu.cpp

@@ -1952,8 +1952,13 @@ bool PopupMenu::_get(const StringName &p_name, Variant &r_ret) const {
 			r_ret = get_item_icon(item_index);
 			return true;
 		} else if (property == "checkable") {
-			r_ret = this->items[item_index].checkable_type;
-			return true;
+			if (item_index >= 0 && item_index < items.size()) {
+				r_ret = items[item_index].checkable_type;
+				return true;
+			} else {
+				r_ret = Item::CHECKABLE_TYPE_NONE;
+				ERR_FAIL_V(true);
+			}
 		} else if (property == "checked") {
 			r_ret = is_item_checked(item_index);
 			return true;

+ 21 - 2
scene/gui/range.cpp

@@ -74,16 +74,26 @@ void Range::Shared::emit_changed(const char *p_what) {
 	}
 }
 
+void Range::Shared::redraw_owners() {
+	for (Range *E : owners) {
+		Range *r = E;
+		if (!r->is_inside_tree()) {
+			continue;
+		}
+		r->queue_redraw();
+	}
+}
+
 void Range::set_value(double p_val) {
 	double prev_val = shared->val;
-	set_value_no_signal(p_val);
+	_set_value_no_signal(p_val);
 
 	if (shared->val != prev_val) {
 		shared->emit_value_changed();
 	}
 }
 
-void Range::set_value_no_signal(double p_val) {
+void Range::_set_value_no_signal(double p_val) {
 	if (shared->step > 0) {
 		p_val = Math::round((p_val - shared->min) / shared->step) * shared->step + shared->min;
 	}
@@ -107,6 +117,15 @@ void Range::set_value_no_signal(double p_val) {
 	shared->val = p_val;
 }
 
+void Range::set_value_no_signal(double p_val) {
+	double prev_val = shared->val;
+	_set_value_no_signal(p_val);
+
+	if (shared->val != prev_val) {
+		shared->redraw_owners();
+	}
+}
+
 void Range::set_min(double p_min) {
 	if (shared->min == p_min) {
 		return;

+ 2 - 0
scene/gui/range.h

@@ -48,6 +48,7 @@ class Range : public Control {
 		HashSet<Range *> owners;
 		void emit_value_changed();
 		void emit_changed(const char *p_what = "");
+		void redraw_owners();
 	};
 
 	Shared *shared = nullptr;
@@ -59,6 +60,7 @@ class Range : public Control {
 
 	void _value_changed_notify();
 	void _changed_notify(const char *p_what = "");
+	void _set_value_no_signal(double p_val);
 
 protected:
 	virtual void _value_changed(double p_value);

+ 1 - 1
scene/gui/slider.cpp

@@ -192,7 +192,7 @@ void Slider::_notification(int p_what) {
 			Ref<StyleBox> style = theme_cache.slider_style;
 			Ref<Texture2D> tick = theme_cache.tick_icon;
 
-			bool highlighted = mouse_inside || has_focus();
+			bool highlighted = editable && (mouse_inside || has_focus());
 			Ref<Texture2D> grabber;
 			if (editable) {
 				if (highlighted) {

+ 12 - 9
scene/gui/spin_box.cpp

@@ -39,7 +39,7 @@ Size2 SpinBox::get_minimum_size() const {
 	return ms;
 }
 
-void SpinBox::_value_changed(double p_value) {
+void SpinBox::_update_text() {
 	String value = String::num(get_value(), Math::range_step_decimals(get_step()));
 	if (is_localizing_numeral_system()) {
 		value = TS->format_number(value);
@@ -54,8 +54,7 @@ void SpinBox::_value_changed(double p_value) {
 		}
 	}
 
-	line_edit->set_text(value);
-	Range::_value_changed(p_value);
+	line_edit->set_text_with_selection(value);
 }
 
 void SpinBox::_text_submitted(const String &p_string) {
@@ -73,7 +72,7 @@ void SpinBox::_text_submitted(const String &p_string) {
 	if (value.get_type() != Variant::NIL) {
 		set_value(value);
 	}
-	_value_changed(0);
+	_update_text();
 }
 
 void SpinBox::_text_changed(const String &p_string) {
@@ -192,7 +191,7 @@ void SpinBox::gui_input(const Ref<InputEvent> &p_event) {
 
 void SpinBox::_line_edit_focus_enter() {
 	int col = line_edit->get_caret_column();
-	_value_changed(0); // Update the LineEdit's text.
+	_update_text();
 	line_edit->set_caret_column(col);
 
 	// LineEdit text might change and it clears any selection. Have to re-select here.
@@ -202,6 +201,10 @@ void SpinBox::_line_edit_focus_enter() {
 }
 
 void SpinBox::_line_edit_focus_exit() {
+	// Discontinue because the focus_exit was caused by left-clicking the arrows.
+	if (get_viewport()->gui_get_focus_owner() == get_line_edit()) {
+		return;
+	}
 	// Discontinue because the focus_exit was caused by right-click context menu.
 	if (line_edit->is_menu_visible()) {
 		return;
@@ -228,6 +231,7 @@ void SpinBox::_update_theme_item_cache() {
 void SpinBox::_notification(int p_what) {
 	switch (p_what) {
 		case NOTIFICATION_DRAW: {
+			_update_text();
 			_adjust_width_for_icon(theme_cache.updown_icon);
 
 			RID ci = get_canvas_item();
@@ -242,7 +246,7 @@ void SpinBox::_notification(int p_what) {
 
 		case NOTIFICATION_ENTER_TREE: {
 			_adjust_width_for_icon(theme_cache.updown_icon);
-			_value_changed(0);
+			_update_text();
 		} break;
 
 		case NOTIFICATION_EXIT_TREE: {
@@ -250,7 +254,6 @@ void SpinBox::_notification(int p_what) {
 		} break;
 
 		case NOTIFICATION_TRANSLATION_CHANGED: {
-			_value_changed(0);
 			queue_redraw();
 		} break;
 
@@ -279,7 +282,7 @@ void SpinBox::set_suffix(const String &p_suffix) {
 	}
 
 	suffix = p_suffix;
-	_value_changed(0);
+	_update_text();
 }
 
 String SpinBox::get_suffix() const {
@@ -292,7 +295,7 @@ void SpinBox::set_prefix(const String &p_prefix) {
 	}
 
 	prefix = p_prefix;
-	_value_changed(0);
+	_update_text();
 }
 
 String SpinBox::get_prefix() const {

+ 1 - 1
scene/gui/spin_box.h

@@ -46,8 +46,8 @@ class SpinBox : public Range {
 	void _range_click_timeout();
 	void _release_mouse();
 
+	void _update_text();
 	void _text_submitted(const String &p_string);
-	virtual void _value_changed(double p_value) override;
 	void _text_changed(const String &p_string);
 
 	String prefix;

+ 3 - 1
scene/gui/text_edit.cpp

@@ -7153,7 +7153,9 @@ void TextEdit::_update_selection_mode_line() {
 	if (line < carets[caret_idx].selection.selecting_line) {
 		/* Caret is above us. */
 		set_caret_line(line - 1, false, true, 0, caret_idx);
-		carets.write[caret_idx].selection.selecting_column = text[get_selection_line(caret_idx)].length();
+		carets.write[caret_idx].selection.selecting_column = has_selection(caret_idx)
+				? text[get_selection_line(caret_idx)].length()
+				: 0;
 	} else {
 		/* Caret is below us. */
 		set_caret_line(line + 1, false, true, 0, caret_idx);

+ 12 - 12
scene/main/node.cpp

@@ -1251,6 +1251,17 @@ Node *Node::get_child(int p_index, bool p_include_internal) const {
 	}
 }
 
+TypedArray<Node> Node::get_children(bool p_include_internal) const {
+	TypedArray<Node> arr;
+	int cc = get_child_count(p_include_internal);
+	arr.resize(cc);
+	for (int i = 0; i < cc; i++) {
+		arr[i] = get_child(i, p_include_internal);
+	}
+
+	return arr;
+}
+
 Node *Node::_get_child_by_name(const StringName &p_name) const {
 	int cc = data.children.size();
 	Node *const *cd = data.children.ptr();
@@ -2660,17 +2671,6 @@ void Node::queue_free() {
 	}
 }
 
-TypedArray<Node> Node::_get_children(bool p_include_internal) const {
-	TypedArray<Node> arr;
-	int cc = get_child_count(p_include_internal);
-	arr.resize(cc);
-	for (int i = 0; i < cc; i++) {
-		arr[i] = get_child(i, p_include_internal);
-	}
-
-	return arr;
-}
-
 void Node::set_import_path(const NodePath &p_import_path) {
 #ifdef TOOLS_ENABLED
 	data.import_path = p_import_path;
@@ -2824,7 +2824,7 @@ void Node::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("remove_child", "node"), &Node::remove_child);
 	ClassDB::bind_method(D_METHOD("reparent", "new_parent", "keep_global_transform"), &Node::reparent, DEFVAL(true));
 	ClassDB::bind_method(D_METHOD("get_child_count", "include_internal"), &Node::get_child_count, DEFVAL(false)); // Note that the default value bound for include_internal is false, while the method is declared with true. This is because internal nodes are irrelevant for GDSCript.
-	ClassDB::bind_method(D_METHOD("get_children", "include_internal"), &Node::_get_children, DEFVAL(false));
+	ClassDB::bind_method(D_METHOD("get_children", "include_internal"), &Node::get_children, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("get_child", "idx", "include_internal"), &Node::get_child, DEFVAL(false));
 	ClassDB::bind_method(D_METHOD("has_node", "path"), &Node::has_node);
 	ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node);

+ 1 - 1
scene/main/node.h

@@ -181,7 +181,6 @@ private:
 	void _duplicate_signals(const Node *p_original, Node *p_copy) const;
 	Node *_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap = nullptr) const;
 
-	TypedArray<Node> _get_children(bool p_include_internal = true) const;
 	TypedArray<StringName> _get_groups() const;
 
 	Error _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
@@ -311,6 +310,7 @@ public:
 
 	int get_child_count(bool p_include_internal = true) const;
 	Node *get_child(int p_index, bool p_include_internal = true) const;
+	TypedArray<Node> get_children(bool p_include_internal = true) const;
 	bool has_node(const NodePath &p_path) const;
 	Node *get_node(const NodePath &p_path) const;
 	Node *get_node_or_null(const NodePath &p_path) const;

+ 8 - 2
scene/resources/particle_process_material.cpp

@@ -474,14 +474,20 @@ void ParticleProcessMaterial::_update_shader() {
 	code += "	CUSTOM.x = base_angle * degree_to_rad;\n"; // angle
 	code += "	CUSTOM.y = 0.0;\n"; // phase
 	code += "	CUSTOM.w = (1.0 - lifetime_randomness * rand_from_seed(alt_seed));\n";
-	code += "	CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, anim_offset_rand);\n"; // animation offset (0-1)
+	code += "	CUSTOM.z = (tex_anim_offset) * mix(anim_offset_min, anim_offset_max, anim_offset_rand);\n\n"; // animation offset (0-1)
+
+	code += "	if (RESTART_ROT_SCALE) {\n";
+	code += "		TRANSFORM[0].xyz = vec3(1.0, 0.0, 0.0);\n";
+	code += "		TRANSFORM[1].xyz = vec3(0.0, 1.0, 0.0);\n";
+	code += "		TRANSFORM[2].xyz = vec3(0.0, 0.0, 1.0);\n";
+	code += "	}\n\n";
 
 	code += "	if (RESTART_POSITION) {\n";
 
 	switch (emission_shape) {
 		case EMISSION_SHAPE_POINT: {
 			//do none, identity (will later be multiplied by emission transform)
-			code += "		TRANSFORM = mat4(vec4(1,0,0,0),vec4(0,1,0,0),vec4(0,0,1,0),vec4(0,0,0,1));\n";
+			code += "		TRANSFORM[3].xyz = vec3(0.0, 0.0, 0.0);\n";
 		} break;
 		case EMISSION_SHAPE_SPHERE: {
 			code += "		float s = rand_from_seed(alt_seed) * 2.0 - 1.0;\n";

+ 1 - 0
scene/resources/tile_set.cpp

@@ -2590,6 +2590,7 @@ void TileSet::_compatibility_conversion() {
 					compatibility_tilemap_mapping_tile_modes[E.key] = COMPATIBILITY_TILE_MODE_SINGLE_TILE;
 
 					TileData *tile_data = atlas_source->get_tile_data(coords, alternative_tile);
+					ERR_CONTINUE(!tile_data);
 
 					tile_data->set_flip_h(flip_h);
 					tile_data->set_flip_v(flip_v);

+ 1 - 1
thirdparty/README.md

@@ -115,7 +115,7 @@ commits.
 ## enet
 
 - Upstream: http://enet.bespin.org
-- Version: 1.3.17 (e0e7045b7e056b454b5093cb34df49dc4cee0bee, 2020)
+- Version: git (ea4607a90dbfbcf4da2669ea998585253d8e70b1, 2023)
 - License: MIT
 
 Files extracted from upstream source:

+ 10 - 6
thirdparty/enet/enet/enet.h

@@ -68,7 +68,8 @@ typedef enum _ENetSocketOption
    ENET_SOCKOPT_RCVTIMEO  = 6,
    ENET_SOCKOPT_SNDTIMEO  = 7,
    ENET_SOCKOPT_ERROR     = 8,
-   ENET_SOCKOPT_NODELAY   = 9
+   ENET_SOCKOPT_NODELAY   = 9,
+   ENET_SOCKOPT_TTL       = 10
 } ENetSocketOption;
 
 typedef enum _ENetSocketShutdown
@@ -179,7 +180,7 @@ typedef struct _ENetOutgoingCommand
    enet_uint16  unreliableSequenceNumber;
    enet_uint32  sentTime;
    enet_uint32  roundTripTimeout;
-   enet_uint32  roundTripTimeoutLimit;
+   enet_uint32  queueTime;
    enet_uint32  fragmentOffset;
    enet_uint16  fragmentLength;
    enet_uint16  sendAttempts;
@@ -222,7 +223,7 @@ enum
    ENET_HOST_RECEIVE_BUFFER_SIZE          = 256 * 1024,
    ENET_HOST_SEND_BUFFER_SIZE             = 256 * 1024,
    ENET_HOST_BANDWIDTH_THROTTLE_INTERVAL  = 1000,
-   ENET_HOST_DEFAULT_MTU                  = 1400,
+   ENET_HOST_DEFAULT_MTU                  = 1392,
    ENET_HOST_DEFAULT_MAXIMUM_PACKET_SIZE  = 32 * 1024 * 1024,
    ENET_HOST_DEFAULT_MAXIMUM_WAITING_DATA = 32 * 1024 * 1024,
 
@@ -262,7 +263,8 @@ typedef struct _ENetChannel
 
 typedef enum _ENetPeerFlag
 {
-   ENET_PEER_FLAG_NEEDS_DISPATCH = (1 << 0)
+   ENET_PEER_FLAG_NEEDS_DISPATCH   = (1 << 0),
+   ENET_PEER_FLAG_CONTINUE_SENDING = (1 << 1)
 } ENetPeerFlag;
 
 /**
@@ -322,7 +324,7 @@ typedef struct _ENetPeer
    enet_uint16   outgoingReliableSequenceNumber;
    ENetList      acknowledgements;
    ENetList      sentReliableCommands;
-   ENetList      sentUnreliableCommands;
+   ENetList      outgoingSendReliableCommands;
    ENetList      outgoingCommands;
    ENetList      dispatchedCommands;
    enet_uint16   flags;
@@ -385,7 +387,7 @@ typedef struct _ENetHost
    size_t               channelLimit;                /**< maximum number of channels allowed for connected peers */
    enet_uint32          serviceTime;
    ENetList             dispatchQueue;
-   int                  continueSending;
+   enet_uint32          totalQueued;
    size_t               packetSize;
    enet_uint16          headerFlags;
    ENetProtocol         commands [ENET_PROTOCOL_MAXIMUM_PACKET_COMMANDS];
@@ -585,6 +587,7 @@ ENET_API void       enet_host_channel_limit (ENetHost *, size_t);
 ENET_API void       enet_host_bandwidth_limit (ENetHost *, enet_uint32, enet_uint32);
 extern   void       enet_host_bandwidth_throttle (ENetHost *);
 extern  enet_uint32 enet_host_random_seed (void);
+extern  enet_uint32 enet_host_random (ENetHost *);
 
 ENET_API int                 enet_peer_send (ENetPeer *, enet_uint8, ENetPacket *);
 ENET_API ENetPacket *        enet_peer_receive (ENetPeer *, enet_uint8 * channelID);
@@ -598,6 +601,7 @@ ENET_API void                enet_peer_disconnect_later (ENetPeer *, enet_uint32
 ENET_API void                enet_peer_throttle_configure (ENetPeer *, enet_uint32, enet_uint32, enet_uint32);
 extern int                   enet_peer_throttle (ENetPeer *, enet_uint32);
 extern void                  enet_peer_reset_queues (ENetPeer *);
+extern int                   enet_peer_has_outgoing_commands (ENetPeer *);
 extern void                  enet_peer_setup_outgoing_command (ENetPeer *, ENetOutgoingCommand *);
 extern ENetOutgoingCommand * enet_peer_queue_outgoing_command (ENetPeer *, const ENetProtocol *, ENetPacket *, enet_uint32, enet_uint16);
 extern ENetIncomingCommand * enet_peer_queue_incoming_command (ENetPeer *, const ENetProtocol *, const void *, size_t, enet_uint32, enet_uint32);

+ 4 - 0
thirdparty/enet/godot.cpp

@@ -535,6 +535,10 @@ int enet_socket_receive(ENetSocket socket, ENetAddress *address, ENetBuffer *buf
 	if (err == ERR_BUSY) {
 		return 0;
 	}
+	if (err == ERR_OUT_OF_MEMORY) {
+		// A packet above the ENET_PROTOCOL_MAXIMUM_MTU was received.
+		return -2;
+	}
 
 	if (err != OK) {
 		return -1;

+ 14 - 2
thirdparty/enet/host.c

@@ -96,6 +96,7 @@ enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelL
     host -> totalSentPackets = 0;
     host -> totalReceivedData = 0;
     host -> totalReceivedPackets = 0;
+    host -> totalQueued = 0;
 
     host -> connectedPeers = 0;
     host -> bandwidthLimitedPeers = 0;
@@ -123,8 +124,8 @@ enet_host_create (const ENetAddress * address, size_t peerCount, size_t channelL
 
        enet_list_clear (& currentPeer -> acknowledgements);
        enet_list_clear (& currentPeer -> sentReliableCommands);
-       enet_list_clear (& currentPeer -> sentUnreliableCommands);
        enet_list_clear (& currentPeer -> outgoingCommands);
+       enet_list_clear (& currentPeer -> outgoingSendReliableCommands);
        enet_list_clear (& currentPeer -> dispatchedCommands);
 
        enet_peer_reset (currentPeer);
@@ -160,6 +161,16 @@ enet_host_destroy (ENetHost * host)
     enet_free (host);
 }
 
+enet_uint32
+enet_host_random (ENetHost * host)
+{
+    /* Mulberry32 by Tommy Ettinger */
+    enet_uint32 n = (host -> randomSeed += 0x6D2B79F5U);
+    n = (n ^ (n >> 15)) * (n | 1U);
+    n ^= n + (n ^ (n >> 7)) * (n | 61U);
+    return n ^ (n >> 14);
+}
+
 /** Initiates a connection to a foreign host.
     @param host host seeking the connection
     @param address destination for the connection
@@ -199,7 +210,8 @@ enet_host_connect (ENetHost * host, const ENetAddress * address, size_t channelC
     currentPeer -> channelCount = channelCount;
     currentPeer -> state = ENET_PEER_STATE_CONNECTING;
     currentPeer -> address = * address;
-    currentPeer -> connectID = ++ host -> randomSeed;
+    currentPeer -> connectID = enet_host_random (host);
+    currentPeer -> mtu = host -> mtu;
 
     if (host -> outgoingBandwidth == 0)
       currentPeer -> windowSize = ENET_PROTOCOL_MAXIMUM_WINDOW_SIZE;

+ 35 - 42
thirdparty/enet/packet.c

@@ -98,53 +98,46 @@ enet_packet_resize (ENetPacket * packet, size_t dataLength)
     return 0;
 }
 
-static int initializedCRC32 = 0;
-static enet_uint32 crcTable [256];
-
-static enet_uint32 
-reflect_crc (int val, int bits)
+static const enet_uint32 crcTable [256] =
 {
-    int result = 0, bit;
-
-    for (bit = 0; bit < bits; bit ++)
-    {
-        if(val & 1) result |= 1 << (bits - 1 - bit); 
-        val >>= 1;
-    }
-
-    return result;
-}
-
-static void 
-initialize_crc32 (void)
-{
-    int byte;
-
-    for (byte = 0; byte < 256; ++ byte)
-    {
-        enet_uint32 crc = reflect_crc (byte, 8) << 24;
-        int offset;
+    0,          0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
+    0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
+    0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
+    0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
+    0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
+    0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
+    0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
+    0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
+    0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
+    0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
+    0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
+    0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
+    0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
+    0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
+    0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
+    0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
+    0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
+    0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
+    0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
+    0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
+    0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
+    0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
+    0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
+    0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
+    0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x5005713,
+    0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0xBDBDF21,
+    0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
+    0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
+    0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
+    0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
+    0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
+    0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
+};
 
-        for(offset = 0; offset < 8; ++ offset)
-        {
-            if (crc & 0x80000000)
-                crc = (crc << 1) ^ 0x04c11db7;
-            else
-                crc <<= 1;
-        }
-
-        crcTable [byte] = reflect_crc (crc, 32);
-    }
-
-    initializedCRC32 = 1;
-}
-    
 enet_uint32
 enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
 {
     enet_uint32 crc = 0xFFFFFFFF;
-    
-    if (! initializedCRC32) initialize_crc32 ();
 
     while (bufferCount -- > 0)
     {
@@ -153,7 +146,7 @@ enet_crc32 (const ENetBuffer * buffers, size_t bufferCount)
 
         while (data < dataEnd)
         {
-            crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++];        
+            crc = (crc >> 8) ^ crcTable [(crc & 0xFF) ^ *data++];
         }
 
         ++ buffers;

+ 54 - 30
thirdparty/enet/peer.c

@@ -90,6 +90,13 @@ enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt)
 }
 
 /** Queues a packet to be sent.
+
+    On success, ENet will assume ownership of the packet, and so enet_packet_destroy
+    should not be called on it thereafter. On failure, the caller still must destroy
+    the packet on its own as ENet has not queued the packet. The caller can also
+    check the packet's referenceCount field after sending to check if ENet queued
+    the packet and thus incremented the referenceCount.
+
     @param peer destination for the packet
     @param channelID channel on which to send
     @param packet packet to send
@@ -99,7 +106,7 @@ enet_peer_throttle (ENetPeer * peer, enet_uint32 rtt)
 int
 enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet)
 {
-   ENetChannel * channel = & peer -> channels [channelID];
+   ENetChannel * channel;
    ENetProtocol command;
    size_t fragmentLength;
 
@@ -108,6 +115,7 @@ enet_peer_send (ENetPeer * peer, enet_uint8 channelID, ENetPacket * packet)
        packet -> dataLength > peer -> host -> maximumPacketSize)
      return -1;
 
+   channel = & peer -> channels [channelID];
    fragmentLength = peer -> mtu - sizeof (ENetProtocolHeader) - sizeof (ENetProtocolSendFragment);
    if (peer -> host -> checksum != NULL)
      fragmentLength -= sizeof(enet_uint32);
@@ -320,8 +328,8 @@ enet_peer_reset_queues (ENetPeer * peer)
       enet_free (enet_list_remove (enet_list_begin (& peer -> acknowledgements)));
 
     enet_peer_reset_outgoing_commands (& peer -> sentReliableCommands);
-    enet_peer_reset_outgoing_commands (& peer -> sentUnreliableCommands);
     enet_peer_reset_outgoing_commands (& peer -> outgoingCommands);
+    enet_peer_reset_outgoing_commands (& peer -> outgoingSendReliableCommands);
     enet_peer_reset_incoming_commands (& peer -> dispatchedCommands);
 
     if (peer -> channels != NULL && peer -> channelCount > 0)
@@ -563,6 +571,17 @@ enet_peer_disconnect (ENetPeer * peer, enet_uint32 data)
     }
 }
 
+int
+enet_peer_has_outgoing_commands (ENetPeer * peer)
+{
+  if (enet_list_empty (& peer -> outgoingCommands) &&
+      enet_list_empty (& peer -> outgoingSendReliableCommands) &&
+      enet_list_empty (& peer -> sentReliableCommands))
+    return 0;
+
+  return 1;
+}
+
 /** Request a disconnection from a peer, but only after all queued outgoing packets are sent.
     @param peer peer to request a disconnection
     @param data data describing the disconnection
@@ -573,8 +592,7 @@ void
 enet_peer_disconnect_later (ENetPeer * peer, enet_uint32 data)
 {   
     if ((peer -> state == ENET_PEER_STATE_CONNECTED || peer -> state == ENET_PEER_STATE_DISCONNECT_LATER) && 
-        ! (enet_list_empty (& peer -> outgoingCommands) &&
-           enet_list_empty (& peer -> sentReliableCommands)))
+        enet_peer_has_outgoing_commands (peer))
     {
         peer -> state = ENET_PEER_STATE_DISCONNECT_LATER;
         peer -> eventData = data;
@@ -618,8 +636,6 @@ enet_peer_queue_acknowledgement (ENetPeer * peer, const ENetProtocol * command,
 void
 enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoingCommand)
 {
-    ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID];
-    
     peer -> outgoingDataTotal += enet_protocol_command_size (outgoingCommand -> command.header.command) + outgoingCommand -> fragmentLength;
 
     if (outgoingCommand -> command.header.channelID == 0xFF)
@@ -630,36 +646,40 @@ enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoin
        outgoingCommand -> unreliableSequenceNumber = 0;
     }
     else
-    if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
     {
-       ++ channel -> outgoingReliableSequenceNumber;
-       channel -> outgoingUnreliableSequenceNumber = 0;
+        ENetChannel * channel = & peer -> channels [outgoingCommand -> command.header.channelID];
 
-       outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
-       outgoingCommand -> unreliableSequenceNumber = 0;
-    }
-    else
-    if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED)
-    {
-       ++ peer -> outgoingUnsequencedGroup;
+        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
+        {
+           ++ channel -> outgoingReliableSequenceNumber;
+           channel -> outgoingUnreliableSequenceNumber = 0;
 
-       outgoingCommand -> reliableSequenceNumber = 0;
-       outgoingCommand -> unreliableSequenceNumber = 0;
-    }
-    else
-    {
-       if (outgoingCommand -> fragmentOffset == 0)
-         ++ channel -> outgoingUnreliableSequenceNumber;
-        
-       outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
-       outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber;
+           outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
+           outgoingCommand -> unreliableSequenceNumber = 0;
+        }
+        else
+        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_UNSEQUENCED)
+        {
+           ++ peer -> outgoingUnsequencedGroup;
+
+           outgoingCommand -> reliableSequenceNumber = 0;
+           outgoingCommand -> unreliableSequenceNumber = 0;
+        }
+        else
+        {
+           if (outgoingCommand -> fragmentOffset == 0)
+             ++ channel -> outgoingUnreliableSequenceNumber;
+
+           outgoingCommand -> reliableSequenceNumber = channel -> outgoingReliableSequenceNumber;
+           outgoingCommand -> unreliableSequenceNumber = channel -> outgoingUnreliableSequenceNumber;
+        }
     }
-   
+
     outgoingCommand -> sendAttempts = 0;
     outgoingCommand -> sentTime = 0;
     outgoingCommand -> roundTripTimeout = 0;
-    outgoingCommand -> roundTripTimeoutLimit = 0;
     outgoingCommand -> command.header.reliableSequenceNumber = ENET_HOST_TO_NET_16 (outgoingCommand -> reliableSequenceNumber);
+    outgoingCommand -> queueTime = ++ peer -> host -> totalQueued;
 
     switch (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_MASK)
     {
@@ -670,12 +690,16 @@ enet_peer_setup_outgoing_command (ENetPeer * peer, ENetOutgoingCommand * outgoin
     case ENET_PROTOCOL_COMMAND_SEND_UNSEQUENCED:
         outgoingCommand -> command.sendUnsequenced.unsequencedGroup = ENET_HOST_TO_NET_16 (peer -> outgoingUnsequencedGroup);
         break;
-    
+
     default:
         break;
     }
 
-    enet_list_insert (enet_list_end (& peer -> outgoingCommands), outgoingCommand);
+    if ((outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE) != 0 &&
+        outgoingCommand -> packet != NULL)
+      enet_list_insert (enet_list_end (& peer -> outgoingSendReliableCommands), outgoingCommand);
+    else
+      enet_list_insert (enet_list_end (& peer -> outgoingCommands), outgoingCommand);
 }
 
 ENetOutgoingCommand *

+ 123 - 87
thirdparty/enet/protocol.c

@@ -9,7 +9,7 @@
 #include "enet/time.h"
 #include "enet/enet.h"
 
-static size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] =
+static const size_t commandSizes [ENET_PROTOCOL_COMMAND_COUNT] =
 {
     0,
     sizeof (ENetProtocolAcknowledge),
@@ -159,16 +159,16 @@ enet_protocol_notify_disconnect (ENetHost * host, ENetPeer * peer, ENetEvent * e
 }
 
 static void
-enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer)
+enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer, ENetList * sentUnreliableCommands)
 {
     ENetOutgoingCommand * outgoingCommand;
 
-    if (enet_list_empty (& peer -> sentUnreliableCommands))
+    if (enet_list_empty (sentUnreliableCommands))
       return;
 
     do
     {
-        outgoingCommand = (ENetOutgoingCommand *) enet_list_front (& peer -> sentUnreliableCommands);
+        outgoingCommand = (ENetOutgoingCommand *) enet_list_front (sentUnreliableCommands);
         
         enet_list_remove (& outgoingCommand -> outgoingCommandList);
 
@@ -185,14 +185,38 @@ enet_protocol_remove_sent_unreliable_commands (ENetPeer * peer)
         }
 
         enet_free (outgoingCommand);
-    } while (! enet_list_empty (& peer -> sentUnreliableCommands));
+    } while (! enet_list_empty (sentUnreliableCommands));
 
     if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER &&
-        enet_list_empty (& peer -> outgoingCommands) &&
-        enet_list_empty (& peer -> sentReliableCommands))
+        ! enet_peer_has_outgoing_commands (peer))
       enet_peer_disconnect (peer, peer -> eventData);
 }
 
+static ENetOutgoingCommand *
+enet_protocol_find_sent_reliable_command (ENetList * list, enet_uint16 reliableSequenceNumber, enet_uint8 channelID)
+{
+    ENetListIterator currentCommand;
+
+    for (currentCommand = enet_list_begin (list);
+         currentCommand != enet_list_end (list);
+         currentCommand = enet_list_next (currentCommand))
+    {
+       ENetOutgoingCommand * outgoingCommand = (ENetOutgoingCommand *) currentCommand;
+
+       if (! (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE))
+         continue;
+
+       if (outgoingCommand -> sendAttempts < 1)
+         break;
+
+       if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber &&
+           outgoingCommand -> command.header.channelID == channelID)
+         return outgoingCommand;
+    }
+
+    return NULL;
+}
+
 static ENetProtocolCommand
 enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliableSequenceNumber, enet_uint8 channelID)
 {
@@ -214,24 +238,9 @@ enet_protocol_remove_sent_reliable_command (ENetPeer * peer, enet_uint16 reliabl
 
     if (currentCommand == enet_list_end (& peer -> sentReliableCommands))
     {
-       for (currentCommand = enet_list_begin (& peer -> outgoingCommands);
-            currentCommand != enet_list_end (& peer -> outgoingCommands);
-            currentCommand = enet_list_next (currentCommand))
-       {
-          outgoingCommand = (ENetOutgoingCommand *) currentCommand;
-
-          if (! (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE))
-            continue;
-
-          if (outgoingCommand -> sendAttempts < 1) return ENET_PROTOCOL_COMMAND_NONE;
-
-          if (outgoingCommand -> reliableSequenceNumber == reliableSequenceNumber &&
-              outgoingCommand -> command.header.channelID == channelID)
-            break;
-       }
-
-       if (currentCommand == enet_list_end (& peer -> outgoingCommands))
-         return ENET_PROTOCOL_COMMAND_NONE;
+       outgoingCommand = enet_protocol_find_sent_reliable_command (& peer -> outgoingCommands, reliableSequenceNumber, channelID);
+       if (outgoingCommand == NULL)
+         outgoingCommand = enet_protocol_find_sent_reliable_command (& peer -> outgoingSendReliableCommands, reliableSequenceNumber, channelID);
 
        wasSent = 0;
     }
@@ -331,6 +340,7 @@ enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENet
     peer -> state = ENET_PEER_STATE_ACKNOWLEDGING_CONNECT;
     peer -> connectID = command -> connect.connectID;
     peer -> address = host -> receivedAddress;
+    peer -> mtu = host -> mtu;
     peer -> outgoingPeerID = ENET_NET_TO_HOST_16 (command -> connect.outgoingPeerID);
     peer -> incomingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.incomingBandwidth);
     peer -> outgoingBandwidth = ENET_NET_TO_HOST_32 (command -> connect.outgoingBandwidth);
@@ -375,7 +385,8 @@ enet_protocol_handle_connect (ENetHost * host, ENetProtocolHeader * header, ENet
     if (mtu > ENET_PROTOCOL_MAXIMUM_MTU)
       mtu = ENET_PROTOCOL_MAXIMUM_MTU;
 
-    peer -> mtu = mtu;
+    if (mtu < peer -> mtu)
+      peer -> mtu = mtu;
 
     if (host -> outgoingBandwidth == 0 &&
         peer -> incomingBandwidth == 0)
@@ -542,7 +553,8 @@ enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENet
 
     fragmentLength = ENET_NET_TO_HOST_16 (command -> sendFragment.dataLength);
     * currentData += fragmentLength;
-    if (fragmentLength > host -> maximumPacketSize ||
+    if (fragmentLength <= 0 ||
+        fragmentLength > host -> maximumPacketSize ||
         * currentData < host -> receivedData ||
         * currentData > & host -> receivedData [host -> receivedDataLength])
       return -1;
@@ -566,6 +578,7 @@ enet_protocol_handle_send_fragment (ENetHost * host, ENetPeer * peer, const ENet
     if (fragmentCount > ENET_PROTOCOL_MAXIMUM_FRAGMENT_COUNT ||
         fragmentNumber >= fragmentCount ||
         totalLength > host -> maximumPacketSize ||
+        totalLength < fragmentCount ||
         fragmentOffset >= totalLength ||
         fragmentLength > totalLength - fragmentOffset)
       return -1;
@@ -921,8 +934,7 @@ enet_protocol_handle_acknowledge (ENetHost * host, ENetEvent * event, ENetPeer *
        break;
 
     case ENET_PEER_STATE_DISCONNECT_LATER:
-       if (enet_list_empty (& peer -> outgoingCommands) &&
-           enet_list_empty (& peer -> sentReliableCommands))
+       if (! enet_peer_has_outgoing_commands (peer))
          enet_peer_disconnect (peer, peer -> eventData);
        break;
 
@@ -1230,6 +1242,9 @@ enet_protocol_receive_incoming_commands (ENetHost * host, ENetEvent * event)
                                              & buffer,
                                              1);
 
+       if (receivedLength == -2)
+         continue;
+
        if (receivedLength < 0)
          return -1;
 
@@ -1293,7 +1308,7 @@ enet_protocol_send_acknowledgements (ENetHost * host, ENetPeer * peer)
            buffer >= & host -> buffers [sizeof (host -> buffers) / sizeof (ENetBuffer)] ||
            peer -> mtu - host -> packetSize < sizeof (ENetProtocolAcknowledge))
        {
-          host -> continueSending = 1;
+          peer -> flags |= ENET_PEER_FLAG_CONTINUE_SENDING;
 
           break;
        }
@@ -1333,10 +1348,11 @@ static int
 enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * event)
 {
     ENetOutgoingCommand * outgoingCommand;
-    ENetListIterator currentCommand, insertPosition;
+    ENetListIterator currentCommand, insertPosition, insertSendReliablePosition;
 
     currentCommand = enet_list_begin (& peer -> sentReliableCommands);
     insertPosition = enet_list_begin (& peer -> outgoingCommands);
+    insertSendReliablePosition = enet_list_begin (& peer -> outgoingSendReliableCommands);
 
     while (currentCommand != enet_list_end (& peer -> sentReliableCommands))
     {
@@ -1353,7 +1369,7 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even
 
        if (peer -> earliestTimeout != 0 &&
              (ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMaximum ||
-               (outgoingCommand -> roundTripTimeout >= outgoingCommand -> roundTripTimeoutLimit &&
+               ((1 << (outgoingCommand -> sendAttempts - 1)) >= peer -> timeoutLimit &&
                  ENET_TIME_DIFFERENCE (host -> serviceTime, peer -> earliestTimeout) >= peer -> timeoutMinimum)))
        {
           enet_protocol_notify_disconnect (host, peer, event);
@@ -1361,14 +1377,18 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even
           return 1;
        }
 
-       if (outgoingCommand -> packet != NULL)
-         peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength;
-          
        ++ peer -> packetsLost;
 
        outgoingCommand -> roundTripTimeout *= 2;
 
-       enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));
+       if (outgoingCommand -> packet != NULL)
+       {
+         peer -> reliableDataInTransit -= outgoingCommand -> fragmentLength;
+
+         enet_list_insert (insertSendReliablePosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));
+       }
+       else
+         enet_list_insert (insertPosition, enet_list_remove (& outgoingCommand -> outgoingCommandList));
 
        if (currentCommand == enet_list_begin (& peer -> sentReliableCommands) &&
            ! enet_list_empty (& peer -> sentReliableCommands))
@@ -1383,22 +1403,41 @@ enet_protocol_check_timeouts (ENetHost * host, ENetPeer * peer, ENetEvent * even
 }
 
 static int
-enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
+enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer, ENetList * sentUnreliableCommands)
 {
     ENetProtocol * command = & host -> commands [host -> commandCount];
     ENetBuffer * buffer = & host -> buffers [host -> bufferCount];
     ENetOutgoingCommand * outgoingCommand;
-    ENetListIterator currentCommand;
-    ENetChannel *channel;
-    enet_uint16 reliableWindow;
+    ENetListIterator currentCommand, currentSendReliableCommand;
+    ENetChannel *channel = NULL;
+    enet_uint16 reliableWindow = 0;
     size_t commandSize;
-    int windowExceeded = 0, windowWrap = 0, canPing = 1;
+    int windowWrap = 0, canPing = 1;
 
     currentCommand = enet_list_begin (& peer -> outgoingCommands);
-    
-    while (currentCommand != enet_list_end (& peer -> outgoingCommands))
+    currentSendReliableCommand = enet_list_begin (& peer -> outgoingSendReliableCommands);
+
+    for (;;)
     {
-       outgoingCommand = (ENetOutgoingCommand *) currentCommand;
+       if (currentCommand != enet_list_end (& peer -> outgoingCommands))
+       {
+          outgoingCommand = (ENetOutgoingCommand *) currentCommand;
+
+          if (currentSendReliableCommand != enet_list_end (& peer -> outgoingSendReliableCommands) &&
+              ENET_TIME_LESS (((ENetOutgoingCommand *) currentSendReliableCommand) -> queueTime, outgoingCommand -> queueTime))
+            goto useSendReliableCommand;
+
+          currentCommand = enet_list_next (currentCommand);
+       }
+       else
+       if (currentSendReliableCommand != enet_list_end (& peer -> outgoingSendReliableCommands))
+       {
+       useSendReliableCommand:
+          outgoingCommand = (ENetOutgoingCommand *) currentSendReliableCommand;
+          currentSendReliableCommand = enet_list_next (currentSendReliableCommand);
+       }
+       else
+         break;
 
        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
        {
@@ -1406,33 +1445,29 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
           reliableWindow = outgoingCommand -> reliableSequenceNumber / ENET_PEER_RELIABLE_WINDOW_SIZE;
           if (channel != NULL)
           {
-             if (! windowWrap &&      
-                  outgoingCommand -> sendAttempts < 1 && 
+             if (windowWrap)
+               continue;
+             else
+             if (outgoingCommand -> sendAttempts < 1 &&
                   ! (outgoingCommand -> reliableSequenceNumber % ENET_PEER_RELIABLE_WINDOW_SIZE) &&
                   (channel -> reliableWindows [(reliableWindow + ENET_PEER_RELIABLE_WINDOWS - 1) % ENET_PEER_RELIABLE_WINDOWS] >= ENET_PEER_RELIABLE_WINDOW_SIZE ||
                     channel -> usedReliableWindows & ((((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) << reliableWindow) |
                       (((1 << (ENET_PEER_FREE_RELIABLE_WINDOWS + 2)) - 1) >> (ENET_PEER_RELIABLE_WINDOWS - reliableWindow)))))
-                windowWrap = 1;
-             if (windowWrap)
              {
-                currentCommand = enet_list_next (currentCommand);
- 
+                windowWrap = 1;
+                currentSendReliableCommand = enet_list_end (& peer -> outgoingSendReliableCommands);
+
                 continue;
              }
           }
- 
+
           if (outgoingCommand -> packet != NULL)
           {
-             if (! windowExceeded)
-             {
-                enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE;
-             
-                if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu))
-                  windowExceeded = 1;
-             }
-             if (windowExceeded)
+             enet_uint32 windowSize = (peer -> packetThrottle * peer -> windowSize) / ENET_PEER_PACKET_THROTTLE_SCALE;
+
+             if (peer -> reliableDataInTransit + outgoingCommand -> fragmentLength > ENET_MAX (windowSize, peer -> mtu))
              {
-                currentCommand = enet_list_next (currentCommand);
+                currentSendReliableCommand = enet_list_end (& peer -> outgoingSendReliableCommands);
 
                 continue;
              }
@@ -1448,13 +1483,11 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
            (outgoingCommand -> packet != NULL && 
              (enet_uint16) (peer -> mtu - host -> packetSize) < (enet_uint16) (commandSize + outgoingCommand -> fragmentLength)))
        {
-          host -> continueSending = 1;
-          
+          peer -> flags |= ENET_PEER_FLAG_CONTINUE_SENDING;
+
           break;
        }
 
-       currentCommand = enet_list_next (currentCommand);
-
        if (outgoingCommand -> command.header.command & ENET_PROTOCOL_COMMAND_FLAG_ACKNOWLEDGE)
        {
           if (channel != NULL && outgoingCommand -> sendAttempts < 1)
@@ -1466,10 +1499,7 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
           ++ outgoingCommand -> sendAttempts;
  
           if (outgoingCommand -> roundTripTimeout == 0)
-          {
-             outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance;
-             outgoingCommand -> roundTripTimeoutLimit = peer -> timeoutLimit * outgoingCommand -> roundTripTimeout;
-          }
+            outgoingCommand -> roundTripTimeout = peer -> roundTripTime + 4 * peer -> roundTripTimeVariance;
 
           if (enet_list_empty (& peer -> sentReliableCommands))
             peer -> nextTimeout = host -> serviceTime + outgoingCommand -> roundTripTimeout;
@@ -1522,7 +1552,7 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
           enet_list_remove (& outgoingCommand -> outgoingCommandList);
 
           if (outgoingCommand -> packet != NULL)
-            enet_list_insert (enet_list_end (& peer -> sentUnreliableCommands), outgoingCommand);
+            enet_list_insert (enet_list_end (sentUnreliableCommands), outgoingCommand);
        }
 
        buffer -> data = command;
@@ -1555,9 +1585,8 @@ enet_protocol_check_outgoing_commands (ENetHost * host, ENetPeer * peer)
     host -> bufferCount = buffer - host -> buffers;
 
     if (peer -> state == ENET_PEER_STATE_DISCONNECT_LATER &&
-        enet_list_empty (& peer -> outgoingCommands) &&
-        enet_list_empty (& peer -> sentReliableCommands) &&
-        enet_list_empty (& peer -> sentUnreliableCommands))
+        ! enet_peer_has_outgoing_commands (peer) &&
+        enet_list_empty (sentUnreliableCommands))
       enet_peer_disconnect (peer, peer -> eventData);
 
     return canPing;
@@ -1568,22 +1597,24 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
 {
     enet_uint8 headerData [sizeof (ENetProtocolHeader) + sizeof (enet_uint32)];
     ENetProtocolHeader * header = (ENetProtocolHeader *) headerData;
-    ENetPeer * currentPeer;
-    int sentLength;
+    int sentLength = 0;
     size_t shouldCompress = 0;
- 
-    host -> continueSending = 1;
+    ENetList sentUnreliableCommands;
 
-    while (host -> continueSending)
-    for (host -> continueSending = 0,
-           currentPeer = host -> peers;
+    enet_list_clear (& sentUnreliableCommands);
+
+    for (int sendPass = 0, continueSending = 0; sendPass <= continueSending; ++ sendPass)
+    for (ENetPeer * currentPeer = host -> peers;
          currentPeer < & host -> peers [host -> peerCount];
          ++ currentPeer)
     {
         if (currentPeer -> state == ENET_PEER_STATE_DISCONNECTED ||
-            currentPeer -> state == ENET_PEER_STATE_ZOMBIE)
+            currentPeer -> state == ENET_PEER_STATE_ZOMBIE ||
+            (sendPass > 0 && ! (currentPeer -> flags & ENET_PEER_FLAG_CONTINUE_SENDING)))
           continue;
 
+        currentPeer -> flags &= ~ ENET_PEER_FLAG_CONTINUE_SENDING;
+
         host -> headerFlags = 0;
         host -> commandCount = 0;
         host -> bufferCount = 1;
@@ -1600,21 +1631,22 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
             if (event != NULL && event -> type != ENET_EVENT_TYPE_NONE)
               return 1;
             else
-              continue;
+              goto nextPeer;
         }
 
-        if ((enet_list_empty (& currentPeer -> outgoingCommands) ||
-              enet_protocol_check_outgoing_commands (host, currentPeer)) &&
+        if (((enet_list_empty (& currentPeer -> outgoingCommands) &&
+              enet_list_empty (& currentPeer -> outgoingSendReliableCommands)) ||
+             enet_protocol_check_outgoing_commands (host, currentPeer, & sentUnreliableCommands)) &&
             enet_list_empty (& currentPeer -> sentReliableCommands) &&
             ENET_TIME_DIFFERENCE (host -> serviceTime, currentPeer -> lastReceiveTime) >= currentPeer -> pingInterval &&
             currentPeer -> mtu - host -> packetSize >= sizeof (ENetProtocolPing))
         { 
             enet_peer_ping (currentPeer);
-            enet_protocol_check_outgoing_commands (host, currentPeer);
+            enet_protocol_check_outgoing_commands (host, currentPeer, & sentUnreliableCommands);
         }
 
         if (host -> commandCount == 0)
-          continue;
+          goto nextPeer;
 
         if (currentPeer -> packetLossEpoch == 0)
           currentPeer -> packetLossEpoch = host -> serviceTime;
@@ -1625,7 +1657,7 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
            enet_uint32 packetLoss = currentPeer -> packetsLost * ENET_PEER_PACKET_LOSS_SCALE / currentPeer -> packetsSent;
 
 #ifdef ENET_DEBUG
-           printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0);
+           printf ("peer %u: %f%%+-%f%% packet loss, %u+-%u ms round trip time, %f%% throttle, %u outgoing, %u/%u incoming\n", currentPeer -> incomingPeerID, currentPeer -> packetLoss / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> packetLossVariance / (float) ENET_PEER_PACKET_LOSS_SCALE, currentPeer -> roundTripTime, currentPeer -> roundTripTimeVariance, currentPeer -> packetThrottle / (float) ENET_PEER_PACKET_THROTTLE_SCALE, enet_list_size (& currentPeer -> outgoingCommands) + enet_list_size (& currentPeer -> outgoingSendReliableCommands), currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingReliableCommands) : 0, currentPeer -> channels != NULL ? enet_list_size (& currentPeer -> channels -> incomingUnreliableCommands) : 0);
 #endif
 
            currentPeer -> packetLossVariance = (currentPeer -> packetLossVariance * 3 + ENET_DIFFERENCE (packetLoss, currentPeer -> packetLoss)) / 4;
@@ -1687,13 +1719,17 @@ enet_protocol_send_outgoing_commands (ENetHost * host, ENetEvent * event, int ch
 
         sentLength = enet_socket_send (host -> socket, & currentPeer -> address, host -> buffers, host -> bufferCount);
 
-        enet_protocol_remove_sent_unreliable_commands (currentPeer);
+        enet_protocol_remove_sent_unreliable_commands (currentPeer, & sentUnreliableCommands);
 
         if (sentLength < 0)
           return -1;
 
         host -> totalSentData += sentLength;
         host -> totalSentPackets ++;
+
+    nextPeer:
+        if (currentPeer -> flags & ENET_PEER_FLAG_CONTINUE_SENDING)
+          continueSending = sendPass + 1;
     }
    
     return 0;