mirror of
https://github.com/izzy2lost/xemu.git
synced 2026-07-06 00:20:22 -07:00
online, HDD, and dashboard stuff
This commit is contained in:
@@ -78,6 +78,83 @@ FetchContent_Declare(
|
||||
)
|
||||
FetchContent_MakeAvailable(tomlplusplus)
|
||||
|
||||
# --- libslirp ---
|
||||
set(LIBSLIRP_GIT_REV "26be815b86e8d49add8c9a8b320239b9594ff03d")
|
||||
FetchContent_Declare(
|
||||
libslirp
|
||||
GIT_REPOSITORY "https://gitlab.freedesktop.org/slirp/libslirp.git"
|
||||
GIT_TAG "${LIBSLIRP_GIT_REV}"
|
||||
GIT_SHALLOW FALSE
|
||||
)
|
||||
FetchContent_GetProperties(libslirp)
|
||||
if(NOT libslirp_POPULATED)
|
||||
if(POLICY CMP0169)
|
||||
cmake_policy(PUSH)
|
||||
cmake_policy(SET CMP0169 OLD)
|
||||
endif()
|
||||
FetchContent_Populate(libslirp)
|
||||
if(POLICY CMP0169)
|
||||
cmake_policy(POP)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(LIBSLIRP_GENERATED_DIR "${CMAKE_BINARY_DIR}/libslirp-generated")
|
||||
file(MAKE_DIRECTORY "${LIBSLIRP_GENERATED_DIR}")
|
||||
file(STRINGS "${libslirp_SOURCE_DIR}/meson.build" LIBSLIRP_VERSION_LINE
|
||||
REGEX "^[ ]*version : '[^']+'")
|
||||
if(NOT LIBSLIRP_VERSION_LINE)
|
||||
message(FATAL_ERROR "Failed to determine libslirp version from meson.build")
|
||||
endif()
|
||||
string(REGEX REPLACE ".*version : '([^']+)'.*" "\\1"
|
||||
LIBSLIRP_VERSION "${LIBSLIRP_VERSION_LINE}")
|
||||
string(REPLACE "." ";" LIBSLIRP_VERSION_COMPONENTS "${LIBSLIRP_VERSION}")
|
||||
list(LENGTH LIBSLIRP_VERSION_COMPONENTS LIBSLIRP_VERSION_COMPONENT_COUNT)
|
||||
if(NOT LIBSLIRP_VERSION_COMPONENT_COUNT EQUAL 3)
|
||||
message(FATAL_ERROR "Unexpected libslirp version format: ${LIBSLIRP_VERSION}")
|
||||
endif()
|
||||
list(GET LIBSLIRP_VERSION_COMPONENTS 0 SLIRP_MAJOR_VERSION)
|
||||
list(GET LIBSLIRP_VERSION_COMPONENTS 1 SLIRP_MINOR_VERSION)
|
||||
list(GET LIBSLIRP_VERSION_COMPONENTS 2 SLIRP_MICRO_VERSION)
|
||||
set(SLIRP_VERSION_STRING "\"${LIBSLIRP_VERSION}\"")
|
||||
configure_file(
|
||||
"${libslirp_SOURCE_DIR}/src/libslirp-version.h.in"
|
||||
"${LIBSLIRP_GENERATED_DIR}/libslirp-version.h"
|
||||
@ONLY
|
||||
)
|
||||
set(LIBSLIRP_SOURCES
|
||||
"${libslirp_SOURCE_DIR}/src/arp_table.c"
|
||||
"${libslirp_SOURCE_DIR}/src/bootp.c"
|
||||
"${libslirp_SOURCE_DIR}/src/cksum.c"
|
||||
"${libslirp_SOURCE_DIR}/src/dhcpv6.c"
|
||||
"${libslirp_SOURCE_DIR}/src/dnssearch.c"
|
||||
"${libslirp_SOURCE_DIR}/src/if.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ip6_icmp.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ip6_input.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ip6_output.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ip_icmp.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ip_input.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ip_output.c"
|
||||
"${libslirp_SOURCE_DIR}/src/mbuf.c"
|
||||
"${libslirp_SOURCE_DIR}/src/misc.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ncsi.c"
|
||||
"${libslirp_SOURCE_DIR}/src/ndp_table.c"
|
||||
"${libslirp_SOURCE_DIR}/src/sbuf.c"
|
||||
"${libslirp_SOURCE_DIR}/src/slirp.c"
|
||||
"${libslirp_SOURCE_DIR}/src/socket.c"
|
||||
"${libslirp_SOURCE_DIR}/src/state.c"
|
||||
"${libslirp_SOURCE_DIR}/src/stream.c"
|
||||
"${libslirp_SOURCE_DIR}/src/tcp_input.c"
|
||||
"${libslirp_SOURCE_DIR}/src/tcp_output.c"
|
||||
"${libslirp_SOURCE_DIR}/src/tcp_subr.c"
|
||||
"${libslirp_SOURCE_DIR}/src/tcp_timer.c"
|
||||
"${libslirp_SOURCE_DIR}/src/tftp.c"
|
||||
"${libslirp_SOURCE_DIR}/src/udp.c"
|
||||
"${libslirp_SOURCE_DIR}/src/udp6.c"
|
||||
"${libslirp_SOURCE_DIR}/src/util.c"
|
||||
"${libslirp_SOURCE_DIR}/src/version.c"
|
||||
"${libslirp_SOURCE_DIR}/src/vmstate.c"
|
||||
)
|
||||
|
||||
if(XEMU_ENABLE_VULKAN)
|
||||
# --- Vulkan deps (volk, glslang, SPIRV-Reflect, VMA) ---
|
||||
set(VOLK_GIT_REV "0b17a763ba5643e32da1b2152f8140461b3b7345")
|
||||
@@ -517,7 +594,6 @@ list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]thirdparty[\\\\/]noc_file_
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]x_keymap\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]vdagent\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]xemu-os-utils-windows\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "ui[\\\\/]xemu-net\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "util[\\\\/]aio-win32\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "util[\\\\/]atomic64\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "util[\\\\/]coroutine-ucontext\\.c$")
|
||||
@@ -549,7 +625,6 @@ list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]af-xdp\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]netmap\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]pcap\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]passt\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]slirp\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]tap-bsd\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]tap-solaris\\.c$")
|
||||
list(FILTER XEMU_CORE_SOURCES EXCLUDE REGEX "net[\\\\/]tap-win32\\.c$")
|
||||
@@ -654,7 +729,6 @@ list(APPEND XEMU_CORE_SOURCES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/watchdog_stub.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/async_teardown_stub.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/block_export_stub.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/xemu_net_stub.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/xemu_notifications_stub.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/xemu_snapshots_stub.c"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/xemu_hud_stub.c"
|
||||
@@ -872,6 +946,7 @@ target_compile_definitions(xemu_core PRIVATE
|
||||
ANDROID
|
||||
__ANDROID__
|
||||
XBOX
|
||||
CONFIG_SLIRP
|
||||
CONFIG_SDL
|
||||
CONFIG_OPENGL
|
||||
$<$<BOOL:${XEMU_ENABLE_VULKAN}>:CONFIG_VULKAN>
|
||||
@@ -906,6 +981,8 @@ target_include_directories(xemu_core PRIVATE
|
||||
"${CMAKE_CURRENT_LIST_DIR}"
|
||||
"${CMAKE_BINARY_DIR}"
|
||||
"${QAPI_GEN_DIR}"
|
||||
"${libslirp_SOURCE_DIR}/src"
|
||||
"${LIBSLIRP_GENERATED_DIR}"
|
||||
"${GLIB_INSTALL_DIR}/include/glib-2.0"
|
||||
"${GLIB_INSTALL_DIR}/lib/glib-2.0/include"
|
||||
"${tomlplusplus_SOURCE_DIR}/include"
|
||||
@@ -946,6 +1023,32 @@ set_target_properties(gthread-2.0 PROPERTIES
|
||||
IMPORTED_LOCATION "${GLIB_INSTALL_DIR}/lib/libgthread-2.0.a"
|
||||
)
|
||||
|
||||
add_library(slirp_static STATIC
|
||||
${LIBSLIRP_SOURCES}
|
||||
)
|
||||
set_target_properties(slirp_static PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
)
|
||||
target_compile_definitions(slirp_static PRIVATE
|
||||
BUILDING_LIBSLIRP
|
||||
G_LOG_DOMAIN=\"Slirp\"
|
||||
GLIB_STATIC_COMPILATION
|
||||
GIO_STATIC_COMPILATION
|
||||
GMODULE_STATIC_COMPILATION
|
||||
GI_STATIC_COMPILATION
|
||||
GOBJECT_STATIC_COMPILATION
|
||||
)
|
||||
target_include_directories(slirp_static PRIVATE
|
||||
"${libslirp_SOURCE_DIR}/src"
|
||||
"${LIBSLIRP_GENERATED_DIR}"
|
||||
"${GLIB_INSTALL_DIR}/include/glib-2.0"
|
||||
"${GLIB_INSTALL_DIR}/lib/glib-2.0/include"
|
||||
)
|
||||
target_link_libraries(slirp_static PRIVATE
|
||||
glib-2.0
|
||||
)
|
||||
add_dependencies(slirp_static glib_ep)
|
||||
|
||||
add_library(xemu SHARED
|
||||
xemu_android.cpp
|
||||
android_crash_handler.cpp
|
||||
@@ -1006,6 +1109,7 @@ endif()
|
||||
|
||||
target_link_libraries(xemu PRIVATE
|
||||
xemu_core
|
||||
slirp_static
|
||||
iconv_stub
|
||||
glib-2.0
|
||||
gobject-2.0
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#define CONFIG_SDL 1
|
||||
#define CONFIG_SDL2 1
|
||||
#define CONFIG_AUDIO_SDL 1
|
||||
#define CONFIG_SLIRP 1
|
||||
#define CONFIG_AUDIO_DRIVERS "sdl",
|
||||
#define CONFIG_DEVICES "config-devices.h"
|
||||
|
||||
|
||||
@@ -725,6 +725,7 @@ struct EmulatorSettings {
|
||||
bool hard_fpu = true;
|
||||
bool vsync = true;
|
||||
bool skip_boot_anim = false;
|
||||
bool network_enabled = false;
|
||||
};
|
||||
|
||||
struct SetupFiles {
|
||||
@@ -773,11 +774,13 @@ static bool WriteConfigToml(const std::string& config_path,
|
||||
toml::table* audio = EnsureTable(tbl, "audio");
|
||||
toml::table* audio_vp = EnsureTable(*audio, "vp");
|
||||
toml::table* android = EnsureTable(tbl, "android");
|
||||
toml::table* net = EnsureTable(tbl, "net");
|
||||
toml::table* net_nat = EnsureTable(*net, "nat");
|
||||
toml::table* sys = EnsureTable(tbl, "sys");
|
||||
toml::table* perf = EnsureTable(tbl, "perf");
|
||||
toml::table* files = EnsureTable(*sys, "files");
|
||||
if (!general || !display || !display_quality || !display_window || !audio ||
|
||||
!audio_vp || !android || !sys || !perf || !files) {
|
||||
!audio_vp || !android || !net || !net_nat || !sys || !perf || !files) {
|
||||
LogErrorFmt("Failed to build config tables at %s", config_path.c_str());
|
||||
return false;
|
||||
}
|
||||
@@ -826,6 +829,8 @@ static bool WriteConfigToml(const std::string& config_path,
|
||||
if (!android->contains("audio_driver")) {
|
||||
android->insert_or_assign("audio_driver", "openslES");
|
||||
}
|
||||
net->insert_or_assign("enable", settings.network_enabled);
|
||||
net->insert_or_assign("backend", "nat");
|
||||
sys->insert_or_assign(
|
||||
"mem_limit", settings.system_memory_mib == 128 ? "128" : "64");
|
||||
|
||||
@@ -965,6 +970,8 @@ static SetupFiles SyncSetupFiles() {
|
||||
emuSettings.hard_fpu = GetPrefBool(env, activity, "setting_hard_fpu", true);
|
||||
emuSettings.skip_boot_anim =
|
||||
GetPrefBool(env, activity, "setting_skip_boot_anim", false);
|
||||
emuSettings.network_enabled =
|
||||
GetPrefBool(env, activity, "setting_network_enable", false);
|
||||
{
|
||||
std::string tcgThread = GetPrefString(env, activity, "setting_tcg_thread");
|
||||
if (tcgThread == "single") {
|
||||
|
||||
@@ -157,6 +157,38 @@ static void xemu_fatx_propagate_error(XemuFatxFs *fs, Error **errp, const char *
|
||||
}
|
||||
}
|
||||
|
||||
static bool xemu_fatx_ascii_equal_n(const char *left, const char *right, size_t len)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (!left[i] || !right[i]) {
|
||||
return false;
|
||||
}
|
||||
if (g_ascii_tolower(left[i]) != g_ascii_tolower(right[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xemu_fatx_ascii_equal(const char *left, const char *right)
|
||||
{
|
||||
size_t left_len = strlen(left);
|
||||
size_t right_len = strlen(right);
|
||||
|
||||
return left_len == right_len && xemu_fatx_ascii_equal_n(left, right, left_len);
|
||||
}
|
||||
|
||||
static bool xemu_fatx_ascii_starts_with(const char *value, const char *prefix)
|
||||
{
|
||||
size_t prefix_len = strlen(prefix);
|
||||
|
||||
return strlen(value) >= prefix_len &&
|
||||
xemu_fatx_ascii_equal_n(value, prefix, prefix_len);
|
||||
}
|
||||
|
||||
static bool xemu_fatx_read_bytes(BlockBackend *blk,
|
||||
uint64_t offset,
|
||||
void *buffer,
|
||||
@@ -785,7 +817,7 @@ static int xemu_fatx_open_dir(XemuFatxFs *fs, const char *path, XemuFatxDir *dir
|
||||
if (status == XEMU_FATX_STATUS_SUCCESS) {
|
||||
if ((attr.attributes & XEMU_FATX_ATTR_DIRECTORY) != 0 &&
|
||||
strlen(dirent.filename) == len &&
|
||||
memcmp(dirent.filename, start, len) == 0) {
|
||||
xemu_fatx_ascii_equal_n(dirent.filename, start, len)) {
|
||||
dir->cluster = attr.first_cluster;
|
||||
dir->entry = 0;
|
||||
break;
|
||||
@@ -817,7 +849,7 @@ static int xemu_fatx_get_attr_dir(XemuFatxFs *fs,
|
||||
while (1) {
|
||||
status = xemu_fatx_read_dir(fs, dir, dirent, attr);
|
||||
if (status == XEMU_FATX_STATUS_SUCCESS) {
|
||||
if (strcmp(basename, dirent->filename) == 0) {
|
||||
if (xemu_fatx_ascii_equal(basename, dirent->filename)) {
|
||||
return XEMU_FATX_STATUS_SUCCESS;
|
||||
}
|
||||
} else if (status == XEMU_FATX_STATUS_FILE_DELETED) {
|
||||
@@ -1283,7 +1315,7 @@ static int xemu_fatx_unlink(XemuFatxFs *fs, const char *path)
|
||||
while (1) {
|
||||
status = xemu_fatx_read_dir(fs, &dir, &entry, &attr);
|
||||
if (status == XEMU_FATX_STATUS_SUCCESS) {
|
||||
if (strcmp(entry.filename, basename) == 0) {
|
||||
if (xemu_fatx_ascii_equal(entry.filename, basename)) {
|
||||
break;
|
||||
}
|
||||
} else if (status == XEMU_FATX_STATUS_FILE_DELETED) {
|
||||
@@ -1692,6 +1724,138 @@ static bool xemu_dashboard_import_partition(BlockBackend *blk,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool xemu_fatx_probe_path(XemuFatxFs *fs,
|
||||
const char *path,
|
||||
bool *exists_out,
|
||||
bool *is_dir_out)
|
||||
{
|
||||
XemuFatxAttr attr;
|
||||
int status = xemu_fatx_get_attr(fs, path, &attr);
|
||||
|
||||
if (status == XEMU_FATX_STATUS_SUCCESS) {
|
||||
if (exists_out) {
|
||||
*exists_out = true;
|
||||
}
|
||||
if (is_dir_out) {
|
||||
*is_dir_out = (attr.attributes & XEMU_FATX_ATTR_DIRECTORY) != 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (status == XEMU_FATX_STATUS_FILE_NOT_FOUND) {
|
||||
if (exists_out) {
|
||||
*exists_out = false;
|
||||
}
|
||||
if (is_dir_out) {
|
||||
*is_dir_out = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool xemu_fatx_scan_root_dashboard_markers(XemuFatxFs *fs,
|
||||
XemuFatxDashboardStatus *status,
|
||||
Error **errp)
|
||||
{
|
||||
XemuFatxDir dir;
|
||||
XemuFatxDirent dirent;
|
||||
XemuFatxAttr attr;
|
||||
int scan_status;
|
||||
|
||||
if (!status) {
|
||||
return true;
|
||||
}
|
||||
|
||||
scan_status = xemu_fatx_open_dir(fs, "/", &dir);
|
||||
if (scan_status != XEMU_FATX_STATUS_SUCCESS) {
|
||||
xemu_fatx_propagate_error(fs, errp, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
scan_status = xemu_fatx_read_dir(fs, &dir, &dirent, &attr);
|
||||
if (scan_status == XEMU_FATX_STATUS_SUCCESS) {
|
||||
if ((attr.attributes & XEMU_FATX_ATTR_DIRECTORY) != 0) {
|
||||
if (xemu_fatx_ascii_equal(dirent.filename, "xodash")) {
|
||||
status->xodash_dir_present = true;
|
||||
} else if (xemu_fatx_ascii_equal(dirent.filename, "audio")) {
|
||||
status->audio_dir_present = true;
|
||||
} else if (xemu_fatx_ascii_equal(dirent.filename, "fonts")) {
|
||||
status->fonts_dir_present = true;
|
||||
} else if (xemu_fatx_ascii_starts_with(dirent.filename, "xboxdashdata.")) {
|
||||
status->xboxdashdata_dir_present = true;
|
||||
}
|
||||
} else {
|
||||
if (xemu_fatx_ascii_equal(dirent.filename, "xboxdash.xbe")) {
|
||||
status->xboxdash_xbe_present = true;
|
||||
} else if (xemu_fatx_ascii_equal(dirent.filename, "msdash.xbe")) {
|
||||
status->msdash_xbe_present = true;
|
||||
} else if (xemu_fatx_ascii_equal(dirent.filename, "xbox.xtf")) {
|
||||
status->xbox_xtf_present = true;
|
||||
}
|
||||
}
|
||||
} else if (scan_status == XEMU_FATX_STATUS_FILE_DELETED) {
|
||||
/* Skip. */
|
||||
} else if (scan_status == XEMU_FATX_STATUS_END_OF_DIR) {
|
||||
return true;
|
||||
} else {
|
||||
xemu_fatx_propagate_error(fs, errp, NULL);
|
||||
return false;
|
||||
}
|
||||
|
||||
scan_status = xemu_fatx_next_dir_entry(fs, &dir);
|
||||
if (scan_status != XEMU_FATX_STATUS_SUCCESS) {
|
||||
xemu_fatx_propagate_error(fs, errp, NULL);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool xemu_fatx_inspect_retail_dashboard(BlockBackend *blk,
|
||||
XemuFatxDashboardStatus *status,
|
||||
Error **errp)
|
||||
{
|
||||
XemuFatxFs fs;
|
||||
bool ok = true;
|
||||
|
||||
if (status) {
|
||||
memset(status, 0, sizeof(*status));
|
||||
}
|
||||
|
||||
if (!xemu_fatx_open_fs(&fs, blk,
|
||||
XEMU_XBOX_HDD_PARTITION_C_OFFSET,
|
||||
XEMU_XBOX_HDD_PARTITION_C_SIZE,
|
||||
errp)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (status) {
|
||||
bool is_dir = false;
|
||||
|
||||
ok = xemu_fatx_probe_path(&fs, "/xboxdash.xbe",
|
||||
&status->xboxdash_xbe_present, NULL) &&
|
||||
xemu_fatx_probe_path(&fs, "/msdash.xbe",
|
||||
&status->msdash_xbe_present, NULL) &&
|
||||
xemu_fatx_probe_path(&fs, "/xbox.xtf",
|
||||
&status->xbox_xtf_present, NULL) &&
|
||||
xemu_fatx_probe_path(&fs, "/xodash",
|
||||
&status->xodash_dir_present, &is_dir);
|
||||
status->xodash_dir_present = status->xodash_dir_present && is_dir;
|
||||
ok = ok && xemu_fatx_scan_root_dashboard_markers(&fs, status, errp);
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
xemu_fatx_propagate_error(&fs, errp, NULL);
|
||||
xemu_fatx_close_fs(&fs);
|
||||
return false;
|
||||
}
|
||||
|
||||
xemu_fatx_close_fs(&fs);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool xemu_fatx_import_dashboard(BlockBackend *blk,
|
||||
const char *source_root,
|
||||
const char *backup_root,
|
||||
|
||||
@@ -6,9 +6,23 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct XemuFatxDashboardStatus {
|
||||
bool xboxdash_xbe_present;
|
||||
bool msdash_xbe_present;
|
||||
bool xbox_xtf_present;
|
||||
bool xodash_dir_present;
|
||||
bool audio_dir_present;
|
||||
bool fonts_dir_present;
|
||||
bool xboxdashdata_dir_present;
|
||||
} XemuFatxDashboardStatus;
|
||||
|
||||
bool xemu_fatx_import_dashboard(BlockBackend *blk,
|
||||
const char *source_root,
|
||||
const char *backup_root,
|
||||
Error **errp);
|
||||
|
||||
bool xemu_fatx_inspect_retail_dashboard(BlockBackend *blk,
|
||||
XemuFatxDashboardStatus *status,
|
||||
Error **errp);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -27,6 +27,16 @@
|
||||
#define ANDROID_XBOX_HDD_REFURB_OFFSET 0x600ULL
|
||||
#define ANDROID_XBOX_HDD_REFURB_SIGNATURE 0x42524652U
|
||||
|
||||
#define ANDROID_XBOX_HDD_CONFIG_STATIC_PRIMARY_DNS_OFFSET 0x102CULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_STATIC_SECONDARY_DNS_OFFSET 0x1030ULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_XBLIVE_PRIMARY_DNS_OFFSET 0x1060ULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_XBLIVE_SECONDARY_DNS_OFFSET 0x1064ULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_DHCP_PRIMARY_DNS_OFFSET 0x1178ULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_DHCP_SECONDARY_DNS_OFFSET 0x117CULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_PPPOE_PRIMARY_DNS_OFFSET 0x118CULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_PPPOE_SECONDARY_DNS_OFFSET 0x1190ULL
|
||||
#define ANDROID_XBOX_HDD_CONFIG_MINIMUM_BYTES 0x1194ULL
|
||||
|
||||
#define ANDROID_XBOX_HDD_FATX_SIGNATURE 0x58544146U
|
||||
#define ANDROID_XBOX_HDD_FATX_SUPERBLOCK_SIZE 4096ULL
|
||||
#define ANDROID_XBOX_HDD_FATX_FAT_OFFSET ANDROID_XBOX_HDD_FATX_SUPERBLOCK_SIZE
|
||||
@@ -82,6 +92,13 @@ static pthread_mutex_t g_android_xbox_hdd_init_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static bool g_android_xbox_hdd_init_complete;
|
||||
static char *g_android_xbox_hdd_init_failure;
|
||||
|
||||
static bool android_xbox_hdd_write_bytes(BlockBackend *blk,
|
||||
uint64_t offset,
|
||||
const void *buffer,
|
||||
uint64_t size,
|
||||
const char *label,
|
||||
Error **errp);
|
||||
|
||||
static void android_xbox_hdd_throw_exception(JNIEnv *env,
|
||||
const char *class_name,
|
||||
const char *message)
|
||||
@@ -231,6 +248,38 @@ static BlockBackend *android_xbox_hdd_open_backend(const char *path,
|
||||
return blk_new_open(path, NULL, options, flags, errp);
|
||||
}
|
||||
|
||||
static bool android_xbox_hdd_copy_ipv4(JNIEnv *env,
|
||||
jbyteArray array,
|
||||
uint8_t out[4],
|
||||
const char *label)
|
||||
{
|
||||
jbyte buffer[4];
|
||||
jsize length;
|
||||
|
||||
if (!array) {
|
||||
android_xbox_hdd_throw_exception(env, "java/lang/IllegalArgumentException",
|
||||
label);
|
||||
return false;
|
||||
}
|
||||
|
||||
length = (*env)->GetArrayLength(env, array);
|
||||
if (length != 4) {
|
||||
android_xbox_hdd_throw_exception(env, "java/lang/IllegalArgumentException",
|
||||
label);
|
||||
return false;
|
||||
}
|
||||
|
||||
(*env)->GetByteArrayRegion(env, array, 0, 4, buffer);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 4; ++i) {
|
||||
out[i] = (uint8_t)buffer[i];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool android_xbox_hdd_inspect_locked(const char *path,
|
||||
AndroidXboxHddFormat *format,
|
||||
int64_t *total_bytes,
|
||||
@@ -266,6 +315,119 @@ cleanup:
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool android_xbox_hdd_inspect_dashboard_locked(const char *path,
|
||||
jint *flags_out,
|
||||
Error **errp)
|
||||
{
|
||||
BlockBackend *blk = NULL;
|
||||
XemuFatxDashboardStatus status;
|
||||
bool ok = false;
|
||||
jint flags = 0;
|
||||
|
||||
blk = android_xbox_hdd_open_backend(path, false, errp);
|
||||
if (!blk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!xemu_fatx_inspect_retail_dashboard(blk, &status, errp)) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
if (status.xboxdash_xbe_present) {
|
||||
flags |= 1 << 0;
|
||||
}
|
||||
if (status.xodash_dir_present) {
|
||||
flags |= 1 << 1;
|
||||
}
|
||||
if (status.xbox_xtf_present) {
|
||||
flags |= 1 << 2;
|
||||
}
|
||||
if (status.msdash_xbe_present) {
|
||||
flags |= 1 << 3;
|
||||
}
|
||||
if (status.audio_dir_present) {
|
||||
flags |= 1 << 4;
|
||||
}
|
||||
if (status.fonts_dir_present) {
|
||||
flags |= 1 << 5;
|
||||
}
|
||||
if (status.xboxdashdata_dir_present) {
|
||||
flags |= 1 << 6;
|
||||
}
|
||||
|
||||
if (flags_out) {
|
||||
*flags_out = flags;
|
||||
}
|
||||
ok = true;
|
||||
|
||||
cleanup:
|
||||
if (blk) {
|
||||
blk_drain(blk);
|
||||
blk_unref(blk);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool android_xbox_hdd_write_insignia_dns_locked(const char *path,
|
||||
const uint8_t primary_dns[4],
|
||||
const uint8_t secondary_dns[4],
|
||||
Error **errp)
|
||||
{
|
||||
static const uint64_t primary_offsets[] = {
|
||||
ANDROID_XBOX_HDD_CONFIG_STATIC_PRIMARY_DNS_OFFSET,
|
||||
ANDROID_XBOX_HDD_CONFIG_XBLIVE_PRIMARY_DNS_OFFSET,
|
||||
ANDROID_XBOX_HDD_CONFIG_DHCP_PRIMARY_DNS_OFFSET,
|
||||
ANDROID_XBOX_HDD_CONFIG_PPPOE_PRIMARY_DNS_OFFSET,
|
||||
};
|
||||
static const uint64_t secondary_offsets[] = {
|
||||
ANDROID_XBOX_HDD_CONFIG_STATIC_SECONDARY_DNS_OFFSET,
|
||||
ANDROID_XBOX_HDD_CONFIG_XBLIVE_SECONDARY_DNS_OFFSET,
|
||||
ANDROID_XBOX_HDD_CONFIG_DHCP_SECONDARY_DNS_OFFSET,
|
||||
ANDROID_XBOX_HDD_CONFIG_PPPOE_SECONDARY_DNS_OFFSET,
|
||||
};
|
||||
BlockBackend *blk = NULL;
|
||||
int64_t total_bytes;
|
||||
bool ok = false;
|
||||
|
||||
blk = android_xbox_hdd_open_backend(path, true, errp);
|
||||
if (!blk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
total_bytes = blk_getlength(blk);
|
||||
if (total_bytes < 0) {
|
||||
error_setg(errp, "Could not determine the HDD size: %s", g_strerror(-total_bytes));
|
||||
goto cleanup;
|
||||
}
|
||||
if ((uint64_t)total_bytes < ANDROID_XBOX_HDD_CONFIG_MINIMUM_BYTES) {
|
||||
error_setg(errp, "The current HDD image is too small to contain the Xbox config sector");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < G_N_ELEMENTS(primary_offsets); ++i) {
|
||||
if (!android_xbox_hdd_write_bytes(blk, primary_offsets[i], primary_dns, 4,
|
||||
"Insignia primary DNS", errp) ||
|
||||
!android_xbox_hdd_write_bytes(blk, secondary_offsets[i], secondary_dns, 4,
|
||||
"Insignia secondary DNS", errp)) {
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (blk_flush(blk) < 0) {
|
||||
error_setg(errp, "Failed to flush Insignia DNS writes");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
|
||||
cleanup:
|
||||
if (blk) {
|
||||
blk_drain(blk);
|
||||
blk_unref(blk);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
static uint32_t android_xbox_hdd_retail_sectors_per_cluster(void)
|
||||
{
|
||||
return (uint32_t)(ANDROID_XBOX_HDD_FATX_RETAIL_CLUSTER_SIZE /
|
||||
@@ -820,6 +982,102 @@ Java_com_izzy2lost_x1box_XboxHddFormatter_00024NativeBridge_nativeInitializeHdd(
|
||||
(*env)->ReleaseStringUTFChars(env, jpath, path);
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_izzy2lost_x1box_XboxInsigniaHelper_00024NativeBridge_nativeInspectDashboardFlags(
|
||||
JNIEnv *env, jobject obj, jstring jhdd_path)
|
||||
{
|
||||
const char *hdd_path;
|
||||
Error *err = NULL;
|
||||
jint flags = 0;
|
||||
|
||||
(void)obj;
|
||||
|
||||
if (!jhdd_path) {
|
||||
android_xbox_hdd_throw_exception(env, "java/io/IOException",
|
||||
"No local HDD image is configured.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
hdd_path = (*env)->GetStringUTFChars(env, jhdd_path, NULL);
|
||||
if (!hdd_path) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!android_xbox_hdd_ensure_block_layer(&err)) {
|
||||
(*env)->ReleaseStringUTFChars(env, jhdd_path, hdd_path);
|
||||
android_xbox_hdd_throw_error(env, "java/io/IOException",
|
||||
"Failed to initialize HDD tools: ", err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
{
|
||||
BQL_LOCK_GUARD();
|
||||
if (!android_xbox_hdd_inspect_dashboard_locked(hdd_path, &flags, &err)) {
|
||||
(*env)->ReleaseStringUTFChars(env, jhdd_path, hdd_path);
|
||||
android_xbox_hdd_throw_error(env, "java/io/IOException", NULL, err);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jhdd_path, hdd_path);
|
||||
return flags;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_izzy2lost_x1box_XboxInsigniaHelper_00024NativeBridge_nativeApplyConfigSectorDns(
|
||||
JNIEnv *env,
|
||||
jobject obj,
|
||||
jstring jhdd_path,
|
||||
jbyteArray jprimary_dns,
|
||||
jbyteArray jsecondary_dns)
|
||||
{
|
||||
const char *hdd_path;
|
||||
uint8_t primary_dns[4];
|
||||
uint8_t secondary_dns[4];
|
||||
Error *err = NULL;
|
||||
|
||||
(void)obj;
|
||||
|
||||
if (!jhdd_path) {
|
||||
android_xbox_hdd_throw_exception(env, "java/io/IOException",
|
||||
"No local HDD image is configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!android_xbox_hdd_copy_ipv4(env, jprimary_dns, primary_dns,
|
||||
"Insignia primary DNS must be an IPv4 address.") ||
|
||||
!android_xbox_hdd_copy_ipv4(env, jsecondary_dns, secondary_dns,
|
||||
"Insignia secondary DNS must be an IPv4 address.")) {
|
||||
return;
|
||||
}
|
||||
|
||||
hdd_path = (*env)->GetStringUTFChars(env, jhdd_path, NULL);
|
||||
if (!hdd_path) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!android_xbox_hdd_ensure_block_layer(&err)) {
|
||||
(*env)->ReleaseStringUTFChars(env, jhdd_path, hdd_path);
|
||||
android_xbox_hdd_throw_error(env, "java/io/IOException",
|
||||
"Failed to initialize HDD tools: ", err);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
BQL_LOCK_GUARD();
|
||||
if (!android_xbox_hdd_write_insignia_dns_locked(hdd_path,
|
||||
primary_dns,
|
||||
secondary_dns,
|
||||
&err)) {
|
||||
(*env)->ReleaseStringUTFChars(env, jhdd_path, hdd_path);
|
||||
android_xbox_hdd_throw_error(env, "java/io/IOException", NULL, err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
(*env)->ReleaseStringUTFChars(env, jhdd_path, hdd_path);
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_izzy2lost_x1box_XboxDashboardImporter_00024NativeBridge_nativeImportDashboard(
|
||||
JNIEnv *env, jobject obj, jstring jhdd_path, jstring jsource_root, jstring jbackup_root)
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
#include <cctype>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "xemu-settings.h"
|
||||
|
||||
struct config g_config;
|
||||
using NetNatForwardPort =
|
||||
std::remove_pointer_t<decltype(g_config.net.nat.forward_ports)>;
|
||||
|
||||
static std::string settings_path_storage;
|
||||
static const char *settings_path;
|
||||
@@ -146,10 +150,79 @@ static bool parse_filtering(const std::string &value, CONFIG_DISPLAY_FILTERING *
|
||||
|
||||
static std::string to_lower_ascii(std::string value)
|
||||
{
|
||||
for (char &c : value) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
for (char &c : value) {
|
||||
c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool parse_network_backend(toml::node_view<toml::node> node,
|
||||
CONFIG_NET_BACKEND *out)
|
||||
{
|
||||
if (!out) {
|
||||
return false;
|
||||
}
|
||||
return value;
|
||||
|
||||
if (auto backend = node.value<std::string>()) {
|
||||
std::string normalized = to_lower_ascii(*backend);
|
||||
if (normalized == "nat" || normalized == "user") {
|
||||
*out = CONFIG_NET_BACKEND_NAT;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "udp" || normalized == "udp_tunnel" ||
|
||||
normalized == "udp tunnel") {
|
||||
*out = CONFIG_NET_BACKEND_UDP;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "pcap" || normalized == "bridge" ||
|
||||
normalized == "bridged adapter") {
|
||||
*out = CONFIG_NET_BACKEND_PCAP;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto backend = node.value<int64_t>()) {
|
||||
if (*backend < 0 || *backend >= CONFIG_NET_BACKEND__COUNT) {
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<CONFIG_NET_BACKEND>(*backend);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parse_nat_forward_protocol(
|
||||
toml::node_view<const toml::node> node,
|
||||
CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL *out)
|
||||
{
|
||||
if (!out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto protocol = node.value<std::string>()) {
|
||||
std::string normalized = to_lower_ascii(*protocol);
|
||||
if (normalized == "tcp") {
|
||||
*out = CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_TCP;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "udp") {
|
||||
*out = CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_UDP;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (auto protocol = node.value<int64_t>()) {
|
||||
if (*protocol < 0 || *protocol >= CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL__COUNT) {
|
||||
return false;
|
||||
}
|
||||
*out = static_cast<CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL>(*protocol);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const char *xemu_settings_get_error_message(void)
|
||||
@@ -240,6 +313,10 @@ bool xemu_settings_load(void)
|
||||
auto audio_vp = audio["vp"];
|
||||
auto perf = tbl["perf"];
|
||||
auto android_cfg = tbl["android"];
|
||||
auto net = tbl["net"];
|
||||
auto net_udp = net["udp"];
|
||||
auto net_pcap = net["pcap"];
|
||||
auto net_nat = net["nat"];
|
||||
auto sys = tbl["sys"];
|
||||
auto sys_files = sys["files"];
|
||||
|
||||
@@ -318,6 +395,69 @@ bool xemu_settings_load(void)
|
||||
g_config.audio.volume_limit = volume;
|
||||
}
|
||||
|
||||
if (auto net_enable = net["enable"].value<bool>()) {
|
||||
g_config.net.enable = *net_enable;
|
||||
}
|
||||
{
|
||||
CONFIG_NET_BACKEND parsed_backend;
|
||||
if (parse_network_backend(net["backend"], &parsed_backend)) {
|
||||
g_config.net.backend = parsed_backend;
|
||||
}
|
||||
}
|
||||
if (auto bind_addr = net_udp["bind_addr"].value<std::string>()) {
|
||||
xemu_settings_set_string(&g_config.net.udp.bind_addr, bind_addr->c_str());
|
||||
}
|
||||
if (auto remote_addr = net_udp["remote_addr"].value<std::string>()) {
|
||||
xemu_settings_set_string(&g_config.net.udp.remote_addr, remote_addr->c_str());
|
||||
}
|
||||
if (auto netif = net_pcap["netif"].value<std::string>()) {
|
||||
xemu_settings_set_string(&g_config.net.pcap.netif, netif->c_str());
|
||||
}
|
||||
if (auto forward_ports = net_nat["forward_ports"].as_array()) {
|
||||
std::vector<NetNatForwardPort> parsed_ports;
|
||||
parsed_ports.reserve(forward_ports->size());
|
||||
|
||||
for (const auto &entry : *forward_ports) {
|
||||
const toml::table *table = entry.as_table();
|
||||
if (!table) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto host = (*table)["host"].value<int64_t>();
|
||||
auto guest = (*table)["guest"].value<int64_t>();
|
||||
if (!host || !guest || *host < 1 || *host > 65535 ||
|
||||
*guest < 1 || *guest > 65535) {
|
||||
continue;
|
||||
}
|
||||
|
||||
CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol =
|
||||
CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_TCP;
|
||||
parse_nat_forward_protocol((*table)["protocol"], &protocol);
|
||||
|
||||
NetNatForwardPort port = {};
|
||||
port.host = static_cast<int>(*host);
|
||||
port.guest = static_cast<int>(*guest);
|
||||
port.protocol = protocol;
|
||||
parsed_ports.push_back(port);
|
||||
}
|
||||
|
||||
free(g_config.net.nat.forward_ports);
|
||||
g_config.net.nat.forward_ports = NULL;
|
||||
g_config.net.nat.forward_ports_count = 0;
|
||||
|
||||
if (!parsed_ports.empty()) {
|
||||
auto *ports = static_cast<NetNatForwardPort *>(calloc(
|
||||
parsed_ports.size(), sizeof(NetNatForwardPort)));
|
||||
if (ports) {
|
||||
memcpy(ports, parsed_ports.data(),
|
||||
parsed_ports.size() * sizeof(NetNatForwardPort));
|
||||
g_config.net.nat.forward_ports = ports;
|
||||
g_config.net.nat.forward_ports_count =
|
||||
static_cast<unsigned int>(parsed_ports.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Android-specific settings
|
||||
if (auto egl_offscreen = android_cfg["egl_offscreen"].value<bool>()) {
|
||||
if (!*egl_offscreen) {
|
||||
@@ -456,14 +596,58 @@ void xemu_settings_save(void)
|
||||
void add_net_nat_forward_ports(int host, int guest,
|
||||
CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol)
|
||||
{
|
||||
(void)host;
|
||||
(void)guest;
|
||||
(void)protocol;
|
||||
if (host < 1 || host > 65535 || guest < 1 || guest > 65535 ||
|
||||
protocol < 0 || protocol >= CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL__COUNT) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int old_count = g_config.net.nat.forward_ports_count;
|
||||
unsigned int new_count = old_count + 1;
|
||||
auto *ports = static_cast<NetNatForwardPort *>(realloc(
|
||||
g_config.net.nat.forward_ports, sizeof(NetNatForwardPort) * new_count));
|
||||
if (!ports) {
|
||||
return;
|
||||
}
|
||||
|
||||
ports[old_count].host = host;
|
||||
ports[old_count].guest = guest;
|
||||
ports[old_count].protocol = protocol;
|
||||
g_config.net.nat.forward_ports = ports;
|
||||
g_config.net.nat.forward_ports_count = new_count;
|
||||
}
|
||||
|
||||
void remove_net_nat_forward_ports(unsigned int index)
|
||||
{
|
||||
(void)index;
|
||||
if (index >= g_config.net.nat.forward_ports_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsigned int old_count = g_config.net.nat.forward_ports_count;
|
||||
unsigned int new_count = old_count - 1;
|
||||
auto *ports =
|
||||
static_cast<NetNatForwardPort *>(g_config.net.nat.forward_ports);
|
||||
|
||||
if (index + 1 < old_count) {
|
||||
memmove(&ports[index], &ports[index + 1],
|
||||
(old_count - index - 1) * sizeof(NetNatForwardPort));
|
||||
}
|
||||
|
||||
if (new_count == 0) {
|
||||
free(ports);
|
||||
g_config.net.nat.forward_ports = NULL;
|
||||
g_config.net.nat.forward_ports_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
auto *resized = static_cast<NetNatForwardPort *>(realloc(
|
||||
ports, sizeof(NetNatForwardPort) * new_count));
|
||||
if (!resized) {
|
||||
g_config.net.nat.forward_ports_count = new_count;
|
||||
return;
|
||||
}
|
||||
|
||||
g_config.net.nat.forward_ports = resized;
|
||||
g_config.net.nat.forward_ports_count = new_count;
|
||||
}
|
||||
|
||||
bool xemu_settings_load_gamepad_mapping(const char *guid,
|
||||
|
||||
@@ -257,6 +257,22 @@ class GameLibraryActivity : AppCompatActivity() {
|
||||
return
|
||||
}
|
||||
|
||||
resolveConfiguredLocalHddFile()?.let { hddFile ->
|
||||
runCatching { XboxInsigniaHelper.inspectDashboard(hddFile) }
|
||||
.getOrNull()
|
||||
?.let { dashboardStatus ->
|
||||
if (!dashboardStatus.looksRetailDashboardInstalled) {
|
||||
val messageRes = if (dashboardStatus.hasAnyRetailDashboardFiles) {
|
||||
R.string.library_boot_dashboard_incomplete_retail
|
||||
} else {
|
||||
R.string.library_boot_dashboard_missing_retail
|
||||
}
|
||||
Toast.makeText(this, getString(messageRes), Toast.LENGTH_LONG).show()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MainActivity runs in :xemu, so the disc selection must be flushed before
|
||||
// the other process reads SharedPreferences during startup.
|
||||
prefs.edit()
|
||||
@@ -285,6 +301,12 @@ class GameLibraryActivity : AppCompatActivity() {
|
||||
return uri != null && hasPersistedReadPermission(uri)
|
||||
}
|
||||
|
||||
private fun resolveConfiguredLocalHddFile(): File? {
|
||||
val path = prefs.getString("hddPath", null) ?: return null
|
||||
val file = File(path)
|
||||
return file.takeIf { it.isFile }
|
||||
}
|
||||
|
||||
private fun slotName(slot: Int) = "android_slot_$slot"
|
||||
|
||||
private fun snapshotPreviewDir(): File = File(filesDir, "x1box/snapshots")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,6 +14,8 @@ internal object XboxEepromEditor {
|
||||
private const val USER_CHECKSUM_START = 0x64
|
||||
private const val USER_CHECKSUM_LENGTH = 0x5C
|
||||
|
||||
private const val XBOX_LIVE_DNS_OFFSET = 0xAC
|
||||
|
||||
private const val VIDEO_STANDARD_OFFSET = 0x58
|
||||
private const val LANGUAGE_OFFSET = 0x90
|
||||
private const val VIDEO_SETTINGS_OFFSET = 0x94
|
||||
@@ -189,6 +191,34 @@ internal object XboxEepromEditor {
|
||||
return true
|
||||
}
|
||||
|
||||
@Throws(IOException::class, IllegalArgumentException::class)
|
||||
fun applyXboxLiveDns(
|
||||
file: File,
|
||||
dnsServer: ByteArray,
|
||||
): Boolean {
|
||||
require(dnsServer.size == 4) { "Xbox Live DNS must be a 4-byte IPv4 address." }
|
||||
|
||||
val data = file.readBytes()
|
||||
ensureValidSize(file, data)
|
||||
|
||||
var changed = false
|
||||
for (index in dnsServer.indices) {
|
||||
if (data[XBOX_LIVE_DNS_OFFSET + index] != dnsServer[index]) {
|
||||
data[XBOX_LIVE_DNS_OFFSET + index] = dnsServer[index]
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
return false
|
||||
}
|
||||
|
||||
val userChecksum = xboxChecksum(data, USER_CHECKSUM_START, USER_CHECKSUM_LENGTH)
|
||||
writeLeUInt32(data, USER_CHECKSUM_OFFSET, userChecksum)
|
||||
|
||||
file.writeBytes(data)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun ensureValidSize(file: File, data: ByteArray) {
|
||||
if (data.size != EEPROM_SIZE) {
|
||||
throw IllegalArgumentException(
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
package com.izzy2lost.x1box
|
||||
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
internal object XboxInsigniaHelper {
|
||||
const val PRIMARY_DNS = "46.101.64.175"
|
||||
const val SECONDARY_DNS = "8.8.8.8"
|
||||
|
||||
private const val FLAG_XBOXDASH_XBE = 1 shl 0
|
||||
private const val FLAG_XODASH_DIR = 1 shl 1
|
||||
private const val FLAG_XBOX_XTF = 1 shl 2
|
||||
private const val FLAG_MSDASH_XBE = 1 shl 3
|
||||
private const val FLAG_AUDIO_DIR = 1 shl 4
|
||||
private const val FLAG_FONTS_DIR = 1 shl 5
|
||||
private const val FLAG_XBOXDASHDATA_DIR = 1 shl 6
|
||||
|
||||
data class DashboardStatus(
|
||||
val flags: Int,
|
||||
) {
|
||||
val hasBootXbe: Boolean
|
||||
get() = (flags and FLAG_XBOXDASH_XBE) != 0
|
||||
|
||||
val hasXodashAssets: Boolean
|
||||
get() = (flags and FLAG_XODASH_DIR) != 0
|
||||
|
||||
val hasXboxFont: Boolean
|
||||
get() = (flags and FLAG_XBOX_XTF) != 0
|
||||
|
||||
val hasMsdashXbe: Boolean
|
||||
get() = (flags and FLAG_MSDASH_XBE) != 0
|
||||
|
||||
val hasAudioDir: Boolean
|
||||
get() = (flags and FLAG_AUDIO_DIR) != 0
|
||||
|
||||
val hasFontsDir: Boolean
|
||||
get() = (flags and FLAG_FONTS_DIR) != 0
|
||||
|
||||
val hasXboxdashdataDir: Boolean
|
||||
get() = (flags and FLAG_XBOXDASHDATA_DIR) != 0
|
||||
|
||||
val looksRetailDashboardInstalled: Boolean
|
||||
get() = hasBootXbe && (
|
||||
hasXodashAssets ||
|
||||
hasXboxFont ||
|
||||
hasMsdashXbe ||
|
||||
hasAudioDir ||
|
||||
hasFontsDir ||
|
||||
hasXboxdashdataDir
|
||||
)
|
||||
|
||||
val hasAnyRetailDashboardFiles: Boolean
|
||||
get() = flags != 0
|
||||
}
|
||||
|
||||
private val primaryDnsBytes = parseIpv4(PRIMARY_DNS)
|
||||
private val secondaryDnsBytes = parseIpv4(SECONDARY_DNS)
|
||||
|
||||
@Throws(IOException::class, IllegalArgumentException::class)
|
||||
fun inspectDashboard(hddFile: File): DashboardStatus {
|
||||
require(hddFile.isFile) { "No local HDD image is configured." }
|
||||
return DashboardStatus(NativeBridge.nativeInspectDashboardFlags(hddFile.absolutePath))
|
||||
}
|
||||
|
||||
@Throws(IOException::class, IllegalArgumentException::class)
|
||||
fun applyConfigSectorDns(hddFile: File) {
|
||||
require(hddFile.isFile) { "No local HDD image is configured." }
|
||||
NativeBridge.nativeApplyConfigSectorDns(
|
||||
hddFile.absolutePath,
|
||||
primaryDnsBytes,
|
||||
secondaryDnsBytes,
|
||||
)
|
||||
}
|
||||
|
||||
fun primaryDnsBytes(): ByteArray = primaryDnsBytes.copyOf()
|
||||
|
||||
private fun parseIpv4(value: String): ByteArray {
|
||||
val octets = value.split('.')
|
||||
require(octets.size == 4) { "Invalid IPv4 address: $value" }
|
||||
return ByteArray(4) { index ->
|
||||
val octet = octets[index].toIntOrNull()
|
||||
?: throw IllegalArgumentException("Invalid IPv4 address: $value")
|
||||
require(octet in 0..255) { "Invalid IPv4 address: $value" }
|
||||
octet.toByte()
|
||||
}
|
||||
}
|
||||
|
||||
private object NativeBridge {
|
||||
init {
|
||||
System.loadLibrary("SDL2")
|
||||
System.loadLibrary("xemu")
|
||||
}
|
||||
|
||||
external fun nativeInspectDashboardFlags(hddPath: String): Int
|
||||
|
||||
external fun nativeApplyConfigSectorDns(
|
||||
hddPath: String,
|
||||
primaryDns: ByteArray,
|
||||
secondaryDns: ByteArray,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,14 @@
|
||||
app:iconTint="@color/xemu_black"
|
||||
app:iconPadding="10dp" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/library_boot_dashboard_note"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<!-- Secondary actions: outlined pills with explicit spring (not in toggle group) -->
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_settings"
|
||||
|
||||
@@ -504,6 +504,78 @@
|
||||
|
||||
</com.google.android.material.button.MaterialButtonToggleGroup>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@color/xemu_outline_variant" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="@drawable/section_header_background"
|
||||
android:text="@string/settings_section_online"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="@color/xemu_green_light" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_network_enable"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_online_enable"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="6dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/settings_online_hint"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_insignia_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_insignia_guide"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/settings_insignia_guide_action" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_insignia_sign_up"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/settings_insignia_sign_up_action" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_prepare_insignia"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/settings_insignia_prepare_action" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_register_insignia"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_insignia_register_action" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1dp"
|
||||
@@ -641,40 +713,74 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/section_header_background"
|
||||
android:text="@string/settings_section_hdd_tools"
|
||||
android:text="@string/settings_section_advanced_experimental"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="@color/xemu_green_light" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_hdd_tools_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/settings_advanced_experimental_hint"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<TextView
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_toggle_advanced_experimental"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/settings_hdd_tools_hint"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
android:text="@string/settings_advanced_experimental_show" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_advanced_experimental_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="@drawable/section_header_background"
|
||||
android:text="@string/settings_section_hdd_tools"
|
||||
android:textAppearance="@style/TextAppearance.Material3.LabelLarge"
|
||||
android:textColor="@color/xemu_green_light" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_hdd_tools_status"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:text="@string/settings_hdd_tools_hint"
|
||||
android:textAppearance="@style/TextAppearance.Material3.BodySmall"
|
||||
android:textColor="@color/xemu_text_muted" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_initialize_retail_hdd"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/settings_hdd_init_action" />
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:text="@string/settings_hdd_init_action" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_import_dashboard"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_dashboard_import_action" />
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/btn_import_dashboard"
|
||||
style="@style/Widget.Xemu.Button.Outlined.Pill"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/settings_dashboard_import_action" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
|
||||
<string name="library_title">Game Library</string>
|
||||
<string name="library_boot_dashboard">Boot Dashboard</string>
|
||||
<string name="library_boot_dashboard_note">Requires a hard drive image with the Microsoft dashboard installed. Barebones HDD layouts will not work.</string>
|
||||
<string name="library_snapshots">Snapshots</string>
|
||||
<string name="library_convert_iso">Convert ISO to XISO</string>
|
||||
<string name="library_view_list">List</string>
|
||||
@@ -66,6 +67,8 @@
|
||||
<string name="library_convert_resolve_failed">Could not resolve destination folder for selected game.</string>
|
||||
<string name="library_restart_failed">Unable to restart. The selected game is no longer accessible.</string>
|
||||
<string name="library_boot_dashboard_failed">Unable to boot the dashboard. One or more core files are no longer accessible.</string>
|
||||
<string name="library_boot_dashboard_missing_retail">No retail dashboard was found on the current HDD image. Import the Microsoft dashboard in Settings first.</string>
|
||||
<string name="library_boot_dashboard_incomplete_retail">Some retail dashboard files are present, but the dashboard looks incomplete. Re-import the Microsoft dashboard files in Settings.</string>
|
||||
<string name="library_delete_option">Delete Game</string>
|
||||
<string name="library_delete_title">Delete Game</string>
|
||||
<string name="library_delete_action">Delete</string>
|
||||
@@ -144,6 +147,37 @@
|
||||
<string name="settings_audio_driver_opensles">OpenSL ES</string>
|
||||
<string name="settings_audio_driver_aaudio">AAudio</string>
|
||||
<string name="settings_audio_driver_disabled">Disabled</string>
|
||||
<string name="settings_section_online">Online</string>
|
||||
<string name="settings_online_enable">Enable online networking (Insignia)</string>
|
||||
<string name="settings_online_hint">Turns on NAT networking for dashboards and Insignia. Use the helper buttons below to write the DNS preset and boot the Setup Assistant.</string>
|
||||
<string name="settings_insignia_guide_action">Insignia Guide</string>
|
||||
<string name="settings_insignia_sign_up_action">Sign Up With Insignia</string>
|
||||
<string name="settings_insignia_prepare_action">Prepare Insignia Networking</string>
|
||||
<string name="settings_insignia_register_action">Register With Setup Assistant</string>
|
||||
<string name="settings_insignia_status_checking">Checking Insignia setup...</string>
|
||||
<string name="settings_insignia_status_dns">DNS preset: %1$s / %2$s</string>
|
||||
<string name="settings_insignia_status_dashboard_ready">Retail dashboard: detected on C:</string>
|
||||
<string name="settings_insignia_status_dashboard_partial">Retail dashboard: some files were found, but the install looks incomplete</string>
|
||||
<string name="settings_insignia_status_dashboard_missing">Retail dashboard: not detected on C:</string>
|
||||
<string name="settings_insignia_status_dashboard_unavailable">Retail dashboard: unavailable because no local HDD image is configured</string>
|
||||
<string name="settings_insignia_status_dashboard_error">Retail dashboard: failed to inspect the HDD image (%1$s)</string>
|
||||
<string name="settings_insignia_status_eeprom_ready">EEPROM: ready for DNS updates</string>
|
||||
<string name="settings_insignia_status_eeprom_missing">EEPROM: missing. Launch emulation once to create eeprom.bin</string>
|
||||
<string name="settings_insignia_status_setup_selected">Setup Assistant: %1$s</string>
|
||||
<string name="settings_insignia_status_setup_inaccessible">Setup Assistant: %1$s (pick it again to restore access)</string>
|
||||
<string name="settings_insignia_status_setup_missing">Setup Assistant: not selected yet</string>
|
||||
<string name="settings_insignia_prepare_working">Writing Insignia DNS settings...</string>
|
||||
<string name="settings_insignia_prepare_success">Insignia networking is ready. Restart emulation, then sign in through the dashboard or boot the Setup Assistant.</string>
|
||||
<string name="settings_insignia_prepare_success_missing_dashboard">Insignia DNS was written, but the retail dashboard is still missing from C:.</string>
|
||||
<string name="settings_insignia_prepare_failed">Failed to prepare Insignia networking: %1$s</string>
|
||||
<string name="settings_insignia_prepare_no_hdd">No local HDD image is configured. Pick one in the setup wizard first.</string>
|
||||
<string name="settings_insignia_prepare_no_eeprom">EEPROM not found yet. Launch emulation once to create eeprom.bin, then try again.</string>
|
||||
<string name="settings_insignia_register_title">Setup Assistant</string>
|
||||
<string name="settings_insignia_register_message">Boot %1$s now? This launches the Insignia Setup Assistant disc image.</string>
|
||||
<string name="settings_insignia_register_boot_action">Boot Setup Assistant</string>
|
||||
<string name="settings_insignia_register_choose_new">Choose Different Image</string>
|
||||
<string name="settings_insignia_register_pick_prompt">Pick your Insignia Setup Assistant disc image.</string>
|
||||
<string name="settings_insignia_setup_source_unknown">Unknown source</string>
|
||||
<string name="settings_section_eeprom">EEPROM</string>
|
||||
<string name="settings_eeprom_language">EEPROM Language</string>
|
||||
<string name="settings_eeprom_language_english">English</string>
|
||||
@@ -183,6 +217,10 @@
|
||||
<string name="settings_saved_with_eeprom">Settings and EEPROM saved</string>
|
||||
<string name="settings_saved_eeprom_missing">Settings saved. Launch a game once, then edit EEPROM.</string>
|
||||
<string name="settings_saved_eeprom_failed">Settings saved, but EEPROM update failed</string>
|
||||
<string name="settings_section_advanced_experimental">Advanced / Experimental</string>
|
||||
<string name="settings_advanced_experimental_hint">Less common HDD management tools live here so the main settings flow stays focused on everyday setup.</string>
|
||||
<string name="settings_advanced_experimental_show">Show Advanced / Experimental Tools</string>
|
||||
<string name="settings_advanced_experimental_hide">Hide Advanced / Experimental Tools</string>
|
||||
<string name="settings_section_hdd_tools">HDD Tools</string>
|
||||
<string name="settings_hdd_tools_hint">Initializes the current local HDD image with a stock retail layout or optional extended F/G partitions. Raw and QCOW2 images are supported. This does not install a dashboard.</string>
|
||||
<string name="settings_hdd_status_missing">No local HDD image is configured. Pick one in the setup wizard first.</string>
|
||||
|
||||
Reference in New Issue
Block a user