ui: Add compatibility report dialog

This commit is contained in:
Matt Borgerson 2020-04-07 01:10:59 -07:00
parent d31e10cc59
commit f8ac9ec658
9 changed files with 471 additions and 4 deletions

View File

@ -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

View File

@ -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)

View File

@ -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 <windows.h>
unsigned long long getTotalSystemMemory()
{
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status);
return status.ullTotalPhys / (1024 * 1024);
}
#else
#include <unistd.h>
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 <cpuid.h>
#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);
}

67
ui/xemu-os-utils-linux.c Normal file
View File

@ -0,0 +1,67 @@
#include "xemu-os-utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <glib.h>
#include <glib/gprintf.h>
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;
}

8
ui/xemu-os-utils-macos.m Normal file
View File

@ -0,0 +1,8 @@
#import <Foundation/Foundation.h>
#include "xemu-os-utils.h"
const char *xemu_get_os_info(void)
{
return [[[NSProcessInfo processInfo] operatingSystemVersionString] UTF8String];
}

View File

@ -0,0 +1,7 @@
#include "xemu-os-utils.h"
const char *xemu_get_os_info(void)
{
return "Windows";
}

14
ui/xemu-os-utils.h Normal file
View File

@ -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

149
xemu-xbe.c Normal file
View File

@ -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;
}

46
xemu-xbe.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef XEMU_XBE_H
#define XEMU_XBE_H
#include <stdint.h>
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