#include #include #include #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(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(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); } }