From f8ac9ec6587d51a72fea07d1f4a36d7a7f3900cd Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Tue, 7 Apr 2020 01:10:59 -0700 Subject: [PATCH] ui: Add compatibility report dialog --- Makefile.target | 4 + ui/Makefile.objs | 6 +- ui/xemu-hud.cc | 174 ++++++++++++++++++++++++++++++++++++- ui/xemu-os-utils-linux.c | 67 ++++++++++++++ ui/xemu-os-utils-macos.m | 8 ++ ui/xemu-os-utils-windows.c | 7 ++ ui/xemu-os-utils.h | 14 +++ xemu-xbe.c | 149 +++++++++++++++++++++++++++++++ xemu-xbe.h | 46 ++++++++++ 9 files changed, 471 insertions(+), 4 deletions(-) create mode 100644 ui/xemu-os-utils-linux.c create mode 100644 ui/xemu-os-utils-macos.m create mode 100644 ui/xemu-os-utils-windows.c create mode 100644 ui/xemu-os-utils.h create mode 100644 xemu-xbe.c create mode 100644 xemu-xbe.h diff --git a/Makefile.target b/Makefile.target index 8ed1eba95b..934e599736 100644 --- a/Makefile.target +++ b/Makefile.target @@ -105,6 +105,10 @@ all: $(PROGS) stap obj-y += trace/ +######################################################### +# xemu +obj-y += xemu-xbe.o + ######################################################### # cpu emulator library obj-y += exec.o exec-vary.o diff --git a/ui/Makefile.objs b/ui/Makefile.objs index 5c9153f6b7..550c9bcdf2 100644 --- a/ui/Makefile.objs +++ b/ui/Makefile.objs @@ -45,15 +45,15 @@ ui/xemu-shaders.o: ui/shader/xemu-logo-frag.h ifeq ($(CONFIG_WIN32),y) IMGUI_FLAGS = -DWIN32 -DMINGW32 -sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_win32.o +sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_win32.o xemu-os-utils-win32.o endif ifeq ($(CONFIG_LINUX),y) IMGUI_FLAGS = -DLINUX -sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_gtk.o +sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_gtk.o xemu-os-utils-linux.o endif ifeq ($(CONFIG_DARWIN),y) IMGUI_FLAGS = -DAPPLE -sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_macos.o +sdl.mo-objs := $(sdl.mo-objs) noc_file_dialog_macos.o xemu-os-utils-macos.o endif sdl.mo-cflags := $(SDL_CFLAGS) -DIMGUI_IMPL_OPENGL_LOADER_CUSTOM="\"epoxy/gl.h\"" -Iui/imgui $(IMGUI_FLAGS) diff --git a/ui/xemu-hud.cc b/ui/xemu-hud.cc index fd428514d5..1f4fcde1e2 100644 --- a/ui/xemu-hud.cc +++ b/ui/xemu-hud.cc @@ -57,6 +57,7 @@ extern "C" { #include "qapi/qmp/qdict.h" #include "qemu/option.h" #include "qemu/config-file.h" + #undef typename #undef atomic_fetch_add #undef atomic_fetch_and @@ -99,6 +100,9 @@ static void ShowAboutWindow(bool* p_open); bool show_network_window = false; static void ShowNetworkWindow(bool* p_open); +bool show_compatibility_reporter_window = true; +static void ShowCompatibilityReporter(bool* p_open); + bool show_demo_window = false; float ui_scale = 1.0; @@ -304,6 +308,8 @@ static void ShowMainMenu() } if (ImGui::BeginMenu("Help")) { + ImGui::MenuItem("Report Compatibility", NULL, &show_compatibility_reporter_window); + ImGui::Separator(); ImGui::MenuItem("About", NULL, &show_about_window); ImGui::EndMenu(); } @@ -532,6 +538,7 @@ void xemu_hud_render(SDL_Window *window) if (show_monitor_window) ShowMonitorConsole(&show_monitor_window); if (show_about_window) ShowAboutWindow(&show_about_window); if (show_network_window) ShowNetworkWindow(&show_network_window); + if (show_compatibility_reporter_window) ShowCompatibilityReporter(&show_compatibility_reporter_window); if (show_demo_window) ImGui::ShowDemoWindow(&show_demo_window); if (notification.active) { @@ -1318,7 +1325,7 @@ struct NetworkWindow bool is_enabled = xemu_net_is_enabled(); ImGui::TextWrapped( - "xemu socket networking works by sending and recieving packets over " + "xemu socket networking works by sending and receiving packets over " "UDP which encapsulate the network traffic that the machine would " "send or recieve when connected to a Local Area Network (LAN)." ); @@ -1385,3 +1392,168 @@ static void ShowNetworkWindow(bool* p_open) static NetworkWindow console; console.Draw("Network", p_open); } + +#ifdef WIN32 +// https://stackoverflow.com/a/2513561 +#include +unsigned long long getTotalSystemMemory() +{ + MEMORYSTATUSEX status; + status.dwLength = sizeof(status); + GlobalMemoryStatusEx(&status); + return status.ullTotalPhys / (1024 * 1024); +} +#else +#include +unsigned long long getTotalSystemMemory() +{ + long pages = sysconf(_SC_PHYS_PAGES); + long page_size = sysconf(_SC_PAGE_SIZE); + return pages * page_size / (1024 * 1024); +} +#endif + +#ifdef CONFIG_CPUID_H +#include +#endif + +const char *get_cpu_info(void) +{ + const char *cpu_info = ""; +#ifdef CONFIG_CPUID_H + static uint32_t brand[12]; + if (__get_cpuid_max(0x80000004, NULL)) { + __get_cpuid(0x80000002, brand+0x0, brand+0x1, brand+0x2, brand+0x3); + __get_cpuid(0x80000003, brand+0x4, brand+0x5, brand+0x6, brand+0x7); + __get_cpuid(0x80000004, brand+0x8, brand+0x9, brand+0xa, brand+0xb); + } + cpu_info = (const char *)brand; +#endif + // FIXME: Support other architectures (e.g. ARM) + return cpu_info; +} + +#include "xemu-os-utils.h" +#include "xemu-xbe.h" + +struct CompatibilityReporter +{ + CompatibilityReporter() + { + } + + ~CompatibilityReporter() + { + } + + void Draw(const char* title, bool* p_open) + { + ImVec2 size(450*ui_scale, 475*ui_scale); + + ImGui::SetNextWindowSize(size, ImGuiCond_Appearing); + if (!ImGui::Begin(title, p_open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse)) { + ImGui::End(); + return; + } + + static char gpu_info[1024]; + static char xbe_info[512]; + static char report_info[4096]; + static int report_info_initialized = 0; + + if (ImGui::IsWindowAppearing()) { + // Refresh whenever the window is re-opened + report_info_initialized = 0; + } + + if (!report_info_initialized) { + struct xbe_info *xbe = xemu_get_xbe_info(); + snprintf( + gpu_info, + sizeof(gpu_info), + "%s, %s, %s, %s", + glGetString(GL_VENDOR), + glGetString(GL_RENDERER), + glGetString(GL_VERSION), + glGetString(GL_SHADING_LANGUAGE_VERSION) + ); + + if (xbe) { + snprintf(xbe_info, sizeof(xbe_info), + "%s v1.%02d", xbe->cert_title_id_str, xbe->cert_version); + } else { + xbe_info[0] = '\x00'; + } + + snprintf( + report_info, + sizeof(report_info), + "xemu: %s [branch %s on %s]\n" + "OS: %s\n" + "CPU: %s\n" + "GPU: %s\n" + "Memory: %lld M\n" + "XBE: %s", + xemu_version, + xemu_branch, + xemu_date, + xemu_get_os_info(), + get_cpu_info(), + gpu_info, + getTotalSystemMemory(), + xbe_info + ); + + report_info_initialized = 1; + } + + ImGui::TextWrapped( + "If you would like to submit a compatibility report for this " + "title, including some basic information about your system listed " + "below, please select an appropriate playability level, enter a " + "brief description of your experience, then click 'Send.' Note: " + "this information may be made publicly available."); + + ImGui::Dummy(ImVec2(0, 5*ui_scale)); + ImGui::Separator(); + ImGui::Dummy(ImVec2(0, 5*ui_scale)); + + static int playability; + ImGui::Combo("Playability Rating", &playability, + "Unknown\0" "Broken\0" "Intro/Menus\0" "Starts\0" "Playable\0" "Perfect\0"); + + char buf[64]; + buf[0] = '\x00'; + ImGui::InputText("Contributor Token", buf, sizeof(buf), 0); + ImGui::SameLine(); + HelpMarker("Optional. This is a unique token that trusted users may " + "provide in order to expedite publication of their compatibility " + "reports."); + + char description[255] = {0}; + ImGui::Text("Description"); + ImGui::InputTextMultiline("###desc", description, sizeof(description), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 6), 0); + + ImGui::Text("Additional Information"); + ImGui::PushFont(fixed_width_font); + ImGui::InputTextMultiline("##build_info", report_info, IM_ARRAYSIZE(report_info), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 7), ImGuiInputTextFlags_ReadOnly); + ImGui::PopFont(); + + ImGui::Columns(1); + + ImGui::SetCursorPosY(ImGui::GetWindowHeight()-(10+25)*ui_scale); + ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)*ui_scale); + + ImGui::SetItemDefaultFocus(); + if (ImGui::Button("Send", ImVec2(120*ui_scale, 0))) { + } + + ImGui::End(); + } +}; + +static void ShowCompatibilityReporter(bool* p_open) +{ + static CompatibilityReporter console; + console.Draw("Report Compatibility", p_open); +} diff --git a/ui/xemu-os-utils-linux.c b/ui/xemu-os-utils-linux.c new file mode 100644 index 0000000000..ff74b8030e --- /dev/null +++ b/ui/xemu-os-utils-linux.c @@ -0,0 +1,67 @@ +#include "xemu-os-utils.h" +#include +#include +#include +#include +#include + +static char *read_file_if_possible(const char *path) +{ + FILE *fd = fopen(path, "rb"); + if (fd == NULL) { + return NULL; + } + + fseek(fd, 0, SEEK_END); + size_t size = ftell(fd); + fseek(fd, 0, SEEK_SET); + + char *buf = malloc(size+1); + int status = fread(buf, 1, size, fd); + if (status != size) { + free(buf); + return NULL; + } + + buf[size] = '\x00'; + return buf; +} + +const char *xemu_get_os_info(void) +{ + static const char *os_info = NULL; + static int attempted_init = 0; + + if (!attempted_init) { + char *os_release = NULL; + + // Try to get the Linux distro "pretty name" from /etc/os-release + char *os_release_file = read_file_if_possible("/etc/os-release"); + if (os_release_file != NULL) { + char *pretty_name = strstr(os_release_file, "PRETTY_NAME=\""); + if (pretty_name != NULL) { + pretty_name = pretty_name + 13; + char *pretty_name_end = strchr(pretty_name, '"'); + if (pretty_name_end != NULL) { + size_t len = pretty_name_end-pretty_name; + os_release = malloc(len+1); + assert(os_release != NULL); + memcpy(os_release, pretty_name, len); + os_release[len] = '\x00'; + } + } + free(os_release_file); + } + + os_info = g_strdup_printf("%s", + os_release ? os_release : "Unknown Distro" + ); + if (os_release) { + free(os_release); + } + + attempted_init = 1; + } + + return os_info; +} diff --git a/ui/xemu-os-utils-macos.m b/ui/xemu-os-utils-macos.m new file mode 100644 index 0000000000..bc05ced262 --- /dev/null +++ b/ui/xemu-os-utils-macos.m @@ -0,0 +1,8 @@ +#import +#include "xemu-os-utils.h" + +const char *xemu_get_os_info(void) +{ + return [[[NSProcessInfo processInfo] operatingSystemVersionString] UTF8String]; +} + diff --git a/ui/xemu-os-utils-windows.c b/ui/xemu-os-utils-windows.c new file mode 100644 index 0000000000..b097a2903d --- /dev/null +++ b/ui/xemu-os-utils-windows.c @@ -0,0 +1,7 @@ +#include "xemu-os-utils.h" + +const char *xemu_get_os_info(void) +{ + return "Windows"; +} + diff --git a/ui/xemu-os-utils.h b/ui/xemu-os-utils.h new file mode 100644 index 0000000000..abe4482fd4 --- /dev/null +++ b/ui/xemu-os-utils.h @@ -0,0 +1,14 @@ +#ifndef XEMU_OS_UTILS_H +#define XEMU_OS_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +const char *xemu_get_os_info(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/xemu-xbe.c b/xemu-xbe.c new file mode 100644 index 0000000000..ec4e31bcea --- /dev/null +++ b/xemu-xbe.c @@ -0,0 +1,149 @@ +#include "xemu-xbe.h" +#include "qemu/osdep.h" +#include "hw/hw.h" +#include "hw/i386/pc.h" +#include "hw/pci/pci.h" +#include "monitor/hmp-target.h" +#include "sysemu/hw_accel.h" + +#if 0 +// http://www.caustik.com/cxbx/download/xbe.htm +struct xbe_header +{ + uint32_t m_magic; // magic number [should be "XBEH"] + uint8_t m_digsig[256]; // digital signature + uint32_t m_base; // base address + uint32_t m_sizeof_headers; // size of headers + uint32_t m_sizeof_image; // size of image + uint32_t m_sizeof_image_header; // size of image header + uint32_t m_timedate; // timedate stamp + uint32_t m_certificate_addr; // certificate address + uint32_t m_sections; // number of sections + uint32_t m_section_headers_addr; // section headers address + + struct init_flags + { + uint32_t m_mount_utility_drive : 1; // mount utility drive flag + uint32_t m_format_utility_drive : 1; // format utility drive flag + uint32_t m_limit_64mb : 1; // limit development kit run time memory to 64mb flag + uint32_t m_dont_setup_harddisk : 1; // don't setup hard disk flag + uint32_t m_unused : 4; // unused (or unknown) + uint32_t m_unused_b1 : 8; // unused (or unknown) + uint32_t m_unused_b2 : 8; // unused (or unknown) + uint32_t m_unused_b3 : 8; // unused (or unknown) + } m_init_flags; + + uint32_t m_entry; // entry point address + uint32_t m_tls_addr; // thread local storage directory address + uint32_t m_pe_stack_commit; // size of stack commit + uint32_t m_pe_heap_reserve; // size of heap reserve + uint32_t m_pe_heap_commit; // size of heap commit + uint32_t m_pe_base_addr; // original base address + uint32_t m_pe_sizeof_image; // size of original image + uint32_t m_pe_checksum; // original checksum + uint32_t m_pe_timedate; // original timedate stamp + uint32_t m_debug_pathname_addr; // debug pathname address + uint32_t m_debug_filename_addr; // debug filename address + uint32_t m_debug_unicode_filename_addr; // debug unicode filename address + uint32_t m_kernel_image_thunk_addr; // kernel image thunk address + uint32_t m_nonkernel_import_dir_addr; // non kernel import directory address + uint32_t m_library_versions; // number of library versions + uint32_t m_library_versions_addr; // library versions address + uint32_t m_kernel_library_version_addr; // kernel library version address + uint32_t m_xapi_library_version_addr; // xapi library version address + uint32_t m_logo_bitmap_addr; // logo bitmap address + uint32_t m_logo_bitmap_size; // logo bitmap size +}; + +struct xbe_certificate +{ + uint32_t m_size; // size of certificate + uint32_t m_timedate; // timedate stamp + uint32_t m_titleid; // title id + uint16_t m_title_name[40]; // title name (unicode) + uint32_t m_alt_title_id[0x10]; // alternate title ids + uint32_t m_allowed_media; // allowed media types + uint32_t m_game_region; // game region + uint32_t m_game_ratings; // game ratings + uint32_t m_disk_number; // disk number + uint32_t m_version; // version + uint8_t m_lan_key[16]; // lan key + uint8_t m_sig_key[16]; // signature key + uint8_t m_title_alt_sig_key[16][16]; // alternate signature keys +}; +#endif + +static int virt_to_phys(target_ulong virt_addr, hwaddr *phys_addr) +{ + MemTxAttrs attrs; + CPUState *cs; + hwaddr gpa; + + cs = qemu_get_cpu(0); + if (!cs) { + return 1; // No cpu + } + + cpu_synchronize_state(cs); + + gpa = cpu_get_phys_page_attrs_debug(cs, virt_addr & TARGET_PAGE_MASK, &attrs); + if (gpa == -1) { + return 1; // Unmapped + } else { + *phys_addr = gpa + (virt_addr & ~TARGET_PAGE_MASK); + } + + return 0; +} + +// Get current XBE info +struct xbe_info *xemu_get_xbe_info(void) +{ + static struct xbe_info xbe_info; + hwaddr hdr_addr; + + // Get physical page offset of headers + if (virt_to_phys(0x10000, &hdr_addr) != 0) { + return NULL; + } + + // Check signature + uint32_t sig = ldl_le_phys(&address_space_memory, hdr_addr); + if (sig != 0x48454258) { + return NULL; + } + + xbe_info.timedate = ldl_le_phys(&address_space_memory, hdr_addr+0x114); + + // Find certificate (likely on same page, but be safe and map it) + uint32_t cert_addr_virt = ldl_le_phys(&address_space_memory, hdr_addr+0x118); + if (cert_addr_virt == 0) { + return NULL; + } + + hwaddr cert_addr; + if (virt_to_phys(cert_addr_virt, &cert_addr) != 0) { + return NULL; + } + + // Extract title info from certificate + xbe_info.cert_timedate = ldl_le_phys(&address_space_memory, cert_addr+0x04); + xbe_info.cert_title_id = ldl_le_phys(&address_space_memory, cert_addr+0x08); + xbe_info.cert_version = ldl_le_phys(&address_space_memory, cert_addr+0xac); + + // Generate friendly name for title id + uint8_t pub_hi = xbe_info.cert_title_id >> 24; + uint8_t pub_lo = xbe_info.cert_title_id >> 16; + + if ((65 > pub_hi) || (pub_hi > 90) || (65 > pub_lo) || (pub_lo > 90)) { + // Non-printable publisher id + snprintf(xbe_info.cert_title_id_str, sizeof(xbe_info.cert_title_id_str), + "0x%08x", xbe_info.cert_title_id); + } else { + // Printable publisher id + snprintf(xbe_info.cert_title_id_str, sizeof(xbe_info.cert_title_id_str), + "%c%c-%03u", pub_hi, pub_lo, xbe_info.cert_title_id & 0xffff); + } + + return &xbe_info; +} diff --git a/xemu-xbe.h b/xemu-xbe.h new file mode 100644 index 0000000000..41354d81a8 --- /dev/null +++ b/xemu-xbe.h @@ -0,0 +1,46 @@ +/* + * xemu XBE accessing + * + * Helper functions to get details about the currently running executable. + * + * Copyright (C) 2020 Matt Borgerson + * + * 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 2 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 . + */ + +#ifndef XEMU_XBE_H +#define XEMU_XBE_H + +#include + +struct xbe_info { + uint32_t timedate; + uint32_t cert_timedate; + uint32_t cert_title_id; + char cert_title_id_str[12]; + uint32_t cert_version; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +// Get current XBE info +struct xbe_info *xemu_get_xbe_info(void); + +#ifdef __cplusplus +} +#endif + +#endif