From bc15f5a63c589b1d2b239216732b2242d9a1136e Mon Sep 17 00:00:00 2001 From: ejaquay Date: Thu, 1 Jan 2026 23:08:45 -0500 Subject: [PATCH 01/11] Clean up SDC 8.3 file conversions --- joystickinput.cpp | 24 --- sdc/sdc.cpp | 410 ++++++++++++++++++++++------------------------ 2 files changed, 197 insertions(+), 237 deletions(-) diff --git a/joystickinput.cpp b/joystickinput.cpp index 1994b7c2..a2c8633b 100644 --- a/joystickinput.cpp +++ b/joystickinput.cpp @@ -105,22 +105,14 @@ static unsigned char RightButton1Status = 0; static unsigned char LeftButton2Status = 0; static unsigned char RightButton2Status = 0; -// FIXME Direct input not working for ARM - disable joysticks for arm builds -#ifdef _M_ARM -unsigned int Joysticks[MAXSTICKS] = {nullptr}; -#else static LPDIRECTINPUTDEVICE8 Joysticks[MAXSTICKS]; -#endif char StickName[MAXSTICKS][STRLEN]; static unsigned char JoyStickIndex=0; -#ifdef _M_ARM -#else static LPDIRECTINPUT8 di; BOOL CALLBACK enumCallback(const DIDEVICEINSTANCE* , VOID* ); BOOL CALLBACK enumAxesCallback(const DIDEVICEOBJECTINSTANCE* , VOID* ); -#endif static unsigned char CurrentStick; @@ -131,9 +123,6 @@ inline int vccJoystickType(); // Locate connected joysticks. Called by config.c int EnumerateJoysticks() { -#ifdef _M_ARM - return(0); -#else HRESULT hr; JoyStickIndex=0; if (FAILED(hr = DirectInput8Create(GetModuleHandle(nullptr), @@ -145,11 +134,9 @@ int EnumerateJoysticks() return 0; return JoyStickIndex; -#endif } /*****************************************************************************/ -#ifndef _M_ARM BOOL CALLBACK enumCallback(const DIDEVICEINSTANCE* instance, VOID* /*context*/) { HRESULT hr; @@ -158,13 +145,11 @@ BOOL CALLBACK enumCallback(const DIDEVICEINSTANCE* instance, VOID* /*context*/) JoyStickIndex++; return(JoyStickIndex>7; RightButton2Status= Stick1.rgbButtons[1]>>7; } -#endif switch (pot) { case 0: diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index 76ef1697..272997b6 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -143,6 +143,7 @@ #include #include #include +#include #include #include #include @@ -180,7 +181,6 @@ void InitCardBox(); void InitFlashBoxes(); void FitEditTextPath(HWND, int, const char *); -void AppendPathChar(char *,char c); bool IsDirectory(const char *); char * LastErrorTxt(); void ConvertSlashes(char *); @@ -201,6 +201,7 @@ void GetDriveInfo(); void SDCControl(); void UpdateSD(); bool LoadFoundFile(struct FileRecord *); +std::string FixSDCPath(const std::string &); void FixSDCPath(char *,const char *); void MountDisk(int,const char *,int); void MountNewDisk(int,const char *,int); @@ -215,6 +216,7 @@ void LoadDirPage(); void SetCurDir(const char *); bool SearchFile(const char *); bool InitiateDir(const char *); +std::string GetFullPath(const std::string&); void GetFullPath(char *,const char *); void RenameFile(const char *); void KillFile(const char *); @@ -277,13 +279,13 @@ static char SeaDir[MAX_PATH] = {}; // Last directory searched // Packed file records for interface #pragma pack(1) struct FileRecord { - char name[8]; - char type[3]; - char attrib; - char hihi_size; - char lohi_size; - char hilo_size; - char lolo_size; + char FR_name[8]; + char FR_type[3]; + char FR_attrib; + char FR_hihi_size; + char FR_lohi_size; + char FR_hilo_size; + char FR_lolo_size; }; #pragma pack() static struct FileRecord DirPage[16]; @@ -323,7 +325,8 @@ static int ClockEnable; static HANDLE hFind = INVALID_HANDLE_VALUE; static WIN32_FIND_DATAA dFound; -// config control handles +// Window handles +static HWND gVccWindow = nullptr; static HWND hConfigureDlg = nullptr; static HWND hSDCardBox = nullptr; static HWND hStartupBank = nullptr; @@ -333,7 +336,8 @@ static int streaming; static unsigned char stream_cmdcode; static unsigned int stream_lsn; -static char Status[16] = {}; +// Status for VCC status line +static char SDC_Status[16] = {}; // Floppy I/O static char FlopDrive = 0; @@ -384,12 +388,14 @@ extern "C" return string_buffer; } + // PakInitialize gets called first, sets up dynamic menues and captures callbacks __declspec(dllexport) void PakInitialize( void* const callback_context, const char* const configuration_path, HWND hVccWnd, const cpak_callbacks* const callbacks) { + gVccWindow = hVccWnd; DLOG_C("SDC %p %p %p %p %p\n",*callbacks); gCallbackContext = callback_context; CartMenuCallback = callbacks->add_menu_item; @@ -457,13 +463,13 @@ extern "C" // Return SDC status. __declspec(dllexport) void PakGetStatus(char* text_buffer, size_t buffer_size) { - strncpy(text_buffer,Status,buffer_size); if (idle_ctr < 100) { idle_ctr++; } else { idle_ctr = 0; - snprintf(Status,16,"SDC:%d idle",CurrentBank); + snprintf(SDC_Status,16,"SDC:%d idle",CurrentBank); } + strncpy(text_buffer,SDC_Status,buffer_size); } // Return a byte from the current PAK ROM @@ -870,23 +876,23 @@ void LoadRom(unsigned char bank) //---------------------------------------------------------------------- void ParseStartup() { - char buf[MAX_PATH+10]; - if (!IsDirectory(SDCard)) { + namespace fs = std::filesystem; + fs::path sd = fs::path(SDCard); + + if (!fs::is_directory(sd)) { DLOG_C("ParseStartup SDCard path invalid\n"); return; } - strncpy(buf,SDCard,MAX_PATH); - AppendPathChar(buf,'/'); - strncat(buf,"startup.cfg",MAX_PATH); - - FILE *su = fopen(buf,"r"); + fs::path cfg = sd / "startup.cfg"; + FILE* su = std::fopen(cfg.string().c_str(), "r"); if (su == nullptr) { - DLOG_C("ParseStartup file not found,%s\n",buf); + DLOG_C("ParseStartup file not found, %s\n", cfg.string().c_str()); return; } // Strict single char followed by '=' then path + char buf[MAX_PATH]; while (fgets(buf,sizeof(buf),su) != nullptr) { //Chomp line ending buf[strcspn(buf,"\r\n")] = 0; @@ -1155,7 +1161,7 @@ void FloppyReadDisk() auto lsn = FloppyLSN(FlopDrive,FlopTrack,FlopSector); //DLOG_C("FloppyReadDisk %d %d\n",FlopDrive,lsn); - snprintf(Status,16,"SDC:%d Rd %d,%d",CurrentBank,FlopDrive,lsn); + snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,FlopDrive,lsn); if (SeekSector(FlopDrive,lsn)) { if (ReadFile(SDC_disk[FlopDrive].hFile,FlopRdBuf,256,&FlopRdCnt,nullptr)) { DLOG_C("FloppyReadDisk %d %d\n",FlopDrive,lsn); @@ -1605,7 +1611,7 @@ bool ReadDrive(unsigned char cmdcode, unsigned int lsn) return false; } - snprintf(Status,16,"SDC:%d Rd %d,%d",CurrentBank,drive,lsn); + snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,drive,lsn); LoadReply(buf,cnt); return true; } @@ -1674,7 +1680,7 @@ void WriteSector() DWORD cnt = 0; int drive = IF.cmdcode & 1; unsigned int lsn = (IF.param1 << 16) + (IF.param2 << 8) + IF.param3; - snprintf(Status,16,"SDC:%d Wr %d,%d",CurrentBank,drive,lsn); + snprintf(SDC_Status,16,"SDC:%d Wr %d,%d",CurrentBank,drive,lsn); if (SDC_disk[drive].hFile == nullptr) { IF.status = STA_FAIL; @@ -1775,47 +1781,37 @@ void LoadReply(const void *data, int count) } //---------------------------------------------------------------------- -// A file path may be in SDC format which does not use a dot to seperate +// A file path may use SDC format which does not use a dot to separate // the name from the extension. User is free to use standard dot format. // "FOODIR/FOO.DSK" = FOODIR/FOO.DSK // "FOODIR/FOO DSK" = FOODIR/FOO.DSK // "FOODIR/ALONGFOODSK" = FOODIR/ALONGFOO.DSK //---------------------------------------------------------------------- -void FixSDCPath(char *path, const char *fpath8) +std::string FixSDCPath(const std::string& sdcpath) { - const char *pname8 = strrchr(fpath8,'/'); - // Copy Directory portion - if (pname8 != nullptr) { - pname8++; - memcpy(path,fpath8,pname8-fpath8); - } else { - pname8 = fpath8; - } - path[pname8-fpath8]='\0'; // terminate directory portion - - // Copy Name portion - char c; - char name[16]; - int namlen=0; - while(c = *pname8++) { - if ((c == '.')||(c == ' ')) break; - name[namlen++] = c; - if (namlen > 7) break; - } - - // Copy extension if any thing is left - if (c) { - name[namlen++] = '.'; - int extlen=0; - while(c = *pname8++) { - if (c == '.' || c == ' ') continue; - name[namlen++] = c; - extlen++; - if (extlen > 2) break; - } + std::filesystem::path p(sdcpath); + + auto chop = [](std::string s) { + size_t space = s.find(' '); + if (space != std::string::npos) s.erase(space); + return s; + }; + + std::string fname = p.filename().string(); + if (fname.length() == 11 && fname.find('.') == std::string::npos) { + auto nam = chop(fname.substr(0,8)); + auto ext = chop(fname.substr(8,3)); + fname = ext.empty() ? nam : nam + "." + ext; } - name[namlen] = '\0'; // terminate name - strncat(path,name,MAX_PATH); // append it to directory + + std::filesystem::path out = p.parent_path() / fname; + return out.generic_string(); +} +void FixSDCPath(char* path, const char* sdcpath) +{ + std::string fixed = FixSDCPath(std::string(sdcpath)); + strncpy(path, fixed.c_str(), MAX_PATH); + path[MAX_PATH - 1] = '\0'; } //---------------------------------------------------------------------- @@ -1823,55 +1819,47 @@ void FixSDCPath(char *path, const char *fpath8) //---------------------------------------------------------------------- bool LoadFoundFile(struct FileRecord * rec) { + std::string fname = dFound.cFileName; + + // clear the file record memset(rec,0,sizeof(rec)); - memset(rec->name,' ',8); - memset(rec->type,' ',3); - - // Special case filename starts with a dot - if (dFound.cFileName[0] == '.' ) { - // Don't load if current directory is SD root, - // is only one dot, or if more than two chars - if ((*CurDir=='\0') | - (dFound.cFileName[1] != '.' ) | - (dFound.cFileName[2] != '\0')) - return false; - rec->name[0]='.'; - rec->name[1]='.'; - rec->attrib = ATTR_DIR; - return true; - } - // File type - const char * pdot = strrchr(dFound.cFileName,'.'); - if (pdot) { - const char * ptyp = pdot + 1; - for (int cnt = 0; cnt<3; cnt++) { - if (*ptyp == '\0') break; - rec->type[cnt] = *ptyp++; - } - } + // Ignore single dot or two dots if current dir is root + if (fname == ".") return false; + if (fname == ".." && *CurDir == '\0') return false; + + // Split found file name into name and extension + std::string nam, ext; - // File name - const char * pnam = dFound.cFileName; - for (int cnt = 0; cnt < 8; cnt++) { - if (*pnam == '\0') break; - if (pdot && (pnam == pdot)) break; - rec->name[cnt] = *pnam++; + // Two dots or no dot is a file without an extension + auto dot = fname.find_last_of('.'); + if (fname == ".." || dot == std::string::npos) { + nam = fname; + ext = ""; + } else { + nam = fname.substr(0, dot); + ext = fname.substr(dot + 1); } - // Attributes + // Fill or trim as required to fit 8.3 + nam.resize(8,' '); + ext.resize(3,' '); + std::memcpy(rec->FR_name, nam.data(), 8); + std::memcpy(rec->FR_type, ext.data(), 3); + if (dFound.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { - rec->attrib |= ATTR_RDONLY; + rec->FR_attrib |= ATTR_RDONLY; } + if (dFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - rec->attrib |= ATTR_DIR; + rec->FR_attrib |= ATTR_DIR; } - // Filesize, sssume < 4G (dFound.nFileSizeHigh == 0) - rec->lolo_size = (dFound.nFileSizeLow) & 0xFF; - rec->hilo_size = (dFound.nFileSizeLow >> 8) & 0xFF; - rec->lohi_size = (dFound.nFileSizeLow >> 16) & 0xFF; - rec->hihi_size = (dFound.nFileSizeLow >> 24) & 0xFF; + // Filesize max 4G. Byte order is reversed so copy byte by byte + rec->FR_lolo_size = (dFound.nFileSizeLow) & 0xFF; + rec->FR_hilo_size = (dFound.nFileSizeLow >> 8) & 0xFF; + rec->FR_lohi_size = (dFound.nFileSizeLow >> 16) & 0xFF; + rec->FR_hihi_size = (dFound.nFileSizeLow >> 24) & 0xFF; return true; } @@ -1971,6 +1959,7 @@ void MountDisk (int drive, const char * path, int raw) //---------------------------------------------------------------------- bool MountNext (int drive) { + //FIXME: should loop back to first file at end of set if (FindNextFile(hFind,&dFound) == 0) { DLOG_C("MountNext no more\n"); FindClose(hFind); @@ -2015,25 +2004,17 @@ void OpenNew( int drive, const char * path, int raw) return; } - // Contruct fully qualified file name - char fqn[MAX_PATH]={}; - strncpy(fqn,SDCard,MAX_PATH); - AppendPathChar(fqn,'/'); - strncat(fqn,CurDir,MAX_PATH); - AppendPathChar(fqn,'/'); - strncat(fqn,path,MAX_PATH); + namespace fs = std::filesystem; + fs::path fqn = fs::path(SDCard) / CurDir / path; - // Trying to mount a directory. Find .DSK files in the ending - // with a digit and mount the first one. - if (IsDirectory(fqn)) { - DLOG_C("OpenNew %s is a directory\n",fqn); + if (fs::is_directory(fqn)) { + DLOG_C("OpenNew %s is a directory\n",path); IF.status = STA_FAIL | STA_INVALID; return; } - // Try to create file CloseDrive(drive); - strncpy(SDC_disk[drive].fullpath,fqn,MAX_PATH); + strncpy(SDC_disk[drive].fullpath,fqn.string().c_str(),MAX_PATH); // Open file for write SDC_disk[drive].hFile = CreateFile( @@ -2095,33 +2076,43 @@ void OpenFound (int drive,int raw) CloseDrive(drive); *SDC_disk[drive].name = '\0'; - // Open a directory of containing DSK files - if (dFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - char path[MAX_PATH]; - strncpy(path,dFound.cFileName,MAX_PATH); - AppendPathChar(path,'/'); - strncat(path,"*.DSK",MAX_PATH); - // Open disk in directory if .DSK is found - if (InitiateDir(path)) { - DLOG_C("OpenFound %s in directory\n",dFound.cFileName); - OpenFound(drive,0); + namespace fs = std::filesystem; + + // If found item is a directory + if (dFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + // Build: "/*.DSK" + fs::path dir = dFound.cFileName; + fs::path pattern = dir / "*.DSK"; + + std::string pat = pattern.string(); + std::replace(pat.begin(), pat.end(), '\\', '/'); + + if (InitiateDir(pat.c_str())) { + DLOG_C("OpenFound %s in directory\n", dFound.cFileName); + OpenFound(drive, 0); if (drive == 0) BuildCartridgeMenu(); } return; } - // Fully qualify name of found file and try to open it - char fqn[MAX_PATH]={}; - strncpy(fqn,SeaDir,MAX_PATH); - strncat(fqn,dFound.cFileName,MAX_PATH); + // Found item is a file + fs::path fqn = fs::path(SeaDir) / dFound.cFileName; - // Open file for read - SDC_disk[drive].hFile = CreateFile( - fqn, GENERIC_READ, FILE_SHARE_READ, - nullptr,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,nullptr); + std::string file = fqn.string(); + + SDC_disk[drive].hFile = CreateFileA( + file.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr + ); if (SDC_disk[drive].hFile == INVALID_HANDLE_VALUE) { - DLOG_C("OpenFound fail %d file %s\n",drive,fqn); + DLOG_C("OpenFound fail %d file %s\n",drive,file.c_str()); DLOG_C("... %s\n",LastErrorTxt()); int ecode = GetLastError(); if (ecode == ERROR_SHARING_VIOLATION) { @@ -2132,7 +2123,7 @@ void OpenFound (int drive,int raw) return; } - strncpy(SDC_disk[drive].fullpath,fqn,MAX_PATH); + strncpy(SDC_disk[drive].fullpath,file.c_str(),MAX_PATH); strncpy(SDC_disk[drive].name,dFound.cFileName,MAX_PATH); // Default sectorsize and sectors per track @@ -2204,10 +2195,10 @@ void OpenFound (int drive,int raw) LoadFoundFile(&SDC_disk[drive].filerec); // Set readonly attrib per find status or file header - if ((SDC_disk[drive].filerec.attrib & ATTR_RDONLY) != 0) { + if ((SDC_disk[drive].filerec.FR_attrib & ATTR_RDONLY) != 0) { writeprotect = 1; } else if (writeprotect) { - SDC_disk[drive].filerec.attrib |= ATTR_RDONLY; + SDC_disk[drive].filerec.FR_attrib |= ATTR_RDONLY; } // If file is writeable reopen read/write @@ -2233,14 +2224,17 @@ void OpenFound (int drive,int raw) //---------------------------------------------------------------------- // Convert file name from SDC format and prepend current dir. //---------------------------------------------------------------------- -void GetFullPath(char * path, const char * file) { - char tmp[MAX_PATH]; - strncpy(path,SDCard,MAX_PATH); - AppendPathChar(path,'/'); - strncat(path,CurDir,MAX_PATH); - AppendPathChar(path,'/'); - FixSDCPath(tmp,file); - strncat(path,tmp,MAX_PATH); +std::string GetFullPath(const std::string& file) +{ + return (std::filesystem::path(SDCard) + / CurDir / FixSDCPath(file)).generic_string(); +} +void GetFullPath(char * path, const char * file) +{ + std::string full = GetFullPath(std::string(file)); + strncpy(path, full.c_str(), MAX_PATH); + path[MAX_PATH - 1] = '\0'; + DLOG_C("GetFullPath %s -> %s\n",file,path); } //---------------------------------------------------------------------- @@ -2340,103 +2334,102 @@ void CloseDrive (int drive) // Set the Current Directory relative to previous current or to SDRoot // This is complicated by the many ways a user can change the directory //---------------------------------------------------------------------- -void SetCurDir(const char * branch) + +void SetCurDir(const char* branch) { - DLOG_C("SetCurdir '%s'\n",branch); + namespace fs = std::filesystem; + + DLOG_C("SetCurdir '%s'\n", branch); - // If branch is "." or "" do nothing - if ((*branch == '\0') || (strcmp(branch,".") == 0)) { + fs::path cur = CurDir; // current relative directory + fs::path root = SDCard; // SD card root + + // "." or "" no change + if (*branch == '\0' || strcmp(branch, ".") == 0) { DLOG_C("SetCurdir no change\n"); IF.status = STA_NORMAL; return; } - // If branch is "/" set CurDir to root - if (strcmp(branch,"/") == 0) { + // "/" go to root + if (strcmp(branch, "/") == 0) { + CurDir[0] = '\0'; DLOG_C("SetCurdir to root\n"); - *CurDir = '\0'; IF.status = STA_NORMAL; return; } - // If branch is ".." go back a directory - if (strcmp(branch,"..") == 0) { - char *p = strrchr(CurDir,'/'); - if (p != nullptr) { - *p = '\0'; - } else { - *CurDir = '\0'; - } - DLOG_C("SetCurdir back %s\n",CurDir); + // ".." parent directory + if (strcmp(branch, "..") == 0) { + cur = cur.parent_path(); + std::string tmp = cur.string(); + std::snprintf(CurDir, MAX_PATH, "%s", tmp.c_str()); + DLOG_C("SetCurdir back %s\n", CurDir); IF.status = STA_NORMAL; return; } - // Disallow branch start with "//" - if (strncmp(branch,"//",2) == 0) { + // Reject "//" + if (strncmp(branch, "//", 2) == 0) { DLOG_C("SetCurdir // invalid\n"); IF.status = STA_FAIL | STA_INVALID; return; } - // Test for CurDir relative branch - int relative = (*branch != '/'); + // Build the candidate directory + bool relative = (branch[0] != '/'); - // Test the full directory path - char test[MAX_PATH]; - strncpy(test,SDCard,MAX_PATH); - AppendPathChar(test,'/'); + fs::path candidate; if (relative) { - strncat(test,CurDir,MAX_PATH); - AppendPathChar(test,'/'); - strncat(test,branch,MAX_PATH); + candidate = root / cur / branch; } else { - strncat(test,branch+1,MAX_PATH); + candidate = root / fs::path(branch).relative_path(); } - if (!IsDirectory(test)) { - DLOG_C("SetCurdir not a directory %s\n",test); + + // Validate directory + if (!fs::is_directory(candidate)) { + DLOG_C("SetCurdir not a directory %s\n", candidate.string().c_str()); IF.status = STA_FAIL | STA_NOTFOUND; return; } - // Set current directory + // Update CurDir + fs::path newCur; if (relative) { - if (*CurDir != '\0') AppendPathChar(CurDir,'/'); - strncat(CurDir,branch,MAX_PATH); + newCur = cur / branch; } else { - strncpy(CurDir,branch+1,MAX_PATH); + newCur = fs::path(branch).relative_path(); } - // Trim trailing '/' - int l = strlen(CurDir); - while (l > 0 && CurDir[l-1] == '/') l--; - CurDir[l] = '\0'; + // Normalize: remove trailing slash + newCur = newCur.lexically_normal(); - DLOG_C("SetCurdir set to '%s'\n",CurDir); + std::string tmp = newCur.string(); + std::replace(tmp.begin(), tmp.end(), '\\', '/'); + std::snprintf(CurDir, MAX_PATH, "%s", tmp.c_str()); + DLOG_C("SetCurdir set to '%s'\n", CurDir); IF.status = STA_NORMAL; - return; } //---------------------------------------------------------------------- // Start File search. Searches start from the root of the SDCard. //---------------------------------------------------------------------- -bool SearchFile(const char * pattern) + +bool SearchFile(const char* pattern) { - // Path always starts with SDCard - char path[MAX_PATH]; - strncpy(path,SDCard,MAX_PATH); - AppendPathChar(path,'/'); + namespace fs = std::filesystem; + + // Build the full search path + fs::path base = fs::path(SDCard); if (*pattern == '/') { - strncat(path,&pattern[1],MAX_PATH); + base /= fs::path(pattern).relative_path(); } else { - strncat(path,CurDir,MAX_PATH); - AppendPathChar(path,'/'); - strncat(path,pattern,MAX_PATH); + base /= fs::path(CurDir); + base /= pattern; } - - DLOG_C("SearchFile %s\n",path); + DLOG_C("SearchFile %s\n", base.string().c_str()); // Close previous search if (hFind != INVALID_HANDLE_VALUE) { @@ -2444,21 +2437,24 @@ bool SearchFile(const char * pattern) hFind = INVALID_HANDLE_VALUE; } - // Search - hFind = FindFirstFile(path, &dFound); - + hFind = FindFirstFileA(base.string().c_str(), &dFound); if (hFind == INVALID_HANDLE_VALUE) { - *SeaDir = '\0'; + SeaDir[0] = '\0'; return false; - } else { - // Save directory portion for prepending to results - char * pnam = strrchr(path,'/'); - if (pnam != nullptr) pnam[1] = '\0'; - strncpy(SeaDir,path,MAX_PATH); - return true; } - return (hFind != INVALID_HANDLE_VALUE); + // Extract directory portion for later use + fs::path dir = base.parent_path(); + + std::string sea = dir.string(); + std::replace(sea.begin(), sea.end(), '\\', '/'); + + // Ensure trailing slash + if (!sea.empty() && sea.back() != '/') sea.push_back('/'); + + std::snprintf(SeaDir, MAX_PATH, "%s", sea.c_str()); + + return true; } //---------------------------------------------------------------------- @@ -2511,18 +2507,6 @@ void LoadDirPage() } } -//---------------------------------------------------------------------- -// Append char to path if not there -//---------------------------------------------------------------------- -void AppendPathChar(char * path, char c) -{ - int l = strlen(path); - if ((l > 0) && (path[l-1] == c)) return; - if (l > (MAX_PATH-2)) return; - path[l] = c; - path[l+1] = '\0'; -} - //---------------------------------------------------------------------- // Determine if path is a direcory //---------------------------------------------------------------------- From ba0d6baee8fdf92b8b4b3663597b5fae6c1384bc Mon Sep 17 00:00:00 2001 From: ejaquay Date: Wed, 7 Jan 2026 22:34:05 -0500 Subject: [PATCH 02/11] SDC use std::string for host files --- sdc/sdc.cpp | 326 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 191 insertions(+), 135 deletions(-) diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index 272997b6..8445d6fe 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -1,3 +1,5 @@ +//#define USE_LOGGING +//====================================================================== // SDC simulator DLL // // By E J Jaquay 2025 @@ -19,7 +21,7 @@ // along with VCC (Virtual Color Computer). If not, see // . // -//---------------------------------------------------------------------- +//====================================================================== // // SDC Floppy port conflicts // ------------------------- @@ -136,9 +138,9 @@ // ROMS require their respective .DLL's to be installed to function, // these are typically in MMI slot 3 and 4 respectively. // -//---------------------------------------------------------------------- -//#define USE_LOGGING - +//====================================================================== +#include +#include #include #include #include @@ -162,7 +164,6 @@ #include #include "sdc.h" -//====================================================================== //====================================================================== static ::vcc::devices::rtc::cloud9 cloud9_rtc; @@ -180,16 +181,12 @@ void UpdateFlashItem(int); void InitCardBox(); void InitFlashBoxes(); void FitEditTextPath(HWND, int, const char *); - -bool IsDirectory(const char *); +inline bool IsDirectory(const std::string&); char * LastErrorTxt(); -void ConvertSlashes(char *); - void SDCInit(); void LoadRom(unsigned char); void SDCWrite(unsigned char,unsigned char); unsigned char SDCRead(unsigned char port); - void ParseStartup(); void SDCCommand(); void ReadSector(); @@ -201,7 +198,7 @@ void GetDriveInfo(); void SDCControl(); void UpdateSD(); bool LoadFoundFile(struct FileRecord *); -std::string FixSDCPath(const std::string &); +std::string FixSDCPath(const std::string&); void FixSDCPath(char *,const char *); void MountDisk(int,const char *,int); void MountNewDisk(int,const char *,int); @@ -270,11 +267,37 @@ static Interface IF = {}; // Cart ROM char PakRom[0x4000]; -// Host paths for SDC static char IniFile[MAX_PATH] = {}; // Vcc ini file name -static char SDCard[MAX_PATH] = {}; // SD card root directory -static char CurDir[256] = {}; // SDC current directory -static char SeaDir[MAX_PATH] = {}; // Last directory searched + +// Directory globals +static std::string gSDRoot {}; // SD card root directory +static std::string gCurDir {}; // Current directory relative to root +static std::string gSeaDir {}; // Last directory searched + +// A less than 4GB host file with a short name +// File location is gSeaDir +struct HostFile { + std::string name; + DWORD size; + bool isDirectory; + bool isReadonly; + // Constructor + HostFile(const char* cName, DWORD nSize, DWORD attr) : + name(cName), + size(nSize), + isDirectory((attr & FILE_ATTRIBUTE_DIRECTORY) != 0), + isReadonly((attr & FILE_ATTRIBUTE_READONLY) != 0) + {} +}; + +// Global list of host files TODO: Use these +static std::vector gFileList; +static size_t gLastFileLoaded {}; +static size_t gLastDirPageFile {}; + +// Clean up slashes on directory or path +void FixDirSlashes(std::string &dir); +void FixDirSlashes(char *dir); // Packed file records for interface #pragma pack(1) @@ -290,16 +313,25 @@ struct FileRecord { #pragma pack() static struct FileRecord DirPage[16]; +enum DiskType { + DTYPE_RAW = 0, + DTYPE_DSK, + DTYPE_JVC, + DTYPE_VDK, + DTYPE_SDF +}; + // Mounted image data struct SDC_disk_t { - HANDLE hFile; - unsigned int size; - unsigned int headersize; - DWORD sectorsize; - DWORD tracksectors; - char doublesided; - char name[MAX_PATH]; - char fullpath[MAX_PATH]; + HANDLE hFile; // stream handle + unsigned int size; // number of bytes total + unsigned int headersize; // number of bytes in header + DWORD sectorsize; // number of bytes per sector + DWORD tracksectors; // number of sectors per side + DiskType type; // Disk image type (RAW,DSK,JVC,VDK,SDF) + char doublesided; // false:1 side, true:2 sides + char name[MAX_PATH]; // name of file (8.3) + char fullpath[MAX_PATH]; // full file path struct FileRecord filerec; }; SDC_disk_t SDC_disk[2] = {}; @@ -555,7 +587,11 @@ SDC_Configure(HWND hDlg, UINT message, WPARAM wParam, LPARAM /*lParam*/) if (HIWORD(wParam) == EN_CHANGE) { char tmp[MAX_PATH]; GetWindowText(hSDCardBox,tmp,MAX_PATH); - if (*tmp != '\0') strncpy(SDCard,tmp,MAX_PATH); + if (*tmp != '\0') { + gCurDir = tmp; + FixDirSlashes(gCurDir); + //strncpy(SDCard,tmp,MAX_PATH); + } } return TRUE; case ID_UPDATE0: @@ -610,12 +646,21 @@ SDC_Configure(HWND hDlg, UINT message, WPARAM wParam, LPARAM /*lParam*/) //------------------------------------------------------------ void LoadConfig() { + char tmp[MAX_PATH]; + GetPrivateProfileString ("DefaultPaths", "MPIPath", "", MPIPath, MAX_PATH, IniFile); + GetPrivateProfileString - ("SDC", "SDCardPath", "", SDCard, MAX_PATH, IniFile); + ("SDC", "SDCardPath", "", tmp, MAX_PATH, IniFile); + + gSDRoot = tmp; + FixDirSlashes(gSDRoot); + + DLOG_C("LoadConfig MPIPath %s\n",MPIPath); + DLOG_C("LoadConfig gSDRoot %s\n",gSDRoot); - if (!IsDirectory(SDCard)) { + if (!IsDirectory(gSDRoot)) { MessageBox (nullptr,"Invalid SDCard Path in VCC init","Error",0); } @@ -635,11 +680,11 @@ void LoadConfig() //------------------------------------------------------------ bool SaveConfig(HWND hDlg) { - if (!IsDirectory(SDCard)) { + if (!IsDirectory(gSDRoot)) { MessageBox(nullptr,"Invalid SDCard Path\n","Error",0); return false; } - WritePrivateProfileString("SDC","SDCardPath",SDCard,IniFile); + WritePrivateProfileString("SDC","SDCardPath",gSDRoot.c_str(),IniFile); for (int i=0;i<8;i++) { char txt[32]; @@ -671,7 +716,7 @@ void FitEditTextPath(HWND hDlg, int ID, const char * path) { GetClientRect(h, &r); strncpy(p, path, MAX_PATH); PathCompactPath(c, p, r.right); - ConvertSlashes(p); + FixDirSlashes(p); SetWindowText(h, p); ReleaseDC(hDlg, c); } @@ -699,7 +744,7 @@ void InitFlashBoxes() void InitCardBox() { hSDCardBox = GetDlgItem(hConfigureDlg,ID_SD_BOX); - SendMessage(hSDCardBox, WM_SETTEXT, 0, (LPARAM)SDCard); + SendMessage(hSDCardBox, WM_SETTEXT, 0, (LPARAM) gSDRoot.c_str()); } //------------------------------------------------------------ @@ -738,26 +783,42 @@ void UpdateFlashItem(int index) //------------------------------------------------------------ // Dialog to select SD card path in user home directory //------------------------------------------------------------ + +// TODO: Replace Win32 browse dialog with the modern IFileDialog API (Vista+) void SelectCardBox() { - // Prompt user for path - BROWSEINFO bi = { nullptr }; + namespace fs = std::filesystem; + + // Prepare browse dialog + BROWSEINFO bi = {}; bi.hwndOwner = GetActiveWindow(); bi.lpszTitle = "Set the SD card path"; bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NONEWFOLDERBUTTON; - // Start from user home diretory - SHGetSpecialFolderLocation - (nullptr,CSIDL_PROFILE, const_cast(& bi.pidlRoot)); + // Set initial folder to user profile + LPITEMIDLIST pidlRoot = nullptr; + if (SUCCEEDED(SHGetSpecialFolderLocation(nullptr, CSIDL_PROFILE, &pidlRoot))) + bi.pidlRoot = pidlRoot; + // Show dialog LPITEMIDLIST pidl = SHBrowseForFolder(&bi); - if (pidl != nullptr) { - SHGetPathFromIDList(pidl,SDCard); + + // Free root PIDL if allocated + if (pidlRoot) + CoTaskMemFree(pidlRoot); + + if (pidl) + { + char tmp[MAX_PATH] = {}; + if (SHGetPathFromIDList(pidl, tmp)) + { + gSDRoot = tmp; + FixDirSlashes(gSDRoot); + SendMessage(hSDCardBox, WM_SETTEXT, 0, (LPARAM)gSDRoot.c_str()); + } + CoTaskMemFree(pidl); } - - ConvertSlashes(SDCard); - SendMessage(hSDCardBox, WM_SETTEXT, 0, (LPARAM)SDCard); } //===================================================================== @@ -877,7 +938,7 @@ void LoadRom(unsigned char bank) void ParseStartup() { namespace fs = std::filesystem; - fs::path sd = fs::path(SDCard); + fs::path sd = fs::path(gSDRoot); if (!fs::is_directory(sd)) { DLOG_C("ParseStartup SDCard path invalid\n"); @@ -1136,16 +1197,18 @@ void FloppyRestore() AssertIntCallback(gCallbackContext, INT_NMI, IS_NMI); } -// floppy seek +// floppy seek. No attempt is made to simulate seek times here. void FloppySeek() { DLOG_C("FloppySeek %d %d\n",FlopTrack,FlopData); FlopTrack = FlopData; } -// Convert floppy drive, track, sector, to LSN -//TODO: Side select -//TODO: Support floppy formats other than raw +// convert side (drive), track, sector, to LSN. The floppy controller +// uses CHS addressing while hard drives, and the SDC, use LBA addressing. +// FIXME: +// Nine sector tracks, (512 byte sectors) +// disk type: SDC_disk[drive].type unsigned int FloppyLSN( unsigned int drive, // 0 or 1 unsigned int track, // 0 to num tracks @@ -1375,47 +1438,31 @@ void GetDriveInfo() } //---------------------------------------------------------------------- -// Get directory leaf. This is the leaf name of the current directory, -// not it's full path. SDCEXP uses this with the set directory '..' -// command to learn the full path when restore last session is active. -// The full path is saved in SDCX.CFG for the next session. +// Reply with directory leaf. Reply is 32 byte directory record. The first +// 8 bytes are the name of the leaf, the next 3 are blanks, and the +// remainder is undefined ("Private" is SDC docs) +// SDCEXP uses this with the set directory '..' command to learn the full +// path when restore last session is active. The full path is saved in +// SDCX.CFG for the next session. //---------------------------------------------------------------------- void GetDirectoryLeaf() { - DLOG_C("GetDirectoryLeaf CurDir '%s'\n",CurDir); - - char leaf[32]; - memset(leaf,0,32); + namespace fs = std::filesystem; - // Strip trailing '/' from current directory. There should not - // be one there but the slash is so bothersome best to check. - int n = strlen(CurDir); - if (n > 0) { - n -= 1; - if (CurDir[n] == '/') CurDir[n] = '\0'; - } + char leaf[32] = {}; - // If at least one leaf find the last one - if (n > 0) { - const char *p = strrchr(CurDir,'/'); - if (p == nullptr) { - p = CurDir; - } else { - p += 1; - } - // Build reply - memset(leaf,32,12); - strncpy(leaf,p,12); - n = strlen(p); - // SDC filenames are fixed 8 chars so blank any terminator - if (n < 12) leaf[n] = ' '; - // If current directory is SDCard root reply with zeros and status + if (!gCurDir.empty()) { + std::string leafName = fs::path(gCurDir).filename().string(); + memset(leaf, ' ', 11); + size_t copyLen = std::min(8,leafName.size()); + memcpy(leaf, leafName.data(), copyLen); } else { IF.reply_status = STA_FAIL | STA_NOTFOUND; } + DLOG_C("GetDirectoryLeaf CurDir '%s' leaf '%s' \n", gCurDir.c_str(),leaf); - IF.reply_mode=0; - LoadReply(leaf,32); + IF.reply_mode = 0; + LoadReply(leaf, sizeof(leaf)); } //---------------------------------------------------------------------- @@ -1805,8 +1852,11 @@ std::string FixSDCPath(const std::string& sdcpath) } std::filesystem::path out = p.parent_path() / fname; + + DLOG_C("FixSDCPath in %s out %s\n",sdcpath.c_str(),out.generic_string().c_str()); return out.generic_string(); } + void FixSDCPath(char* path, const char* sdcpath) { std::string fixed = FixSDCPath(std::string(sdcpath)); @@ -1826,7 +1876,7 @@ bool LoadFoundFile(struct FileRecord * rec) // Ignore single dot or two dots if current dir is root if (fname == ".") return false; - if (fname == ".." && *CurDir == '\0') return false; + if (fname == ".." && gCurDir == "") return false; // Split found file name into name and extension std::string nam, ext; @@ -1921,15 +1971,15 @@ void MountDisk (int drive, const char * path, int raw) return; } - char file[MAX_PATH]; - char tmp[MAX_PATH]; - // Convert from SDC format + char file[MAX_PATH]; FixSDCPath(file,path); // Look for the file bool found = SearchFile(file); + char tmp[MAX_PATH]; + // if no '.' in the name try appending .DSK or wildcard if (!found && (strchr(file,'.') == nullptr)) { strncpy(tmp,file,MAX_PATH); @@ -2005,7 +2055,7 @@ void OpenNew( int drive, const char * path, int raw) } namespace fs = std::filesystem; - fs::path fqn = fs::path(SDCard) / CurDir / path; + fs::path fqn = fs::path(gSDRoot) / gCurDir / path; if (fs::is_directory(fqn)) { DLOG_C("OpenNew %s is a directory\n",path); @@ -2086,7 +2136,7 @@ void OpenFound (int drive,int raw) fs::path pattern = dir / "*.DSK"; std::string pat = pattern.string(); - std::replace(pat.begin(), pat.end(), '\\', '/'); + FixDirSlashes(pat); if (InitiateDir(pat.c_str())) { DLOG_C("OpenFound %s in directory\n", dFound.cFileName); @@ -2097,7 +2147,7 @@ void OpenFound (int drive,int raw) } // Found item is a file - fs::path fqn = fs::path(SeaDir) / dFound.cFileName; + fs::path fqn = fs::path(gSeaDir) / dFound.cFileName; std::string file = fqn.string(); @@ -2133,8 +2183,10 @@ void OpenFound (int drive,int raw) // Grab filesize from found record SDC_disk[drive].size = dFound.nFileSizeLow; + // Determine file type (RAW,DSK,JVC,VDK,SDF) if (raw) { writeprotect = 0; + SDC_disk[drive].type = DTYPE_RAW; SDC_disk[drive].headersize = 0; SDC_disk[drive].doublesided = 0; } else { @@ -2151,8 +2203,9 @@ void OpenFound (int drive,int raw) // track record is 6250 bytes. Assume at least 35 tracks so // minimum size of a SDF file is 219262 bytes. The first four // bytes of the header contains "SDF1" - if ((SDC_disk[drive].size >= 219262) && + if ((SDC_disk[drive].size >= 219262) && // is this reasonable? (strncmp("SDF1",(const char *) header,4) == 0)) { + SDC_disk[drive].type = DTYPE_SDF; DLOG_C("OpenFound SDF file unsupported\n"); IF.status = STA_FAIL | STA_INVALID; return; @@ -2168,6 +2221,7 @@ void OpenFound (int drive,int raw) case 2: // Number of sides = header[1] 1 or 2 case 1: // Sectors per trk = header[0] 18 assumed SDC_disk[drive].doublesided = (header[1] == 2); + SDC_disk[drive].type = DTYPE_JVC; break; // No apparant header // JVC or OS9 disk if no header, side count per file size @@ -2175,11 +2229,13 @@ void OpenFound (int drive,int raw) numsec = SDC_disk[drive].size >> 8; DLOG_C("OpenFound JVC/OS9 sectors %d\n",numsec); SDC_disk[drive].doublesided = ((numsec > 720) && (numsec <= 2880)); + SDC_disk[drive].type = DTYPE_JVC; break; // VDK case 12: SDC_disk[drive].doublesided = (header[8] == 2); writeprotect = header[9] & 1; + SDC_disk[drive].type = DTYPE_VDK; break; // Unknown or unsupported default: // More than 4 byte header is not supported @@ -2226,15 +2282,15 @@ void OpenFound (int drive,int raw) //---------------------------------------------------------------------- std::string GetFullPath(const std::string& file) { - return (std::filesystem::path(SDCard) - / CurDir / FixSDCPath(file)).generic_string(); + std::string out = gSDRoot + '/' + gCurDir + '/' + FixSDCPath(file); + DLOG_C("GetFullPath in %s out %s\n",file,out); + return (out); } + void GetFullPath(char * path, const char * file) { std::string full = GetFullPath(std::string(file)); - strncpy(path, full.c_str(), MAX_PATH); - path[MAX_PATH - 1] = '\0'; - DLOG_C("GetFullPath %s -> %s\n",file,path); + std::snprintf(path, MAX_PATH, "%s", full.c_str()); } //---------------------------------------------------------------------- @@ -2341,8 +2397,8 @@ void SetCurDir(const char* branch) DLOG_C("SetCurdir '%s'\n", branch); - fs::path cur = CurDir; // current relative directory - fs::path root = SDCard; // SD card root + fs::path cur = gCurDir; // current relative directory + fs::path root = gSDRoot; // SD card root // "." or "" no change if (*branch == '\0' || strcmp(branch, ".") == 0) { @@ -2353,7 +2409,7 @@ void SetCurDir(const char* branch) // "/" go to root if (strcmp(branch, "/") == 0) { - CurDir[0] = '\0'; + gCurDir = ""; DLOG_C("SetCurdir to root\n"); IF.status = STA_NORMAL; return; @@ -2362,9 +2418,9 @@ void SetCurDir(const char* branch) // ".." parent directory if (strcmp(branch, "..") == 0) { cur = cur.parent_path(); - std::string tmp = cur.string(); - std::snprintf(CurDir, MAX_PATH, "%s", tmp.c_str()); - DLOG_C("SetCurdir back %s\n", CurDir); + gCurDir = cur.string(); + FixDirSlashes(gCurDir); + DLOG_C("SetCurdir back %s\n", gCurDir.c_str()); IF.status = STA_NORMAL; return; } @@ -2401,14 +2457,11 @@ void SetCurDir(const char* branch) newCur = fs::path(branch).relative_path(); } - // Normalize: remove trailing slash - newCur = newCur.lexically_normal(); - - std::string tmp = newCur.string(); - std::replace(tmp.begin(), tmp.end(), '\\', '/'); - std::snprintf(CurDir, MAX_PATH, "%s", tmp.c_str()); + // String based host files + gCurDir = newCur.string(); + FixDirSlashes(gCurDir); - DLOG_C("SetCurdir set to '%s'\n", CurDir); + DLOG_C("SetCurdir set to '%s'\n", gCurDir.c_str()); IF.status = STA_NORMAL; } @@ -2418,41 +2471,39 @@ void SetCurDir(const char* branch) bool SearchFile(const char* pattern) { + // Close previous search + if (hFind != INVALID_HANDLE_VALUE) { + FindClose(hFind); + hFind = INVALID_HANDLE_VALUE; + } + namespace fs = std::filesystem; // Build the full search path - fs::path base = fs::path(SDCard); + fs::path base = fs::path(gSDRoot); if (*pattern == '/') { base /= fs::path(pattern).relative_path(); } else { - base /= fs::path(CurDir); + base /= fs::path(gCurDir); base /= pattern; } - DLOG_C("SearchFile %s\n", base.string().c_str()); - // Close previous search - if (hFind != INVALID_HANDLE_VALUE) { - FindClose(hFind); - hFind = INVALID_HANDLE_VALUE; - } - - hFind = FindFirstFileA(base.string().c_str(), &dFound); - if (hFind == INVALID_HANDLE_VALUE) { - SeaDir[0] = '\0'; - return false; - } + std::string search = base.string(); + FixDirSlashes(search); // Extract directory portion for later use - fs::path dir = base.parent_path(); - - std::string sea = dir.string(); - std::replace(sea.begin(), sea.end(), '\\', '/'); + gSeaDir = base.parent_path().string(); + FixDirSlashes(gSeaDir); - // Ensure trailing slash - if (!sea.empty() && sea.back() != '/') sea.push_back('/'); + DLOG_C("SearchFile %s\n", search.c_str()); - std::snprintf(SeaDir, MAX_PATH, "%s", sea.c_str()); + hFind = FindFirstFileA(search.c_str(), &dFound); + if (hFind == INVALID_HANDLE_VALUE) { + DLOG_C("SearchFile fail\n"); + gSeaDir = ""; + return false; + } return true; } @@ -2510,12 +2561,10 @@ void LoadDirPage() //---------------------------------------------------------------------- // Determine if path is a direcory //---------------------------------------------------------------------- -bool IsDirectory(const char * path) +inline bool IsDirectory(const std::string& path) { - struct _stat file_stat; - int result = _stat(path,&file_stat); - if (result != 0) return false; - return ((file_stat.st_mode & _S_IFDIR) != 0); + std::error_code ec; + return std::filesystem::is_directory(path, ec) && !ec; } //---------------------------------------------------------------------- @@ -2533,12 +2582,19 @@ char * LastErrorTxt() { } //------------------------------------------------------------ -// Convert path slashes +// Convert path slashes. SDC expects forward slashes as path +// delimiter but Windows uses backslashes. This is the fix. //------------------------------------------------------------ -void ConvertSlashes(char * path) +void FixDirSlashes(std::string &dir) { - for(size_t i=0; i < strlen(path); i++) { - if (path[i] == '\\') path[i] = '/'; - } + if (dir.empty()) return; + std::replace(dir.begin(), dir.end(), '\\', '/'); + if (dir.back() == '/') dir.pop_back(); +} +void FixDirSlashes(char *dir) +{ + if (!dir) return; + std::string tmp(dir); + FixDirSlashes(tmp); + strcpy(dir, tmp.c_str()); } - From e7a5339f47cbe39c14f8ea1c2c012da056e85854 Mon Sep 17 00:00:00 2001 From: ejaquay Date: Sat, 10 Jan 2026 15:02:36 -0500 Subject: [PATCH 03/11] SDC message box if startup rom can not be loaded --- sdc/sdc.cpp | 247 ++++++++++++++++++++++++++++------------------------ 1 file changed, 132 insertions(+), 115 deletions(-) diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index 8445d6fe..764edab3 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -322,7 +322,7 @@ enum DiskType { }; // Mounted image data -struct SDC_disk_t { +struct CocoDisk_t { HANDLE hFile; // stream handle unsigned int size; // number of bytes total unsigned int headersize; // number of bytes in header @@ -333,8 +333,20 @@ struct SDC_disk_t { char name[MAX_PATH]; // name of file (8.3) char fullpath[MAX_PATH]; // full file path struct FileRecord filerec; + // Constructor + CocoDisk_t() noexcept + : hFile(INVALID_HANDLE_VALUE), + size(0), + headersize(0), + sectorsize(0), + tracksectors(0), + type(DiskType::DTYPE_RAW), + doublesided(0) { + name[0] = '\0'; + fullpath[0] = '\0'; + }; }; -SDC_disk_t SDC_disk[2] = {}; +CocoDisk_t gCocoDisk[2] {}; // Flash banks static char FlashFile[8][MAX_PATH]; @@ -399,6 +411,24 @@ static PakAppendCartridgeMenuHostCallback CartMenuCallback = nullptr; //====================================================================== extern "C" { + // PakInitialize gets called first, sets up dynamic menues and captures callbacks + __declspec(dllexport) void PakInitialize( + void* const callback_context, + const char* const configuration_path, + HWND hVccWnd, + const cpak_callbacks* const callbacks) + { + gVccWindow = hVccWnd; + DLOG_C("SDC %p %p %p %p %p\n",*callbacks); + gCallbackContext = callback_context; + CartMenuCallback = callbacks->add_menu_item; + AssertIntCallback = callbacks->assert_interrupt; + strcpy(IniFile, configuration_path); + + LoadConfig(); + BuildCartridgeMenu(); + } + __declspec(dllexport) const char* PakGetName() { static char string_buffer[MAX_LOADSTRING]; @@ -420,24 +450,6 @@ extern "C" return string_buffer; } - // PakInitialize gets called first, sets up dynamic menues and captures callbacks - __declspec(dllexport) void PakInitialize( - void* const callback_context, - const char* const configuration_path, - HWND hVccWnd, - const cpak_callbacks* const callbacks) - { - gVccWindow = hVccWnd; - DLOG_C("SDC %p %p %p %p %p\n",*callbacks); - gCallbackContext = callback_context; - CartMenuCallback = callbacks->add_menu_item; - AssertIntCallback = callbacks->assert_interrupt; - strcpy(IniFile, configuration_path); - - LoadConfig(); - BuildCartridgeMenu(); - } - // Clean up must also be done on DLL_UNLOAD incase VCC is closed! __declspec(dllexport) void PakTerminate() { @@ -543,10 +555,10 @@ void BuildCartridgeMenu() CartMenuCallback(gCallbackContext, "", MID_ENTRY, MIT_Seperator); CartMenuCallback(gCallbackContext, "SDC Drive 0",MID_ENTRY,MIT_Head); char tmp[64]={}; - if (strcmp(SDC_disk[0].name,"") == 0) { + if (strcmp(gCocoDisk[0].name,"") == 0) { strcpy(tmp,"empty"); } else { - strcpy(tmp,SDC_disk[0].name); + strcpy(tmp,gCocoDisk[0].name); strcat(tmp," (load next)"); } CartMenuCallback(gCallbackContext, tmp, ControlId(11),MIT_Slave); @@ -661,7 +673,7 @@ void LoadConfig() DLOG_C("LoadConfig gSDRoot %s\n",gSDRoot); if (!IsDirectory(gSDRoot)) { - MessageBox (nullptr,"Invalid SDCard Path in VCC init","Error",0); + MessageBox (gVccWindow,"Invalid SDCard Path in VCC init","Error",0); } for (int i=0;i<8;i++) { @@ -681,7 +693,7 @@ void LoadConfig() bool SaveConfig(HWND hDlg) { if (!IsDirectory(gSDRoot)) { - MessageBox(nullptr,"Invalid SDCard Path\n","Error",0); + MessageBox(gVccWindow,"Invalid SDCard Path\n","Error",0); return false; } WritePrivateProfileString("SDC","SDCardPath",gSDRoot.c_str(),IniFile); @@ -849,8 +861,8 @@ void SDCInit() SetCurDir(""); // May be changed by ParseStartup() - SDC_disk[0] = {}; - SDC_disk[1] = {}; + gCocoDisk[0] = {}; + gCocoDisk[1] = {}; // Process the startup config file ParseStartup(); @@ -866,12 +878,14 @@ void SDCInit() //------------------------------------------------------------- void LoadRom(unsigned char bank) { - unsigned char ch; int ctr = 0; char *p_rom; char *RomFile; + DLOG_C("LoadRom load flash bank %d\n",bank); + + // Skip if bank is already active if (bank == CurrentBank) return; // Make sure flash file is closed @@ -880,12 +894,13 @@ void LoadRom(unsigned char bank) h_RomFile = nullptr; } + // If bank contents have been changed write to the flash file if (BankDirty) { RomFile = FlashFile[CurrentBank]; DLOG_C("LoadRom switching out dirty bank %d %s\n",CurrentBank,RomFile); h_RomFile = fopen(RomFile,"wb"); - if (h_RomFile == nullptr) { - DLOG_C("LoadRom failed to open bank file%d\n",bank); + if (!h_RomFile) { + MessageBox (gVccWindow,"Can not write Rom file","SDC Rom Save Failed",0); } else { ctr = 0; p_rom = PakRom; @@ -896,7 +911,6 @@ void LoadRom(unsigned char bank) BankDirty = 0; } - DLOG_C("LoadRom load flash bank %d\n",bank); RomFile = FlashFile[bank]; CurrentBank = bank; @@ -909,13 +923,16 @@ void LoadRom(unsigned char bank) } } - // Open romfile for read or write if not startup bank - h_RomFile = fopen(RomFile,"rb"); - if (h_RomFile == nullptr) { - if (CurrentBank != StartupBank) h_RomFile = fopen(RomFile,"wb"); - } + // Open romfile for write if not startup bank + if (CurrentBank != StartupBank) + h_RomFile = fopen(RomFile,"wb"); + + if (CurrentBank == StartupBank || h_RomFile == nullptr) + h_RomFile = fopen(RomFile,"rb"); + if (h_RomFile == nullptr) { - DLOG_C("LoadRom '%s' failed %s \n",RomFile,LastErrorTxt()); + std::string msg="Check Rom Path and SDC Config\n"+std::string(RomFile); + MessageBox (gVccWindow,msg.c_str(),"SDC Startup Rom Load Failed",0); return; } @@ -1208,7 +1225,7 @@ void FloppySeek() // uses CHS addressing while hard drives, and the SDC, use LBA addressing. // FIXME: // Nine sector tracks, (512 byte sectors) -// disk type: SDC_disk[drive].type +// disk type: gCocoDisk[drive].type unsigned int FloppyLSN( unsigned int drive, // 0 or 1 unsigned int track, // 0 to num tracks @@ -1226,7 +1243,7 @@ void FloppyReadDisk() snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,FlopDrive,lsn); if (SeekSector(FlopDrive,lsn)) { - if (ReadFile(SDC_disk[FlopDrive].hFile,FlopRdBuf,256,&FlopRdCnt,nullptr)) { + if (ReadFile(gCocoDisk[FlopDrive].hFile,FlopRdBuf,256,&FlopRdCnt,nullptr)) { DLOG_C("FloppyReadDisk %d %d\n",FlopDrive,lsn); FlopStatus = FLP_DATAREQ; } else { @@ -1603,15 +1620,15 @@ bool SeekSector(unsigned char cmdcode, unsigned int lsn) int drive = cmdcode & 1; // Drive number 0 or 1 int sside = cmdcode & 2; // Single sided LSN flag - int trk = lsn / SDC_disk[drive].tracksectors; - int sec = lsn % SDC_disk[drive].tracksectors; + int trk = lsn / gCocoDisk[drive].tracksectors; + int sec = lsn % gCocoDisk[drive].tracksectors; // The single sided LSN flag tells the controller that the lsn // assumes the disk image is a single-sided floppy disk. If the // disk is actually double-sided the LSN must be adjusted. - if (sside && SDC_disk[drive].doublesided) { + if (sside && gCocoDisk[drive].doublesided) { DLOG_C("SeekSector sside %d %d\n",drive,lsn); - lsn = 2 * SDC_disk[drive].tracksectors * trk + sec; + lsn = 2 * gCocoDisk[drive].tracksectors * trk + sec; } // Allow seek to expand a writable file to a resonable limit @@ -1622,9 +1639,9 @@ bool SeekSector(unsigned char cmdcode, unsigned int lsn) // Seek to logical sector on drive. LARGE_INTEGER pos{0}; - pos.QuadPart = lsn * SDC_disk[drive].sectorsize + SDC_disk[drive].headersize; + pos.QuadPart = lsn * gCocoDisk[drive].sectorsize + gCocoDisk[drive].headersize; - if (!SetFilePointerEx(SDC_disk[drive].hFile,pos,nullptr,FILE_BEGIN)) { + if (!SetFilePointerEx(gCocoDisk[drive].hFile,pos,nullptr,FILE_BEGIN)) { DLOG_C("SeekSector error %s\n",LastErrorTxt()); return false; } @@ -1639,7 +1656,7 @@ bool ReadDrive(unsigned char cmdcode, unsigned int lsn) char buf[520]; DWORD cnt = 0; int drive = cmdcode & 1; - if (SDC_disk[drive].hFile == nullptr) { + if (gCocoDisk[drive].hFile == nullptr) { DLOG_C("ReadDrive %d not open\n"); return false; } @@ -1648,12 +1665,12 @@ bool ReadDrive(unsigned char cmdcode, unsigned int lsn) return false; } - if (!ReadFile(SDC_disk[drive].hFile,buf,SDC_disk[drive].sectorsize,&cnt,nullptr)) { + if (!ReadFile(gCocoDisk[drive].hFile,buf,gCocoDisk[drive].sectorsize,&cnt,nullptr)) { DLOG_C("ReadDrive %d %s\n",drive,LastErrorTxt()); return false; } - if (cnt != SDC_disk[drive].sectorsize) { + if (cnt != gCocoDisk[drive].sectorsize) { DLOG_C("ReadDrive %d short read\n",drive); return false; } @@ -1701,10 +1718,10 @@ void StreamImage() // For now can only stream 512 byte sectors int drive = stream_cmdcode & 1; - SDC_disk[drive].sectorsize = 512; - SDC_disk[drive].tracksectors = 9; + gCocoDisk[drive].sectorsize = 512; + gCocoDisk[drive].tracksectors = 9; - if (stream_lsn > (SDC_disk[drive].size/SDC_disk[drive].sectorsize)) { + if (stream_lsn > (gCocoDisk[drive].size/gCocoDisk[drive].sectorsize)) { DLOG_C("StreamImage done\n"); streaming = 0; return; @@ -1729,7 +1746,7 @@ void WriteSector() unsigned int lsn = (IF.param1 << 16) + (IF.param2 << 8) + IF.param3; snprintf(SDC_Status,16,"SDC:%d Wr %d,%d",CurrentBank,drive,lsn); - if (SDC_disk[drive].hFile == nullptr) { + if (gCocoDisk[drive].hFile == nullptr) { IF.status = STA_FAIL; return; } @@ -1738,13 +1755,13 @@ void WriteSector() IF.status = STA_FAIL; return; } - if (!WriteFile(SDC_disk[drive].hFile,IF.blkbuf, - SDC_disk[drive].sectorsize,&cnt,nullptr)) { + if (!WriteFile(gCocoDisk[drive].hFile,IF.blkbuf, + gCocoDisk[drive].sectorsize,&cnt,nullptr)) { DLOG_C("WriteSector %d %s\n",drive,LastErrorTxt()); IF.status = STA_FAIL; return; } - if (cnt != SDC_disk[drive].sectorsize) { + if (cnt != gCocoDisk[drive].sectorsize) { DLOG_C("WriteSector %d short write\n",drive); IF.status = STA_FAIL; return; @@ -1759,7 +1776,7 @@ void WriteSector() void GetSectorCount() { int drive = IF.cmdcode & 1; - unsigned int numsec = SDC_disk[drive].size/SDC_disk[drive].sectorsize; + unsigned int numsec = gCocoDisk[drive].size/gCocoDisk[drive].sectorsize; IF.reply3 = numsec & 0xFF; numsec = numsec >> 8; IF.reply2 = numsec & 0xFF; @@ -1774,12 +1791,12 @@ void GetSectorCount() { void GetMountedImageRec() { int drive = IF.cmdcode & 1; - //DLOG_C("GetMountedImageRec %d %s\n",drive,SDC_disk[drive].fullpath); - if (strlen(SDC_disk[drive].fullpath) == 0) { + //DLOG_C("GetMountedImageRec %d %s\n",drive,gCocoDisk[drive].fullpath); + if (strlen(gCocoDisk[drive].fullpath) == 0) { IF.status = STA_FAIL; } else { IF.reply_mode = 0; - LoadReply(&SDC_disk[drive].filerec,sizeof(FileRecord)); + LoadReply(&gCocoDisk[drive].filerec,sizeof(FileRecord)); } } @@ -1929,7 +1946,7 @@ void MountNewDisk (int drive, const char * path, int raw) // Close and clear previous entry CloseDrive(drive); - SDC_disk[drive] = {}; + gCocoDisk[drive] = {}; // Convert from SDC format char file[MAX_PATH]; @@ -1961,7 +1978,7 @@ void MountDisk (int drive, const char * path, int raw) // Close and clear previous entry CloseDrive(drive); - SDC_disk[drive] = {}; + gCocoDisk[drive] = {}; // Check for UNLOAD. Path will be an empty string. if (*path == '\0') { @@ -2064,45 +2081,45 @@ void OpenNew( int drive, const char * path, int raw) } CloseDrive(drive); - strncpy(SDC_disk[drive].fullpath,fqn.string().c_str(),MAX_PATH); + strncpy(gCocoDisk[drive].fullpath,fqn.string().c_str(),MAX_PATH); // Open file for write - SDC_disk[drive].hFile = CreateFile( - SDC_disk[drive].fullpath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, + gCocoDisk[drive].hFile = CreateFile( + gCocoDisk[drive].fullpath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, nullptr,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,nullptr); - if (SDC_disk[drive].hFile == INVALID_HANDLE_VALUE) { - DLOG_C("OpenNew fail %d file %s\n",drive,SDC_disk[drive].fullpath); + if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { + DLOG_C("OpenNew fail %d file %s\n",drive,gCocoDisk[drive].fullpath); DLOG_C("... %s\n",LastErrorTxt()); IF.status = STA_FAIL | STA_WIN_ERROR; return; } // Sectorsize and sectors per track - SDC_disk[drive].sectorsize = 256; - SDC_disk[drive].tracksectors = 18; + gCocoDisk[drive].sectorsize = 256; + gCocoDisk[drive].tracksectors = 18; IF.status = STA_FAIL; if (raw) { // New raw file is empty - can be any format IF.status = STA_NORMAL; - SDC_disk[drive].doublesided = 0; - SDC_disk[drive].headersize = 0; - SDC_disk[drive].size = 0; + gCocoDisk[drive].doublesided = 0; + gCocoDisk[drive].headersize = 0; + gCocoDisk[drive].size = 0; } else { // TODO: handle SDF // Create headerless 35 track JVC file - SDC_disk[drive].doublesided = 0; - SDC_disk[drive].headersize = 0; - SDC_disk[drive].size = 35 - * SDC_disk[drive].sectorsize - * SDC_disk[drive].tracksectors - + SDC_disk[drive].headersize; + gCocoDisk[drive].doublesided = 0; + gCocoDisk[drive].headersize = 0; + gCocoDisk[drive].size = 35 + * gCocoDisk[drive].sectorsize + * gCocoDisk[drive].tracksectors + + gCocoDisk[drive].headersize; // Extend file to size LARGE_INTEGER l_siz; - l_siz.QuadPart = SDC_disk[drive].size; - if (SetFilePointerEx(SDC_disk[drive].hFile,l_siz,nullptr,FILE_BEGIN)) { - if (SetEndOfFile(SDC_disk[drive].hFile)) { + l_siz.QuadPart = gCocoDisk[drive].size; + if (SetFilePointerEx(gCocoDisk[drive].hFile,l_siz,nullptr,FILE_BEGIN)) { + if (SetEndOfFile(gCocoDisk[drive].hFile)) { IF.status = STA_NORMAL; } else { IF.status = STA_FAIL | STA_WIN_ERROR; @@ -2121,10 +2138,10 @@ void OpenFound (int drive,int raw) int writeprotect = 0; DLOG_C("OpenFound drive %d %s hfile %d\n", - drive, dFound.cFileName, SDC_disk[drive].hFile); + drive, dFound.cFileName, gCocoDisk[drive].hFile); CloseDrive(drive); - *SDC_disk[drive].name = '\0'; + *gCocoDisk[drive].name = '\0'; namespace fs = std::filesystem; @@ -2151,7 +2168,7 @@ void OpenFound (int drive,int raw) std::string file = fqn.string(); - SDC_disk[drive].hFile = CreateFileA( + gCocoDisk[drive].hFile = CreateFileA( file.c_str(), GENERIC_READ, FILE_SHARE_READ, @@ -2161,7 +2178,7 @@ void OpenFound (int drive,int raw) nullptr ); - if (SDC_disk[drive].hFile == INVALID_HANDLE_VALUE) { + if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { DLOG_C("OpenFound fail %d file %s\n",drive,file.c_str()); DLOG_C("... %s\n",LastErrorTxt()); int ecode = GetLastError(); @@ -2173,27 +2190,27 @@ void OpenFound (int drive,int raw) return; } - strncpy(SDC_disk[drive].fullpath,file.c_str(),MAX_PATH); - strncpy(SDC_disk[drive].name,dFound.cFileName,MAX_PATH); + strncpy(gCocoDisk[drive].fullpath,file.c_str(),MAX_PATH); + strncpy(gCocoDisk[drive].name,dFound.cFileName,MAX_PATH); // Default sectorsize and sectors per track - SDC_disk[drive].sectorsize = 256; - SDC_disk[drive].tracksectors = 18; + gCocoDisk[drive].sectorsize = 256; + gCocoDisk[drive].tracksectors = 18; // Grab filesize from found record - SDC_disk[drive].size = dFound.nFileSizeLow; + gCocoDisk[drive].size = dFound.nFileSizeLow; // Determine file type (RAW,DSK,JVC,VDK,SDF) if (raw) { writeprotect = 0; - SDC_disk[drive].type = DTYPE_RAW; - SDC_disk[drive].headersize = 0; - SDC_disk[drive].doublesided = 0; + gCocoDisk[drive].type = DTYPE_RAW; + gCocoDisk[drive].headersize = 0; + gCocoDisk[drive].doublesided = 0; } else { // Read a few bytes of the file to determine it's type unsigned char header[16]; - if (ReadFile(SDC_disk[drive].hFile,header,12,nullptr,nullptr) == 0) { + if (ReadFile(gCocoDisk[drive].hFile,header,12,nullptr,nullptr) == 0) { DLOG_C("OpenFound header read error\n"); IF.status = STA_FAIL | STA_INVALID; return; @@ -2203,44 +2220,44 @@ void OpenFound (int drive,int raw) // track record is 6250 bytes. Assume at least 35 tracks so // minimum size of a SDF file is 219262 bytes. The first four // bytes of the header contains "SDF1" - if ((SDC_disk[drive].size >= 219262) && // is this reasonable? + if ((gCocoDisk[drive].size >= 219262) && // is this reasonable? (strncmp("SDF1",(const char *) header,4) == 0)) { - SDC_disk[drive].type = DTYPE_SDF; + gCocoDisk[drive].type = DTYPE_SDF; DLOG_C("OpenFound SDF file unsupported\n"); IF.status = STA_FAIL | STA_INVALID; return; } unsigned int numsec; - SDC_disk[drive].headersize = SDC_disk[drive].size & 255; - DLOG_C("OpenFound headersize %d\n",SDC_disk[drive].headersize); - switch (SDC_disk[drive].headersize) { + gCocoDisk[drive].headersize = gCocoDisk[drive].size & 255; + DLOG_C("OpenFound headersize %d\n",gCocoDisk[drive].headersize); + switch (gCocoDisk[drive].headersize) { // JVC optional header bytes case 4: // First Sector = header[3] 1 assumed case 3: // Sector Size code = header[2] 256 assumed {128,256,512,1024} case 2: // Number of sides = header[1] 1 or 2 case 1: // Sectors per trk = header[0] 18 assumed - SDC_disk[drive].doublesided = (header[1] == 2); - SDC_disk[drive].type = DTYPE_JVC; + gCocoDisk[drive].doublesided = (header[1] == 2); + gCocoDisk[drive].type = DTYPE_JVC; break; // No apparant header // JVC or OS9 disk if no header, side count per file size case 0: - numsec = SDC_disk[drive].size >> 8; + numsec = gCocoDisk[drive].size >> 8; DLOG_C("OpenFound JVC/OS9 sectors %d\n",numsec); - SDC_disk[drive].doublesided = ((numsec > 720) && (numsec <= 2880)); - SDC_disk[drive].type = DTYPE_JVC; + gCocoDisk[drive].doublesided = ((numsec > 720) && (numsec <= 2880)); + gCocoDisk[drive].type = DTYPE_JVC; break; // VDK case 12: - SDC_disk[drive].doublesided = (header[8] == 2); + gCocoDisk[drive].doublesided = (header[8] == 2); writeprotect = header[9] & 1; - SDC_disk[drive].type = DTYPE_VDK; + gCocoDisk[drive].type = DTYPE_VDK; break; // Unknown or unsupported default: // More than 4 byte header is not supported DLOG_C("OpenFound unsuported image type %d %d\n", - drive, SDC_disk[drive].headersize); + drive, gCocoDisk[drive].headersize); IF.status = STA_FAIL | STA_INVALID; return; break; @@ -2248,22 +2265,22 @@ void OpenFound (int drive,int raw) } // Fill in image info. - LoadFoundFile(&SDC_disk[drive].filerec); + LoadFoundFile(&gCocoDisk[drive].filerec); // Set readonly attrib per find status or file header - if ((SDC_disk[drive].filerec.FR_attrib & ATTR_RDONLY) != 0) { + if ((gCocoDisk[drive].filerec.FR_attrib & ATTR_RDONLY) != 0) { writeprotect = 1; } else if (writeprotect) { - SDC_disk[drive].filerec.FR_attrib |= ATTR_RDONLY; + gCocoDisk[drive].filerec.FR_attrib |= ATTR_RDONLY; } // If file is writeable reopen read/write if (!writeprotect) { - CloseHandle(SDC_disk[drive].hFile); - SDC_disk[drive].hFile = CreateFile( - SDC_disk[drive].fullpath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, + CloseHandle(gCocoDisk[drive].hFile); + gCocoDisk[drive].hFile = CreateFile( + gCocoDisk[drive].fullpath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, nullptr,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,nullptr); - if (SDC_disk[drive].hFile == INVALID_HANDLE_VALUE) { + if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { DLOG_C("OpenFound reopen fail %d\n",drive); DLOG_C("... %s\n",LastErrorTxt()); IF.status = STA_FAIL | STA_WIN_ERROR; @@ -2380,9 +2397,9 @@ void MakeDirectory(const char *name) void CloseDrive (int drive) { drive &= 1; - if (SDC_disk[drive].hFile && SDC_disk[drive].hFile != INVALID_HANDLE_VALUE) { - CloseHandle(SDC_disk[drive].hFile); - SDC_disk[drive].hFile = INVALID_HANDLE_VALUE; + if (gCocoDisk[drive].hFile && gCocoDisk[drive].hFile != INVALID_HANDLE_VALUE) { + CloseHandle(gCocoDisk[drive].hFile); + gCocoDisk[drive].hFile = INVALID_HANDLE_VALUE; } } From 8ba09e77c3205b15d23ff4062e3a58b4c1b396e6 Mon Sep 17 00:00:00 2001 From: ejaquay Date: Mon, 12 Jan 2026 00:58:46 -0500 Subject: [PATCH 04/11] SDC new FileList structure --- sdc/sdc.cpp | 1975 +++++++++++++++++++++++++++++---------------------- 1 file changed, 1138 insertions(+), 837 deletions(-) diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index 764edab3..b2603020 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -1,8 +1,6 @@ -//#define USE_LOGGING +#define USE_LOGGING //====================================================================== -// SDC simulator DLL -// -// By E J Jaquay 2025 +// SDC simulator. EJ Jaquay 2025 // // This file is part of VCC (Virtual Color Computer). // Vcc is Copyright 2015 by Joseph Forgione @@ -23,20 +21,19 @@ // //====================================================================== // -// SDC Floppy port conflicts -// ------------------------- -// The SDC interface shares ports with the FD502 floppy controller. +// SDC Floppy port +// --------------- +// The SDC interface shares ports with the FDC floppy emulator. // -// $FF40 ; controller latch (write) +// $FF40 ; SDC latch / FDC control // $FF42 ; flash data register // $FF43 ; flash control register -// $FF48 ; command register (write) -// $FF48 ; status register (read) -// $FF49 ; param register 1 -// $FF4A ; param register 2 -// $FF4B ; param register 3 +// $FF48 ; command / status +// $FF49 ; param register 1 / FDC Track +// $FF4A ; param register 2 / FDC Sector +// $FF4B ; param register 3 / FDC Data // -// The FD502 interface uses following ports; +// The FDC emulation uses following ports; // // $FF40 ; Control register (write) // Bit 7 halt flag 0 = disabled 1 = enabled @@ -48,6 +45,9 @@ // Bit 1 drive select 1 // Bit 0 drive select 0 // +// SDC latch code is 0x43 which is unlikey to be used for FDC +// control because that would be multiple drives selected. +// // $FF48 ; Command register (write) // high order nibble; low order nibble type; command // 0x0 ; I ; Restore @@ -56,9 +56,9 @@ // 0x4 ; I ; Step in // 0x5 ; I ; Step out // 0x8 ; II ; Read sector -// 0x9 ; II ; Read sector multiple +// 0x9 ; II ; Read sector mfm // 0xA ; II ; write sector -// 0xB ; II ; write sector multiple +// 0xB ; II ; write sector mfm // 0xC ; III ; read address // 0xD ; III ; force interrupt // 0xE ; III ; read track @@ -69,20 +69,9 @@ // II ; b3 side compare enable, b2 delay, b1 side, b0 0 // III ; b2 delay others 0 // IV ; interrupt control b3 immediate, b2 index pulse, b1 notready, b0 ready -// $FF48 ; Status register (read) -// $FF49 ; Track register (read/write) -// $FF4A ; Sector register (read/write) -// $FF4B ; Data register (read/write) // -// Port conflicts are resolved in the MPI by using the SCS (select -// cart signal) to direct the floppy ports ($FF40-$FF5F) to the -// selected cartridge slot, either SDC or FD502. -// -// Sometime in the past the VCC cart select was disabled in mmi.cpp. This -// had to be be re-enabled for for sdc to co-exist with FD502. Additionally -// the becker port (drivewire) uses port $FF41 for status and port $FF42 -// for data so these must be always alowed for becker.dll to work. This -// means sdc.dll requires the new version of mmi.dll to work properly. +// The becker port (drivewire) uses port $FF41 for status and port $FF42 +// for data so these must be always allowed for becker.dll to work. // // NOTE: Stock SDCDOS does not in support the becker ports, it expects // drivewire to be on the bitbanger ports. RGBDOS does, however, and will @@ -125,20 +114,15 @@ // bank is stored. When a bank is selected the file is read into ROM. // The SDC-DOS RUN@n command can be used to select a bank. // -// This simulator has no provision for writing to the banks or the -// associated files. These are easily managed using the host system. -// // Data written to the flash data port (0x42) can be read back. // When the flash data port (0x43) is written the three low bits // select the ROM from the corresponding flash bank (0-7). When // read the flash control port returns the currently selected bank // in the three low bits and the five bits from the Flash Data port. // -// SDC-DOS is typically in bank zero and disk basic in bank one. These -// ROMS require their respective .DLL's to be installed to function, -// these are typically in MMI slot 3 and 4 respectively. -// +// SDC-DOS is typically in bank zero. //====================================================================== + #include #include #include @@ -164,12 +148,253 @@ #include #include "sdc.h" -//====================================================================== - static ::vcc::devices::rtc::cloud9 cloud9_rtc; + +//========================================================================= +// Host file utilities. Most of these are general purpose +//========================================================================= + +// Get most recent windows error text +inline const char * LastErrorTxt(); +inline std::string LastErrorString(); + +// Convert backslashes to slashes in directory string / char +void FixDirSlashes(std::string &dir); +void FixDirSlashes(char *dir); + +// copy string into fixed-size char array and blank-pad +template +void copy_to_fixed_char(char (&dest)[N], const std::string& src); + +// Return copy of string with spaces trimmed from end of a string +inline std::string trim_right_spaces(const std::string &s); + +// Convert LFN to FAT filename parts, 8 char name, 3 char ext +void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn); + +// Convert FAT file name parts to LFN. Returns empty string if invalid LFN +std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]); + +// Return slash normalized directory part of a path +inline std::string GetDirectoryPart(const std::string& input); + +// Return filename part of a path +inline std::string GetFileNamePart(const std::string& input); + +// SDC interface often presents a path which does not use a dot to +// delimit name and extension: "FOODIR/FOO DSK" -> FOODIR/FOO.DSK +std::string FixFATPath(const std::string& sdcpath); +void FixFATPath(char* path, const char* sdcpath); + +// Determine if path is a direcory +inline bool IsDirectory(const std::string& path); + +//---------------------------------------------------------------------- +// Get most recent windows error text +//---------------------------------------------------------------------- +inline const char * LastErrorTxt() { + static char msg[200]; + DWORD error_code = GetLastError(); + FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msg, sizeof(msg), nullptr ); + return msg; +} +inline std::string LastErrorString() { + return LastErrorTxt(); +} + +//------------------------------------------------------------------- +// SDC expects forward slashes as path delimiter, not backslashes +//------------------------------------------------------------------- +void FixDirSlashes(std::string &dir) +{ + if (dir.empty()) return; + std::replace(dir.begin(), dir.end(), '\\', '/'); + if (dir.back() == '/') dir.pop_back(); +} +void FixDirSlashes(char *dir) +{ + if (!dir) return; + std::string tmp(dir); + FixDirSlashes(tmp); + strcpy(dir, tmp.c_str()); +} + +//------------------------------------------------------------------- +// Copy string to fixed size char array (non terminated) +//------------------------------------------------------------------- +template +void copy_to_fixed_char(char (&dest)[N], const std::string& src) +{ + size_t i = 0; + for (; i < src.size() && i < N; ++i) + dest[i] = src[i]; + for (; i < N; ++i) + dest[i] = ' '; +} + +//------------------------------------------------------------------- +// Return copy of string with spaces trimmed from end of a string +//------------------------------------------------------------------- +inline std::string trim_right_spaces(const std::string& s) +{ + size_t end = s.find_last_not_of(' '); + if (end == std::string::npos) + return {}; + return s.substr(0, end + 1); +} + +//------------------------------------------------------------------- +// Convert LFN to FAT filename parts, 8 char name, 3 char ext +// A LNF file is less than 4GB and has a short (8.3) name. +//------------------------------------------------------------------- +void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn) +{ + // Special case: parent directory + if (lfn == "..") { + copy_to_fixed_char(name, ".."); + std::fill(ext, ext + 3, ' '); + return; + } + + size_t dot = lfn.find('.'); + std::string base, extension; + + if (dot == std::string::npos) { + base = lfn; + } else { + base = lfn.substr(0, dot); + extension = lfn.substr(dot + 1); + } + + copy_to_fixed_char(name, base); + copy_to_fixed_char(ext, extension); +} + +//------------------------------------------------------------------- +// Convert FAT filename parts to LFN. Returns empty string if invalid +//------------------------------------------------------------------- +std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]) +{ + std::string base(name, 8); + std::string extension(ext, 3); + + base = trim_right_spaces(base); + extension = trim_right_spaces(extension); + + if (base == ".." && extension.empty()) + return ".."; + + std::string lfn = base; + + if (!extension.empty()) + lfn += "." + extension; + + if (lfn.empty()) + return {}; + + if (!PathIsLFNFileSpecA(lfn.c_str())) + return {}; + + return lfn; +} + +//------------------------------------------------------------------- +// Return slash normalized directory part of a path +//------------------------------------------------------------------- +inline std::string GetDirectoryPart(const std::string& input) +{ + std::filesystem::path p(input); + std::string out = p.parent_path().string(); + FixDirSlashes(out); + return out; +} + +//------------------------------------------------------------------- +// Return filename part of a path +//------------------------------------------------------------------- +inline std::string GetFileNamePart(const std::string& input) +{ + std::filesystem::path p(input); + return p.filename().string(); +} + +//------------------------------------------------------------------- +// Convert string containing possible FAT name and extension to an +// LFN string. Returns empty string if invalid LFN +//------------------------------------------------------------------- +std::string NormalizeInputToLFN(const std::string& s) +{ + if (s.empty()) return {}; + if (s.size() > 11) return {}; + if (s == "..") return ".."; + + // LFN candidate + if (s.find('.') != std::string::npos) { + if (!PathIsLFNFileSpecA(s.c_str())) + return {}; // invalid + return s; + } + + // SFN candidate + char name[8]; + char ext[3]; + sfn_from_lfn(name,ext,s); + return lfn_from_sfn(name,ext); +} + +//------------------------------------------------------------------- +// Determine if path is a direcory +//------------------------------------------------------------------- +inline bool IsDirectory(const std::string& path) +{ + std::error_code ec; + return std::filesystem::is_directory(path, ec) && !ec; +} + +//---------------------------------------------------------------------- +// A file path may use 11 char FAT format which does not use a separater +// between name and extension. User is free to use standard dot format. +// "FOODIR/FOO.DSK" = FOODIR/FOO.DSK +// "FOODIR/FOO DSK" = FOODIR/FOO.DSK +// "FOODIR/ALONGFOODSK" = FOODIR/ALONGFOO.DSK +//---------------------------------------------------------------------- +std::string FixFATPath(const std::string& sdcpath) +{ + std::filesystem::path p(sdcpath); + + auto chop = [](std::string s) { + size_t space = s.find(' '); + if (space != std::string::npos) s.erase(space); + return s; + }; + + std::string fname = p.filename().string(); + if (fname.length() == 11 && fname.find('.') == std::string::npos) { + auto nam = chop(fname.substr(0,8)); + auto ext = chop(fname.substr(8,3)); + fname = ext.empty() ? nam : nam + "." + ext; + } + + std::filesystem::path out = p.parent_path() / fname; + + DLOG_C("FixFATPath in %s out %s\n",sdcpath.c_str(),out.generic_string().c_str()); + return out.generic_string(); +} + +void FixFATPath(char* path, const char* sdcpath) +{ + std::string fixed = FixFATPath(std::string(sdcpath)); + strncpy(path, fixed.c_str(), MAX_PATH); + path[MAX_PATH - 1] = '\0'; +} + //====================================================================== -// Private functions +// SDC specific funtions //====================================================================== LRESULT CALLBACK SDC_Configure(HWND, UINT, WPARAM, LPARAM); @@ -181,127 +406,185 @@ void UpdateFlashItem(int); void InitCardBox(); void InitFlashBoxes(); void FitEditTextPath(HWND, int, const char *); -inline bool IsDirectory(const std::string&); -char * LastErrorTxt(); -void SDCInit(); +void InitSDC(); void LoadRom(unsigned char); -void SDCWrite(unsigned char,unsigned char); -unsigned char SDCRead(unsigned char port); void ParseStartup(); -void SDCCommand(); -void ReadSector(); -void StreamImage(); -void WriteSector(); -bool SeekSector(unsigned char,unsigned int); -bool ReadDrive(unsigned char,unsigned int); -void GetDriveInfo(); -void SDCControl(); -void UpdateSD(); -bool LoadFoundFile(struct FileRecord *); -std::string FixSDCPath(const std::string&); -void FixSDCPath(char *,const char *); -void MountDisk(int,const char *,int); -void MountNewDisk(int,const char *,int); -bool MountNext(int); -void OpenNew(int,const char *,int); -void CloseDrive(int); -void OpenFound(int,int); -void LoadReply(const void *, int); -void BlockReceive(unsigned char); -void FlashControl(unsigned char); -void LoadDirPage(); -void SetCurDir(const char *); bool SearchFile(const char *); -bool InitiateDir(const char *); -std::string GetFullPath(const std::string&); -void GetFullPath(char *,const char *); -void RenameFile(const char *); -void KillFile(const char *); -void MakeDirectory(const char *); -void GetMountedImageRec(); -void GetSectorCount(); -void GetDirectoryLeaf(); -unsigned char PickReplyByte(unsigned char); -unsigned char WriteFlashBank(unsigned short); - -void FloppyCommand(unsigned char); -void FloppyRestore(); -void FloppySeek(); -void FloppyReadDisk(); -void FloppyWriteDisk(); -void FloppyTrack(unsigned char); -void FloppySector(unsigned char); -void FloppyWriteData(unsigned char); -unsigned int FloppyLSN(unsigned int,unsigned int,unsigned int); -unsigned char FloppyStatus(); -unsigned char FloppyReadData(); //====================================================================== -// Globals +// SDC interface functions //====================================================================== +void SDCReadSector(); +void SDCStreamImage(); +void SDCWriteSector(); +void SDCGetDriveInfo(); +void SDCUpdateSD(); +void SDCSetCurDir(const char *); +bool SDCInitiateDir(const char *); +void SDCRenameFile(const char *); +void SDCKillFile(const char *); +void SDCMakeDirectory(const char *); +void SDCGetMountedImageRec(); +void SDCGetSectorCount(); +void SDCGetDirectoryLeaf(); +void SDCMountDisk(int,const char *,int); +void SDCMountNewDisk(int,const char *,int); +bool SDCMountNext(int); +unsigned char SDCRead(unsigned char port); +void SDCOpenFound(int,int); +void SDCOpenNew(int,const char *,int); +void SDCWrite(unsigned char,unsigned char); +void SDCControl(); +void SDCCommand(); +void SDCBlockReceive(unsigned char); +void SDCFlashControl(unsigned char); +void SDCFloppyCommand(unsigned char); +void SDCFloppyRestore(); +void SDCFloppySeek(); +void SDCFloppyReadDisk(); +void SDCFloppyWriteDisk(); +void SDCFloppyTrack(unsigned char); +void SDCFloppySector(unsigned char); +void SDCFloppyWriteData(unsigned char); +unsigned int SDCFloppyLSN(unsigned int,unsigned int,unsigned int); +unsigned char SDCFloppyStatus(); +unsigned char SDCFloppyReadData(); +unsigned char SDCPickReplyByte(unsigned char); +unsigned char SDCWriteFlashBank(unsigned short); +void SDCLoadReply(const void *, int); +bool SDCSeekSector(unsigned char,unsigned int); +bool SDCReadDrive(unsigned char,unsigned int); +bool SDCLoadNextDirPage(); +std::string SDCGetFullPath(const std::string&); -// Idle Status counter -int idle_ctr = 0; - -// SDC CoCo Interface -struct Interface -{ - int sdclatch; - unsigned char cmdcode; - unsigned char status; - unsigned char reply1; - unsigned char reply2; - unsigned char reply3; - unsigned char param1; - unsigned char param2; - unsigned char param3; - unsigned char reply_mode; // 0=words, 1=bytes - unsigned char reply_status; - unsigned char half_sent; - int bufcnt; - char *bufptr; - char blkbuf[600]; -}; -static Interface IF = {}; - -// Cart ROM -char PakRom[0x4000]; +//====================================================================== -static char IniFile[MAX_PATH] = {}; // Vcc ini file name +//========================================================================= +// Host directory handling +//========================================================================= -// Directory globals +// Host Directory globals static std::string gSDRoot {}; // SD card root directory static std::string gCurDir {}; // Current directory relative to root -static std::string gSeaDir {}; // Last directory searched -// A less than 4GB host file with a short name -// File location is gSeaDir +//------------------------------------------------------------------- +// HostFile contains a file name, it's size, and directory and readonly flags +// gFileList is a list of HostFiles found within a particular host directory +// using a search pattern. The pattern may include a directory specifier. +// Host file name, size, and attributes are kept in HostFile structs. +//------------------------------------------------------------------- + struct HostFile { - std::string name; - DWORD size; - bool isDirectory; - bool isReadonly; - // Constructor - HostFile(const char* cName, DWORD nSize, DWORD attr) : - name(cName), - size(nSize), - isDirectory((attr & FILE_ATTRIBUTE_DIRECTORY) != 0), - isReadonly((attr & FILE_ATTRIBUTE_READONLY) != 0) - {} + std::string name; // LFN 8.3 name + DWORD size; // < 4GB + bool isDir; // is a directory + bool isRdOnly; // is read only + HostFile() {} + // Construct a HostFile from an argument list + HostFile(const char* cName, DWORD nSize, bool isDir, bool isRdOnly) : + name(cName), size(nSize), isDir(isDir), isRdOnly(isRdOnly) {} + // Construct a HostFile from a WIN32_FIND_DATAA record. + HostFile(WIN32_FIND_DATAA fd) : + name(fd.cFileName), size(fd.nFileSizeLow), + isDir((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0), + isRdOnly((fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {} +}; + +struct FileList { + std::vector files; // files + std::string directory; // directory files are in + size_t cursor = 0; // current file curspr + bool nextload_flag = false; // enable next disk loading + // append a host file to the list + void append(const HostFile& hf) { files.push_back(hf); } }; -// Global list of host files TODO: Use these -static std::vector gFileList; -static size_t gLastFileLoaded {}; -static size_t gLastDirPageFile {}; +static FileList gFileList; -// Clean up slashes on directory or path -void FixDirSlashes(std::string &dir); -void FixDirSlashes(char *dir); +void GetFileList(const std::string& pattern); +void SortFileList(); -// Packed file records for interface -#pragma pack(1) -struct FileRecord { +//------------------------------------------------------------------- +// Get list of files matching pattern starting from CurDir or SDRoot +//------------------------------------------------------------------- +void GetFileList(const std::string& pattern) +{ + + DLOG_C("GetFileList %s\n",pattern.c_str()); + + // Init search results + gFileList = {}; + + // None if pattern is empty + if (pattern.empty()) return; + + // Set up search path. + bool not_at_root = !gCurDir.empty(); + bool rel_pattern = pattern.front() != '/'; + + std::string search_path = gSDRoot; + if (rel_pattern) { + search_path += "/"; + if (not_at_root) + search_path += gCurDir + "/"; + } + search_path += pattern; + + // A directory lookup if pattern name is "*" or "*.*"; + std::string fnpat = GetFileNamePart(pattern); + bool dir_lookup = (fnpat == "*" || fnpat == "*.*"); + + std::string search_dir = GetDirectoryPart(search_path); + + // Include ".." if dir_lookup and search_dir is not SDRoot + if (dir_lookup && GetDirectoryPart(search_path) != gSDRoot) { + gFileList.append({"..", 0, true, false}); + } + + // Add matching files to the list + WIN32_FIND_DATAA fd; + HANDLE hFind = FindFirstFileA(search_path.c_str(), &fd); + if (hFind == INVALID_HANDLE_VALUE) { + gFileList = {}; + return; + } + do { + if (strcmp(fd.cFileName,".") == 0) continue; // exclude single dot file + if (PathIsLFNFileSpecA(fd.cFileName)) continue; // exclude ugly or long filenames + if (fd.nFileSizeHigh != 0) continue; // exclude files > 4 GB + gFileList.append({fd}); + } while (FindNextFileA(hFind, &fd)); + FindClose(hFind); + + // Save directory + gFileList.directory = search_dir; + + DLOG_C("GetFileList found %d\n",gFileList.files.size()); +} + +//------------------------------------------------------------------- +// SortFileList() may be needed when mounting a directory +//------------------------------------------------------------------- +void SortFileList() +{ + std::sort( + gFileList.files.begin(), + gFileList.files.end(), + [] (const HostFile& a, const HostFile& b) { + return a.name < b.name; + } + ); + gFileList.cursor = 0; +} + +//========================================================================= +// FileRecord is 16 packed bytes file name, type, attrib, and size that can +// be passed directly via the SDC interface. gDirPage is a 256 byte block +// containing up to 16 packed FileRecords. +//========================================================================= +#pragma pack(push, 1) +struct FileRecord +{ char FR_name[8]; char FR_type[3]; char FR_attrib; @@ -309,9 +592,30 @@ struct FileRecord { char FR_lohi_size; char FR_hilo_size; char FR_lolo_size; + FileRecord() noexcept {}; + // Construct FileRecord from a HostFile object. + FileRecord(const HostFile& hf) noexcept { + memset(this, 0, sizeof(*this)); + // name and type + sfn_from_lfn(FR_name, FR_type, hf.name); + // Attributes + FR_attrib = 0; + if (hf.isDir) FR_attrib |= 0x10; + if (hf.isRdOnly) FR_attrib |= 0x01; + // Size -> 4 reversed endian bytes + DWORD s = hf.size; + FR_hihi_size = (s >> 24) & 0xFF; + FR_lohi_size = (s >> 16) & 0xFF; + FR_hilo_size = (s >> 8) & 0xFF; + FR_lolo_size = (s >> 0) & 0xFF; + } }; -#pragma pack() -static struct FileRecord DirPage[16]; +static struct FileRecord gDirPage[16] = {}; +#pragma pack(pop) + +//========================================================================= +// CocoDisk contains info about mounted disk files 0 and 1 +//========================================================================= enum DiskType { DTYPE_RAW = 0, @@ -322,93 +626,94 @@ enum DiskType { }; // Mounted image data -struct CocoDisk_t { - HANDLE hFile; // stream handle - unsigned int size; // number of bytes total - unsigned int headersize; // number of bytes in header - DWORD sectorsize; // number of bytes per sector - DWORD tracksectors; // number of sectors per side - DiskType type; // Disk image type (RAW,DSK,JVC,VDK,SDF) - char doublesided; // false:1 side, true:2 sides - char name[MAX_PATH]; // name of file (8.3) - char fullpath[MAX_PATH]; // full file path +struct CocoDisk { + HANDLE hFile; // file handle + unsigned int size; // number of bytes total + unsigned int headersize; // number of bytes in header + DWORD sectorsize; // number of bytes per sector + DWORD tracksectors; // number of sectors per side + DiskType type; // Disk image type (RAW,DSK,JVC,VDK,SDF) + char doublesided; // false:1 side, true:2 sides + char name[MAX_PATH]; // name of file (8.3) + char fullpath[MAX_PATH]; // full file path + struct HostFile hf{"",0,false,false}; //name,size,isDir,isRdOnly struct FileRecord filerec; // Constructor - CocoDisk_t() noexcept + CocoDisk() noexcept : hFile(INVALID_HANDLE_VALUE), size(0), headersize(0), - sectorsize(0), - tracksectors(0), + sectorsize(256), + tracksectors(18), type(DiskType::DTYPE_RAW), - doublesided(0) { + doublesided(1) { name[0] = '\0'; fullpath[0] = '\0'; }; }; -CocoDisk_t gCocoDisk[2] {}; +CocoDisk gCocoDisk[2] {}; -// Flash banks -static char FlashFile[8][MAX_PATH]; -static FILE *h_RomFile = nullptr; -static unsigned char StartupBank = 0; -static unsigned char CurrentBank = 0xff; -static unsigned char EnableBankWrite = 0; -static unsigned char BankWriteNum = 0; -static unsigned char BankWriteState = 0; -static unsigned char BankDirty = 0; -static unsigned char BankData = 0; - -// Dll handle -static HINSTANCE gModuleInstance; - -// Clock enable IDC_CLOCK -static int ClockEnable; - -// Windows file lookup handle and data -static HANDLE hFind = INVALID_HANDLE_VALUE; -static WIN32_FIND_DATAA dFound; - -// Window handles -static HWND gVccWindow = nullptr; -static HWND hConfigureDlg = nullptr; -static HWND hSDCardBox = nullptr; -static HWND hStartupBank = nullptr; +//---------------------------------------------------------------------- +// Host File search. +//---------------------------------------------------------------------- -// Streaming control -static int streaming; -static unsigned char stream_cmdcode; -static unsigned int stream_lsn; +bool SearchFile(const char* pattern) +{ + std::string s = pattern; + GetFileList(s); + DLOG_C("SearchFile found %d pat %s\n",gFileList.files.size(),s.c_str()); -// Status for VCC status line -static char SDC_Status[16] = {}; + // Update menu to show next disk status + BuildCartridgeMenu(); -// Floppy I/O -static char FlopDrive = 0; -static char FlopData = 0; -static char FlopTrack = 0; -static char FlopSector = 0; -static char FlopStatus = 0; -static DWORD FlopWrCnt = 0; -static DWORD FlopRdCnt = 0; -static char FlopWrBuf[256]; -static char FlopRdBuf[256]; + return (gFileList.files.size() > 0); +} -static int EDBOXES[8] = {ID_TEXT0,ID_TEXT1,ID_TEXT2,ID_TEXT3, - ID_TEXT4,ID_TEXT5,ID_TEXT6,ID_TEXT7}; -static int UPDBTNS[8] = {ID_UPDATE0,ID_UPDATE1,ID_UPDATE2,ID_UPDATE3, - ID_UPDATE4,ID_UPDATE5,ID_UPDATE6,ID_UPDATE7}; +//====================================================================== +// Static Globals +//====================================================================== -static char MPIPath[MAX_PATH]; +static HINSTANCE gModuleInstance; // Dll handle +static int idle_ctr = 0; // Idle Status counter -// DLL Callback pointers +// Callback pointers static void* gCallbackContext = nullptr; static PakAssertInteruptHostCallback AssertIntCallback = nullptr; static PakAppendCartridgeMenuHostCallback CartMenuCallback = nullptr; +static char IniFile[MAX_PATH] = {}; // Vcc ini file name + +// Data used elsewhere +static HWND gVccWindow = nullptr; +static HWND hConfigureDlg = nullptr; +static int ClockEnable = 0; +static char SDC_Status[16] = {}; +static char PakRom[0x4000] = {}; +static unsigned char CurrentBank = 0xff; +static unsigned char EnableBankWrite = 0; +static unsigned char BankWriteState = 0; //====================================================================== -// DLL exports +// DLL interface //====================================================================== + +//---------------------------------------------------------------------- +// Functions referenced by DLL interface +//---------------------------------------------------------------------- + +void LoadConfig(); +void BuildCartridgeMenu(); +//void CloseDrive(int); +void UnloadDisk(int); +void SDCWrite(unsigned char,unsigned char); +unsigned char SDCRead(unsigned char port); +LRESULT CALLBACK SDC_Configure(HWND, UINT, WPARAM, LPARAM); +bool SDCMountNext(int); +void InitSDC(); +unsigned char SDCWriteFlashBank(unsigned short); + +//---------------------------------------------------------------------- +// DLL exports +//---------------------------------------------------------------------- extern "C" { // PakInitialize gets called first, sets up dynamic menues and captures callbacks @@ -455,8 +760,10 @@ extern "C" { CloseCartDialog(hConfigureDlg); hConfigureDlg = nullptr; - CloseDrive(0); - CloseDrive(1); + UnloadDisk(0); + UnloadDisk(1); + //CloseDrive(0); + //CloseDrive(1); } // Write to port @@ -482,7 +789,7 @@ extern "C" __declspec(dllexport) void PakReset() { DLOG_C("PakReset\n"); - SDCInit(); + InitSDC(); } // Dll export run config dialog @@ -497,7 +804,7 @@ extern "C" ShowWindow(hConfigureDlg,1); break; case 11: - MountNext (0); + SDCMountNext (0); break; } BuildCartridgeMenu(); @@ -521,7 +828,7 @@ extern "C" { adr &= 0x3FFF; if (EnableBankWrite) { - return WriteFlashBank(adr); + return SDCWriteFlashBank(adr); } else { BankWriteState = 0; // Any read resets write state return(PakRom[adr]); @@ -529,9 +836,10 @@ extern "C" } } -//------------------------------------------------------------- -// Dll Main here so it can use PakTerminate -//------------------------------------------------------------- +//---------------------------------------------------------------------- +// DLL main +//---------------------------------------------------------------------- + BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID rsvd) { if (reason == DLL_PROCESS_ATTACH) { @@ -542,6 +850,86 @@ BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID rsvd) return TRUE; } +//====================================================================== +// Unsorted Globals +//====================================================================== + +// SDC CoCo Interface +struct Interface +{ + int sdclatch; + unsigned char cmdcode; + unsigned char status; + unsigned char reply1; + unsigned char reply2; + unsigned char reply3; + unsigned char param1; + unsigned char param2; + unsigned char param3; + unsigned char reply_mode; // 0=words, 1=bytes + unsigned char reply_status; + unsigned char half_sent; + int bufcnt; + char *bufptr; + char blkbuf[600]; +}; +static Interface IF = {}; + +//static std::string gSeaDir {}; // Last directory searched + +// Flash banks +static char FlashFile[8][MAX_PATH]; +static FILE *h_RomFile = nullptr; +static unsigned char StartupBank = 0; +static unsigned char BankWriteNum = 0; +static unsigned char BankDirty = 0; +static unsigned char BankData = 0; +// Following are in DLL section +//static unsigned char CurrentBank = 0xff; +//static unsigned char EnableBankWrite = 0; +//static unsigned char BankWriteState = 0; + +// Clock enable IDC_CLOCK +//static int ClockEnable; + +// Windows file lookup handle and data +//static HANDLE hFind = INVALID_HANDLE_VALUE; +//static WIN32_FIND_DATAA dFound; + +// Window handles +static HWND hSDCardBox = nullptr; +static HWND hStartupBank = nullptr; +// Following two are in DLL section +//static HWND hConfigureDlg = nullptr; +//static HWND gVccWindow = nullptr; + +// Streaming control +static int streaming; +static unsigned char stream_cmdcode; +static unsigned int stream_lsn; + +// Status for VCC status line +//static char SDC_Status[16] = {}; + +// Floppy I/O +static char FlopDrive = 0; +static char FlopData = 0; +static char FlopTrack = 0; +static char FlopSector = 0; +static char FlopStatus = 0; +static DWORD FlopWrCnt = 0; +static DWORD FlopRdCnt = 0; +static char FlopWrBuf[256]; +static char FlopRdBuf[256]; + +static int EDBOXES[8] = {ID_TEXT0,ID_TEXT1,ID_TEXT2,ID_TEXT3, + ID_TEXT4,ID_TEXT5,ID_TEXT6,ID_TEXT7}; +static int UPDBTNS[8] = {ID_UPDATE0,ID_UPDATE1,ID_UPDATE2,ID_UPDATE3, + ID_UPDATE4,ID_UPDATE5,ID_UPDATE6,ID_UPDATE7}; + +static char MPIPath[MAX_PATH]; + + //===================================================================== // User interface //====================================================================== @@ -559,7 +947,11 @@ void BuildCartridgeMenu() strcpy(tmp,"empty"); } else { strcpy(tmp,gCocoDisk[0].name); - strcat(tmp," (load next)"); + if (gFileList.nextload_flag) { + strcat(tmp," (load next)"); + } else { + strcat(tmp," (no next)"); + } } CartMenuCallback(gCallbackContext, tmp, ControlId(11),MIT_Slave); CartMenuCallback(gCallbackContext, "SDC Config", ControlId(10), MIT_StandAlone); @@ -840,30 +1232,29 @@ void SelectCardBox() //---------------------------------------------------------------------- // Init the controller. This gets called by PakReset //---------------------------------------------------------------------- -void SDCInit() +void InitSDC() { - DLOG_C("\nSDCInit\n"); + DLOG_C("\nInitSDC\n"); #ifdef USE_LOGGING MoveWindow(GetConsoleWindow(),0,0,300,800,TRUE); #endif - // Init the hFind handle (otherwise could crash on dll load) - hFind = INVALID_HANDLE_VALUE; - // Make sure drives are unloaded - MountDisk (0,"",0); - MountDisk (1,"",0); + SDCMountDisk (0,"",0); + SDCMountDisk (1,"",0); // Load SDC settings LoadConfig(); LoadRom(StartupBank); - SetCurDir(""); // May be changed by ParseStartup() + SDCSetCurDir(""); // May be changed by ParseStartup() gCocoDisk[0] = {}; gCocoDisk[1] = {}; + gFileList = {}; + // Process the startup config file ParseStartup(); @@ -983,19 +1374,24 @@ void ParseStartup() // Attempt to mount drive switch (drv) { case '0': - MountDisk(0,&buf[2],0); + SDCMountDisk(0,&buf[2],0); break; case '1': - MountDisk(1,&buf[2],0); + SDCMountDisk(1,&buf[2],0); break; case 'D': - SetCurDir(&buf[2]); + SDCSetCurDir(&buf[2]); break; } } fclose(su); } + +//===================================================================== +// SDC Interface functions +//===================================================================== + //---------------------------------------------------------------------- // Write port. If a command needs a data block to complete it // will put a count (256 or 512) in IF.bufcnt. @@ -1022,14 +1418,14 @@ void SDCWrite(unsigned char data,unsigned char port) // Command param #2 or block data receive case 0x4A: if (IF.bufcnt > 0) - BlockReceive(data); + SDCBlockReceive(data); else IF.param2 = data; break; // Command param #3 or block data receive case 0x4B: if (IF.bufcnt > 0) - BlockReceive(data); + SDCBlockReceive(data); else IF.param3 = data; break; @@ -1073,23 +1469,23 @@ void SDCWrite(unsigned char data,unsigned char port) break; // Flash Control case 0x43: - FlashControl(data); + SDCFlashControl(data); break; // floppy command case 0x48: - FloppyCommand(data); + SDCFloppyCommand(data); break; // floppy set track case 0x49: - FloppyTrack(data); + SDCFloppyTrack(data); break; // floppy set sector case 0x4A: - FloppySector(data); + SDCFloppySector(data); break; // floppy write data case 0x4B: - FloppyWriteData(data); + SDCFloppyWriteData(data); break; // Unhandled default: @@ -1123,7 +1519,7 @@ unsigned char SDCRead(unsigned char port) // Reply data 2 or block reply case 0x4A: if (IF.bufcnt > 0) { - rpy = PickReplyByte(port); + rpy = SDCPickReplyByte(port); } else { rpy = IF.reply2; } @@ -1131,7 +1527,7 @@ unsigned char SDCRead(unsigned char port) // Reply data 3 or block reply case 0x4B: if (IF.bufcnt > 0) { - rpy = PickReplyByte(port); + rpy = SDCPickReplyByte(port); } else { rpy = IF.reply3; } @@ -1143,17 +1539,34 @@ unsigned char SDCRead(unsigned char port) } } else { switch (port) { + case 0x40: + //Nitros9 driver reads this + DLOG_C("SDCRead floppy port 40?\n"); + rpy = 0; + break; // Flash control read is used by SDCDOS to detect the SDC case 0x43: rpy = CurrentBank | (BankData & 0xF8); break; // Floppy read status case 0x48: - rpy = FloppyStatus(); + rpy = SDCFloppyStatus(); + break; + // Current Track + case 0x49: + //Nitros9 driver reads this every sector + //DLOG_C("SDCRead floppy track?\n"); + rpy = 0; + break; + // Current Sector + case 0x4A: + //Nitros9 driver reads this every sector + //DLOG_C("SDCRead floppy sector?\n"); + rpy = 0; break; // Floppy read data case 0x4B: - rpy = FloppyReadData(); + rpy = SDCFloppyReadData(); break; default: DLOG_C("SDCRead U %02x\n",port); @@ -1161,49 +1574,195 @@ unsigned char SDCRead(unsigned char port) break; } } - return rpy; + return rpy; +} + +//---------------------------------------------------------------------- +// Dispatch SDC commands +//---------------------------------------------------------------------- +void SDCCommand() +{ + switch (IF.cmdcode & 0xF0) { + // Read sector + case 0x80: + SDCReadSector(); + break; + // Stream 512 byte sectors + case 0x90: + SDCStreamImage(); + break; + // Get drive info + case 0xC0: + SDCGetDriveInfo(); + break; + // Control SDC + case 0xD0: + SDCControl(); + break; + // Next two are block receive commands + case 0xA0: + case 0xE0: + IF.status = STA_READY | STA_BUSY; + IF.bufptr = IF.blkbuf; + IF.bufcnt = 256; + IF.half_sent = 0; + break; + } + return; +} + +//---------------------------------------------------------------------- +// Floppy I/O +//---------------------------------------------------------------------- + +void SDCFloppyCommand(unsigned char data) +{ + unsigned char cmd = data >> 4; + switch (cmd) { + case 0: //RESTORE + SDCFloppyRestore(); + break; + case 1: //SEEK + SDCFloppySeek(); + break; + //case 2: //STEP + //case 3: //STEPUPD + //case 4: //STEPIN + //case 5: //STEPINUPD + //case 6: //STEFOUT + //case 7: //STEPOUTUPD + case 8: //READSECTOR + case 9: //READSECTORM + SDCFloppyReadDisk(); + break; + case 10: //WRITESECTOR + case 11: //WRITESECTORM + SDCFloppyWriteDisk(); + break; + case 12: //READADDRESS + //Nitros9 driver does this + DLOG_C("SDCFloppyCommand read address?\n"); + break; + case 13: //FORCEINTERUPT + //Nitros9 driver does this + DLOG_C("SDCFloppyCommand force interrupt?\n"); + break; + //case 14: //READTRACK + //case 15: //WRITETRACK + default: + DLOG_C("SDCFloppyCommand %d not implemented\n",cmd); + break; + } +} + +//---------------------------------------------------------------------- +// Get drive information +//---------------------------------------------------------------------- +void SDCGetDriveInfo() +{ + int drive = IF.cmdcode & 1; + switch (IF.param1) { + case 0x49: + // 'I' - return drive information in block + SDCGetMountedImageRec(); + break; + case 0x43: + // 'C' Return current directory leaf in block + SDCGetDirectoryLeaf(); + break; + case 0x51: + // 'Q' Return the size of disk image in p1,p2,p3 + SDCGetSectorCount(); + break; + case 0x3E: + // '>' Get directory page + SDCLoadNextDirPage(); + IF.reply_mode=0; + SDCLoadReply(gDirPage,256); + break; + case 0x2B: + // '+' Mount next next disk in set. + SDCMountNext(drive); + break; + case 0x56: + // 'V' Get BCD firmware version number in p2, p3. + IF.reply2 = 0x00; + IF.reply3 = 0x01; + break; + } } //---------------------------------------------------------------------- -// Floppy I/O +// Update SD Commands. //---------------------------------------------------------------------- - -void FloppyCommand(unsigned char data) +void SDCUpdateSD() { - unsigned char cmd = data >> 4; - switch (cmd) { - case 0: //RESTORE - FloppyRestore(); + switch (IF.blkbuf[0]) { + case 0x4D: //M + SDCMountDisk(IF.cmdcode&1,&IF.blkbuf[2],0); break; - case 1: //SEEK - FloppySeek(); + case 0x6D: //m + SDCMountDisk(IF.cmdcode&1,&IF.blkbuf[2],1); break; - //case 2: //STEP - //case 3: //STEPUPD - //case 4: //STEPIN - //case 5: //STEPINUPD - //case 6: //STEFOUT - //case 7: //STEPOUTUPD - case 8: //READSECTOR - FloppyReadDisk(); + case 0x4E: //N + SDCMountNewDisk(IF.cmdcode&1,&IF.blkbuf[2],0); break; - //case 9: //READSECTORM - case 10: //WRITESECTOR - FloppyWriteDisk(); + case 0x6E: //n + SDCMountNewDisk(IF.cmdcode&1,&IF.blkbuf[2],1); + break; + case 0x44: //D + SDCSetCurDir(&IF.blkbuf[2]); + break; + case 0x4C: //L + SDCInitiateDir(&IF.blkbuf[2]); + break; + case 0x4B: //K + SDCMakeDirectory(&IF.blkbuf[2]); + break; + case 0x52: //R + SDCRenameFile(&IF.blkbuf[2]); + break; + case 0x58: //X + SDCKillFile(&IF.blkbuf[2]); break; - //case 11: //WRITESECTORM - //case 12: //READADDRESS - //case 13: //FORCEINTERUPT - //case 14: //READTRACK - //case 15: //WRITETRACK default: - DLOG_C("Floppy cmd not implemented %d\n",cmd); + DLOG_C("SDCUpdateSD %02x not Supported\n",IF.blkbuf[0]); + IF.status = STA_FAIL; break; } } +//------------------------------------------------------------------- +// Load a DirPage from FileList. Called until list is exhausted +//------------------------------------------------------------------- +bool SDCLoadNextDirPage() +{ + DLOG_C("SDCLoadNextDirPage cur:%d siz:%d\n", + gFileList.cursor,gFileList.files.size()); + + std::memset(gDirPage, 0, sizeof gDirPage); + + if (gFileList.cursor >= gFileList.files.size()) { + DLOG_C("SDCLoadNextDirPage no files left\n"); + return false; + } + + size_t count = 0; + while (count < 16 && gFileList.cursor < gFileList.files.size()) { + gDirPage[count++] = FileRecord(gFileList.files[gFileList.cursor]); + ++gFileList.cursor; + } + + // gFileList.cursor not valid for next file loading + gFileList.nextload_flag = false; + + return true; +} + +//---------------------------------------------------------------------- // floppy restore -void FloppyRestore() +//---------------------------------------------------------------------- +void SDCFloppyRestore() { DLOG_C("FloppyRestore\n"); FlopTrack = 0; @@ -1214,19 +1773,23 @@ void FloppyRestore() AssertIntCallback(gCallbackContext, INT_NMI, IS_NMI); } +//---------------------------------------------------------------------- // floppy seek. No attempt is made to simulate seek times here. -void FloppySeek() +//---------------------------------------------------------------------- +void SDCFloppySeek() { DLOG_C("FloppySeek %d %d\n",FlopTrack,FlopData); FlopTrack = FlopData; } +//---------------------------------------------------------------------- // convert side (drive), track, sector, to LSN. The floppy controller // uses CHS addressing while hard drives, and the SDC, use LBA addressing. // FIXME: // Nine sector tracks, (512 byte sectors) // disk type: gCocoDisk[drive].type -unsigned int FloppyLSN( +//---------------------------------------------------------------------- +unsigned int SDCFloppyLSN( unsigned int drive, // 0 or 1 unsigned int track, // 0 to num tracks unsigned int sector // 1 to 18 @@ -1235,14 +1798,16 @@ unsigned int FloppyLSN( return track * 18 + sector - 1; } +//---------------------------------------------------------------------- // floppy read sector -void FloppyReadDisk() +//---------------------------------------------------------------------- +void SDCFloppyReadDisk() { - auto lsn = FloppyLSN(FlopDrive,FlopTrack,FlopSector); + auto lsn = SDCFloppyLSN(FlopDrive,FlopTrack,FlopSector); //DLOG_C("FloppyReadDisk %d %d\n",FlopDrive,lsn); snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,FlopDrive,lsn); - if (SeekSector(FlopDrive,lsn)) { + if (SDCSeekSector(FlopDrive,lsn)) { if (ReadFile(gCocoDisk[FlopDrive].hFile,FlopRdBuf,256,&FlopRdCnt,nullptr)) { DLOG_C("FloppyReadDisk %d %d\n",FlopDrive,lsn); FlopStatus = FLP_DATAREQ; @@ -1256,8 +1821,10 @@ void FloppyReadDisk() } } +//---------------------------------------------------------------------- // floppy write sector -void FloppyWriteDisk() +//---------------------------------------------------------------------- +void SDCFloppyWriteDisk() { // write not implemented int lsn = FlopTrack * 18 + FlopSector - 1; @@ -1265,14 +1832,18 @@ void FloppyWriteDisk() FlopStatus = FLP_READONLY; } +//---------------------------------------------------------------------- // floppy set track -void FloppyTrack(unsigned char data) +//---------------------------------------------------------------------- +void SDCFloppyTrack(unsigned char data) { FlopTrack = data; } +//---------------------------------------------------------------------- // floppy set sector -void FloppySector(unsigned char data) +//---------------------------------------------------------------------- +void SDCFloppySector(unsigned char data) { FlopSector = data; // Sector num in track (1-18) //int lsn = FlopTrack * 18 + FlopSector - 1; @@ -1280,8 +1851,10 @@ void FloppySector(unsigned char data) FlopStatus = FLP_NORMAL; } +//---------------------------------------------------------------------- // floppy write data -void FloppyWriteData(unsigned char data) +//---------------------------------------------------------------------- +void SDCFloppyWriteData(unsigned char data) { //DLOG_C("FloppyWriteData %d\n",data); if (FlopWrCnt<256) { @@ -1295,15 +1868,19 @@ void FloppyWriteData(unsigned char data) FlopData = data; } +//---------------------------------------------------------------------- // floppy get status -unsigned char FloppyStatus() +//---------------------------------------------------------------------- +unsigned char SDCFloppyStatus() { //DLOG_C("FloppyStatus %02x\n",FlopStatus); return FlopStatus; } +//---------------------------------------------------------------------- // floppy read data -unsigned char FloppyReadData() +//---------------------------------------------------------------------- +unsigned char SDCFloppyReadData() { unsigned char rpy; if (FlopRdCnt>0) { @@ -1323,7 +1900,7 @@ unsigned char FloppyReadData() // has most replies in words and the order the word bytes are read can // vary so we play games to send the right ones //---------------------------------------------------------------------- -unsigned char PickReplyByte(unsigned char port) +unsigned char SDCPickReplyByte(unsigned char port) { unsigned char rpy = 0; @@ -1350,50 +1927,15 @@ unsigned char PickReplyByte(unsigned char port) } // Keep stream going until stopped - if ((IF.bufcnt < 1) && streaming) StreamImage(); + if ((IF.bufcnt < 1) && streaming) SDCStreamImage(); return rpy; } -//---------------------------------------------------------------------- -// Dispatch SDC commands -//---------------------------------------------------------------------- -void SDCCommand() -{ - - switch (IF.cmdcode & 0xF0) { - // Read sector - case 0x80: - ReadSector(); - break; - // Stream 512 byte sectors - case 0x90: - StreamImage(); - break; - // Get drive info - case 0xC0: - GetDriveInfo(); - break; - // Control SDC - case 0xD0: - SDCControl(); - break; - // Next two are block receive commands - case 0xA0: - case 0xE0: - IF.status = STA_READY | STA_BUSY; - IF.bufptr = IF.blkbuf; - IF.bufcnt = 256; - IF.half_sent = 0; - break; - } - return; -} - //---------------------------------------------------------------------- // Receive block data //---------------------------------------------------------------------- -void BlockReceive(unsigned char byte) +void SDCBlockReceive(unsigned char byte) { if (IF.bufcnt > 0) { IF.bufcnt--; @@ -1404,56 +1946,19 @@ void BlockReceive(unsigned char byte) if (IF.bufcnt < 1) { switch (IF.cmdcode & 0xF0) { case 0xA0: - WriteSector(); + SDCWriteSector(); break; case 0xE0: - UpdateSD(); + SDCUpdateSD(); break; default: - DLOG_C("BlockReceive invalid cmd %d\n",IF.cmdcode); + DLOG_C("SDCBlockReceive invalid cmd %d\n",IF.cmdcode); IF.status = STA_FAIL; break; } } } -//---------------------------------------------------------------------- -// Get drive information -//---------------------------------------------------------------------- -void GetDriveInfo() -{ - int drive = IF.cmdcode & 1; - switch (IF.param1) { - case 0x49: - // 'I' - return drive information in block - GetMountedImageRec(); - break; - case 0x43: - // 'C' Return current directory leaf in block - GetDirectoryLeaf(); - break; - case 0x51: - // 'Q' Return the size of disk image in p1,p2,p3 - GetSectorCount(); - break; - case 0x3E: - // '>' Get directory page - LoadDirPage(); - IF.reply_mode=0; - LoadReply(DirPage,256); - break; - case 0x2B: - // '+' Mount next next disk in set. - MountNext(drive); - break; - case 0x56: - // 'V' Get BCD firmware version number in p2, p3. - IF.reply2 = 0x00; - IF.reply3 = 0x01; - break; - } -} - //---------------------------------------------------------------------- // Reply with directory leaf. Reply is 32 byte directory record. The first // 8 bytes are the name of the leaf, the next 3 are blanks, and the @@ -1462,7 +1967,7 @@ void GetDriveInfo() // path when restore last session is active. The full path is saved in // SDCX.CFG for the next session. //---------------------------------------------------------------------- -void GetDirectoryLeaf() +void SDCGetDirectoryLeaf() { namespace fs = std::filesystem; @@ -1476,56 +1981,16 @@ void GetDirectoryLeaf() } else { IF.reply_status = STA_FAIL | STA_NOTFOUND; } - DLOG_C("GetDirectoryLeaf CurDir '%s' leaf '%s' \n", gCurDir.c_str(),leaf); + DLOG_C("SDCGetDirectoryLeaf CurDir '%s' leaf '%s' \n", gCurDir.c_str(),leaf); IF.reply_mode = 0; - LoadReply(leaf, sizeof(leaf)); -} - -//---------------------------------------------------------------------- -// Update SD Commands. -//---------------------------------------------------------------------- -void UpdateSD() -{ - switch (IF.blkbuf[0]) { - case 0x4D: //M - MountDisk(IF.cmdcode&1,&IF.blkbuf[2],0); - break; - case 0x6D: //m - MountDisk(IF.cmdcode&1,&IF.blkbuf[2],1); - break; - case 0x4E: //N - MountNewDisk(IF.cmdcode&1,&IF.blkbuf[2],0); - break; - case 0x6E: //n - MountNewDisk(IF.cmdcode&1,&IF.blkbuf[2],1); - break; - case 0x44: //D - SetCurDir(&IF.blkbuf[2]); - break; - case 0x4C: //L - InitiateDir(&IF.blkbuf[2]); - break; - case 0x4B: //K - MakeDirectory(&IF.blkbuf[2]); - break; - case 0x52: //R - RenameFile(&IF.blkbuf[2]); - break; - case 0x58: //X - KillFile(&IF.blkbuf[2]); - break; - default: - DLOG_C("UpdateSD %02x not Supported\n",IF.blkbuf[0]); - IF.status = STA_FAIL; - break; - } + SDCLoadReply(leaf, sizeof(leaf)); } //---------------------------------------------------------------------- // Flash control //---------------------------------------------------------------------- -void FlashControl(unsigned char data) +void SDCFlashControl(unsigned char data) { unsigned char bank = data & 7; EnableBankWrite = data & 0x80; @@ -1558,9 +2023,9 @@ void FlashControl(unsigned char data) // state 6 write bank # adr sect val 30 kill bank # sect (adr & 0x3000) // //---------------------------------------------------------------------- -unsigned char WriteFlashBank(unsigned short adr) +unsigned char SDCWriteFlashBank(unsigned short adr) { - DLOG_C("WriteFlashBank %d %d %04X %02X\n", + DLOG_C("SDCWriteFlashBank %d %d %04X %02X\n", BankWriteState,BankWriteNum,adr,BankData); // BankWriteState controls the write or kill @@ -1577,107 +2042,34 @@ unsigned char WriteFlashBank(unsigned short adr) if ((BankWriteNum == 1) && (adr == 0x1555)) { if (BankData == 0xA0) BankWriteState = 3; else if (BankData == 0x80) BankWriteState = 4; - } - break; - // State three writes data - case 3: - if (BankWriteNum != CurrentBank) LoadRom(BankWriteNum); - PakRom[adr] = BankData; - BankDirty = 1; - break; - // State four continues kill sequence - case 4: - if ((BankWriteNum == 1) && (adr == 0x1555)) - if (BankData == 0xAA) BankWriteState = 5; - break; - case 5: - if ((BankWriteNum == 0) && (adr == 0x2AAA)) - if (BankData == 0x55) BankWriteState = 6; - break; - // State six kills (fills with 0xFF) bank sector - case 6: - if (BankData == 0x30) { - if (BankWriteNum != CurrentBank) LoadRom(BankWriteNum); - memset(PakRom + (adr & 0x3000), 0xFF, 0x1000); - BankDirty = 1; - } - break; - } - EnableBankWrite = 0; - return BankData; -} - -//---------------------------------------------------------------------- -// Seek sector in drive image -// cmdcode: -// b0 drive number -// b1 single sided LSN flag -// b2 eight bit transfer flag -// -//---------------------------------------------------------------------- -bool SeekSector(unsigned char cmdcode, unsigned int lsn) -{ - int drive = cmdcode & 1; // Drive number 0 or 1 - int sside = cmdcode & 2; // Single sided LSN flag - - int trk = lsn / gCocoDisk[drive].tracksectors; - int sec = lsn % gCocoDisk[drive].tracksectors; - - // The single sided LSN flag tells the controller that the lsn - // assumes the disk image is a single-sided floppy disk. If the - // disk is actually double-sided the LSN must be adjusted. - if (sside && gCocoDisk[drive].doublesided) { - DLOG_C("SeekSector sside %d %d\n",drive,lsn); - lsn = 2 * gCocoDisk[drive].tracksectors * trk + sec; - } - - // Allow seek to expand a writable file to a resonable limit - if (lsn > MAX_DSK_SECTORS) { - DLOG_C("SeekSector exceed max image %d %d\n",drive,lsn); - return false; - } - - // Seek to logical sector on drive. - LARGE_INTEGER pos{0}; - pos.QuadPart = lsn * gCocoDisk[drive].sectorsize + gCocoDisk[drive].headersize; - - if (!SetFilePointerEx(gCocoDisk[drive].hFile,pos,nullptr,FILE_BEGIN)) { - DLOG_C("SeekSector error %s\n",LastErrorTxt()); - return false; - } - return true; -} - -//---------------------------------------------------------------------- -// Read a sector from drive image and load reply -//---------------------------------------------------------------------- -bool ReadDrive(unsigned char cmdcode, unsigned int lsn) -{ - char buf[520]; - DWORD cnt = 0; - int drive = cmdcode & 1; - if (gCocoDisk[drive].hFile == nullptr) { - DLOG_C("ReadDrive %d not open\n"); - return false; - } - - if (!SeekSector(cmdcode,lsn)) { - return false; - } - - if (!ReadFile(gCocoDisk[drive].hFile,buf,gCocoDisk[drive].sectorsize,&cnt,nullptr)) { - DLOG_C("ReadDrive %d %s\n",drive,LastErrorTxt()); - return false; - } - - if (cnt != gCocoDisk[drive].sectorsize) { - DLOG_C("ReadDrive %d short read\n",drive); - return false; + } + break; + // State three writes data + case 3: + if (BankWriteNum != CurrentBank) LoadRom(BankWriteNum); + PakRom[adr] = BankData; + BankDirty = 1; + break; + // State four continues kill sequence + case 4: + if ((BankWriteNum == 1) && (adr == 0x1555)) + if (BankData == 0xAA) BankWriteState = 5; + break; + case 5: + if ((BankWriteNum == 0) && (adr == 0x2AAA)) + if (BankData == 0x55) BankWriteState = 6; + break; + // State six kills (fills with 0xFF) bank sector + case 6: + if (BankData == 0x30) { + if (BankWriteNum != CurrentBank) LoadRom(BankWriteNum); + memset(PakRom + (adr & 0x3000), 0xFF, 0x1000); + BankDirty = 1; + } + break; } - - snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,drive,lsn); - LoadReply(buf,cnt); - return true; + EnableBankWrite = 0; + return BankData; } //---------------------------------------------------------------------- @@ -1687,14 +2079,14 @@ bool ReadDrive(unsigned char cmdcode, unsigned int lsn) // b1 single sided flag // b2 eight bit transfer flag //---------------------------------------------------------------------- -void ReadSector() +void SDCReadSector() { unsigned int lsn = (IF.param1 << 16) + (IF.param2 << 8) + IF.param3; - DLOG_C("R%d\n",lsn); + //DLOG_C("R%d\n",lsn); IF.reply_mode = ((IF.cmdcode & 4) == 0) ? 0 : 1; // words : bytes - if (!ReadDrive(IF.cmdcode,lsn)) + if (!SDCReadDrive(IF.cmdcode,lsn)) IF.status = STA_FAIL | STA_READERROR; else IF.status = STA_NORMAL; @@ -1703,7 +2095,7 @@ void ReadSector() //---------------------------------------------------------------------- // Stream image data //---------------------------------------------------------------------- -void StreamImage() +void SDCStreamImage() { // If already streaming continue if (streaming) { @@ -1713,7 +2105,7 @@ void StreamImage() stream_cmdcode = IF.cmdcode; IF.reply_mode = ((IF.cmdcode & 4) == 0) ? 0 : 1; stream_lsn = (IF.param1 << 16) + (IF.param2 << 8) + IF.param3; - DLOG_C("StreamImage lsn %d\n",stream_lsn); + DLOG_C("SDCStreamImage lsn %d\n",stream_lsn); } // For now can only stream 512 byte sectors @@ -1722,13 +2114,13 @@ void StreamImage() gCocoDisk[drive].tracksectors = 9; if (stream_lsn > (gCocoDisk[drive].size/gCocoDisk[drive].sectorsize)) { - DLOG_C("StreamImage done\n"); + DLOG_C("SDCStreamImage done\n"); streaming = 0; return; } - if (!ReadDrive(stream_cmdcode,stream_lsn)) { - DLOG_C("StreamImage read error %s\n",LastErrorTxt()); + if (!SDCReadDrive(stream_cmdcode,stream_lsn)) { + DLOG_C("SDCStreamImage read error %s\n",LastErrorTxt()); IF.status = STA_FAIL; streaming = 0; return; @@ -1739,7 +2131,7 @@ void StreamImage() //---------------------------------------------------------------------- // Write logical sector //---------------------------------------------------------------------- -void WriteSector() +void SDCWriteSector() { DWORD cnt = 0; int drive = IF.cmdcode & 1; @@ -1751,18 +2143,18 @@ void WriteSector() return; } - if (!SeekSector(drive,lsn)) { + if (!SDCSeekSector(drive,lsn)) { IF.status = STA_FAIL; return; } if (!WriteFile(gCocoDisk[drive].hFile,IF.blkbuf, gCocoDisk[drive].sectorsize,&cnt,nullptr)) { - DLOG_C("WriteSector %d %s\n",drive,LastErrorTxt()); + DLOG_C("SDCWriteSector %d %s\n",drive,LastErrorTxt()); IF.status = STA_FAIL; return; } if (cnt != gCocoDisk[drive].sectorsize) { - DLOG_C("WriteSector %d short write\n",drive); + DLOG_C("SDCWriteSector %d short write\n",drive); IF.status = STA_FAIL; return; } @@ -1773,7 +2165,7 @@ void WriteSector() //---------------------------------------------------------------------- // Return sector count for mounted disk image //---------------------------------------------------------------------- -void GetSectorCount() { +void SDCGetSectorCount() { int drive = IF.cmdcode & 1; unsigned int numsec = gCocoDisk[drive].size/gCocoDisk[drive].sectorsize; @@ -1788,15 +2180,15 @@ void GetSectorCount() { //---------------------------------------------------------------------- // Return file record for mounted disk image //---------------------------------------------------------------------- -void GetMountedImageRec() +void SDCGetMountedImageRec() { int drive = IF.cmdcode & 1; - //DLOG_C("GetMountedImageRec %d %s\n",drive,gCocoDisk[drive].fullpath); + //DLOG_C("SDCGetMountedImageRec %d %s\n",drive,gCocoDisk[drive].fullpath); if (strlen(gCocoDisk[drive].fullpath) == 0) { IF.status = STA_FAIL; } else { IF.reply_mode = 0; - LoadReply(&gCocoDisk[drive].filerec,sizeof(FileRecord)); + SDCLoadReply(&gCocoDisk[drive].filerec,sizeof(FileRecord)); } } @@ -1822,113 +2214,99 @@ void SDCControl() } //---------------------------------------------------------------------- -// Load reply. Count is bytes, 512 max. +// Seek sector in drive image +// cmdcode: +// b0 drive number +// b1 single sided LSN flag +// b2 eight bit transfer flag +// //---------------------------------------------------------------------- -void LoadReply(const void *data, int count) +bool SDCSeekSector(unsigned char cmdcode, unsigned int lsn) { - if ((count < 2) | (count > 512)) { - DLOG_C("LoadReply bad count %d\n",count); - return; - } + int drive = cmdcode & 1; // Drive number 0 or 1 + int sside = cmdcode & 2; // Single sided LSN flag - memcpy(IF.blkbuf,data,count); + int trk = lsn / gCocoDisk[drive].tracksectors; + int sec = lsn % gCocoDisk[drive].tracksectors; - IF.bufptr = IF.blkbuf; - IF.bufcnt = count; - IF.half_sent = 0; + // The single sided LSN flag tells the controller that the lsn + // assumes the disk image is a single-sided floppy disk. If the + // disk is actually double-sided the LSN must be adjusted. + if (sside && gCocoDisk[drive].doublesided) { + DLOG_C("SDCSeekSector sside %d %d\n",drive,lsn); + lsn = 2 * gCocoDisk[drive].tracksectors * trk + sec; + } - // If port reads exceed the count zeros will be returned - IF.reply2 = 0; - IF.reply3 = 0; + // Allow seek to expand a writable file to a resonable limit + if (lsn > MAX_DSK_SECTORS) { + DLOG_C("SDCSeekSector exceed max image %d %d\n",drive,lsn); + return false; + } - return; + // Seek to logical sector on drive. + LARGE_INTEGER pos{0}; + pos.QuadPart = lsn * gCocoDisk[drive].sectorsize + gCocoDisk[drive].headersize; + + if (!SetFilePointerEx(gCocoDisk[drive].hFile,pos,nullptr,FILE_BEGIN)) { + DLOG_C("SDCSeekSector error %s\n",LastErrorTxt()); + return false; + } + return true; } //---------------------------------------------------------------------- -// A file path may use SDC format which does not use a dot to separate -// the name from the extension. User is free to use standard dot format. -// "FOODIR/FOO.DSK" = FOODIR/FOO.DSK -// "FOODIR/FOO DSK" = FOODIR/FOO.DSK -// "FOODIR/ALONGFOODSK" = FOODIR/ALONGFOO.DSK +// Read a sector from drive image and load reply //---------------------------------------------------------------------- -std::string FixSDCPath(const std::string& sdcpath) +bool SDCReadDrive(unsigned char cmdcode, unsigned int lsn) { - std::filesystem::path p(sdcpath); - - auto chop = [](std::string s) { - size_t space = s.find(' '); - if (space != std::string::npos) s.erase(space); - return s; - }; + char buf[520]; + DWORD cnt = 0; + int drive = cmdcode & 1; + if (gCocoDisk[drive].hFile == nullptr) { + DLOG_C("SDCReadDrive %d not open\n"); + return false; + } - std::string fname = p.filename().string(); - if (fname.length() == 11 && fname.find('.') == std::string::npos) { - auto nam = chop(fname.substr(0,8)); - auto ext = chop(fname.substr(8,3)); - fname = ext.empty() ? nam : nam + "." + ext; + if (!SDCSeekSector(cmdcode,lsn)) { + return false; } - std::filesystem::path out = p.parent_path() / fname; + if (!ReadFile(gCocoDisk[drive].hFile,buf,gCocoDisk[drive].sectorsize,&cnt,nullptr)) { + DLOG_C("SDCReadDrive %d %s\n",drive,LastErrorTxt()); + return false; + } - DLOG_C("FixSDCPath in %s out %s\n",sdcpath.c_str(),out.generic_string().c_str()); - return out.generic_string(); -} + if (cnt != gCocoDisk[drive].sectorsize) { + DLOG_C("SDCReadDrive %d short read\n",drive); + return false; + } -void FixSDCPath(char* path, const char* sdcpath) -{ - std::string fixed = FixSDCPath(std::string(sdcpath)); - strncpy(path, fixed.c_str(), MAX_PATH); - path[MAX_PATH - 1] = '\0'; + snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,drive,lsn); + SDCLoadReply(buf,cnt); + return true; } //---------------------------------------------------------------------- -// Load a file record with the file found by Find File +// Load reply. Count is bytes, 512 max. //---------------------------------------------------------------------- -bool LoadFoundFile(struct FileRecord * rec) +void SDCLoadReply(const void *data, int count) { - std::string fname = dFound.cFileName; - - // clear the file record - memset(rec,0,sizeof(rec)); - - // Ignore single dot or two dots if current dir is root - if (fname == ".") return false; - if (fname == ".." && gCurDir == "") return false; - - // Split found file name into name and extension - std::string nam, ext; - - // Two dots or no dot is a file without an extension - auto dot = fname.find_last_of('.'); - if (fname == ".." || dot == std::string::npos) { - nam = fname; - ext = ""; - } else { - nam = fname.substr(0, dot); - ext = fname.substr(dot + 1); + if ((count < 2) | (count > 512)) { + DLOG_C("SDCLoadReply bad count %d\n",count); + return; } - // Fill or trim as required to fit 8.3 - nam.resize(8,' '); - ext.resize(3,' '); - std::memcpy(rec->FR_name, nam.data(), 8); - std::memcpy(rec->FR_type, ext.data(), 3); - - if (dFound.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { - rec->FR_attrib |= ATTR_RDONLY; - } + memcpy(IF.blkbuf,data,count); - if (dFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - rec->FR_attrib |= ATTR_DIR; - } + IF.bufptr = IF.blkbuf; + IF.bufcnt = count; + IF.half_sent = 0; - // Filesize max 4G. Byte order is reversed so copy byte by byte - rec->FR_lolo_size = (dFound.nFileSizeLow) & 0xFF; - rec->FR_hilo_size = (dFound.nFileSizeLow >> 8) & 0xFF; - rec->FR_lohi_size = (dFound.nFileSizeLow >> 16) & 0xFF; - rec->FR_hihi_size = (dFound.nFileSizeLow >> 24) & 0xFF; + // If port reads exceed the count zeros will be returned + IF.reply2 = 0; + IF.reply3 = 0; - return true; + return; } //---------------------------------------------------------------------- @@ -1937,31 +2315,44 @@ bool LoadFoundFile(struct FileRecord * rec) // IF.param1 SDF image number of cylinders // IF.param2 SDC image number of sides //---------------------------------------------------------------------- -void MountNewDisk (int drive, const char * path, int raw) +void SDCMountNewDisk (int drive, const char * path, int raw) { - //DLOG_C("MountNewDisk %d %s %d\n",drive,path,raw); - + DLOG_C("SDCMountNewDisk %d %s %d\n",drive,path,raw); // limit drive to 0 or 1 drive &= 1; // Close and clear previous entry - CloseDrive(drive); - gCocoDisk[drive] = {}; + UnloadDisk(drive); + //CloseDrive(drive); + //gCocoDisk[drive] = {}; // Convert from SDC format char file[MAX_PATH]; - FixSDCPath(file,path); + FixFATPath(file,path); // Look for pre-existing file if (SearchFile(file)) { - OpenFound(drive,raw); + SDCOpenFound(drive,raw); return; } - OpenNew(drive,path,raw); + SDCOpenNew(drive,path,raw); return; } +//---------------------------------------------------------------------- +// Unload disk in drive +//---------------------------------------------------------------------- +void UnloadDisk(int drive) { + int d = drive & 1; + if ( gCocoDisk[d].hFile && + gCocoDisk[d].hFile != INVALID_HANDLE_VALUE) { + CloseHandle(gCocoDisk[d].hFile); + DLOG_C("UnloadDisk %d %s\n",drive,gCocoDisk[d].name); + } + gCocoDisk[d] = {}; +} + //---------------------------------------------------------------------- // Mount Disk. If image path starts with '/' load drive relative // to SDRoot, else load drive relative to the current directory. @@ -1970,19 +2361,18 @@ void MountNewDisk (int drive, const char * path, int raw) // the 'Next Disk' function. // TODO: Sets of type SOMEAPPn.DSK //---------------------------------------------------------------------- -void MountDisk (int drive, const char * path, int raw) +void SDCMountDisk (int drive, const char * path, int raw) { - DLOG_C("MountDisk %d %s %d\n",drive,path,raw); + DLOG_C("SDCMountDisk %d %s %d\n",drive,path,raw); drive &= 1; - // Close and clear previous entry - CloseDrive(drive); - gCocoDisk[drive] = {}; + // Unload previous dsk + UnloadDisk(drive); // Check for UNLOAD. Path will be an empty string. if (*path == '\0') { - DLOG_C("MountDisk unload %d %s\n",drive,path); + DLOG_C("SDCMountDisk unload %d %s\n",drive,path); IF.status = STA_NORMAL; if (drive == 0) BuildCartridgeMenu(); return; @@ -1990,7 +2380,7 @@ void MountDisk (int drive, const char * path, int raw) // Convert from SDC format char file[MAX_PATH]; - FixSDCPath(file,path); + FixFATPath(file,path); //TODO use new routine // Look for the file bool found = SearchFile(file); @@ -2011,31 +2401,46 @@ void MountDisk (int drive, const char * path, int raw) // Give up if (!found) { - DLOG_C("MountDisk not found '%s'\n",file); + DLOG_C("SDCMountDisk not found '%s'\n",file); IF.status = STA_FAIL | STA_NOTFOUND; return; } // Mount first image found - OpenFound(drive,raw); + SDCOpenFound(drive,raw); return; } //---------------------------------------------------------------------- // Mount Next Disk from found set //---------------------------------------------------------------------- -bool MountNext (int drive) +bool SDCMountNext (int drive) { - //FIXME: should loop back to first file at end of set - if (FindNextFile(hFind,&dFound) == 0) { - DLOG_C("MountNext no more\n"); - FindClose(hFind); - hFind = INVALID_HANDLE_VALUE; + + // Can't mount next unless disk set has been loaded + if (!gFileList.nextload_flag) { + DLOG_C("SDCMountNext disabled\n"); + return false; + } + + if (gFileList.files.size() <= 1) { + DLOG_C("SDCMountNext List empty or only one file\n"); return false; } + gFileList.cursor++; + + if (gFileList.cursor >= gFileList.files.size()) { + gFileList.cursor = 0; + DLOG_C("SDCMountNext reset cursor\n"); + } + + DLOG_C("SDCMountNext %d %d\n", + gFileList.cursor, gFileList.files.size()); + // Open next image found on the drive - OpenFound(drive,0); + SDCOpenFound(drive,0); + return true; } @@ -2045,18 +2450,18 @@ bool MountNext (int drive) // IF.Param1: $FF49 B number of cylinders for SDF // IF.Param2: $FF4A X.H number of sides for SDF image // -// OpenNew 0 'A.DSK' 0 40 0 NEW -// OpenNew 0 'B.DSK' 0 40 1 NEW++ one side -// OpenNew 0 'C.DSK' 0 40 2 NEW++ two sides +// SDCOpenNew 0 'A.DSK' 0 40 0 NEW +// SDCOpenNew 0 'B.DSK' 0 40 1 NEW++ one side +// SDCOpenNew 0 'C.DSK' 0 40 2 NEW++ two sides // // Currently new JVC files are 35 cylinders, one sided // Possibly future num cylinders could specify 40 or more // cylinders with num cyl controlling num sides // //---------------------------------------------------------------------- -void OpenNew( int drive, const char * path, int raw) +void SDCOpenNew( int drive, const char * path, int raw) { - DLOG_C("OpenNew %d '%s' %d %d %d\n", + DLOG_C("SDCOpenNew %d '%s' %d %d %d\n", drive,path,raw,IF.param1,IF.param2); // Number of sides controls file type @@ -2066,7 +2471,7 @@ void OpenNew( int drive, const char * path, int raw) break; case 1: //NEW+ case 2: //NEW++ - DLOG_C("OpenNew SDF file not supported\n"); + DLOG_C("SDCOpenNew SDF file not supported\n"); IF.status = STA_FAIL | STA_INVALID; return; } @@ -2075,12 +2480,12 @@ void OpenNew( int drive, const char * path, int raw) fs::path fqn = fs::path(gSDRoot) / gCurDir / path; if (fs::is_directory(fqn)) { - DLOG_C("OpenNew %s is a directory\n",path); + DLOG_C("SDCOpenNew %s is a directory\n",path); IF.status = STA_FAIL | STA_INVALID; return; } + UnloadDisk(drive); - CloseDrive(drive); strncpy(gCocoDisk[drive].fullpath,fqn.string().c_str(),MAX_PATH); // Open file for write @@ -2089,7 +2494,7 @@ void OpenNew( int drive, const char * path, int raw) nullptr,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,nullptr); if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { - DLOG_C("OpenNew fail %d file %s\n",drive,gCocoDisk[drive].fullpath); + DLOG_C("SDCOpenNew fail %d file %s\n",drive,gCocoDisk[drive].fullpath); DLOG_C("... %s\n",LastErrorTxt()); IF.status = STA_FAIL | STA_WIN_ERROR; return; @@ -2132,41 +2537,36 @@ void OpenNew( int drive, const char * path, int raw) //---------------------------------------------------------------------- // Open disk image found. Raw flag skips file type checks //---------------------------------------------------------------------- -void OpenFound (int drive,int raw) +void SDCOpenFound (int drive,int raw) { drive &= 1; int writeprotect = 0; - DLOG_C("OpenFound drive %d %s hfile %d\n", - drive, dFound.cFileName, gCocoDisk[drive].hFile); + if (gFileList.cursor >= gFileList.files.size()) { + DLOG_C("SDCOpenFound no file %d %d %d\n", + drive, gFileList.cursor, gFileList.files.size()); + return; + } - CloseDrive(drive); - *gCocoDisk[drive].name = '\0'; + UnloadDisk(drive); - namespace fs = std::filesystem; + std::string name = gFileList.files[gFileList.cursor].name.c_str(); + DLOG_C("SDCOpenFound drive %d %d %s\n", + drive, gFileList.cursor, name.c_str()); - // If found item is a directory - if (dFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + if (gFileList.files[gFileList.cursor].isDir) { - // Build: "/*.DSK" - fs::path dir = dFound.cFileName; - fs::path pattern = dir / "*.DSK"; - - std::string pat = pattern.string(); - FixDirSlashes(pat); - - if (InitiateDir(pat.c_str())) { - DLOG_C("OpenFound %s in directory\n", dFound.cFileName); - OpenFound(drive, 0); + std::string pattern = name + "/*.DSK"; + DLOG_C("SDCOpenFound %s directory initiate\n",pattern.c_str()); + if (SDCInitiateDir(pattern.c_str())) { + SDCOpenFound(drive, 0); if (drive == 0) BuildCartridgeMenu(); } return; } - // Found item is a file - fs::path fqn = fs::path(gSeaDir) / dFound.cFileName; - - std::string file = fqn.string(); + std::string file = gFileList.directory + "/" + name; + DLOG_C("SDCOpenFound %s\n", file.c_str()); gCocoDisk[drive].hFile = CreateFileA( file.c_str(), @@ -2179,7 +2579,7 @@ void OpenFound (int drive,int raw) ); if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { - DLOG_C("OpenFound fail %d file %s\n",drive,file.c_str()); + DLOG_C("SDCOpenFound fail %d file %s\n",drive,file.c_str()); DLOG_C("... %s\n",LastErrorTxt()); int ecode = GetLastError(); if (ecode == ERROR_SHARING_VIOLATION) { @@ -2190,15 +2590,18 @@ void OpenFound (int drive,int raw) return; } + // If more than one file in list enable nextload function + if (gFileList.files.size() > 1) gFileList.nextload_flag = true; + strncpy(gCocoDisk[drive].fullpath,file.c_str(),MAX_PATH); - strncpy(gCocoDisk[drive].name,dFound.cFileName,MAX_PATH); + strncpy(gCocoDisk[drive].name,name.c_str(),MAX_PATH); // Default sectorsize and sectors per track gCocoDisk[drive].sectorsize = 256; gCocoDisk[drive].tracksectors = 18; - // Grab filesize from found record - gCocoDisk[drive].size = dFound.nFileSizeLow; + // Grab filesize + gCocoDisk[drive].size = gFileList.files[gFileList.cursor].size; // Determine file type (RAW,DSK,JVC,VDK,SDF) if (raw) { @@ -2211,7 +2614,7 @@ void OpenFound (int drive,int raw) // Read a few bytes of the file to determine it's type unsigned char header[16]; if (ReadFile(gCocoDisk[drive].hFile,header,12,nullptr,nullptr) == 0) { - DLOG_C("OpenFound header read error\n"); + DLOG_C("SDCOpenFound header read error\n"); IF.status = STA_FAIL | STA_INVALID; return; } @@ -2223,14 +2626,14 @@ void OpenFound (int drive,int raw) if ((gCocoDisk[drive].size >= 219262) && // is this reasonable? (strncmp("SDF1",(const char *) header,4) == 0)) { gCocoDisk[drive].type = DTYPE_SDF; - DLOG_C("OpenFound SDF file unsupported\n"); + DLOG_C("SDCOpenFound SDF file unsupported\n"); IF.status = STA_FAIL | STA_INVALID; return; } unsigned int numsec; gCocoDisk[drive].headersize = gCocoDisk[drive].size & 255; - DLOG_C("OpenFound headersize %d\n",gCocoDisk[drive].headersize); + DLOG_C("SDCOpenFound headersize %d\n",gCocoDisk[drive].headersize); switch (gCocoDisk[drive].headersize) { // JVC optional header bytes case 4: // First Sector = header[3] 1 assumed @@ -2244,7 +2647,7 @@ void OpenFound (int drive,int raw) // JVC or OS9 disk if no header, side count per file size case 0: numsec = gCocoDisk[drive].size >> 8; - DLOG_C("OpenFound JVC/OS9 sectors %d\n",numsec); + DLOG_C("SDCOpenFound JVC/OS9 sectors %d\n",numsec); gCocoDisk[drive].doublesided = ((numsec > 720) && (numsec <= 2880)); gCocoDisk[drive].type = DTYPE_JVC; break; @@ -2256,7 +2659,7 @@ void OpenFound (int drive,int raw) break; // Unknown or unsupported default: // More than 4 byte header is not supported - DLOG_C("OpenFound unsuported image type %d %d\n", + DLOG_C("SDCOpenFound unsuported image type %d %d\n", drive, gCocoDisk[drive].headersize); IF.status = STA_FAIL | STA_INVALID; return; @@ -2264,8 +2667,8 @@ void OpenFound (int drive,int raw) } } - // Fill in image info. - LoadFoundFile(&gCocoDisk[drive].filerec); + gCocoDisk[drive].hf = gFileList.files[gFileList.cursor]; + gCocoDisk[drive].filerec = FileRecord(gCocoDisk[drive].hf); // Set readonly attrib per find status or file header if ((gCocoDisk[drive].filerec.FR_attrib & ATTR_RDONLY) != 0) { @@ -2281,7 +2684,7 @@ void OpenFound (int drive,int raw) gCocoDisk[drive].fullpath, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, nullptr,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,nullptr); if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { - DLOG_C("OpenFound reopen fail %d\n",drive); + DLOG_C("SDCOpenFound reopen fail %d\n",drive); DLOG_C("... %s\n",LastErrorTxt()); IF.status = STA_FAIL | STA_WIN_ERROR; return; @@ -2295,19 +2698,16 @@ void OpenFound (int drive,int raw) } //---------------------------------------------------------------------- -// Convert file name from SDC format and prepend current dir. +// Convert file name from FAT format and prepend current dir. //---------------------------------------------------------------------- -std::string GetFullPath(const std::string& file) +std::string SDCGetFullPath(const std::string& file) { - std::string out = gSDRoot + '/' + gCurDir + '/' + FixSDCPath(file); - DLOG_C("GetFullPath in %s out %s\n",file,out); - return (out); -} + std::string out = gSDRoot; + if (gCurDir.size() > 0) out += '/' + gCurDir; + out += '/' + FixFATPath(file); -void GetFullPath(char * path, const char * file) -{ - std::string full = GetFullPath(std::string(file)); - std::snprintf(path, MAX_PATH, "%s", full.c_str()); + DLOG_C("SDCGetFullPath in %s out %s\n",file.c_str(),out.c_str()); + return (out); } //---------------------------------------------------------------------- @@ -2315,18 +2715,19 @@ void GetFullPath(char * path, const char * file) // names contains two consecutive null terminated name strings // first is file or directory to rename, second is target name //---------------------------------------------------------------------- -void RenameFile(const char *names) +void SDCRenameFile(const char *names) { - char from[MAX_PATH]; - char target[MAX_PATH]; - - GetFullPath(from,names); - GetFullPath(target,1+strchr(names,'\0')); + const char* p = names; + std::string sfrom = p; + p += sfrom.size() + 1; + std::string starget = p; - DLOG_C("UpdateSD rename %s %s\n",from,target); + DLOG_C("SDCRenameFile %s to %s\n",sfrom.c_str(),starget.c_str()); + std::string from = SDCGetFullPath(sfrom); + std::string target = SDCGetFullPath(starget); - if (std::rename(from,target)) { - DLOG_C("RenameFile %s\n", strerror(errno)); + if (std::rename(from.c_str(),target.c_str())) { + DLOG_C("SDCRenameFile %s\n", strerror(errno)); IF.status = STA_FAIL | STA_WIN_ERROR; } else { IF.status = STA_NORMAL; @@ -2337,70 +2738,77 @@ void RenameFile(const char *names) //---------------------------------------------------------------------- // Delete disk or directory //---------------------------------------------------------------------- -void KillFile(const char *file) +void SDCKillFile(const char* file) { - char path[MAX_PATH]; - GetFullPath(path,file); - DLOG_C("KillFile delete %s\n",path); + namespace fs = std::filesystem; + + fs::path p = SDCGetFullPath(std::string(file)); + std::error_code ec; + + DLOG_C("SDCKillFile %s\n",file); - if (IsDirectory(path)) { - if (PathIsDirectoryEmpty(path)) { - if (RemoveDirectory(path)) { + // Does it exist? + if (!fs::exists(p, ec)) { + DLOG_C("SDCKillFile does not exist\n"); + IF.status = STA_FAIL | STA_NOTFOUND; + return; + } + // Regular file + if (fs::is_regular_file(p, ec)) { + if (fs::remove(p, ec)) { + IF.status = STA_NORMAL; + } else { + DLOG_C("SDCKillFile error: %s\n", ec.message().c_str()); + IF.status = STA_FAIL | STA_WPROTECT; + } + return; + } + // Directory + if (fs::is_directory(p, ec)) { + if (fs::is_empty(p, ec)) { + if (fs::remove(p, ec)) { IF.status = STA_NORMAL; } else { - DLOG_C("Deletefile %s\n", strerror(errno)); - IF.status = STA_FAIL | STA_NOTFOUND; + IF.status = STA_FAIL | STA_WPROTECT; + DLOG_C("SDCKillFile dir error: %s\n", ec.message().c_str()); } } else { + DLOG_C("SDCKillFile directory not empty\n"); IF.status = STA_FAIL | STA_NOTEMPTY; } - } else { - if (DeleteFile(path)) { - IF.status = STA_NORMAL; - } else { - DLOG_C("Deletefile %s\n", strerror(errno)); - IF.status = STA_FAIL | STA_NOTFOUND; - } + return; } - return; + // Not a file or directory (symlink, device, etc.) + IF.status = STA_FAIL; } //---------------------------------------------------------------------- // Create directory //---------------------------------------------------------------------- -void MakeDirectory(const char *name) + +void SDCMakeDirectory(const char* name) { - char path[MAX_PATH]; - GetFullPath(path,name); - DLOG_C("MakeDirectory %s\n",path); + namespace fs = std::filesystem; + + std::string s = SDCGetFullPath(std::string(name)); + fs::path p(s); - // Make sure directory is not in use - struct _stat file_stat; - int result = _stat(path,&file_stat); - if (result == 0) { + DLOG_C("SDCMakeDirectory %s\n", s.c_str()); + + std::error_code ec; + + if (fs::exists(p, ec)) { + DLOG_C("SDCMakeDirectory already exists\n"); IF.status = STA_FAIL | STA_INVALID; return; } - if (CreateDirectory(path,nullptr)) { + if (fs::create_directory(p, ec)) { IF.status = STA_NORMAL; } else { - DLOG_C("MakeDirectory %s\n", strerror(errno)); + DLOG_C("SDCMakeDirectory error: %s\n", ec.message().c_str()); IF.status = STA_FAIL | STA_WIN_ERROR; } - return; -} - -//---------------------------------------------------------------------- -// Close virtual disk -//---------------------------------------------------------------------- -void CloseDrive (int drive) -{ - drive &= 1; - if (gCocoDisk[drive].hFile && gCocoDisk[drive].hFile != INVALID_HANDLE_VALUE) { - CloseHandle(gCocoDisk[drive].hFile); - gCocoDisk[drive].hFile = INVALID_HANDLE_VALUE; - } } //---------------------------------------------------------------------- @@ -2408,7 +2816,7 @@ void CloseDrive (int drive) // This is complicated by the many ways a user can change the directory //---------------------------------------------------------------------- -void SetCurDir(const char* branch) +void SDCSetCurDir(const char* branch) { namespace fs = std::filesystem; @@ -2483,53 +2891,12 @@ void SetCurDir(const char* branch) } //---------------------------------------------------------------------- -// Start File search. Searches start from the root of the SDCard. +// SDCInitiateDir command. //---------------------------------------------------------------------- - -bool SearchFile(const char* pattern) +bool SDCInitiateDir(const char * path) { - // Close previous search - if (hFind != INVALID_HANDLE_VALUE) { - FindClose(hFind); - hFind = INVALID_HANDLE_VALUE; - } - - namespace fs = std::filesystem; - - // Build the full search path - fs::path base = fs::path(gSDRoot); - - if (*pattern == '/') { - base /= fs::path(pattern).relative_path(); - } else { - base /= fs::path(gCurDir); - base /= pattern; - } - - std::string search = base.string(); - FixDirSlashes(search); - - // Extract directory portion for later use - gSeaDir = base.parent_path().string(); - FixDirSlashes(gSeaDir); - - DLOG_C("SearchFile %s\n", search.c_str()); + DLOG_C("SDCInitiateDir '%s'\n",path); - hFind = FindFirstFileA(search.c_str(), &dFound); - if (hFind == INVALID_HANDLE_VALUE) { - DLOG_C("SearchFile fail\n"); - gSeaDir = ""; - return false; - } - - return true; -} - -//---------------------------------------------------------------------- -// InitiateDir command. -//---------------------------------------------------------------------- -bool InitiateDir(const char * path) -{ bool rc; // Append "*.*" if last char in path was '/'; int l = strlen(path); @@ -2549,69 +2916,3 @@ bool InitiateDir(const char * path) return false; } } - -//---------------------------------------------------------------------- -// Load directory page containing up to 16 file records that match -// the pattern used in SearchFile. Can be called multiple times until -// there are no more matches. -//---------------------------------------------------------------------- -void LoadDirPage() -{ - memset(DirPage,0,sizeof(DirPage)); - - if (hFind == INVALID_HANDLE_VALUE) { - DLOG_C("LoadDirPage Search fail\n"); - return; - } - - int cnt = 0; - while (cnt < 16) { - if (LoadFoundFile(&DirPage[cnt])) cnt++; - if (FindNextFile(hFind,&dFound) == 0) { - FindClose(hFind); - hFind = INVALID_HANDLE_VALUE; - break; - } - } -} - -//---------------------------------------------------------------------- -// Determine if path is a direcory -//---------------------------------------------------------------------- -inline bool IsDirectory(const std::string& path) -{ - std::error_code ec; - return std::filesystem::is_directory(path, ec) && !ec; -} - -//---------------------------------------------------------------------- -// Get most recent windows error text -//---------------------------------------------------------------------- -char * LastErrorTxt() { - static char msg[200]; - DWORD error_code = GetLastError(); - FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - msg, 200, nullptr ); - return msg; -} - -//------------------------------------------------------------ -// Convert path slashes. SDC expects forward slashes as path -// delimiter but Windows uses backslashes. This is the fix. -//------------------------------------------------------------ -void FixDirSlashes(std::string &dir) -{ - if (dir.empty()) return; - std::replace(dir.begin(), dir.end(), '\\', '/'); - if (dir.back() == '/') dir.pop_back(); -} -void FixDirSlashes(char *dir) -{ - if (!dir) return; - std::string tmp(dir); - FixDirSlashes(tmp); - strcpy(dir, tmp.c_str()); -} From 5f2d4b9739297b66129ac6c5b7545a9accaec99c Mon Sep 17 00:00:00 2001 From: ejaquay Date: Tue, 13 Jan 2026 15:09:13 -0500 Subject: [PATCH 05/11] SDC move file utilities to separate compile unit --- sdc/sdc.cpp | 880 +++++++++++++----------------------------------- sdc/sdc.h | 109 +++++- sdc/sdc.vcxproj | 4 +- 3 files changed, 345 insertions(+), 648 deletions(-) diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index b2603020..34057741 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -1,4 +1,4 @@ -#define USE_LOGGING +//#define USE_LOGGING //====================================================================== // SDC simulator. EJ Jaquay 2025 // @@ -73,7 +73,7 @@ // The becker port (drivewire) uses port $FF41 for status and port $FF42 // for data so these must be always allowed for becker.dll to work. // -// NOTE: Stock SDCDOS does not in support the becker ports, it expects +// NOTE: Stock SDCDOS does not support the becker ports, it expects // drivewire to be on the bitbanger ports. RGBDOS does, however, and will // still work with sdc.dll installed. // @@ -125,276 +125,94 @@ #include #include -#include -#include -#include -#include #include -#include -#include +#include #include #pragma warning(push) #pragma warning(disable:4091) #include #pragma warning(pop) -#include -#include -#include +#include + #include #include #include #include "../CartridgeMenu.h" #include #include + #include "sdc.h" +#include "fileutil.h" static ::vcc::devices::rtc::cloud9 cloud9_rtc; +//====================================================================== +// Globals +//====================================================================== -//========================================================================= -// Host file utilities. Most of these are general purpose -//========================================================================= - -// Get most recent windows error text -inline const char * LastErrorTxt(); -inline std::string LastErrorString(); - -// Convert backslashes to slashes in directory string / char -void FixDirSlashes(std::string &dir); -void FixDirSlashes(char *dir); - -// copy string into fixed-size char array and blank-pad -template -void copy_to_fixed_char(char (&dest)[N], const std::string& src); - -// Return copy of string with spaces trimmed from end of a string -inline std::string trim_right_spaces(const std::string &s); - -// Convert LFN to FAT filename parts, 8 char name, 3 char ext -void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn); - -// Convert FAT file name parts to LFN. Returns empty string if invalid LFN -std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]); - -// Return slash normalized directory part of a path -inline std::string GetDirectoryPart(const std::string& input); - -// Return filename part of a path -inline std::string GetFileNamePart(const std::string& input); - -// SDC interface often presents a path which does not use a dot to -// delimit name and extension: "FOODIR/FOO DSK" -> FOODIR/FOO.DSK -std::string FixFATPath(const std::string& sdcpath); -void FixFATPath(char* path, const char* sdcpath); - -// Determine if path is a direcory -inline bool IsDirectory(const std::string& path); - -//---------------------------------------------------------------------- -// Get most recent windows error text -//---------------------------------------------------------------------- -inline const char * LastErrorTxt() { - static char msg[200]; - DWORD error_code = GetLastError(); - FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - msg, sizeof(msg), nullptr ); - return msg; -} -inline std::string LastErrorString() { - return LastErrorTxt(); -} - -//------------------------------------------------------------------- -// SDC expects forward slashes as path delimiter, not backslashes -//------------------------------------------------------------------- -void FixDirSlashes(std::string &dir) -{ - if (dir.empty()) return; - std::replace(dir.begin(), dir.end(), '\\', '/'); - if (dir.back() == '/') dir.pop_back(); -} -void FixDirSlashes(char *dir) -{ - if (!dir) return; - std::string tmp(dir); - FixDirSlashes(tmp); - strcpy(dir, tmp.c_str()); -} - -//------------------------------------------------------------------- -// Copy string to fixed size char array (non terminated) -//------------------------------------------------------------------- -template -void copy_to_fixed_char(char (&dest)[N], const std::string& src) -{ - size_t i = 0; - for (; i < src.size() && i < N; ++i) - dest[i] = src[i]; - for (; i < N; ++i) - dest[i] = ' '; -} - -//------------------------------------------------------------------- -// Return copy of string with spaces trimmed from end of a string -//------------------------------------------------------------------- -inline std::string trim_right_spaces(const std::string& s) -{ - size_t end = s.find_last_not_of(' '); - if (end == std::string::npos) - return {}; - return s.substr(0, end + 1); -} - -//------------------------------------------------------------------- -// Convert LFN to FAT filename parts, 8 char name, 3 char ext -// A LNF file is less than 4GB and has a short (8.3) name. -//------------------------------------------------------------------- -void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn) -{ - // Special case: parent directory - if (lfn == "..") { - copy_to_fixed_char(name, ".."); - std::fill(ext, ext + 3, ' '); - return; - } - - size_t dot = lfn.find('.'); - std::string base, extension; - - if (dot == std::string::npos) { - base = lfn; - } else { - base = lfn.substr(0, dot); - extension = lfn.substr(dot + 1); - } - - copy_to_fixed_char(name, base); - copy_to_fixed_char(ext, extension); -} - -//------------------------------------------------------------------- -// Convert FAT filename parts to LFN. Returns empty string if invalid -//------------------------------------------------------------------- -std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]) -{ - std::string base(name, 8); - std::string extension(ext, 3); - - base = trim_right_spaces(base); - extension = trim_right_spaces(extension); - - if (base == ".." && extension.empty()) - return ".."; - - std::string lfn = base; - - if (!extension.empty()) - lfn += "." + extension; - - if (lfn.empty()) - return {}; - - if (!PathIsLFNFileSpecA(lfn.c_str())) - return {}; - - return lfn; -} +static HINSTANCE gModuleInstance; // Dll handle +static int idle_ctr = 0; // Idle Status counter -//------------------------------------------------------------------- -// Return slash normalized directory part of a path -//------------------------------------------------------------------- -inline std::string GetDirectoryPart(const std::string& input) -{ - std::filesystem::path p(input); - std::string out = p.parent_path().string(); - FixDirSlashes(out); - return out; -} +// Callback pointers +static void* gCallbackContext = nullptr; +static PakAssertInteruptHostCallback AssertIntCallback = nullptr; +static PakAppendCartridgeMenuHostCallback CartMenuCallback = nullptr; +static char IniFile[MAX_PATH] = {}; // Vcc ini file name -//------------------------------------------------------------------- -// Return filename part of a path -//------------------------------------------------------------------- -inline std::string GetFileNamePart(const std::string& input) -{ - std::filesystem::path p(input); - return p.filename().string(); -} +static HWND gVccWindow = nullptr; +static HWND hConfigureDlg = nullptr; +static HWND hSDCardBox = nullptr; +static HWND hStartupBank = nullptr; -//------------------------------------------------------------------- -// Convert string containing possible FAT name and extension to an -// LFN string. Returns empty string if invalid LFN -//------------------------------------------------------------------- -std::string NormalizeInputToLFN(const std::string& s) -{ - if (s.empty()) return {}; - if (s.size() > 11) return {}; - if (s == "..") return ".."; - - // LFN candidate - if (s.find('.') != std::string::npos) { - if (!PathIsLFNFileSpecA(s.c_str())) - return {}; // invalid - return s; - } - - // SFN candidate - char name[8]; - char ext[3]; - sfn_from_lfn(name,ext,s); - return lfn_from_sfn(name,ext); -} +static int ClockEnable = 0; +static char SDC_Status[16] = {}; +static char PakRom[0x4000] = {}; +static unsigned char CurrentBank = 0xff; +static unsigned char EnableBankWrite = 0; +static unsigned char BankWriteState = 0; -//------------------------------------------------------------------- -// Determine if path is a direcory -//------------------------------------------------------------------- -inline bool IsDirectory(const std::string& path) -{ - std::error_code ec; - return std::filesystem::is_directory(path, ec) && !ec; -} +static std::string gSDRoot {}; // SD card root directory +static std::string gCurDir {}; // Current directory relative to root -//---------------------------------------------------------------------- -// A file path may use 11 char FAT format which does not use a separater -// between name and extension. User is free to use standard dot format. -// "FOODIR/FOO.DSK" = FOODIR/FOO.DSK -// "FOODIR/FOO DSK" = FOODIR/FOO.DSK -// "FOODIR/ALONGFOODSK" = FOODIR/ALONGFOO.DSK -//---------------------------------------------------------------------- -std::string FixFATPath(const std::string& sdcpath) -{ - std::filesystem::path p(sdcpath); +// SDC CoCo Interface +static FileList gFileList {}; +static struct FileRecord gDirPage[16] {}; +static CocoDisk gCocoDisk[2] {}; +static Interface IFace {}; - auto chop = [](std::string s) { - size_t space = s.find(' '); - if (space != std::string::npos) s.erase(space); - return s; - }; +// Flash banks +static char FlashFile[8][MAX_PATH]; +static FILE *h_RomFile = nullptr; +static unsigned char StartupBank = 0; +static unsigned char BankWriteNum = 0; +static unsigned char BankDirty = 0; +static unsigned char BankData = 0; - std::string fname = p.filename().string(); - if (fname.length() == 11 && fname.find('.') == std::string::npos) { - auto nam = chop(fname.substr(0,8)); - auto ext = chop(fname.substr(8,3)); - fname = ext.empty() ? nam : nam + "." + ext; - } +// Streaming control +static int streaming; +static unsigned char stream_cmdcode; +static unsigned int stream_lsn; - std::filesystem::path out = p.parent_path() / fname; +// Floppy I/O +static char FlopDrive = 0; +static char FlopData = 0; +static char FlopTrack = 0; +static char FlopSector = 0; +static char FlopStatus = 0; +static DWORD FlopWrCnt = 0; +static DWORD FlopRdCnt = 0; +static char FlopWrBuf[256]; +static char FlopRdBuf[256]; - DLOG_C("FixFATPath in %s out %s\n",sdcpath.c_str(),out.generic_string().c_str()); - return out.generic_string(); -} +static int EDBOXES[8] = {ID_TEXT0,ID_TEXT1,ID_TEXT2,ID_TEXT3, + ID_TEXT4,ID_TEXT5,ID_TEXT6,ID_TEXT7}; +static int UPDBTNS[8] = {ID_UPDATE0,ID_UPDATE1,ID_UPDATE2,ID_UPDATE3, + ID_UPDATE4,ID_UPDATE5,ID_UPDATE6,ID_UPDATE7}; -void FixFATPath(char* path, const char* sdcpath) -{ - std::string fixed = FixFATPath(std::string(sdcpath)); - strncpy(path, fixed.c_str(), MAX_PATH); - path[MAX_PATH - 1] = '\0'; -} +static char ROMPath[MAX_PATH]; //====================================================================== -// SDC specific funtions +// Compile unit functions //====================================================================== LRESULT CALLBACK SDC_Configure(HWND, UINT, WPARAM, LPARAM); @@ -410,10 +228,10 @@ void InitSDC(); void LoadRom(unsigned char); void ParseStartup(); bool SearchFile(const char *); +void UnloadDisk(int); +void GetFileList(const std::string&); +void SortFileList(); -//====================================================================== -// SDC interface functions -//====================================================================== void SDCReadSector(); void SDCStreamImage(); void SDCWriteSector(); @@ -457,59 +275,33 @@ bool SDCReadDrive(unsigned char,unsigned int); bool SDCLoadNextDirPage(); std::string SDCGetFullPath(const std::string&); -//====================================================================== - //========================================================================= -// Host directory handling +// Host file handling //========================================================================= -// Host Directory globals -static std::string gSDRoot {}; // SD card root directory -static std::string gCurDir {}; // Current directory relative to root - -//------------------------------------------------------------------- -// HostFile contains a file name, it's size, and directory and readonly flags -// gFileList is a list of HostFiles found within a particular host directory -// using a search pattern. The pattern may include a directory specifier. -// Host file name, size, and attributes are kept in HostFile structs. -//------------------------------------------------------------------- - -struct HostFile { - std::string name; // LFN 8.3 name - DWORD size; // < 4GB - bool isDir; // is a directory - bool isRdOnly; // is read only - HostFile() {} - // Construct a HostFile from an argument list - HostFile(const char* cName, DWORD nSize, bool isDir, bool isRdOnly) : - name(cName), size(nSize), isDir(isDir), isRdOnly(isRdOnly) {} - // Construct a HostFile from a WIN32_FIND_DATAA record. - HostFile(WIN32_FIND_DATAA fd) : - name(fd.cFileName), size(fd.nFileSizeLow), - isDir((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0), - isRdOnly((fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {} -}; - -struct FileList { - std::vector files; // files - std::string directory; // directory files are in - size_t cursor = 0; // current file curspr - bool nextload_flag = false; // enable next disk loading - // append a host file to the list - void append(const HostFile& hf) { files.push_back(hf); } -}; - -static FileList gFileList; - -void GetFileList(const std::string& pattern); -void SortFileList(); +// Construct FileRecord from a HostFile object. +FileRecord::FileRecord(const HostFile& hf) noexcept +{ + memset(this, 0, sizeof(*this)); + // name and type + sfn_from_lfn(FR_name, FR_type, hf.name); + // Attributes + FR_attrib = 0; + if (hf.isDir) FR_attrib |= 0x10; + if (hf.isRdOnly) FR_attrib |= 0x01; + // Size -> 4 reversed endian bytes + DWORD s = hf.size; + FR_hihi_size = (s >> 24) & 0xFF; + FR_lohi_size = (s >> 16) & 0xFF; + FR_hilo_size = (s >> 8) & 0xFF; + FR_lolo_size = (s >> 0) & 0xFF; +} //------------------------------------------------------------------- // Get list of files matching pattern starting from CurDir or SDRoot //------------------------------------------------------------------- void GetFileList(const std::string& pattern) { - DLOG_C("GetFileList %s\n",pattern.c_str()); // Init search results @@ -577,90 +369,14 @@ void SortFileList() gFileList.cursor = 0; } -//========================================================================= -// FileRecord is 16 packed bytes file name, type, attrib, and size that can -// be passed directly via the SDC interface. gDirPage is a 256 byte block -// containing up to 16 packed FileRecords. -//========================================================================= -#pragma pack(push, 1) -struct FileRecord -{ - char FR_name[8]; - char FR_type[3]; - char FR_attrib; - char FR_hihi_size; - char FR_lohi_size; - char FR_hilo_size; - char FR_lolo_size; - FileRecord() noexcept {}; - // Construct FileRecord from a HostFile object. - FileRecord(const HostFile& hf) noexcept { - memset(this, 0, sizeof(*this)); - // name and type - sfn_from_lfn(FR_name, FR_type, hf.name); - // Attributes - FR_attrib = 0; - if (hf.isDir) FR_attrib |= 0x10; - if (hf.isRdOnly) FR_attrib |= 0x01; - // Size -> 4 reversed endian bytes - DWORD s = hf.size; - FR_hihi_size = (s >> 24) & 0xFF; - FR_lohi_size = (s >> 16) & 0xFF; - FR_hilo_size = (s >> 8) & 0xFF; - FR_lolo_size = (s >> 0) & 0xFF; - } -}; -static struct FileRecord gDirPage[16] = {}; -#pragma pack(pop) - -//========================================================================= -// CocoDisk contains info about mounted disk files 0 and 1 -//========================================================================= - -enum DiskType { - DTYPE_RAW = 0, - DTYPE_DSK, - DTYPE_JVC, - DTYPE_VDK, - DTYPE_SDF -}; - -// Mounted image data -struct CocoDisk { - HANDLE hFile; // file handle - unsigned int size; // number of bytes total - unsigned int headersize; // number of bytes in header - DWORD sectorsize; // number of bytes per sector - DWORD tracksectors; // number of sectors per side - DiskType type; // Disk image type (RAW,DSK,JVC,VDK,SDF) - char doublesided; // false:1 side, true:2 sides - char name[MAX_PATH]; // name of file (8.3) - char fullpath[MAX_PATH]; // full file path - struct HostFile hf{"",0,false,false}; //name,size,isDir,isRdOnly - struct FileRecord filerec; - // Constructor - CocoDisk() noexcept - : hFile(INVALID_HANDLE_VALUE), - size(0), - headersize(0), - sectorsize(256), - tracksectors(18), - type(DiskType::DTYPE_RAW), - doublesided(1) { - name[0] = '\0'; - fullpath[0] = '\0'; - }; -}; -CocoDisk gCocoDisk[2] {}; - //---------------------------------------------------------------------- // Host File search. //---------------------------------------------------------------------- - bool SearchFile(const char* pattern) { std::string s = pattern; GetFileList(s); + DLOG_C("SearchFile found %d pat %s\n",gFileList.files.size(),s.c_str()); // Update menu to show next disk status @@ -669,51 +385,9 @@ bool SearchFile(const char* pattern) return (gFileList.files.size() > 0); } -//====================================================================== -// Static Globals -//====================================================================== - -static HINSTANCE gModuleInstance; // Dll handle -static int idle_ctr = 0; // Idle Status counter - -// Callback pointers -static void* gCallbackContext = nullptr; -static PakAssertInteruptHostCallback AssertIntCallback = nullptr; -static PakAppendCartridgeMenuHostCallback CartMenuCallback = nullptr; -static char IniFile[MAX_PATH] = {}; // Vcc ini file name - -// Data used elsewhere -static HWND gVccWindow = nullptr; -static HWND hConfigureDlg = nullptr; -static int ClockEnable = 0; -static char SDC_Status[16] = {}; -static char PakRom[0x4000] = {}; -static unsigned char CurrentBank = 0xff; -static unsigned char EnableBankWrite = 0; -static unsigned char BankWriteState = 0; - //====================================================================== // DLL interface //====================================================================== - -//---------------------------------------------------------------------- -// Functions referenced by DLL interface -//---------------------------------------------------------------------- - -void LoadConfig(); -void BuildCartridgeMenu(); -//void CloseDrive(int); -void UnloadDisk(int); -void SDCWrite(unsigned char,unsigned char); -unsigned char SDCRead(unsigned char port); -LRESULT CALLBACK SDC_Configure(HWND, UINT, WPARAM, LPARAM); -bool SDCMountNext(int); -void InitSDC(); -unsigned char SDCWriteFlashBank(unsigned short); - -//---------------------------------------------------------------------- -// DLL exports -//---------------------------------------------------------------------- extern "C" { // PakInitialize gets called first, sets up dynamic menues and captures callbacks @@ -762,8 +436,6 @@ extern "C" hConfigureDlg = nullptr; UnloadDisk(0); UnloadDisk(1); - //CloseDrive(0); - //CloseDrive(1); } // Write to port @@ -836,10 +508,6 @@ extern "C" } } -//---------------------------------------------------------------------- -// DLL main -//---------------------------------------------------------------------- - BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID rsvd) { if (reason == DLL_PROCESS_ATTACH) { @@ -850,86 +518,6 @@ BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID rsvd) return TRUE; } -//====================================================================== -// Unsorted Globals -//====================================================================== - -// SDC CoCo Interface -struct Interface -{ - int sdclatch; - unsigned char cmdcode; - unsigned char status; - unsigned char reply1; - unsigned char reply2; - unsigned char reply3; - unsigned char param1; - unsigned char param2; - unsigned char param3; - unsigned char reply_mode; // 0=words, 1=bytes - unsigned char reply_status; - unsigned char half_sent; - int bufcnt; - char *bufptr; - char blkbuf[600]; -}; -static Interface IF = {}; - -//static std::string gSeaDir {}; // Last directory searched - -// Flash banks -static char FlashFile[8][MAX_PATH]; -static FILE *h_RomFile = nullptr; -static unsigned char StartupBank = 0; -static unsigned char BankWriteNum = 0; -static unsigned char BankDirty = 0; -static unsigned char BankData = 0; -// Following are in DLL section -//static unsigned char CurrentBank = 0xff; -//static unsigned char EnableBankWrite = 0; -//static unsigned char BankWriteState = 0; - -// Clock enable IDC_CLOCK -//static int ClockEnable; - -// Windows file lookup handle and data -//static HANDLE hFind = INVALID_HANDLE_VALUE; -//static WIN32_FIND_DATAA dFound; - -// Window handles -static HWND hSDCardBox = nullptr; -static HWND hStartupBank = nullptr; -// Following two are in DLL section -//static HWND hConfigureDlg = nullptr; -//static HWND gVccWindow = nullptr; - -// Streaming control -static int streaming; -static unsigned char stream_cmdcode; -static unsigned int stream_lsn; - -// Status for VCC status line -//static char SDC_Status[16] = {}; - -// Floppy I/O -static char FlopDrive = 0; -static char FlopData = 0; -static char FlopTrack = 0; -static char FlopSector = 0; -static char FlopStatus = 0; -static DWORD FlopWrCnt = 0; -static DWORD FlopRdCnt = 0; -static char FlopWrBuf[256]; -static char FlopRdBuf[256]; - -static int EDBOXES[8] = {ID_TEXT0,ID_TEXT1,ID_TEXT2,ID_TEXT3, - ID_TEXT4,ID_TEXT5,ID_TEXT6,ID_TEXT7}; -static int UPDBTNS[8] = {ID_UPDATE0,ID_UPDATE1,ID_UPDATE2,ID_UPDATE3, - ID_UPDATE4,ID_UPDATE5,ID_UPDATE6,ID_UPDATE7}; - -static char MPIPath[MAX_PATH]; - - //===================================================================== // User interface //====================================================================== @@ -1052,8 +640,9 @@ void LoadConfig() { char tmp[MAX_PATH]; + // FIXME should be "SDCRomPath" and saved when changed GetPrivateProfileString - ("DefaultPaths", "MPIPath", "", MPIPath, MAX_PATH, IniFile); + ("DefaultPaths", "MPIPath", "", ROMPath, MAX_PATH, IniFile); GetPrivateProfileString ("SDC", "SDCardPath", "", tmp, MAX_PATH, IniFile); @@ -1061,7 +650,7 @@ void LoadConfig() gSDRoot = tmp; FixDirSlashes(gSDRoot); - DLOG_C("LoadConfig MPIPath %s\n",MPIPath); + DLOG_C("LoadConfig ROMPath %s\n",ROMPath); DLOG_C("LoadConfig gSDRoot %s\n",gSDRoot); if (!IsDirectory(gSDRoot)) { @@ -1170,7 +759,7 @@ void UpdateFlashItem(int index) dlg.setDefExt("rom"); dlg.setFilter("Rom File\0*.rom\0All Files\0*.*\0\0"); dlg.setTitle(title); - dlg.setInitialDir(MPIPath); // FIXME should be SDC rom path + dlg.setInitialDir(ROMPath); if (dlg.show(0,hConfigureDlg)) { dlg.getupath(filename,MAX_PATH); // cvt to unix style strncpy(FlashFile[index],filename,MAX_PATH); @@ -1235,9 +824,9 @@ void SelectCardBox() void InitSDC() { - DLOG_C("\nInitSDC\n"); #ifdef USE_LOGGING - MoveWindow(GetConsoleWindow(),0,0,300,800,TRUE); + DLOG_C("\nInitSDC\n"); + MoveWindow(GetConsoleWindow(),0,0,400,800,TRUE); #endif // Make sure drives are unloaded @@ -1259,7 +848,7 @@ void InitSDC() ParseStartup(); // init the interface - IF = {}; + IFace = {}; return; } @@ -1387,47 +976,46 @@ void ParseStartup() fclose(su); } - //===================================================================== // SDC Interface functions //===================================================================== //---------------------------------------------------------------------- // Write port. If a command needs a data block to complete it -// will put a count (256 or 512) in IF.bufcnt. +// will put a count (256 or 512) in IFace.bufcnt. //---------------------------------------------------------------------- void SDCWrite(unsigned char data,unsigned char port) { if (port < 0x40 || port > 0x4F) return; - if (IF.sdclatch) { + if (IFace.sdclatch) { switch (port) { - // Control Latch + // Toggle Control Latch case 0x40: - if (IF.sdclatch) IF = {}; + if (IFace.sdclatch) IFace = {}; break; // Command registor case 0x48: - IF.cmdcode = data; + IFace.cmdcode = data; SDCCommand(); break; // Command param #1 case 0x49: - IF.param1 = data; + IFace.param1 = data; break; // Command param #2 or block data receive case 0x4A: - if (IF.bufcnt > 0) + if (IFace.bufcnt > 0) SDCBlockReceive(data); else - IF.param2 = data; + IFace.param2 = data; break; // Command param #3 or block data receive case 0x4B: - if (IF.bufcnt > 0) + if (IFace.bufcnt > 0) SDCBlockReceive(data); else - IF.param3 = data; + IFace.param3 = data; break; // Unhandled default: @@ -1441,10 +1029,10 @@ void SDCWrite(unsigned char data,unsigned char port) // Mask out halt, density, precomp, and motor switch (data & 0x43) { // 0b01000111 case 0: - IF.sdclatch = false; + IFace.sdclatch = false; break; case 0x43: - IF.sdclatch = true; + IFace.sdclatch = true; break; case 0b00000001: FlopDrive = 0; @@ -1503,33 +1091,33 @@ unsigned char SDCRead(unsigned char port) { unsigned char rpy = 0; - if (IF.sdclatch) { + if (IFace.sdclatch) { switch (port) { case 0x48: - if (IF.bufcnt > 0) { - rpy = (IF.reply_status != 0) ? IF.reply_status:STA_BUSY|STA_READY; + if (IFace.bufcnt > 0) { + rpy = (IFace.reply_status != 0) ? IFace.reply_status:STA_BUSY|STA_READY; } else { - rpy = IF.status; + rpy = IFace.status; } break; // Reply data 1 case 0x49: - rpy = IF.reply1; + rpy = IFace.reply1; break; // Reply data 2 or block reply case 0x4A: - if (IF.bufcnt > 0) { + if (IFace.bufcnt > 0) { rpy = SDCPickReplyByte(port); } else { - rpy = IF.reply2; + rpy = IFace.reply2; } break; // Reply data 3 or block reply case 0x4B: - if (IF.bufcnt > 0) { + if (IFace.bufcnt > 0) { rpy = SDCPickReplyByte(port); } else { - rpy = IF.reply3; + rpy = IFace.reply3; } break; default: @@ -1582,7 +1170,7 @@ unsigned char SDCRead(unsigned char port) //---------------------------------------------------------------------- void SDCCommand() { - switch (IF.cmdcode & 0xF0) { + switch (IFace.cmdcode & 0xF0) { // Read sector case 0x80: SDCReadSector(); @@ -1602,10 +1190,10 @@ void SDCCommand() // Next two are block receive commands case 0xA0: case 0xE0: - IF.status = STA_READY | STA_BUSY; - IF.bufptr = IF.blkbuf; - IF.bufcnt = 256; - IF.half_sent = 0; + IFace.status = STA_READY | STA_BUSY; + IFace.bufptr = IFace.blkbuf; + IFace.bufcnt = 256; + IFace.half_sent = 0; break; } return; @@ -1660,8 +1248,8 @@ void SDCFloppyCommand(unsigned char data) //---------------------------------------------------------------------- void SDCGetDriveInfo() { - int drive = IF.cmdcode & 1; - switch (IF.param1) { + int drive = IFace.cmdcode & 1; + switch (IFace.param1) { case 0x49: // 'I' - return drive information in block SDCGetMountedImageRec(); @@ -1677,7 +1265,7 @@ void SDCGetDriveInfo() case 0x3E: // '>' Get directory page SDCLoadNextDirPage(); - IF.reply_mode=0; + IFace.reply_mode=0; SDCLoadReply(gDirPage,256); break; case 0x2B: @@ -1686,8 +1274,8 @@ void SDCGetDriveInfo() break; case 0x56: // 'V' Get BCD firmware version number in p2, p3. - IF.reply2 = 0x00; - IF.reply3 = 0x01; + IFace.reply2 = 0x00; + IFace.reply3 = 0x01; break; } } @@ -1697,37 +1285,37 @@ void SDCGetDriveInfo() //---------------------------------------------------------------------- void SDCUpdateSD() { - switch (IF.blkbuf[0]) { + switch (IFace.blkbuf[0]) { case 0x4D: //M - SDCMountDisk(IF.cmdcode&1,&IF.blkbuf[2],0); + SDCMountDisk(IFace.cmdcode&1,&IFace.blkbuf[2],0); break; case 0x6D: //m - SDCMountDisk(IF.cmdcode&1,&IF.blkbuf[2],1); + SDCMountDisk(IFace.cmdcode&1,&IFace.blkbuf[2],1); break; case 0x4E: //N - SDCMountNewDisk(IF.cmdcode&1,&IF.blkbuf[2],0); + SDCMountNewDisk(IFace.cmdcode&1,&IFace.blkbuf[2],0); break; case 0x6E: //n - SDCMountNewDisk(IF.cmdcode&1,&IF.blkbuf[2],1); + SDCMountNewDisk(IFace.cmdcode&1,&IFace.blkbuf[2],1); break; case 0x44: //D - SDCSetCurDir(&IF.blkbuf[2]); + SDCSetCurDir(&IFace.blkbuf[2]); break; case 0x4C: //L - SDCInitiateDir(&IF.blkbuf[2]); + SDCInitiateDir(&IFace.blkbuf[2]); break; case 0x4B: //K - SDCMakeDirectory(&IF.blkbuf[2]); + SDCMakeDirectory(&IFace.blkbuf[2]); break; case 0x52: //R - SDCRenameFile(&IF.blkbuf[2]); + SDCRenameFile(&IFace.blkbuf[2]); break; case 0x58: //X - SDCKillFile(&IF.blkbuf[2]); + SDCKillFile(&IFace.blkbuf[2]); break; default: - DLOG_C("SDCUpdateSD %02x not Supported\n",IF.blkbuf[0]); - IF.status = STA_FAIL; + DLOG_C("SDCUpdateSD %02x not Supported\n",IFace.blkbuf[0]); + IFace.status = STA_FAIL; break; } } @@ -1905,29 +1493,29 @@ unsigned char SDCPickReplyByte(unsigned char port) unsigned char rpy = 0; // Byte mode bytes come on port 0x4B - if (IF.reply_mode == 1) { - if (IF.bufcnt > 0) { - rpy = *IF.bufptr++; - IF.bufcnt--; + if (IFace.reply_mode == 1) { + if (IFace.bufcnt > 0) { + rpy = *IFace.bufptr++; + IFace.bufcnt--; } // Word mode bytes come on port 0x4A and 0x4B } else { if (port == 0x4A) { - rpy = IF.bufptr[0]; + rpy = IFace.bufptr[0]; } else { - rpy = IF.bufptr[1]; + rpy = IFace.bufptr[1]; } - if (IF.half_sent) { - IF.bufcnt -= 2; - IF.bufptr += 2; - IF.half_sent = 0; + if (IFace.half_sent) { + IFace.bufcnt -= 2; + IFace.bufptr += 2; + IFace.half_sent = 0; } else { - IF.half_sent = 1; + IFace.half_sent = 1; } } // Keep stream going until stopped - if ((IF.bufcnt < 1) && streaming) SDCStreamImage(); + if ((IFace.bufcnt < 1) && streaming) SDCStreamImage(); return rpy; } @@ -1937,14 +1525,14 @@ unsigned char SDCPickReplyByte(unsigned char port) //---------------------------------------------------------------------- void SDCBlockReceive(unsigned char byte) { - if (IF.bufcnt > 0) { - IF.bufcnt--; - *IF.bufptr++ = byte; + if (IFace.bufcnt > 0) { + IFace.bufcnt--; + *IFace.bufptr++ = byte; } // Done receiving block - if (IF.bufcnt < 1) { - switch (IF.cmdcode & 0xF0) { + if (IFace.bufcnt < 1) { + switch (IFace.cmdcode & 0xF0) { case 0xA0: SDCWriteSector(); break; @@ -1952,8 +1540,8 @@ void SDCBlockReceive(unsigned char byte) SDCUpdateSD(); break; default: - DLOG_C("SDCBlockReceive invalid cmd %d\n",IF.cmdcode); - IF.status = STA_FAIL; + DLOG_C("SDCBlockReceive invalid cmd %d\n",IFace.cmdcode); + IFace.status = STA_FAIL; break; } } @@ -1979,11 +1567,11 @@ void SDCGetDirectoryLeaf() size_t copyLen = std::min(8,leafName.size()); memcpy(leaf, leafName.data(), copyLen); } else { - IF.reply_status = STA_FAIL | STA_NOTFOUND; + IFace.reply_status = STA_FAIL | STA_NOTFOUND; } DLOG_C("SDCGetDirectoryLeaf CurDir '%s' leaf '%s' \n", gCurDir.c_str(),leaf); - IF.reply_mode = 0; + IFace.reply_mode = 0; SDCLoadReply(leaf, sizeof(leaf)); } @@ -2081,15 +1669,15 @@ unsigned char SDCWriteFlashBank(unsigned short adr) //---------------------------------------------------------------------- void SDCReadSector() { - unsigned int lsn = (IF.param1 << 16) + (IF.param2 << 8) + IF.param3; + unsigned int lsn = (IFace.param1 << 16) + (IFace.param2 << 8) + IFace.param3; //DLOG_C("R%d\n",lsn); - IF.reply_mode = ((IF.cmdcode & 4) == 0) ? 0 : 1; // words : bytes - if (!SDCReadDrive(IF.cmdcode,lsn)) - IF.status = STA_FAIL | STA_READERROR; + IFace.reply_mode = ((IFace.cmdcode & 4) == 0) ? 0 : 1; // words : bytes + if (!SDCReadDrive(IFace.cmdcode,lsn)) + IFace.status = STA_FAIL | STA_READERROR; else - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } //---------------------------------------------------------------------- @@ -2102,9 +1690,9 @@ void SDCStreamImage() stream_lsn++; // Else start streaming } else { - stream_cmdcode = IF.cmdcode; - IF.reply_mode = ((IF.cmdcode & 4) == 0) ? 0 : 1; - stream_lsn = (IF.param1 << 16) + (IF.param2 << 8) + IF.param3; + stream_cmdcode = IFace.cmdcode; + IFace.reply_mode = ((IFace.cmdcode & 4) == 0) ? 0 : 1; + stream_lsn = (IFace.param1 << 16) + (IFace.param2 << 8) + IFace.param3; DLOG_C("SDCStreamImage lsn %d\n",stream_lsn); } @@ -2121,7 +1709,7 @@ void SDCStreamImage() if (!SDCReadDrive(stream_cmdcode,stream_lsn)) { DLOG_C("SDCStreamImage read error %s\n",LastErrorTxt()); - IF.status = STA_FAIL; + IFace.status = STA_FAIL; streaming = 0; return; } @@ -2134,31 +1722,31 @@ void SDCStreamImage() void SDCWriteSector() { DWORD cnt = 0; - int drive = IF.cmdcode & 1; - unsigned int lsn = (IF.param1 << 16) + (IF.param2 << 8) + IF.param3; + int drive = IFace.cmdcode & 1; + unsigned int lsn = (IFace.param1 << 16) + (IFace.param2 << 8) + IFace.param3; snprintf(SDC_Status,16,"SDC:%d Wr %d,%d",CurrentBank,drive,lsn); if (gCocoDisk[drive].hFile == nullptr) { - IF.status = STA_FAIL; + IFace.status = STA_FAIL; return; } if (!SDCSeekSector(drive,lsn)) { - IF.status = STA_FAIL; + IFace.status = STA_FAIL; return; } - if (!WriteFile(gCocoDisk[drive].hFile,IF.blkbuf, + if (!WriteFile(gCocoDisk[drive].hFile,IFace.blkbuf, gCocoDisk[drive].sectorsize,&cnt,nullptr)) { DLOG_C("SDCWriteSector %d %s\n",drive,LastErrorTxt()); - IF.status = STA_FAIL; + IFace.status = STA_FAIL; return; } if (cnt != gCocoDisk[drive].sectorsize) { DLOG_C("SDCWriteSector %d short write\n",drive); - IF.status = STA_FAIL; + IFace.status = STA_FAIL; return; } - IF.status = 0; + IFace.status = 0; return; } @@ -2167,14 +1755,14 @@ void SDCWriteSector() //---------------------------------------------------------------------- void SDCGetSectorCount() { - int drive = IF.cmdcode & 1; + int drive = IFace.cmdcode & 1; unsigned int numsec = gCocoDisk[drive].size/gCocoDisk[drive].sectorsize; - IF.reply3 = numsec & 0xFF; + IFace.reply3 = numsec & 0xFF; numsec = numsec >> 8; - IF.reply2 = numsec & 0xFF; + IFace.reply2 = numsec & 0xFF; numsec = numsec >> 8; - IF.reply1 = numsec & 0xFF; - IF.status = STA_READY; + IFace.reply1 = numsec & 0xFF; + IFace.status = STA_READY; } //---------------------------------------------------------------------- @@ -2182,20 +1770,20 @@ void SDCGetSectorCount() { //---------------------------------------------------------------------- void SDCGetMountedImageRec() { - int drive = IF.cmdcode & 1; + int drive = IFace.cmdcode & 1; //DLOG_C("SDCGetMountedImageRec %d %s\n",drive,gCocoDisk[drive].fullpath); if (strlen(gCocoDisk[drive].fullpath) == 0) { - IF.status = STA_FAIL; + IFace.status = STA_FAIL; } else { - IF.reply_mode = 0; + IFace.reply_mode = 0; SDCLoadReply(&gCocoDisk[drive].filerec,sizeof(FileRecord)); } } //---------------------------------------------------------------------- // $DO Abort stream and mount disk in a set of disks. -// IF.param1 0: Next disk 1-9: specific disk. -// IF.param2 b0: Blink Enable +// IFace.param1 0: Next disk 1-9: specific disk. +// IFace.param2 b0: Blink Enable //---------------------------------------------------------------------- void SDCControl() { @@ -2203,13 +1791,13 @@ void SDCControl() if (streaming) { DLOG_C ("Streaming abort"); streaming = 0; - IF.status = STA_READY; - IF.bufcnt = 0; + IFace.status = STA_READY; + IFace.bufcnt = 0; } else { // TODO: Mount in set DLOG_C("SDCControl Mount in set unsupported %d %d %d \n", - IF.cmdcode,IF.param1,IF.param2); - IF.status = STA_FAIL | STA_NOTFOUND; + IFace.cmdcode,IFace.param1,IFace.param2); + IFace.status = STA_FAIL | STA_NOTFOUND; } } @@ -2296,24 +1884,24 @@ void SDCLoadReply(const void *data, int count) return; } - memcpy(IF.blkbuf,data,count); + memcpy(IFace.blkbuf,data,count); - IF.bufptr = IF.blkbuf; - IF.bufcnt = count; - IF.half_sent = 0; + IFace.bufptr = IFace.blkbuf; + IFace.bufcnt = count; + IFace.half_sent = 0; // If port reads exceed the count zeros will be returned - IF.reply2 = 0; - IF.reply3 = 0; + IFace.reply2 = 0; + IFace.reply3 = 0; return; } //---------------------------------------------------------------------- // Create and mount a new disk image -// IF.param1 == 0 create 161280 byte JVC file -// IF.param1 SDF image number of cylinders -// IF.param2 SDC image number of sides +// IFace.param1 == 0 create 161280 byte JVC file +// IFace.param1 SDF image number of cylinders +// IFace.param2 SDC image number of sides //---------------------------------------------------------------------- void SDCMountNewDisk (int drive, const char * path, int raw) { @@ -2373,7 +1961,7 @@ void SDCMountDisk (int drive, const char * path, int raw) // Check for UNLOAD. Path will be an empty string. if (*path == '\0') { DLOG_C("SDCMountDisk unload %d %s\n",drive,path); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; if (drive == 0) BuildCartridgeMenu(); return; } @@ -2402,7 +1990,7 @@ void SDCMountDisk (int drive, const char * path, int raw) // Give up if (!found) { DLOG_C("SDCMountDisk not found '%s'\n",file); - IF.status = STA_FAIL | STA_NOTFOUND; + IFace.status = STA_FAIL | STA_NOTFOUND; return; } @@ -2447,8 +2035,8 @@ bool SDCMountNext (int drive) //---------------------------------------------------------------------- // Open new disk image // -// IF.Param1: $FF49 B number of cylinders for SDF -// IF.Param2: $FF4A X.H number of sides for SDF image +// IFace.Param1: $FF49 B number of cylinders for SDF +// IFace.Param2: $FF4A X.H number of sides for SDF image // // SDCOpenNew 0 'A.DSK' 0 40 0 NEW // SDCOpenNew 0 'B.DSK' 0 40 1 NEW++ one side @@ -2462,17 +2050,17 @@ bool SDCMountNext (int drive) void SDCOpenNew( int drive, const char * path, int raw) { DLOG_C("SDCOpenNew %d '%s' %d %d %d\n", - drive,path,raw,IF.param1,IF.param2); + drive,path,raw,IFace.param1,IFace.param2); // Number of sides controls file type - switch (IF.param2) { + switch (IFace.param2) { case 0: //NEW // create JVC DSK file break; case 1: //NEW+ case 2: //NEW++ DLOG_C("SDCOpenNew SDF file not supported\n"); - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return; } @@ -2481,7 +2069,7 @@ void SDCOpenNew( int drive, const char * path, int raw) if (fs::is_directory(fqn)) { DLOG_C("SDCOpenNew %s is a directory\n",path); - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return; } UnloadDisk(drive); @@ -2496,7 +2084,7 @@ void SDCOpenNew( int drive, const char * path, int raw) if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { DLOG_C("SDCOpenNew fail %d file %s\n",drive,gCocoDisk[drive].fullpath); DLOG_C("... %s\n",LastErrorTxt()); - IF.status = STA_FAIL | STA_WIN_ERROR; + IFace.status = STA_FAIL | STA_WIN_ERROR; return; } @@ -2504,10 +2092,10 @@ void SDCOpenNew( int drive, const char * path, int raw) gCocoDisk[drive].sectorsize = 256; gCocoDisk[drive].tracksectors = 18; - IF.status = STA_FAIL; + IFace.status = STA_FAIL; if (raw) { // New raw file is empty - can be any format - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; gCocoDisk[drive].doublesided = 0; gCocoDisk[drive].headersize = 0; gCocoDisk[drive].size = 0; @@ -2525,9 +2113,9 @@ void SDCOpenNew( int drive, const char * path, int raw) l_siz.QuadPart = gCocoDisk[drive].size; if (SetFilePointerEx(gCocoDisk[drive].hFile,l_siz,nullptr,FILE_BEGIN)) { if (SetEndOfFile(gCocoDisk[drive].hFile)) { - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } else { - IF.status = STA_FAIL | STA_WIN_ERROR; + IFace.status = STA_FAIL | STA_WIN_ERROR; } } } @@ -2583,9 +2171,9 @@ void SDCOpenFound (int drive,int raw) DLOG_C("... %s\n",LastErrorTxt()); int ecode = GetLastError(); if (ecode == ERROR_SHARING_VIOLATION) { - IF.status = STA_FAIL | STA_INUSE; + IFace.status = STA_FAIL | STA_INUSE; } else { - IF.status = STA_FAIL | STA_WIN_ERROR; + IFace.status = STA_FAIL | STA_WIN_ERROR; } return; } @@ -2615,7 +2203,7 @@ void SDCOpenFound (int drive,int raw) unsigned char header[16]; if (ReadFile(gCocoDisk[drive].hFile,header,12,nullptr,nullptr) == 0) { DLOG_C("SDCOpenFound header read error\n"); - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return; } @@ -2627,7 +2215,7 @@ void SDCOpenFound (int drive,int raw) (strncmp("SDF1",(const char *) header,4) == 0)) { gCocoDisk[drive].type = DTYPE_SDF; DLOG_C("SDCOpenFound SDF file unsupported\n"); - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return; } @@ -2661,7 +2249,7 @@ void SDCOpenFound (int drive,int raw) default: // More than 4 byte header is not supported DLOG_C("SDCOpenFound unsuported image type %d %d\n", drive, gCocoDisk[drive].headersize); - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return; break; } @@ -2686,14 +2274,14 @@ void SDCOpenFound (int drive,int raw) if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { DLOG_C("SDCOpenFound reopen fail %d\n",drive); DLOG_C("... %s\n",LastErrorTxt()); - IF.status = STA_FAIL | STA_WIN_ERROR; + IFace.status = STA_FAIL | STA_WIN_ERROR; return; } } if (drive == 0) BuildCartridgeMenu(); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; return; } @@ -2728,9 +2316,9 @@ void SDCRenameFile(const char *names) if (std::rename(from.c_str(),target.c_str())) { DLOG_C("SDCRenameFile %s\n", strerror(errno)); - IF.status = STA_FAIL | STA_WIN_ERROR; + IFace.status = STA_FAIL | STA_WIN_ERROR; } else { - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } return; } @@ -2750,16 +2338,16 @@ void SDCKillFile(const char* file) // Does it exist? if (!fs::exists(p, ec)) { DLOG_C("SDCKillFile does not exist\n"); - IF.status = STA_FAIL | STA_NOTFOUND; + IFace.status = STA_FAIL | STA_NOTFOUND; return; } // Regular file if (fs::is_regular_file(p, ec)) { if (fs::remove(p, ec)) { - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } else { DLOG_C("SDCKillFile error: %s\n", ec.message().c_str()); - IF.status = STA_FAIL | STA_WPROTECT; + IFace.status = STA_FAIL | STA_WPROTECT; } return; } @@ -2767,19 +2355,19 @@ void SDCKillFile(const char* file) if (fs::is_directory(p, ec)) { if (fs::is_empty(p, ec)) { if (fs::remove(p, ec)) { - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } else { - IF.status = STA_FAIL | STA_WPROTECT; + IFace.status = STA_FAIL | STA_WPROTECT; DLOG_C("SDCKillFile dir error: %s\n", ec.message().c_str()); } } else { DLOG_C("SDCKillFile directory not empty\n"); - IF.status = STA_FAIL | STA_NOTEMPTY; + IFace.status = STA_FAIL | STA_NOTEMPTY; } return; } // Not a file or directory (symlink, device, etc.) - IF.status = STA_FAIL; + IFace.status = STA_FAIL; } //---------------------------------------------------------------------- @@ -2799,15 +2387,15 @@ void SDCMakeDirectory(const char* name) if (fs::exists(p, ec)) { DLOG_C("SDCMakeDirectory already exists\n"); - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return; } if (fs::create_directory(p, ec)) { - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } else { DLOG_C("SDCMakeDirectory error: %s\n", ec.message().c_str()); - IF.status = STA_FAIL | STA_WIN_ERROR; + IFace.status = STA_FAIL | STA_WIN_ERROR; } } @@ -2828,7 +2416,7 @@ void SDCSetCurDir(const char* branch) // "." or "" no change if (*branch == '\0' || strcmp(branch, ".") == 0) { DLOG_C("SetCurdir no change\n"); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; return; } @@ -2836,7 +2424,7 @@ void SDCSetCurDir(const char* branch) if (strcmp(branch, "/") == 0) { gCurDir = ""; DLOG_C("SetCurdir to root\n"); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; return; } @@ -2846,14 +2434,14 @@ void SDCSetCurDir(const char* branch) gCurDir = cur.string(); FixDirSlashes(gCurDir); DLOG_C("SetCurdir back %s\n", gCurDir.c_str()); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; return; } // Reject "//" if (strncmp(branch, "//", 2) == 0) { DLOG_C("SetCurdir // invalid\n"); - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return; } @@ -2870,7 +2458,7 @@ void SDCSetCurDir(const char* branch) // Validate directory if (!fs::is_directory(candidate)) { DLOG_C("SetCurdir not a directory %s\n", candidate.string().c_str()); - IF.status = STA_FAIL | STA_NOTFOUND; + IFace.status = STA_FAIL | STA_NOTFOUND; return; } @@ -2887,7 +2475,7 @@ void SDCSetCurDir(const char* branch) FixDirSlashes(gCurDir); DLOG_C("SetCurdir set to '%s'\n", gCurDir.c_str()); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } //---------------------------------------------------------------------- @@ -2909,10 +2497,10 @@ bool SDCInitiateDir(const char * path) rc = SearchFile(path); } if (rc) { - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; return true; } else { - IF.status = STA_FAIL | STA_INVALID; + IFace.status = STA_FAIL | STA_INVALID; return false; } } diff --git a/sdc/sdc.h b/sdc/sdc.h index 2316f406..56159534 100644 --- a/sdc/sdc.h +++ b/sdc/sdc.h @@ -19,7 +19,11 @@ // along with VCC (Virtual Color Computer). If not, see // . // +//====================================================================== +#pragma once + +#include #include #include @@ -58,6 +62,109 @@ #define ATTR_SDF 0x04 #define ATTR_DIR 0x10 -// Self imposed limit on maximum dsk file size. +// Disk Types +enum DiskType { + DTYPE_RAW = 0, + DTYPE_DSK, + DTYPE_JVC, + DTYPE_VDK, + DTYPE_SDF +}; + +// Limit maximum dsk file size. #define MAX_DSK_SECTORS 2097152 +// HostFile contains a file name, it's size, and directory and readonly flags +struct HostFile +{ + std::string name; // LFN 8.3 name + DWORD size; // < 4GB + bool isDir; // is a directory + bool isRdOnly; // is read only + HostFile() {} + // Construct a HostFile from an argument list + HostFile(const char* cName, DWORD nSize, bool isDir, bool isRdOnly) : + name(cName), size(nSize), isDir(isDir), isRdOnly(isRdOnly) {} + // Construct a HostFile from a WIN32_FIND_DATAA record. + HostFile(WIN32_FIND_DATAA fd) : + name(fd.cFileName), size(fd.nFileSizeLow), + isDir((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0), + isRdOnly((fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY) != 0) {} +}; + +// FileList contains a list of HostFiles +struct FileList +{ + std::vector files; // files + std::string directory; // directory files are in + size_t cursor = 0; // current file curspr + bool nextload_flag = false; // enable next disk loading + // append a host file to the list + void append(const HostFile& hf) { files.push_back(hf); } +}; + +// FileRecord is 16 packed bytes file name, type, attrib, and size that can +// be passed directly via the SDC interface. +#pragma pack(push, 1) +struct FileRecord +{ + char FR_name[8]; + char FR_type[3]; + char FR_attrib; + char FR_hihi_size; + char FR_lohi_size; + char FR_hilo_size; + char FR_lolo_size; + FileRecord() noexcept {}; + FileRecord(const HostFile& hf) noexcept; +}; +#pragma pack(pop) + +// CocoDisk contains info about mounted disk files 0 and 1 +struct CocoDisk +{ + HANDLE hFile; // file handle + unsigned int size; // number of bytes total + unsigned int headersize; // number of bytes in header + DWORD sectorsize; // number of bytes per sector + DWORD tracksectors; // number of sectors per side + DiskType type; // Disk image type (RAW,DSK,JVC,VDK,SDF) + char doublesided; // false:1 side, true:2 sides + char name[MAX_PATH]; // name of file (8.3) + char fullpath[MAX_PATH]; // full file path + struct HostFile hf{"",0,false,false}; //name,size,isDir,isRdOnly + struct FileRecord filerec; + // Constructor + CocoDisk() noexcept + : hFile(INVALID_HANDLE_VALUE), + size(0), + headersize(0), + sectorsize(256), + tracksectors(18), + type(DiskType::DTYPE_RAW), + doublesided(1) { + name[0] = '\0'; + fullpath[0] = '\0'; + }; +}; + +// SDC CoCo Interface +struct Interface +{ + int sdclatch; + unsigned char cmdcode; + unsigned char status; + unsigned char reply1; + unsigned char reply2; + unsigned char reply3; + unsigned char param1; + unsigned char param2; + unsigned char param3; + unsigned char reply_mode; // 0=words, 1=bytes + unsigned char reply_status; + unsigned char half_sent; + int bufcnt; + char *bufptr; + char blkbuf[600]; +}; + diff --git a/sdc/sdc.vcxproj b/sdc/sdc.vcxproj index 04bf9198..34062aaa 100644 --- a/sdc/sdc.vcxproj +++ b/sdc/sdc.vcxproj @@ -97,10 +97,12 @@ + + @@ -113,4 +115,4 @@ - \ No newline at end of file + From 847a2cb2c0c15ab3843182c634752527d35fa6bb Mon Sep 17 00:00:00 2001 From: ejaquay Date: Tue, 13 Jan 2026 19:55:27 -0500 Subject: [PATCH 06/11] Don't send activation mouse click to emulation --- Vcc.cpp | 49 +++++----- keyboard.cpp | 42 +++++---- sdc/fileutil.cpp | 232 +++++++++++++++++++++++++++++++++++++++++++++++ sdc/fileutil.h | 66 ++++++++++++++ 4 files changed, 349 insertions(+), 40 deletions(-) create mode 100644 sdc/fileutil.cpp create mode 100644 sdc/fileutil.h diff --git a/Vcc.cpp b/Vcc.cpp index 8d8178fc..8938ae34 100644 --- a/Vcc.cpp +++ b/Vcc.cpp @@ -1,27 +1,27 @@ -/* -Copyright 2015 by Joseph Forgione -This file is part of VCC (Virtual Color Computer). - - VCC (Virtual Color Computer) is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - VCC (Virtual Color Computer) is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with VCC (Virtual Color Computer). If not, see . -*/ - -/*--------------------------------------------------------------- - ----------------------------------------------------------------*/ +//#define USE_LOGGING +//====================================================================== +// This file is part of VCC (Virtual Color Computer). +// Vcc is Copyright 2015 by Joseph Forgione +// +// VCC (Virtual Color Computer) is free software, you can redistribute +// and/or modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// VCC (Virtual Color Computer) is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with VCC (Virtual Color Computer). If not, see +// . +//====================================================================== + +/*---------------------------------------------------------------------*/ //#define STRICT //#define WIN32_LEAN_AND_MEAN -/*--------------------------------------------------------------------------*/ +/*---------------------------------------------------------------------*/ // FIXME: This should be defined on the command line #define DIRECTINPUT_VERSION 0x0800 @@ -604,6 +604,11 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) return 0; break; + // Don't send click that activates window to emulation + case WM_MOUSEACTIVATE: + return MA_ACTIVATEANDEAT; + break; + case WM_LBUTTONDOWN: //0 = Left 1=right SetButtonStatus(0,1); break; diff --git a/keyboard.cpp b/keyboard.cpp index 239b61e2..24e00327 100644 --- a/keyboard.cpp +++ b/keyboard.cpp @@ -1,21 +1,23 @@ -/*****************************************************************************/ -/* -Copyright 2015 by Joseph Forgione -This file is part of VCC (Virtual Color Computer). - - VCC (Virtual Color Computer) is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - VCC (Virtual Color Computer) is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. +//#define USE_LOGGING +//====================================================================== +// This file is part of VCC (Virtual Color Computer). +// Vcc is Copyright 2015 by Joseph Forgione +// +// VCC (Virtual Color Computer) is free software, you can redistribute +// and/or modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// VCC (Virtual Color Computer) is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with VCC (Virtual Color Computer). If not, see +// . +//====================================================================== - You should have received a copy of the GNU General Public License - along with VCC (Virtual Color Computer). If not, see . -*/ /*****************************************************************************/ /* Keyboard handling / translation - system -> emulator @@ -50,7 +52,7 @@ This file is part of VCC (Virtual Color Computer). #include #include - +#include /*****************************************************************************/ /* @@ -244,6 +246,9 @@ void _vccKeyboardUpdateRolloverTable() void vccKeyboardHandleKey(unsigned char ScanCode, keyevent_e keyState) { + + DLOG_C("HandleKey %d %d\n",ScanCode,keyState); + //If requested, abort pasting operation. if (ScanCode == 0x01 || ScanCode == 0x43 || ScanCode == 0x3F) { pasting = false; } @@ -253,6 +258,7 @@ void vccKeyboardHandleKey(unsigned char ScanCode, keyevent_e keyState) { ScanCode = DIK_LSHIFT; } + #if 0 // TODO: CTRL and/or ALT? // CTRL key - right -> left if (ScanCode == DIK_RCONTROL) diff --git a/sdc/fileutil.cpp b/sdc/fileutil.cpp new file mode 100644 index 00000000..6e10442d --- /dev/null +++ b/sdc/fileutil.cpp @@ -0,0 +1,232 @@ +//define USE_LOGGING +//====================================================================== +// General purpose Host file utilities. EJ Jaquay 2026 +// +// This file is part of VCC (Virtual Color Computer). +// Vcc is Copyright 2015 by Joseph Forgione +// +// VCC (Virtual Color Computer) is free software, you can redistribute +// and/or modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// VCC (Virtual Color Computer) is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with VCC (Virtual Color Computer). If not, see +// . +// +//====================================================================== + +#include +#include +#include +#include +#include "fileutil.h" + +//---------------------------------------------------------------------- +// Get most recent windows error text +//---------------------------------------------------------------------- +const char * LastErrorTxt() { + static char msg[200]; + DWORD error_code = GetLastError(); + FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msg, sizeof(msg), nullptr ); + return msg; +} +std::string LastErrorString() { + return LastErrorTxt(); +} + +//---------------------------------------------------------------------- +// Return copy of string with spaces trimmed from end of a string +//---------------------------------------------------------------------- +std::string trim_right_spaces(const std::string& s) +{ + size_t end = s.find_last_not_of(' '); + if (end == std::string::npos) + return {}; + return s.substr(0, end + 1); +} + +//---------------------------------------------------------------------- +// Return slash normalized directory part of a path +//---------------------------------------------------------------------- +std::string GetDirectoryPart(const std::string& input) +{ + std::filesystem::path p(input); + std::string out = p.parent_path().string(); + FixDirSlashes(out); + return out; +} + +//---------------------------------------------------------------------- +// Return filename part of a path +//---------------------------------------------------------------------- +std::string GetFileNamePart(const std::string& input) +{ + std::filesystem::path p(input); + return p.filename().string(); +} + +//---------------------------------------------------------------------- +// Determine if path is a direcory +//---------------------------------------------------------------------- +bool IsDirectory(const std::string& path) +{ + std::error_code ec; + return std::filesystem::is_directory(path, ec) && !ec; +} + +//------------------------------------------------------------------- +// Convert path directory backslashes to forward slashes +//------------------------------------------------------------------- +void FixDirSlashes(std::string &dir) +{ + if (dir.empty()) return; + std::replace(dir.begin(), dir.end(), '\\', '/'); + if (dir.back() == '/') dir.pop_back(); +} +void FixDirSlashes(char *dir) +{ + if (!dir) return; + std::string tmp(dir); + FixDirSlashes(tmp); + strcpy(dir, tmp.c_str()); +} + +//------------------------------------------------------------------- +// Copy string to fixed size char array (non terminated) +//------------------------------------------------------------------- +template +void copy_to_fixed_char(char (&dest)[N], const std::string& src) +{ + size_t i = 0; + for (; i < src.size() && i < N; ++i) + dest[i] = src[i]; + for (; i < N; ++i) + dest[i] = ' '; +} + +//------------------------------------------------------------------- +// Convert LFN to FAT filename parts, 8 char name, 3 char ext +// A LNF file is less than 4GB and has a short (8.3) name. +//------------------------------------------------------------------- +void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn) +{ + // Special case: parent directory + if (lfn == "..") { + copy_to_fixed_char(name, ".."); + std::fill(ext, ext + 3, ' '); + return; + } + + size_t dot = lfn.find('.'); + std::string base, extension; + + if (dot == std::string::npos) { + base = lfn; + } else { + base = lfn.substr(0, dot); + extension = lfn.substr(dot + 1); + } + + copy_to_fixed_char(name, base); + copy_to_fixed_char(ext, extension); +} + +//------------------------------------------------------------------- +// Convert FAT filename parts to LFN. Returns empty string if invalid +//------------------------------------------------------------------- +std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]) +{ + std::string base(name, 8); + std::string extension(ext, 3); + + base = trim_right_spaces(base); + extension = trim_right_spaces(extension); + + if (base == ".." && extension.empty()) + return ".."; + + std::string lfn = base; + + if (!extension.empty()) + lfn += "." + extension; + + if (lfn.empty()) + return {}; + + if (!PathIsLFNFileSpecA(lfn.c_str())) + return {}; + + return lfn; +} + +//------------------------------------------------------------------- +// Convert string containing possible FAT name and extension to an +// LFN string. Returns empty string if invalid LFN +//------------------------------------------------------------------- +std::string NormalizeInputToLFN(const std::string& s) +{ + if (s.empty()) return {}; + if (s.size() > 11) return {}; + if (s == "..") return ".."; + + // LFN candidate + if (s.find('.') != std::string::npos) { + if (!PathIsLFNFileSpecA(s.c_str())) + return {}; // invalid + return s; + } + + // SFN candidate + char name[8]; + char ext[3]; + sfn_from_lfn(name,ext,s); + return lfn_from_sfn(name,ext); +} + +//---------------------------------------------------------------------- +// A file path may use 11 char FAT format which does not use a separater +// between name and extension. User is free to use standard dot format. +// "FOODIR/FOO.DSK" = FOODIR/FOO.DSK +// "FOODIR/FOO DSK" = FOODIR/FOO.DSK +// "FOODIR/ALONGFOODSK" = FOODIR/ALONGFOO.DSK +//---------------------------------------------------------------------- +std::string FixFATPath(const std::string& sdcpath) +{ + std::filesystem::path p(sdcpath); + + auto chop = [](std::string s) { + size_t space = s.find(' '); + if (space != std::string::npos) s.erase(space); + return s; + }; + + std::string fname = p.filename().string(); + if (fname.length() == 11 && fname.find('.') == std::string::npos) { + auto nam = chop(fname.substr(0,8)); + auto ext = chop(fname.substr(8,3)); + fname = ext.empty() ? nam : nam + "." + ext; + } + + std::filesystem::path out = p.parent_path() / fname; + + DLOG_C("FixFATPath in %s out %s\n",sdcpath.c_str(),out.generic_string().c_str()); + return out.generic_string(); +} + +void FixFATPath(char* path, const char* sdcpath) +{ + std::string fixed = FixFATPath(std::string(sdcpath)); + strncpy(path, fixed.c_str(), MAX_PATH); + path[MAX_PATH - 1] = '\0'; +} + diff --git a/sdc/fileutil.h b/sdc/fileutil.h new file mode 100644 index 00000000..ab3a103d --- /dev/null +++ b/sdc/fileutil.h @@ -0,0 +1,66 @@ +//====================================================================== +// General purpose Host file utilities. EJ Jaquay 2026 +// +// This file is part of VCC (Virtual Color Computer). +// Vcc is Copyright 2015 by Joseph Forgione +// +// VCC (Virtual Color Computer) is free software, you can redistribute +// and/or modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// VCC (Virtual Color Computer) is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with VCC (Virtual Color Computer). If not, see +// . +// +//====================================================================== +#pragma once + +#include +#include +#include + +//========================================================================= +// Host file utilities. Most of these are general purpose +//========================================================================= + +// Get most recent windows error text +const char * LastErrorTxt(); +std::string LastErrorString(); + +// Convert backslashes to slashes in directory string / char +void FixDirSlashes(std::string &dir); +void FixDirSlashes(char *dir); + +// copy string into fixed-size char array and blank-pad +template +void copy_to_fixed_char(char (&dest)[N], const std::string& src); + +// Return copy of string with spaces trimmed from end of a string +std::string trim_right_spaces(const std::string &s); + +// Convert LFN to FAT filename parts, 8 char name, 3 char ext +void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn); + +// Convert FAT file name parts to LFN. Returns empty string if invalid LFN +std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]); + +// Return slash normalized directory part of a path +std::string GetDirectoryPart(const std::string& input); + +// Return filename part of a path +std::string GetFileNamePart(const std::string& input); + +// SDC interface often presents a path which does not use a dot to +// delimit name and extension: "FOODIR/FOO DSK" -> FOODIR/FOO.DSK +std::string FixFATPath(const std::string& sdcpath); +void FixFATPath(char* path, const char* sdcpath); + +// Determine if path is a direcory +bool IsDirectory(const std::string& path); + From 4ad1d5848f4af3372a2c9ea837f43aebaf7070a0 Mon Sep 17 00:00:00 2001 From: ejaquay Date: Wed, 14 Jan 2026 20:25:12 -0500 Subject: [PATCH 07/11] SDC rearrange sources. No functional change --- sdc/sdc.cpp | 333 ++++++++++++++++++++++++++-------------------------- 1 file changed, 168 insertions(+), 165 deletions(-) diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index 34057741..47b604d4 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -212,7 +212,7 @@ static int UPDBTNS[8] = {ID_UPDATE0,ID_UPDATE1,ID_UPDATE2,ID_UPDATE3, static char ROMPath[MAX_PATH]; //====================================================================== -// Compile unit functions +// Functions //====================================================================== LRESULT CALLBACK SDC_Configure(HWND, UINT, WPARAM, LPARAM); @@ -275,115 +275,6 @@ bool SDCReadDrive(unsigned char,unsigned int); bool SDCLoadNextDirPage(); std::string SDCGetFullPath(const std::string&); -//========================================================================= -// Host file handling -//========================================================================= - -// Construct FileRecord from a HostFile object. -FileRecord::FileRecord(const HostFile& hf) noexcept -{ - memset(this, 0, sizeof(*this)); - // name and type - sfn_from_lfn(FR_name, FR_type, hf.name); - // Attributes - FR_attrib = 0; - if (hf.isDir) FR_attrib |= 0x10; - if (hf.isRdOnly) FR_attrib |= 0x01; - // Size -> 4 reversed endian bytes - DWORD s = hf.size; - FR_hihi_size = (s >> 24) & 0xFF; - FR_lohi_size = (s >> 16) & 0xFF; - FR_hilo_size = (s >> 8) & 0xFF; - FR_lolo_size = (s >> 0) & 0xFF; -} - -//------------------------------------------------------------------- -// Get list of files matching pattern starting from CurDir or SDRoot -//------------------------------------------------------------------- -void GetFileList(const std::string& pattern) -{ - DLOG_C("GetFileList %s\n",pattern.c_str()); - - // Init search results - gFileList = {}; - - // None if pattern is empty - if (pattern.empty()) return; - - // Set up search path. - bool not_at_root = !gCurDir.empty(); - bool rel_pattern = pattern.front() != '/'; - - std::string search_path = gSDRoot; - if (rel_pattern) { - search_path += "/"; - if (not_at_root) - search_path += gCurDir + "/"; - } - search_path += pattern; - - // A directory lookup if pattern name is "*" or "*.*"; - std::string fnpat = GetFileNamePart(pattern); - bool dir_lookup = (fnpat == "*" || fnpat == "*.*"); - - std::string search_dir = GetDirectoryPart(search_path); - - // Include ".." if dir_lookup and search_dir is not SDRoot - if (dir_lookup && GetDirectoryPart(search_path) != gSDRoot) { - gFileList.append({"..", 0, true, false}); - } - - // Add matching files to the list - WIN32_FIND_DATAA fd; - HANDLE hFind = FindFirstFileA(search_path.c_str(), &fd); - if (hFind == INVALID_HANDLE_VALUE) { - gFileList = {}; - return; - } - do { - if (strcmp(fd.cFileName,".") == 0) continue; // exclude single dot file - if (PathIsLFNFileSpecA(fd.cFileName)) continue; // exclude ugly or long filenames - if (fd.nFileSizeHigh != 0) continue; // exclude files > 4 GB - gFileList.append({fd}); - } while (FindNextFileA(hFind, &fd)); - FindClose(hFind); - - // Save directory - gFileList.directory = search_dir; - - DLOG_C("GetFileList found %d\n",gFileList.files.size()); -} - -//------------------------------------------------------------------- -// SortFileList() may be needed when mounting a directory -//------------------------------------------------------------------- -void SortFileList() -{ - std::sort( - gFileList.files.begin(), - gFileList.files.end(), - [] (const HostFile& a, const HostFile& b) { - return a.name < b.name; - } - ); - gFileList.cursor = 0; -} - -//---------------------------------------------------------------------- -// Host File search. -//---------------------------------------------------------------------- -bool SearchFile(const char* pattern) -{ - std::string s = pattern; - GetFileList(s); - - DLOG_C("SearchFile found %d pat %s\n",gFileList.files.size(),s.c_str()); - - // Update menu to show next disk status - BuildCartridgeMenu(); - - return (gFileList.files.size() > 0); -} //====================================================================== // DLL interface @@ -815,7 +706,7 @@ void SelectCardBox() } //===================================================================== -// SDC Simulation +// SDC startup //====================================================================== //---------------------------------------------------------------------- @@ -853,6 +744,53 @@ void InitSDC() return; } +//---------------------------------------------------------------------- +// Parse the startup.cfg file +//---------------------------------------------------------------------- +void ParseStartup() +{ + namespace fs = std::filesystem; + fs::path sd = fs::path(gSDRoot); + + if (!fs::is_directory(sd)) { + DLOG_C("ParseStartup SDCard path invalid\n"); + return; + } + + fs::path cfg = sd / "startup.cfg"; + FILE* su = std::fopen(cfg.string().c_str(), "r"); + if (su == nullptr) { + DLOG_C("ParseStartup file not found, %s\n", cfg.string().c_str()); + return; + } + + // Strict single char followed by '=' then path + char buf[MAX_PATH]; + while (fgets(buf,sizeof(buf),su) != nullptr) { + //Chomp line ending + buf[strcspn(buf,"\r\n")] = 0; + // Skip line if less than 3 chars; + if (strlen(buf) < 3) continue; + // Skip line if second char is not '=' + if (buf[1] != '=') continue; + // Grab drive num char + char drv = buf[0]; + // Attempt to mount drive + switch (drv) { + case '0': + SDCMountDisk(0,&buf[2],0); + break; + case '1': + SDCMountDisk(1,&buf[2],0); + break; + case 'D': + SDCSetCurDir(&buf[2]); + break; + } + } + fclose(su); +} + //------------------------------------------------------------- // Load rom from flash bank //------------------------------------------------------------- @@ -874,7 +812,7 @@ void LoadRom(unsigned char bank) h_RomFile = nullptr; } - // If bank contents have been changed write to the flash file + // If bank contents have been changed write the flash file if (BankDirty) { RomFile = FlashFile[CurrentBank]; DLOG_C("LoadRom switching out dirty bank %d %s\n",CurrentBank,RomFile); @@ -904,7 +842,7 @@ void LoadRom(unsigned char bank) } // Open romfile for write if not startup bank - if (CurrentBank != StartupBank) + if (CurrentBank != StartupBank) h_RomFile = fopen(RomFile,"wb"); if (CurrentBank == StartupBank || h_RomFile == nullptr) @@ -929,55 +867,8 @@ void LoadRom(unsigned char bank) return; } -//---------------------------------------------------------------------- -// Parse the startup.cfg file -//---------------------------------------------------------------------- -void ParseStartup() -{ - namespace fs = std::filesystem; - fs::path sd = fs::path(gSDRoot); - - if (!fs::is_directory(sd)) { - DLOG_C("ParseStartup SDCard path invalid\n"); - return; - } - - fs::path cfg = sd / "startup.cfg"; - FILE* su = std::fopen(cfg.string().c_str(), "r"); - if (su == nullptr) { - DLOG_C("ParseStartup file not found, %s\n", cfg.string().c_str()); - return; - } - - // Strict single char followed by '=' then path - char buf[MAX_PATH]; - while (fgets(buf,sizeof(buf),su) != nullptr) { - //Chomp line ending - buf[strcspn(buf,"\r\n")] = 0; - // Skip line if less than 3 chars; - if (strlen(buf) < 3) continue; - // Skip line if second char is not '=' - if (buf[1] != '=') continue; - // Grab drive num char - char drv = buf[0]; - // Attempt to mount drive - switch (drv) { - case '0': - SDCMountDisk(0,&buf[2],0); - break; - case '1': - SDCMountDisk(1,&buf[2],0); - break; - case 'D': - SDCSetCurDir(&buf[2]); - break; - } - } - fclose(su); -} - //===================================================================== -// SDC Interface functions +// SDC Interface //===================================================================== //---------------------------------------------------------------------- @@ -1125,6 +1016,7 @@ unsigned char SDCRead(unsigned char port) rpy = 0; break; } + // If not SDC latched do floppy controller simulation } else { switch (port) { case 0x40: @@ -1933,7 +1825,7 @@ void SDCMountNewDisk (int drive, const char * path, int raw) //---------------------------------------------------------------------- void UnloadDisk(int drive) { int d = drive & 1; - if ( gCocoDisk[d].hFile && + if ( gCocoDisk[d].hFile && gCocoDisk[d].hFile != INVALID_HANDLE_VALUE) { CloseHandle(gCocoDisk[d].hFile); DLOG_C("UnloadDisk %d %s\n",drive,gCocoDisk[d].name); @@ -2022,8 +1914,8 @@ bool SDCMountNext (int drive) gFileList.cursor = 0; DLOG_C("SDCMountNext reset cursor\n"); } - - DLOG_C("SDCMountNext %d %d\n", + + DLOG_C("SDCMountNext %d %d\n", gFileList.cursor, gFileList.files.size()); // Open next image found on the drive @@ -2188,7 +2080,7 @@ void SDCOpenFound (int drive,int raw) gCocoDisk[drive].sectorsize = 256; gCocoDisk[drive].tracksectors = 18; - // Grab filesize + // Grab filesize gCocoDisk[drive].size = gFileList.files[gFileList.cursor].size; // Determine file type (RAW,DSK,JVC,VDK,SDF) @@ -2504,3 +2396,114 @@ bool SDCInitiateDir(const char * path) return false; } } + +//========================================================================= +// Host file handling +//========================================================================= + +// Construct FileRecord from a HostFile object. +FileRecord::FileRecord(const HostFile& hf) noexcept +{ + memset(this, 0, sizeof(*this)); + // name and type + sfn_from_lfn(FR_name, FR_type, hf.name); + // Attributes + FR_attrib = 0; + if (hf.isDir) FR_attrib |= 0x10; + if (hf.isRdOnly) FR_attrib |= 0x01; + // Size -> 4 reversed endian bytes + DWORD s = hf.size; + FR_hihi_size = (s >> 24) & 0xFF; + FR_lohi_size = (s >> 16) & 0xFF; + FR_hilo_size = (s >> 8) & 0xFF; + FR_lolo_size = (s >> 0) & 0xFF; +} + +//------------------------------------------------------------------- +// Get list of files matching pattern starting from CurDir or SDRoot +//------------------------------------------------------------------- +void GetFileList(const std::string& pattern) +{ + DLOG_C("GetFileList %s\n",pattern.c_str()); + + // Init search results + gFileList = {}; + + // None if pattern is empty + if (pattern.empty()) return; + + // Set up search path. + bool not_at_root = !gCurDir.empty(); + bool rel_pattern = pattern.front() != '/'; + + std::string search_path = gSDRoot; + if (rel_pattern) { + search_path += "/"; + if (not_at_root) + search_path += gCurDir + "/"; + } + search_path += pattern; + + // A directory lookup if pattern name is "*" or "*.*"; + std::string fnpat = GetFileNamePart(pattern); + bool dir_lookup = (fnpat == "*" || fnpat == "*.*"); + + std::string search_dir = GetDirectoryPart(search_path); + + // Include ".." if dir_lookup and search_dir is not SDRoot + if (dir_lookup && GetDirectoryPart(search_path) != gSDRoot) { + gFileList.append({"..", 0, true, false}); + } + + // Add matching files to the list + WIN32_FIND_DATAA fd; + HANDLE hFind = FindFirstFileA(search_path.c_str(), &fd); + if (hFind == INVALID_HANDLE_VALUE) { + gFileList = {}; + return; + } + do { + if (strcmp(fd.cFileName,".") == 0) continue; // exclude single dot file + if (PathIsLFNFileSpecA(fd.cFileName)) continue; // exclude ugly or long filenames + if (fd.nFileSizeHigh != 0) continue; // exclude files > 4 GB + gFileList.append({fd}); + } while (FindNextFileA(hFind, &fd)); + FindClose(hFind); + + // Save directory + gFileList.directory = search_dir; + + DLOG_C("GetFileList found %d\n",gFileList.files.size()); +} + +//------------------------------------------------------------------- +// SortFileList() may be needed when mounting a directory +//------------------------------------------------------------------- +void SortFileList() +{ + std::sort( + gFileList.files.begin(), + gFileList.files.end(), + [] (const HostFile& a, const HostFile& b) { + return a.name < b.name; + } + ); + gFileList.cursor = 0; +} + +//---------------------------------------------------------------------- +// Host File search. +//---------------------------------------------------------------------- +bool SearchFile(const char* pattern) +{ + std::string s = pattern; + GetFileList(s); + + DLOG_C("SearchFile found %d pat %s\n",gFileList.files.size(),s.c_str()); + + // Update menu to show next disk status + BuildCartridgeMenu(); + + return (gFileList.files.size() > 0); +} + From 8f42c9f6f6dee606de16d5a9f93799122e15831b Mon Sep 17 00:00:00 2001 From: ejaquay Date: Wed, 14 Jan 2026 21:22:10 -0500 Subject: [PATCH 08/11] MPI clear configured slot path if cart load fails on startup --- mpi/configuration_dialog.cpp | 2 ++ mpi/multipak_cartridge.cpp | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/mpi/configuration_dialog.cpp b/mpi/configuration_dialog.cpp index b405adc5..5bdb8c9c 100644 --- a/mpi/configuration_dialog.cpp +++ b/mpi/configuration_dialog.cpp @@ -1,3 +1,4 @@ +//#define USE_LOGGING //////////////////////////////////////////////////////////////////////////////// // Copyright 2015 by Joseph Forgione // This file is part of VCC (Virtual Color Computer). @@ -180,6 +181,7 @@ void configuration_dialog::cart_type_menu(unsigned int Button) void configuration_dialog::eject_or_select_new_cartridge(unsigned int Button) { + // Disable Slot changes if parent is disabled. This prevents user using the // config dialog to eject a cartridge while VCC main is using a modal dialog // Otherwise user can crash VCC by unloading a disk cart while inserting a disk diff --git a/mpi/multipak_cartridge.cpp b/mpi/multipak_cartridge.cpp index 0d4a8906..f254744e 100644 --- a/mpi/multipak_cartridge.cpp +++ b/mpi/multipak_cartridge.cpp @@ -1,3 +1,4 @@ +//#define USE_LOGGING //////////////////////////////////////////////////////////////////////////////// // Copyright 2015 by Joseph Forgione // This file is part of VCC (Virtual Color Computer). @@ -15,7 +16,6 @@ // You should have received a copy of the GNU General Public License along with // VCC (Virtual Color Computer). If not, see . //////////////////////////////////////////////////////////////////////////////// -//#define USE_LOGGING #include "multipak_cartridge.h" #include "multipak_cartridge_context.h" #include "mpi.h" @@ -108,7 +108,10 @@ void multipak_cartridge::start() const auto path(vcc::core::utils::find_pak_module_path(configuration_.slot_cartridge_path(slot))); if (!path.empty()) { - mount_cartridge(slot, path); + if (mount_cartridge(slot, path) != vcc::core::cartridge_loader_status::success) { + DLOG_C("Clearing configured slot path %d\n",slot); + configuration_.slot_cartridge_path(slot,""); + } } } @@ -339,6 +342,8 @@ multipak_cartridge::mount_status_type multipak_cartridge::mount_cartridge( *context_, *multipakHost); + DLOG_C("load cart %d %s\n",slot,filename); + auto loadedCartridge = vcc::core::load_cartridge( filename, std::move(ctx), From 0d7992f4f55138babc9b0b119bb59d8e820a168b Mon Sep 17 00:00:00 2001 From: ejaquay Date: Thu, 15 Jan 2026 15:41:29 -0500 Subject: [PATCH 09/11] MPI Config Reset VCC button closes config. Add OK button. --- mpi/configuration_dialog.cpp | 4 ++++ mpi/mpi.rc | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mpi/configuration_dialog.cpp b/mpi/configuration_dialog.cpp index 5bdb8c9c..858a6b29 100644 --- a/mpi/configuration_dialog.cpp +++ b/mpi/configuration_dialog.cpp @@ -297,6 +297,10 @@ INT_PTR configuration_dialog::process_message( return TRUE; case IDC_RESET: SendMessage(gVccWnd,WM_COMMAND,(WPARAM) ID_FILE_RESET,(LPARAM) 0); + close(); + return TRUE; + case IDOK: + close(); return TRUE; } // End switch LOWORD break; diff --git a/mpi/mpi.rc b/mpi/mpi.rc index 9bd5a7d9..36fa3d9d 100644 --- a/mpi/mpi.rc +++ b/mpi/mpi.rc @@ -81,12 +81,13 @@ BEGIN LTEXT "> Load cartridge.", IDC_STATIC, 124,128,60,10 LTEXT "X Eject cartridge.", IDC_STATIC, 194,128,60,10 - LTEXT "Reset VCC for startup slot change to take effect", IDC_STATIC, 74,144,230,10 - PUSHBUTTON "Reset VCC",IDC_RESET, 10,142,58,14 + PUSHBUTTON "Reset VCC",IDC_RESET, 6,142,55,15 + LTEXT "Reset VCC for startup slot change to take effect", IDC_STATIC, 68,145,230,10 + + DEFPUSHBUTTON "OK",IDOK, 240,142,50,15 // CHECKBOX "Persistent ProgramPak Images",IDC_PERSIST_PAK, 15,165,110,10 // CHECKBOX "Disable Cart Select Signal",IDC_SCS_DISABLE, 140,165,100,10 -// DEFPUSHBUTTON "OK",IDOK, 245,163, 40,14 END IDD_POPUP_MENU MENU From f4f7a1802e7bfcc6fa19a89172df63cff0d5c033 Mon Sep 17 00:00:00 2001 From: ejaquay Date: Fri, 16 Jan 2026 22:31:21 -0500 Subject: [PATCH 10/11] Move SDC fileutil to libcommon --- docs/namespaces.txt | 25 ++ libcommon/include/vcc/core/filesystem.h | 6 +- .../include/vcc/core}/fileutil.h | 50 ++-- libcommon/libcommon.vcxproj | 2 + libcommon/src/core/FileOps.cpp | 3 + libcommon/src/core/filesystem.cpp | 49 +--- libcommon/src/core/fileutil.cpp | 146 +++++++++++ sdc/fileutil.cpp | 232 ------------------ sdc/sdc.cpp | 216 ++++++++++++---- sdc/sdc.vcxproj | 2 - 10 files changed, 383 insertions(+), 348 deletions(-) create mode 100644 docs/namespaces.txt rename {sdc => libcommon/include/vcc/core}/fileutil.h (50%) create mode 100644 libcommon/src/core/fileutil.cpp delete mode 100644 sdc/fileutil.cpp diff --git a/docs/namespaces.txt b/docs/namespaces.txt new file mode 100644 index 00000000..c7c9949b --- /dev/null +++ b/docs/namespaces.txt @@ -0,0 +1,25 @@ +We let namspaces get a bit overboard. Namespaces are for preventing +name collisions, not to fully classify sources or to define directory +or common library structures. + +Also we let the case of the VCC namespace change. This gains nothing +but confusion. I see two choices here. a) go all lowercase or +b) use CamelCase. Since we already had CamelCase I suggest we stick +with it: + +Valid VCC namespaces + +VCC // The VCC core emulator +VCC::UI // The VCC core user interface (future) +VCC::Util // Generic utilities used by VCC +VCC::Debugger // Coco Debugger code +VCC::Debugger::UI // Coco Debugger user interface + +The following are invalid and should be changed when touched: + +namespace vcc -> namespace VCC +namespace vcc::core -> namespace VCC +namespace vcc::core::cartridges -> namespace VCC +namespace vcc::core::utils -> namespace VCC::Util +namespace vcc::devices::rtc -> namespace VCC +namespace vcc::modules::mpi -> namespace VCC diff --git a/libcommon/include/vcc/core/filesystem.h b/libcommon/include/vcc/core/filesystem.h index cfbab3fe..742f0ceb 100644 --- a/libcommon/include/vcc/core/filesystem.h +++ b/libcommon/include/vcc/core/filesystem.h @@ -20,13 +20,11 @@ #include #include +//TODO replace get_directory_from_path and get_filename with fileutil functions +//TODO move find_pak_module_path to point of use namespace vcc::core::utils { - - LIBCOMMON_EXPORT std::string get_module_path(HMODULE module_handle = nullptr); LIBCOMMON_EXPORT std::string find_pak_module_path(std::string path); LIBCOMMON_EXPORT std::string get_directory_from_path(std::string path); LIBCOMMON_EXPORT std::string get_filename(std::string path); - LIBCOMMON_EXPORT std::string strip_application_path(std::string path); - } diff --git a/sdc/fileutil.h b/libcommon/include/vcc/core/fileutil.h similarity index 50% rename from sdc/fileutil.h rename to libcommon/include/vcc/core/fileutil.h index ab3a103d..857a7bd2 100644 --- a/sdc/fileutil.h +++ b/libcommon/include/vcc/core/fileutil.h @@ -25,42 +25,38 @@ #include #include +// FIXME: directory names in libcommon are wrong +// libcommon/include/vcc/core should be libcommon/include/vcc/util +// libcommon/src/core should be libcommon/include/util + //========================================================================= // Host file utilities. Most of these are general purpose //========================================================================= -// Get most recent windows error text -const char * LastErrorTxt(); -std::string LastErrorString(); - -// Convert backslashes to slashes in directory string / char -void FixDirSlashes(std::string &dir); -void FixDirSlashes(char *dir); - -// copy string into fixed-size char array and blank-pad -template -void copy_to_fixed_char(char (&dest)[N], const std::string& src); +namespace VCC::Util { -// Return copy of string with spaces trimmed from end of a string -std::string trim_right_spaces(const std::string &s); + // Get most recent windows error text + std::string LastErrorString(); + const char * LastErrorTxt(); -// Convert LFN to FAT filename parts, 8 char name, 3 char ext -void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn); + // Convert backslashes to slashes in directory string + void FixDirSlashes(std::string &dir); -// Convert FAT file name parts to LFN. Returns empty string if invalid LFN -std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]); + // Return copy of string with spaces trimmed from end of a string + std::string trim_right_spaces(const std::string &s); -// Return slash normalized directory part of a path -std::string GetDirectoryPart(const std::string& input); + // Return slash normalized directory part of a path + std::string GetDirectoryPart(const std::string& input); -// Return filename part of a path -std::string GetFileNamePart(const std::string& input); + // Return filename part of a path + std::string GetFileNamePart(const std::string& input); -// SDC interface often presents a path which does not use a dot to -// delimit name and extension: "FOODIR/FOO DSK" -> FOODIR/FOO.DSK -std::string FixFATPath(const std::string& sdcpath); -void FixFATPath(char* path, const char* sdcpath); + // Determine if path is a direcory + bool IsDirectory(const std::string& path); -// Determine if path is a direcory -bool IsDirectory(const std::string& path); + // Get path of loaded module or current application + std::string get_module_path(HMODULE module_handle); + // If path is in the application directory strip directory + std::string strip_application_path(std::string path); +} diff --git a/libcommon/libcommon.vcxproj b/libcommon/libcommon.vcxproj index 2d8dbec1..39752eb3 100644 --- a/libcommon/libcommon.vcxproj +++ b/libcommon/libcommon.vcxproj @@ -118,6 +118,7 @@ + @@ -138,6 +139,7 @@ + diff --git a/libcommon/src/core/FileOps.cpp b/libcommon/src/core/FileOps.cpp index 7b5a9326..10e0a941 100644 --- a/libcommon/src/core/FileOps.cpp +++ b/libcommon/src/core/FileOps.cpp @@ -15,6 +15,9 @@ // You should have received a copy of the GNU General Public License along with // VCC (Virtual Color Computer). If not, see . //////////////////////////////////////////////////////////////////////////////// + +// TODO: FileOps should be depreciated and functions moved to fileutil.cpp + #include #include #include diff --git a/libcommon/src/core/filesystem.cpp b/libcommon/src/core/filesystem.cpp index 6de559e0..2fe18171 100644 --- a/libcommon/src/core/filesystem.cpp +++ b/libcommon/src/core/filesystem.cpp @@ -15,39 +15,17 @@ // You should have received a copy of the GNU General Public License along with // VCC (Virtual Color Computer). If not, see . //////////////////////////////////////////////////////////////////////////////// +#include #include #include +//TODO filesystem.cpp should be depreciated +//TODO replace get_directory_from_path and get_filename with fileutil functions +//TODO move find_pak_module_path to point of use namespace vcc::core::utils { - - - LIBCOMMON_EXPORT std::string get_module_path(HMODULE module_handle) - { - std::string text(MAX_PATH, 0); - - for (;;) - { - DWORD ret = GetModuleFileName(module_handle, &text[0], text.size()); - if (ret == 0) - { - // An error occurred; return an empty string - return {}; - } - - if (ret < text.size()) - { - text.resize(ret); - - return text; - } - - // Buffer was too small, double its size and try again - text.resize(text.size() * 2); - } - } - + // TODO: move this LIBCOMMON_EXPORT std::string find_pak_module_path(std::string path) { if (path.empty()) @@ -58,7 +36,7 @@ namespace vcc::core::utils auto file_handle(CreateFile(path.c_str(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); if (file_handle == INVALID_HANDLE_VALUE) { - const auto application_path = get_directory_from_path(::vcc::core::utils::get_module_path()); + const auto application_path = get_directory_from_path(::VCC::Util::get_module_path(NULL)); const auto alternate_path = application_path + path; file_handle = CreateFile(alternate_path.c_str(), 0, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (file_handle == INVALID_HANDLE_VALUE) @@ -72,6 +50,7 @@ namespace vcc::core::utils return path; } + // TODO: replace with ::VCC::Util::GetDirectoryPart(const std::string& input) LIBCOMMON_EXPORT std::string get_directory_from_path(std::string path) { const auto last_separator(path.find_last_of('\\')); @@ -83,18 +62,7 @@ namespace vcc::core::utils return path; } - LIBCOMMON_EXPORT std::string strip_application_path(std::string path) - { - const auto module_path = get_directory_from_path(vcc::core::utils::get_module_path(nullptr)); - auto temp_path(get_directory_from_path(path)); - if (module_path == temp_path) // If they match remove the Path - { - path = get_filename(path); - } - - return path; - } - + //TODO: replace with ::VCC::Util::GetFileNamePart(const std::string& input) LIBCOMMON_EXPORT std::string get_filename(std::string path) { const auto last_seperator = path.find_last_of('\\'); @@ -107,5 +75,4 @@ namespace vcc::core::utils return path; } - } diff --git a/libcommon/src/core/fileutil.cpp b/libcommon/src/core/fileutil.cpp new file mode 100644 index 00000000..67bd7603 --- /dev/null +++ b/libcommon/src/core/fileutil.cpp @@ -0,0 +1,146 @@ +//#define USE_LOGGING +//====================================================================== +// General purpose Host file utilities. EJ Jaquay 2026 +// +// This file is part of VCC (Virtual Color Computer). +// Vcc is Copyright 2015 by Joseph Forgione +// +// VCC (Virtual Color Computer) is free software, you can redistribute +// and/or modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// VCC (Virtual Color Computer) is distributed in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +// See the GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with VCC (Virtual Color Computer). If not, see +// . +// +//====================================================================== + +#include +#include +#include +#include +#include + +// FIXME: directory names in libcommon are wrong +// libcommon/include/vcc/core should be libcommon/include/vcc/util +// libcommon/src/core should be libcommon/include/util + +namespace VCC::Util +{ + //---------------------------------------------------------------------- + // Get most recent windows error text + //---------------------------------------------------------------------- + std::string LastErrorString() + { + DWORD error_code = GetLastError(); + char msg[256]; + DWORD len = FormatMessageA( + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error_code, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msg, sizeof(msg), nullptr + ); + if (len == 0) return "Unknown error"; + return std::string(msg, len); + } + + // Only use this overload as argument to debug logger (DLOG) + const char* LastErrorTxt() + { + thread_local static std::string buffer; + buffer = LastErrorString(); + return buffer.c_str(); + } + + //TODO: This is generic, move to something for generic utils + //---------------------------------------------------------------------- + // Return copy of string with spaces trimmed from end of a string + //---------------------------------------------------------------------- + std::string trim_right_spaces(const std::string& s) + { + size_t end = s.find_last_not_of(' '); + if (end == std::string::npos) + return {}; + return s.substr(0, end + 1); + } + + //---------------------------------------------------------------------- + // Return slash normalized directory part of a path + //---------------------------------------------------------------------- + std::string GetDirectoryPart(const std::string& input) + { + std::filesystem::path p(input); + std::string out = p.parent_path().string(); + FixDirSlashes(out); + return out; + } + + //---------------------------------------------------------------------- + // Return filename part of a path + //---------------------------------------------------------------------- + std::string GetFileNamePart(const std::string& input) + { + std::filesystem::path p(input); + return p.filename().string(); + } + + //---------------------------------------------------------------------- + // Determine if path is a direcory + //---------------------------------------------------------------------- + bool IsDirectory(const std::string& path) + { + std::error_code ec; + return std::filesystem::is_directory(path, ec) && !ec; + } + + //------------------------------------------------------------------- + // Convert path directory backslashes to forward slashes + //------------------------------------------------------------------- + void FixDirSlashes(std::string &dir) + { + if (dir.empty()) return; + std::replace(dir.begin(), dir.end(), '\\', '/'); + if (dir.back() == '/') dir.pop_back(); + } + + //------------------------------------------------------------------- + // Get the file path of a loaded module + // Current exe path is returned if module_handle is null + //------------------------------------------------------------------- + std::string get_module_path(HMODULE module_handle) + { + std::string text(MAX_PATH, '\0'); + for (;;) { + DWORD ret = GetModuleFileName(module_handle, &text[0], text.size()); + if (ret == 0) + return {}; // error + + if (ret < text.size() - 1) { + text.resize(ret); + return text; + } + // truncated grow and retry + text.resize(text.size() * 2); + } + } + + //------------------------------------------------------------------- + // If path directory matches application directory strip directory + //------------------------------------------------------------------- + std::string strip_application_path(std::string path) + { + auto app = get_module_path(nullptr); + auto app_dir = GetDirectoryPart(app); + auto path_dir = GetDirectoryPart(path); + if (path_dir == app_dir) { + path = GetFileNamePart(path); + } + return path; + } +} diff --git a/sdc/fileutil.cpp b/sdc/fileutil.cpp deleted file mode 100644 index 6e10442d..00000000 --- a/sdc/fileutil.cpp +++ /dev/null @@ -1,232 +0,0 @@ -//define USE_LOGGING -//====================================================================== -// General purpose Host file utilities. EJ Jaquay 2026 -// -// This file is part of VCC (Virtual Color Computer). -// Vcc is Copyright 2015 by Joseph Forgione -// -// VCC (Virtual Color Computer) is free software, you can redistribute -// and/or modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation, either version 3 of -// the License, or (at your option) any later version. -// -// VCC (Virtual Color Computer) is distributed in the hope that it will -// be useful, but WITHOUT ANY WARRANTY; without even the implied -// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -// See the GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with VCC (Virtual Color Computer). If not, see -// . -// -//====================================================================== - -#include -#include -#include -#include -#include "fileutil.h" - -//---------------------------------------------------------------------- -// Get most recent windows error text -//---------------------------------------------------------------------- -const char * LastErrorTxt() { - static char msg[200]; - DWORD error_code = GetLastError(); - FormatMessageA( FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error_code, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - msg, sizeof(msg), nullptr ); - return msg; -} -std::string LastErrorString() { - return LastErrorTxt(); -} - -//---------------------------------------------------------------------- -// Return copy of string with spaces trimmed from end of a string -//---------------------------------------------------------------------- -std::string trim_right_spaces(const std::string& s) -{ - size_t end = s.find_last_not_of(' '); - if (end == std::string::npos) - return {}; - return s.substr(0, end + 1); -} - -//---------------------------------------------------------------------- -// Return slash normalized directory part of a path -//---------------------------------------------------------------------- -std::string GetDirectoryPart(const std::string& input) -{ - std::filesystem::path p(input); - std::string out = p.parent_path().string(); - FixDirSlashes(out); - return out; -} - -//---------------------------------------------------------------------- -// Return filename part of a path -//---------------------------------------------------------------------- -std::string GetFileNamePart(const std::string& input) -{ - std::filesystem::path p(input); - return p.filename().string(); -} - -//---------------------------------------------------------------------- -// Determine if path is a direcory -//---------------------------------------------------------------------- -bool IsDirectory(const std::string& path) -{ - std::error_code ec; - return std::filesystem::is_directory(path, ec) && !ec; -} - -//------------------------------------------------------------------- -// Convert path directory backslashes to forward slashes -//------------------------------------------------------------------- -void FixDirSlashes(std::string &dir) -{ - if (dir.empty()) return; - std::replace(dir.begin(), dir.end(), '\\', '/'); - if (dir.back() == '/') dir.pop_back(); -} -void FixDirSlashes(char *dir) -{ - if (!dir) return; - std::string tmp(dir); - FixDirSlashes(tmp); - strcpy(dir, tmp.c_str()); -} - -//------------------------------------------------------------------- -// Copy string to fixed size char array (non terminated) -//------------------------------------------------------------------- -template -void copy_to_fixed_char(char (&dest)[N], const std::string& src) -{ - size_t i = 0; - for (; i < src.size() && i < N; ++i) - dest[i] = src[i]; - for (; i < N; ++i) - dest[i] = ' '; -} - -//------------------------------------------------------------------- -// Convert LFN to FAT filename parts, 8 char name, 3 char ext -// A LNF file is less than 4GB and has a short (8.3) name. -//------------------------------------------------------------------- -void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn) -{ - // Special case: parent directory - if (lfn == "..") { - copy_to_fixed_char(name, ".."); - std::fill(ext, ext + 3, ' '); - return; - } - - size_t dot = lfn.find('.'); - std::string base, extension; - - if (dot == std::string::npos) { - base = lfn; - } else { - base = lfn.substr(0, dot); - extension = lfn.substr(dot + 1); - } - - copy_to_fixed_char(name, base); - copy_to_fixed_char(ext, extension); -} - -//------------------------------------------------------------------- -// Convert FAT filename parts to LFN. Returns empty string if invalid -//------------------------------------------------------------------- -std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]) -{ - std::string base(name, 8); - std::string extension(ext, 3); - - base = trim_right_spaces(base); - extension = trim_right_spaces(extension); - - if (base == ".." && extension.empty()) - return ".."; - - std::string lfn = base; - - if (!extension.empty()) - lfn += "." + extension; - - if (lfn.empty()) - return {}; - - if (!PathIsLFNFileSpecA(lfn.c_str())) - return {}; - - return lfn; -} - -//------------------------------------------------------------------- -// Convert string containing possible FAT name and extension to an -// LFN string. Returns empty string if invalid LFN -//------------------------------------------------------------------- -std::string NormalizeInputToLFN(const std::string& s) -{ - if (s.empty()) return {}; - if (s.size() > 11) return {}; - if (s == "..") return ".."; - - // LFN candidate - if (s.find('.') != std::string::npos) { - if (!PathIsLFNFileSpecA(s.c_str())) - return {}; // invalid - return s; - } - - // SFN candidate - char name[8]; - char ext[3]; - sfn_from_lfn(name,ext,s); - return lfn_from_sfn(name,ext); -} - -//---------------------------------------------------------------------- -// A file path may use 11 char FAT format which does not use a separater -// between name and extension. User is free to use standard dot format. -// "FOODIR/FOO.DSK" = FOODIR/FOO.DSK -// "FOODIR/FOO DSK" = FOODIR/FOO.DSK -// "FOODIR/ALONGFOODSK" = FOODIR/ALONGFOO.DSK -//---------------------------------------------------------------------- -std::string FixFATPath(const std::string& sdcpath) -{ - std::filesystem::path p(sdcpath); - - auto chop = [](std::string s) { - size_t space = s.find(' '); - if (space != std::string::npos) s.erase(space); - return s; - }; - - std::string fname = p.filename().string(); - if (fname.length() == 11 && fname.find('.') == std::string::npos) { - auto nam = chop(fname.substr(0,8)); - auto ext = chop(fname.substr(8,3)); - fname = ext.empty() ? nam : nam + "." + ext; - } - - std::filesystem::path out = p.parent_path() / fname; - - DLOG_C("FixFATPath in %s out %s\n",sdcpath.c_str(),out.generic_string().c_str()); - return out.generic_string(); -} - -void FixFATPath(char* path, const char* sdcpath) -{ - std::string fixed = FixFATPath(std::string(sdcpath)); - strncpy(path, fixed.c_str(), MAX_PATH); - path[MAX_PATH - 1] = '\0'; -} - diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index 47b604d4..f8595a03 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -141,8 +141,20 @@ #include #include +//#include "fileutil.h" +#include + #include "sdc.h" -#include "fileutil.h" + +#ifdef VCC +#pragma message("VCC is defined as a macro!") +#endif + +#ifdef Util +#pragma message("Util is defined as a macro!") +#endif + +namespace util = VCC::Util; static ::vcc::devices::rtc::cloud9 cloud9_rtc; @@ -223,14 +235,18 @@ void SelectCardBox(); void UpdateFlashItem(int); void InitCardBox(); void InitFlashBoxes(); -void FitEditTextPath(HWND, int, const char *); +void FitEditTextPath(HWND, int, const std::string&); void InitSDC(); void LoadRom(unsigned char); void ParseStartup(); -bool SearchFile(const char *); +bool SearchFile(const std::string&); void UnloadDisk(int); void GetFileList(const std::string&); void SortFileList(); +std::string FixFATPath(const std::string&); +std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]); +void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn); +template void copy_to_fixed_char(char (&dest)[N], const std::string& src); void SDCReadSector(); void SDCStreamImage(); @@ -472,7 +488,7 @@ SDC_Configure(HWND hDlg, UINT message, WPARAM wParam, LPARAM /*lParam*/) GetWindowText(hSDCardBox,tmp,MAX_PATH); if (*tmp != '\0') { gCurDir = tmp; - FixDirSlashes(gCurDir); + util::FixDirSlashes(gCurDir); //strncpy(SDCard,tmp,MAX_PATH); } } @@ -539,12 +555,12 @@ void LoadConfig() ("SDC", "SDCardPath", "", tmp, MAX_PATH, IniFile); gSDRoot = tmp; - FixDirSlashes(gSDRoot); + util::FixDirSlashes(gSDRoot); DLOG_C("LoadConfig ROMPath %s\n",ROMPath); DLOG_C("LoadConfig gSDRoot %s\n",gSDRoot); - if (!IsDirectory(gSDRoot)) { + if (!util::IsDirectory(gSDRoot)) { MessageBox (gVccWindow,"Invalid SDCard Path in VCC init","Error",0); } @@ -564,7 +580,7 @@ void LoadConfig() //------------------------------------------------------------ bool SaveConfig(HWND hDlg) { - if (!IsDirectory(gSDRoot)) { + if (!util::IsDirectory(gSDRoot)) { MessageBox(gVccWindow,"Invalid SDCard Path\n","Error",0); return false; } @@ -587,21 +603,21 @@ bool SaveConfig(HWND hDlg) return true; } -//------------------------------------------------------------ -// Fit path in edit text box (Box must be ES_READONLY) -//------------------------------------------------------------ -void FitEditTextPath(HWND hDlg, int ID, const char * path) { +void FitEditTextPath(HWND hDlg, int ID, const std::string& path) +{ HDC c; HWND h; RECT r; - char p[MAX_PATH]; + if ((c = GetDC(hDlg)) == NULL) return; if ((h = GetDlgItem(hDlg, ID)) == NULL) return; + GetClientRect(h, &r); - strncpy(p, path, MAX_PATH); - PathCompactPath(c, p, r.right); - FixDirSlashes(p); - SetWindowText(h, p); + std::string p = path; + p.resize(MAX_PATH); + PathCompactPathA(c, p.data(), r.right); + util::FixDirSlashes(p); + SetWindowTextA(h, p.c_str()); ReleaseDC(hDlg, c); } @@ -697,7 +713,7 @@ void SelectCardBox() if (SHGetPathFromIDList(pidl, tmp)) { gSDRoot = tmp; - FixDirSlashes(gSDRoot); + util::FixDirSlashes(gSDRoot); SendMessage(hSDCardBox, WM_SETTEXT, 0, (LPARAM)gSDRoot.c_str()); } @@ -1803,12 +1819,10 @@ void SDCMountNewDisk (int drive, const char * path, int raw) // Close and clear previous entry UnloadDisk(drive); - //CloseDrive(drive); - //gCocoDisk[drive] = {}; // Convert from SDC format - char file[MAX_PATH]; - FixFATPath(file,path); + char file[MAX_PATH] {}; + strncpy(file,FixFATPath(path).c_str(),MAX_PATH-1); // Look for pre-existing file if (SearchFile(file)) { @@ -1859,8 +1873,8 @@ void SDCMountDisk (int drive, const char * path, int raw) } // Convert from SDC format - char file[MAX_PATH]; - FixFATPath(file,path); //TODO use new routine + char file[MAX_PATH] {}; + strncpy(file,FixFATPath(path).c_str(),MAX_PATH-1); // Look for the file bool found = SearchFile(file); @@ -2324,7 +2338,7 @@ void SDCSetCurDir(const char* branch) if (strcmp(branch, "..") == 0) { cur = cur.parent_path(); gCurDir = cur.string(); - FixDirSlashes(gCurDir); + util::FixDirSlashes(gCurDir); DLOG_C("SetCurdir back %s\n", gCurDir.c_str()); IFace.status = STA_NORMAL; return; @@ -2364,7 +2378,7 @@ void SDCSetCurDir(const char* branch) // String based host files gCurDir = newCur.string(); - FixDirSlashes(gCurDir); + util::FixDirSlashes(gCurDir); DLOG_C("SetCurdir set to '%s'\n", gCurDir.c_str()); IFace.status = STA_NORMAL; @@ -2375,19 +2389,17 @@ void SDCSetCurDir(const char* branch) //---------------------------------------------------------------------- bool SDCInitiateDir(const char * path) { + bool rc; DLOG_C("SDCInitiateDir '%s'\n",path); - bool rc; - // Append "*.*" if last char in path was '/'; - int l = strlen(path); + // Append "*.*" if last char in path is '/'; + size_t l = strlen(path); if (path[l-1] == '/') { - char tmp[MAX_PATH]; - strncpy(tmp,path,MAX_PATH); - strncat(tmp,"*.*",MAX_PATH); - rc = SearchFile(tmp); + rc = SearchFile(std::string(path) + "*.*"); } else { rc = SearchFile(path); } + if (rc) { IFace.status = STA_NORMAL; return true; @@ -2445,13 +2457,13 @@ void GetFileList(const std::string& pattern) search_path += pattern; // A directory lookup if pattern name is "*" or "*.*"; - std::string fnpat = GetFileNamePart(pattern); + std::string fnpat = util::GetFileNamePart(pattern); bool dir_lookup = (fnpat == "*" || fnpat == "*.*"); - std::string search_dir = GetDirectoryPart(search_path); + std::string search_dir = util::GetDirectoryPart(search_path); // Include ".." if dir_lookup and search_dir is not SDRoot - if (dir_lookup && GetDirectoryPart(search_path) != gSDRoot) { + if (dir_lookup && util::GetDirectoryPart(search_path) != gSDRoot) { gFileList.append({"..", 0, true, false}); } @@ -2494,16 +2506,136 @@ void SortFileList() //---------------------------------------------------------------------- // Host File search. //---------------------------------------------------------------------- -bool SearchFile(const char* pattern) +bool SearchFile(const std::string& pat) { - std::string s = pattern; - GetFileList(s); - - DLOG_C("SearchFile found %d pat %s\n",gFileList.files.size(),s.c_str()); - + // Fill gFileList with files found. + GetFileList(pat); // Update menu to show next disk status BuildCartridgeMenu(); - + // Return true if something found return (gFileList.files.size() > 0); } +//---------------------------------------------------------------------- +// A file path may use 11 char FAT format which does not use a separater +// between name and extension. User is free to use standard dot format. +// "FOODIR/FOO.DSK" = FOODIR/FOO.DSK +// "FOODIR/FOO DSK" = FOODIR/FOO.DSK +// "FOODIR/ALONGFOODSK" = FOODIR/ALONGFOO.DSK +//---------------------------------------------------------------------- +std::string FixFATPath(const std::string& sdcpath) +{ + std::filesystem::path p(sdcpath); + + auto chop = [](std::string s) { + size_t space = s.find(' '); + if (space != std::string::npos) s.erase(space); + return s; + }; + + std::string fname = p.filename().string(); + if (fname.length() == 11 && fname.find('.') == std::string::npos) { + auto nam = chop(fname.substr(0,8)); + auto ext = chop(fname.substr(8,3)); + fname = ext.empty() ? nam : nam + "." + ext; + } + + std::filesystem::path out = p.parent_path() / fname; + + DLOG_C("FixFATPath in %s out %s\n",sdcpath.c_str(),out.generic_string().c_str()); + return out.generic_string(); +} + + +//------------------------------------------------------------------- +// Convert string containing possible FAT name and extension to an +// LFN string. Returns empty string if invalid LFN +//------------------------------------------------------------------- +std::string NormalizeInputToLFN(const std::string& s) +{ + if (s.empty()) return {}; + if (s.size() > 11) return {}; + if (s == "..") return ".."; + + // LFN candidate + if (s.find('.') != std::string::npos) { + if (!PathIsLFNFileSpecA(s.c_str())) + return {}; // invalid + return s; + } + + // SFN candidate + char name[8]; + char ext[3]; + sfn_from_lfn(name,ext,s); + return lfn_from_sfn(name,ext); +} + +//------------------------------------------------------------------- +// Convert LFN to FAT filename parts, 8 char name, 3 char ext +// A LNF file is less than 4GB and has a short (8.3) name. +//------------------------------------------------------------------- +void sfn_from_lfn(char (&name)[8], char (&ext)[3], const std::string& lfn) +{ + // Special case: parent directory + if (lfn == "..") { + copy_to_fixed_char(name, ".."); + std::fill(ext, ext + 3, ' '); + return; + } + + size_t dot = lfn.find('.'); + std::string base, extension; + + if (dot == std::string::npos) { + base = lfn; + } else { + base = lfn.substr(0, dot); + extension = lfn.substr(dot + 1); + } + + copy_to_fixed_char(name, base); + copy_to_fixed_char(ext, extension); +} + +//------------------------------------------------------------------- +// Convert FAT filename parts to LFN. Returns empty string if invalid +//------------------------------------------------------------------- +std::string lfn_from_sfn(const char (&name)[8], const char (&ext)[3]) +{ + std::string base(name, 8); + std::string extension(ext, 3); + + base = util::trim_right_spaces(base); + extension = util::trim_right_spaces(extension); + + if (base == ".." && extension.empty()) + return ".."; + + std::string lfn = base; + + if (!extension.empty()) + lfn += "." + extension; + + if (lfn.empty()) + return {}; + + if (!PathIsLFNFileSpecA(lfn.c_str())) + return {}; + + return lfn; +} + +//------------------------------------------------------------------- +// Copy string to fixed size char array (non terminated) +//------------------------------------------------------------------- +template +void copy_to_fixed_char(char (&dest)[N], const std::string& src) +{ + size_t i = 0; + for (; i < src.size() && i < N; ++i) + dest[i] = src[i]; + for (; i < N; ++i) + dest[i] = ' '; +} + diff --git a/sdc/sdc.vcxproj b/sdc/sdc.vcxproj index 34062aaa..063f2b88 100644 --- a/sdc/sdc.vcxproj +++ b/sdc/sdc.vcxproj @@ -97,12 +97,10 @@ - - From ecb86150dafce55aa729c4709f2be03bd0befc42 Mon Sep 17 00:00:00 2001 From: ejaquay Date: Fri, 16 Jan 2026 22:45:10 -0500 Subject: [PATCH 11/11] SDC correct DLOG LastErrorTxt argument --- sdc/sdc.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index f8595a03..af3d8746 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -1616,7 +1616,7 @@ void SDCStreamImage() } if (!SDCReadDrive(stream_cmdcode,stream_lsn)) { - DLOG_C("SDCStreamImage read error %s\n",LastErrorTxt()); + DLOG_C("SDCStreamImage read error %s\n",util::LastErrorTxt()); IFace.status = STA_FAIL; streaming = 0; return; @@ -1645,7 +1645,7 @@ void SDCWriteSector() } if (!WriteFile(gCocoDisk[drive].hFile,IFace.blkbuf, gCocoDisk[drive].sectorsize,&cnt,nullptr)) { - DLOG_C("SDCWriteSector %d %s\n",drive,LastErrorTxt()); + DLOG_C("SDCWriteSector %d %s\n",drive,util::LastErrorTxt()); IFace.status = STA_FAIL; return; } @@ -1744,7 +1744,7 @@ bool SDCSeekSector(unsigned char cmdcode, unsigned int lsn) pos.QuadPart = lsn * gCocoDisk[drive].sectorsize + gCocoDisk[drive].headersize; if (!SetFilePointerEx(gCocoDisk[drive].hFile,pos,nullptr,FILE_BEGIN)) { - DLOG_C("SDCSeekSector error %s\n",LastErrorTxt()); + DLOG_C("SDCSeekSector error %s\n",util::LastErrorTxt()); return false; } return true; @@ -1768,7 +1768,7 @@ bool SDCReadDrive(unsigned char cmdcode, unsigned int lsn) } if (!ReadFile(gCocoDisk[drive].hFile,buf,gCocoDisk[drive].sectorsize,&cnt,nullptr)) { - DLOG_C("SDCReadDrive %d %s\n",drive,LastErrorTxt()); + DLOG_C("SDCReadDrive %d %s\n",drive,util::LastErrorTxt()); return false; } @@ -1989,7 +1989,7 @@ void SDCOpenNew( int drive, const char * path, int raw) if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { DLOG_C("SDCOpenNew fail %d file %s\n",drive,gCocoDisk[drive].fullpath); - DLOG_C("... %s\n",LastErrorTxt()); + DLOG_C("... %s\n",util::LastErrorTxt()); IFace.status = STA_FAIL | STA_WIN_ERROR; return; } @@ -2074,7 +2074,7 @@ void SDCOpenFound (int drive,int raw) if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { DLOG_C("SDCOpenFound fail %d file %s\n",drive,file.c_str()); - DLOG_C("... %s\n",LastErrorTxt()); + DLOG_C("... %s\n",util::LastErrorTxt()); int ecode = GetLastError(); if (ecode == ERROR_SHARING_VIOLATION) { IFace.status = STA_FAIL | STA_INUSE; @@ -2179,7 +2179,7 @@ void SDCOpenFound (int drive,int raw) nullptr,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,nullptr); if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { DLOG_C("SDCOpenFound reopen fail %d\n",drive); - DLOG_C("... %s\n",LastErrorTxt()); + DLOG_C("... %s\n",util::LastErrorTxt()); IFace.status = STA_FAIL | STA_WIN_ERROR; return; }