mirror of
https://github.com/akuker/RASCSI.git
synced 2024-11-22 16:33:17 +00:00
52c2aa474f
* Rebrand project to PiSCSI - rascsi ->piscsi - rasctl -> scsictl - rasdump -> scsidump - ras* -> piscsi* (rasutil -> piscsi_util, etc.) * Refined the formatting and wording of the app startup banner * Kept some references to rascsi and rasctl where backwards compatibility is concerned * Point to the new github repo URL Co-authored-by: nucleogenic <nr@nucleogenic.com> Co-authored-by: Uwe Seimet <Uwe.Seimet@seimet.de>
3942 lines
102 KiB
C++
3942 lines
102 KiB
C++
//---------------------------------------------------------------------------
|
|
//
|
|
// 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 <dirent.h>
|
|
#include <iconv.h>
|
|
#include <utime.h>
|
|
|
|
#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;
|
|
}
|