diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index c90e376e262..37a377e0f03 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -626,6 +626,10 @@ bool DirAccess::is_case_sensitive(const String &p_path) const {
return true;
}
+bool DirAccess::is_equivalent(const String &p_path_a, const String &p_path_b) const {
+ return p_path_a == p_path_b;
+}
+
void DirAccess::_bind_methods() {
ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open);
ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error);
@@ -671,6 +675,7 @@ void DirAccess::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_include_hidden"), &DirAccess::get_include_hidden);
ClassDB::bind_method(D_METHOD("is_case_sensitive", "path"), &DirAccess::is_case_sensitive);
+ ClassDB::bind_method(D_METHOD("is_equivalent", "path_a", "path_b"), &DirAccess::is_equivalent);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden");
diff --git a/core/io/dir_access.h b/core/io/dir_access.h
index f9e1ee10346..511a414f1c6 100644
--- a/core/io/dir_access.h
+++ b/core/io/dir_access.h
@@ -168,6 +168,7 @@ public:
virtual bool is_case_sensitive(const String &p_path) const;
virtual bool is_bundle(const String &p_file) const { return false; }
+ virtual bool is_equivalent(const String &p_path_a, const String &p_path_b) const;
public:
DirAccess() {}
diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml
index cee4d1fe7ac..beb57aef2c3 100644
--- a/doc/classes/DirAccess.xml
+++ b/doc/classes/DirAccess.xml
@@ -248,6 +248,14 @@
[b]Note:[/b] This method is implemented on macOS, Linux (for EXT4 and F2FS filesystems only) and Windows. On other platforms, it always returns [code]true[/code].
+
+
+
+
+
+ Returns [code]true[/code] if paths [param path_a] and [param path_b] resolve to the same file system object. Returns [code]false[/code] otherwise, even if the files are bit-for-bit identical (e.g., identical copies of the file that are not symbolic links).
+
+
diff --git a/drivers/unix/dir_access_unix.cpp b/drivers/unix/dir_access_unix.cpp
index 223dee38afc..1b3757e0b00 100644
--- a/drivers/unix/dir_access_unix.cpp
+++ b/drivers/unix/dir_access_unix.cpp
@@ -43,6 +43,7 @@
#include
#include
#include
+#include
#include
#ifdef HAVE_MNTENT
@@ -544,6 +545,24 @@ bool DirAccessUnix::is_case_sensitive(const String &p_path) const {
return true;
}
+bool DirAccessUnix::is_equivalent(const String &p_path_a, const String &p_path_b) const {
+ String f1 = fix_path(p_path_a);
+ struct stat st1 = {};
+ int err = stat(f1.utf8().get_data(), &st1);
+ if (err) {
+ return DirAccess::is_equivalent(p_path_a, p_path_b);
+ }
+
+ String f2 = fix_path(p_path_b);
+ struct stat st2 = {};
+ err = stat(f2.utf8().get_data(), &st2);
+ if (err) {
+ return DirAccess::is_equivalent(p_path_a, p_path_b);
+ }
+
+ return (st1.st_dev == st2.st_dev) && (st1.st_ino == st2.st_ino);
+}
+
DirAccessUnix::DirAccessUnix() {
dir_stream = nullptr;
_cisdir = false;
diff --git a/drivers/unix/dir_access_unix.h b/drivers/unix/dir_access_unix.h
index a6493d0323b..bfb8600ad55 100644
--- a/drivers/unix/dir_access_unix.h
+++ b/drivers/unix/dir_access_unix.h
@@ -85,6 +85,7 @@ public:
virtual Error create_link(String p_source, String p_target) override;
virtual bool is_case_sensitive(const String &p_path) const override;
+ virtual bool is_equivalent(const String &p_path_a, const String &p_path_b) const override;
virtual uint64_t get_space_left() override;
diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp
index 2817acd3353..0bf89ebcd76 100644
--- a/drivers/windows/dir_access_windows.cpp
+++ b/drivers/windows/dir_access_windows.cpp
@@ -391,6 +391,38 @@ bool DirAccessWindows::is_case_sensitive(const String &p_path) const {
}
}
+typedef struct {
+ ULONGLONG LowPart;
+ ULONGLONG HighPart;
+} GD_FILE_ID_128;
+
+typedef struct {
+ ULONGLONG VolumeSerialNumber;
+ GD_FILE_ID_128 FileId;
+} GD_FILE_ID_INFO;
+
+bool DirAccessWindows::is_equivalent(const String &p_path_a, const String &p_path_b) const {
+ String f1 = fix_path(p_path_a);
+ GD_FILE_ID_INFO st1;
+ HANDLE h1 = ::CreateFileW((LPCWSTR)(f1.utf16().get_data()), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ if (h1 == INVALID_HANDLE_VALUE) {
+ return DirAccess::is_equivalent(p_path_a, p_path_b);
+ }
+ ::GetFileInformationByHandleEx(h1, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st1, sizeof(st1));
+ ::CloseHandle(h1);
+
+ String f2 = fix_path(p_path_b);
+ GD_FILE_ID_INFO st2;
+ HANDLE h2 = ::CreateFileW((LPCWSTR)(f2.utf16().get_data()), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
+ if (h2 == INVALID_HANDLE_VALUE) {
+ return DirAccess::is_equivalent(p_path_a, p_path_b);
+ }
+ ::GetFileInformationByHandleEx(h2, (FILE_INFO_BY_HANDLE_CLASS)0x12 /*FileIdInfo*/, &st2, sizeof(st2));
+ ::CloseHandle(h2);
+
+ return (st1.VolumeSerialNumber == st2.VolumeSerialNumber) && (st1.FileId.LowPart == st2.FileId.LowPart) && (st1.FileId.HighPart == st2.FileId.HighPart);
+}
+
bool DirAccessWindows::is_link(String p_file) {
String f = fix_path(p_file);
diff --git a/drivers/windows/dir_access_windows.h b/drivers/windows/dir_access_windows.h
index 353e27833aa..a7e05f7662a 100644
--- a/drivers/windows/dir_access_windows.h
+++ b/drivers/windows/dir_access_windows.h
@@ -84,6 +84,7 @@ public:
virtual String get_filesystem_type() const override;
virtual bool is_case_sensitive(const String &p_path) const override;
+ virtual bool is_equivalent(const String &p_path_a, const String &p_path_b) const override;
DirAccessWindows();
~DirAccessWindows();