pcdrv.cpp (8029B)
1 // SPDX-FileCopyrightText: 2023-2024 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) 3 4 #include "pcdrv.h" 5 #include "cpu_core.h" 6 #include "settings.h" 7 8 #include "common/file_system.h" 9 #include "common/log.h" 10 #include "common/path.h" 11 #include "common/string_util.h" 12 13 Log_SetChannel(PCDrv); 14 15 static constexpr u32 MAX_FILES = 100; 16 17 static std::vector<FileSystem::ManagedCFilePtr> s_files; 18 19 enum PCDrvAttribute : u32 20 { 21 PCDRV_ATTRIBUTE_READ_ONLY = (1 << 0), 22 PCDRV_ATTRIBUTE_HIDDEN = (1 << 1), 23 PCDRV_ATTRIBUTE_SYSTEM = (1 << 2), 24 PCDRV_ATTRIBUTE_DIRECTORY = (1 << 4), 25 PCDRV_ATTRIBUTE_ARCHIVE = (1 << 5), 26 }; 27 28 static s32 GetFreeFileHandle() 29 { 30 for (s32 i = 0; i < static_cast<s32>(s_files.size()); i++) 31 { 32 if (!s_files[i]) 33 return i; 34 } 35 36 if (s_files.size() >= MAX_FILES) 37 { 38 ERROR_LOG("Too many open files."); 39 return -1; 40 } 41 42 const s32 index = static_cast<s32>(s_files.size()); 43 s_files.emplace_back(nullptr, FileSystem::FileDeleter()); 44 return index; 45 } 46 47 static void CloseAllFiles() 48 { 49 if (!s_files.empty()) 50 DEV_LOG("Closing {} open files.", s_files.size()); 51 52 s_files.clear(); 53 } 54 55 static FILE* GetFileFromHandle(u32 handle) 56 { 57 if (handle >= static_cast<u32>(s_files.size()) || !s_files[handle]) 58 { 59 ERROR_LOG("Invalid file handle {}", static_cast<s32>(handle)); 60 return nullptr; 61 } 62 63 return s_files[handle].get(); 64 } 65 66 static bool CloseFileHandle(u32 handle) 67 { 68 if (handle >= static_cast<u32>(s_files.size()) || !s_files[handle]) 69 { 70 ERROR_LOG("Invalid file handle {}", static_cast<s32>(handle)); 71 return false; 72 } 73 74 s_files[handle].reset(); 75 while (!s_files.empty() && !s_files.back()) 76 s_files.pop_back(); 77 return true; 78 } 79 80 static std::string ResolveHostPath(const std::string& path) 81 { 82 // Double-check that it falls within the directory of the elf. 83 // Not a real sandbox, but emulators shouldn't be treated as such. Don't run untrusted code! 84 const std::string& root = g_settings.pcdrv_root; 85 std::string canonicalized_path = Path::Canonicalize(Path::Combine(root, path)); 86 if (canonicalized_path.length() < root.length() || // Length has to be longer (a file), 87 !canonicalized_path.starts_with(root) || // and start with the host root, 88 canonicalized_path[root.length()] != FS_OSPATH_SEPARATOR_CHARACTER) // and we can't access a sibling. 89 { 90 ERROR_LOG("Denying access to path outside of PCDrv directory. Requested path: '{}', " 91 "Resolved path: '{}', Root directory: '{}'", 92 path, root, canonicalized_path); 93 canonicalized_path.clear(); 94 } 95 96 return canonicalized_path; 97 } 98 99 void PCDrv::Initialize() 100 { 101 if (!g_settings.pcdrv_enable) 102 return; 103 104 WARNING_LOG("{} PCDrv is enabled at '{}'", g_settings.pcdrv_enable_writes ? "Read/Write" : "Read-Only", 105 g_settings.pcdrv_root); 106 } 107 108 void PCDrv::Reset() 109 { 110 CloseAllFiles(); 111 } 112 113 void PCDrv::Shutdown() 114 { 115 CloseAllFiles(); 116 } 117 118 bool PCDrv::HandleSyscall(u32 instruction_bits, CPU::Registers& regs) 119 { 120 // Based on https://problemkaputt.de/psxspx-bios-pc-file-server.htm 121 122 #define RETURN_ERROR() \ 123 regs.v0 = 0xffffffff; \ 124 regs.v1 = 0xffffffff; // error code 125 126 const u32 code = (instruction_bits >> 6) & 0xfffff; // 20 bits, funct = 0 127 switch (code) 128 { 129 case 0x101: // PCinit 130 { 131 DEV_LOG("PCinit"); 132 CloseAllFiles(); 133 regs.v0 = 0; 134 regs.v1 = 0; 135 return true; 136 } 137 138 case 0x102: // PCcreat 139 case 0x103: // PCopen 140 { 141 const bool is_open = (code == 0x103); 142 const char* func = (code == 0x102) ? "PCcreat" : "PCopen"; 143 const u32 mode = regs.a2; 144 std::string filename; 145 if (!CPU::SafeReadMemoryCString(regs.a1, &filename)) 146 { 147 ERROR_LOG("{}: Invalid string", func); 148 return false; 149 } 150 151 DEBUG_LOG("{}: '{}' mode {}", func, filename, mode); 152 if ((filename = ResolveHostPath(filename)).empty()) 153 { 154 RETURN_ERROR(); 155 return true; 156 } 157 158 if (!is_open && !g_settings.pcdrv_enable_writes) 159 { 160 ERROR_LOG("{}: Writes are not enabled", func); 161 RETURN_ERROR(); 162 return true; 163 } 164 165 // Directories are unsupported for now, ignore other attributes 166 if (mode & PCDRV_ATTRIBUTE_DIRECTORY) 167 { 168 ERROR_LOG("{}: Directories are unsupported", func); 169 RETURN_ERROR(); 170 return true; 171 } 172 173 // Create empty file, truncate if exists. 174 const s32 handle = GetFreeFileHandle(); 175 if (handle < 0) 176 { 177 RETURN_ERROR(); 178 return true; 179 } 180 181 s_files[handle] = FileSystem::OpenManagedCFile(filename.c_str(), 182 is_open ? (g_settings.pcdrv_enable_writes ? "r+b" : "rb") : "w+b"); 183 if (!s_files[handle]) 184 { 185 ERROR_LOG("{}: Failed to open '{}'", func, filename); 186 RETURN_ERROR(); 187 return true; 188 } 189 190 ERROR_LOG("PCDrv: Opened '{}' => {}", filename, handle); 191 regs.v0 = 0; 192 regs.v1 = static_cast<u32>(handle); 193 return true; 194 } 195 196 case 0x104: // PCclose 197 { 198 DEBUG_LOG("PCclose({})", regs.a1); 199 200 if (!CloseFileHandle(regs.a1)) 201 { 202 RETURN_ERROR(); 203 return true; 204 } 205 206 regs.v0 = 0; 207 regs.v1 = 0; 208 return true; 209 } 210 211 case 0x105: // PCread 212 { 213 DEBUG_LOG("PCread({}, {}, 0x{:08X})", regs.a1, regs.a2, regs.a3); 214 215 std::FILE* fp = GetFileFromHandle(regs.a1); 216 if (!fp) 217 { 218 RETURN_ERROR(); 219 return true; 220 } 221 222 const u32 count = regs.a2; 223 u32 dstaddr = regs.a3; 224 for (u32 i = 0; i < count; i++) 225 { 226 // Certainly less than optimal, but it's not like you're going to be reading megabytes of data here. 227 u8 val; 228 if (std::fread(&val, 1, 1, fp) != 1) 229 { 230 // Does not stop at EOF according to psx-spx. 231 if (std::ferror(fp) != 0) 232 { 233 RETURN_ERROR(); 234 return true; 235 } 236 237 val = 0; 238 } 239 240 CPU::SafeWriteMemoryByte(dstaddr, val); 241 dstaddr++; 242 } 243 244 regs.v0 = 0; 245 regs.v1 = count; 246 return true; 247 } 248 249 case 0x106: // PCwrite 250 { 251 DEBUG_LOG("PCwrite({}, {}, 0x{:08x})", regs.a1, regs.a2, regs.a3); 252 253 std::FILE* fp = GetFileFromHandle(regs.a1); 254 if (!fp) 255 { 256 RETURN_ERROR(); 257 return true; 258 } 259 260 const u32 count = regs.a2; 261 u32 srcaddr = regs.a3; 262 u32 written = 0; 263 for (u32 i = 0; i < count; i++) 264 { 265 u8 val; 266 if (!CPU::SafeReadMemoryByte(srcaddr, &val)) 267 break; 268 269 if (std::fwrite(&val, 1, 1, fp) != 1) 270 { 271 RETURN_ERROR(); 272 return true; 273 } 274 275 srcaddr++; 276 written++; 277 } 278 279 regs.v0 = 0; 280 regs.v1 = written; 281 return true; 282 } 283 284 case 0x107: // PClseek 285 { 286 DEBUG_LOG("PClseek({}, {}, {})", regs.a1, regs.a2, regs.a3); 287 288 std::FILE* fp = GetFileFromHandle(regs.a1); 289 if (!fp) 290 { 291 RETURN_ERROR(); 292 return true; 293 } 294 295 const s32 offset = static_cast<s32>(regs.a2); 296 const u32 mode = regs.a3; 297 int hmode; 298 switch (mode) 299 { 300 case 0: 301 hmode = SEEK_SET; 302 break; 303 case 1: 304 hmode = SEEK_CUR; 305 break; 306 case 2: 307 hmode = SEEK_END; 308 break; 309 default: 310 RETURN_ERROR(); 311 return true; 312 } 313 314 if (FileSystem::FSeek64(fp, offset, hmode) != 0) 315 { 316 ERROR_LOG("FSeek for PCDrv failed: {} {}", offset, hmode); 317 RETURN_ERROR(); 318 return true; 319 } 320 321 regs.v0 = 0; 322 regs.v1 = static_cast<u32>(static_cast<s32>(FileSystem::FTell64(fp))); 323 return true; 324 } 325 326 default: 327 return false; 328 } 329 }