1// MIT License
2
3// Copyright (c) 2017 Vadim Grigoruk @nesbox // grigoruk@gmail.com
4
5// Permission is hereby granted, free of charge, to any person obtaining a copy
6// of this software and associated documentation files (the "Software"), to deal
7// in the Software without restriction, including without limitation the rights
8// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9// copies of the Software, and to permit persons to whom the Software is
10// furnished to do so, subject to the following conditions:
11
12// The above copyright notice and this permission notice shall be included in all
13// copies or substantial portions of the Software.
14
15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21// SOFTWARE.
22
23#include "studio.h"
24#include "fs.h"
25#include "net.h"
26
27#if defined(BAREMETALPI) || defined(_3DS)
28 #ifdef EN_DEBUG
29 #define dbg(...) printf(__VA_ARGS__)
30 #else
31 #define dbg(...)
32 #endif
33#endif
34
35#if defined(BAREMETALPI)
36#include "../../circle-stdlib/libs/circle/addon/fatfs/ff.h"
37#else
38#include <dirent.h>
39#include <sys/stat.h>
40#include <sys/types.h>
41#endif
42
43#if defined(TIC_BUILD_WITH_LUA)
44
45#include <lua.h>
46#include <lauxlib.h>
47#include <lualib.h>
48
49#endif
50
51#if defined(__TIC_WINDOWS__)
52#include <direct.h>
53#include <windows.h>
54#else
55#include <unistd.h>
56#endif
57
58#if defined(__EMSCRIPTEN__)
59#include <emscripten.h>
60#endif
61
62static const char* PublicDir = TIC_HOST;
63
64struct tic_fs
65{
66 char dir[TICNAME_MAX];
67 char work[TICNAME_MAX];
68 tic_net* net;
69};
70
71#if defined(__EMSCRIPTEN__)
72void syncfs()
73{
74 EM_ASM({Module.syncFSRequests++;});
75}
76#endif
77
78const char* tic_fs_pathroot(tic_fs* fs, const char* name)
79{
80 static char path[TICNAME_MAX];
81
82 snprintf(path, sizeof path, "%s%s", fs->dir, name);
83
84#if defined(__TIC_WINDOWS__)
85 char* ptr = path;
86 while (*ptr)
87 {
88 if (*ptr == '/') *ptr = '\\';
89 ptr++;
90 }
91#endif
92
93 return path;
94}
95
96const char* tic_fs_path(tic_fs* fs, const char* name)
97{
98 static char path[TICNAME_MAX];
99
100 if(*name == '/')
101 strncpy(path, name + 1, sizeof path);
102 else if(strlen(fs->work))
103 snprintf(path, sizeof path, "%s/%s", fs->work, name);
104 else
105 strncpy(path, name, sizeof path);
106
107 return tic_fs_pathroot(fs, path);
108}
109
110static bool isRoot(tic_fs* fs)
111{
112 return fs->work[0] == '\0';
113}
114
115static bool isPublicRoot(tic_fs* fs)
116{
117 return strcmp(fs->work, PublicDir) == 0;
118}
119
120static bool isPublic(tic_fs* fs)
121{
122 return memcmp(fs->work, PublicDir, STRLEN(PublicDir)) == 0;
123}
124
125bool tic_fs_ispubdir(tic_fs* fs)
126{
127 return isPublic(fs);
128}
129
130#if defined(__TIC_WINDOWS__)
131
132typedef wchar_t FsString;
133
134#define __S(x) L ## x
135#define _S(x) __S(x)
136
137static const FsString* utf8ToString(const char* str)
138{
139 FsString* wstr = malloc(TICNAME_MAX * sizeof(FsString));
140
141 MultiByteToWideChar(CP_UTF8, 0, str, TICNAME_MAX, wstr, TICNAME_MAX);
142
143 return wstr;
144}
145
146static const char* stringToUtf8(const FsString* wstr)
147{
148 char* str = malloc(TICNAME_MAX * sizeof(char));
149
150 WideCharToMultiByte(CP_UTF8, 0, wstr, TICNAME_MAX, str, TICNAME_MAX, 0, 0);
151
152 return str;
153}
154
155#define freeString(S) free((void*)S)
156
157
158#define TIC_DIR _WDIR
159#define tic_dirent _wdirent
160#define tic_stat_struct _stat
161
162#define tic_opendir _wopendir
163#define tic_readdir _wreaddir
164#define tic_closedir _wclosedir
165#define tic_rmdir _wrmdir
166#define tic_stat _wstat
167#define tic_remove _wremove
168#define tic_fopen _wfopen
169#define tic_mkdir(name) _wmkdir(name)
170#define tic_strncpy wcsncpy
171#define tic_strncat wcsncat
172
173#else
174
175typedef char FsString;
176
177#define _S(x) (x)
178
179#define utf8ToString(S) (S)
180#define stringToUtf8(S) (S)
181
182#define freeString(S)
183
184#define TIC_DIR DIR
185#define tic_dirent dirent
186#define tic_stat_struct stat
187
188#define tic_opendir opendir
189#define tic_readdir readdir
190#define tic_closedir closedir
191#define tic_rmdir rmdir
192#define tic_stat stat
193#define tic_remove remove
194#define tic_fopen fopen
195#define tic_mkdir(name) mkdir(name, 0700)
196#define tic_strncpy strncpy
197#define tic_strncat strncat
198
199#endif
200
201typedef struct
202{
203 fs_list_callback item;
204 fs_done_callback done;
205 void* data;
206} NetDirData;
207
208#if defined(TIC_BUILD_WITH_LUA)
209
210static lua_State* netLuaInit(u8* buffer, s32 size)
211{
212 if (buffer && size)
213 {
214 char* script = calloc(1, size + 1);
215 memcpy(script, buffer, size);
216
217 lua_State* lua = luaL_newstate();
218
219 if(lua)
220 {
221 if(luaL_loadstring(lua, script) == LUA_OK && lua_pcall(lua, 0, LUA_MULTRET, 0) == LUA_OK)
222 return lua;
223
224 else lua_close(lua);
225 }
226
227 free(script);
228 }
229
230 return NULL;
231}
232
233static void onDirResponse(const net_get_data* netData)
234{
235 NetDirData* netDirData = (NetDirData*)netData->calldata;
236
237 if(netData->type == net_get_done)
238 {
239 lua_State* lua = netLuaInit(netData->done.data, netData->done.size);
240
241 if (lua)
242 {
243 {
244 lua_getglobal(lua, "folders");
245
246 if (lua_type(lua, -1) == LUA_TTABLE)
247 {
248 s32 count = (s32)lua_rawlen(lua, -1);
249
250 for (s32 i = 1; i <= count; i++)
251 {
252 lua_geti(lua, -1, i);
253
254 {
255 lua_getfield(lua, -1, "name");
256 if (lua_isstring(lua, -1))
257 netDirData->item(lua_tostring(lua, -1), NULL, NULL, 0, netDirData->data, true);
258
259 lua_pop(lua, 1);
260 }
261
262 lua_pop(lua, 1);
263 }
264 }
265
266 lua_pop(lua, 1);
267 }
268
269 {
270 lua_getglobal(lua, "files");
271
272 if (lua_type(lua, -1) == LUA_TTABLE)
273 {
274 s32 count = (s32)lua_rawlen(lua, -1);
275
276 for (s32 i = 1; i <= count; i++)
277 {
278 lua_geti(lua, -1, i);
279
280 char hash[TICNAME_MAX];
281 char name[TICNAME_MAX];
282 char title[TICNAME_MAX];
283
284 {
285 lua_getfield(lua, -1, "hash");
286 if (lua_isstring(lua, -1))
287 strncpy(hash, lua_tostring(lua, -1), sizeof hash);
288
289 lua_pop(lua, 1);
290 }
291
292 {
293 lua_getfield(lua, -1, "filename");
294
295 if (lua_isstring(lua, -1))
296 strncpy(name, lua_tostring(lua, -1), sizeof name);
297
298 lua_pop(lua, 1);
299 }
300
301 {
302 lua_getfield(lua, -1, "name");
303
304 if (lua_isstring(lua, -1))
305 strncpy(title, lua_tostring(lua, -1), sizeof title);
306
307 lua_pop(lua, 1);
308 }
309
310 {
311 lua_getfield(lua, -1, "id");
312
313 if (lua_isinteger(lua, -1))
314 netDirData->item(name, title, hash, (s32)lua_tointeger(lua, -1), netDirData->data, false);
315
316 lua_pop(lua, 1);
317 }
318
319 lua_pop(lua, 1);
320 }
321 }
322
323 lua_pop(lua, 1);
324 }
325
326 lua_close(lua);
327 }
328 }
329
330 switch (netData->type)
331 {
332 case net_get_done:
333 case net_get_error:
334 netDirData->done(netDirData->data);
335 free(netDirData);
336 break;
337 default: break;
338 }
339}
340
341#endif
342
343static void enumFiles(tic_fs* fs, const char* path, fs_list_callback callback, void* data)
344{
345#if defined(BAREMETALPI)
346 dbg("enumFiles %s", path);
347
348 if (path && *path) {
349 // ok
350 }
351 else
352 {
353 return;
354 }
355
356 static char path2[TICNAME_MAX];
357 strncpy(path2, path, sizeof path2);
358
359 if (path2[strlen(path2) - 1] == '/') // one character
360 path2[strlen(path2) - 1] = 0;
361
362 dbg("enumFiles Real %s", path2);
363
364
365 DIR Directory;
366 FILINFO FileInfo;
367 FRESULT Result = f_findfirst (&Directory, &FileInfo, path2, "*");
368 dbg("enumFilesRes %d", Result);
369
370 for (unsigned i = 0; Result == FR_OK && FileInfo.fname[0]; i++)
371 {
372 if (!(FileInfo.fattrib & (AM_HID | AM_SYS)))
373 {
374 bool result = callback(FileInfo.fname, NULL, NULL, 0, data, FileInfo.fattrib & AM_DIR);
375
376 if (!result)
377 {
378 break;
379 }
380 }
381
382 Result = f_findnext (&Directory, &FileInfo);
383 }
384
385#else
386 TIC_DIR *dir = NULL;
387 struct tic_dirent* ent = NULL;
388
389 const FsString* pathString = utf8ToString(path);
390
391 if ((dir = tic_opendir(pathString)) != NULL)
392 {
393 FsString fullPath[TICNAME_MAX];
394 struct tic_stat_struct s;
395
396 while ((ent = tic_readdir(dir)) != NULL)
397 {
398 if(*ent->d_name != _S('.'))
399 {
400 tic_strncpy(fullPath, pathString, COUNT_OF(fullPath));
401 tic_strncat(fullPath, ent->d_name, COUNT_OF(fullPath));
402
403 if(tic_stat(fullPath, &s) == 0)
404 {
405 const char* name = stringToUtf8(ent->d_name);
406 bool result = callback(name, NULL, NULL, 0, data, S_ISDIR(s.st_mode));
407 freeString(name);
408
409 if(!result) break;
410 }
411 }
412 }
413
414 tic_closedir(dir);
415 }
416
417 freeString(pathString);
418#endif
419}
420
421void tic_fs_enum(tic_fs* fs, fs_list_callback onItem, fs_done_callback onDone, void* data)
422{
423 if (isRoot(fs) && !onItem(PublicDir, NULL, NULL, 0, data, true))
424 {
425 onDone(data);
426 return;
427 }
428
429#if defined(TIC_BUILD_WITH_LUA) && defined(BUILD_EDITORS)
430 if(isPublic(fs))
431 {
432 char request[TICNAME_MAX];
433 snprintf(request, sizeof request, "/api?fn=dir&path=%s", fs->work + sizeof(TIC_HOST));
434
435 NetDirData netDirData = { onItem, onDone, data };
436 tic_net_get(fs->net, request, onDirResponse, MOVE(netDirData));
437
438 return;
439 }
440#endif
441
442 const char* path = tic_fs_path(fs, "");
443
444 enumFiles(fs, path, onItem, data);
445
446 onDone(data);
447}
448
449bool tic_fs_deldir(tic_fs* fs, const char* name)
450{
451#if defined(BAREMETALPI)
452 // TODO BAREMETALPI
453 dbg("tic_fs_deldir %s", name);
454 return 0;
455#else
456#if defined(__TIC_WINDOWS__)
457 const char* path = tic_fs_path(fs, name);
458
459 const FsString* pathString = utf8ToString(path);
460 bool result = tic_rmdir(pathString);
461 freeString(pathString);
462
463#else
464 bool result = rmdir(tic_fs_path(fs, name));
465#endif
466
467#if defined(__EMSCRIPTEN__)
468 syncfs();
469#endif
470
471 return result;
472#endif
473}
474
475bool tic_fs_delfile(tic_fs* fs, const char* name)
476{
477#if defined(BAREMETALPI)
478 dbg("tic_fs_delfile %s", name);
479 // TODO BAREMETALPI
480 return false;
481#else
482 const char* path = tic_fs_path(fs, name);
483
484 const FsString* pathString = utf8ToString(path);
485 bool result = tic_remove(pathString);
486 freeString(pathString);
487
488#if defined(__EMSCRIPTEN__)
489 syncfs();
490#endif
491
492 return result;
493#endif
494}
495
496void tic_fs_homedir(tic_fs* fs)
497{
498 memset(fs->work, 0, sizeof fs->work);
499}
500
501void tic_fs_dirback(tic_fs* fs)
502{
503 if(isPublicRoot(fs))
504 {
505 tic_fs_homedir(fs);
506 return;
507 }
508
509 char* start = fs->work;
510 char* ptr = start + strlen(fs->work);
511
512 while (ptr > start && *ptr != '/') ptr--;
513 while (*ptr) *ptr++ = '\0';
514}
515
516void tic_fs_dir(tic_fs* fs, char* dir)
517{
518 strncpy(dir, fs->work, TICNAME_MAX);
519}
520
521void tic_fs_changedir(tic_fs* fs, const char* dir)
522{
523 if(strlen(fs->work))
524 strncat(fs->work, "/", TICNAME_MAX);
525
526 strcat(fs->work, dir);
527
528#if defined(__TIC_WINDOWS__)
529 for(char *ptr = fs->work, *end = ptr + strlen(ptr); ptr < end; ptr++)
530 if(*ptr == '\\')
531 *ptr = '/';
532#endif
533}
534
535typedef struct
536{
537 char* name;
538 bool found;
539 fs_isdir_callback done;
540 void* data;
541
542} EnumPublicDirsData;
543
544static bool onEnumPublicDirs(const char* name, const char* title, const char* hash, s32 id, void* data, bool dir)
545{
546 EnumPublicDirsData* enumPublicDirsData = (EnumPublicDirsData*)data;
547
548 if(strcmp(name, enumPublicDirsData->name) == 0)
549 {
550 enumPublicDirsData->found = true;
551 return false;
552 }
553
554 return true;
555}
556
557static void onEnumPublicDirsDone(void* data)
558{
559 EnumPublicDirsData* enumPublicDirsData = data;
560 enumPublicDirsData->done(enumPublicDirsData->found, enumPublicDirsData->data);
561 free(enumPublicDirsData->name);
562 free(enumPublicDirsData);
563}
564
565bool tic_fs_isdir(tic_fs* fs, const char* name)
566{
567 if (*name == '.') return false;
568 if (isRoot(fs) && strcmp(name, PublicDir) == 0) return true;
569
570#if defined(BAREMETALPI)
571 dbg("fsIsDirSync %s\n", name);
572 FILINFO s;
573 const char* path = tic_fs_path(fs, name);
574 FRESULT res = f_stat(path, &s);
575 if (res != FR_OK) return false;
576
577 return s.fattrib & AM_DIR;
578#else
579 const char* path = tic_fs_path(fs, name);
580 struct tic_stat_struct s;
581 const FsString* pathString = utf8ToString(path);
582 bool ret = tic_stat(pathString, &s) == 0 && S_ISDIR(s.st_mode);
583 freeString(pathString);
584
585 return ret;
586#endif
587}
588
589void tic_fs_isdir_async(tic_fs* fs, const char* name, fs_isdir_callback callback, void* data)
590{
591 if(isPublic(fs))
592 {
593 EnumPublicDirsData enumPublicDirsData = { strdup(name), false, callback, data};
594 tic_fs_enum(fs, onEnumPublicDirs, onEnumPublicDirsDone, MOVE(enumPublicDirsData));
595 return;
596 }
597
598 callback(tic_fs_isdir(fs, name), data);
599}
600
601bool fs_write(const char* name, const void* buffer, s32 size)
602{
603#if defined(BAREMETALPI)
604 dbg("fs_write %s\n", name);
605 FIL File;
606 FRESULT res = f_open (&File, name, FA_WRITE | FA_CREATE_ALWAYS);
607 if (res != FR_OK)
608 {
609 return false;
610 }
611
612 u32 written=0;
613
614 res = f_write(&File, buffer, size, &written);
615
616 f_close(&File);
617 if (res != FR_OK)
618 {
619 return false;
620 }
621 if(written!=size)
622 {
623 dbg("Write size diff %d %d", size, written);
624 return false;
625 }
626 return true;
627#else
628 const FsString* pathString = utf8ToString(name);
629 FILE* file = tic_fopen(pathString, _S("wb"));
630 freeString(pathString);
631
632 if(file)
633 {
634 fwrite(buffer, 1, size, file);
635 fclose(file);
636
637#if defined(__EMSCRIPTEN__)
638 syncfs();
639#endif
640
641 return true;
642 }
643
644 return false;
645#endif
646}
647
648void* fs_read(const char* path, s32* size)
649{
650#if defined(BAREMETALPI)
651 dbg("fs_read %s\n", path);
652 FILINFO fi;
653 FRESULT res = f_stat(path, &fi);
654 if(res!=FR_OK) return NULL;
655 FIL file;
656 res = f_open (&file, path, FA_READ | FA_OPEN_EXISTING);
657 if(res!=FR_OK) return NULL;
658 *size = fi.fsize; // size is in output!
659 void* buffer = malloc(*size);
660 UINT read = 0;
661 res = f_read(&file, buffer, fi.fsize, &read);
662
663 f_close(&file);
664 if(read!=(*size)) return NULL;
665
666 return buffer;
667
668#else
669 const FsString* pathString = utf8ToString(path);
670 FILE* file = tic_fopen(pathString, _S("rb"));
671 freeString(pathString);
672
673 void* buffer = NULL;
674
675 if(file)
676 {
677
678 fseek(file, 0, SEEK_END);
679 *size = ftell(file);
680 fseek(file, 0, SEEK_SET);
681
682 if((buffer = malloc(*size)) && fread(buffer, *size, 1, file)) {}
683
684 fclose(file);
685 }
686
687 return buffer;
688
689#endif
690}
691
692bool fs_exists(const char* name)
693{
694#if defined(BAREMETALPI)
695 dbg("fs_exists %s\n", name);
696 FILINFO s;
697
698 FRESULT res = f_stat(name, &s);
699 return res == FR_OK;
700#else
701 struct tic_stat_struct s;
702
703 const FsString* pathString = utf8ToString(name);
704 bool ret = tic_stat(pathString, &s) == 0;
705 freeString(pathString);
706
707 return ret;
708#endif
709}
710
711bool tic_fs_exists(tic_fs* fs, const char* name)
712{
713 return fs_exists(tic_fs_path(fs, name));
714}
715
716u64 fs_date(const char* path)
717{
718#if defined(BAREMETALPI)
719 dbg("fs_date %s\n", path);
720 // TODO BAREMETALPI
721 return 0;
722#else
723 struct tic_stat_struct s;
724
725 const FsString* pathString = utf8ToString(path);
726 s32 ret = tic_stat(pathString, &s);
727 freeString(pathString);
728
729 if(ret == 0 && S_ISREG(s.st_mode))
730 {
731 return s.st_mtime;
732 }
733
734 return 0;
735#endif
736}
737
738bool tic_fs_save(tic_fs* fs, const char* name, const void* data, s32 size, bool overwrite)
739{
740 if(!overwrite)
741 {
742 if(tic_fs_exists(fs, name))
743 return false;
744 }
745
746 return fs_write(tic_fs_path(fs, name), data, size);
747}
748
749bool tic_fs_saveroot(tic_fs* fs, const char* name, const void* data, s32 size, bool overwrite)
750{
751 const char* path = tic_fs_pathroot(fs, name);
752
753 if(!overwrite)
754 {
755 if(fs_exists(path))
756 return false;
757 }
758
759 return fs_write(path, data, size);
760}
761
762typedef struct
763{
764 tic_fs* fs;
765 fs_load_callback done;
766 void* data;
767 char* cachePath;
768} LoadFileByHashData;
769
770static void fileByHashLoaded(const net_get_data* netData)
771{
772 LoadFileByHashData* loadFileByHashData = netData->calldata;
773
774 if (netData->type == net_get_done)
775 {
776 tic_fs_saveroot(loadFileByHashData->fs, loadFileByHashData->cachePath, netData->done.data, netData->done.size, false);
777 loadFileByHashData->done(netData->done.data, netData->done.size, loadFileByHashData->data);
778 }
779
780 switch (netData->type)
781 {
782 case net_get_done:
783 case net_get_error:
784
785 free(loadFileByHashData->cachePath);
786 free(loadFileByHashData);
787 break;
788 default: break;
789 }
790}
791
792void tic_fs_hashload(tic_fs* fs, const char* name, const char* hash, fs_load_callback callback, void* data)
793{
794#if defined(BAREMETALPI)
795 // TODO BAREMETALPI
796 return;
797#else
798
799 char cachePath[TICNAME_MAX];
800 snprintf(cachePath, sizeof cachePath, TIC_CACHE "%s.tic", hash);
801
802 {
803 s32 size = 0;
804 void* buffer = tic_fs_loadroot(fs, cachePath, &size);
805 if (buffer)
806 {
807 callback(buffer, size, data);
808 return;
809 }
810 }
811
812#if defined(BUILD_EDITORS)
813 char path[TICNAME_MAX];
814 snprintf(path, sizeof path, "/cart/%s/%s", hash, name);
815
816 LoadFileByHashData loadFileByHashData = { fs, callback, data, strdup(cachePath) };
817 tic_net_get(fs->net, path, fileByHashLoaded, MOVE(loadFileByHashData));
818#endif
819
820#endif
821}
822
823void* tic_fs_load(tic_fs* fs, const char* name, s32* size)
824{
825#if defined(BAREMETALPI)
826 dbg("tic_fs_load x %s\n", name);
827 dbg("fs.dir %s\n", fs->dir);
828 dbg("fs.work %s\n", fs->work);
829
830 if(isPublic(fs))
831 {
832 dbg("Public ??\n");
833 return NULL;
834 }
835 else
836 {
837 dbg("non public \n");
838 const char* fp = tic_fs_path(fs, name);
839 dbg("loading: %s\n", fp);
840
841
842 FILINFO fi;
843 FRESULT res = f_stat(fp, &fi);
844 dbg("fstat done %d \n", res);
845
846 if(res!=FR_OK)
847 {
848 dbg("NO F_STAT %d\n", res);
849 return NULL;
850 }
851 FIL file;
852 res = f_open (&file, fp, FA_READ | FA_OPEN_EXISTING);
853 if(res!=FR_OK)
854 {
855 dbg("NO F_OPEN %d\n", res);
856 return NULL;
857 }
858 dbg("BUFFERING %d\n", res);
859
860 void* buffer = malloc(fi.fsize);
861 dbg("BUFFERED %d\n", fi.fsize);
862
863 UINT read = 0;
864 res = f_read(&file, buffer, fi.fsize, &read);
865 dbg("F_READ %d %ld\n", res, read);
866
867 f_close(&file);
868 if(read!=fi.fsize)
869 {
870 dbg("NO F_READ %d \n", res);
871 return NULL;
872 }
873 dbg("RETURNING!!\n");
874 *size = fi.fsize;
875 return buffer;
876 }
877 return NULL;
878#else
879
880 const FsString* pathString = utf8ToString(tic_fs_path(fs, name));
881 FILE* file = tic_fopen(pathString, _S("rb"));
882 freeString(pathString);
883
884 void* ptr = NULL;
885
886 if(file)
887 {
888 fseek(file, 0, SEEK_END);
889 *size = ftell(file);
890 fseek(file, 0, SEEK_SET);
891
892 u8* buffer = malloc(*size);
893
894 if(buffer && fread(buffer, *size, 1, file)) ptr = buffer;
895
896 fclose(file);
897 }
898
899 return ptr;
900
901#endif
902}
903
904void* tic_fs_loadroot(tic_fs* fs, const char* name, s32* size)
905{
906 return fs_read(tic_fs_pathroot(fs, name), size);
907}
908
909bool tic_fs_makedir(tic_fs* fs, const char* name)
910{
911#if defined(BAREMETALPI)
912 // TODO BAREMETALPI
913 dbg("makeDir %s\n", name);
914
915 const FsString* pathString = tic_fs_path(fs, name);
916 char* path = strdup(pathString);
917 if (path && *path) { // make sure result has at least
918 if (path[strlen(path) - 1] == '/') // one character
919 path[strlen(path) - 1] = 0;
920 }
921
922 FRESULT res = f_mkdir(path);
923 if(res != FR_OK)
924 {
925 dbg("Could not mkdir %s\n", name);
926 }
927 free(path);
928 return (res != FR_OK);
929#else
930
931 const FsString* pathString = utf8ToString(tic_fs_path(fs, name));
932 int result = tic_mkdir(pathString);
933 freeString(pathString);
934
935#if defined(__EMSCRIPTEN__)
936 syncfs();
937#endif
938 return result;
939#endif
940}
941
942void tic_fs_openfolder(tic_fs* fs)
943{
944 const char* path = tic_fs_path(fs, "");
945
946 if(isPublic(fs))
947 path = fs->dir;
948
949 tic_sys_open_path(path);
950}
951
952#if defined(__TIC_WINDOWS__)
953#define SEP "\\"
954#else
955#define SEP "/"
956#endif
957
958tic_fs* tic_fs_create(const char* path, tic_net* net)
959{
960 tic_fs* fs = (tic_fs*)calloc(1, sizeof(tic_fs));
961
962 strncpy(fs->dir, path, TICNAME_MAX);
963
964 if(path[strlen(path) - 1] != SEP[0])
965 strcat(fs->dir, SEP);
966
967 fs->net = net;
968
969 return fs;
970}
971