Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)。外壳放在 `<ModAVD 根目录>\mumu-shim\shell\sdk\` 时,按根目录 `mas-avd.json` 的 `portBase` 算控制台端口(没写就是 20000),即 `portBase + 10×编号`。
- 等 MaaCore 有了 ModAVD 自己的连接方式,这个外壳就退场。

只能在 Windows 上用。
Expand Down
71 changes: 66 additions & 5 deletions shim/mumu_shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <path>\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
Expand Down Expand Up @@ -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
* <root>\mumu-shim\shell\sdk\external_renderer_ipc.dll and MAA's MuMu path is
* <root>\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-<port>"
* 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 = <root's portBase> + 10 * index */
static int index_to_port(const wchar_t* path, int index)
{
if (index >= 20000) {
return index;
Expand All @@ -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)
Expand All @@ -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;
Expand Down
76 changes: 76 additions & 0 deletions tests/test_shim.c
Original file line number Diff line number Diff line change
Expand Up @@ -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, <root>\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) {
Expand Down
Loading