From 80f94927b938daec5637e7452916698518d3c34a Mon Sep 17 00:00:00 2001 From: qiyinxi Date: Thu, 12 Nov 2026 16:57:03 +0100 Subject: [PATCH] =?UTF-8?q?MuMu=20=E5=A4=96=E5=A3=B3=E8=AE=A4=E5=AE=9E?= =?UTF-8?q?=E4=BE=8B=E7=BC=96=E5=8F=B7=E6=97=B6=E6=8C=89=E6=A0=B9=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E7=9A=84=20portBase=20=E7=AE=97=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=8F=B0=E7=AB=AF=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 外壳放在 <根目录>\mumu-shim\shell\sdk\ 时,从 MuMu 路径的上一级读 mas-avd.json 的 portBase(1100–65400 之间 100 的整数倍),按 portBase + 10×编号 算;读不到或不合规照旧用 20000。 --- README.md | 1 + shim/mumu_shim.c | 71 +++++++++++++++++++++++++++++++++++++++---- tests/test_shim.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 02636f8..cc6ede6 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ if (modavd_open(&p, &h) == MODAVD_OK) { - 本仓库编出的同名 DLL 就导出这四个函数,内部用的是同一套截图代码(静态编进去,不依赖 `modavd_extras.dll`)。 - 它不导出输入函数,所以 MaaCore 照旧用 MaaTouch 点击。 - AUTO-MAS 现在就是这样让 MAA 接 ModAVD 的:把这个 DLL 放进一个目录,在 MAA 里把它当作 MuMu 的安装路径,实例编号填控制台端口。这个外壳替换的是 AUTO-MAS 仓库里原来的 `res/avdshim`,行为不变。 +- 实例编号也可以直接填 ModAVD 的编号(0–9)。外壳放在 `\mumu-shim\shell\sdk\` 时,按根目录 `mas-avd.json` 的 `portBase` 算控制台端口(没写就是 20000),即 `portBase + 10×编号`。 - 等 MaaCore 有了 ModAVD 自己的连接方式,这个外壳就退场。 只能在 Windows 上用。 diff --git a/shim/mumu_shim.c b/shim/mumu_shim.c index 30d0cbf..c25718a 100644 --- a/shim/mumu_shim.c +++ b/shim/mumu_shim.c @@ -9,7 +9,9 @@ * ModAVD instance's shared frame. It replaces the earlier avdshim and behaves * the same way: * - * - nemu_connect(path, index) maps index to the console port (index_to_port), + * - nemu_connect(path, index) maps index to the console port (index_to_port: + * the console port itself, or the ModAVD instance number on the port plan + * of the root that path is in), * writes its log to \avdshim.log and starts frame sharing through the * console when it is off. * - nemu_capture_display returns 0 on success, RGBA rows bottom-up (MaaCore @@ -84,11 +86,70 @@ static void core_log(int level, const char* msg, void* user) } } +/* The port plan of the ModAVD root the shell sits in. ModAVD ships it as + * \mumu-shim\shell\sdk\external_renderer_ipc.dll and MAA's MuMu path is + * \mumu-shim, so the root is the parent of path: its mas-avd.json + * "portBase" when that is an integer multiple of 100 in 1100..65400 (the + * plans ModAVD accepts), else 20000. Only that one key is read, by a plain + * scan: the file is ModAVD's own and portBase is a number there. */ +static int root_port_base(const wchar_t* path) +{ + if (!path || !path[0]) { + return DEFAULT_PORT_BASE; + } + size_t n = wcslen(path); + while (n > 0 && (path[n - 1] == L'\\' || path[n - 1] == L'/')) { + --n; + } + while (n > 0 && path[n - 1] != L'\\' && path[n - 1] != L'/') { + --n; + } + if (n == 0 || n + 16 >= MAX_PATH) { + return DEFAULT_PORT_BASE; + } + wchar_t file[MAX_PATH]; + wmemcpy(file, path, n); + wcscpy(file + n, L"mas-avd.json"); + FILE* f = _wfopen(file, L"rb"); + if (!f) { + return DEFAULT_PORT_BASE; + } + static char text[65536]; + size_t len = fread(text, 1, sizeof(text) - 1, f); + fclose(f); + text[len] = 0; + const char* p = strstr(text, "\"portBase\""); + if (!p) { + return DEFAULT_PORT_BASE; + } + p += strlen("\"portBase\""); + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { + ++p; + } + if (*p++ != ':') { + return DEFAULT_PORT_BASE; + } + while (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { + ++p; + } + long base = 0; + int digits = 0; + while (*p >= '0' && *p <= '9' && digits < 6) { + base = base * 10 + (*p++ - '0'); + ++digits; + } + if (!digits || *p == '.' || *p == 'e' || *p == 'E' || base % 100 != 0 || base < 1100 || + base > 65400) { + return DEFAULT_PORT_BASE; + } + return (int)base; +} + /* index >= 20000 -> index is the emulator console port (what AUTO-MAS passes) * 5554 + 2 * index in 20000..20100 -> MAA's GUI derives index = (port - 5554) / 2 from "emulator-" * index >= 5554 -> index is the emulator console port - * otherwise -> AUTO-MAS native index, console port = 20000 + 10 * index */ -static int index_to_port(int index) + * otherwise -> the ModAVD instance number: console port = + 10 * index */ +static int index_to_port(const wchar_t* path, int index) { if (index >= 20000) { return index; @@ -97,7 +158,7 @@ static int index_to_port(int index) int maa = 5554 + 2 * index; return (maa >= 20000 && maa <= 20100) ? maa : index; } - return DEFAULT_PORT_BASE + PORT_STEP * index; + return root_port_base(path) + PORT_STEP * index; } static modavd_handle get_slot(int handle) @@ -115,7 +176,7 @@ EXPORT int nemu_connect(const wchar_t* path, int index) wcscat(g_log_path, L"\\avdshim.log"); } modavd_set_log(core_log, NULL); - int port = index_to_port(index); + int port = index_to_port(path, index); EnterCriticalSection(&g_lock); int handle = 0; diff --git a/tests/test_shim.c b/tests/test_shim.c index 2d325fb..3282aaf 100644 --- a/tests/test_shim.c +++ b/tests/test_shim.c @@ -114,6 +114,82 @@ int main(int argc, char** argv) /* No frame on that port and nothing listens: connect fails with 0. */ CHECK(nemu_connect(scratch, 65021) == 0); + /* An instance number (as a user types it into MAA): the console port is on + * the port plan of the root that the MuMu path sits in, \mumu-shim. + * Plan 1100 and index 3000: port 31100, while the default plan would give + * 50000; neither is near a real instance's ports (20000.., 21000..), so a + * wrong plan only fails to connect and never reaches a running emulator. */ + { + const int base = 1100, index = 3000; + fake_frame g; + if (fake_frame_create(&g, base + 10 * index, w, h, w, h) != 0) { + fprintf(stderr, "cannot create the second fake frame\n"); + return 1; + } + char dir[MAX_PATH], json[MAX_PATH]; + snprintf(dir, sizeof(dir), "%s\\root\\mumu-shim", argv[2]); + snprintf(json, sizeof(json), "%s\\root\\mas-avd.json", argv[2]); + char parent[MAX_PATH]; + snprintf(parent, sizeof(parent), "%s\\root", argv[2]); + CreateDirectoryA(parent, NULL); + CreateDirectoryA(dir, NULL); + wchar_t wdir[MAX_PATH]; + MultiByteToWideChar(CP_UTF8, 0, dir, -1, wdir, MAX_PATH); + static const char* plans[] = { + "{\"portBase\": 1100}", + "\xEF\xBB\xBF{\r\n \"components\": {},\r\n \"portBase\" :\r\n 1100,\r\n \"instances\": {}\r\n}", + }; + for (size_t k = 0; k < sizeof(plans) / sizeof(plans[0]); ++k) { + FILE* jf = fopen(json, "wb"); + CHECK(jf != NULL); + if (jf) { + fputs(plans[k], jf); + fclose(jf); + } + int hi = nemu_connect(wdir, index); + CHECK(hi > 0); + CHECK(nemu_capture_display(hi, 0, 0, &cw, &ch, NULL) == 0 && cw == (int)w && ch == (int)h); + nemu_disconnect(hi); + /* a trailing separator on the MuMu path names the same root */ + wchar_t slash[MAX_PATH]; + swprintf(slash, MAX_PATH, L"%ls\\", wdir); + hi = nemu_connect(slash, index); + CHECK(hi > 0); + nemu_disconnect(hi); + } + /* A portBase ModAVD does not accept is not taken: the plan falls back to 20000 (port 50000, no frame). + * Frames wait where taking 1150 or 1000 would land, so taking them shows. */ + fake_frame g1150, g1000; + if (fake_frame_create(&g1150, 1150 + 10 * index, w, h, w, h) != 0 || + fake_frame_create(&g1000, 1000 + 10 * index, w, h, w, h) != 0) { + fprintf(stderr, "cannot create the fake frames for refused plans\n"); + return 1; + } + static const char* bad_plans[] = { "{\"portBase\": 1150}", "{\"portBase\": 1100000}", "{\"portBase\": 1100.5}", + "{\"portBase\": \"1100\"}", "{\"portBase\": 1000}", "{\"portBase\": 65500}", + "{\"portbase\": 1100}" }; + for (size_t k = 0; k < sizeof(bad_plans) / sizeof(bad_plans[0]); ++k) { + FILE* jf = fopen(json, "wb"); + CHECK(jf != NULL); + if (jf) { + fputs(bad_plans[k], jf); + fclose(jf); + } + int hi = nemu_connect(wdir, index); + if (hi > 0) { + fprintf(stderr, "portBase %s was taken\n", bad_plans[k]); + ++g_failures; + nemu_disconnect(hi); + } + } + fake_frame_destroy(&g1150); + fake_frame_destroy(&g1000); + fake_frame_destroy(&g); + remove(json); + RemoveDirectoryA(dir); + RemoveDirectoryA(parent); + } + FILE* lf = fopen(log_path, "rb"); CHECK(lf != NULL); if (lf) {