qpsxserial/qpsxserial.cpp

807 lines
19 KiB
C++

#include <QFileDialog>
#include <QFile>
#include <QSerialPortInfo>
#include "qpsxserial.h"
#include "ui_qpsxserial.h"
#define LOST_PACKET_TIMEOUT 3000
#define CONNECT_TO_PSX_TIMEOUT 500
QPSXSerial::QPSXSerial(QWidget *parent, APP_INTERFACE interface) :
QMainWindow(parent),
ui(new Ui::QPSXSerial),
stdout_ui(new Ui::Stdout_Console),
stdout_dialog(new QDialog),
app_interface(interface),
ack(false),
exe_sent(false),
write_ready(false),
byte_sent_received(false),
first_entered(false),
disable_psx_stdout(false)
{
connect(&lost_packet_timer, SIGNAL(timeout()), this, SLOT(onPacketLost()));
connect(&serial, SIGNAL(bytesWritten(qint64)), this, SLOT(onBytesWritten(qint64)));
connect(&serial, SIGNAL(readyRead()), this, SLOT(onReadyRead(void)));
init_timer.setSingleShot(true);
init_timer.setInterval(CONNECT_TO_PSX_TIMEOUT);
connect(&init_timer, SIGNAL(timeout(void)), this, SLOT(connectToPSXTimeout(void)));
if (app_interface == GUI_APP)
{
ui->setupUi(this);
connect(ui->stdout_Button, SIGNAL(released()), this, SLOT(onStdOutButtonReleased()));
connect(ui->loadFile_Btn, SIGNAL(released()), this, SLOT(onLoadFileBtnReleased()));
connect(ui->updatePorts_Btn, SIGNAL(released()), this, SLOT(onUpdatePortsBtnReleased()));
connect(ui->send_Btn, SIGNAL(released()), this, SLOT(onSendBtnReleased()));
connect(ui->ports_ComboBox, SIGNAL(currentIndexChanged(QString)), this, SLOT(onPortSelectedComboBox(QString)));
ui->exeProgressBar->setVisible(false);
ui->send_Btn->setEnabled(false);
ui->inFileName->setVisible(false);
stdout_ui->setupUi(stdout_dialog);
connect(stdout_ui->clean_Btn, SIGNAL(released()), stdout_ui->stdout_Log, SLOT(clear()));
connect(stdout_ui->close_Btn, SIGNAL(released()), stdout_dialog, SLOT(close()));
connect(this, SIGNAL(debug_frame_received(QString)), stdout_ui->stdout_Log, SLOT(append(QString)));
setWindowTitle( "QPSXSerial "
+ QString(QPSXSERIAL_VERSION_STR) );
}
}
QPSXSerial::~QPSXSerial()
{
delete ui;
}
void QPSXSerial::onStdOutButtonReleased(void)
{
stdout_dialog->show();
}
void QPSXSerial::showError(QString error)
{
QMessageBox box(QMessageBox::Critical, "QPSXSerial serror", error);
box.show();
}
void QPSXSerial::onLoadFileBtnReleased(void)
{
selectedFolder = QFileDialog::getExistingDirectory( this,
"Load folder with PSX data",
"C:/",
QFileDialog::ShowDirsOnly );
if (selectedFolder.isEmpty())
{
return;
}
ui->loadedFile_LineEdit->setText(selectedFolder);
inputExe = getInputExeFromFolder(&selectedFolder);
if (selectedPort.isEmpty() == false)
{
ui->send_Btn->setEnabled(true);
}
}
void QPSXSerial::onUpdatePortsBtnReleased(void)
{
ui->ports_ComboBox->clear();
foreach(QSerialPortInfo port, QSerialPortInfo::availablePorts())
{
ui->ports_ComboBox->addItem(port.portName());
}
}
void QPSXSerial::onSendBtnReleased(void)
{
if (serial.isOpen() == false)
{
if (selectedPort.isEmpty())
{
showGUICLIerror("No selected port!");
return;
}
serial.setPortName(selectedPort);
serial.setBaudRate(QSerialPort::Baud115200);
serial.setParity(QSerialPort::NoParity);
serial.setDataBits(QSerialPort::Data8);
serial.setStopBits(QSerialPort::OneStop);
if (serial.open(QIODevice::ReadWrite) == false)
{
showGUICLIerror("Could not open port " + selectedPort);
return;
}
init_timer.start();
if (app_interface == GUI_APP)
{
ui->exeProgressBar->setVisible(true);
ui->exeProgressBar->setMinimum(0);
ui->exeProgressBar->setMaximum(0);
ui->inFileName->setText("Waiting for response from the device...");
ui->inFileName->setVisible(true);
ui->send_Btn->setText("Disconnect from PSX");
}
else
{
printf("Connected to port successfully.\n");
printf("Waiting for response from the device...\n");
}
}
else
{
if (app_interface == GUI_APP)
{
ui->send_Btn->setText("Send to PSX!");
ui->exeProgressBar->setVisible(false);
ui->inFileName->clear();
}
serial.close();
first_entered = false;
exe_sent = false;
lost_packet_timer.stop();
init_timer.stop();
}
}
void QPSXSerial::connectToPSXTimeout(void)
{
QByteArray ba;
if (ack == false)
{
ba.append(99);
serial.write(ba);
init_timer.start(CONNECT_TO_PSX_TIMEOUT);
}
}
void QPSXSerial::onPortSelectedComboBox(QString port)
{
selectedPort = port;
if (inputExe.isEmpty() == false)
{
ui->send_Btn->setEnabled(true);
}
}
void QPSXSerial::onBytesWritten(qint64)
{
write_ready = true;
}
void QPSXSerial::onReadyRead(void)
{
const QByteArray data = serial.readAll();
static QTime time = QTime::currentTime();
static bool cdrom_petition;
if (exe_sent)
{
static QByteArray fileName;
if (data.contains("#"))
{
cdrom_petition = true;
fileName.clear();
}
if (data.count() == 1)
{
if (data.at(0) == 'b')
{
ack = true;
return;
}
}
qDebug() << data;
if (cdrom_petition)
{
if (data.contains("#"))
{
int initial_i = data.indexOf("#");
qDebug() << initial_i;
fileName.append(data.mid(initial_i, data.count() - initial_i));
}
else
{
fileName.append(data);
}
if (fileName.contains("@"))
{
int terminator_i = fileName.indexOf("@");
if (terminator_i >= fileName.count())
{
fileName.chop(terminator_i - fileName.count() - 1);
}
cdrom_petition = false;
qDebug() << "INPUT FRAME: " + fileName;
QString filePath = QString(fileName);
filePath.remove("@");
filePath.remove("#");
filePath.remove("cdrom:\\");
qDebug() << "selectedFolder = " + selectedFolder;
filePath.prepend(selectedFolder);
filePath.replace("\\", "/");
filePath.chop(2); // Remove ending ";1"
qDebug() << "filePath = " + filePath;
QFile f(filePath);
if (f.open(QFile::ReadOnly) == false)
{
qDebug() << "Error while reading input file!";
return;
}
QByteArray file_data = f.readAll();
quint32 sz = file_data.count();
sendDataSize(sz);
if (serial.waitForReadyRead() == false)
{
qDebug() << "Did not receive any ACK!";
}
else
{
qDebug() << "sendData for file...";
onReadyRead();
sendData(file_data, filePath);
f.close();
}
}
}
else
{
if (app_interface == GUI_APP)
{
emit debug_frame_received(QString(data));
}
else if (app_interface == CLI_APP)
{
if (disable_psx_stdout == false)
{
const char* string_received = data.toStdString().c_str();
printf("%s\n", string_received);
}
}
}
}
if (data.isEmpty() == false)
{
quint8 data_byte = static_cast<quint8>(data.at(0));
if (data_byte == 'b')
{
// Received handshaking byte. Start transmission!
ack = true;
init_timer.stop();
if (first_entered == false)
{
first_entered = true;
if (sendExe() == false)
{
qDebug() << "An error happened when sending EXE file!";
}
exe_sent = true;
}
}
}
time = QTime::currentTime();
}
bool QPSXSerial::sendExe(void)
{
QFile f(inputExe);
if (f.open(QFile::ReadOnly) == false)
{
qDebug() << "Could not open input EXE file!";
return false;
}
f.seek(0);
ack = false;
QByteArray data = f.read(2048 /* PSX-EXE header */);
if (app_interface == GUI_APP)
{
ui->inFileName->setText("Sending PSX-EXE size...");
}
else if (app_interface == CLI_APP)
{
printf("Sending PSX-EXE header data...\n");
}
// PSX-EXE header is actually 2048 bytes long, but initial 32 bytes
// contain all the information we need for this.
for (int i = 0; i < 32; i+=4)
{
QByteArray send;
write_ready = false;
QThread::msleep(100);
QApplication::processEvents();
qDebug() << "Sending " + data.mid(i, 4).toHex();
send.append(data.mid(i, 4));
serial.write(send);
}
if (app_interface == GUI_APP)
{
ui->inFileName->setText("Sending PSX-EXE size...");
}
else if (app_interface == CLI_APP)
{
printf("Sending PSX-EXE size...\n");
}
if (serial.waitForReadyRead() == false)
{
qDebug() << "Did not receive any ACK!";
}
else
{
onReadyRead();
}
qDebug () << "Sending EXE size...";
qint64 sz = f.size() - 2048;
sendDataSize(static_cast<quint32>(sz));
if (serial.waitForReadyRead() == false)
{
qDebug() << "Did not receive any ACK!";
}
else
{
onReadyRead();
}
// Send file size without header
qDebug() << "Dump EXE data...";
f.seek(2048);
data = f.readAll();
qDebug() << data.count();
sendData(data, inputExe);
f.close();
qDebug() << "PSX-EXE sent successfully!";
return true;
}
void QPSXSerial::sendDataSize(quint32 size)
{
QByteArray ar;
for (unsigned int i = 0; i < sizeof(quint32); i++)
{
char send = (char)( size >> (i << 3) );
QByteArray send_arr;
send_arr.append(send);
ar.append(send);
serial.write(send_arr);
while (write_ready == false)
{
QApplication::processEvents();
}
}
/*for (unsigned int i = 0; i < sizeof(quint32); i++)
{
qDebug() << "0x" + QString::number(ar.at(i), 16);
}*/
ack = false;
}
void QPSXSerial::onPacketLost(void)
{
qDebug() << "Entering onPacketLost...";
if (ack == false)
{
serial.write(last_packet_sent);
lost_packet_timer.start(LOST_PACKET_TIMEOUT);
}
}
void QPSXSerial::sendData(QByteArray data, QString fileName)
{
static int last_i;
last_i = 0;
if (app_interface == GUI_APP)
{
ui->exeProgressBar->setVisible(true);
ui->exeProgressBar->setRange(0, data.count());
ui->inFileName->setText(fileName);
ui->inFileName->setVisible(true);
}
lost_packet_timer.setInterval(LOST_PACKET_TIMEOUT);
lost_packet_timer.setSingleShot(true);
for (int i = 0; i < data.count(); i+= 8)
{
QByteArray send;
ack = false;
send.append(data.mid(i, 8));
//qDebug() << "Sent packet";
QApplication::processEvents();
serial.write(send);
last_packet_sent = send;
lost_packet_timer.start(LOST_PACKET_TIMEOUT);
if (serial.waitForReadyRead() == false)
{
qDebug() << "Did not receive any ACK!";
}
else
{
onReadyRead();
}
if (app_interface == GUI_APP)
{
ui->exeProgressBar->setValue(i);
}
else if (app_interface == CLI_APP)
{
if ( ( (i - last_i) > (data.count() >> 7))
||
(i > (data.count() - (data.count() >> 7) ) ) )
{
int j;
bool draw_arrow = true;
// Fancy, CLI progress bar
printf("\r");
printf("|");
for (j = 0; j < data.count(); j += data.count() >> 5)
{
if (i > j)
{
printf("=");
}
else if (i < j)
{
if (draw_arrow)
{
draw_arrow = false;
printf(">");
}
printf(" ");
}
else
{
printf(">");
}
}
printf("|");
printf("\t%d/%d bytes sent...", i, data.count());
last_i = i;
}
}
}
printf("\n");
lost_packet_timer.stop();
if (app_interface == GUI_APP)
{
ui->exeProgressBar->setValue(data.count());
ui->inFileName->setText("Transfer complete!");
}
else if (app_interface == CLI_APP)
{
printf("Transfer complete!\n");
}
ack = false;
}
void QPSXSerial::cli_run(void)
{
QStringList allowed_flags;
bool correct_flag_used = false;
allowed_flags.append("--help");
allowed_flags.append("--port");
allowed_flags.append("--inpath");
foreach(QString flag, allowed_flags)
{
if (_paramlist.contains(flag))
{
correct_flag_used = true;
break;
}
}
if ( (_paramlist.count() < 2) ||
(_paramlist.contains("--help")) ||
(correct_flag_used == false) )
{
showHelp();
emit finished();
return;
}
// Check specified port
int port_i = _paramlist.indexOf("--port");
if (port_i == -1)
{
printf("No port specified! Please include \"--port\" flag and port name e.g.: \"--port COM7\"\n");
emit finished();
return;
}
if (_paramlist.count() > (++port_i ) )
{
selectedPort = _paramlist.at(port_i);
}
else
{
printf("No port name specified! Please write port name e.g.: \"--port COM7\"\n");
emit finished();
return;
}
// Check specified folder
int folder_i = _paramlist.indexOf("--input_folder");
if (folder_i == -1)
{
printf("No input folder has been specified. Please include \"--help\" for further reference"
" about QPSXSerial usage.\n");
emit finished();
return;
}
if (_paramlist.count() > (++folder_i ) )
{
selectedFolder = _paramlist.at(folder_i);
}
else
{
printf( "No input folder path! Please write valid folder path e.g.: "
"--input-folder ~/MyGame/cdimg\n");
emit finished();
return;
}
inputExe = getInputExeFromFolder(&selectedFolder);
if (inputExe.isEmpty())
{
printf("Could not find PSX-EXE from specified input folder.\n");
emit finished();
return;
}
// Check optional flags
int psx_sdtout_flag = _paramlist.indexOf("--disable_psx_stdout");
if (psx_sdtout_flag != -1)
{
disable_psx_stdout = true;
}
else
{
disable_psx_stdout = false;
}
onSendBtnReleased();
}
void QPSXSerial::showHelp(void)
{
printf("##########################\n");
printf("QPSXSerial version %s\n", QPSXSERIAL_VERSION_STR);
printf("##########################\n");
printf("This application allows uploading PSX-EXE files to a PlayStation 1\n");
printf("console (also known as PSX) using serial port interface.\n");
printf("To be used together with OpenSend, available on the link below:\n");
printf("\n%s\n\n", OPENSEND_URL);
printf("Report any bugs, issues or suggestions on the official Github repository for QPSXSerial:\n");
printf("\n%s\n\n", QPSXSERIAL_URL);
printf("Usage:\n\n");
printf("QPSXSerial --input_folder inpath --port PORTNAME [options]\n\n");
printf("The following options are available:\n");
printf("--disable_psx_stdout\t\tDisables stdout messages from console to PC\n\n");
printf("Additionally to PSX-EXEs, QPSXSerial can also send external files e.g.:\n"
"*.TIM or *.VAG files whenever the game requests such data.\n");
printf("A working example of this bidirectional communication can be found on my open-source\n");
printf("video game \"Airport\", available on the following Github repository:\n");
printf("\nhttps://github.com/XaviDCR92/Airport/\n\n");
printf("QPSXSerial, OpenSend and Airport written by Xavier Del Campo (aka Xavi92).\n");
printf("All software released under the General Public License (GPL-3.0).\n");
printf("This application has been created using Qt toolkit %s\n", QT_VERSION_STR);
printf("\nINFO: In case you do not set any parameters, GUI is enabled by default.\n");
}
QString QPSXSerial::getInputExeFromFolder(QString* folder)
{
QFile system_f(*folder + "\\SYSTEM.CNF");
QString fileSelected;
QString retExe;
if ( (folder->endsWith("/") == false) && (folder->endsWith("\\") == false) )
{
folder->append("/");
}
if (system_f.exists() == false)
{
QDir d(*folder);
QStringList filters;
filters.append("*.EXE");
filters.append("*.exe");
QStringList exe_list = d.entryList(filters);
if (exe_list.isEmpty())
{
retExe.clear();
return retExe;
}
if (exe_list.contains("PSX.EXE"))
{
fileSelected = "PSX.EXE";
}
else
{
fileSelected = exe_list.first();
}
}
else
{
if (system_f.open(QFile::ReadOnly) == false)
{
QString error = "Could not open " + *folder + "\\SYSTEM.CNF";
showGUICLIerror(error);
retExe.clear();
return retExe;
}
QTextStream system_txt(&system_f);
do
{
QString line = system_txt.readLine();
QStringList tokens = line.split("=");
if (tokens.isEmpty())
{
continue;
}
if (tokens.at(0).contains("BOOT") == false)
{
continue;
}
fileSelected = tokens.at(1).split("\\").at(1);
fileSelected.chop(2); // Remove ";1" suffix.
break;
}while (system_txt.atEnd() == false);
}
retExe = *folder + fileSelected;
return retExe;
}
void QPSXSerial::showGUICLIerror(QString error)
{
if (app_interface == GUI_APP)
{
showError(error);
}
else if (app_interface == CLI_APP)
{
const char* c_str_error = error.toStdString().c_str();
printf("%s\n", c_str_error);
}
}