mirror of
https://gitea.tendokyu.moe/TeamTofuShop/segatools.git
synced 2026-09-22 22:37:59 +03:00
unity: support debugging (#99)
User can debug their Unity game with dnspy and other debugger, it needn't patch mono.dll. most code is copied&modified from [UnityDoorstop](https://github.com/NeighTools/UnityDoorstop/blob/master/src/bootstrap.c#L328) currently it work on SDEZ/SDDT <details> <summary>How to debug and breakpoint Unity game (For example SDDT)</summary> 1. Modify segatools.ini and set `enableDebug` and `debugAddress`  2. Inject mu3 with hook dll  3. dnspy connect unity  4. open Modules tab and double click Assembly-CSharp.dll, yeah, now you can add breakpoints now.  </details> and make unity_config.target_assembly is optional if we just want to debug unity. Now PR is ready to code review and merge into upstream. Reviewed-on: https://gitea.tendokyu.moe/TeamTofuShop/segatools/pulls/99 Co-authored-by: MikiraSora <mikirasora0409@126.com> Co-committed-by: MikiraSora <mikirasora0409@126.com>
This commit is contained in:
@@ -4,6 +4,8 @@
|
||||
|
||||
void unity_config_load(struct unity_config *cfg, const wchar_t *filename) {
|
||||
cfg->enable = GetPrivateProfileIntW(L"unity", L"enable", 1, filename);
|
||||
cfg->debug_enable = GetPrivateProfileIntW(L"unity", L"enableDebug", 0, filename);
|
||||
cfg->debug_suspend = GetPrivateProfileIntW(L"unity", L"debugSuspend", 0, filename);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"unity",
|
||||
@@ -13,4 +15,13 @@ void unity_config_load(struct unity_config *cfg, const wchar_t *filename) {
|
||||
_countof(cfg->target_assembly),
|
||||
filename
|
||||
);
|
||||
|
||||
GetPrivateProfileStringW(
|
||||
L"unity",
|
||||
L"debugAddress",
|
||||
L"127.0.0.1:55555",
|
||||
cfg->debug_address,
|
||||
_countof(cfg->debug_address),
|
||||
filename
|
||||
);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
|
||||
struct unity_config {
|
||||
bool enable;
|
||||
bool debug_enable;
|
||||
bool debug_suspend;
|
||||
wchar_t target_assembly[MAX_PATH];
|
||||
wchar_t debug_address[MAX_PATH];
|
||||
};
|
||||
|
||||
void unity_config_load(struct unity_config *cfg, const wchar_t *filename);
|
||||
|
||||
+116
-12
@@ -18,21 +18,38 @@
|
||||
#include "mono.h"
|
||||
#include "util.h"
|
||||
|
||||
static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version);
|
||||
void doorstop_invoke(void *domain);
|
||||
#define MONO_DEBUG_ARG_START "--debugger-agent=transport=dt_socket,server=y,address="
|
||||
#define MONO_DEBUG_NO_SUSPEND ",suspend=n"
|
||||
#define MONO_DEBUG_NO_SUSPEND_NET35 ",suspend=n,defer=y"
|
||||
|
||||
static void* my_mono_jit_init_version(const char* root_domain_name,
|
||||
const char* runtime_version);
|
||||
static void my_mono_jit_parse_options(int argc, const char** argv);
|
||||
static void* my_mono_debug_init(MonoDebugFormat format);
|
||||
|
||||
void doorstop_invoke(void* domain);
|
||||
|
||||
static char module_name[MAX_PATH];
|
||||
static bool doorstop_hook_initted;
|
||||
static bool mono_debug_init_called;
|
||||
static bool mono_is_net35;
|
||||
static struct unity_config unity_config;
|
||||
static struct hook_symbol unity_mono_syms[] = {
|
||||
{
|
||||
{
|
||||
.name = "mono_jit_init_version",
|
||||
.patch = my_mono_jit_init_version,
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
.name = "mono_debug_init",
|
||||
.patch = my_mono_debug_init,
|
||||
},
|
||||
{
|
||||
.name = "mono_jit_parse_options",
|
||||
.patch = my_mono_jit_parse_options,
|
||||
}};
|
||||
|
||||
void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module) {
|
||||
if (doorstop_hook_initted || cfg->target_assembly[0] == 0) {
|
||||
void doorstop_mono_hook_init(const struct unity_config* cfg, HINSTANCE module) {
|
||||
if (doorstop_hook_initted) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,22 +57,104 @@ void doorstop_mono_hook_init(const struct unity_config *cfg, HINSTANCE module) {
|
||||
|
||||
memcpy(&unity_config, cfg, sizeof(*cfg));
|
||||
load_mono_functions(module);
|
||||
proc_addr_table_push(NULL, module_name, unity_mono_syms, _countof(unity_mono_syms));
|
||||
proc_addr_table_push(NULL, module_name, unity_mono_syms,
|
||||
_countof(unity_mono_syms));
|
||||
|
||||
doorstop_hook_initted = true;
|
||||
}
|
||||
|
||||
static void * my_mono_jit_init_version(const char *root_domain_name, const char *runtime_version) {
|
||||
dprintf("Unity: Starting Mono domain \"%s\"\n", root_domain_name);
|
||||
dprintf("Unity: Starting Mono domain \"%s\", runtime version %s\n", root_domain_name, runtime_version);
|
||||
if (strlen(runtime_version) > 2 && (runtime_version[1] == L'2' || runtime_version[1] == L'1')) {
|
||||
mono_is_net35 = TRUE;
|
||||
}
|
||||
|
||||
SetEnvironmentVariableW(L"DOORSTOP_DLL_SEARCH_DIRS", widen(mono_assembly_getrootdir()));
|
||||
my_mono_jit_parse_options(0, NULL);
|
||||
|
||||
bool debugger_already_enabled = mono_debug_init_called;
|
||||
if (mono_debug_enabled) {
|
||||
debugger_already_enabled |= mono_debug_enabled();
|
||||
}
|
||||
|
||||
if (!debugger_already_enabled) {
|
||||
dprintf("Unity: Detected mono debugger is not initialized; initialized it\n");
|
||||
mono_debug_init(MONO_DEBUG_FORMAT_MONO);
|
||||
}
|
||||
void* domain = mono_jit_init_version(root_domain_name, runtime_version);
|
||||
|
||||
doorstop_invoke(domain);
|
||||
return domain;
|
||||
}
|
||||
|
||||
static void my_mono_jit_parse_options(int argc, const char** argv) {
|
||||
char* debug_options = getenv(TEXT("DNSPY_UNITY_DBG2"));
|
||||
if (debug_options) {
|
||||
unity_config.debug_enable = TRUE;
|
||||
}
|
||||
|
||||
if (unity_config.debug_enable) {
|
||||
dprintf("Unity: Configuring mono debug server\n");
|
||||
|
||||
int size = argc + 1;
|
||||
const char** new_argv = calloc(size, sizeof(char*));
|
||||
memcpy(new_argv, argv, argc * sizeof(char*));
|
||||
|
||||
//check if debug options are already set by env variable, if not, build it from config
|
||||
if (!debug_options) {
|
||||
size_t debug_args_len = strlen(MONO_DEBUG_ARG_START) +
|
||||
wcslen(unity_config.debug_address);
|
||||
if (!unity_config.debug_suspend) {
|
||||
if (mono_is_net35) {
|
||||
debug_args_len += strlen(MONO_DEBUG_NO_SUSPEND_NET35);
|
||||
} else {
|
||||
debug_args_len += strlen(MONO_DEBUG_NO_SUSPEND);
|
||||
}
|
||||
}
|
||||
|
||||
debug_options = calloc(debug_args_len + 1, sizeof(wchar_t));
|
||||
strcat(debug_options, MONO_DEBUG_ARG_START);
|
||||
|
||||
char* temp_address = narrow(unity_config.debug_address);
|
||||
strcat(debug_options, temp_address);
|
||||
free(temp_address);
|
||||
|
||||
if (!unity_config.debug_suspend) {
|
||||
if (mono_is_net35) {
|
||||
strcat(debug_options, MONO_DEBUG_NO_SUSPEND_NET35);
|
||||
} else {
|
||||
strcat(debug_options, MONO_DEBUG_NO_SUSPEND);
|
||||
}
|
||||
}
|
||||
dprintf("Unity: Debug options from config: %s\n", debug_options);
|
||||
} else
|
||||
dprintf("Unity: Debug options from env DNSPY_UNITY_DBG2: %s\n",
|
||||
debug_options);
|
||||
|
||||
new_argv[argc] = debug_options;
|
||||
/*
|
||||
dprintf("Unity: mono_jit_parse_options argc: %d\n", size);
|
||||
dprintf("Unity: mono_jit_parse_options argv:\n");
|
||||
for (int i = 0; i < size; i++) {
|
||||
dprintf(" %d: %s\n", i, new_argv[i]);
|
||||
}
|
||||
dprintf("\n");
|
||||
*/
|
||||
mono_jit_parse_options(size, new_argv);
|
||||
free(debug_options);
|
||||
free(new_argv);
|
||||
} else {
|
||||
mono_jit_parse_options(argc, argv);
|
||||
}
|
||||
}
|
||||
|
||||
static void* my_mono_debug_init(MonoDebugFormat format) {
|
||||
dprintf("Unity: Start mono debug init with format %d\n",format);
|
||||
mono_debug_init_called = true;
|
||||
void* domain = mono_debug_init(format);
|
||||
return domain;
|
||||
}
|
||||
|
||||
void doorstop_invoke(void* domain) {
|
||||
if (GetEnvironmentVariableW(L"DOORSTOP_INITIALIZED", NULL, 0) != 0) {
|
||||
dprintf("Unity: Doorstop is already initialized.\n");
|
||||
@@ -90,6 +189,11 @@ void doorstop_invoke(void* domain) {
|
||||
#undef CONFIG_EXT
|
||||
}
|
||||
|
||||
if(unity_config.target_assembly[0] == 0) {
|
||||
dprintf("Unity: No target assembly file specified, skipping doorstop invocation.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
SetEnvironmentVariableW(L"DOORSTOP_INVOKE_DLL_PATH", unity_config.target_assembly);
|
||||
|
||||
char *assembly_dir = mono_assembly_getrootdir();
|
||||
@@ -108,7 +212,7 @@ void doorstop_invoke(void* domain) {
|
||||
void* assembly = mono_domain_assembly_open(domain, dll_path);
|
||||
|
||||
if (!assembly) {
|
||||
dprintf("Unity: Failed to load assembly\n");
|
||||
dprintf("Unity: Failed to load target assembly\n");
|
||||
free(dll_path);
|
||||
return;
|
||||
}
|
||||
@@ -116,7 +220,7 @@ void doorstop_invoke(void* domain) {
|
||||
void *image = mono_assembly_get_image(assembly);
|
||||
|
||||
if (!image) {
|
||||
dprintf("Unity: Assembly image doesn't exist\n");
|
||||
dprintf("Unity: Target assembly image doesn't exist\n");
|
||||
free(dll_path);
|
||||
return;
|
||||
}
|
||||
@@ -135,7 +239,7 @@ void doorstop_invoke(void* domain) {
|
||||
}
|
||||
|
||||
if (!method) {
|
||||
dprintf("Unity: Assembly does not have a valid entrypoint.\n");
|
||||
dprintf("Unity: Target assembly does not have a valid entrypoint.\n");
|
||||
free(dll_path);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ void* (*mono_get_exception_class)();
|
||||
void* (*mono_object_get_virtual_method)(void* obj_raw, void* method);
|
||||
|
||||
void* (*mono_jit_parse_options)(int argc, const char** argv);
|
||||
UINT32 (*mono_debug_enabled)();
|
||||
|
||||
typedef enum {
|
||||
MONO_DEBUG_FORMAT_NONE,
|
||||
@@ -95,6 +96,7 @@ void load_mono_functions(HMODULE mono_lib) {
|
||||
GET_MONO_PROC(mono_jit_parse_options);
|
||||
GET_MONO_PROC(mono_debug_init);
|
||||
GET_MONO_PROC(mono_debug_domain_create);
|
||||
GET_MONO_PROC(mono_debug_enabled);
|
||||
|
||||
#undef GET_MONO_PROC
|
||||
}
|
||||
Reference in New Issue
Block a user