duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

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 }