1+ #include " FSWrapperReplaceSingleFile.h"
2+ #include " utils/StringTools.h"
3+ #include " utils/logger.h"
4+ #include " utils/utils.h"
5+
6+ #include < coreinit/cache.h>
7+ #include < coreinit/debug.h>
8+ #include < coreinit/filesystem.h>
9+
10+ #include < filesystem>
11+
12+ FSWrapperReplaceSingleFile::FSWrapperReplaceSingleFile (const std::string &name,
13+ const std::string &fileToReplace,
14+ const std::string &replaceWithPath,
15+ const bool fallbackOnError) : FSWrapper(name,
16+ fileToReplace,
17+ replaceWithPath,
18+ fallbackOnError,
19+ false ) {
20+ auto strCpy = fileToReplace;
21+ std::ranges::replace (strCpy, ' \\ ' , ' /' );
22+ auto asPath = std::filesystem::path (strCpy);
23+ mPathToReplace = asPath.parent_path ();
24+ mFileNameToReplace = asPath.filename ();
25+ mFullPathToReplace = fileToReplace;
26+
27+ strCpy = replaceWithPath;
28+ std::ranges::replace (strCpy, ' \\ ' , ' /' );
29+ asPath = std::filesystem::path (strCpy);
30+ mReplacedWithPath = asPath.parent_path ();
31+ mReplacedWithFileName = asPath.filename ();
32+
33+ FSAInit ();
34+ this ->mClientHandle = FSAAddClient (nullptr );
35+ if (mClientHandle < 0 ) {
36+ DEBUG_FUNCTION_LINE_ERR (" [%s] FSAClientHandle failed: %s (%d)" , name.c_str (), FSAGetStatusStr (static_cast <FSError>(mClientHandle )), mClientHandle );
37+ mClientHandle = 0 ;
38+ }
39+ }
40+
41+ FSWrapperReplaceSingleFile::~FSWrapperReplaceSingleFile () {
42+ if (mClientHandle ) {
43+ if (const FSError res = FSADelClient (mClientHandle ); res != FS_ERROR_OK) {
44+ DEBUG_FUNCTION_LINE_ERR (" [%s] FSADelClient failed: %s (%d)" , FSAGetStatusStr (res), res);
45+ }
46+ mClientHandle = 0 ;
47+ }
48+ }
49+
50+ FSError FSWrapperReplaceSingleFile::FSOpenDirWrapper (const char *path,
51+ FSADirectoryHandle *handle) {
52+ if (!IsDirPathToReplace (path)) {
53+ return FS_ERROR_FORCE_PARENT_LAYER;
54+ }
55+ if (handle == nullptr ) {
56+ DEBUG_FUNCTION_LINE_ERR (" [%s] handle was NULL" , getName ().c_str ());
57+ return FS_ERROR_INVALID_PARAM;
58+ }
59+ if (const auto dirInfo = make_shared_nothrow<DirInfoExSingleFile>()) {
60+ dirInfo->handle = (reinterpret_cast <uint32_t >(dirInfo.get ()) & 0x0FFFFFFF ) | 0x30000000 ;
61+ *handle = dirInfo->handle ;
62+ addDirHandle (dirInfo);
63+ if (!isValidDirHandle (*handle)) {
64+ FSWrapper::FSCloseDirWrapper (*handle);
65+ DEBUG_FUNCTION_LINE_ERR (" [%s] No valid dir handle %08X" , getName ().c_str (), *handle);
66+ return FS_ERROR_INVALID_DIRHANDLE;
67+ }
68+ if (const auto dirHandle = getDirExFromHandle (*handle); dirHandle != nullptr ) {
69+ dirHandle->entryRead = false ;
70+ dirHandle->entryReadSuccess = false ;
71+ dirHandle->directoryEntry = {};
72+ dirHandle->realDirHandle = 0 ;
73+
74+ if (mClientHandle ) {
75+ FSADirectoryHandle realHandle = 0 ;
76+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] Call FSAOpenDir with %s for parent layer" , getName ().c_str (), path);
77+ if (const FSError err = FSAOpenDir (mClientHandle , path, &realHandle); err == FS_ERROR_OK) {
78+ dirHandle->realDirHandle = realHandle;
79+ } else {
80+ DEBUG_FUNCTION_LINE_ERR (" [%s] Failed to open real dir %s. %s (%d)" , getName ().c_str (), path, FSAGetStatusStr (err), err);
81+ }
82+ } else {
83+ DEBUG_FUNCTION_LINE_ERR (" [%s] clientHandle was null" , getName ().c_str ());
84+ }
85+ OSMemoryBarrier ();
86+ }
87+ } else {
88+ DEBUG_FUNCTION_LINE_ERR (" [%s] Failed to alloc dir handle" , getName ().c_str ());
89+ return FS_ERROR_MAX_DIRS;
90+ }
91+ return FS_ERROR_OK;
92+ }
93+
94+ FSError FSWrapperReplaceSingleFile::FSReadDirWrapper (const FSADirectoryHandle handle, FSADirectoryEntry *entry) {
95+ if (!isValidDirHandle (handle)) {
96+ return FS_ERROR_FORCE_PARENT_LAYER;
97+ }
98+ const auto dirHandle = getDirExFromHandle (handle);
99+ if (!dirHandle) {
100+ DEBUG_FUNCTION_LINE_ERR (" [%s] No valid dir handle %08X" , getName ().c_str (), handle);
101+ return FS_ERROR_INVALID_DIRHANDLE;
102+ }
103+ FSError res = FS_ERROR_OK;
104+ do {
105+ if (!dirHandle->entryRead ) {
106+ dirHandle->entryRead = true ;
107+ const auto newPath = GetNewPath (mFullPathToReplace );
108+
109+ struct stat path_stat {};
110+
111+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] dir read of %s (%s)" , getName ().c_str (), mFullPathToReplace .c_str (), newPath.c_str ());
112+ if (stat (newPath.c_str (), &path_stat) < 0 ) {
113+ DEBUG_FUNCTION_LINE_WARN (" [%s] Path %s (%s) for dir read not found " , getName ().c_str (), mFullPathToReplace .c_str (), newPath.c_str ());
114+ dirHandle->entryReadSuccess = false ;
115+ continue ;
116+ }
117+ translate_stat (&path_stat, &dirHandle->directoryEntry .info );
118+ strncpy (dirHandle->directoryEntry .name , mFileNameToReplace .c_str (), sizeof (dirHandle->directoryEntry .name ));
119+ memcpy (entry, &dirHandle->directoryEntry , sizeof (FSADirectoryEntry));
120+
121+ dirHandle->entryReadSuccess = true ;
122+ OSMemoryBarrier ();
123+ } else {
124+ // Read the real directory.
125+ if (dirHandle->realDirHandle != 0 ) {
126+ if (mClientHandle ) {
127+ FSADirectoryEntry realDirEntry;
128+ while (true ) {
129+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] Call FSReadDir with %08X for parent layer" , getName ().c_str (), dirHandle->realDirHandle );
130+ if (const FSError readDirResult = FSAReadDir (mClientHandle , dirHandle->realDirHandle , &realDirEntry); readDirResult == FS_ERROR_OK) {
131+ // Skip already read new files
132+ if (dirHandle->entryRead && dirHandle->entryReadSuccess && strcmp (dirHandle->directoryEntry .name , realDirEntry.name ) == 0 ) {
133+ continue ;
134+ }
135+
136+ // But use new entries!
137+ memcpy (entry, &realDirEntry, sizeof (FSADirectoryEntry));
138+ res = FS_ERROR_OK;
139+ break ;
140+ } else if (readDirResult == FS_ERROR_END_OF_DIR) {
141+ res = FS_ERROR_END_OF_DIR;
142+ break ;
143+ } else {
144+ DEBUG_FUNCTION_LINE_ERR (" [%s] real_FSReadDir returned an unexpected error: %s (%d)" , getName ().c_str (), FSAGetStatusStr (readDirResult), readDirResult);
145+ res = FS_ERROR_END_OF_DIR;
146+ break ;
147+ }
148+ }
149+ } else {
150+ DEBUG_FUNCTION_LINE_ERR (" [%s] clientHandle was null" , getName ().c_str ());
151+ }
152+ }
153+ }
154+ return res;
155+ } while (true );
156+ }
157+
158+ FSError FSWrapperReplaceSingleFile::FSCloseDirWrapper (const FSADirectoryHandle handle) {
159+ if (!isValidDirHandle (handle)) {
160+ return FS_ERROR_FORCE_PARENT_LAYER;
161+ }
162+ const auto dirHandle = getDirExFromHandle (handle);
163+ if (dirHandle->realDirHandle != 0 ) {
164+ if (mClientHandle ) {
165+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] Call FSCloseDir with %08X for parent layer" , getName ().c_str (), dirHandle->realDirHandle );
166+ auto realResult = FSACloseDir (mClientHandle , dirHandle->realDirHandle );
167+ if (realResult == FS_ERROR_OK) {
168+ dirHandle->realDirHandle = 0 ;
169+ } else {
170+ DEBUG_FUNCTION_LINE_ERR (" [%s] Failed to close realDirHandle %d: res %s (%d)" , getName ().c_str (), dirHandle->realDirHandle , FSAGetStatusStr (realResult), realResult);
171+ return realResult == FS_ERROR_CANCELLED ? FS_ERROR_CANCELLED : FS_ERROR_MEDIA_ERROR;
172+ }
173+ } else {
174+ DEBUG_FUNCTION_LINE_ERR (" [%s] clientHandle was null" , getName ().c_str ());
175+ }
176+ } else {
177+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] dirHandle->realDirHandle was 0" , getName ().c_str ());
178+ }
179+ dirHandle->entryRead = false ;
180+ dirHandle->entryReadSuccess = false ;
181+
182+ OSMemoryBarrier ();
183+ return FS_ERROR_OK;
184+ }
185+
186+ FSError FSWrapperReplaceSingleFile::FSRewindDirWrapper (const FSADirectoryHandle handle) {
187+ if (!isValidDirHandle (handle)) {
188+ return FS_ERROR_FORCE_PARENT_LAYER;
189+ }
190+ const auto dirHandle = getDirExFromHandle (handle);
191+ dirHandle->entryRead = false ;
192+ dirHandle->entryReadSuccess = false ;
193+ if (dirHandle->realDirHandle != 0 ) {
194+ if (mClientHandle ) {
195+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] Call FSARewindDir with %08X for parent layer" , getName ().c_str (), dirHandle->realDirHandle );
196+ if (const FSError err = FSARewindDir (mClientHandle , dirHandle->realDirHandle ); err != FS_ERROR_OK) {
197+ DEBUG_FUNCTION_LINE_ERR (" [%s] Failed to rewind dir for realDirHandle %08X. %s (%d)" , getName ().c_str (), dirHandle->realDirHandle , FSAGetStatusStr (err), err);
198+ }
199+ } else {
200+ DEBUG_FUNCTION_LINE_ERR (" [%s] clientHandle was null" , getName ().c_str ());
201+ }
202+ } else {
203+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] dirHandle->realDirHandle was 0" , getName ().c_str ());
204+ }
205+ OSMemoryBarrier ();
206+
207+ return FS_ERROR_OK;
208+ }
209+
210+ bool FSWrapperReplaceSingleFile::SkipDeletedFilesInReadDir () {
211+ return false ;
212+ }
213+
214+ bool FSWrapperReplaceSingleFile::IsDirPathToReplace (const std::string_view &path) const {
215+ return starts_with_case_insensitive (path, mPathToReplace );
216+ }
217+
218+ std::string FSWrapperReplaceSingleFile::GetNewPath (const std::string_view &path) const {
219+ auto pathCpy = std::string (path);
220+ SafeReplaceInString (pathCpy, this ->mPathToReplace , this ->mReplacedWithPath );
221+ SafeReplaceInString (pathCpy, this ->mFileNameToReplace , this ->mReplacedWithFileName );
222+ std::ranges::replace (pathCpy, ' \\ ' , ' /' );
223+
224+ uint32_t length = pathCpy.size ();
225+
226+ // ! clear path of double slashes
227+ for (uint32_t i = 1 ; i < length; ++i) {
228+ if (pathCpy[i - 1 ] == ' /' && pathCpy[i] == ' /' ) {
229+ pathCpy.erase (i, 1 );
230+ i--;
231+ length--;
232+ }
233+ }
234+
235+ DEBUG_FUNCTION_LINE_VERBOSE (" [%s] Redirect %.*s -> %s" , getName ().c_str (), int (path.length ()), path.data (), res.c_str ());
236+ return pathCpy;
237+ }
238+
239+ std::shared_ptr<DirInfoExSingleFile> FSWrapperReplaceSingleFile::getDirExFromHandle (FSADirectoryHandle handle) {
240+ auto dir = std::dynamic_pointer_cast<DirInfoExSingleFile>(getDirFromHandle (handle));
241+
242+ if (!dir) {
243+ DEBUG_FUNCTION_LINE_ERR (" [%s] dynamic_pointer_cast<DirInfoExSingleFile *>(%08X) failed" , getName ().c_str (), handle);
244+ OSFatal (" ContentRedirectionModule: dynamic_pointer_cast<DirInfoExSingleFile *> failed" );
245+ }
246+ return dir;
247+ }
0 commit comments