1#include <filedevice/seadPath.h>
2#include <prim/seadSafeString.h>
3
4namespace sead
5{
6bool Path::getDriveName(BufferedSafeString* driveName, const SafeString& path)
7{
8 SEAD_ASSERT_MSG(driveName, "destination buffer is null");
9
10 driveName->trim(trim_length: 0);
11
12 const s32 index = path.findIndex(str: ":");
13 if (index == -1)
14 return false;
15
16 driveName->copy(src: path, copyLength: index);
17 return true;
18}
19
20void Path::getPathExceptDrive(BufferedSafeString* pathNoDrive, const SafeString& path)
21{
22 SEAD_ASSERT_MSG(pathNoDrive, "destination buffer is null");
23
24 pathNoDrive->trim(trim_length: 0);
25
26 s32 index = path.findIndex(str: "://");
27 if (index == -1)
28 pathNoDrive->copyAt(at: 0, src: path);
29
30 else
31 pathNoDrive->copyAt(at: 0, src: path.getPart(at: index + 3));
32}
33
34namespace
35{
36s32 rfindCharIndex(const SafeString& path, char c)
37{
38 const s32 length = path.calcLength();
39 const char* cstr = path.cstr();
40 for (s32 i = length; i >= 0; --i)
41 if (cstr[i] == c)
42 return i;
43 return -1;
44}
45
46char getLastChar(const SafeString& str)
47{
48 return str.at(idx: str.calcLength() - 1);
49}
50} // namespace
51
52// NON_MATCHING: redundant checks for dot_index < 0 in SafeString::getPart() are optimized out
53bool Path::getExt(BufferedSafeString* ext, const SafeString& path)
54{
55 SEAD_ASSERT_MSG(ext, "destination buffer is null");
56
57 ext->trim(trim_length: 0);
58
59 const s32 dot_index = rfindCharIndex(path, c: '.');
60 if (dot_index < 0)
61 return false;
62
63 if (path.getPart(at: dot_index).include(c: '/') || path.getPart(at: dot_index).include(c: '\\'))
64 return false;
65
66 ext->copy(src: path.getPart(at: dot_index + 1));
67 return true;
68}
69
70bool Path::getFileName(BufferedSafeString* name, const SafeString& path)
71{
72 SEAD_ASSERT_MSG(name, "destination buffer is null");
73 name->trim(trim_length: 0);
74
75 const s32 slash_index = rfindCharIndex(path, c: '/');
76 const s32 bslash_index = rfindCharIndex(path, c: '\\');
77 const s32 idx = slash_index > bslash_index ? slash_index : bslash_index;
78 name->copy(src: path.getPart(at: idx + 1));
79 return true;
80}
81
82bool Path::getBaseFileName(BufferedSafeString* name, const SafeString& path)
83{
84 const s32 bslash_index = rfindCharIndex(path, c: '\\');
85 const s32 slash_index = rfindCharIndex(path, c: '/');
86
87 const s32 i = bslash_index > slash_index ? bslash_index : slash_index;
88 const s32 part_idx = i < 0 ? 0 : i + 1;
89
90 s32 dot_idx = rfindCharIndex(path, c: '.');
91 if (dot_idx < 0)
92 dot_idx = path.calcLength();
93
94 name->copy(src: path.getPart(at: part_idx), copyLength: dot_idx - part_idx);
95 return true;
96}
97
98bool Path::getDirectoryName(BufferedSafeString* name, const SafeString& path)
99{
100 SEAD_ASSERT_MSG(name, "destination buffer is null");
101
102 if (name == &path)
103 {
104 const s32 slash_index = rfindCharIndex(path, c: '/');
105 const s32 bslash_index = rfindCharIndex(path, c: '\\');
106 const s32 trim_index = slash_index > bslash_index ? slash_index : bslash_index;
107 if (trim_index < 1)
108 return false;
109
110 name->trim(trim_length: trim_index);
111 }
112 else
113 {
114 name->trim(trim_length: 0);
115
116 const s32 slash_index = rfindCharIndex(path, c: '/');
117 const s32 bslash_index = rfindCharIndex(path, c: '\\');
118 const s32 trim_index = slash_index > bslash_index ? slash_index : bslash_index;
119 if (trim_index < 1)
120 return false;
121
122 name->copy(src: path, copyLength: trim_index);
123 }
124 return true;
125}
126
127void Path::join(BufferedSafeString* out, const char* path1, const char* path2)
128{
129 // Trivial case 1: path1 is empty.
130 if (!path1 || !path1[0])
131 {
132 out->copy(src: path2);
133 return;
134 }
135
136 // Trivial case 2: path2 is empty.
137 if (!path2 || !path2[0])
138 {
139 out->copy(src: path1);
140 return;
141 }
142
143 if (path2[0] == '\\' || path2[0] == '/')
144 {
145 // If path1 also ends with a slash, skip the slash in path2 to avoid getting "//".
146 const char last_char1 = getLastChar(str: path1);
147 if (last_char1 == '\\' || last_char1 == '/')
148 {
149 ++path2;
150 if (!path2[0])
151 {
152 out->copy(src: path1);
153 return;
154 }
155 }
156 out->format(formatStr: "%s%s", path1, path2);
157 }
158 else
159 {
160 // If path1 already ends with a slash, do not insert "/" in the middle to avoid "//".
161 const char last_char1 = getLastChar(str: path1);
162 if (last_char1 == '\\' || last_char1 == '/')
163 out->format(formatStr: "%s%s", path1, path2);
164 else
165 out->format(formatStr: "%s/%s", path1, path2);
166 }
167}
168
169void Path::changeDelimiter(BufferedSafeString* out, char delimiter)
170{
171 const s32 length = out->calcLength();
172 char* buffer = out->getBuffer();
173 for (s32 i = 0; i < length; ++i)
174 {
175 const char c = (*out)[i];
176 if (c == '\\' || c == '/')
177 buffer[i] = delimiter;
178 }
179}
180} // namespace sead
181