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/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/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/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/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/libcommon/include/vcc/core/fileutil.h b/libcommon/include/vcc/core/fileutil.h new file mode 100644 index 00000000..857a7bd2 --- /dev/null +++ b/libcommon/include/vcc/core/fileutil.h @@ -0,0 +1,62 @@ +//====================================================================== +// 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 + +// 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 +//========================================================================= + +namespace VCC::Util { + + // Get most recent windows error text + std::string LastErrorString(); + const char * LastErrorTxt(); + + // Convert backslashes to slashes in directory string + void FixDirSlashes(std::string &dir); + + // 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 filename part of a path + std::string GetFileNamePart(const std::string& input); + + // 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/mpi/configuration_dialog.cpp b/mpi/configuration_dialog.cpp index b405adc5..858a6b29 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 @@ -295,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 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), diff --git a/sdc/sdc.cpp b/sdc/sdc.cpp index 76ef1697..af3d8746 100644 --- a/sdc/sdc.cpp +++ b/sdc/sdc.cpp @@ -1,6 +1,6 @@ -// SDC simulator DLL -// -// By E J Jaquay 2025 +//#define USE_LOGGING +//====================================================================== +// SDC simulator. EJ Jaquay 2025 // // This file is part of VCC (Virtual Color Computer). // Vcc is Copyright 2015 by Joseph Forgione @@ -19,22 +19,21 @@ // along with VCC (Virtual Color Computer). If not, see // . // -//---------------------------------------------------------------------- +//====================================================================== // -// 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 @@ -46,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 @@ -54,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 @@ -67,22 +69,11 @@ // 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. +// 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. // -// 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. -// -// 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. // @@ -123,218 +114,97 @@ // 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. -// -//---------------------------------------------------------------------- -//#define USE_LOGGING +// SDC-DOS is typically in bank zero. +//====================================================================== -#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" - -//====================================================================== -//====================================================================== -static ::vcc::devices::rtc::cloud9 cloud9_rtc; +//#include "fileutil.h" +#include -//====================================================================== -// Private functions -//====================================================================== +#include "sdc.h" -LRESULT CALLBACK SDC_Configure(HWND, UINT, WPARAM, LPARAM); -void LoadConfig(); -bool SaveConfig(HWND); -void BuildCartridgeMenu(); -void SelectCardBox(); -void UpdateFlashItem(int); -void InitCardBox(); -void InitFlashBoxes(); -void FitEditTextPath(HWND, int, const char *); +#ifdef VCC +#pragma message("VCC is defined as a macro!") +#endif -void AppendPathChar(char *,char c); -bool IsDirectory(const char *); -char * LastErrorTxt(); -void ConvertSlashes(char *); +#ifdef Util +#pragma message("Util is defined as a macro!") +#endif -void SDCInit(); -void LoadRom(unsigned char); -void SDCWrite(unsigned char,unsigned char); -unsigned char SDCRead(unsigned char port); +namespace util = VCC::Util; -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 *); -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 *); -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(); +static ::vcc::devices::rtc::cloud9 cloud9_rtc; //====================================================================== // Globals //====================================================================== -// Idle Status counter -int idle_ctr = 0; +static HINSTANCE gModuleInstance; // Dll handle +static int idle_ctr = 0; // Idle Status counter -// 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]; - -// Host paths for SDC +// Callback pointers +static void* gCallbackContext = nullptr; +static PakAssertInteruptHostCallback AssertIntCallback = nullptr; +static PakAppendCartridgeMenuHostCallback CartMenuCallback = nullptr; 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 -// 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; -}; -#pragma pack() -static struct FileRecord DirPage[16]; - -// 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]; - struct FileRecord filerec; -}; -SDC_disk_t SDC_disk[2] = {}; +static HWND gVccWindow = nullptr; +static HWND hConfigureDlg = nullptr; +static HWND hSDCardBox = nullptr; +static HWND hStartupBank = 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; + +static std::string gSDRoot {}; // SD card root directory +static std::string gCurDir {}; // Current directory relative to root + +// SDC CoCo Interface +static FileList gFileList {}; +static struct FileRecord gDirPage[16] {}; +static CocoDisk gCocoDisk[2] {}; +static Interface IFace {}; // 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; - -// config control handles -static HWND hConfigureDlg = nullptr; -static HWND hSDCardBox = nullptr; -static HWND hStartupBank = nullptr; - // Streaming control static int streaming; static unsigned char stream_cmdcode; static unsigned int stream_lsn; -static char Status[16] = {}; - // Floppy I/O static char FlopDrive = 0; static char FlopData = 0; @@ -351,18 +221,100 @@ static int EDBOXES[8] = {ID_TEXT0,ID_TEXT1,ID_TEXT2,ID_TEXT3, 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]; +static char ROMPath[MAX_PATH]; + +//====================================================================== +// Functions +//====================================================================== + +LRESULT CALLBACK SDC_Configure(HWND, UINT, WPARAM, LPARAM); +void LoadConfig(); +bool SaveConfig(HWND); +void BuildCartridgeMenu(); +void SelectCardBox(); +void UpdateFlashItem(int); +void InitCardBox(); +void InitFlashBoxes(); +void FitEditTextPath(HWND, int, const std::string&); +void InitSDC(); +void LoadRom(unsigned char); +void ParseStartup(); +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(); +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&); -// DLL Callback pointers -static void* gCallbackContext = nullptr; -static PakAssertInteruptHostCallback AssertIntCallback = nullptr; -static PakAppendCartridgeMenuHostCallback CartMenuCallback = nullptr; //====================================================================== -// DLL exports +// DLL interface //====================================================================== 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]; @@ -384,29 +336,13 @@ extern "C" return string_buffer; } - __declspec(dllexport) void PakInitialize( - void* const callback_context, - const char* const configuration_path, - HWND hVccWnd, - const cpak_callbacks* const callbacks) - { - 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() { CloseCartDialog(hConfigureDlg); hConfigureDlg = nullptr; - CloseDrive(0); - CloseDrive(1); + UnloadDisk(0); + UnloadDisk(1); } // Write to port @@ -432,7 +368,7 @@ extern "C" __declspec(dllexport) void PakReset() { DLOG_C("PakReset\n"); - SDCInit(); + InitSDC(); } // Dll export run config dialog @@ -447,7 +383,7 @@ extern "C" ShowWindow(hConfigureDlg,1); break; case 11: - MountNext (0); + SDCMountNext (0); break; } BuildCartridgeMenu(); @@ -457,13 +393,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 @@ -471,7 +407,7 @@ extern "C" { adr &= 0x3FFF; if (EnableBankWrite) { - return WriteFlashBank(adr); + return SDCWriteFlashBank(adr); } else { BankWriteState = 0; // Any read resets write state return(PakRom[adr]); @@ -479,9 +415,6 @@ extern "C" } } -//------------------------------------------------------------- -// Dll Main here so it can use PakTerminate -//------------------------------------------------------------- BOOL WINAPI DllMain(HINSTANCE hinst, DWORD reason, LPVOID rsvd) { if (reason == DLL_PROCESS_ATTACH) { @@ -505,11 +438,15 @@ 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); - strcat(tmp," (load next)"); + strcpy(tmp,gCocoDisk[0].name); + 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); @@ -549,7 +486,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; + util::FixDirSlashes(gCurDir); + //strncpy(SDCard,tmp,MAX_PATH); + } } return TRUE; case ID_UPDATE0: @@ -604,13 +545,23 @@ SDC_Configure(HWND hDlg, UINT message, WPARAM wParam, LPARAM /*lParam*/) //------------------------------------------------------------ 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", "", SDCard, MAX_PATH, IniFile); + ("SDC", "SDCardPath", "", tmp, MAX_PATH, IniFile); + + gSDRoot = tmp; + util::FixDirSlashes(gSDRoot); - if (!IsDirectory(SDCard)) { - MessageBox (nullptr,"Invalid SDCard Path in VCC init","Error",0); + DLOG_C("LoadConfig ROMPath %s\n",ROMPath); + DLOG_C("LoadConfig gSDRoot %s\n",gSDRoot); + + if (!util::IsDirectory(gSDRoot)) { + MessageBox (gVccWindow,"Invalid SDCard Path in VCC init","Error",0); } for (int i=0;i<8;i++) { @@ -629,11 +580,11 @@ void LoadConfig() //------------------------------------------------------------ bool SaveConfig(HWND hDlg) { - if (!IsDirectory(SDCard)) { - MessageBox(nullptr,"Invalid SDCard Path\n","Error",0); + if (!util::IsDirectory(gSDRoot)) { + MessageBox(gVccWindow,"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]; @@ -652,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); - ConvertSlashes(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); } @@ -693,7 +644,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()); } //------------------------------------------------------------ @@ -715,7 +666,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); @@ -732,79 +683,143 @@ 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; + util::FixDirSlashes(gSDRoot); + SendMessage(hSDCardBox, WM_SETTEXT, 0, (LPARAM)gSDRoot.c_str()); + } + CoTaskMemFree(pidl); } - - ConvertSlashes(SDCard); - SendMessage(hSDCardBox, WM_SETTEXT, 0, (LPARAM)SDCard); } //===================================================================== -// SDC Simulation +// SDC startup //====================================================================== //---------------------------------------------------------------------- // Init the controller. This gets called by PakReset //---------------------------------------------------------------------- -void SDCInit() +void InitSDC() { - DLOG_C("\nSDCInit\n"); #ifdef USE_LOGGING - MoveWindow(GetConsoleWindow(),0,0,300,800,TRUE); + DLOG_C("\nInitSDC\n"); + MoveWindow(GetConsoleWindow(),0,0,400,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() - SDC_disk[0] = {}; - SDC_disk[1] = {}; + gCocoDisk[0] = {}; + gCocoDisk[1] = {}; + + gFileList = {}; // Process the startup config file ParseStartup(); // init the interface - IF = {}; + IFace = {}; 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 //------------------------------------------------------------- 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 @@ -813,12 +828,13 @@ void LoadRom(unsigned char bank) h_RomFile = nullptr; } + // 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); 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; @@ -829,7 +845,6 @@ void LoadRom(unsigned char bank) BankDirty = 0; } - DLOG_C("LoadRom load flash bank %d\n",bank); RomFile = FlashFile[bank]; CurrentBank = bank; @@ -842,13 +857,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; } @@ -865,89 +883,46 @@ void LoadRom(unsigned char bank) return; } -//---------------------------------------------------------------------- -// Parse the startup.cfg file -//---------------------------------------------------------------------- -void ParseStartup() -{ - char buf[MAX_PATH+10]; - if (!IsDirectory(SDCard)) { - 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"); - if (su == nullptr) { - DLOG_C("ParseStartup file not found,%s\n",buf); - return; - } - - // Strict single char followed by '=' then 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': - MountDisk(0,&buf[2],0); - break; - case '1': - MountDisk(1,&buf[2],0); - break; - case 'D': - SetCurDir(&buf[2]); - break; - } - } - fclose(su); -} +//===================================================================== +// SDC Interface +//===================================================================== //---------------------------------------------------------------------- // 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) - BlockReceive(data); + 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) - BlockReceive(data); + if (IFace.bufcnt > 0) + SDCBlockReceive(data); else - IF.param3 = data; + IFace.param3 = data; break; // Unhandled default: @@ -961,10 +936,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; @@ -989,23 +964,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: @@ -1023,33 +998,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) { - rpy = PickReplyByte(port); + 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) { - rpy = PickReplyByte(port); + if (IFace.bufcnt > 0) { + rpy = SDCPickReplyByte(port); } else { - rpy = IF.reply3; + rpy = IFace.reply3; } break; default: @@ -1057,19 +1032,37 @@ unsigned char SDCRead(unsigned char port) rpy = 0; break; } + // If not SDC latched do floppy controller simulation } 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); @@ -1081,45 +1074,191 @@ unsigned char SDCRead(unsigned char port) } //---------------------------------------------------------------------- -// Floppy I/O +// Dispatch SDC commands //---------------------------------------------------------------------- - -void FloppyCommand(unsigned char data) +void SDCCommand() { - unsigned char cmd = data >> 4; - switch (cmd) { - case 0: //RESTORE - FloppyRestore(); + switch (IFace.cmdcode & 0xF0) { + // Read sector + case 0x80: + SDCReadSector(); break; - case 1: //SEEK - FloppySeek(); + // Stream 512 byte sectors + case 0x90: + SDCStreamImage(); break; - //case 2: //STEP - //case 3: //STEPUPD - //case 4: //STEPIN - //case 5: //STEPINUPD + // Get drive info + case 0xC0: + SDCGetDriveInfo(); + break; + // Control SDC + case 0xD0: + SDCControl(); + break; + // Next two are block receive commands + case 0xA0: + case 0xE0: + IFace.status = STA_READY | STA_BUSY; + IFace.bufptr = IFace.blkbuf; + IFace.bufcnt = 256; + IFace.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 - FloppyReadDisk(); + case 9: //READSECTORM + SDCFloppyReadDisk(); break; - //case 9: //READSECTORM case 10: //WRITESECTOR - FloppyWriteDisk(); + 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 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("SDCFloppyCommand %d not implemented\n",cmd); break; } } +//---------------------------------------------------------------------- +// Get drive information +//---------------------------------------------------------------------- +void SDCGetDriveInfo() +{ + int drive = IFace.cmdcode & 1; + switch (IFace.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(); + IFace.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. + IFace.reply2 = 0x00; + IFace.reply3 = 0x01; + break; + } +} + +//---------------------------------------------------------------------- +// Update SD Commands. +//---------------------------------------------------------------------- +void SDCUpdateSD() +{ + switch (IFace.blkbuf[0]) { + case 0x4D: //M + SDCMountDisk(IFace.cmdcode&1,&IFace.blkbuf[2],0); + break; + case 0x6D: //m + SDCMountDisk(IFace.cmdcode&1,&IFace.blkbuf[2],1); + break; + case 0x4E: //N + SDCMountNewDisk(IFace.cmdcode&1,&IFace.blkbuf[2],0); + break; + case 0x6E: //n + SDCMountNewDisk(IFace.cmdcode&1,&IFace.blkbuf[2],1); + break; + case 0x44: //D + SDCSetCurDir(&IFace.blkbuf[2]); + break; + case 0x4C: //L + SDCInitiateDir(&IFace.blkbuf[2]); + break; + case 0x4B: //K + SDCMakeDirectory(&IFace.blkbuf[2]); + break; + case 0x52: //R + SDCRenameFile(&IFace.blkbuf[2]); + break; + case 0x58: //X + SDCKillFile(&IFace.blkbuf[2]); + break; + default: + DLOG_C("SDCUpdateSD %02x not Supported\n",IFace.blkbuf[0]); + IFace.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; @@ -1130,17 +1269,23 @@ void FloppyRestore() AssertIntCallback(gCallbackContext, INT_NMI, IS_NMI); } -// floppy seek -void FloppySeek() +//---------------------------------------------------------------------- +// floppy seek. No attempt is made to simulate seek times here. +//---------------------------------------------------------------------- +void SDCFloppySeek() { 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 -unsigned int FloppyLSN( +//---------------------------------------------------------------------- +// 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 SDCFloppyLSN( unsigned int drive, // 0 or 1 unsigned int track, // 0 to num tracks unsigned int sector // 1 to 18 @@ -1149,15 +1294,17 @@ 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(Status,16,"SDC:%d Rd %d,%d",CurrentBank,FlopDrive,lsn); - if (SeekSector(FlopDrive,lsn)) { - if (ReadFile(SDC_disk[FlopDrive].hFile,FlopRdBuf,256,&FlopRdCnt,nullptr)) { + snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,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; } else { @@ -1170,8 +1317,10 @@ void FloppyReadDisk() } } +//---------------------------------------------------------------------- // floppy write sector -void FloppyWriteDisk() +//---------------------------------------------------------------------- +void SDCFloppyWriteDisk() { // write not implemented int lsn = FlopTrack * 18 + FlopSector - 1; @@ -1179,14 +1328,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; @@ -1194,8 +1347,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) { @@ -1209,15 +1364,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) { @@ -1237,225 +1396,97 @@ 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; // 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) StreamImage(); + if ((IFace.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--; - *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: - WriteSector(); + SDCWriteSector(); break; case 0xE0: - UpdateSD(); + SDCUpdateSD(); break; default: - DLOG_C("BlockReceive invalid cmd %d\n",IF.cmdcode); - IF.status = STA_FAIL; + DLOG_C("SDCBlockReceive invalid cmd %d\n",IFace.cmdcode); + IFace.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; - } -} - -//---------------------------------------------------------------------- -// 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() +void SDCGetDirectoryLeaf() { - DLOG_C("GetDirectoryLeaf CurDir '%s'\n",CurDir); + namespace fs = std::filesystem; - char leaf[32]; - memset(leaf,0,32); - - // 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; + IFace.reply_status = STA_FAIL | STA_NOTFOUND; } + DLOG_C("SDCGetDirectoryLeaf CurDir '%s' leaf '%s' \n", gCurDir.c_str(),leaf); - IF.reply_mode=0; - LoadReply(leaf,32); -} - -//---------------------------------------------------------------------- -// 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; - } + IFace.reply_mode = 0; + SDCLoadReply(leaf, sizeof(leaf)); } //---------------------------------------------------------------------- // Flash control //---------------------------------------------------------------------- -void FlashControl(unsigned char data) +void SDCFlashControl(unsigned char data) { unsigned char bank = data & 7; EnableBankWrite = data & 0x80; @@ -1488,9 +1519,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 @@ -1537,79 +1568,6 @@ unsigned char WriteFlashBank(unsigned short adr) 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 / SDC_disk[drive].tracksectors; - int sec = lsn % SDC_disk[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) { - DLOG_C("SeekSector sside %d %d\n",drive,lsn); - lsn = 2 * SDC_disk[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 * SDC_disk[drive].sectorsize + SDC_disk[drive].headersize; - - if (!SetFilePointerEx(SDC_disk[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 (SDC_disk[drive].hFile == nullptr) { - DLOG_C("ReadDrive %d not open\n"); - return false; - } - - if (!SeekSector(cmdcode,lsn)) { - return false; - } - - if (!ReadFile(SDC_disk[drive].hFile,buf,SDC_disk[drive].sectorsize,&cnt,nullptr)) { - DLOG_C("ReadDrive %d %s\n",drive,LastErrorTxt()); - return false; - } - - if (cnt != SDC_disk[drive].sectorsize) { - DLOG_C("ReadDrive %d short read\n",drive); - return false; - } - - snprintf(Status,16,"SDC:%d Rd %d,%d",CurrentBank,drive,lsn); - LoadReply(buf,cnt); - return true; -} - //---------------------------------------------------------------------- // Read logical sector // cmdcode: @@ -1617,49 +1575,49 @@ 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; + unsigned int lsn = (IFace.param1 << 16) + (IFace.param2 << 8) + IFace.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.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; } //---------------------------------------------------------------------- // Stream image data //---------------------------------------------------------------------- -void StreamImage() +void SDCStreamImage() { // If already streaming continue if (streaming) { 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; - DLOG_C("StreamImage lsn %d\n",stream_lsn); + 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); } // 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)) { - DLOG_C("StreamImage done\n"); + if (stream_lsn > (gCocoDisk[drive].size/gCocoDisk[drive].sectorsize)) { + DLOG_C("SDCStreamImage done\n"); streaming = 0; return; } - if (!ReadDrive(stream_cmdcode,stream_lsn)) { - DLOG_C("StreamImage read error %s\n",LastErrorTxt()); - IF.status = STA_FAIL; + if (!SDCReadDrive(stream_cmdcode,stream_lsn)) { + DLOG_C("SDCStreamImage read error %s\n",util::LastErrorTxt()); + IFace.status = STA_FAIL; streaming = 0; return; } @@ -1669,71 +1627,71 @@ void StreamImage() //---------------------------------------------------------------------- // Write logical sector //---------------------------------------------------------------------- -void WriteSector() +void SDCWriteSector() { 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); + 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 (SDC_disk[drive].hFile == nullptr) { - IF.status = STA_FAIL; + if (gCocoDisk[drive].hFile == nullptr) { + IFace.status = STA_FAIL; return; } - if (!SeekSector(drive,lsn)) { - IF.status = STA_FAIL; + if (!SDCSeekSector(drive,lsn)) { + IFace.status = STA_FAIL; return; } - if (!WriteFile(SDC_disk[drive].hFile,IF.blkbuf, - SDC_disk[drive].sectorsize,&cnt,nullptr)) { - DLOG_C("WriteSector %d %s\n",drive,LastErrorTxt()); - IF.status = STA_FAIL; + if (!WriteFile(gCocoDisk[drive].hFile,IFace.blkbuf, + gCocoDisk[drive].sectorsize,&cnt,nullptr)) { + DLOG_C("SDCWriteSector %d %s\n",drive,util::LastErrorTxt()); + IFace.status = STA_FAIL; return; } - if (cnt != SDC_disk[drive].sectorsize) { - DLOG_C("WriteSector %d short write\n",drive); - IF.status = STA_FAIL; + if (cnt != gCocoDisk[drive].sectorsize) { + DLOG_C("SDCWriteSector %d short write\n",drive); + IFace.status = STA_FAIL; return; } - IF.status = 0; + IFace.status = 0; return; } //---------------------------------------------------------------------- // Return sector count for mounted disk image //---------------------------------------------------------------------- -void GetSectorCount() { +void SDCGetSectorCount() { - int drive = IF.cmdcode & 1; - unsigned int numsec = SDC_disk[drive].size/SDC_disk[drive].sectorsize; - IF.reply3 = numsec & 0xFF; + int drive = IFace.cmdcode & 1; + unsigned int numsec = gCocoDisk[drive].size/gCocoDisk[drive].sectorsize; + 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; } //---------------------------------------------------------------------- // Return file record for mounted disk image //---------------------------------------------------------------------- -void GetMountedImageRec() +void SDCGetMountedImageRec() { - int drive = IF.cmdcode & 1; - //DLOG_C("GetMountedImageRec %d %s\n",drive,SDC_disk[drive].fullpath); - if (strlen(SDC_disk[drive].fullpath) == 0) { - IF.status = STA_FAIL; + int drive = IFace.cmdcode & 1; + //DLOG_C("SDCGetMountedImageRec %d %s\n",drive,gCocoDisk[drive].fullpath); + if (strlen(gCocoDisk[drive].fullpath) == 0) { + IFace.status = STA_FAIL; } else { - IF.reply_mode = 0; - LoadReply(&SDC_disk[drive].filerec,sizeof(FileRecord)); + 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() { @@ -1741,172 +1699,154 @@ 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; } } //---------------------------------------------------------------------- -// 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; -//---------------------------------------------------------------------- -// A file path may be in SDC format which does not use a dot to seperate -// 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) -{ - 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; - } + if (!SetFilePointerEx(gCocoDisk[drive].hFile,pos,nullptr,FILE_BEGIN)) { + DLOG_C("SDCSeekSector error %s\n",util::LastErrorTxt()); + return false; } - name[namlen] = '\0'; // terminate name - strncat(path,name,MAX_PATH); // append it to directory + return true; } //---------------------------------------------------------------------- -// Load a file record with the file found by Find File +// Read a sector from drive image and load reply //---------------------------------------------------------------------- -bool LoadFoundFile(struct FileRecord * rec) +bool SDCReadDrive(unsigned char cmdcode, unsigned int lsn) { - 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; + char buf[520]; + DWORD cnt = 0; + int drive = cmdcode & 1; + if (gCocoDisk[drive].hFile == nullptr) { + DLOG_C("SDCReadDrive %d not open\n"); + return false; } - // 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++; - } + if (!SDCSeekSector(cmdcode,lsn)) { + return false; } - // 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++; + if (!ReadFile(gCocoDisk[drive].hFile,buf,gCocoDisk[drive].sectorsize,&cnt,nullptr)) { + DLOG_C("SDCReadDrive %d %s\n",drive,util::LastErrorTxt()); + return false; } - // Attributes - if (dFound.dwFileAttributes & FILE_ATTRIBUTE_READONLY) { - rec->attrib |= ATTR_RDONLY; + if (cnt != gCocoDisk[drive].sectorsize) { + DLOG_C("SDCReadDrive %d short read\n",drive); + return false; } - if (dFound.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - rec->attrib |= ATTR_DIR; + + snprintf(SDC_Status,16,"SDC:%d Rd %d,%d",CurrentBank,drive,lsn); + SDCLoadReply(buf,cnt); + return true; +} + +//---------------------------------------------------------------------- +// Load reply. Count is bytes, 512 max. +//---------------------------------------------------------------------- +void SDCLoadReply(const void *data, int count) +{ + if ((count < 2) | (count > 512)) { + DLOG_C("SDCLoadReply bad count %d\n",count); + return; } - // 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; + memcpy(IFace.blkbuf,data,count); - return true; + IFace.bufptr = IFace.blkbuf; + IFace.bufcnt = count; + IFace.half_sent = 0; + + // If port reads exceed the count zeros will be returned + 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 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); - SDC_disk[drive] = {}; + UnloadDisk(drive); // Convert from SDC format - char file[MAX_PATH]; - FixSDCPath(file,path); + char file[MAX_PATH] {}; + strncpy(file,FixFATPath(path).c_str(),MAX_PATH-1); // 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. @@ -1915,33 +1855,32 @@ 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); - SDC_disk[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); - IF.status = STA_NORMAL; + DLOG_C("SDCMountDisk unload %d %s\n",drive,path); + IFace.status = STA_NORMAL; if (drive == 0) BuildCartridgeMenu(); return; } - char file[MAX_PATH]; - char tmp[MAX_PATH]; - // Convert from SDC format - FixSDCPath(file,path); + char file[MAX_PATH] {}; + strncpy(file,FixFATPath(path).c_str(),MAX_PATH-1); // 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); @@ -1956,125 +1895,133 @@ void MountDisk (int drive, const char * path, int raw) // Give up if (!found) { - DLOG_C("MountDisk not found '%s'\n",file); - IF.status = STA_FAIL | STA_NOTFOUND; + DLOG_C("SDCMountDisk not found '%s'\n",file); + IFace.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) { - 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; } //---------------------------------------------------------------------- // 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 // -// 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", - drive,path,raw,IF.param1,IF.param2); + DLOG_C("SDCOpenNew %d '%s' %d %d %d\n", + 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("OpenNew SDF file not supported\n"); - IF.status = STA_FAIL | STA_INVALID; + DLOG_C("SDCOpenNew SDF file not supported\n"); + IFace.status = STA_FAIL | STA_INVALID; 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(gSDRoot) / gCurDir / 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.status = STA_FAIL | STA_INVALID; + if (fs::is_directory(fqn)) { + DLOG_C("SDCOpenNew %s is a directory\n",path); + IFace.status = STA_FAIL | STA_INVALID; return; } + UnloadDisk(drive); - // Try to create file - CloseDrive(drive); - strncpy(SDC_disk[drive].fullpath,fqn,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); - DLOG_C("... %s\n",LastErrorTxt()); - IF.status = STA_FAIL | STA_WIN_ERROR; + if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { + DLOG_C("SDCOpenNew fail %d file %s\n",drive,gCocoDisk[drive].fullpath); + DLOG_C("... %s\n",util::LastErrorTxt()); + IFace.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; + IFace.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; + IFace.status = STA_NORMAL; + 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)) { - IF.status = STA_NORMAL; + l_siz.QuadPart = gCocoDisk[drive].size; + if (SetFilePointerEx(gCocoDisk[drive].hFile,l_siz,nullptr,FILE_BEGIN)) { + if (SetEndOfFile(gCocoDisk[drive].hFile)) { + IFace.status = STA_NORMAL; } else { - IF.status = STA_FAIL | STA_WIN_ERROR; + IFace.status = STA_FAIL | STA_WIN_ERROR; } } } @@ -2084,75 +2031,85 @@ 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, SDC_disk[drive].hFile); - - 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); + if (gFileList.cursor >= gFileList.files.size()) { + DLOG_C("SDCOpenFound no file %d %d %d\n", + drive, gFileList.cursor, gFileList.files.size()); + return; + } + + UnloadDisk(drive); + + 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 (gFileList.files[gFileList.cursor].isDir) + { + 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; } - // 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); + std::string file = gFileList.directory + "/" + name; + DLOG_C("SDCOpenFound %s\n", file.c_str()); - // Open file for read - SDC_disk[drive].hFile = CreateFile( - fqn, GENERIC_READ, FILE_SHARE_READ, - nullptr,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,nullptr); + gCocoDisk[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("... %s\n",LastErrorTxt()); + if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { + DLOG_C("SDCOpenFound fail %d file %s\n",drive,file.c_str()); + DLOG_C("... %s\n",util::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; } - strncpy(SDC_disk[drive].fullpath,fqn,MAX_PATH); - strncpy(SDC_disk[drive].name,dFound.cFileName,MAX_PATH); + // 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,name.c_str(),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; + // Grab filesize + gCocoDisk[drive].size = gFileList.files[gFileList.cursor].size; + // Determine file type (RAW,DSK,JVC,VDK,SDF) if (raw) { writeprotect = 0; - 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) { - DLOG_C("OpenFound header read error\n"); - IF.status = STA_FAIL | STA_INVALID; + if (ReadFile(gCocoDisk[drive].hFile,header,12,nullptr,nullptr) == 0) { + DLOG_C("SDCOpenFound header read error\n"); + IFace.status = STA_FAIL | STA_INVALID; return; } @@ -2160,87 +2117,91 @@ 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 ((gCocoDisk[drive].size >= 219262) && // is this reasonable? (strncmp("SDF1",(const char *) header,4) == 0)) { - DLOG_C("OpenFound SDF file unsupported\n"); - IF.status = STA_FAIL | STA_INVALID; + gCocoDisk[drive].type = DTYPE_SDF; + DLOG_C("SDCOpenFound SDF file unsupported\n"); + IFace.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("SDCOpenFound 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); + 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; - DLOG_C("OpenFound JVC/OS9 sectors %d\n",numsec); - SDC_disk[drive].doublesided = ((numsec > 720) && (numsec <= 2880)); + numsec = gCocoDisk[drive].size >> 8; + DLOG_C("SDCOpenFound JVC/OS9 sectors %d\n",numsec); + 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; + 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); - IF.status = STA_FAIL | STA_INVALID; + DLOG_C("SDCOpenFound unsuported image type %d %d\n", + drive, gCocoDisk[drive].headersize); + IFace.status = STA_FAIL | STA_INVALID; return; break; } } - // Fill in image info. - LoadFoundFile(&SDC_disk[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 ((SDC_disk[drive].filerec.attrib & ATTR_RDONLY) != 0) { + if ((gCocoDisk[drive].filerec.FR_attrib & ATTR_RDONLY) != 0) { writeprotect = 1; } else if (writeprotect) { - SDC_disk[drive].filerec.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) { - DLOG_C("OpenFound reopen fail %d\n",drive); - DLOG_C("... %s\n",LastErrorTxt()); - IF.status = STA_FAIL | STA_WIN_ERROR; + if (gCocoDisk[drive].hFile == INVALID_HANDLE_VALUE) { + DLOG_C("SDCOpenFound reopen fail %d\n",drive); + DLOG_C("... %s\n",util::LastErrorTxt()); + IFace.status = STA_FAIL | STA_WIN_ERROR; return; } } if (drive == 0) BuildCartridgeMenu(); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; return; } //---------------------------------------------------------------------- -// Convert file name from SDC format and prepend current dir. +// Convert file name from FAT 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 SDCGetFullPath(const std::string& file) +{ + std::string out = gSDRoot; + if (gCurDir.size() > 0) out += '/' + gCurDir; + out += '/' + FixFATPath(file); + + DLOG_C("SDCGetFullPath in %s out %s\n",file.c_str(),out.c_str()); + return (out); } //---------------------------------------------------------------------- @@ -2248,21 +2209,22 @@ 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')); - - DLOG_C("UpdateSD rename %s %s\n",from,target); - - if (std::rename(from,target)) { - DLOG_C("RenameFile %s\n", strerror(errno)); - IF.status = STA_FAIL | STA_WIN_ERROR; + const char* p = names; + std::string sfrom = p; + p += sfrom.size() + 1; + std::string starget = p; + + 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.c_str(),target.c_str())) { + DLOG_C("SDCRenameFile %s\n", strerror(errno)); + IFace.status = STA_FAIL | STA_WIN_ERROR; } else { - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; } return; } @@ -2270,69 +2232,76 @@ 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; - if (IsDirectory(path)) { - if (PathIsDirectoryEmpty(path)) { - if (RemoveDirectory(path)) { - IF.status = STA_NORMAL; - } else { - DLOG_C("Deletefile %s\n", strerror(errno)); - IF.status = STA_FAIL | STA_NOTFOUND; - } + fs::path p = SDCGetFullPath(std::string(file)); + std::error_code ec; + + DLOG_C("SDCKillFile %s\n",file); + + // Does it exist? + if (!fs::exists(p, ec)) { + DLOG_C("SDCKillFile does not exist\n"); + IFace.status = STA_FAIL | STA_NOTFOUND; + return; + } + // Regular file + if (fs::is_regular_file(p, ec)) { + if (fs::remove(p, ec)) { + IFace.status = STA_NORMAL; } else { - IF.status = STA_FAIL | STA_NOTEMPTY; + DLOG_C("SDCKillFile error: %s\n", ec.message().c_str()); + IFace.status = STA_FAIL | STA_WPROTECT; } - } else { - if (DeleteFile(path)) { - IF.status = STA_NORMAL; + return; + } + // Directory + if (fs::is_directory(p, ec)) { + if (fs::is_empty(p, ec)) { + if (fs::remove(p, ec)) { + IFace.status = STA_NORMAL; + } else { + IFace.status = STA_FAIL | STA_WPROTECT; + DLOG_C("SDCKillFile dir error: %s\n", ec.message().c_str()); + } } else { - DLOG_C("Deletefile %s\n", strerror(errno)); - IF.status = STA_FAIL | STA_NOTFOUND; + DLOG_C("SDCKillFile directory not empty\n"); + IFace.status = STA_FAIL | STA_NOTEMPTY; } + return; } - return; + // Not a file or directory (symlink, device, etc.) + IFace.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); + + DLOG_C("SDCMakeDirectory %s\n", s.c_str()); + + std::error_code ec; - // Make sure directory is not in use - struct _stat file_stat; - int result = _stat(path,&file_stat); - if (result == 0) { - IF.status = STA_FAIL | STA_INVALID; + if (fs::exists(p, ec)) { + DLOG_C("SDCMakeDirectory already exists\n"); + IFace.status = STA_FAIL | STA_INVALID; return; } - if (CreateDirectory(path,nullptr)) { - IF.status = STA_NORMAL; + if (fs::create_directory(p, ec)) { + IFace.status = STA_NORMAL; } else { - DLOG_C("MakeDirectory %s\n", strerror(errno)); - IF.status = STA_FAIL | STA_WIN_ERROR; - } - return; -} - -//---------------------------------------------------------------------- -// Close virtual disk -//---------------------------------------------------------------------- -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; + DLOG_C("SDCMakeDirectory error: %s\n", ec.message().c_str()); + IFace.status = STA_FAIL | STA_WIN_ERROR; } } @@ -2340,221 +2309,333 @@ 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 SDCSetCurDir(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 = gCurDir; // current relative directory + fs::path root = gSDRoot; // SD card root + + // "." or "" no change + if (*branch == '\0' || strcmp(branch, ".") == 0) { DLOG_C("SetCurdir no change\n"); - IF.status = STA_NORMAL; + IFace.status = STA_NORMAL; return; } - // If branch is "/" set CurDir to root - if (strcmp(branch,"/") == 0) { + // "/" go to root + if (strcmp(branch, "/") == 0) { + gCurDir = ""; DLOG_C("SetCurdir to root\n"); - *CurDir = '\0'; - IF.status = STA_NORMAL; + IFace.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); - IF.status = STA_NORMAL; + // ".." parent directory + if (strcmp(branch, "..") == 0) { + cur = cur.parent_path(); + gCurDir = cur.string(); + util::FixDirSlashes(gCurDir); + DLOG_C("SetCurdir back %s\n", gCurDir.c_str()); + IFace.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; + IFace.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); - IF.status = STA_FAIL | STA_NOTFOUND; + + // Validate directory + if (!fs::is_directory(candidate)) { + DLOG_C("SetCurdir not a directory %s\n", candidate.string().c_str()); + IFace.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'; + // String based host files + gCurDir = newCur.string(); + util::FixDirSlashes(gCurDir); - DLOG_C("SetCurdir set to '%s'\n",CurDir); - - IF.status = STA_NORMAL; - return; + DLOG_C("SetCurdir set to '%s'\n", gCurDir.c_str()); + IFace.status = STA_NORMAL; } //---------------------------------------------------------------------- -// Start File search. Searches start from the root of the SDCard. +// SDCInitiateDir command. //---------------------------------------------------------------------- -bool SearchFile(const char * pattern) -{ - // Path always starts with SDCard - char path[MAX_PATH]; - strncpy(path,SDCard,MAX_PATH); - AppendPathChar(path,'/'); - - if (*pattern == '/') { - strncat(path,&pattern[1],MAX_PATH); - } else { - strncat(path,CurDir,MAX_PATH); - AppendPathChar(path,'/'); - strncat(path,pattern,MAX_PATH); - } - - DLOG_C("SearchFile %s\n",path); - - // Close previous search - if (hFind != INVALID_HANDLE_VALUE) { - FindClose(hFind); - hFind = INVALID_HANDLE_VALUE; - } - - // Search - hFind = FindFirstFile(path, &dFound); - - if (hFind == INVALID_HANDLE_VALUE) { - *SeaDir = '\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); -} - -//---------------------------------------------------------------------- -// InitiateDir command. -//---------------------------------------------------------------------- -bool InitiateDir(const char * path) +bool SDCInitiateDir(const char * path) { bool rc; - // Append "*.*" if last char in path was '/'; - int l = strlen(path); + DLOG_C("SDCInitiateDir '%s'\n",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) { - 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; } } -//---------------------------------------------------------------------- -// 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() +//========================================================================= +// 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) { - memset(DirPage,0,sizeof(DirPage)); + 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 = util::GetFileNamePart(pattern); + bool dir_lookup = (fnpat == "*" || fnpat == "*.*"); + + std::string search_dir = util::GetDirectoryPart(search_path); + + // Include ".." if dir_lookup and search_dir is not SDRoot + if (dir_lookup && util::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) { - DLOG_C("LoadDirPage Search fail\n"); + 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); - int cnt = 0; - while (cnt < 16) { - if (LoadFoundFile(&DirPage[cnt])) cnt++; - if (FindNextFile(hFind,&dFound) == 0) { - FindClose(hFind); - hFind = INVALID_HANDLE_VALUE; - break; + // 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; } //---------------------------------------------------------------------- -// Append char to path if not there +// Host File search. //---------------------------------------------------------------------- -void AppendPathChar(char * path, char c) +bool SearchFile(const std::string& pat) { - 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'; + // 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); } //---------------------------------------------------------------------- -// Determine if path is a direcory +// 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 //---------------------------------------------------------------------- -bool IsDirectory(const char * path) +std::string FixFATPath(const std::string& sdcpath) { - struct _stat file_stat; - int result = _stat(path,&file_stat); - if (result != 0) return false; - return ((file_stat.st_mode & _S_IFDIR) != 0); + 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(); } -//---------------------------------------------------------------------- -// 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 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 path slashes -//------------------------------------------------------------ -void ConvertSlashes(char * path) +//------------------------------------------------------------------- +// 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) { - for(size_t i=0; i < strlen(path); i++) { - if (path[i] == '\\') path[i] = '/'; + // 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.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..063f2b88 100644 --- a/sdc/sdc.vcxproj +++ b/sdc/sdc.vcxproj @@ -113,4 +113,4 @@ - \ No newline at end of file +