Files
archived-pcsx2/pcsx2/USB/usb-printer/usb-printer.cpp

371 lines
11 KiB
C++

// SPDX-FileCopyrightText: 2002-2026 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "IconsPromptFont.h"
#include "USB/qemu-usb/qusb.h"
#include "USB/qemu-usb/USBinternal.h"
#include "USB/usb-printer/usb-printer.h"
#include "USB/USB.h"
#include "common/Console.h"
#include "common/FileSystem.h"
#include "common/Path.h"
#include "common/StringUtil.h"
#include "Config.h"
#include "fmt/format.h"
#include "StateWrapper.h"
#include "Host.h"
#include "IconsFontAwesome.h"
namespace usb_printer
{
static const char* sPrinterNames[] = {
"Sony DPP-MP1",
};
typedef struct PrinterState
{
USBDevice dev{};
USBDesc desc{};
USBDescDevice desc_dev{};
int selected_printer = 0;
int cmd_state = 0;
uint8_t last_command[65] = {};
int last_command_size = 0;
std::string print_filename;
std::FILE* print_file = nullptr;
int width = 0;
int height = 0;
long stride = 0;
int data_size = 0;
long data_pos = 0;
} PrinterState;
static void usb_printer_handle_reset(USBDevice* dev)
{
PrinterState* s = USB_CONTAINER_OF(dev, PrinterState, dev);
s->cmd_state = 0;
if (s->print_file)
{
std::fclose(s->print_file);
s->print_file = nullptr;
}
}
static void usb_printer_handle_control(USBDevice* dev, USBPacket* p, int request, int value,
int index, int length, uint8_t* data)
{
PrinterState* s = USB_CONTAINER_OF(dev, PrinterState, dev);
int ret = 0;
ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
if (ret >= 0)
{
return;
}
switch (request)
{
case ClassInterfaceRequest | GET_DEVICE_ID:
ret = 2 + StringUtil::Strlcpy((char*)data + 2, sPrinters[s->selected_printer].device_id, length - 2);
data[0] = ret >> 8;
data[1] = ret & 0xff;
p->actual_length = ret;
break;
case ClassInterfaceRequest | GET_PORT_STATUS:
data[0] = GET_PORT_STATUS_PAPER_NOT_EMPTY | GET_PORT_STATUS_SELECTED | GET_PORT_STATUS_NO_ERROR;
p->actual_length = 1;
break;
}
}
static void sony_open_file(PrinterState* s)
{
char cur_time_str[32];
const time_t cur_time = time(nullptr);
strftime(cur_time_str, sizeof(cur_time_str), "%Y_%m_%d_%H_%M_%S", localtime(&cur_time));
s->print_filename = Path::Combine(EmuFolders::Snapshots, fmt::format("print_{}.bmp", cur_time_str));
s->print_file = FileSystem::OpenCFile(s->print_filename.c_str(), "wb");
if (!s->print_file)
{
Host::AddIconOSDMessage("USBPrinterOpen", ICON_FA_TRIANGLE_EXCLAMATION,
fmt::format(TRANSLATE_FS("USB", "Failed to open '{}' for printing."), s->print_filename),
Host::OSD_ERROR_DURATION);
return;
}
Host::AddIconOSDMessage("USBPrinterOpen", ICON_FA_FLOPPY_DISK,
fmt::format(TRANSLATE_FS("USB", "Printer saving to '{}'..."), Path::GetFileName(s->print_filename)),
Host::OSD_INFO_DURATION);
BMPHeader header = {0};
header.magic = 0x4D42;
header.filesize = sizeof(BMPHeader) + 3 * s->width * s->height;
header.data_offset = sizeof(BMPHeader);
header.core_header_size = 0x0C;
header.width = s->width;
header.height = s->height;
header.planes = 1;
header.bpp = 24;
if (std::fwrite(&header, sizeof(header), 1, s->print_file) != 1)
{
Console.Error("Error writing header to print file");
}
s->stride = 3 * s->width + 3 - ((3 * s->width + 3) & 3);
s->data_pos = 0;
FileSystem::FSeek64(s->print_file, sizeof(BMPHeader) + s->stride * s->height - 1, SEEK_SET);
char zero = 0;
if (std::fwrite(&zero, 1, 1, s->print_file) != 1)
{
Console.Error("Error writing zero padding to header to print file");
}
}
static void sony_write_data(PrinterState* s, int size, uint8_t* data)
{
for (int i = 0; i < size; i++)
{
long line = (s->data_pos / 3) / s->width;
long col = (s->data_pos / 3) % s->width;
long pos_out = s->stride * (s->height - 1 - line) + 3 * col;
if (pos_out < 0)
{
Console.WriteLn("Printer: Sony: error: pos_out=0x%x", pos_out);
break;
}
// print_file might be null if we're loading a state
if (s->print_file)
{
FileSystem::FSeek64(s->print_file, sizeof(BMPHeader) + pos_out + 2 - s->data_pos % 3, SEEK_SET);
if (std::fwrite(data + i, 1, 1, s->print_file) != 1)
{
Console.Error("Error writing data to print file");
}
}
s->data_pos++;
}
}
static void sony_close_file(PrinterState* s)
{
Console.WriteLn("Printer: Sony: done.");
if (s->print_file)
{
std::fclose(s->print_file);
s->print_file = nullptr;
s->print_filename = {};
}
}
static void sony_cancel_file(PrinterState* s)
{
if (!s->print_file)
return;
Console.Warning("Removing incomplete printer file '%s'", s->print_filename.c_str());
std::fclose(s->print_file);
s->print_file = nullptr;
FileSystem::DeleteFilePath(s->print_filename.c_str());
s->print_filename = {};
}
static void usb_printer_handle_data_sony(USBDevice* dev, USBPacket* p)
{
struct req_reply
{
uint8_t cmd[64];
uint8_t ret[256];
};
const struct req_reply commands[] =
{
{{0x1B, 0xE0, 0x00, 0x00, 0x00, 0x0E, 0x00},
{0x0D, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x01, 0x24, 0x38, 0x03, 0xF2, 0x02, 0x74}},
{{0x1B, 0xCF, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00},
{0x00, 0x00, 0x00, 0x29, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
{{0x1B, 0xCF, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00},
{0x00, 0x00, 0x00, 0x12, 0x02, 0x00, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x0E, 0x00, 0x00, 0x07}},
{{0x1B, 0xCF, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00},
{0x00, 0x00, 0x00, 0x12, 0x03, 0x00, 0x20, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x07, 0x00, 0x00, 0x04}},
{{0x1B, 0xCF, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00},
{0x00, 0x00, 0x00, 0x0A, 0x04, 0x00, 0x00, 0x18, 0x01, 0x01, 0x01, 0x00, 0x02, 0x00}},
{{0x1B, 0xCF, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00},
{0x00, 0x00, 0x00, 0x0E, 0x05, 0x00, 0x02, 0x74, 0x03, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00}},
{{0x1B, 0x12, 0x01, 0x00, 0x00, 0x19, 0x00},
{0x02, 0xC0, 0x00, 0x15, 0x03, 0xF2, 0x02, 0x74, 0x03, 0xF2, 0x02, 0x74, 0x01, 0x33, 0x00, 0x00,
0x15, 0x01, 0x03, 0x23, 0x30, 0x31, 0x2E, 0x30, 0x30}}};
const uint8_t set_size[] = {0x00, 0x00, 0x00, 0x00, 0xa7, 0x00};
const uint8_t set_data[] = {0x1b, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t print_compl[] = {0x1b, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00};
PrinterState* s = USB_CONTAINER_OF(dev, PrinterState, dev);
//const uint8_t ep_nr = p->ep->nr;
//const uint8_t ep_type = p->ep->type;
switch (p->pid)
{
case USB_TOKEN_OUT:
s->last_command_size = p->buffer_size;
usb_packet_copy(p, s->last_command, s->last_command_size);
if (s->cmd_state == 0 && s->last_command_size > 5)
{
if (memcmp(s->last_command, set_size, sizeof(set_size)) == 0)
{
s->width = s->last_command[9] << 8 | s->last_command[10];
s->height = s->last_command[11] << 8 | s->last_command[12];
Console.WriteLn("Printer: Sony: Size=%dx%d", s->width, s->height);
sony_open_file(s);
}
else if (memcmp(s->last_command, set_data, sizeof(set_data)) == 0)
{
s->data_size = s->last_command[8] << 8 | s->last_command[9];
s->cmd_state = 1;
}
else if (memcmp(s->last_command, print_compl, sizeof(print_compl)) == 0)
{
sony_close_file(s);
}
}
else if (s->cmd_state == 1 && s->last_command_size > 0)
{
sony_write_data(s, s->last_command_size, s->last_command);
s->data_size -= s->last_command_size;
if (s->data_size <= 0)
{
s->cmd_state = 0;
}
}
break;
case USB_TOKEN_IN:
if (s->cmd_state == 0 && s->last_command_size > 0)
{
for (uint32_t i = 0; i < sizeof(commands) / sizeof(commands[0]); i++)
{
if (s->last_command[1] == 0xCF)
{
if (memcmp(s->last_command, commands[i].cmd, 11) == 0)
{
int length = commands[i].cmd[9];
usb_packet_copy(p, (void*)commands[i].ret, length);
s->last_command_size = 0;
}
}
else
{
if (memcmp(s->last_command, commands[i].cmd, 7) == 0)
{
int length = commands[i].cmd[5];
usb_packet_copy(p, (void*)commands[i].ret, length);
s->last_command_size = 0;
}
}
}
}
break;
default:
p->status = USB_RET_STALL;
break;
}
}
static void usb_printer_handle_destroy(USBDevice* dev)
{
PrinterState* s = USB_CONTAINER_OF(dev, PrinterState, dev);
sony_cancel_file(s);
delete s;
}
USBDevice* PrinterDevice::CreateDevice(SettingsInterface& si, u32 port, u32 subtype) const
{
PrinterState* s = new PrinterState();
s->selected_printer = std::min<u32>(subtype, std::size(sPrinters));
s->dev.speed = USB_SPEED_FULL;
s->desc.full = &s->desc_dev;
s->desc.str = sPrinters[subtype].usb_strings;
if (usb_desc_parse_dev(sPrinters[subtype].device_descriptor, sPrinters[subtype].device_descriptor_size, s->desc, s->desc_dev) < 0)
goto fail;
if (usb_desc_parse_config(sPrinters[subtype].config_descriptor, sPrinters[subtype].config_descriptor_size, s->desc_dev) < 0)
goto fail;
s->dev.klass.handle_attach = usb_desc_attach;
s->dev.klass.handle_reset = usb_printer_handle_reset;
s->dev.klass.handle_control = usb_printer_handle_control;
switch (sPrinters[subtype].protocol)
{
case ProtocolSonyUPD:
s->dev.klass.handle_data = usb_printer_handle_data_sony;
break;
}
s->dev.klass.unrealize = usb_printer_handle_destroy;
s->dev.klass.usb_desc = &s->desc;
s->dev.klass.product_desc = s->desc.str[2];
usb_desc_init(&s->dev);
usb_ep_init(&s->dev);
usb_printer_handle_reset(&s->dev);
return &s->dev;
fail:
usb_printer_handle_destroy(&s->dev);
return nullptr;
}
const char* PrinterDevice::Name() const
{
return TRANSLATE_NOOP("USB", "Printer");
}
const char* PrinterDevice::TypeName() const
{
return "printer";
}
const char* PrinterDevice::IconName() const
{
return ICON_PF_PRINTER;
}
bool PrinterDevice::Freeze(USBDevice* dev, StateWrapper& sw) const
{
PrinterState* s = USB_CONTAINER_OF(dev, PrinterState, dev);
if (!sw.DoMarker("PrinterDevice"))
return false;
sw.Do(&s->selected_printer);
sw.Do(&s->cmd_state);
sw.DoBytes(&s->last_command, sizeof(s->last_command));
sw.Do(&s->last_command_size);
sw.Do(&s->width);
sw.Do(&s->height);
sw.Do(&s->stride);
sw.Do(&s->data_size);
sw.Do(&s->data_pos);
// toss any file being saved when we're loading, since we'd probably
// end up with a corrupted file otherwise
if (sw.IsReading())
sony_cancel_file(s);
return true;
}
std::span<const char*> PrinterDevice::SubTypes() const
{
return sPrinterNames;
}
} // namespace usb_printer