Open aotta opened 2 years ago
some update... Z88 needs some other corrections to code, first af all the "Attribute" management (byte no. 18 in request) and other ones, as its client don't use directory.
For loading, save, rename (but this doesn't work always as there is some "name mismatch" in request and answer, cause to clients) and delete a file , i made further changes:
in "PDDuino", i added the definition:
byte ATTRIBUTE = 0x00; //Attribute for dir/file request
and following the "3_tppd_commands" sheet, with change noted with a final "AOAO" comment:
// Sends a Drive Condition return to the TPDD port
void return_drive(byte errorCode){
DEBUG_PRINTL(F("return_normal()"));
#if DEBUG > 1
DEBUG_PRINT("R:Drive Cond ");
DEBUG_PRINTIL(errorCode, HEX);
#endif
tpddWrite(RET_CONDITION); // Return type (normal)
tpddWrite(0x01); // Data size (1)
tpddWrite(errorCode); // Error code
tpddSendChecksum(); // Checksum
}
// Sends a normal return to the TPDD port with error code errorCode
void return_normal(byte errorCode){
DEBUG_PRINTL(F("return_normal()"));
#if DEBUG > 1
DEBUG_PRINT("R:Norm ");
DEBUG_PRINTIL(errorCode, HEX);
#endif
tpddWrite(RET_NORMAL); // Return type (normal)
tpddWrite(0x01); // Data size (1)
tpddWrite(errorCode); // Error code
tpddSendChecksum(); // Checksum
}
// Sends a reference return to the TPDD port
void returnReference(const char *name, bool isDir, uint16_t size ) {
uint8_t i, j;
DEBUG_PRINTL(F("returnReference()"));
tpddWrite(RET_DIRECTORY); // Return type (reference)
tpddWrite(0x1C); // Data size (1C)
if(name == NULL) {
for(i = 0; i < FILENAME_SZ; i++)
tpddWrite(0x00); // Write the reference file name to the TPDD port
} else {
if(isDir && DME) { // handle dirname.
for(i = 0; (i < 6) && (name[i] != 0); i++)
for(i = 0; (i < 6) && (name[i] != 0); i++)
tpddWrite(name[i]);
for(;i < 6; i++)
tpddWrite(' '); // pad out the dir
tpddWrite('.'); // Tack the expected ".<>" to the end of the name
tpddWrite('<');
tpddWrite('>');
j = 9;
} else {
for(i = 0; (i < 6) && (name[i] != '.'); i++) {
tpddWrite(name[i]);
}
for(j = i; j < 6; j++) {
tpddWrite(' ');
}
for(; j < FILENAME_SZ && (name[i] != 0); j++) {
tpddWrite(name[i++]); // send the file extension
}
}
for(; j < FILENAME_SZ; j++) {
tpddWrite(0); // pad out
}
}
tpddWrite(ATTRIBUTE); // Attribute, unused //AOAO use for Z88
tpddWrite((uint8_t)(size >> 8)); // File size most significant byte
tpddWrite((uint8_t)(size & 0xFF)); // File size least significant byte
tpddWrite(0x80); // Free sectors, SD card has more than we'll ever care about
tpddSendChecksum(); // Checksum
}
void return_reference() {
DEBUG_PRINTL(F("return_reference()"));
entry.getName(tempRefFileName,FILENAME_SZ); // Save the current file entry's name to the reference file name buffer
returnReference(tempRefFileName, entry.isDirectory(), entry.fileSize());
#if DEBUG > 1
DEBUG_PRINT("R:Ref:");
DEBUG_PRINTL(F(tempRefFileName));
#endif
}
// Sends a blank reference return to the TPDD port
void return_blank_reference() {
DEBUG_PRINTL(F("return_blank_reference()"));
entry.getName(tempRefFileName,FILENAME_SZ); // Save the current file entry's name to the reference file name buffer
returnReference(NULL, false, 0);
#if DEBUG > 1
DEBUG_PRINTL("R:BRef");
#endif
}
void return_parent_reference(){
DEBUG_PRINTL(F("return_parent_reference()"));
returnReference("PARENT", true, 0);
}
/*
*
* TPDD Port command handler routines
*
*/
void command_reference(){ // Reference command handler
byte searchForm = _cmd_buffer[0x19]; // The search form byte exists 0x19 bytes into the command
byte refIndex = 0x00; // Reference file name index
ATTRIBUTE = _cmd_buffer[0x18]; // AOAO
DEBUG_PRINTL(F("command_reference()"));
#if DEBUG > 1
DEBUG_PRINT("SF:");
DEBUG_PRINTIL(searchForm,HEX);
#endif
if(searchForm == SF_NAME){ // Request entry by name
for(uint8_t i = 0; i < FILENAME_SZ; i++){ // Put the reference file name into a buffer
if(_cmd_buffer[i] != ' '){ // If the char pulled from the command is not a space character (0x20)...
refFileName[refIndex++]=_cmd_buffer[i]; // write it into the buffer and increment the index.
}
}
refFileName[refIndex] = 0x00; // Terminate the file name buffer with a null character
#if DEBUG > 1
DEBUG_PRINT("Ref: ");
DEBUG_PRINTL(refFileName);
#endif
if(DME){ // !!!Strips the ".<>" off of the reference name if we're in DME mode
if(strstr(refFileName, ".<>") != 0x00){
for(byte i=0x00; i<FILENAME_SZ; i++){ // Copies the reference file name to a scratchpad buffer with no directory extension if the reference is for a directory
if(refFileName[i] != '.' && refFileName[i] != '<' && refFileName[i] != '>'){
refFileNameNoDir[i]=refFileName[i];
}else{
refFileNameNoDir[i]=0x00; // If the character is part of a directory extension, don't copy it
}
}
}else{
for(byte i=0x00; i<FILENAME_SZ; i++) refFileNameNoDir[i]=refFileName[i]; // Copy the reference directly to the scratchpad buffer if it's not a directory reference
}
}
directoryAppend(refFileNameNoDir); // Add the reference to the directory buffer
#if DEBUG > 1
DEBUG_PRINT("Ref: ");
DEBUG_PRINTL(refFileName);
#endif
SD_LED_ON
if(SD.exists(refFileName)){ // If the file or directory exists on the SD card... AOAO
entry=SD.open(refFileName); // ...open it... AOAO
#if DEBUG > 1
DEBUG_PRINT("opened file: ");
DEBUG_PRINTL(refFileName);
#endif
return_reference(); // send a refernce return to the TPDD port with its info...
entry.close(); // ...close the entry
}else{ // If the file does not exist...
return_blank_reference();
}
upDirectory(); // Strip the reference off of the directory buffer aoao
}else if(searchForm == SF_FIRST){ // Request first directory block
SD_LED_ON
root.close();
root = SD.open(directory);
ref_openFirst();
}else if(searchForm == SF_NEXT){ // Request next directory block
SD_LED_ON
root.close();
root = SD.open(directory);
ref_openNext();
}else{ // Parameter is invalid
return_normal(ERR_PARM); // Send a normal return to the TPDD port with a parameter error
}
SD_LED_OFF
}
void ref_openFirst(){
DEBUG_PRINTL(F("ref_openFirst()"));
directoryBlock = 0x00; // Set the current directory entry index to 0
if(DME && directoryDepth>0x00){ // Return the "PARENT.<>" reference if we're in DME mode
SD_LED_OFF
return_parent_reference();
}else{
ref_openNext(); // otherwise we just return the next reference
}
}
void ref_openNext(){
DEBUG_PRINTL(F("ref_openNext()"));
directoryBlock++; // Increment the directory entry index
SD_LED_ON
root.rewindDirectory(); // Pull back to the begining of the directory
for(byte i=0x00; i<directoryBlock-0x01; i++) root.openNextFile(); // skip to the current entry offset by the index
entry = root.openNextFile(); // Open the entry
if(entry){ // If the entry exists it is returned
if(entry.isDirectory() && !DME){ // If it's a directory and we're not in DME mode
entry.close(); // the entry is skipped over
ref_openNext(); // and this function is called again
}
return_reference(); // Send the reference info to the TPDD port
entry.close(); // Close the entry
SD_LED_OFF
}else{
SD_LED_OFF
return_blank_reference();
}
}
void command_open(){ // Opens an entry for reading, writing, or appending
_mode = (openmode_t)_cmd_buffer[0]; // The access mode is stored in the 1st byte of the command payload
DEBUG_PRINTL(F("command_open()"));
entry.close();
if(DME && strcmp(refFileNameNoDir, "PARENT") == 0x00){ // If DME mode is enabled and the reference is for the "PARENT" directory
upDirectory(); // The top-most entry in the directory buffer is taken away
directoryDepth--; // and the directory depth index is decremented
}else{
directoryAppend(refFileNameNoDir); // Push the reference name onto the directory buffer
SD_LED_ON
if(DME && strstr(refFileName, ".<>") != 0x00 && !SD.exists(directory)){ // If the reference is for a directory and the directory buffer points to a directory that does not exist
SD.mkdir(directory); // create the directory
upDirectory();
}else{
entry=SD.open(directory); // Open the directory to reference the entry
//AOAO if(entry.isDirectory()){ // !!!Moves into a sub-directory
//AOAO entry.close(); // If the entry is a directory
//AOAO directoryAppend("/"); // append a slash to the directory buffer
//AOAO directoryDepth++; // and increment the directory depth index
//AOAO }else{ // If the reference isn't a sub-directory, it's a file
entry.close();
switch(_mode){
case F_OPEN_WRITE: entry = SD.open(refFileName, FILE_WRITE); break; //AOAO
case F_OPEN_APPEND: entry = SD.open(refFileName, FILE_WRITE | O_APPEND); break;
case F_OPEN_READ: entry = SD.open(refFileName, FILE_READ); break;
// } AOAO
// upDirectory(); AOAO
}
}
}
if(SD.exists(refFileName)){ // If the file actually exists... AOAO
SD_LED_OFF
return_normal(ERR_SUCCESS); // ...send a normal return with no error.
}else{ // If the file doesn't exist...
SD_LED_OFF
DEBUG_PRINTL(F("No file ERR")); // AOAO
return_normal(ERR_NO_FILE); // ...send a normal return with a "file does not exist" error.
}
}
void command_close(){ // Closes the currently open entry
DEBUG_PRINTL(F("command_close()"));
entry.close(); // Close the entry
SD_LED_OFF
return_normal(ERR_SUCCESS); // Normal return with no error
}
void command_read(){ // Read a block of data from the currently open entry
DEBUG_PRINTL(F("command_read()"));
SD_LED_ON
byte bytesRead = entry.read(fileBuffer, FILE_BUFFER_SZ); // Try to pull 128 bytes from the file into the buffer
SD_LED_OFF
#if DEBUG > 1
DEBUG_PRINT("A: ");
DEBUG_PRINTIL(entry.available(),HEX);
#endif
if(bytesRead > 0x00){ // Send the read return if there is data to be read
tpddWrite(RET_READ); // Return type
tpddWrite(bytesRead); // Data length
for(byte i=0x00; i<bytesRead; i++) tpddWrite(fileBuffer[i]);
tpddSendChecksum();
}else{
return_normal(ERR_EOF); // send a normal return with an end-of-file error if there is no data left to read
}
}
void command_write(){ // Write a block of data from the command to the currently open entry
DEBUG_PRINTL(F("command_write()"));
DEBUG_PRINTL(F(_cmd_buffer));
DEBUG_PRINTL(F(_length));
SD_LED_ON
entry.write(_cmd_buffer, _length);
SD_LED_OFF
return_normal(ERR_SUCCESS); // Send a normal return to the TPDD port with no error
}
void command_delete(){ // Delete the currently open entry
DEBUG_PRINTL(F("command_delete()"));
SD_LED_ON
entry.close(); // Close any open entries
directoryAppend(refFileNameNoDir); // Push the reference name onto the directory buffer
entry = SD.open(directory, FILE_READ); // directory can be deleted if opened "READ"
if(DME && entry.isDirectory()){
entry.rmdir(); // If we're in DME mode and the entry is a directory, delete it
}else{
entry.close(); // Files can be deleted if opened "WRITE", so it needs to be re-opened
entry = SD.open(refFileName, FILE_WRITE); // AOAO
entry.remove();
}
SD_LED_OFF
upDirectory();
return_normal(ERR_SUCCESS); // Send a normal return with no error
}
void command_format(){ // Not implemented
DEBUG_PRINTL(F("command_format()"));
return_normal(ERR_SUCCESS);
}
void command_status(){ // Drive status
DEBUG_PRINTL(F("command_status()"));
return_normal(ERR_SUCCESS);
}
void command_condition(){ // Not implemented AOAO
DEBUG_PRINTL(F("command_condition()"));
return_drive(0); //return_normal(ERR_SUCCESS);
}
void command_rename(){ // Renames the currently open entry
DEBUG_PRINTL(F("command_rename()"));
directoryAppend(refFileNameNoDir); // Push the current reference name onto the directory buffer
SD_LED_ON
if(entry) entry.close(); // Close any currently open entries
entry = SD.open(directory); // Open the entry
if(entry.isDirectory()) directoryAppend("/"); // Append a slash to the end of the directory buffer if the reference is a sub-directory
copyDirectory(); // Copy the directory buffer to the scratchpad directory buffer
upDirectory(); // Strip the previous directory reference off of the directory buffer
uint8_t i;
for(i = 0; i < FILENAME_SZ;i++) {
if(_cmd_buffer[i] == 0 || _cmd_buffer[i] == ' ')
break;
refFileNameNoDir[i] = _cmd_buffer[i]; //AOAO
}
refFileNameNoDir[i] = 0x00; // Terminate the temporary reference name with a null //AOAO
if(DME && entry.isDirectory()){ // !!!If the entry is a directory, we need to strip the ".<>" off of the new directory name
if(strstr(tempRefFileName, ".<>") != 0x00){
for(byte i=0x00; i<FILENAME_SZ; i++){
if(tempRefFileName[i] == '.' || tempRefFileName[i] == '<' || tempRefFileName[i] == '>'){
tempRefFileName[i]=0x00;
}
}
}
}
directoryAppend(tempRefFileName);
if(entry.isDirectory()) directoryAppend("/");
if(DME && entry.isDirectory()){ // !!!If the entry is a directory, we need to strip the ".<>" off of the new directory name
DEBUG_PRINTL(directory);
DEBUG_PRINTL(tempDirectory);
SD.rename(tempDirectory,directory); // Rename the entry
} else // AOAO
{ // AOAO
DEBUG_PRINTL(tempRefFileName); // AOAO
DEBUG_PRINTL(refFileNameNoDir); // AOAO
DEBUG_PRINTL(tempDirectory);
SD.rename(tempRefFileName,refFileNameNoDir); // AOAO
}
upDirectory();
entry.close();
SD_LED_OFF
return_normal(ERR_SUCCESS); // Send a normal return to the TPDD port with no error
}
/*
*
* TS-DOS DME Commands
*
*/
void command_DMEreq() { // Send the dmeLabel
DEBUG_PRINT(F("command_DMEReq(): dmeLabel[")); DEBUG_PRINT(dmeLabel); DEBUG_PRINTL(F("]"));
DME = true;
if (directoryDepth>0x00) setLabel(directory); else setLabel(ROOT_DME_LABEL);
tpddWrite(RET_NORMAL);
tpddWrite(0x0B);
tpddWrite(0x20);
for (byte i=0x00 ; i<0x06 ; i++) tpddWrite(dmeLabel[i]);
tpddWrite('.');
tpddWrite('<');
tpddWrite('>');
tpddWrite(0x20);
tpddSendChecksum();
}
Awesome I'll check it out. I have a Z88 to test with. Thanks much.
Thank you! I'll made only a quick and dirty patch, and not tested back too M10 (I'm afraid a big code reworking is needed to have a version working fine with both z88 and Trs). It will be useful to manage the short names bug in renaming and deleting files too. Following your tests and improvement!
Il mar 10 mag 2022, 06:15 Brian K. White @.***> ha scritto:
Awesome I'll check it out. I have a Z88 to test with. Thanks much.
— Reply to this email directly, view it on GitHub https://github.com/bkw777/PDDuino/issues/4#issuecomment-1121898272, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACV2MY2RD3HGFRINXNDW26TVJHPHPANCNFSM5QK54V7A . You are receiving this because you authored the thread.Message ID: @.***>
Yeah after making https://github.com/bkw777/pdd.sh , I have a much better understanding of what a real drive does than I did the last time I worked on this, or dlplus, so this is ripe for some improvement, and some of it should be pretty easy.
Not counting the rest of the FCB record beyond the filename, a real drive just a 24 byte field and it doesn't care what's in any of those bytes at all except the special case of null in the first byte. It doesn't know or care about filename lengths, extensions, where the dot is or if there even is any dot, even nulls don't matter except in the first byte. All the formatting is created by the clients and interpreted by the clients.
That makes our job simpler, except for one issue that arises from the fact that a real drive is a filesystem, in that it presents a high level interface to clients and stores the file data on raw storage, while we are trying to emulate that filesystem, yet we are not storing the data in raw space but are in turn using another filesystem's already high-level client interface, which want's to deal with filenames not raw bytes.
So, we get "A . B A " from the client, and all else being equal, all we're supposed to do is store that and then later read that back out, verbatim in both cases. Brainless, blind, easy.
One way to do that could be, we could actually create filenames in that format on the sd card. Fat32 supports over 24 bytes length and space characters in the name, so we could just write the given strings and read them back verbatim.
But that would make filenames that aren't convenient when you take the sd card out of the device and access it directly from a modern machine. So ideally we want to collaps that to "A.BA" to create a normal filename on the sd card.
But that means that, if we want to do the actual proper emulation job that will be the most compatible to any client that works with real drives, then later we should expand that back out to the original 24 bytes when reading back out to the client. And that means we need to either make an unsafe assumption (the original filename might have been exactly "A.BA" or a 6.2 version, or an 8.2 version, or an 8.3 version, or who knows what else), or we need to record the original string somehow. A cheap way to do that would be just to url-encode the filename when writing to the sdcard, and decode when reading back to a client. That removes spaces from the on-disk name, but is not exactly any better to have a name like "A%20%20%20%20%20.BA" vs "A _ .BA" (We can at least safely discard trailing spaces, just not leading or embedded.)
Maybe the most practical answer is just do the simple assumption, always expand according to a simple rule, but have it user-configurable. Have a config file on disk with a setting that says to treat all the filenames as Kyotronic/Tandy/NEC/Olivetti, or WP-2 or Z88 or raw or whatever other format.
The user just dedicates the sdcard to some particular machine they use it with. That will be fine in most cases. If you only have a z88 you play with, you set the z88 option and pretty much forget about it after that. The only problem would be from moving a given sd card from something like a WP-2 which has 8.2 filenames to a Model 100 which has only 6.2.
One of the modes would be a "raw" mode, which would write the filenames to the sd card without re-formatting, which would be less convenient for accessing the sdcard outside of the device, but would automatically be compatible with ANY client even if we never saw it before and have no idea what it expects.
This way we don't have to have some hidden file containing the original filenames or something. That would just be a constant source of problems.
Maybe an even better option is a slightly technically wrong behavior but maybe it's fine, where we collapse embedded spaces and don't bother to re-expand them.
It's technically wrong because a real drive sends back the original embedded spaces, and so all clients expect to read that, and so there COULD be a client that would be broken without it. But, I don't think it actually breaks any clients to send back the collapsed version. A Model 100 running Floppy or TS-DOS will accept "A.BA" from the drive I think. The drive is just blindly counting bytes, but I think all the clients are parsing the string and will find the dot wherever it is. Maybe this is the best mode to make the default mode with no config file, and then the config option could force it to behave more strictly like a real drive if there turns out to be some client that needs it.
Also now I understand what the "F" byte does which I didn't last time I worked on this so I know better how to handle it now.
Brian, thank you for the great analysis of the naming related bug, i read on FB Z88's users that other emulator didn't work so i was in doubt if it was a bug of the Z88 disk manager, not becouse of a not accurate drive emulation. Anyway, i'm now in a fully immersion with a different (but similar) project (emulating an ATA Drive with a Beaglebone black and a customized cape i designed), and i won't come back to Z88 or M10 for testing for a while. In the meanwhile, I'll follow with interest this project, and vote for the good idea of a "config" file for different platform: even if the code is the same, in most case (i think to my Z88 and Olivetti) the connector and adapter are different, so i think all users (like me) have different "box" for each retrocomputer, with its own sdcard (and config). And again, thank you for your amazing PDDuino!
I just got done doing a major re-work of dlplus over the last couple weeks and I'm really happy with it now. Now I'm starting to re-work PDDuino the same way. It's easier for me to follow and definitely more solid and correct, and probably even more efficient since it's now more like a set of small machines that each have limited and well-defined jobs and it's easy to know that they are doing their things right, and easy to debug when some new issue turns up. And since dlplus is all plain c, it should map to this almost cut & paste.
Z88 programs for handling PDDuino needs the "Drive condition" handling. I succesfully added it and now it works with Z88 too! changed in 3_tppd_commands:
and added this routines: