/**
* Copyright (C) 2023 Eric Helgeson
*
* This file is part of BlueSCSI
*
* This program 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.
*
* This program 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 this program. If not, see .
**/
#include "BlueSCSI_Toolbox.h"
#include "BlueSCSI_disk.h"
#include "BlueSCSI_cdrom.h"
#include "BlueSCSI_log.h"
#include
#include
extern "C" {
#include
#include
#include
}
static void doCountFiles(const char * dir_name)
{
File dir;
File file;
char name[ MAX_MAC_PATH + 1];
dir.open(dir_name);
dir.rewindDirectory();
uint8_t file_count = 0;
while (file.openNext(&dir, O_RDONLY))
{
if(file.getError() > 0)
{
file.close();
break;
}
file.getName(name, 32 + 1);
file.close();
// only count valid files.
if(!scsiDiskFilenameValid(name))
{
file_count = file_count + 1;
if(file_count > 100) {
scsiDev.status = CHECK_CONDITION;
scsiDev.target->sense.code = ILLEGAL_REQUEST;
scsiDev.target->sense.asc = OPEN_RETRO_SCSI_TOO_MANY_FILES;
scsiDev.phase = STATUS;
dir.close();
return;
}
}
}
scsiDev.data[0] = file_count;
scsiDev.dataLen = sizeof(file_count);
scsiDev.phase = DATA_IN;
}
void onListFiles(const char * dir_name) {
File dir;
File file;
memset(scsiDev.data, 0, 4096);
int ENTRY_SIZE = 40;
char name[MAX_MAC_PATH + 1];
dir.open(dir_name);
dir.rewindDirectory();
uint8_t index = 0;
byte file_entry[40] = {0};
while (file.openNext(&dir, O_RDONLY))
{
uint8_t isDir = file.isDirectory() ? 0x00 : 0x01;
file.getName(name, MAX_MAC_PATH + 1);
uint64_t size = file.fileSize();
file.close();
if(!scsiDiskFilenameValid(name))
continue;
file_entry[0] = index;
file_entry[1] = isDir;
int c = 0; // Index of char in name[]
for(int i = 2; i < (MAX_MAC_PATH + 1 + 2); i++) { // bytes 2 - 34
file_entry[i] = name[c++];
}
file_entry[35] = 0; //(size >> 32) & 0xff;
file_entry[36] = (size >> 24) & 0xff;
file_entry[37] = (size >> 16) & 0xff;
file_entry[38] = (size >> 8) & 0xff;
file_entry[39] = (size) & 0xff;
memcpy(&(scsiDev.data[ENTRY_SIZE * index]), file_entry, ENTRY_SIZE);
index = index + 1;
}
dir.close();
scsiDev.dataLen = 4096;
scsiDev.phase = DATA_IN;
}
File get_file_from_index(uint8_t index, const char * dir_name)
{
File dir;
FsFile file_test;
char name[MAX_MAC_PATH + 1];
dir.open(dir_name);
dir.rewindDirectory(); // Back to the top
int count = 0;
while (file_test.openNext(&dir, O_RDONLY))
{
// If error there is no next file to open.
if(file_test.getError() > 0) {
file_test.close();
break;
}
file_test.getName(name, MAX_MAC_PATH + 1);
if(!scsiDiskFilenameValid(name))
continue;
if (count == index)
{
dir.close();
return file_test;
}
else
{
file_test.close();
}
count++;
}
file_test.close();
dir.close();
return file_test;
}
// Devices that are active on this SCSI device.
void onListDevices()
{
for (int i = 0; i < NUM_SCSIID; i++)
{
const S2S_TargetCfg* cfg = s2s_getConfigById(i);
// id, type
if (cfg && (cfg->scsiId & S2S_CFG_TARGET_ENABLED))
{
scsiDev.data[i] = (int)cfg->deviceType; // 2 == cd
}
else
{
scsiDev.data[i] = 255; // not enabled target.
}
}
scsiDev.dataLen = 16;
}
void onSetNextCD()
{
char name[MAX_FILE_PATH];
char full_path[MAX_FILE_PATH * 2];
uint8_t file_index = scsiDev.cdb[1];
uint8_t cd_scsi_id = scsiDev.cdb[2];
image_config_t &img = *(image_config_t*)scsiDev.target->cfg;
File next_cd = get_file_from_index(file_index, CD_IMG_DIR);
next_cd.getName(name, sizeof(name));
next_cd.close();
snprintf(full_path, (MAX_FILE_PATH * 2), "%s/%s", CD_IMG_DIR, name);
cdromSwitch(img, full_path);
}
File gFile; // global so we can keep it open while transfering.
void onGetFile10(void) {
uint8_t index = scsiDev.cdb[1];
uint32_t offset = ((uint32_t)scsiDev.cdb[2] << 24) | ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
if (offset == 0) // first time, open the file.
{
gFile = get_file_from_index(index, "/shared");
if(!gFile.isDirectory() && !gFile.isReadable())
{
scsiDev.status = CHECK_CONDITION;
scsiDev.target->sense.code = ILLEGAL_REQUEST;
//SCSI_ASC_INVALID_FIELD_IN_CDB
scsiDev.phase = STATUS;
return;
}
}
uint32_t file_total = gFile.size();
memset(scsiDev.data, 0, 4096);
gFile.seekSet(offset * 4096);
int bytes_read = gFile.read(scsiDev.data, 4096);
if(offset * 4096 >= file_total) // transfer done, close.
{
gFile.close();
}
scsiDev.dataLen = bytes_read;
scsiDev.phase = DATA_IN;
}
/*
Prepares a file for receving. The file name is null terminated in the scsi data.
*/
File receveFile;
void onSendFilePrep(void)
{
char file_name[32+1];
memset(file_name, '\0', 32+1);
scsiEnterPhase(DATA_OUT);
for (int i = 0; i < 32+1; ++i)
{
file_name[i] = scsiReadByte();
}
SD.chdir("/shared");
receveFile.open(file_name, FILE_WRITE);
SD.chdir("/");
if(receveFile.isOpen() && receveFile.isWritable())
{
receveFile.rewind();
receveFile.sync();
// do i need to manually set phase to status here?
return;
} else {
receveFile.close();
scsiDev.status = CHECK_CONDITION;
scsiDev.target->sense.code = ILLEGAL_REQUEST;
//SCSI_ASC_INVALID_FIELD_IN_CDB
scsiDev.phase = STATUS;
}
}
void onSendFileEnd(void)
{
receveFile.sync();
receveFile.close();
scsiDev.phase = STATUS;
}
void onSendFile10(void)
{
if(!receveFile.isOpen() || !receveFile.isWritable())
{
scsiDev.status = CHECK_CONDITION;
scsiDev.target->sense.code = ILLEGAL_REQUEST;
//SCSI_ASC_INVALID_FIELD_IN_CDB
scsiDev.phase = STATUS;
}
// Number of bytes sent this request, 1..512.
uint16_t bytes_sent = ((uint16_t)scsiDev.cdb[1] << 8) | scsiDev.cdb[2];
// 512 byte offset of where to put these bytes.
uint32_t offset = ((uint32_t)scsiDev.cdb[3] << 16) | ((uint32_t)scsiDev.cdb[4] << 8) | scsiDev.cdb[5];
uint16_t buf_size = 512;
uint8_t buf[512];
// Check if last block of file, and not the only bock in file.
if(bytes_sent < buf_size)
{
buf_size = bytes_sent;
}
scsiEnterPhase(DATA_OUT);
scsiRead(buf, bytes_sent, NULL);
receveFile.seekCur(offset * 512);
receveFile.write(buf, buf_size);
if(receveFile.getWriteError())
{
receveFile.clearWriteError();
receveFile.close();
scsiDev.status = CHECK_CONDITION;
scsiDev.target->sense.code = ILLEGAL_REQUEST;
}
//scsiDev.phase = STATUS;
}
void onToggleDebug()
{
if(g_log_debug)
{
debuglog("Turning Debug logs off.");
}
else
{
log("Turning Debug logs on.");
}
g_log_debug = !g_log_debug;
scsiDev.phase = STATUS;
}
extern "C" int scsiBlueSCSIToolboxCommand()
{
int commandHandled = 1;
uint8_t command = scsiDev.cdb[0];
if (unlikely(command == BLUESCSI_TOOLBOX_COUNT_FILES))
{
doCountFiles("/shared");
}
else if (unlikely(command == BLUESCSI_TOOLBOX_LIST_FILES))
{
// TODO: Allow user to set dir name via ini
onListFiles("/shared");
}
else if (unlikely(command == BLUESCSI_TOOLBOX_GET_FILE))
{
onGetFile10();
}
else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_PREP))
{
onSendFilePrep();
}
else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_10))
{
onSendFile10();
}
else if (unlikely(command == BLUESCSI_TOOLBOX_SEND_FILE_END))
{
onSendFileEnd();
}
else if(unlikely(command == BLUESCSI_TOOLBOX_TOGGLE_DEBUG))
{
onToggleDebug();
}
else if(unlikely(command == BLUESCSI_TOOLBOX_LIST_CDS))
{
onListFiles("/CDImages");
}
else if(unlikely(command == BLUESCSI_TOOLBOX_SET_NEXT_CD))
{
onSetNextCD();
}
else if(unlikely(command == BLUESCSI_TOOLBOX_LIST_DEVICES))
{
onListDevices();
}
else if (unlikely(command == BLUESCSI_TOOLBOX_COUNT_CDS))
{
// TODO: Allow user to set dir name via ini
doCountFiles("/CDImages");
}
else
{
commandHandled = 0;
}
return commandHandled;
}