-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
File.cpp
227 lines (188 loc) · 6.84 KB
/
File.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
// Copyright 2018 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <memory>
#include "Common/File.h"
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Core/IOS/FS/HostBackend/FS.h"
namespace IOS::HLE::FS
{
// This isn't theadsafe, but it's only called from the CPU thread.
std::shared_ptr<File::IOFile> HostFileSystem::OpenHostFile(const std::string& host_path)
{
// On the wii, all file operations are strongly ordered.
// If a game opens the same file twice (or 8 times, looking at you PokePark Wii)
// and writes to one file handle, it will be able to immediately read the written
// data from the other handle.
// On 'real' operating systems, there are various buffers and caches meaning
// applications doing such naughty things will not get expected results.
// So we fix this by catching any attempts to open the same file twice and
// only opening one file. Accesses to a single file handle are ordered.
//
// Hall of Shame:
// - PokePark Wii (gets stuck on the loading screen of Pikachu falling)
// - PokePark 2 (Also gets stuck while loading)
// - Wii System Menu (Can't access the system settings, gets stuck on blank screen)
// - The Beatles: Rock Band (saving doesn't work)
// Check if the file has already been opened.
auto search = m_open_files.find(host_path);
if (search != m_open_files.end())
{
// Lock a shared pointer to use.
return search->second.lock();
}
// All files are opened read/write. Actual access rights will be controlled per handle by the
// read/write functions below
File::IOFile file;
while (!file.Open(host_path, "r+b"))
{
const bool try_again =
PanicYesNoFmt("File \"{}\" could not be opened!\n"
"This may happen with improper permissions or use by another process.\n"
"Press \"Yes\" to make another attempt.",
host_path);
if (!try_again)
{
// We've failed to open the file:
ERROR_LOG_FMT(IOS_FS, "OpenHostFile {}", host_path);
return nullptr;
}
}
// This code will be called when all references to the shared pointer below have been removed.
auto deleter = [this, host_path](File::IOFile* ptr) {
delete ptr; // IOFile's deconstructor closes the file.
m_open_files.erase(host_path); // erase the weak pointer from the list of open files.
};
// Use the custom deleter from above.
std::shared_ptr<File::IOFile> file_ptr(new File::IOFile(std::move(file)), deleter);
// Store a weak pointer to our newly opened file in the cache.
m_open_files[host_path] = std::weak_ptr<File::IOFile>(file_ptr);
return file_ptr;
}
Result<FileHandle> HostFileSystem::OpenFile(Uid, Gid, const std::string& path, Mode mode)
{
Handle* handle = AssignFreeHandle();
if (!handle)
return ResultCode::NoFreeHandle;
const std::string host_path = BuildFilename(path);
if (!File::IsFile(host_path))
{
*handle = Handle{};
return ResultCode::NotFound;
}
handle->host_file = OpenHostFile(host_path);
if (!handle->host_file)
{
*handle = Handle{};
return ResultCode::AccessDenied;
}
handle->wii_path = path;
handle->mode = mode;
handle->file_offset = 0;
return FileHandle{this, ConvertHandleToFd(handle)};
}
ResultCode HostFileSystem::Close(Fd fd)
{
Handle* handle = GetHandleFromFd(fd);
if (!handle)
return ResultCode::Invalid;
// Let go of our pointer to the file, it will automatically close if we are the last handle
// accessing it.
*handle = Handle{};
return ResultCode::Success;
}
Result<u32> HostFileSystem::ReadBytesFromFile(Fd fd, u8* ptr, u32 count)
{
Handle* handle = GetHandleFromFd(fd);
if (!handle || !handle->host_file->IsOpen())
return ResultCode::Invalid;
if ((u8(handle->mode) & u8(Mode::Read)) == 0)
return ResultCode::AccessDenied;
const u32 file_size = static_cast<u32>(handle->host_file->GetSize());
// IOS has this check in the read request handler.
if (count + handle->file_offset > file_size)
count = file_size - handle->file_offset;
// File might be opened twice, need to seek before we read
handle->host_file->Seek(handle->file_offset, SEEK_SET);
const u32 actually_read = static_cast<u32>(fread(ptr, 1, count, handle->host_file->GetHandle()));
if (actually_read != count && ferror(handle->host_file->GetHandle()))
return ResultCode::AccessDenied;
// IOS returns the number of bytes read and adds that value to the seek position,
// instead of adding the *requested* read length.
handle->file_offset += actually_read;
return actually_read;
}
Result<u32> HostFileSystem::WriteBytesToFile(Fd fd, const u8* ptr, u32 count)
{
Handle* handle = GetHandleFromFd(fd);
if (!handle || !handle->host_file->IsOpen())
return ResultCode::Invalid;
if ((u8(handle->mode) & u8(Mode::Write)) == 0)
return ResultCode::AccessDenied;
// File might be opened twice, need to seek before we read
handle->host_file->Seek(handle->file_offset, SEEK_SET);
if (!handle->host_file->WriteBytes(ptr, count))
return ResultCode::AccessDenied;
handle->file_offset += count;
return count;
}
Result<u32> HostFileSystem::SeekFile(Fd fd, std::uint32_t offset, SeekMode mode)
{
Handle* handle = GetHandleFromFd(fd);
if (!handle || !handle->host_file->IsOpen())
return ResultCode::Invalid;
u32 new_position = 0;
switch (mode)
{
case SeekMode::Set:
new_position = offset;
break;
case SeekMode::Current:
new_position = handle->file_offset + offset;
break;
case SeekMode::End:
new_position = handle->host_file->GetSize() + offset;
break;
default:
return ResultCode::Invalid;
}
// This differs from POSIX behaviour which allows seeking past the end of the file.
if (handle->host_file->GetSize() < new_position)
return ResultCode::Invalid;
handle->file_offset = new_position;
return handle->file_offset;
}
Result<FileStatus> HostFileSystem::GetFileStatus(Fd fd)
{
const Handle* handle = GetHandleFromFd(fd);
if (!handle || !handle->host_file->IsOpen())
return ResultCode::Invalid;
FileStatus status;
status.size = handle->host_file->GetSize();
status.offset = handle->file_offset;
return status;
}
HostFileSystem::Handle* HostFileSystem::AssignFreeHandle()
{
const auto it = std::find_if(m_handles.begin(), m_handles.end(),
[](const Handle& handle) { return !handle.opened; });
if (it == m_handles.end())
return nullptr;
*it = Handle{};
it->opened = true;
return &*it;
}
HostFileSystem::Handle* HostFileSystem::GetHandleFromFd(Fd fd)
{
if (fd >= m_handles.size() || !m_handles[fd].opened)
return nullptr;
return &m_handles[fd];
}
Fd HostFileSystem::ConvertHandleToFd(const Handle* handle) const
{
return handle - m_handles.data();
}
} // namespace IOS::HLE::FS