//--------------------------------------------------------------------------- // // SCSI Target Emulator PiSCSI // for Raspberry Pi // // Powered by XM6 TypeG Technology. // Copyright (C) 2016-2020 GIMONS // // Imported sava's bugfix patch(in RASDRV DOS edition). // // [ Host File System for the X68000 ] // // Note: This functionality is specific to the X68000 // operating system. // It is highly unlikely that this will work for other // platforms. // //--------------------------------------------------------------------------- #include "shared/log.h" #include "cfilesystem.h" #include #include #include #define ARRAY_SIZE(x) (sizeof(x)/(sizeof(x[0]))) //--------------------------------------------------------------------------- // // Kanji code conversion // //--------------------------------------------------------------------------- static const int IC_BUF_SIZE = 1024; static char convert_buf[IC_BUF_SIZE]; #define CONVERT(src, dest, inbuf, outbuf, outsize) \ convert(src, dest, (char *)inbuf, outbuf, outsize) static void convert(char const *src, char const *dest, char *inbuf, char *outbuf, size_t outsize) { *outbuf = '\0'; size_t in = strlen(inbuf); size_t out = outsize - 1; iconv_t cd = iconv_open(dest, src); if (cd == (iconv_t)-1) { return; } if (const size_t ret = iconv(cd, &inbuf, &in, &outbuf, &out); ret == (size_t)-1) { return; } iconv_close(cd); *outbuf = '\0'; } //--------------------------------------------------------------------------- // // SJIS->UTF8 conversion // //--------------------------------------------------------------------------- static char* SJIS2UTF8(const char *sjis, char *utf8, size_t bufsize) { CONVERT("SJIS", "UTF-8", sjis, utf8, bufsize); return convert_buf; } //--------------------------------------------------------------------------- // // UTF8->SJIS conversion // //--------------------------------------------------------------------------- static char* UTF82SJIS(const char *utf8, char *sjis, size_t bufsize) { CONVERT("UTF-8", "SJIS", utf8, sjis, bufsize); return convert_buf; } //--------------------------------------------------------------------------- // // SJIS->UTF8 conversion (simplified versoin) // //--------------------------------------------------------------------------- static char* S2U(const char *sjis) { SJIS2UTF8(sjis, convert_buf, IC_BUF_SIZE); return convert_buf; } //--------------------------------------------------------------------------- // // UTF8->SJIS conversion (simplified version) // //--------------------------------------------------------------------------- static char* U2S(const char *utf8) { UTF82SJIS(utf8, convert_buf, IC_BUF_SIZE); return convert_buf; } //--------------------------------------------------------------------------- // /// Get path name /// /// From the structure used in Human68k namests, get the Human68k path name. /// A 66 byte buffer is required for writing. // //--------------------------------------------------------------------------- void Human68k::namests_t::GetCopyPath(uint8_t* szPath) const { assert(szPath); uint8_t* p = szPath; for (uint8_t c : path) { if (c == '\0') break; if (c == 0x09) { c = '/'; } *p++ = c; } *p = '\0'; } //--------------------------------------------------------------------------- // /// Get file name /// /// From the structure used in Human68k namests, get the Human68k file name. /// A 23 byte buffer is required for writing. // //--------------------------------------------------------------------------- void Human68k::namests_t::GetCopyFilename(uint8_t* szFilename) const { assert(szFilename); size_t i; uint8_t* p = szFilename; // Transfer the base file name for (i = 0; i < 8; i++) { uint8_t c = name[i]; if (c == ' ') { // Check that the file name continues after a space is detected /// TODO: Should change this function to be compatible with 8+3 chars and TwentyOne // Continue if add[0] is a valid character if (add[0] != '\0') goto next_name; // Continue if a non-space character exists after name[i] for (size_t j = i + 1; j < 8; j++) { if (name[j] != ' ') goto next_name; } // Exit if the file name ends break; } next_name: *p++ = c; } // At this point, the number of read characters becomes i >= 8 // If the body of the file name exceeds 8 characters, add the extraneous part if (i >= 8) { // Transfer the extraneous part for (i = 0; i < 10; i++) { const uint8_t c = add[i]; if (c == '\0') break; *p++ = c; } // At this point, the number of read characters becomes i >= 10 } // Transfer the file extension if it exists if (ext[0] != ' ' || ext[1] != ' ' || ext[2] != ' ') { *p++ = '.'; for (i = 0; i < 3; i++) { const uint8_t c = ext[i]; if (c == ' ') { // Check that the file extension continues after a space is detected /// TODO: Should change this function to be compatible with 8+3 chars and TwentyOne // Continue if a non-space character exists after ext[i] for (size_t j = i + 1; j < 3; j++) { if (ext[j] != ' ') goto next_ext; } // If the extension ends, the transfer ends break; } next_ext: *p++ = c; } // When all the characters are read, here i >= 3 } // Add sentinel *p = '\0'; } //=========================================================================== // // Host side drive // //=========================================================================== CHostDrv::~CHostDrv() { CHostPath* p; while ((p = (CHostPath*)m_cRing.Next()) != &m_cRing) { delete p; assert(m_nRing); m_nRing--; } // Confirm that the entity does not exist (just in case) assert(m_cRing.Next() == &m_cRing); assert(m_cRing.Prev() == &m_cRing); assert(m_nRing == 0); } //--------------------------------------------------------------------------- // // Initialization (device boot and load) // //--------------------------------------------------------------------------- void CHostDrv::Init(const TCHAR* szBase, uint32_t nFlag) { assert(szBase); assert(strlen(szBase) < FILEPATH_MAX); assert(!m_bWriteProtect); assert(!m_bEnable); assert(m_capCache.sectors == 0); assert(!m_bVolumeCache); assert(m_szVolumeCache[0] == '\0'); // Confirm that the entity does not exist (just in case) assert(m_cRing.Next() == &m_cRing); assert(m_cRing.Prev() == &m_cRing); assert(m_nRing == 0); m_capCache.sectors = 0; // Receive parameters if (nFlag & FSFLAG_WRITE_PROTECT) m_bWriteProtect = true; strcpy(m_szBase, szBase); // Remove the last path delimiter in the base path // @warning needs to be modified when using Unicode TCHAR* pClear = nullptr; TCHAR* p = m_szBase; for (;;) { const TCHAR c = *p; if (c == '\0') break; if (c == '/' || c == '\\') { pClear = p; } else { pClear = nullptr; } if ((c <= (TCHAR)0x9F) || (TCHAR)0xE0 <= c) { // To be precise: 0x81~0x9F 0xE0~0xEF p++; if (*p == '\0') break; } p++; } if (pClear) *pClear = '\0'; // Status update m_bEnable = true; } //--------------------------------------------------------------------------- // // Media check // //--------------------------------------------------------------------------- bool CHostDrv::isMediaOffline() const { // Offline status check return !m_bEnable; } //--------------------------------------------------------------------------- // // Get media bytes // //--------------------------------------------------------------------------- uint8_t CHostDrv::GetMediaByte() const { return Human68k::MEDIA_REMOTE; } //--------------------------------------------------------------------------- // // Get drive status // //--------------------------------------------------------------------------- uint32_t CHostDrv::GetStatus() const { return 0x40 | (m_bEnable ? (m_bWriteProtect ? 0x08 : 0) | 0x02 : 0); } //--------------------------------------------------------------------------- // // Media status settings // //--------------------------------------------------------------------------- void CHostDrv::SetEnable(bool bEnable) { m_bEnable = bEnable; if (!bEnable) { // Clear cache m_capCache.sectors = 0; m_bVolumeCache = false; m_szVolumeCache[0] = '\0'; } } //--------------------------------------------------------------------------- // // Media change check // //--------------------------------------------------------------------------- bool CHostDrv::CheckMedia() { // Status update Update(); if (!m_bEnable) CleanCache(); return m_bEnable; } //--------------------------------------------------------------------------- // // Media status update // //--------------------------------------------------------------------------- void CHostDrv::Update() { // Considered as media insertion // Media status reflected SetEnable(true); } //--------------------------------------------------------------------------- // // Eject // //--------------------------------------------------------------------------- void CHostDrv::Eject() { // Media discharge CleanCache(); SetEnable(false); // Status update Update(); } //--------------------------------------------------------------------------- // // Get volume label // //--------------------------------------------------------------------------- void CHostDrv::GetVolume(TCHAR* szLabel) { assert(szLabel); assert(m_bEnable); // Get volume label strcpy(m_szVolumeCache, "RASDRV "); if (m_szBase[0]) { strcat(m_szVolumeCache, m_szBase); } else { strcat(m_szVolumeCache, "/"); } // Cache update m_bVolumeCache = true; // Transfer content strcpy(szLabel, m_szVolumeCache); } //--------------------------------------------------------------------------- // /// Get volume label from cache /// /// Transfer the cached volume label information. /// Return true if the cache contents are valid. // //--------------------------------------------------------------------------- bool CHostDrv::GetVolumeCache(TCHAR* szLabel) const { assert(szLabel); // Transfer contents strcpy(szLabel, m_szVolumeCache); return m_bVolumeCache; } uint32_t CHostDrv::GetCapacity(Human68k::capacity_t* pCapacity) { assert(pCapacity); assert(m_bEnable); const uint32_t nFree = 0x7FFF8000; uint32_t freearea; uint32_t clusters; uint32_t sectors; freearea = 0xFFFF; clusters = 0xFFFF; sectors = 64; // Estimated parameter range assert(freearea <= 0xFFFF); assert(clusters <= 0xFFFF); assert(sectors <= 64); // Update cache m_capCache.freearea = (uint16_t)freearea; m_capCache.clusters = (uint16_t)clusters; m_capCache.sectors = (uint16_t)sectors; m_capCache.bytes = 512; // Transfer contents memcpy(pCapacity, &m_capCache, sizeof(m_capCache)); return nFree; } //--------------------------------------------------------------------------- // /// Get capacity from the cache /// /// Transfer the capacity data stored in cache. /// Return true if the contents of the cache are valid. // //--------------------------------------------------------------------------- bool CHostDrv::GetCapacityCache(Human68k::capacity_t* pCapacity) const { assert(pCapacity); // Transfer contents memcpy(pCapacity, &m_capCache, sizeof(m_capCache)); return m_capCache.sectors != 0; } //--------------------------------------------------------------------------- // /// Update all cache // //--------------------------------------------------------------------------- void CHostDrv::CleanCache() const { for (auto p = (CHostPath*)m_cRing.Next(); p != &m_cRing;) { p->Release(); p = (CHostPath*)p->Next(); } } //--------------------------------------------------------------------------- // /// Update the cache for the specified path // //--------------------------------------------------------------------------- void CHostDrv::CleanCache(const uint8_t* szHumanPath) { assert(szHumanPath); CHostPath* p = FindCache(szHumanPath); if (p) { p->Restore(); p->Release(); } } //--------------------------------------------------------------------------- // /// Update the cache below and including the specified path // //--------------------------------------------------------------------------- void CHostDrv::CleanCacheChild(const uint8_t* szHumanPath) const { assert(szHumanPath); auto p = (CHostPath*)m_cRing.Next(); while (p != &m_cRing) { if (p->isSameChild(szHumanPath)) p->Release(); p = (CHostPath*)p->Next(); } } //--------------------------------------------------------------------------- // /// Delete the cache for the specified path // //--------------------------------------------------------------------------- void CHostDrv::DeleteCache(const uint8_t* szHumanPath) { auto p = FindCache(szHumanPath); if (p) { delete p; assert(m_nRing); m_nRing--; } } //--------------------------------------------------------------------------- // /// Check if the specified path is cached /// /// Check if whether it is a perfect match with the cache buffer, and return the name if found. /// File names are excempted. /// Make sure to lock from the top. // //--------------------------------------------------------------------------- CHostPath* CHostDrv::FindCache(const uint8_t* szHuman) { assert(szHuman); // Find something that matches perfectly with either of the stored file names for (auto p = (CHostPath*)m_cRing.Next(); p != &m_cRing;) { if (p->isSameHuman(szHuman)) return p; p = (CHostPath*)p->Next(); } return nullptr; } //--------------------------------------------------------------------------- // /// Get the host side name from cached data. /// /// Confirm if the path is cached. If not, throw an error. /// Carry out an update check on found cache. If an update is needed, throw an error. /// Make sure to lock from the top. // //--------------------------------------------------------------------------- CHostPath* CHostDrv::CopyCache(CHostFiles* pFiles) { assert(pFiles); assert(strlen((const char*)pFiles->GetHumanPath()) < HUMAN68K_PATH_MAX); // Find in cache CHostPath* pPath = FindCache(pFiles->GetHumanPath()); if (pPath == nullptr) { return nullptr; // Error: No cache } // Move to the beginning of the ring pPath->Insert(&m_cRing); // Cache update check if (pPath->isRefresh()) { return nullptr; // Error: Cache update is required } // Store the host side path pFiles->SetResult(pPath->GetHost()); return pPath; } //--------------------------------------------------------------------------- // /// Get all the data required for a host side name structure /// /// File names can be abbreviated. (Normally not selected) /// Make sure to lock from the top. /// Be careful not to append the path separator char to the end of the base path. /// Initiate VM threads when there is a chance of multiple file accesses. /// /// How to use: /// When CopyCache() throws an error execute MakeCache(). It ensures you get a correct host side path. /// /// Split all file names and path names. /// Confirm that it's cached from the top level directory down. /// If it's cached, do a destruction check. If it was destroyed, treat it as uncached. /// If it isn't cached, build cache. /// Exit after processing all directory and file names in order. /// Make it nullptr is an error is thrown. // //--------------------------------------------------------------------------- CHostPath* CHostDrv::MakeCache(CHostFiles* pFiles) { assert(pFiles); assert(strlen((const char*)pFiles->GetHumanPath()) < HUMAN68K_PATH_MAX); assert(m_szBase); assert(strlen(m_szBase) < FILEPATH_MAX); uint8_t szHumanPath[HUMAN68K_PATH_MAX]; // Path names are entered in order from the route szHumanPath[0] = '\0'; size_t nHumanPath = 0; TCHAR szHostPath[FILEPATH_MAX]; strcpy(szHostPath, m_szBase); size_t nHostPath = strlen(szHostPath); CHostPath* pPath; const uint8_t* p = pFiles->GetHumanPath(); for (;;) { // Add path separators if (nHumanPath + 1 >= HUMAN68K_PATH_MAX) return nullptr; // Error: The Human68k path is too long szHumanPath[nHumanPath++] = '/'; szHumanPath[nHumanPath] = '\0'; if (nHostPath + 1 >= FILEPATH_MAX) return nullptr; // Error: The host side path is too long szHostPath[nHostPath++] = '/'; szHostPath[nHostPath] = '\0'; // Insert one file uint8_t szHumanFilename[24]; // File name part p = SeparateCopyFilename(p, szHumanFilename); if (p == nullptr) return nullptr; // Error: Failed to read file name size_t n = strlen((const char*)szHumanFilename); if (nHumanPath + n >= HUMAN68K_PATH_MAX) return nullptr; // Error: The Human68k path is too long // Is the relevant path cached? pPath = FindCache(szHumanPath); if (pPath == nullptr) { // Check for max number of cache if (m_nRing >= XM6_HOST_DIRENTRY_CACHE_MAX) { // Destroy the oldest cache and reuse it pPath = (CHostPath*)m_cRing.Prev(); pPath->Clean(); // Release all files. Release update check handlers. } else { // Register new pPath = new CHostPath; assert(pPath); m_nRing++; } pPath->SetHuman(szHumanPath); pPath->SetHost(szHostPath); // Update status pPath->Refresh(); } // Cache update check if (pPath->isRefresh()) { Update(); // Update status pPath->Refresh(); } // Into the beginning of the ring pPath->Insert(&m_cRing); // Exit if there is not file name if (n == 0) break; // Find the next path // Confirm if directory from the middle of the path const CHostFilename* pFilename; if (*p != '\0') pFilename = pPath->FindFilename(szHumanFilename, Human68k::AT_DIRECTORY); else pFilename = pPath->FindFilename(szHumanFilename); if (pFilename == nullptr) return nullptr; // Error: Could not find path or file names in the middle // Link path name strcpy((char*)szHumanPath + nHumanPath, (const char*)szHumanFilename); nHumanPath += n; n = strlen(pFilename->GetHost()); if (nHostPath + n >= FILEPATH_MAX) return nullptr; // Error: Host side path is too long strcpy(szHostPath + nHostPath, pFilename->GetHost()); nHostPath += n; // PLEASE CONTINUE if (*p == '\0') break; } // Store the host side path name pFiles->SetResult(szHostPath); return pPath; } //--------------------------------------------------------------------------- // /// Find host side name (path name + file name (can be abbeviated) + attribute) /// /// Set all Human68k parameters once more. // //--------------------------------------------------------------------------- bool CHostDrv::Find(CHostFiles* pFiles) { assert(pFiles); // Get path name and build cache const CHostPath* pPath = CopyCache(pFiles); if (pPath == nullptr) { pPath = MakeCache(pFiles); if (pPath == nullptr) { CleanCache(); return false; // Error: Failed to build cache } } // Store host side path pFiles->SetResult(pPath->GetHost()); // Exit if only path name if (pFiles->isPathOnly()) { return true; // Normal exit: only path name } // Find file name const CHostFilename* pFilename = pFiles->Find(pPath); if (pFilename == nullptr) { return false; // Error: Could not get file name } // Store the Human68k side search results pFiles->SetEntry(pFilename); // Store the host side full path name pFiles->AddResult(pFilename->GetHost()); return true; } //=========================================================================== // // Directory entry: File name // //=========================================================================== //--------------------------------------------------------------------------- // /// Set host side name // //--------------------------------------------------------------------------- void CHostFilename::SetHost(const TCHAR* szHost) { assert(szHost); assert(strlen(szHost) < FILEPATH_MAX); strcpy(m_szHost, szHost); } //--------------------------------------------------------------------------- // /// Copy the Human68k file name elements // //--------------------------------------------------------------------------- uint8_t* CHostFilename::CopyName(uint8_t* pWrite, const uint8_t* pFirst, const uint8_t* pLast) // static { assert(pWrite); assert(pFirst); assert(pLast); for (const uint8_t* p = pFirst; p < pLast; p++) { *pWrite++ = *p; } return pWrite; } //--------------------------------------------------------------------------- // /// Convert the Human68k side name /// /// Once more, execute SetHost(). /// Carry out name conversion to the 18+3 standard. /// Automatically delete spaces in the beginning and end of the names, since Human68k can't handle them. /// The directory entry name segment is created at the time of conversion using knowledge of the location of the file name extension. /// Afterwards, a file name validity check is performed. (Ex. file names consisting of 8 spaces only.) /// No file name duplication check is performed so be careful. Such validation is carried out in classes higher up. /// Adhers to the naming standards of: TwentyOne version 1.36c modified +14 patchlevel9 or later // //--------------------------------------------------------------------------- void CHostFilename::ConvertHuman(int nCount) { // Don't do conversion for special directory names if (m_szHost[0] == '.' && (m_szHost[1] == '\0' || (m_szHost[1] == '.' && m_szHost[2] == '\0'))) { strcpy((char*)m_szHuman, m_szHost); m_bCorrect = true; m_pszHumanLast = m_szHuman + strlen((const char*)m_szHuman); m_pszHumanExt = m_pszHumanLast; return; } size_t nMax = 18; // Number of bytes for the base segment (base name and extension) uint32_t nOption = CFileSys::GetFileOption(); if (nOption & WINDRV_OPT_CONVERT_LENGTH) nMax = 8; // Preparations to adjust the base name segment uint8_t szNumber[8]; uint8_t* pNumber = nullptr; if (nCount >= 0) { pNumber = &szNumber[8]; for (uint32_t i = 0; i < 5; i++) { // Max 5+1 digits (always leave the first 2 bytes of the base name) int n = nCount % 36; nMax--; pNumber--; *pNumber = (uint8_t)(n + (n < 10 ? '0' : 'A' - 10)); nCount /= 36; if (nCount == 0) break; } nMax--; pNumber--; auto c = (uint8_t)((nOption >> 24) & 0x7F); if (c == 0) c = XM6_HOST_FILENAME_MARK; *pNumber = c; } // Char conversion uint8_t szHuman[FILEPATH_MAX]; const uint8_t* pFirst = szHuman; uint8_t* pLast; uint8_t* pExt = nullptr; { char szHost[FILEPATH_MAX]; strcpy(szHost, m_szHost); auto pRead = (const uint8_t*)szHost; uint8_t* pWrite = szHuman; const auto pPeriod = SeparateExt(pRead); for (bool bFirst = true;; bFirst = false) { uint8_t c = *pRead++; switch (c) { case ' ': if (nOption & WINDRV_OPT_REDUCED_SPACE) continue; if (nOption & WINDRV_OPT_CONVERT_SPACE) c = '_'; else if (pWrite == szHuman) continue; // Ignore spaces in the beginning break; case '=': case '+': if (nOption & WINDRV_OPT_REDUCED_BADCHAR) continue; if (nOption & WINDRV_OPT_CONVERT_BADCHAR) c = '_'; break; case '-': if (bFirst) { if (nOption & WINDRV_OPT_REDUCED_HYPHEN) continue; if (nOption & WINDRV_OPT_CONVERT_HYPHEN) c = '_'; break; } if (nOption & WINDRV_OPT_REDUCED_HYPHENS) continue; if (nOption & WINDRV_OPT_CONVERT_HYPHENS) c = '_'; break; case '.': if (pRead - 1 == pPeriod) { // Make exception for Human68k extensions pExt = pWrite; break; } if (bFirst) { if (nOption & WINDRV_OPT_REDUCED_PERIOD) continue; if (nOption & WINDRV_OPT_CONVERT_PERIOD) c = '_'; break; } if (nOption & WINDRV_OPT_REDUCED_PERIODS) continue; if (nOption & WINDRV_OPT_CONVERT_PERIODS) c = '_'; break; default: break; } *pWrite++ = c; if (c == '\0') break; } pLast = pWrite - 1; } // Adjust extensions if (pExt) { // Delete spaces at the end while (pExt < pLast - 1 && *(pLast - 1) == ' ') { pLast--; auto p = pLast; *p = '\0'; } // Delete if the file name disappeared after conversion if (pExt + 1 >= pLast) { pLast = pExt; *pLast = '\0'; // Just in case } } else { pExt = pLast; } // Introducing the cast of characters // // pFirst: I'm the glorious leader. The start of the file name. // pCut: A.k.a. Phase. Location of the initial period. Afterwards becomes the end of the base name. // pSecond: Hello there! I'm the incredible Murdock. The start of the file name extension. What's it to you? // pExt: B.A. Baracus. The Human68k extension genius. But don't you dare giving me more than 3 chars, fool. // The location of the final period. If not applicable, gets the same value as pLast. // // ↓pFirst ↓pStop ↓pSecond ← ↓pExt // T h i s _ i s _ a . V e r y . L o n g . F i l e n a m e . t x t \0 // ↑pCut ← ↑pCut initial location ↑pLast // // The above example becomes "This.Long.Filename.txt" after conversion // Evaluate first char const uint8_t* pCut = pFirst; const uint8_t* pStop = pExt - nMax; // Allow for up to 17 bytes for extension (leave base name) if (pFirst < pExt) { pCut++; // 1 byte always uses the base name uint8_t c = *pFirst; if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF pCut++; // Base name. At least 2 bytes. pStop++; // File extension. Max 16 bytes. } } if (pStop < pFirst) pStop = pFirst; // Evaluate base name pCut = (const uint8_t*)strchr((const char*)pCut, '.'); // The 2nd byte of Shift-JIS is always 0x40 or higher, so this is ok if (pCut == nullptr) pCut = pLast; if ((size_t)(pCut - pFirst) > nMax) pCut = pFirst + nMax; // Execute Shift-JIS 2 byte evaluation/adjustment later. Not allowed to do it here. // Evaluate extension const uint8_t* pSecond = pExt; const uint8_t* p; for (p = pExt - 1; pStop < p; p--) { if (*p == '.') pSecond = p; // The 2nd byte of Shift-JIS is always 0x40 or higher, so this is ok } // Shorten base name // Length of extension segment if (size_t nExt = pExt - pSecond; (size_t)(pCut - pFirst) + nExt > nMax) pCut = pFirst + nMax - nExt; // If in the middle of a 2 byte char, shorten even further for (p = pFirst; p < pCut; p++) { uint8_t c = *p; if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF p++; if (p >= pCut) { pCut--; break; } } } // Joining the name uint8_t* pWrite = m_szHuman; pWrite = CopyName(pWrite, pFirst, pCut); // Transfer the base name if (pNumber) pWrite = CopyName(pWrite, pNumber, &szNumber[8]); // Transfer the adjustment char pWrite = CopyName(pWrite, pSecond, pExt); // Transfer the extension name m_pszHumanExt = pWrite; // Store the extention position pWrite = CopyName(pWrite, pExt, pLast); // Transfer the Human68k extension m_pszHumanLast = pWrite; // Store the end position *pWrite = '\0'; // Confirm the conversion results m_bCorrect = true; // Fail if the base file name does not exist if (m_pszHumanExt <= m_szHuman) m_bCorrect = false; // Fail if the base file name is more than 1 char and ends with a space // While it is theoretically valid to have a base file name exceed 8 chars, // Human68k is unable to handle it, so failing this case too. else if (m_pszHumanExt[-1] == ' ') m_bCorrect = false; // Fail if the conversion result is the same as a special directory name if (m_szHuman[0] == '.' && (m_szHuman[1] == '\0' || (m_szHuman[1] == '.' && m_szHuman[2] == '\0'))) m_bCorrect = false; } //--------------------------------------------------------------------------- // /// Human68k side name duplication /// /// Duplicates the file name segment data, then executes the correspoding initialization with ConvertHuman(). // //--------------------------------------------------------------------------- void CHostFilename::CopyHuman(const uint8_t* szHuman) { assert(szHuman); assert(strlen((const char*)szHuman) < 23); strcpy((char*)m_szHuman, (const char*)szHuman); m_bCorrect = true; m_pszHumanLast = m_szHuman + strlen((const char*)m_szHuman); m_pszHumanExt = SeparateExt(m_szHuman); } //--------------------------------------------------------------------------- // /// Set Human68k directory entry /// /// Apply the set file name to the directory entry with ConvertHuman(). // //--------------------------------------------------------------------------- void CHostFilename::SetEntryName() { // Set file name const uint8_t* p = m_szHuman; size_t i; for (i = 0; i < 8; i++) { if (p < m_pszHumanExt) m_dirHuman.name[i] = *p++; else m_dirHuman.name[i] = ' '; } for (i = 0; i < 10; i++) { if (p < m_pszHumanExt) m_dirHuman.add[i] = *p++; else m_dirHuman.add[i] = '\0'; } if (*p == '.') p++; for (i = 0; i < 3; i++) { uint8_t c = *p; if (c) p++; m_dirHuman.ext[i] = c; } } //--------------------------------------------------------------------------- // /// Investigate if the Human68k side name has been processed // //--------------------------------------------------------------------------- bool CHostFilename::isReduce() const { return strcmp((const char *)m_szHost, (const char*)m_szHuman) != 0; } //--------------------------------------------------------------------------- // /// Evaluate Human68k directory entry attribute // //--------------------------------------------------------------------------- int CHostFilename::CheckAttribute(uint32_t nHumanAttribute) const { uint8_t nAttribute = m_dirHuman.attr; if ((nAttribute & (Human68k::AT_ARCHIVE | Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) == 0) nAttribute |= Human68k::AT_ARCHIVE; return nAttribute & nHumanAttribute; } //--------------------------------------------------------------------------- // /// Split the extension from Human68k file name // //--------------------------------------------------------------------------- const uint8_t* CHostFilename::SeparateExt(const uint8_t* szHuman) // static { // Obtain the file name length const size_t nLength = strlen((const char*)szHuman); const uint8_t* pFirst = szHuman; const uint8_t* pLast = pFirst + nLength; // Confirm the position of the Human68k extension auto pExt = (const uint8_t*)strrchr((const char*)pFirst, '.'); // The 2nd byte of Shift-JIS is always 0x40 or higher, so this is ok if (pExt == nullptr) pExt = pLast; // Special handling of the pattern where the file name is 20~22 chars, and the 19th char is '.' or ends with '.' if (20 <= nLength && nLength <= 22 && pFirst[18] == '.' && pFirst[nLength - 1] == '.') pExt = pFirst + 18; // Calculate the number of chars in the extension (-1:None 0:Only period 1~3:Human68k extension 4 or above:extension name) // Consider it an extension only when '.' is anywhere except the beginning of the string, and between 1~3 chars long if (size_t nExt = pLast - pExt - 1; pExt == pFirst || nExt < 1 || nExt > 3) pExt = pLast; return pExt; } //=========================================================================== // // Directory entry: path name // //=========================================================================== uint32_t CHostPath::g_nId; ///< Identifier creation counter CHostPath::~CHostPath() { Clean(); } //--------------------------------------------------------------------------- // /// File name memory allocation /// /// In most cases, the length of the host side file name is way shorter /// than the size of the buffer. In addition, file names may be created in huge volumes. /// Therefore, allocate variable lengths that correspond to the number of chars. // //--------------------------------------------------------------------------- CHostPath::ring_t* CHostPath::Alloc(size_t nLength) // static { assert(nLength < FILEPATH_MAX); const size_t n = offsetof(ring_t, f) + CHostFilename::Offset() + (nLength + 1) * sizeof(TCHAR); auto p = (ring_t*)malloc(n); assert(p); p->r.Init(); // This is nothing to worry about! return p; } //--------------------------------------------------------------------------- // // Release file name allocations // //--------------------------------------------------------------------------- void CHostPath::Free(ring_t* pRing) // static { assert(pRing); pRing->~ring_t(); free(pRing); } //--------------------------------------------------------------------------- // /// Initialize for reuse // //--------------------------------------------------------------------------- void CHostPath::Clean() { Release(); // Release all file names ring_t* p; while ((p = (ring_t*)m_cRing.Next()) != (ring_t*)&m_cRing) { Free(p); } } //--------------------------------------------------------------------------- // /// Specify Human68k side names directly // //--------------------------------------------------------------------------- void CHostPath::SetHuman(const uint8_t* szHuman) { assert(szHuman); assert(strlen((const char*)szHuman) < HUMAN68K_PATH_MAX); strcpy((char*)m_szHuman, (const char*)szHuman); } //--------------------------------------------------------------------------- // /// Specify host side names directly // //--------------------------------------------------------------------------- void CHostPath::SetHost(const TCHAR* szHost) { assert(szHost); assert(strlen(szHost) < FILEPATH_MAX); strcpy(m_szHost, szHost); } //--------------------------------------------------------------------------- // /// Compare arrays (supports wildcards) // //--------------------------------------------------------------------------- int CHostPath::Compare(const uint8_t* pFirst, const uint8_t* pLast, const uint8_t* pBufFirst, const uint8_t* pBufLast) { assert(pFirst); assert(pLast); assert(pBufFirst); assert(pBufLast); // Compare chars bool bSkip0 = false; bool bSkip1 = false; for (const uint8_t* p = pFirst; p < pLast; p++) { // Read 1 char uint8_t c = *p; uint8_t d = '\0'; if (pBufFirst < pBufLast) d = *pBufFirst++; // Ajust char for comparison if (!bSkip0) { if (!bSkip1) { // First byte for both c and d if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF bSkip0 = true; } if ((0x80 <= d && d <= 0x9F) || 0xE0 <= d) { // Specifically 0x81~0x9F 0xE0~0xEF bSkip1 = true; } if (c == d) continue; // Finishes the evaluation here with high probability if ((CFileSys::GetFileOption() & WINDRV_OPT_ALPHABET) == 0) { if ('A' <= c && c <= 'Z') c += 'a' - 'A'; // To lower case if ('A' <= d && d <= 'Z') d += 'a' - 'A'; // To lower case } // Unify slashes and backslashes for comparison if (c == '\\') { c = '/'; } if (d == '\\') { d = '/'; } } else { // Only c is first byte if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F 0xE0~0xEF bSkip0 = true; } bSkip1 = false; } } else { if (!bSkip1) { // Only d is first byte bSkip0 = false; if ((0x80 <= d && d <= 0x9F) || 0xE0 <= d) { // Specifically 0x81~0x9F 0xE0~0xEF bSkip1 = true; } } else { // Second byte for both c and d bSkip0 = false; bSkip1 = false; } } // Compare if (c == d) continue; if (c == '?') continue; return 1; } if (pBufFirst < pBufLast) return 2; return 0; } //--------------------------------------------------------------------------- // /// Compare Human68k side name // //--------------------------------------------------------------------------- bool CHostPath::isSameHuman(const uint8_t* szHuman) const { assert(szHuman); // Calulate number of chars const size_t nLength = strlen((const char*)m_szHuman); const size_t n = strlen((const char*)szHuman); // Check number of chars if (nLength != n) return false; // Compare Human68k path name return Compare(m_szHuman, m_szHuman + nLength, szHuman, szHuman + n) == 0; } bool CHostPath::isSameChild(const uint8_t* szHuman) const { assert(szHuman); // Calulate number of chars size_t nLength = strlen((const char*)m_szHuman); size_t n = strlen((const char*)szHuman); // Check number of chars if (nLength < n) return false; // Compare Human68k path name return Compare(m_szHuman, m_szHuman + n, szHuman, szHuman + n) == 0; } //--------------------------------------------------------------------------- // /// Find file name /// /// Check if whether it is a perfect match with the cache buffer, and return the name if found. /// Path names are excempted. /// Make sure to lock from the top. // //--------------------------------------------------------------------------- const CHostFilename* CHostPath::FindFilename(const uint8_t* szHuman, uint32_t nHumanAttribute) const { assert(szHuman); // Calulate number of chars const uint8_t* pFirst = szHuman; size_t nLength = strlen((const char*)pFirst); const uint8_t* pLast = pFirst + nLength; // Find something that matches perfectly with either of the stored file names const ring_t* p = (ring_t*)m_cRing.Next(); for (; p != (const ring_t*)&m_cRing; p = (ring_t*)p->r.Next()) { if (p->f.CheckAttribute(nHumanAttribute) == 0) continue; // Calulate number of chars const uint8_t* pBufFirst = p->f.GetHuman(); const uint8_t* pBufLast = p->f.GetHumanLast(); // Check number of chars if (size_t nBufLength = pBufLast - pBufFirst; nLength != nBufLength) continue; // File name check if (Compare(pFirst, pLast, pBufFirst, pBufLast) == 0) return &p->f; } return nullptr; } //--------------------------------------------------------------------------- // /// Find file name (with wildcard support) /// /// Check if whether it is a perfect match with the cache buffer, and return the name if found. /// Path names are excempted. /// Make sure to lock from the top. // //--------------------------------------------------------------------------- const CHostFilename* CHostPath::FindFilenameWildcard(const uint8_t* szHuman, uint32_t nHumanAttribute, find_t* pFind) const { assert(szHuman); assert(pFind); // Split the base file name and Human68k file extension const uint8_t* pFirst = szHuman; const uint8_t* pLast = pFirst + strlen((const char*)pFirst); const uint8_t* pExt = CHostFilename::SeparateExt(pFirst); // Move to the start position auto p = (const ring_t*)m_cRing.Next(); if (pFind->count > 0) { if (pFind->id == m_nId) { // If the same directory entry, continue right away from the previous position p = pFind->pos; } else { // Find the start position in the directory entry contents uint32_t n = 0; for (;; p = (const ring_t*)p->r.Next()) { if (p == (const ring_t*)&m_cRing) { // Extrapolate from the count when the same entry isn't found (just in case) p = (const ring_t*)m_cRing.Next(); n = 0; for (; p != (const ring_t*)&m_cRing; p = (const ring_t*)p->r.Next()) { if (n >= pFind->count) break; n++; } break; } if (p->f.isSameEntry(&pFind->entry)) { // Same entry is found pFind->count = n; break; } n++; } } } // Find files for (; p != (const ring_t*)&m_cRing; p = (const ring_t*)p->r.Next()) { pFind->count++; if (p->f.CheckAttribute(nHumanAttribute) == 0) continue; // Split the base file name and Human68k file extension const uint8_t* pBufFirst = p->f.GetHuman(); const uint8_t* pBufLast = p->f.GetHumanLast(); const uint8_t* pBufExt = p->f.GetHumanExt(); // Compare base file name if (Compare(pFirst, pExt, pBufFirst, pBufExt)) continue; // Compare Human68k extension // In the case of a '.???' extension, match the Human68k extension without period. if (strcmp((const char*)pExt, ".???") == 0 || Compare(pExt, pLast, pBufExt, pBufLast) == 0) { // Store the contents of the next candidate's directory entry const auto pNext = (const ring_t*)p->r.Next(); pFind->id = m_nId; pFind->pos = pNext; if (pNext != (const ring_t*)&m_cRing) memcpy(&pFind->entry, pNext->f.GetEntry(), sizeof(pFind->entry)); else memset(&pFind->entry, 0, sizeof(pFind->entry)); return &p->f; } } pFind->id = m_nId; pFind->pos = p; memset(&pFind->entry, 0, sizeof(pFind->entry)); return nullptr; } //--------------------------------------------------------------------------- // /// Confirm that the file update has been carried out // //--------------------------------------------------------------------------- bool CHostPath::isRefresh() const { return m_bRefresh; } //--------------------------------------------------------------------------- // /// ASCII sort function // //--------------------------------------------------------------------------- int AsciiSort(const dirent **a, const dirent **b) { return strcmp((*a)->d_name, (*b)->d_name); } //--------------------------------------------------------------------------- // /// Reconstruct the file /// /// Here we carry out the first host side file system observation. /// Always lock from the top. // //--------------------------------------------------------------------------- void CHostPath::Refresh() { assert(strlen(m_szHost) + 22 < FILEPATH_MAX); // Store time stamp Backup(); TCHAR szPath[FILEPATH_MAX]; strcpy(szPath, m_szHost); // Update refresh flag m_bRefresh = false; // Store previous cache contents CRing cRingBackup; m_cRing.InsertRing(&cRingBackup); // Register file name bool bUpdate = false; dirent **pd = nullptr; int nument = 0; int maxent = XM6_HOST_DIRENTRY_FILE_MAX; for (int i = 0; i < maxent; i++) { TCHAR szFilename[FILEPATH_MAX]; if (pd == nullptr) { nument = scandir(S2U(szPath), &pd, nullptr, AsciiSort); if (nument == -1) { pd = nullptr; break; } maxent = nument; } // When at the top level directory, exclude current and parent const dirent* pe = pd[i]; if (m_szHuman[0] == '/' && m_szHuman[1] == 0) { if (strcmp(pe->d_name, ".") == 0 || strcmp(pe->d_name, "..") == 0) { continue; } } // Get file name strcpy(szFilename, U2S(pe->d_name)); // Allocate file name memory ring_t* pRing = Alloc(strlen(szFilename)); CHostFilename* pFilename = &pRing->f; pFilename->SetHost(szFilename); // If there is a relevant file name in the previous cache, prioritize that for the Human68k name auto pCache = (ring_t*)cRingBackup.Next(); for (;;) { if (pCache == (ring_t*)&cRingBackup) { pCache = nullptr; // No relevant entry bUpdate = true; // Confirm new entry pFilename->ConvertHuman(); break; } if (strcmp(pFilename->GetHost(), pCache->f.GetHost()) == 0) { pFilename->CopyHuman(pCache->f.GetHuman()); // Copy Human68k name break; } pCache = (ring_t*)pCache->r.Next(); } // If there is a new entry, carry out file name duplication check. // If the host side file name changed, or if Human68k cannot express the file name, // generate a new file name that passes all the below checks: // - File name correctness // - No duplicated names in previous entries // - No entity with the same name exists if (pFilename->isReduce() || !pFilename->isCorrect()) { // Confirm that file name update is required for (uint32_t n = 0; n < XM6_HOST_FILENAME_PATTERN_MAX; n++) { // Confirm file name validity if (pFilename->isCorrect()) { // Confirm match with previous entry const CHostFilename* pCheck = FindFilename(pFilename->GetHuman()); if (pCheck == nullptr) { // If no match, confirm existence of real file strcpy(szPath, m_szHost); strcat(szPath, (const char*)pFilename->GetHuman()); if (struct stat sb; stat(S2U(szPath), &sb)) break; // Discover available patterns } } // Generate new name pFilename->ConvertHuman(n); } } // Directory entry name pFilename->SetEntryName(); // Get data strcpy(szPath, m_szHost); strcat(szPath, U2S(pe->d_name)); struct stat sb; if (stat(S2U(szPath), &sb)) continue; uint8_t nHumanAttribute = Human68k::AT_ARCHIVE; if (S_ISDIR(sb.st_mode)) nHumanAttribute = Human68k::AT_DIRECTORY; if ((sb.st_mode & 0200) == 0) nHumanAttribute |= Human68k::AT_READONLY; pFilename->SetEntryAttribute(nHumanAttribute); auto nHumanSize = (uint32_t)sb.st_size; pFilename->SetEntrySize(nHumanSize); uint16_t nHumanDate = 0; uint16_t nHumanTime = 0; if (tm pt = {}; localtime_r(&sb.st_mtime, &pt) != nullptr) { nHumanDate = (uint16_t)(((pt.tm_year - 80) << 9) | ((pt.tm_mon + 1) << 5) | pt.tm_mday); nHumanTime = (uint16_t)((pt.tm_hour << 11) | (pt.tm_min << 5) | (pt.tm_sec >> 1)); } pFilename->SetEntryDate(nHumanDate); pFilename->SetEntryTime(nHumanTime); pFilename->SetEntryCluster(0); // Compare with previous cached contents if (pCache) { if (pCache->f.isSameEntry(pFilename->GetEntry())) { Free(pRing); // Destroy entry that was created here pRing = pCache; // Use previous cache } else { Free(pCache); // Remove from the next search target bUpdate = true; // Flag for update if no match } } // Add to end of ring pRing->r.InsertTail(&m_cRing); } // Release directory entry if (pd) { for (int i = 0; i < nument; i++) { free(pd[i]); } free(pd); } // Delete remaining cache ring_t* p; while ((p = (ring_t*)cRingBackup.Next()) != (ring_t*)&cRingBackup) { bUpdate = true; // Confirms the decrease in entries due to deletion Free(p); } // Update the identifier if the update has been carried out if (bUpdate) m_nId = ++g_nId; } //--------------------------------------------------------------------------- // /// Store the host side time stamp // //--------------------------------------------------------------------------- void CHostPath::Backup() { assert(m_szHost); assert(strlen(m_szHost) < FILEPATH_MAX); TCHAR szPath[FILEPATH_MAX]; strcpy(szPath, m_szHost); size_t len = strlen(szPath); m_tBackup = 0; if (len > 1) { // Don't do anything if it is the root directory len--; assert(szPath[len] == '/'); szPath[len] = '\0'; if (struct stat sb; stat(S2U(szPath), &sb) == 0) m_tBackup = sb.st_mtime; } } //--------------------------------------------------------------------------- // /// Restore the host side time stamp // //--------------------------------------------------------------------------- void CHostPath::Restore() const { assert(m_szHost); assert(strlen(m_szHost) < FILEPATH_MAX); TCHAR szPath[FILEPATH_MAX]; strcpy(szPath, m_szHost); size_t len = strlen(szPath); if (m_tBackup) { assert(len); len--; assert(szPath[len] == '/'); szPath[len] = '\0'; utimbuf ut; ut.actime = m_tBackup; ut.modtime = m_tBackup; utime(szPath, &ut); } } //--------------------------------------------------------------------------- // /// Update // //--------------------------------------------------------------------------- void CHostPath::Release() { m_bRefresh = true; } //=========================================================================== // // Manage directory entries // //=========================================================================== CHostEntry::~CHostEntry() { Clean(); #ifdef DEBUG // Confirm object for (const auto& d : m_pDrv) { assert(d == nullptr); } #endif } //--------------------------------------------------------------------------- // /// Initialize (when the driver is installed) // //--------------------------------------------------------------------------- void CHostEntry::Init() const { #ifdef DEBUG // Confirm object for (const auto& d : m_pDrv) { assert(d == nullptr); } #endif } //--------------------------------------------------------------------------- // /// Release (at startup and reset) // //--------------------------------------------------------------------------- void CHostEntry::Clean() { // Delete object for (auto& d: m_pDrv) { delete d; d = nullptr; } } //--------------------------------------------------------------------------- // /// Update all cache // //--------------------------------------------------------------------------- void CHostEntry::CleanCache() const { for (const auto& d : m_pDrv) { if (d) d->CleanCache(); } CHostPath::InitId(); } //--------------------------------------------------------------------------- // /// Update the cache for the specified unit // //--------------------------------------------------------------------------- void CHostEntry::CleanCache(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); m_pDrv[nUnit]->CleanCache(); } //--------------------------------------------------------------------------- // /// Update the cache for the specified path // //--------------------------------------------------------------------------- void CHostEntry::CleanCache(uint32_t nUnit, const uint8_t* szHumanPath) const { assert(szHumanPath); assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); m_pDrv[nUnit]->CleanCache(szHumanPath); } //--------------------------------------------------------------------------- // /// Update all cache for the specified path and below // //--------------------------------------------------------------------------- void CHostEntry::CleanCacheChild(uint32_t nUnit, const uint8_t* szHumanPath) const { assert(szHumanPath); assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); m_pDrv[nUnit]->CleanCacheChild(szHumanPath); } //--------------------------------------------------------------------------- // /// Delete cache for the specified path // //--------------------------------------------------------------------------- void CHostEntry::DeleteCache(uint32_t nUnit, const uint8_t* szHumanPath) const { assert(szHumanPath); assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); m_pDrv[nUnit]->DeleteCache(szHumanPath); } //--------------------------------------------------------------------------- // /// Find host side names (path name + file name (can be abbreviated) + attribute) // //--------------------------------------------------------------------------- bool CHostEntry::Find(uint32_t nUnit, CHostFiles* pFiles) const { assert(pFiles); assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->Find(pFiles); } //--------------------------------------------------------------------------- // /// Drive settings // //--------------------------------------------------------------------------- void CHostEntry::SetDrv(uint32_t nUnit, CHostDrv* pDrv) { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit] == nullptr); m_pDrv[nUnit] = pDrv; } //--------------------------------------------------------------------------- // /// Is it write-protected? // //--------------------------------------------------------------------------- bool CHostEntry::isWriteProtect(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->isWriteProtect(); } //--------------------------------------------------------------------------- // /// Is it accessible? // //--------------------------------------------------------------------------- bool CHostEntry::isEnable(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->isEnable(); } //--------------------------------------------------------------------------- // /// Media check // //--------------------------------------------------------------------------- bool CHostEntry::isMediaOffline(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->isMediaOffline(); } //--------------------------------------------------------------------------- // /// Get media byte // //--------------------------------------------------------------------------- uint8_t CHostEntry::GetMediaByte(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->GetMediaByte(); } //--------------------------------------------------------------------------- // /// Get drive status // //--------------------------------------------------------------------------- uint32_t CHostEntry::GetStatus(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->GetStatus(); } //--------------------------------------------------------------------------- // /// Media change check // //--------------------------------------------------------------------------- bool CHostEntry::CheckMedia(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->CheckMedia(); } //--------------------------------------------------------------------------- // /// Eject // //--------------------------------------------------------------------------- void CHostEntry::Eject(uint32_t nUnit) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); m_pDrv[nUnit]->Eject(); } //--------------------------------------------------------------------------- // /// Get volume label // //--------------------------------------------------------------------------- void CHostEntry::GetVolume(uint32_t nUnit, TCHAR* szLabel) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); m_pDrv[nUnit]->GetVolume(szLabel); } //--------------------------------------------------------------------------- // /// Get volume label from cache // //--------------------------------------------------------------------------- bool CHostEntry::GetVolumeCache(uint32_t nUnit, TCHAR* szLabel) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->GetVolumeCache(szLabel); } //--------------------------------------------------------------------------- // /// Get capacity // //--------------------------------------------------------------------------- uint32_t CHostEntry::GetCapacity(uint32_t nUnit, Human68k::capacity_t* pCapacity) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->GetCapacity(pCapacity); } //--------------------------------------------------------------------------- // /// Get cluster size from cache // //--------------------------------------------------------------------------- bool CHostEntry::GetCapacityCache(uint32_t nUnit, Human68k::capacity_t* pCapacity) const { assert(nUnit < DRIVE_MAX); assert(m_pDrv[nUnit]); return m_pDrv[nUnit]->GetCapacityCache(pCapacity); } //--------------------------------------------------------------------------- // /// Split and copy the first element from the Human68k full path name /// /// Get the first element from the Human68k full path name and delete the path separator char. /// 23 bytes is required in the buffer to write to. /// A Human68k path always starts with a '/'. /// Throw an error if 2 '/' appears in sequence. /// If the array ends with a '/' treat it as an empty array and don't trow an error. // //--------------------------------------------------------------------------- const uint8_t* CHostDrv::SeparateCopyFilename(const uint8_t* szHuman, uint8_t* szBuffer) // static { assert(szHuman); assert(szBuffer); const size_t nMax = 22; const uint8_t* p = szHuman; uint8_t c = *p++; // Read if (c != '/' && c != '\\') return nullptr; // Error: Invalid path name // Insert one file size_t i = 0; for (;;) { c = *p; // Read if (c == '\0') break; // Exit if at the end of an array (return the end position) if (c == '/' || c == '\\') { if (i == 0) return nullptr; // Error: Two separator chars appear in sequence break; // Exit after reading the separator (return the char position) } p++; if (i >= nMax) return nullptr; // Error: The first byte hits the end of the buffer szBuffer[i++] = c; // Read if ((0x80 <= c && c <= 0x9F) || 0xE0 <= c) { // Specifically 0x81~0x9F and 0xE0~0xEF c = *p++; // Read if (c < 0x40) return nullptr; // Error: Invalid Shift-JIS 2nd byte if (i >= nMax) return nullptr; // Error: The second byte hits the end of the buffer szBuffer[i++] = c; // Read } } szBuffer[i] = '\0'; // Read return p; } //--------------------------------------------------------------------------- // /// Generate path and file name internally // //--------------------------------------------------------------------------- void CHostFiles::SetPath(const Human68k::namests_t* pNamests) { assert(pNamests); pNamests->GetCopyPath(m_szHumanPath); pNamests->GetCopyFilename(m_szHumanFilename); m_nHumanWildcard = 0; m_nHumanAttribute = Human68k::AT_ARCHIVE; m_findNext.Clear(); } //--------------------------------------------------------------------------- // /// Find file on the Human68k side and create data on the host side // //--------------------------------------------------------------------------- bool CHostFiles::Find(uint32_t nUnit, const CHostEntry* pEntry) { assert(pEntry); return pEntry->Find(nUnit, this); } //--------------------------------------------------------------------------- // /// Find file name // //--------------------------------------------------------------------------- const CHostFilename* CHostFiles::Find(const CHostPath* pPath) { assert(pPath); if (m_nHumanWildcard) return pPath->FindFilenameWildcard(m_szHumanFilename, m_nHumanAttribute, &m_findNext); return pPath->FindFilename(m_szHumanFilename, m_nHumanAttribute); } //--------------------------------------------------------------------------- // /// Store the Human68k side search results // //--------------------------------------------------------------------------- void CHostFiles::SetEntry(const CHostFilename* pFilename) { assert(pFilename); // Store Human68k directory entry memcpy(&m_dirHuman, pFilename->GetEntry(), sizeof(m_dirHuman)); // Stire Human68k file name strcpy((char*)m_szHumanResult, (const char*)pFilename->GetHuman()); } //--------------------------------------------------------------------------- // /// Set host side name // //--------------------------------------------------------------------------- void CHostFiles::SetResult(const TCHAR* szPath) { assert(szPath); assert(strlen(szPath) < FILEPATH_MAX); strcpy(m_szHostResult, szPath); } //--------------------------------------------------------------------------- // /// Add file name to the host side name // //--------------------------------------------------------------------------- void CHostFiles::AddResult(const TCHAR* szPath) { assert(szPath); assert(strlen(m_szHostResult) + strlen(szPath) < FILEPATH_MAX); strcat(m_szHostResult, szPath); } //--------------------------------------------------------------------------- // /// Add a new Human68k file name to the host side name // //--------------------------------------------------------------------------- void CHostFiles::AddFilename() { assert(strlen(m_szHostResult) + strlen((const char*)m_szHumanFilename) < FILEPATH_MAX); strncat(m_szHostResult, (const char*)m_szHumanFilename, ARRAY_SIZE(m_szHumanFilename)); } //=========================================================================== // // File search memory manager // //=========================================================================== #ifdef _DEBUG CHostFilesManager::~CHostFilesManager() { // Confirm that the entity does not exist (just in case) assert(m_cRing.Next() == &m_cRing); assert(m_cRing.Prev() == &m_cRing); } #endif // _DEBUG //--------------------------------------------------------------------------- // /// Initialization (when the driver is installed) // //--------------------------------------------------------------------------- void CHostFilesManager::Init() { // Confirm that the entity does not exist (just in case) assert(m_cRing.Next() == &m_cRing); assert(m_cRing.Prev() == &m_cRing); // Allocate memory for (uint32_t i = 0; i < XM6_HOST_FILES_MAX; i++) { auto p = new ring_t(); p->r.Insert(&m_cRing); } } //--------------------------------------------------------------------------- // /// Release (at startup and reset) // //--------------------------------------------------------------------------- void CHostFilesManager::Clean() { // Release memory CRing* p; while ((p = m_cRing.Next()) != &m_cRing) { delete (ring_t*)p; } } CHostFiles* CHostFilesManager::Alloc(uint32_t nKey) { assert(nKey); // Select from the end auto p = (ring_t*)m_cRing.Prev(); // Move to the start of the ring p->r.Insert(&m_cRing); p->f.SetKey(nKey); return &p->f; } CHostFiles* CHostFilesManager::Search(uint32_t nKey) { // assert(nKey); // The search key may become 0 due to DPB damage // Find the relevant object auto p = (ring_t*)m_cRing.Next(); for (; p != (ring_t*)&m_cRing; p = (ring_t*)p->r.Next()) { if (p->f.isSameKey(nKey)) { // Move to the start of the ring p->r.Insert(&m_cRing); return &p->f; } } return nullptr; } void CHostFilesManager::Free(CHostFiles* pFiles) { assert(pFiles); // Release pFiles->SetKey(0); // Move to the end of the ring auto p = (ring_t*)((size_t)pFiles - offsetof(ring_t, f)); p->r.InsertTail(&m_cRing); } //=========================================================================== // // FCB processing // //=========================================================================== //--------------------------------------------------------------------------- // /// Set file open mode // //--------------------------------------------------------------------------- bool CHostFcb::SetMode(uint32_t nHumanMode) { switch (nHumanMode & Human68k::OP_MASK) { case Human68k::OP_READ: m_pszMode = "rb"; break; case Human68k::OP_WRITE: m_pszMode = "wb"; break; case Human68k::OP_FULL: m_pszMode = "r+b"; break; default: return false; } m_bFlag = (nHumanMode & Human68k::OP_SPECIAL) != 0; return true; } void CHostFcb::SetFilename(const TCHAR* szFilename) { assert(szFilename); assert(strlen(szFilename) < FILEPATH_MAX); strcpy(m_szFilename, szFilename); } void CHostFcb::SetHumanPath(const uint8_t* szHumanPath) { assert(szHumanPath); assert(strlen((const char*)szHumanPath) < HUMAN68K_PATH_MAX); strcpy((char*)m_szHumanPath, (const char*)szHumanPath); } //--------------------------------------------------------------------------- // /// Create file /// /// Return false if error is thrown. // //--------------------------------------------------------------------------- bool CHostFcb::Create(uint32_t, bool bForce) { assert((Human68k::AT_DIRECTORY | Human68k::AT_VOLUME) == 0); assert(strlen(m_szFilename) > 0); assert(m_pFile == nullptr); // Duplication check if (!bForce) { if (struct stat sb; stat(S2U(m_szFilename), &sb) == 0) return false; } // Create file m_pFile = fopen(S2U(m_szFilename), "w+b"); /// @warning The ideal operation is to overwrite each attribute return m_pFile != nullptr; } //--------------------------------------------------------------------------- // /// File open /// /// Return false if error is thrown. // //--------------------------------------------------------------------------- bool CHostFcb::Open() { assert(strlen(m_szFilename) > 0); // Fail if directory if (struct stat st; stat(S2U(m_szFilename), &st) == 0 && ((st.st_mode & S_IFMT) == S_IFDIR)) { return false || m_bFlag; } // File open if (m_pFile == nullptr) m_pFile = fopen(S2U(m_szFilename), m_pszMode); return m_pFile != nullptr || m_bFlag; } //--------------------------------------------------------------------------- // /// Read file /// /// Handle a 0 byte read as normal operation too. /// Return -1 if error is thrown. // //--------------------------------------------------------------------------- uint32_t CHostFcb::Read(uint8_t* pBuffer, uint32_t nSize) { assert(pBuffer); assert(m_pFile); size_t nResult = fread(pBuffer, sizeof(uint8_t), nSize, m_pFile); if (ferror(m_pFile)) nResult = (size_t)-1; return (uint32_t)nResult; } //--------------------------------------------------------------------------- // /// Write file /// /// Handle a 0 byte read as normal operation too. /// Return -1 if error is thrown. // //--------------------------------------------------------------------------- uint32_t CHostFcb::Write(const uint8_t* pBuffer, uint32_t nSize) { assert(pBuffer); assert(m_pFile); size_t nResult = fwrite(pBuffer, sizeof(uint8_t), nSize, m_pFile); if (ferror(m_pFile)) nResult = (size_t)-1; return (uint32_t)nResult; } //--------------------------------------------------------------------------- // /// Truncate file /// /// Return false if error is thrown. // //--------------------------------------------------------------------------- bool CHostFcb::Truncate() const { assert(m_pFile); return ftruncate(fileno(m_pFile), ftell(m_pFile)) == 0; } //--------------------------------------------------------------------------- // /// File seek /// /// Return -1 if error is thrown. // //--------------------------------------------------------------------------- uint32_t CHostFcb::Seek(uint32_t nOffset, Human68k::seek_t nHumanSeek) { assert(m_pFile); int nSeek; switch (nHumanSeek) { case Human68k::seek_t::SK_BEGIN: nSeek = SEEK_SET; break; case Human68k::seek_t::SK_CURRENT: nSeek = SEEK_CUR; break; // case SK_END: default: nSeek = SEEK_END; break; } if (fseek(m_pFile, nOffset, nSeek)) return (uint32_t)-1; return (uint32_t)ftell(m_pFile); } //--------------------------------------------------------------------------- // /// Set file time stamp /// /// Return false if error is thrown. // //--------------------------------------------------------------------------- bool CHostFcb::TimeStamp(uint32_t nHumanTime) const { assert(m_pFile || m_bFlag); tm t = { }; t.tm_year = (nHumanTime >> 25) + 80; t.tm_mon = ((nHumanTime >> 21) - 1) & 15; t.tm_mday = (nHumanTime >> 16) & 31; t.tm_hour = (nHumanTime >> 11) & 31; t.tm_min = (nHumanTime >> 5) & 63; t.tm_sec = (nHumanTime & 31) << 1; time_t ti = mktime(&t); if (ti == (time_t)-1) return false; utimbuf ut; ut.actime = ti; ut.modtime = ti; // This is for preventing the last updated time stamp to be overwritten upon closing. // Flush and synchronize before updating the time stamp. fflush(m_pFile); return utime(S2U(m_szFilename), &ut) == 0 || m_bFlag; } //--------------------------------------------------------------------------- // /// File close /// /// Return false if error is thrown. // //--------------------------------------------------------------------------- void CHostFcb::Close() { // File close // Always initialize because of the Close→Free (internally one more Close) flow. if (m_pFile) { fclose(m_pFile); m_pFile = nullptr; } } //=========================================================================== // // FCB processing manager // //=========================================================================== #ifdef _DEBUG CHostFcbManager::~CHostFcbManager() { // Confirm that the entity does not exist (just in case) assert(m_cRing.Next() == &m_cRing); assert(m_cRing.Prev() == &m_cRing); } #endif // _DEBUG //--------------------------------------------------------------------------- // // Initialization (when the driver is installed) // //--------------------------------------------------------------------------- void CHostFcbManager::Init() { // Confirm that the entity does not exist (just in case) assert(m_cRing.Next() == &m_cRing); assert(m_cRing.Prev() == &m_cRing); // Memory allocation for (uint32_t i = 0; i < XM6_HOST_FCB_MAX; i++) { auto p = new ring_t; assert(p); p->r.Insert(&m_cRing); } } //--------------------------------------------------------------------------- // // Clean (at startup/reset) // //--------------------------------------------------------------------------- void CHostFcbManager::Clean() const { // Fast task killer CRing* p; while ((p = m_cRing.Next()) != &m_cRing) { delete (ring_t*)p; } } CHostFcb* CHostFcbManager::Alloc(uint32_t nKey) { assert(nKey); // Select from the end auto p = (ring_t*)m_cRing.Prev(); // Error if in use (just in case) if (!p->f.isSameKey(0)) { assert(false); return nullptr; } // Move to the top of the ring p->r.Insert(&m_cRing); // Set key p->f.SetKey(nKey); return &p->f; } CHostFcb* CHostFcbManager::Search(uint32_t nKey) { assert(nKey); // Search for applicable objects auto p = (ring_t*)m_cRing.Next(); while (p != (ring_t*)&m_cRing) { if (p->f.isSameKey(nKey)) { // Move to the top of the ring p->r.Insert(&m_cRing); return &p->f; } p = (ring_t*)p->r.Next(); } return nullptr; } void CHostFcbManager::Free(CHostFcb* pFcb) { assert(pFcb); // Free pFcb->SetKey(0); pFcb->Close(); // Move to the end of the ring auto p = (ring_t*)((size_t)pFcb - offsetof(ring_t, f)); p->r.InsertTail(&m_cRing); } //=========================================================================== // // Host side file system // //=========================================================================== uint32_t CFileSys::g_nOption; // File name conversion flag //--------------------------------------------------------------------------- // /// Reset (close all) // //--------------------------------------------------------------------------- void CFileSys::Reset() { // Initialize virtual sectors m_nHostSectorCount = 0; memset(m_nHostSectorBuffer, 0, sizeof(m_nHostSectorBuffer)); // File search memory - release (on startup and reset) m_cFiles.Clean(); // FCB operation memory (on startup and reset) m_cFcb.Clean(); // Directory entry - release (on startup and reset) m_cEntry.Clean(); // Initialize TwentyOne option monitoring m_nKernel = 0; m_nKernelSearch = 0; // Initialize operational flags SetOption(m_nOptionDefault); } //--------------------------------------------------------------------------- // /// Initialize (device startup and load) // //--------------------------------------------------------------------------- void CFileSys::Init() { // Initialize file search memory (device startup and load) m_cFiles.Init(); // Initialize FCB operation memory (device startup and load) m_cFcb.Init(); // Initialize directory entries (device startup and load) m_cEntry.Init(); // Evaluate per-path setting validity uint32_t nDrives = m_nDrives; if (nDrives == 0) { // Use root directory instead of per-path settings strcpy(m_szBase[0], "/"); m_nFlag[0] = 0; nDrives++; } // Register file system uint32_t nUnit = 0; for (uint32_t n = 0; n < nDrives; n++) { // Don't register is base path do not exist if (m_szBase[n][0] == '\0') continue; // Create 1 unit file system auto p = new CHostDrv; if (p) { m_cEntry.SetDrv(nUnit, p); p->Init(m_szBase[n], m_nFlag[n]); // To the next unit nUnit++; } } // Store the registered number of drives m_nUnits = nUnit; } //--------------------------------------------------------------------------- // /// $40 - Device startup // //--------------------------------------------------------------------------- uint32_t CFileSys::InitDevice(const Human68k::argument_t* pArgument) { InitOption(pArgument); // File system initialization Init(); return m_nUnits; } //--------------------------------------------------------------------------- // /// $41 - Directory check // //--------------------------------------------------------------------------- int CFileSys::CheckDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const { assert(pNamests); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive // Generate path name CHostFiles f; f.SetPath(pNamests); if (f.isRootPath()) return 0; f.SetPathOnly(); if (!f.Find(nUnit, &m_cEntry)) return FS_DIRNOTFND; return 0; } //--------------------------------------------------------------------------- // /// $42 - Create directory // //--------------------------------------------------------------------------- int CFileSys::MakeDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const { assert(pNamests); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Generate path name CHostFiles f; f.SetPath(pNamests); f.SetPathOnly(); if (!f.Find(nUnit, &m_cEntry)) return FS_INVALIDPATH; f.AddFilename(); // Create directory if (mkdir(S2U(f.GetPath()), 0777)) return FS_INVALIDPATH; // Update cache m_cEntry.CleanCache(nUnit, f.GetHumanPath()); return 0; } //--------------------------------------------------------------------------- // /// $43 - Delete directory // //--------------------------------------------------------------------------- int CFileSys::RemoveDir(uint32_t nUnit, const Human68k::namests_t* pNamests) const { assert(pNamests); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Generate path name CHostFiles f; f.SetPath(pNamests); f.SetAttribute(Human68k::AT_DIRECTORY); if (!f.Find(nUnit, &m_cEntry)) return FS_DIRNOTFND; // Delete cache uint8_t szHuman[HUMAN68K_PATH_MAX + 24]; assert(strlen((const char*)f.GetHumanPath()) + strlen((const char*)f.GetHumanFilename()) < HUMAN68K_PATH_MAX + 24); strcpy((char*)szHuman, (const char*)f.GetHumanPath()); strcat((char*)szHuman, (const char*)f.GetHumanFilename()); strcat((char*)szHuman, "/"); m_cEntry.DeleteCache(nUnit, szHuman); // Delete directory if (rmdir(S2U(f.GetPath()))) return FS_CANTDELETE; // Update cache m_cEntry.CleanCache(nUnit, f.GetHumanPath()); return 0; } //--------------------------------------------------------------------------- // /// $44 - Change file name // //--------------------------------------------------------------------------- int CFileSys::Rename(uint32_t nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew) const { assert(pNamests); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Generate path name CHostFiles f; f.SetPath(pNamests); f.SetAttribute(Human68k::AT_ALL); if (!f.Find(nUnit, &m_cEntry)) return FS_FILENOTFND; CHostFiles fNew; fNew.SetPath(pNamestsNew); fNew.SetPathOnly(); if (!fNew.Find(nUnit, &m_cEntry)) return FS_INVALIDPATH; fNew.AddFilename(); // Update cache if (f.GetAttribute() & Human68k::AT_DIRECTORY) m_cEntry.CleanCacheChild(nUnit, f.GetHumanPath()); // Change file name char szFrom[FILENAME_MAX]; char szTo[FILENAME_MAX]; SJIS2UTF8(f.GetPath(), szFrom, FILENAME_MAX); SJIS2UTF8(fNew.GetPath(), szTo, FILENAME_MAX); if (rename(szFrom, szTo)) { return FS_FILENOTFND; } // Update cache m_cEntry.CleanCache(nUnit, f.GetHumanPath()); m_cEntry.CleanCache(nUnit, fNew.GetHumanPath()); return 0; } //--------------------------------------------------------------------------- // /// $45 - Delete file // //--------------------------------------------------------------------------- int CFileSys::Delete(uint32_t nUnit, const Human68k::namests_t* pNamests) const { assert(pNamests); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Generate path name CHostFiles f; f.SetPath(pNamests); if (!f.Find(nUnit, &m_cEntry)) return FS_FILENOTFND; // Delete file if (unlink(S2U(f.GetPath()))) return FS_CANTDELETE; // Update cache m_cEntry.CleanCache(nUnit, f.GetHumanPath()); return 0; } //--------------------------------------------------------------------------- // /// $46 - Get/set file attribute // //--------------------------------------------------------------------------- int CFileSys::Attribute(uint32_t nUnit, const Human68k::namests_t* pNamests, uint32_t nHumanAttribute) const { assert(pNamests); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Generate path name CHostFiles f; f.SetPath(pNamests); f.SetAttribute(Human68k::AT_ALL); if (!f.Find(nUnit, &m_cEntry)) return FS_FILENOTFND; // Exit if attribute is acquired if (nHumanAttribute == 0xFF) return f.GetAttribute(); // Attribute check if (nHumanAttribute & Human68k::AT_VOLUME) return FS_CANTACCESS; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Generate attribute if (uint32_t nAttribute = (nHumanAttribute & Human68k::AT_READONLY) | (f.GetAttribute() & ~Human68k::AT_READONLY); f.GetAttribute() != nAttribute) { struct stat sb; if (stat(S2U(f.GetPath()), &sb)) return FS_FILENOTFND; mode_t m = sb.st_mode & 0777; if (nAttribute & Human68k::AT_READONLY) m &= 0555; // ugo-w else m |= 0200; // u+w // Set attribute if (chmod(S2U(f.GetPath()), m)) return FS_FILENOTFND; } // Update cache m_cEntry.CleanCache(nUnit, f.GetHumanPath()); // Get attribute after changing if (!f.Find(nUnit, &m_cEntry)) return FS_FILENOTFND; return f.GetAttribute(); } //--------------------------------------------------------------------------- // /// $47 - File search // //--------------------------------------------------------------------------- int CFileSys::Files(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles) { assert(pNamests); assert(nKey); assert(pFiles); // Release if memory with the same key already exists CHostFiles* pHostFiles = m_cFiles.Search(nKey); if (pHostFiles != nullptr) { m_cFiles.Free(pHostFiles); } // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive // Get volume label /** @note This skips the media check when getting the volume label to avoid triggering a fatal error with host side removable media (CD-ROM, etc.) Some poorly coded applications will attempt to get the volume label even though a proper error was thrown doing a media change check just before. */ if ((pFiles->fatr & (Human68k::AT_ARCHIVE | Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) == Human68k::AT_VOLUME) { // Path check CHostFiles f; f.SetPath(pNamests); if (!f.isRootPath()) return FS_FILENOTFND; // Immediately return the results without allocating buffer if (!FilesVolume(nUnit, pFiles)) return FS_FILENOTFND; return 0; } // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Allocate buffer pHostFiles = m_cFiles.Alloc(nKey); if (pHostFiles == nullptr) return FS_OUTOFMEM; // Directory check pHostFiles->SetPath(pNamests); if (!pHostFiles->isRootPath()) { pHostFiles->SetPathOnly(); if (!pHostFiles->Find(nUnit, &m_cEntry)) { m_cFiles.Free(pHostFiles); return FS_DIRNOTFND; } } // Enable wildcards pHostFiles->SetPathWildcard(); pHostFiles->SetAttribute(pFiles->fatr); // Find file if (!pHostFiles->Find(nUnit, &m_cEntry)) { m_cFiles.Free(pHostFiles); return FS_FILENOTFND; } // Store search results pFiles->attr = (uint8_t)pHostFiles->GetAttribute(); pFiles->date = pHostFiles->GetDate(); pFiles->time = pHostFiles->GetTime(); pFiles->size = pHostFiles->GetSize(); strcpy((char*)pFiles->full, (const char*)pHostFiles->GetHumanResult()); // Specify pseudo-directory entry pFiles->sector = nKey; pFiles->offset = 0; return 0; } //--------------------------------------------------------------------------- // /// $48 - Search next file // //--------------------------------------------------------------------------- int CFileSys::NFiles(uint32_t nUnit, uint32_t nKey, Human68k::files_t* pFiles) { assert(nKey); assert(pFiles); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Find buffer CHostFiles* pHostFiles = m_cFiles.Search(nKey); if (pHostFiles == nullptr) return FS_INVALIDPTR; // Find file if (!pHostFiles->Find(nUnit, &m_cEntry)) { m_cFiles.Free(pHostFiles); return FS_FILENOTFND; } assert(pFiles->sector == nKey); assert(pFiles->offset == 0); // Store search results pFiles->attr = (uint8_t)pHostFiles->GetAttribute(); pFiles->date = pHostFiles->GetDate(); pFiles->time = pHostFiles->GetTime(); pFiles->size = pHostFiles->GetSize(); strcpy((char*)pFiles->full, (const char*)pHostFiles->GetHumanResult()); return 0; } //--------------------------------------------------------------------------- // /// $49 - Create new file // //--------------------------------------------------------------------------- int CFileSys::Create(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, uint32_t nHumanAttribute, bool bForce) { assert(pNamests); assert(nKey); assert(pFcb); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Release if memory with the same key already exists if (m_cFcb.Search(nKey) != nullptr) return FS_INVALIDPTR; // Generate path name CHostFiles f; f.SetPath(pNamests); f.SetPathOnly(); if (!f.Find(nUnit, &m_cEntry)) return FS_INVALIDPATH; f.AddFilename(); // Attribute check if (nHumanAttribute & (Human68k::AT_DIRECTORY | Human68k::AT_VOLUME)) return FS_CANTACCESS; // Store path name CHostFcb* pHostFcb = m_cFcb.Alloc(nKey); if (pHostFcb == nullptr) return FS_OUTOFMEM; pHostFcb->SetFilename(f.GetPath()); pHostFcb->SetHumanPath(f.GetHumanPath()); // Set open mode pFcb->mode = (uint16_t)((pFcb->mode & ~Human68k::OP_MASK) | Human68k::OP_FULL); if (!pHostFcb->SetMode(pFcb->mode)) { m_cFcb.Free(pHostFcb); return FS_ILLEGALMOD; } // Create file if (!pHostFcb->Create(nHumanAttribute, bForce)) { m_cFcb.Free(pHostFcb); return FS_FILEEXIST; } // Update cache m_cEntry.CleanCache(nUnit, f.GetHumanPath()); return 0; } //--------------------------------------------------------------------------- // /// $4A - File open // //--------------------------------------------------------------------------- int CFileSys::Open(uint32_t nUnit, uint32_t nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb) { assert(pNamests); assert(nKey); assert(pFcb); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check switch (pFcb->mode & Human68k::OP_MASK) { case Human68k::OP_WRITE: case Human68k::OP_FULL: if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; break; default: break; } // Release if memory with the same key already exists if (m_cFcb.Search(nKey) != nullptr) return FS_INVALIDPRM; // Generate path name CHostFiles f; f.SetPath(pNamests); f.SetAttribute(Human68k::AT_ALL); if (!f.Find(nUnit, &m_cEntry)) return FS_FILENOTFND; // Time stamp pFcb->date = f.GetDate(); pFcb->time = f.GetTime(); // File size pFcb->size = f.GetSize(); // Store path name CHostFcb* pHostFcb = m_cFcb.Alloc(nKey); if (pHostFcb == nullptr) return FS_OUTOFMEM; pHostFcb->SetFilename(f.GetPath()); pHostFcb->SetHumanPath(f.GetHumanPath()); // Set open mode if (!pHostFcb->SetMode(pFcb->mode)) { m_cFcb.Free(pHostFcb); return FS_ILLEGALMOD; } // File open if (!pHostFcb->Open()) { m_cFcb.Free(pHostFcb); return FS_INVALIDPATH; } return 0; } //--------------------------------------------------------------------------- // /// $4B - File close // //--------------------------------------------------------------------------- int CFileSys::Close(uint32_t nUnit, uint32_t nKey, const Human68k::fcb_t* /* pFcb */) { assert(nKey); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Throw error if memory with the same key does not exist CHostFcb* pHostFcb = m_cFcb.Search(nKey); if (pHostFcb == nullptr) return FS_INVALIDPRM; // File close and release memory m_cFcb.Free(pHostFcb); // Update cache if (pHostFcb->isUpdate()) m_cEntry.CleanCache(nUnit, pHostFcb->GetHumanPath()); /// TODO: Match the FCB status on close with other devices return 0; } //--------------------------------------------------------------------------- // /// $4C - Read file /// /// Clean exit when 0 bytes are read. // //--------------------------------------------------------------------------- int CFileSys::Read(uint32_t nKey, Human68k::fcb_t* pFcb, uint8_t* pBuffer, uint32_t nSize) { assert(nKey); assert(pFcb); // assert(pBuffer); // Evaluate only when needed assert(nSize <= 0xFFFFFF); // Clipped // Throw error if memory with the same key does not exist CHostFcb* pHostFcb = m_cFcb.Search(nKey); if (pHostFcb == nullptr) return FS_NOTOPENED; // Confirm the existence of the buffer if (pBuffer == nullptr) { m_cFcb.Free(pHostFcb); return FS_INVALIDFUNC; } // Read const uint32_t nResult = pHostFcb->Read(pBuffer, nSize); if (nResult == (uint32_t)-1) { m_cFcb.Free(pHostFcb); return FS_INVALIDFUNC; // TODO: Should return error code 10 (read error) as well here } assert(nResult <= nSize); // Update file pointer pFcb->fileptr += nResult; /// TODO: Maybe an overflow check is needed here? return nResult; } //--------------------------------------------------------------------------- // /// $4D - Write file /// /// Truncate file if 0 bytes are written. // //--------------------------------------------------------------------------- int CFileSys::Write(uint32_t nKey, Human68k::fcb_t* pFcb, const uint8_t* pBuffer, uint32_t nSize) { assert(nKey); assert(pFcb); // assert(pBuffer); // Evaluate only when needed assert(nSize <= 0xFFFFFF); // Clipped // Throw error if memory with the same key does not exist CHostFcb* pHostFcb = m_cFcb.Search(nKey); if (pHostFcb == nullptr) return FS_NOTOPENED; uint32_t nResult; if (nSize == 0) { // Truncate if (!pHostFcb->Truncate()) { m_cFcb.Free(pHostFcb); return FS_CANTSEEK; } // Update file size pFcb->size = pFcb->fileptr; nResult = 0; } else { // Confirm the existence of the buffer if (pBuffer == nullptr) { m_cFcb.Free(pHostFcb); return FS_INVALIDFUNC; } // Write nResult = pHostFcb->Write(pBuffer, nSize); if (nResult == (uint32_t)-1) { m_cFcb.Free(pHostFcb); return FS_CANTWRITE; /// TODO: Should return error code 11 (write error) as well here } assert(nResult <= nSize); // Update file pointer pFcb->fileptr += nResult; /// TODO: Do we need an overflow check here? // Update file size if (pFcb->size < pFcb->fileptr) pFcb->size = pFcb->fileptr; } // Update flag pHostFcb->SetUpdate(); return nResult; } //--------------------------------------------------------------------------- // /// $4E - File seek // //--------------------------------------------------------------------------- int CFileSys::Seek(uint32_t nKey, Human68k::fcb_t* pFcb, uint32_t nSeek, int nOffset) { assert(pFcb); // Throw error if memory with the same key does not exist CHostFcb* pHostFcb = m_cFcb.Search(nKey); if (pHostFcb == nullptr) return FS_NOTOPENED; // Parameter check if (nSeek > (uint32_t)Human68k::seek_t::SK_END) { m_cFcb.Free(pHostFcb); return FS_INVALIDPRM; } // File seek uint32_t nResult = pHostFcb->Seek(nOffset, (Human68k::seek_t)nSeek); if (nResult == (uint32_t)-1) { m_cFcb.Free(pHostFcb); return FS_CANTSEEK; } // Update file pointer pFcb->fileptr = nResult; return nResult; } //--------------------------------------------------------------------------- // /// $4F - Get/set file time stamp /// /// Throw error when the top 16 bits are $FFFF. // //--------------------------------------------------------------------------- uint32_t CFileSys::TimeStamp(uint32_t nUnit, uint32_t nKey, Human68k::fcb_t* pFcb, uint32_t nHumanTime) { assert(nKey); assert(pFcb); // Get only if (nHumanTime == 0) return ((uint32_t)pFcb->date << 16) | pFcb->time; // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Throw error if memory with the same key does not exist CHostFcb* pHostFcb = m_cFcb.Search(nKey); if (pHostFcb == nullptr) return FS_NOTOPENED; // Set time stamp if (!pHostFcb->TimeStamp(nHumanTime)) { m_cFcb.Free(pHostFcb); return FS_INVALIDPRM; } pFcb->date = (uint16_t)(nHumanTime >> 16); pFcb->time = (uint16_t)nHumanTime; // Update cache m_cEntry.CleanCache(nUnit, pHostFcb->GetHumanPath()); return 0; } //--------------------------------------------------------------------------- // /// $50 - Get capacity // //--------------------------------------------------------------------------- int CFileSys::GetCapacity(uint32_t nUnit, Human68k::capacity_t* pCapacity) const { assert(pCapacity); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Get capacity return m_cEntry.GetCapacity(nUnit, pCapacity); } //--------------------------------------------------------------------------- // /// $51 - Inspect/control drive status // //--------------------------------------------------------------------------- int CFileSys::CtrlDrive(uint32_t nUnit, Human68k::ctrldrive_t* pCtrlDrive) const { assert(pCtrlDrive); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive switch (pCtrlDrive->status) { case 0: // Inspect status case 9: // Inspect status 2 pCtrlDrive->status = (uint8_t)m_cEntry.GetStatus(nUnit); return pCtrlDrive->status; case 1: // Eject case 2: // Eject forbidden 1 (not implemented) case 3: // Eject allowed 1 (not implemented) case 4: // Flash LED when media is not inserted (not implemented) case 5: // Turn off LED when media is not inserted (not implemented) case 6: // Eject forbidden 2 (not implemented) case 7: // Eject allowed 2 (not implemented) return 0; case 8: // Eject inspection return 1; default: break; } return FS_INVALIDFUNC; } //--------------------------------------------------------------------------- // /// $52 - Get DPB /// /// If DPB cannot be acquired after resuming, the drive will be torn down internally in Human68k. /// Therefore, treat even a unit out of bounds as normal operation. // //--------------------------------------------------------------------------- int CFileSys::GetDPB(uint32_t nUnit, Human68k::dpb_t* pDpb) const { assert(pDpb); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; Human68k::capacity_t cap; uint8_t media = Human68k::MEDIA_REMOTE; if (nUnit < m_nUnits) { media = m_cEntry.GetMediaByte(nUnit); // Acquire sector data if (!m_cEntry.GetCapacityCache(nUnit, &cap)) { // Carry out an extra media check here because it may be skipped when doing a manual eject if (!m_cEntry.isEnable(nUnit)) goto none; // Media check if (m_cEntry.isMediaOffline(nUnit)) goto none; // Get drive status m_cEntry.GetCapacity(nUnit, &cap); } } else { none: cap.clusters = 4; // This is totally fine, right? cap.sectors = 64; cap.bytes = 512; } // Calculate number of shifts uint32_t nSize = 1; uint32_t nShift = 0; for (;;) { if (nSize >= cap.sectors) break; nSize <<= 1; nShift++; } // Sector number calculation // // In the following order: // Cluster 0: Unused // Cluster 1: FAT // Cluster 2: Root directory // Cluster 3: Data memory (pseudo-sector) const uint32_t nFat = cap.sectors; const uint32_t nRoot = cap.sectors * 2; const uint32_t nData = cap.sectors * 3; // Set DPB pDpb->sector_size = cap.bytes; // Bytes per sector pDpb->cluster_size = (uint8_t)(cap.sectors - 1); // Sectors per cluster - 1 pDpb->shift = (uint8_t)nShift; // Number of cluster → sector shifts pDpb->fat_sector = (uint16_t)nFat; // First FAT sector number pDpb->fat_max = 1; // Number of FAT memory spaces pDpb->fat_size = (uint8_t)cap.sectors; // Number of sectors controlled by FAT (excluding copies) pDpb->file_max = (uint16_t)(cap.sectors * cap.bytes / 0x20); // Number of files in the root directory pDpb->data_sector = (uint16_t)nData; // First sector number of data memory pDpb->cluster_max = cap.clusters; // Total number of clusters + 1 pDpb->root_sector = (uint16_t)nRoot; // First sector number of the root directory pDpb->media = media; // Media byte return 0; } //--------------------------------------------------------------------------- // /// $53 - Read sector /// /// We use pseudo-sectors. /// Buffer size is hard coded to $200 byte. // //--------------------------------------------------------------------------- int CFileSys::DiskRead(uint32_t nUnit, uint8_t* pBuffer, uint32_t nSector, uint32_t nSize) { assert(pBuffer); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // This occurs when resuming with an invalid drive // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Throw error if number of sectors exceed 1 if (nSize != 1) return FS_INVALIDPRM; // Acquire sector data Human68k::capacity_t cap; if (!m_cEntry.GetCapacityCache(nUnit, &cap)) { // Get drive status m_cEntry.GetCapacity(nUnit, &cap); } // Access pseudo-directory entry const CHostFiles* pHostFiles = m_cFiles.Search(nSector); if (pHostFiles) { // Generate pseudo-directory entry auto dir = (Human68k::dirent_t*)pBuffer; memcpy(pBuffer, pHostFiles->GetEntry(), sizeof(*dir)); memset(pBuffer + sizeof(*dir), 0xE5, 0x200 - sizeof(*dir)); // Register the pseudo-sector number that points to the file entity inside the pseudo-directory. // Note that in lzdsys the sector number to read is calculated by the following formula: // (dirent.cluster - 2) * (dpb.cluster_size + 1) + dpb.data_sector /// @warning little endian only dir->cluster = (uint16_t)(m_nHostSectorCount + 2); // Pseudo-sector number m_nHostSectorBuffer[m_nHostSectorCount] = nSector; // Entity that points to the pseudo-sector m_nHostSectorCount++; m_nHostSectorCount %= XM6_HOST_PSEUDO_CLUSTER_MAX; return 0; } // Calculate the sector number from the cluster number uint32_t n = nSector - (3 * cap.sectors); uint32_t nMod = 1; if (cap.sectors) { // Beware that cap.sectors becomes 0 when media does not exist nMod = n % cap.sectors; n /= cap.sectors; } // Access the file entity if (nMod == 0 && n < XM6_HOST_PSEUDO_CLUSTER_MAX) { pHostFiles = m_cFiles.Search(m_nHostSectorBuffer[n]); // Find entity if (pHostFiles) { // Generate pseudo-sector CHostFcb f; f.SetFilename(pHostFiles->GetPath()); f.SetMode(Human68k::OP_READ); if (!f.Open()) return FS_INVALIDPRM; memset(pBuffer, 0, 0x200); uint32_t nResult = f.Read(pBuffer, 0x200); f.Close(); if (nResult == (uint32_t)-1) return FS_INVALIDPRM; return 0; } } return FS_INVALIDPRM; } //--------------------------------------------------------------------------- // /// $54 - Write sector // //--------------------------------------------------------------------------- int CFileSys::DiskWrite(uint32_t nUnit) const { // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Write-protect check if (m_cEntry.isWriteProtect(nUnit)) return FS_FATAL_WRITEPROTECT; // Thrust at reality return FS_INVALIDPRM; } //--------------------------------------------------------------------------- // /// $55 - IOCTRL // //--------------------------------------------------------------------------- int CFileSys::Ioctrl(uint32_t nUnit, uint32_t nFunction, Human68k::ioctrl_t* pIoctrl) { assert(pIoctrl); // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive switch (nFunction) { case 0: // Acquire media byte pIoctrl->media = m_cEntry.GetMediaByte(nUnit); return 0; case 1: // Dummy for Human68k compatibility pIoctrl->param = -1; return 0; case 2: switch (pIoctrl->param) { case (uint32_t)-1: // Re-identify media m_cEntry.isMediaOffline(nUnit); return 0; case 0: case 1: // Dummy for Human68k compatibility return 0; default: return FS_NOTIOCTRL; } break; case (uint32_t)-1: // Resident evaluation memcpy(pIoctrl->buffer, "WindrvXM", 8); return 0; case (uint32_t)-2: // Set options SetOption(pIoctrl->param); return 0; case (uint32_t)-3: // Get options pIoctrl->param = GetOption(); return 0; default: break; } return FS_NOTIOCTRL; } //--------------------------------------------------------------------------- // /// $56 - Flush // //--------------------------------------------------------------------------- int CFileSys::Flush(uint32_t nUnit) const { // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_INVALIDFUNC; // Avoid triggering a fatal error returning from a mint command when resuming with an invalid drive // Always succeed return 0; } //--------------------------------------------------------------------------- // /// $57 - Media change check // //--------------------------------------------------------------------------- int CFileSys::CheckMedia(uint32_t nUnit) const { // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; if (nUnit >= m_nUnits) return FS_INVALIDFUNC; // Avoid triggering a fatal error in mint when resuming with an invalid drive // Media change check // Throw error when media is not inserted if (!m_cEntry.CheckMedia(nUnit)) { return FS_INVALIDFUNC; } return 0; } //--------------------------------------------------------------------------- // /// $58 - Lock // //--------------------------------------------------------------------------- int CFileSys::Lock(uint32_t nUnit) const { // Unit check if (nUnit >= CHostEntry::DRIVE_MAX) return FS_FATAL_INVALIDUNIT; assert(nUnit < m_nUnits); if (nUnit >= m_nUnits) return FS_FATAL_MEDIAOFFLINE; // Just in case // Media check if (m_cEntry.isMediaOffline(nUnit)) return FS_FATAL_MEDIAOFFLINE; // Always succeed return 0; } //--------------------------------------------------------------------------- // /// Set options // //--------------------------------------------------------------------------- void CFileSys::SetOption(uint32_t nOption) { // Clear cache when option settings change if (m_nOption ^ nOption) m_cEntry.CleanCache(); m_nOption = nOption; g_nOption = nOption; } //--------------------------------------------------------------------------- // /// Initialize options // //--------------------------------------------------------------------------- void CFileSys::InitOption(const Human68k::argument_t* pArgument) { assert(pArgument); // Initialize number of drives m_nDrives = 0; const uint8_t* pp = pArgument->buf; pp += strlen((const char*)pp) + 1; uint32_t nOption = m_nOptionDefault; for (;;) { assert(pp < pArgument->buf + sizeof(*pArgument)); const uint8_t* p = pp; uint8_t c = *p++; if (c == '\0') break; uint32_t nMode; if (c == '+') { nMode = 1; } else if (c == '-') { nMode = 0; } else if (c == '/') { // Specify default base path if (m_nDrives < CHostEntry::DRIVE_MAX) { p--; strcpy(m_szBase[m_nDrives], (const char *)p); m_nDrives++; } pp += strlen((const char*)pp) + 1; continue; } else { // Continue since no option is specified pp += strlen((const char*)pp) + 1; continue; } for (;;) { c = *p++; if (c == '\0') break; uint32_t nBit = 0; switch (c) { case 'A': case 'a': nBit = WINDRV_OPT_CONVERT_LENGTH; break; case 'T': case 't': nBit = WINDRV_OPT_COMPARE_LENGTH; nMode ^= 1; break; case 'C': case 'c': nBit = WINDRV_OPT_ALPHABET; break; case 'E': nBit = WINDRV_OPT_CONVERT_PERIOD; break; case 'P': nBit = WINDRV_OPT_CONVERT_PERIODS; break; case 'N': nBit = WINDRV_OPT_CONVERT_HYPHEN; break; case 'H': nBit = WINDRV_OPT_CONVERT_HYPHENS; break; case 'X': nBit = WINDRV_OPT_CONVERT_BADCHAR; break; case 'S': nBit = WINDRV_OPT_CONVERT_SPACE; break; case 'e': nBit = WINDRV_OPT_REDUCED_PERIOD; break; case 'p': nBit = WINDRV_OPT_REDUCED_PERIODS; break; case 'n': nBit = WINDRV_OPT_REDUCED_HYPHEN; break; case 'h': nBit = WINDRV_OPT_REDUCED_HYPHENS; break; case 'x': nBit = WINDRV_OPT_REDUCED_BADCHAR; break; case 's': nBit = WINDRV_OPT_REDUCED_SPACE; break; default: break; } if (nMode) nOption |= nBit; else nOption &= ~nBit; } pp = p; } // Set options if (nOption != m_nOption) { SetOption(nOption); } } //--------------------------------------------------------------------------- // /// Get volume label // //--------------------------------------------------------------------------- bool CFileSys::FilesVolume(uint32_t nUnit, Human68k::files_t* pFiles) const { assert(pFiles); // Get volume label TCHAR szVolume[32]; if (bool bResult = m_cEntry.GetVolumeCache(nUnit, szVolume); !bResult) { // Carry out an extra media check here because it may be skipped when doing a manual eject if (!m_cEntry.isEnable(nUnit)) return false; // Media check if (m_cEntry.isMediaOffline(nUnit)) return false; // Get volume label m_cEntry.GetVolume(nUnit, szVolume); } if (szVolume[0] == '\0') return false; pFiles->attr = Human68k::AT_VOLUME; pFiles->time = 0; pFiles->date = 0; pFiles->size = 0; CHostFilename fname; fname.SetHost(szVolume); fname.ConvertHuman(); strcpy((char*)pFiles->full, (const char*)fname.GetHuman()); return true; }