ui: Improve extra snapshot data storage

- Store snapshot extra data in BE, per convention
- Add a version field for back compat
- Some minor refactoring and renaming for clarity
This commit is contained in:
Matt Borgerson 2023-06-03 22:22:24 -07:00 committed by mborgerson
parent 60a7f55ac4
commit f62bfc95e3
4 changed files with 114 additions and 165 deletions

View File

@ -49,88 +49,65 @@ const char **g_snapshot_shortcut_index_key_map[] = {
&g_config.general.snapshots.shortcuts.f8,
};
static bool xemu_snapshots_load_thumbnail(BlockDriverState *bs_ro,
XemuSnapshotData *data,
int64_t *offset)
{
int res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->thumbnail, *offset,
sizeof(TextureBuffer) -
sizeof(data->thumbnail.buffer));
if (res != sizeof(TextureBuffer) - sizeof(data->thumbnail.buffer))
return false;
*offset += res;
data->thumbnail.buffer = g_malloc(data->thumbnail.size);
res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->thumbnail.buffer, *offset,
data->thumbnail.size);
if (res != data->thumbnail.size) {
return false;
}
*offset += res;
return true;
}
static void xemu_snapshots_load_data(BlockDriverState *bs_ro,
QEMUSnapshotInfo *info,
XemuSnapshotData *data, Error **err)
{
int res;
XemuSnapshotHeader header;
data->xbe_title_name = NULL;
data->gl_thumbnail = 0;
int res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err);
if (res < 0) {
return;
}
uint32_t header[3];
int64_t offset = 0;
data->xbe_title_present = false;
data->thumbnail_present = false;
res = bdrv_snapshot_load_tmp(bs_ro, info->id_str, info->name, err);
if (res < 0)
return;
res = bdrv_load_vmstate(bs_ro, (uint8_t *)&header, offset, sizeof(header));
if (res != sizeof(header))
goto error;
offset += res;
if (header.magic != XEMU_SNAPSHOT_DATA_MAGIC)
goto error;
res = bdrv_load_vmstate(bs_ro, (uint8_t *)&data->xbe_title_len, offset,
sizeof(data->xbe_title_len));
if (res != sizeof(data->xbe_title_len))
goto error;
offset += res;
data->xbe_title = (char *)g_malloc(data->xbe_title_len);
res = bdrv_load_vmstate(bs_ro, (uint8_t *)data->xbe_title, offset,
data->xbe_title_len);
if (res != data->xbe_title_len)
goto error;
offset += res;
data->xbe_title_present = (offset <= sizeof(header) + header.size);
if (offset == sizeof(header) + header.size)
if (res != sizeof(header)) {
return;
}
offset += res;
if (!xemu_snapshots_load_thumbnail(bs_ro, data, &offset)) {
goto error;
if (be32_to_cpu(header[0]) != XEMU_SNAPSHOT_DATA_MAGIC ||
be32_to_cpu(header[1]) != XEMU_SNAPSHOT_DATA_VERSION) {
return;
}
data->thumbnail_present = (offset <= sizeof(header) + header.size);
if (data->thumbnail_present) {
glGenTextures(1, &data->gl_thumbnail);
xemu_snapshots_render_thumbnail(data->gl_thumbnail, &data->thumbnail);
size_t size = be32_to_cpu(header[2]);
uint8_t *buf = g_malloc(size);
res = bdrv_load_vmstate(bs_ro, buf, offset, size);
if (res != size) {
g_free(buf);
return;
}
return;
error:
g_free(data->xbe_title);
data->xbe_title_present = false;
const size_t xbe_title_name_size = buf[0];
offset = 1;
g_free(data->thumbnail.buffer);
data->thumbnail_present = false;
if (xbe_title_name_size) {
data->xbe_title_name = (char *)g_malloc(xbe_title_name_size + 1);
memcpy(data->xbe_title_name, &buf[offset], xbe_title_name_size);
data->xbe_title_name[xbe_title_name_size] = 0;
offset += xbe_title_name_size;
}
const size_t thumbnail_size = be32_to_cpu(*(uint32_t *)&buf[offset]);
offset += 4;
if (thumbnail_size) {
GLuint thumbnail;
glGenTextures(1, &thumbnail);
if (xemu_snapshots_load_png_to_texture(thumbnail, &buf[offset],
thumbnail_size)) {
data->gl_thumbnail = thumbnail;
} else {
glDeleteTextures(1, &thumbnail);
}
offset += thumbnail_size;
}
g_free(buf);
}
static void xemu_snapshots_all_load_data(QEMUSnapshotInfo **info,
@ -144,12 +121,8 @@ static void xemu_snapshots_all_load_data(QEMUSnapshotInfo **info,
if (*data) {
for (int i = 0; i < xemu_snapshots_len; ++i) {
if ((*data)[i].xbe_title_present) {
g_free((*data)[i].xbe_title);
}
if ((*data)[i].thumbnail_present) {
g_free((*data)[i].thumbnail.buffer);
g_free((*data)[i].xbe_title_name);
if ((*data)[i].gl_thumbnail) {
glDeleteTextures(1, &((*data)[i].gl_thumbnail));
}
}
@ -250,65 +223,61 @@ void xemu_snapshots_delete(const char *vm_name, Error **err)
void xemu_snapshots_save_extra_data(QEMUFile *f)
{
size_t xbe_title_name_size = 0;
char *xbe_title_name = NULL;
struct xbe *xbe_data = xemu_get_xbe_info();
int64_t xbe_title_len = 0;
char *xbe_title = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL,
&xbe_title_len, NULL);
xbe_title_len++;
XemuSnapshotHeader header = { XEMU_SNAPSHOT_DATA_MAGIC, 0 };
header.size += sizeof(xbe_title_len);
header.size += xbe_title_len;
TextureBuffer *thumbnail = xemu_snapshots_extract_thumbnail();
if (thumbnail && thumbnail->buffer) {
header.size += sizeof(TextureBuffer) - sizeof(thumbnail->buffer);
header.size += thumbnail->size;
if (xbe_data && xbe_data->cert) {
glong items_written = 0;
xbe_title_name = g_utf16_to_utf8(xbe_data->cert->m_title_name, 40, NULL, &items_written, NULL);
if (xbe_title_name) {
xbe_title_name_size = items_written;
}
}
qemu_put_buffer(f, (const uint8_t *)&header, sizeof(header));
qemu_put_buffer(f, (const uint8_t *)&xbe_title_len, sizeof(xbe_title_len));
qemu_put_buffer(f, (const uint8_t *)xbe_title, xbe_title_len);
size_t thumbnail_size = 0;
void *thumbnail_buf = xemu_snapshots_create_framebuffer_thumbnail_png(&thumbnail_size);
if (thumbnail && thumbnail->buffer) {
qemu_put_buffer(f, (const uint8_t *)thumbnail,
sizeof(TextureBuffer) - sizeof(thumbnail->buffer));
qemu_put_buffer(f, (const uint8_t *)thumbnail->buffer, thumbnail->size);
qemu_put_be32(f, XEMU_SNAPSHOT_DATA_MAGIC);
qemu_put_be32(f, XEMU_SNAPSHOT_DATA_VERSION);
qemu_put_be32(f, 1 + xbe_title_name_size + 4 + thumbnail_size);
qemu_put_byte(f, xbe_title_name_size);
if (xbe_title_name_size) {
qemu_put_buffer(f, (const uint8_t *)xbe_title_name, xbe_title_name_size);
g_free(xbe_title_name);
}
g_free(xbe_title);
if (thumbnail && thumbnail->buffer) {
g_free(thumbnail->buffer);
qemu_put_be32(f, thumbnail_size);
if (thumbnail_size) {
qemu_put_buffer(f, (const uint8_t *)thumbnail_buf, thumbnail_size);
g_free(thumbnail_buf);
}
g_free(thumbnail);
xemu_snapshots_dirty = true;
}
bool xemu_snapshots_offset_extra_data(QEMUFile *f)
{
size_t ret;
XemuSnapshotHeader header;
ret = qemu_get_buffer(f, (uint8_t *)&header, sizeof(header));
if (ret != sizeof(header)) {
return false;
unsigned int v;
uint32_t version;
uint32_t size;
v = qemu_get_be32(f);
if (v != XEMU_SNAPSHOT_DATA_MAGIC) {
qemu_file_skip(f, -4);
return true;
}
if (header.magic == XEMU_SNAPSHOT_DATA_MAGIC) {
/*
* qemu_file_skip only works if you aren't skipping past its buffer.
* Unfortunately, it's not usable here.
*/
void *buf = g_malloc(header.size);
qemu_get_buffer(f, buf, header.size);
g_free(buf);
} else {
qemu_file_skip(f, -((int)sizeof(header)));
}
version = qemu_get_be32(f);
(void)version;
/* qemu_file_skip only works if you aren't skipping past internal buffer limit.
* Unfortunately, it's not usable here.
*/
size = qemu_get_be32(f);
void *buf = g_malloc(size);
qemu_get_buffer(f, buf, size);
g_free(buf);
return true;
}

View File

@ -26,33 +26,19 @@ extern "C" {
#include "qemu/osdep.h"
#include "block/snapshot.h"
#include <epoxy/gl.h>
#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75 // 'xemu'
#define XEMU_SNAPSHOT_DATA_VERSION 1
#define XEMU_SNAPSHOT_DATA_MAGIC 0x78656d75
#define XEMU_SNAPSHOT_THUMBNAIL_WIDTH 160
#define XEMU_SNAPSHOT_THUMBNAIL_HEIGHT 120
extern const char **g_snapshot_shortcut_index_key_map[];
#pragma pack(1)
typedef struct TextureBuffer {
int channels;
unsigned long size;
void *buffer;
} TextureBuffer;
#pragma pack()
typedef struct XemuSnapshotHeader {
uint32_t magic;
uint32_t size;
} XemuSnapshotHeader;
typedef struct XemuSnapshotData {
int64_t xbe_title_len;
char *xbe_title;
bool xbe_title_present;
TextureBuffer thumbnail;
bool thumbnail_present;
unsigned int gl_thumbnail;
char *xbe_title_name;
GLuint gl_thumbnail;
} XemuSnapshotData;
// Implemented in xemu-snapshots.c
@ -67,10 +53,9 @@ bool xemu_snapshots_offset_extra_data(QEMUFile *f);
void xemu_snapshots_mark_dirty(void);
// Implemented in xemu-thumbnail.cc
void xemu_snapshots_set_framebuffer_texture(unsigned int tex, bool flip);
void xemu_snapshots_render_thumbnail(unsigned int tex,
TextureBuffer *thumbnail);
TextureBuffer *xemu_snapshots_extract_thumbnail(void);
void xemu_snapshots_set_framebuffer_texture(GLuint tex, bool flip);
bool xemu_snapshots_load_png_to_texture(GLuint tex, void *buf, size_t size);
void *xemu_snapshots_create_framebuffer_thumbnail_png(size_t *size);
#ifdef __cplusplus
}

View File

@ -33,14 +33,13 @@ void xemu_snapshots_set_framebuffer_texture(GLuint tex, bool flip)
display_flip = flip;
}
void xemu_snapshots_render_thumbnail(GLuint tex, TextureBuffer *thumbnail)
bool xemu_snapshots_load_png_to_texture(GLuint tex, void *buf, size_t size)
{
std::vector<uint8_t> pixels;
unsigned int width, height, channels;
if (fpng::fpng_decode_memory(
thumbnail->buffer, thumbnail->size, pixels, width, height, channels,
thumbnail->channels) != fpng::FPNG_DECODE_SUCCESS) {
return;
if (fpng::fpng_decode_memory(buf, size, pixels, width, height, channels,
3) != fpng::FPNG_DECODE_SUCCESS) {
return false;
}
glActiveTexture(GL_TEXTURE0);
@ -52,9 +51,11 @@ void xemu_snapshots_render_thumbnail(GLuint tex, TextureBuffer *thumbnail)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
GL_UNSIGNED_BYTE, pixels.data());
return true;
}
TextureBuffer *xemu_snapshots_extract_thumbnail()
void *xemu_snapshots_create_framebuffer_thumbnail_png(size_t *size)
{
/*
* Avoids crashing if a snapshot is made on a thread with no GL context
@ -74,10 +75,8 @@ TextureBuffer *xemu_snapshots_extract_thumbnail()
return NULL;
}
TextureBuffer *thumbnail = (TextureBuffer *)g_malloc(sizeof(TextureBuffer));
thumbnail->buffer = g_malloc(png.size() * sizeof(uint8_t));
thumbnail->channels = 3;
thumbnail->size = png.size() * sizeof(uint8_t);
memcpy(thumbnail->buffer, png.data(), thumbnail->size);
return thumbnail;
void *buf = g_malloc(png.size());
memcpy(buf, png.data(), png.size());
*size = png.size();
return buf;
}

View File

@ -955,8 +955,8 @@ void MainMenuSnapshotsView::Draw()
bool search_buf_equal = false;
for (int i = m_snapshots_len - 1; i >= 0; i--) {
if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_present &&
(strcmp(m_current_title_name, m_extra_data[i].xbe_title) != 0)) {
if (g_config.general.snapshots.filter_current_game && m_extra_data[i].xbe_title_name &&
(strcmp(m_current_title_name, m_extra_data[i].xbe_title_name) != 0)) {
continue;
}
@ -968,8 +968,8 @@ void MainMenuSnapshotsView::Draw()
keep_entry |= g_match_info_matches(match);
g_match_info_free(match);
if (m_extra_data[i].xbe_title_present) {
g_regex_match(m_search_regex, m_extra_data[i].xbe_title, (GRegexMatchFlags)0, &match);
if (m_extra_data[i].xbe_title_name) {
g_regex_match(m_search_regex, m_extra_data[i].xbe_title_name, (GRegexMatchFlags)0, &match);
keep_entry |= g_match_info_matches(match);
g_free(match);
}
@ -982,14 +982,10 @@ void MainMenuSnapshotsView::Draw()
search_buf_equal |= g_strcmp0(m_search_buf.c_str(), m_snapshots[i].name) == 0;
ImGui::PushID(i);
GLuint thumbnail = 0;
if (m_extra_data[i].thumbnail_present) {
thumbnail = m_extra_data[i].gl_thumbnail;
}
SnapshotBigButton(
m_snapshots + i,
m_extra_data[i].xbe_title_present ? m_extra_data[i].xbe_title : "Unknown",
thumbnail
m_extra_data[i].xbe_title_name ? m_extra_data[i].xbe_title_name : "Unknown",
m_extra_data[i].gl_thumbnail
);
ImGui::PopID();
}