Add a log message when loading libretro save state made with incompatible mkxp-z version

This commit is contained in:
刘皓 2025-06-04 11:32:32 -04:00
parent e342d6187c
commit 41557aba6e
No known key found for this signature in database
GPG key ID: 7901753DB465B711
7 changed files with 168 additions and 2 deletions

View file

@ -20,7 +20,8 @@
*/
#include "binding-sandbox.h"
#include <cstring>
#include "mkxp-polyfill.h" // std::strtol
#include <string>
#include <libretro.h>
#include <zlib.h>
#include "sharedstate.h"
@ -535,6 +536,26 @@ void sandbox_binding_init::operator()() {
static VALUE system_module;
static VALUE cfg_module;
struct register_ruby_revision : boost::asio::coroutine {
typedef decl_slots<wasm_ptr_t, VALUE, ID> slots;
void operator()() {
BOOST_ASIO_CORO_REENTER (this) {
SANDBOX_AWAIT_S(2, rb_intern, "RUBY_REVISION");
SANDBOX_AWAIT_S(1, rb_const_get, sb()->rb_cObject(), SANDBOX_SLOT(2));
SANDBOX_AWAIT_S(0, rb_string_value_cstr, &SANDBOX_SLOT(1));
struct sandbox_str_guard str = sb()->str(SANDBOX_SLOT(0));
if (std::strlen(str) != 2 * sizeof mkxp_retro::ruby_revision) {
std::abort();
}
for (size_t i = 0; i < sizeof mkxp_retro::ruby_revision; ++i) {
mkxp_retro::ruby_revision[i] = std::strtol(std::string(str + 2 * i, 2).c_str(), nullptr, 16);
}
}
}
};
struct register_utf8_encoding : boost::asio::coroutine {
typedef decl_slots<ID> slots;
@ -547,6 +568,7 @@ void sandbox_binding_init::operator()() {
};
BOOST_ASIO_CORO_REENTER (this) {
SANDBOX_AWAIT(register_ruby_revision);
SANDBOX_AWAIT(register_utf8_encoding);
SANDBOX_AWAIT(exception_binding_init);

View file

@ -0,0 +1,70 @@
/*
** hasher.cpp
**
** This file is part of mkxp.
**
** Copyright (C) 2013 - 2021 Amaryllis Kulla <ancurio@mapleshrine.eu>
**
** mkxp 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.
**
** mkxp 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 mkxp. If not, see <http://www.gnu.org/licenses/>.
*/
#include <cstdint>
#include <fstream>
#include <iostream>
#include <vector>
#include <picosha2.h>
int main(int argc, char **argv) {
if (argc < 2) {
std::cerr << "[hasher] error: at least one argument must be passed to this program" << std::endl;
return 1;
}
picosha2::hash256_one_by_one digest;
for (int i = 2; i < argc; ++i) {
std::ifstream file(argv[i], std::ios::binary);
if (!file.is_open()) {
std::cerr << "[hasher] error: could not open input file " << argv[i] << std::endl;
return 2;
}
std::vector<uint8_t> buffer;
for (;;) {
uint8_t byte = file.get();
if (file.eof()) {
break;
}
buffer.push_back(byte);
}
digest.process(buffer.begin(), buffer.end());
}
digest.finish();
std::vector<uint8_t> bytes(picosha2::k_digest_size);
digest.get_hash_bytes(bytes.begin(), bytes.end());
std::ofstream file(argv[1]);
if (!file.is_open()) {
std::cerr << "[hasher] error: could not open output file " << argv[1] << std::endl;
return 3;
}
file << "#ifndef MKXPZ_BINDING_SANDBOX_HASH" << std::endl;
file << "# define MKXPZ_BINDING_SANDBOX_HASH \"" << std::hex;
for (uint8_t byte : bytes) {
file << "\\x" << (unsigned int)byte;
}
file << '"' << std::endl << "#endif" << std::endl;
return 0;
}

View file

@ -1,4 +1,4 @@
global_sources += files(
binding_sandbox_sources = files(
'audio-binding.cpp',
'binding-base.cpp',
'binding-sandbox.cpp',
@ -22,3 +22,32 @@ global_sources += files(
'window-binding.cpp',
'windowvx-binding.cpp',
)
global_sources += binding_sandbox_sources
binding_sandbox_hasher = executable(
'binding-sandbox-hasher',
dependencies: subproject('picosha2').get_variable('picosha2'),
sources: 'hasher.cpp',
native: true,
override_options: [
'buildtype=release',
'b_coverage=false',
'b_lto=false',
'b_ndebug=false',
'b_pgo=off',
'b_sanitize=none',
'cpp_std=c++11',
],
)
global_sources += custom_target(
'binding-sandbox-hash',
input: binding_sandbox_sources,
output: 'binding-sandbox-hash.h',
command: [
binding_sandbox_hasher,
'@OUTPUT@',
'@INPUT@',
],
)

View file

@ -40,6 +40,7 @@
#include "mkxp-polyfill.h" // std::mutex, std::strtoul
#include "git-hash.h"
#include "binding-sandbox-hash.h"
#include "al-util.h"
#include "audio.h"
@ -636,6 +637,8 @@ namespace mkxp_retro {
uint8_t midi_chorus_override;
uint8_t midi_reverb_override;
uint8_t ruby_revision[20];
uint64_t get_ticks_ms() noexcept {
return frame_time / 1000;
}
@ -1622,6 +1625,19 @@ extern "C" RETRO_API bool retro_serialize(void *data, size_t len) {
// Write 4-byte version: 1
if (!sandbox_serialize((uint32_t)1, data, max_size)) return false;
// Write mkxp-z version
if (!sandbox_serialize(MKXPZ_VERSION "/" MKXPZ_GIT_HASH, data, max_size)) return false;
// Write 20-byte Ruby revision
RESERVE(sizeof ruby_revision);
std::memcpy(data, ruby_revision, sizeof ruby_revision);
ADVANCE(sizeof ruby_revision);
// Write 32-byte hash of binding-sandbox source files
RESERVE(sizeof MKXPZ_BINDING_SANDBOX_HASH - 1);
std::memcpy(data, MKXPZ_BINDING_SANDBOX_HASH, sizeof MKXPZ_BINDING_SANDBOX_HASH - 1);
ADVANCE(sizeof MKXPZ_BINDING_SANDBOX_HASH - 1);
// Write the capacity of the VM memory
if (!sandbox_serialize(sb()->memory_capacity(), data, max_size)) return false;
@ -1770,6 +1786,26 @@ extern "C" RETRO_API bool retro_unserialize(const void *data, size_t len) {
if (version != 1) return false;
}
// Read mkxp-z version that the save state was created by
std::string mkxpz_version;
if (!sandbox_deserialize(mkxpz_version, data, max_size)) return false;
// Make sure the Ruby revision matches that of that version of mkxp-z, since save state compatibility breaks when the Ruby version changes
RESERVE(sizeof ruby_revision);
if (std::memcmp(data, ruby_revision, sizeof ruby_revision)) {
log_printf(RETRO_LOG_ERROR, "Failed to load save state because it uses a different Ruby version than the current version of mkxp-z; try using mkxp-z version %s to load this save state instead\n", mkxpz_version.c_str());
return false;
}
ADVANCE(sizeof ruby_revision);
// Make sure the hash of the binding-sandbox source files matches that of that version of mkxp-z, since save state compatibility breaks when the sandbox bindings are modified
RESERVE(sizeof MKXPZ_BINDING_SANDBOX_HASH - 1);
if (std::memcmp(data, MKXPZ_BINDING_SANDBOX_HASH, sizeof MKXPZ_BINDING_SANDBOX_HASH - 1)) {
log_printf(RETRO_LOG_ERROR, "Failed to load save state because the sandbox bindings used are incompatible with the current version of mkxp-z; try using mkxp-z version %s to load this save state instead\n", mkxpz_version.c_str());
return false;
}
ADVANCE(sizeof MKXPZ_BINDING_SANDBOX_HASH - 1);
// Read the VM memory
{
wasm_size_t memory_capacity;

View file

@ -50,6 +50,8 @@ namespace mkxp_retro {
extern uint8_t midi_chorus_override;
extern uint8_t midi_reverb_override;
extern uint8_t ruby_revision[20];
uint64_t get_ticks_ms() noexcept;
uint64_t get_ticks_us() noexcept;
double get_refresh_rate() noexcept;

View file

@ -0,0 +1,2 @@
project('picosha2', 'cpp', meson_version: '>=1.3.0')
picosha2 = declare_dependency(include_directories: '.')

View file

@ -0,0 +1,5 @@
[wrap-git]
url = https://github.com/okdshin/PicoSHA2
revision = v1.0.1
depth = 1
patch_directory = picosha2