@@ -16,19 +16,9 @@ use crate::compat::BindingCompat;
16
16
17
17
pub type InitCompat = sys:: GDExtensionInterfaceGetProcAddress ;
18
18
19
- #[ cfg( not( target_family = "wasm" ) ) ]
20
- #[ repr( C ) ]
21
- struct LegacyLayout {
22
- version_major : u32 ,
23
- version_minor : u32 ,
24
- version_patch : u32 ,
25
- version_string : * const std:: ffi:: c_char ,
26
- }
27
-
28
19
impl BindingCompat for sys:: GDExtensionInterfaceGetProcAddress {
29
- // Fundamentally in wasm function references and data pointers live in different memory
30
- // spaces so trying to read the "memory" at a function pointer (an index into a table) to
31
- // heuristically determine which API we have (as is done below) is not quite going to work.
20
+ // In WebAssembly, function references and data pointers live in different memory spaces, so trying to read the "memory"
21
+ // at a function pointer (an index into a table) to heuristically determine which API we have (as is done below) won't work.
32
22
#[ cfg( target_family = "wasm" ) ]
33
23
fn ensure_static_runtime_compatibility ( & self ) { }
34
24
@@ -56,54 +46,59 @@ impl BindingCompat for sys::GDExtensionInterfaceGetProcAddress {
56
46
// As a result, we can try to interpret the function pointer as a legacy GDExtensionInterface data pointer and check if the
57
47
// first fields have values version_major=4 and version_minor=0. This might be deep in UB territory, but the alternative is
58
48
// to not be able to detect Godot 4.0.x at all, and run into UB anyway.
59
-
60
49
let get_proc_address = self . expect ( "get_proc_address unexpectedly null" ) ;
61
- let data_ptr = get_proc_address as * const LegacyLayout ; // crowbar it via `as` cast
62
-
63
- // Assumption is that we have at least 8 bytes of memory to safely read from (for both the data and the function case).
64
- let major = unsafe { data_ptr. read ( ) . version_major } ;
65
- let minor = unsafe { data_ptr. read ( ) . version_minor } ;
66
- let patch = unsafe { data_ptr. read ( ) . version_patch } ;
67
50
68
- if major != 4 || minor != 0 {
69
- // Technically, major should always be 4; loading Godot 3 will crash anyway.
70
- return ;
51
+ let static_version_str = crate :: GdextBuild :: godot_static_version_string ( ) ;
52
+
53
+ // Strictly speaking, this is NOT the type GDExtensionGodotVersion but a 4.0 legacy version of it. They have the exact same
54
+ // layout, and due to GDExtension's compatibility promise, the 4.1+ struct won't change; so we can reuse the type.
55
+ // We thus read u32 pointers (field by field).
56
+ let data_ptr = get_proc_address as * const u32 ; // crowbar it via `as` cast
57
+
58
+ // SAFETY: borderline UB, but on Desktop systems, we should be able to reinterpret function pointers as data.
59
+ // On 64-bit systems, a function pointer is typically 8 bytes long, meaning we can interpret 8 bytes of it.
60
+ // On 32-bit systems, we can only read the first 4 bytes safely. If that happens to have value 4 (exceedingly unlikely for
61
+ // a function pointer), it's likely that it's the actual version and we run 4.0.x. In that case, read 4 more bytes.
62
+ let major = unsafe { data_ptr. read ( ) } ;
63
+ if major == 4 {
64
+ // SAFETY: see above.
65
+ let minor = unsafe { data_ptr. offset ( 1 ) . read ( ) } ;
66
+ if minor == 0 {
67
+ // SAFETY: at this point it's reasonably safe to say that we are indeed dealing with that version struct; read the whole.
68
+ let data_ptr = get_proc_address as * const sys:: GDExtensionGodotVersion ;
69
+ let runtime_version_str = unsafe { read_version_string ( & data_ptr. read ( ) ) } ;
70
+
71
+ panic ! (
72
+ "gdext was compiled against a newer Godot version: {static_version_str}\n \
73
+ but loaded by legacy Godot binary, with version: {runtime_version_str}\n \
74
+ \n \
75
+ Update your Godot engine version, or read https://godot-rust.github.io/book/toolchain/compatibility.html.\n \
76
+ \n "
77
+ ) ;
78
+ }
71
79
}
72
80
73
- let static_version = crate :: GdextBuild :: godot_static_version_string ( ) ;
74
- let runtime_version = unsafe {
75
- let char_ptr = data_ptr. read ( ) . version_string ;
76
- let c_str = std:: ffi:: CStr :: from_ptr ( char_ptr) ;
77
-
78
- String :: from_utf8_lossy ( c_str. to_bytes ( ) )
79
- . as_ref ( )
80
- . strip_prefix ( "Godot Engine " )
81
- . unwrap_or ( & String :: from_utf8_lossy ( c_str. to_bytes ( ) ) )
82
- . to_string ( )
83
- } ;
84
-
85
- // Version 4.0.999 is used to signal that we're running Godot 4.1+ but loading extensions in legacy mode.
86
- if patch == 999 {
87
- // Godot 4.1+ loading the extension in legacy mode.
88
- // Note: this can not happen as of June 2023 anymore, because Godot disallows loading 4.0 extensions now.
89
- // TODO(bromeon): a while after 4.1 release, remove this branch.
90
- //
91
- // Instead of panicking, we could *theoretically* fall back to the legacy API at runtime, but then gdext would need to
92
- // always ship two versions of gdextension_interface.h (+ generated code) and would encourage use of the legacy API.
93
- panic ! (
94
- "gdext was compiled against a modern Godot version ({static_version}), but loaded in legacy (4.0.x) mode.\n \
95
- In your .gdextension file, add `compatibility_minimum = 4.1` under the [configuration] section.\n "
96
- )
97
- } else {
98
- // Truly a Godot 4.0 version.
81
+ // From here we can assume Godot 4.1+. We need to make sure that the runtime version is >= static version.
82
+ // Lexicographical tuple comparison does that.
83
+ let static_version = crate :: GdextBuild :: godot_static_version_triple ( ) ;
84
+ let runtime_version_raw = self . runtime_version ( ) ;
85
+
86
+ // SAFETY: Godot provides this version struct.
87
+ let runtime_version = (
88
+ runtime_version_raw. major as u8 ,
89
+ runtime_version_raw. minor as u8 ,
90
+ runtime_version_raw. patch as u8 ,
91
+ ) ;
92
+
93
+ if runtime_version < static_version {
94
+ let runtime_version_str = read_version_string ( & runtime_version_raw) ;
95
+
99
96
panic ! (
100
- "gdext was compiled against a newer Godot version ({static_version}),\n \
101
- but loaded by a legacy Godot binary ({runtime_version}).\n \
102
- \n \
103
- Update your Godot engine version.\n \
97
+ "gdext was compiled against newer Godot version: {static_version_str}\n \
98
+ but loaded by older Godot binary, with version: {runtime_version_str}\n \
104
99
\n \
105
- (If you _really_ need an older Godot version, recompile your Rust extension against that one \
106
- (see `custom-godot` feature). However, that setup will not be supported for a long time .\n \
100
+ Update your Godot engine version, or compile gdext against an older version. \n \
101
+ For more information, read https://godot-rust.github.io/book/toolchain/compatibility.html .\n \
107
102
\n "
108
103
) ;
109
104
}
@@ -127,3 +122,16 @@ impl BindingCompat for sys::GDExtensionInterfaceGetProcAddress {
127
122
unsafe { sys:: GDExtensionInterface :: load ( * self ) }
128
123
}
129
124
}
125
+
126
+ fn read_version_string ( version_ptr : & sys:: GDExtensionGodotVersion ) -> String {
127
+ let char_ptr = version_ptr. string ;
128
+
129
+ // SAFETY: `version_ptr` points to a layout-compatible version struct.
130
+ let c_str = unsafe { std:: ffi:: CStr :: from_ptr ( char_ptr) } ;
131
+
132
+ String :: from_utf8_lossy ( c_str. to_bytes ( ) )
133
+ . as_ref ( )
134
+ . strip_prefix ( "Godot Engine " )
135
+ . unwrap_or ( & String :: from_utf8_lossy ( c_str. to_bytes ( ) ) )
136
+ . to_string ( )
137
+ }
0 commit comments