1#include <container/seadBuffer.h>
2#include <prim/seadPtrUtil.h>
3#include <prim/seadSafeString.h>
4#include <resource/seadSharcArchiveRes.h>
5
6namespace
7{
8u32 calcHash32(const sead::SafeString& str, u32 key)
9{
10 const char* str_ = str.cstr();
11
12 u32 result = 0;
13 // Each character must be treated as a signed value.
14 // The cast to s8 (not s32) is necessary to avoid unsigned conversions.
15 for (s32 i = 0; str_[i] != '\0'; i++)
16 result = result * key + s8(str_[i]);
17
18 return result;
19}
20
21#ifdef NNSDK
22s32 binarySearch_(u32 hash, const sead::SharcArchiveRes::FATEntry* buffer, s32 start, s32 end,
23 sead::Endian::Types endian)
24#else
25s32 binarySearch_(u32 hash, const sead::SharcArchiveRes::FATEntry* buffer, s32 start, s32 end)
26#endif
27{
28 s32 middle;
29
30 for (;;)
31 {
32 middle = (start + end) / 2;
33
34#ifdef NNSDK
35 u32 entryHash = sead::Endian::toHostU32(from: endian, v: buffer[middle].hash);
36#else
37 u32 entryHash = buffer[middle].hash;
38#endif
39 if (entryHash == hash)
40 return middle;
41
42 else if (entryHash < hash)
43 {
44 if (start == middle)
45 return -1;
46
47 start = middle;
48 }
49
50 else
51 {
52 if (end == middle)
53 return -1;
54
55 end = middle;
56 }
57 }
58}
59
60} // namespace
61
62namespace sead
63{
64struct SharcArchiveRes::HandleInner
65{
66 u32 x;
67};
68
69SharcArchiveRes::SharcArchiveRes()
70 : ArchiveRes(), mArchiveBlockHeader(NULL), mFATBlockHeader(NULL), mFNTBlock(NULL),
71 mDataBlock(NULL)
72#ifdef cafe
73 ,
74 mEndianType(Endian::cBig)
75#else
76 ,
77 mEndianType(Endian::cLittle)
78#endif
79{
80}
81
82SharcArchiveRes::~SharcArchiveRes() {}
83
84const void* SharcArchiveRes::getFileImpl_(const SafeString& file_path, FileInfo* file_info) const
85{
86 s32 id = convertPathToEntryIDImpl_(file_path);
87 if (id < 0)
88 return NULL;
89
90 return getFileFastImpl_(entry_id: id, file_info);
91}
92
93const void* SharcArchiveRes::getFileFastImpl_(s32 entry_id, FileInfo* file_info) const
94{
95 if (entry_id < 0 || entry_id >= mFATEntrys.size())
96 return NULL;
97
98 u32 start = Endian::toHostU32(from: mEndianType, v: mFATEntrys(entry_id).data_start_offset);
99
100 if (file_info != NULL)
101 {
102 u32 end = Endian::toHostU32(from: mEndianType, v: mFATEntrys(entry_id).data_end_offset);
103 if (start > end)
104 return NULL;
105
106 u32 length = end - start;
107
108 file_info->mStartOffset = start;
109 file_info->mLength = length;
110 }
111
112 return mDataBlock + start;
113}
114
115s32 SharcArchiveRes::convertPathToEntryIDImpl_(const SafeString& file_path) const
116{
117 u32 hash = calcHash32(str: file_path, key: Endian::toHostU32(from: mEndianType, v: mFATBlockHeader->hash_key));
118
119 s32 start = 0;
120 s32 end = mFATEntrys.size();
121
122#ifdef NNSDK
123 s32 id = binarySearch_(hash, buffer: mFATEntrys.getBufferPtr(), start, end, endian: mEndianType);
124#else
125 s32 id = binarySearch_(hash, mFATEntrys.getBufferPtr(), start, end);
126#endif
127 if (id == -1)
128 return -1;
129
130 u32 offset = Endian::toHostU32(from: mEndianType, v: mFATEntrys(id).name_offset);
131 if (offset != 0)
132 {
133 id -= (offset >> 24) - 1;
134
135 while (id < end)
136 {
137 const FATEntry* entry = mFATEntrys.unsafeGet(idx: id);
138 if (Endian::toHostU32(from: mEndianType, v: entry->hash) != hash)
139 return -1;
140
141 else
142 {
143 u32 offset_ = Endian::toHostU32(from: mEndianType, v: entry->name_offset);
144
145 if (PtrUtil::addOffset(ptr: mFNTBlock, offset: offset_ & 0xffffff) > mDataBlock)
146 {
147 SEAD_ASSERT_MSG(false, "Invalid data start offset");
148 return -1;
149 }
150
151 if (file_path.isEqual(str: mFNTBlock + (offset_ & 0xffffff) * cFileNameTableAlign))
152 return id;
153 }
154
155 id++;
156 }
157 }
158
159 return id;
160}
161
162bool SharcArchiveRes::setCurrentDirectoryImpl_(const SafeString&)
163{
164 SEAD_ASSERT_MSG(false, "Not support.");
165 return false;
166}
167
168bool SharcArchiveRes::openDirectoryImpl_(HandleBuffer* handle, const SafeString& path) const
169{
170 if (path.isEmpty() || path == "/")
171 {
172 getHandleInner_(handle)->x = 0;
173 return true;
174 }
175
176 SEAD_WARN("dir_path[%s] is not allowed to open sharc directory. must be root.", path.cstr());
177 return false;
178}
179
180bool SharcArchiveRes::closeDirectoryImpl_(HandleBuffer*) const
181{
182 return true;
183}
184
185u32 SharcArchiveRes::readDirectoryImpl_(HandleBuffer* handle_, DirectoryEntry* entry, u32 num) const
186{
187 auto* handle = getHandleInner_(handle: handle_);
188 u32 count = 0;
189
190 while (handle->x + count < Endian::toHostU16(from: mEndianType, v: mFATBlockHeader->file_num) &&
191 count < num)
192 {
193 u32 id = handle->x + count;
194 SEAD_ASSERT(id >= handle->x);
195
196 u32 offset = Endian::toHostU32(from: mEndianType, v: mFATEntrys(id).name_offset);
197 if (offset == 0)
198 entry[count].name.format(formatStr: "%08x", Endian::toHostU32(from: mEndianType, v: mFATEntrys(id).hash));
199 else
200 {
201 if (reinterpret_cast<const u8*>(mFNTBlock + (offset & 0xffffff)) > mDataBlock)
202 {
203 SEAD_WARN("Invalid data start offset");
204 entry[count].name.clear();
205 }
206 else
207 entry[count].name.copy(src: mFNTBlock + (offset & 0xffffff) * cFileNameTableAlign);
208 }
209
210 entry[count].is_directory = false;
211 count++;
212 }
213
214 handle->x += count;
215 return count;
216}
217
218bool SharcArchiveRes::prepareArchive_(const void* archive)
219{
220 if (archive == nullptr)
221 {
222 SEAD_ASSERT_MSG(false, "archive must not be nullptr.");
223 return false;
224 }
225
226 const u8* archive_ = reinterpret_cast<const u8*>(archive);
227
228 mArchiveBlockHeader = reinterpret_cast<const ArchiveBlockHeader*>(archive_);
229 if (std::memcmp(mArchiveBlockHeader->signature, "SARC", 4) != 0)
230 {
231 SEAD_ASSERT_MSG(false, "Invalid ArchiveBlockHeader");
232 return false;
233 }
234
235 mEndianType = Endian::markToEndian(bom: mArchiveBlockHeader->byte_order);
236
237 if (Endian::toHostU16(from: mEndianType, v: mArchiveBlockHeader->version) != cArchiveVersion)
238 {
239 SEAD_ASSERT_MSG(false, "unmatching version ( expect: %x, actual: %x )", cArchiveVersion,
240 mArchiveBlockHeader->version);
241 return false;
242 }
243
244 if (Endian::toHostU16(from: mEndianType, v: mArchiveBlockHeader->header_size) !=
245 sizeof(ArchiveBlockHeader))
246 {
247 SEAD_ASSERT_MSG(false, "Invalid ArchiveBlockHeader");
248 return false;
249 }
250
251 mFATBlockHeader = reinterpret_cast<const FATBlockHeader*>(
252 archive_ + Endian::toHostU16(from: mEndianType, v: mArchiveBlockHeader->header_size));
253 if (std::memcmp(mFATBlockHeader->signature, "SFAT", 4) != 0)
254 {
255 SEAD_ASSERT_MSG(false, "Invalid FATBlockHeader");
256 return false;
257 }
258
259 if (Endian::toHostU16(from: mEndianType, v: mFATBlockHeader->header_size) != sizeof(FATBlockHeader))
260 {
261 SEAD_ASSERT_MSG(false, "Invalid FATBlockHeader");
262 return false;
263 }
264
265 if (Endian::toHostU16(from: mEndianType, v: mFATBlockHeader->file_num) > cArchiveEntryMax)
266 {
267 SEAD_ASSERT_MSG(false, "Invalid FATBlockHeader");
268 return false;
269 }
270
271 mFATEntrys.setBuffer(
272 size: Endian::toHostU16(from: mEndianType, v: mFATBlockHeader->file_num),
273 bufferptr: const_cast<FATEntry*>(reinterpret_cast<const FATEntry*>(
274 archive_ + Endian::toHostU16(from: mEndianType, v: mArchiveBlockHeader->header_size) +
275 Endian::toHostU16(from: mEndianType, v: mFATBlockHeader->header_size))));
276
277 auto* fnt_header = reinterpret_cast<const FNTBlockHeader*>(
278 archive_ + Endian::toHostU16(from: mEndianType, v: mArchiveBlockHeader->header_size) +
279 Endian::toHostU16(from: mEndianType, v: mFATBlockHeader->header_size) +
280 Endian::toHostU16(from: mEndianType, v: mFATBlockHeader->file_num) * sizeof(FATEntry));
281 if (std::memcmp(fnt_header->signature, "SFNT", 4) != 0)
282 {
283 SEAD_ASSERT_MSG(false, "Invalid FNTBlockHeader");
284 return false;
285 }
286
287 if (Endian::toHostU16(from: mEndianType, v: fnt_header->header_size) != sizeof(FNTBlockHeader))
288 {
289 SEAD_ASSERT_MSG(false, "Invalid FNTBlockHeader");
290 return false;
291 }
292
293 mFNTBlock = reinterpret_cast<const char*>(fnt_header) +
294 Endian::toHostU16(from: mEndianType, v: fnt_header->header_size);
295 if (Endian::toHostU32(from: mEndianType, v: mArchiveBlockHeader->data_block_offset) <
296 PtrUtil::diff(ptr1: mFNTBlock, ptr2: mArchiveBlockHeader))
297 {
298 SEAD_ASSERT_MSG(false, "Invalid data block offset");
299 return false;
300 }
301
302 mDataBlock = archive_ + Endian::toHostU32(from: mEndianType, v: mArchiveBlockHeader->data_block_offset);
303 return true;
304}
305
306SharcArchiveRes::HandleInner* SharcArchiveRes::getHandleInner_(HandleBuffer* handle,
307 bool create_new) const
308{
309 static_assert(sizeof(HandleInner) <= sizeof(HandleBuffer));
310 if (create_new)
311 return new (handle) HandleInner;
312 return reinterpret_cast<HandleInner*>(handle);
313}
314
315bool SharcArchiveRes::isExistFileImpl_(const SafeString& path) const
316{
317 const u32 hash = calcHash32(str: path, key: Endian::toHostU32(from: mEndianType, v: mFATBlockHeader->hash_key));
318 const u32 size = mFATEntrys.size();
319#ifdef NNSDK
320 const s32 id = binarySearch_(hash, buffer: mFATEntrys.getBufferPtr(), start: 0, end: size, endian: mEndianType);
321#else
322 const s32 id = binarySearch_(hash, mFATEntrys.getBufferPtr(), 0, size);
323#endif
324 return id != -1;
325}
326} // namespace sead
327