// 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(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 PrinterDevice::SubTypes() const { return sPrinterNames; } } // namespace usb_printer