├── .gitignore
├── Injector
├── Injector.cs
├── MainForm.Designer.cs
├── MainForm.cs
├── MainForm.resx
└── Program.cs
├── LunarInputFix
└── dllmain.cpp
├── README.md
└── lunarinputfix.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .vs
2 | LunarInputFix.sln
3 | x64
4 | LunarInputFix/x64
5 | LunarInputFix/framework.h
6 | LunarInputFix/libMinHook.lib
7 | LunarInputFix/LunarInputFix.vcxproj*
8 | LunarInputFix/MinHook.h
9 |
10 | Injector/Injector.csproj
11 | Injector/Injector.csproj.user
12 | Injector/Properties
13 | Injector/bin
14 | Injector/obj
--------------------------------------------------------------------------------
/Injector/Injector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System;
7 | using System.Diagnostics;
8 | using System.Runtime.InteropServices;
9 |
10 | namespace Injector
11 | {
12 |
13 | public class Injector
14 | {
15 | [DllImport("kernel32.dll")]
16 | public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
17 |
18 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
19 | public static extern IntPtr GetModuleHandle(string lpModuleName);
20 |
21 | [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
22 | static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
23 |
24 | [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
25 | static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
26 | uint dwSize, uint flAllocationType, uint flProtect);
27 |
28 | [DllImport("kernel32.dll", SetLastError = true)]
29 | static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
30 |
31 | [DllImport("kernel32.dll")]
32 | static extern IntPtr CreateRemoteThread(IntPtr hProcess,
33 | IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
34 |
35 | // privileges
36 | const int PROCESS_CREATE_THREAD = 0x0002;
37 | const int PROCESS_QUERY_INFORMATION = 0x0400;
38 | const int PROCESS_VM_OPERATION = 0x0008;
39 | const int PROCESS_VM_WRITE = 0x0020;
40 | const int PROCESS_VM_READ = 0x0010;
41 |
42 | // used for memory allocation
43 | const uint MEM_COMMIT = 0x00001000;
44 | const uint MEM_RESERVE = 0x00002000;
45 | const uint PAGE_READWRITE = 4;
46 |
47 | public static int InjectDLL(int pid, String dllName)
48 | {
49 |
50 | // geting the handle of the process - with required privileges
51 | IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, pid);
52 |
53 | // searching for the address of LoadLibraryA and storing it in a pointer
54 | IntPtr loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
55 |
56 | // alocating some memory on the target process - enough to store the name of the dll
57 | // and storing its address in a pointer
58 | IntPtr allocMemAddress = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
59 |
60 | // writing the name of the dll there
61 | UIntPtr bytesWritten;
62 | WriteProcessMemory(procHandle, allocMemAddress, Encoding.Default.GetBytes(dllName), (uint)((dllName.Length + 1) * Marshal.SizeOf(typeof(char))), out bytesWritten);
63 |
64 | // creating a thread that will call LoadLibraryA with allocMemAddress as argument
65 | CreateRemoteThread(procHandle, IntPtr.Zero, 0, loadLibraryAddr, allocMemAddress, 0, IntPtr.Zero);
66 |
67 | return 0;
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/Injector/MainForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace Injector
2 | {
3 | partial class MainForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.btn_refresh = new System.Windows.Forms.Button();
32 | this.comboBox = new System.Windows.Forms.ComboBox();
33 | this.textBox = new System.Windows.Forms.TextBox();
34 | this.btn_choose = new System.Windows.Forms.Button();
35 | this.label1 = new System.Windows.Forms.Label();
36 | this.openFileDialog = new System.Windows.Forms.OpenFileDialog();
37 | this.btn_inject = new System.Windows.Forms.Button();
38 | this.linkLabel1 = new System.Windows.Forms.LinkLabel();
39 | this.SuspendLayout();
40 | //
41 | // btn_refresh
42 | //
43 | this.btn_refresh.AccessibleName = "btn_refresh";
44 | this.btn_refresh.Location = new System.Drawing.Point(12, 12);
45 | this.btn_refresh.Name = "btn_refresh";
46 | this.btn_refresh.Size = new System.Drawing.Size(94, 29);
47 | this.btn_refresh.TabIndex = 0;
48 | this.btn_refresh.Text = "刷新";
49 | this.btn_refresh.UseVisualStyleBackColor = true;
50 | this.btn_refresh.Click += new System.EventHandler(this.btn_refresh_Click);
51 | //
52 | // comboBox
53 | //
54 | this.comboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
55 | this.comboBox.FormattingEnabled = true;
56 | this.comboBox.Location = new System.Drawing.Point(112, 13);
57 | this.comboBox.Name = "comboBox";
58 | this.comboBox.Size = new System.Drawing.Size(151, 28);
59 | this.comboBox.TabIndex = 2;
60 | //
61 | // textBox
62 | //
63 | this.textBox.Location = new System.Drawing.Point(58, 47);
64 | this.textBox.Name = "textBox";
65 | this.textBox.ReadOnly = true;
66 | this.textBox.Size = new System.Drawing.Size(205, 27);
67 | this.textBox.TabIndex = 3;
68 | //
69 | // btn_choose
70 | //
71 | this.btn_choose.Location = new System.Drawing.Point(269, 47);
72 | this.btn_choose.Name = "btn_choose";
73 | this.btn_choose.Size = new System.Drawing.Size(94, 29);
74 | this.btn_choose.TabIndex = 4;
75 | this.btn_choose.Text = "浏览";
76 | this.btn_choose.UseVisualStyleBackColor = true;
77 | this.btn_choose.Click += new System.EventHandler(this.btn_choose_Click);
78 | //
79 | // label1
80 | //
81 | this.label1.AutoSize = true;
82 | this.label1.Location = new System.Drawing.Point(12, 50);
83 | this.label1.Name = "label1";
84 | this.label1.Size = new System.Drawing.Size(40, 20);
85 | this.label1.TabIndex = 5;
86 | this.label1.Text = "DLL:";
87 | //
88 | // openFileDialog
89 | //
90 | this.openFileDialog.Filter = "DLL|*.dll|所有文件|*.*";
91 | //
92 | // btn_inject
93 | //
94 | this.btn_inject.Location = new System.Drawing.Point(269, 13);
95 | this.btn_inject.Name = "btn_inject";
96 | this.btn_inject.Size = new System.Drawing.Size(94, 29);
97 | this.btn_inject.TabIndex = 6;
98 | this.btn_inject.Text = "注入";
99 | this.btn_inject.UseVisualStyleBackColor = true;
100 | this.btn_inject.Click += new System.EventHandler(this.btn_inject_Click);
101 | //
102 | // linkLabel1
103 | //
104 | this.linkLabel1.AutoSize = true;
105 | this.linkLabel1.Location = new System.Drawing.Point(12, 77);
106 | this.linkLabel1.Name = "linkLabel1";
107 | this.linkLabel1.Size = new System.Drawing.Size(58, 20);
108 | this.linkLabel1.TabIndex = 7;
109 | this.linkLabel1.TabStop = true;
110 | this.linkLabel1.Text = "Github";
111 | this.linkLabel1.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this.linkLabel1_LinkClicked);
112 | //
113 | // MainForm
114 | //
115 | this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F);
116 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
117 | this.ClientSize = new System.Drawing.Size(381, 118);
118 | this.Controls.Add(this.linkLabel1);
119 | this.Controls.Add(this.btn_inject);
120 | this.Controls.Add(this.label1);
121 | this.Controls.Add(this.btn_choose);
122 | this.Controls.Add(this.textBox);
123 | this.Controls.Add(this.comboBox);
124 | this.Controls.Add(this.btn_refresh);
125 | this.Name = "MainForm";
126 | this.Text = "LunarInputFix";
127 | this.ResumeLayout(false);
128 | this.PerformLayout();
129 |
130 | }
131 |
132 | #endregion
133 |
134 | private Button btn_refresh;
135 | private ComboBox comboBox;
136 | private TextBox textBox;
137 | private Button btn_choose;
138 | private Label label1;
139 | private OpenFileDialog openFileDialog;
140 | private Button btn_inject;
141 | private LinkLabel linkLabel1;
142 | }
143 | }
--------------------------------------------------------------------------------
/Injector/MainForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Data;
5 | using System.Diagnostics;
6 | using System.Drawing;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 | using System.Windows.Forms;
11 |
12 | namespace Injector
13 | {
14 | public partial class MainForm : Form
15 | {
16 |
17 | private List pids = new List();
18 |
19 | public MainForm()
20 | {
21 | InitializeComponent();
22 | }
23 |
24 | private void btn_refresh_Click(object sender, EventArgs e)
25 | {
26 | pids.Clear();
27 | comboBox.Items.Clear();
28 | foreach (Process process in Process.GetProcessesByName("javaw"))
29 | {
30 | pids.Add(process.Id);
31 | comboBox.Items.Add($"{process.Id} - {process.MainWindowTitle}");
32 | }
33 | foreach (Process process in Process.GetProcessesByName("java"))
34 | {
35 | pids.Add(process.Id);
36 | comboBox.Items.Add($"{process.Id} - {process.MainWindowTitle}");
37 | }
38 | }
39 |
40 | private void btn_choose_Click(object sender, EventArgs e)
41 | {
42 | if(openFileDialog.ShowDialog() == DialogResult.OK)
43 | {
44 | textBox.Text = openFileDialog.FileName;
45 | }
46 | }
47 |
48 | private void btn_inject_Click(object sender, EventArgs e)
49 | {
50 | if (comboBox.SelectedIndex < 0)
51 | return;
52 | int pid = pids[comboBox.SelectedIndex];
53 | Injector.InjectDLL(pid, textBox.Text);
54 | }
55 |
56 | private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
57 | {
58 | Process.Start("rundll32.exe", "url.dll,FileProtocolHandler https://github.com/sduoduo233/LunarInputFix/");
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Injector/MainForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 | text/microsoft-resx
50 |
51 |
52 | 2.0
53 |
54 |
55 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
56 |
57 |
58 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
59 |
60 |
61 | 17, 17
62 |
63 |
--------------------------------------------------------------------------------
/Injector/Program.cs:
--------------------------------------------------------------------------------
1 | namespace Injector
2 | {
3 | internal static class Program
4 | {
5 | ///
6 | /// The main entry point for the application.
7 | ///
8 | [STAThread]
9 | static void Main()
10 | {
11 | // To customize application configuration such as set high DPI settings or default font,
12 | // see https://aka.ms/applicationconfiguration.
13 | ApplicationConfiguration.Initialize();
14 | Application.Run(new MainForm());
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/LunarInputFix/dllmain.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "MinHook.h"
3 | #include
4 | #include
5 | #include
6 |
7 | #pragma comment(lib, "libMinHook.lib")
8 |
9 | typedef BOOL (WINAPI* PEEKMESSAGEW)(LPMSG, HWND, UINT, UINT, UINT);
10 |
11 | PEEKMESSAGEW fpPeekMessageW = NULL;
12 |
13 | struct Message
14 | {
15 | HWND hWnd;
16 | UINT message;
17 | WPARAM wParam;
18 | LPARAM lParam;
19 | };
20 |
21 | std::vector messages;
22 |
23 | BOOL WINAPI DetourPeekMessageW(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg)
24 | {
25 | BOOL result = fpPeekMessageW(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg);
26 | if (result != 0) {
27 |
28 | if (lpMsg->message == WM_CHAR) {
29 | UINT chr = lpMsg->wParam & 65535;
30 | printf("WM_CHAR hWnd=%p char=%c wParam=0x%llx lParam=0x%llx", lpMsg->hwnd, chr, lpMsg->wParam, lpMsg->lParam);
31 |
32 | if (chr > 255) {
33 |
34 | UINT keycode = VK_PACKET;
35 |
36 | // keydown VK_PACKET
37 | Message msg1 = Message();
38 | msg1.hWnd = lpMsg->hwnd;
39 | msg1.message = WM_KEYDOWN;
40 | msg1.wParam = keycode;
41 | msg1.lParam = 0x1;
42 | messages.push_back(msg1);
43 |
44 | // real char
45 | Message msg2 = Message();
46 | msg2.hWnd = lpMsg->hwnd;
47 | msg2.message = WM_CHAR;
48 | msg2.wParam = chr;
49 | msg2.lParam = 0x1;
50 | messages.push_back(msg2);
51 |
52 | // keyup VK_PACKET
53 | Message msg3 = Message();
54 | msg3.hWnd = lpMsg->hwnd;
55 | msg3.message = WM_KEYUP;
56 | msg3.wParam = keycode;
57 | msg3.lParam = 0xC0000001;
58 | messages.push_back(msg3);
59 |
60 | printf(" CANCELLED\n");
61 |
62 | return 0;
63 | }
64 | printf("\n");
65 | }
66 |
67 | if (lpMsg->message == WM_KEYDOWN) {
68 | printf("WM_KEYDOWN hWnd=%p wParam=0x%llx lParam=0x%llx", lpMsg->hwnd, lpMsg->wParam, lpMsg->lParam);
69 | printf("\n");
70 | }
71 |
72 | if (lpMsg->message == WM_KEYUP) {
73 | printf("WM_KEYUP hWnd=%p wParam=0x%llx lParam=0x%llx", lpMsg->hwnd, lpMsg->wParam, lpMsg->lParam);
74 | printf("\n");
75 | }
76 |
77 | }
78 | else {
79 | if (messages.size() > 0) {
80 | Message msg = messages[0];
81 | lpMsg->hwnd = msg.hWnd;
82 | lpMsg->message = msg.message;
83 | lpMsg->wParam = msg.wParam;
84 | lpMsg->lParam = msg.lParam;
85 |
86 | printf("SEND hWnd=%p message=0x%x wParam=0x%llx lParam=0x%llx\n", lpMsg->hwnd, lpMsg->message, lpMsg->wParam, lpMsg->lParam);
87 |
88 | messages.erase(messages.begin());
89 |
90 | return 1;
91 | }
92 | }
93 | return result;
94 | }
95 |
96 | void hook() {
97 |
98 | #if _DEBUG
99 |
100 | AllocConsole();
101 | FILE* fOut;
102 | freopen_s(&fOut, "conout$", "w", stdout);
103 |
104 | #endif // DEBUG
105 |
106 | printf("LunarInputFix by duoduo\n");
107 |
108 | if (MH_Initialize() != MH_OK) {
109 | printf("initialize error");
110 | return;
111 | }
112 |
113 | if (MH_CreateHook(PeekMessageW, &DetourPeekMessageW,
114 | reinterpret_cast(&fpPeekMessageW)) != MH_OK)
115 | {
116 | printf("MH_CreateHook error");
117 | return;
118 | }
119 |
120 | if (MH_EnableHook(PeekMessageW) != MH_OK)
121 | {
122 | printf("MH_EnableHook error");
123 | return;
124 | }
125 |
126 | MessageBox(NULL, (LPCWSTR)L"注入成功", (LPCWSTR)L"LunarInputFix by duoduo", 0);
127 | }
128 |
129 | BOOL APIENTRY DllMain( HMODULE hModule,
130 | DWORD ul_reason_for_call,
131 | LPVOID lpReserved
132 | )
133 | {
134 | switch (ul_reason_for_call)
135 | {
136 | case DLL_PROCESS_ATTACH:
137 | hook();
138 | break;
139 | case DLL_THREAD_ATTACH:
140 | case DLL_THREAD_DETACH:
141 | case DLL_PROCESS_DETACH:
142 | break;
143 | }
144 | return TRUE;
145 | }
146 |
147 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lunar 已经支持中文输入了
2 | # Chinese IME is officially supported by Lunar Client
3 |
4 | # Lunar Input Fix
5 | 修复 Lunar Client 不能输入中文的 bug, 只支持windows
6 | # 使用方法
7 | 1. 下载[Release](https://github.com/sduoduo233/LunarInputFix/releases/latest)
8 | 2. 安装[ProcessHacker2](https://processhacker.sourceforge.io/downloads.php)或其它DLL注入工具
9 | 3. 把```LunarInputFix.dll```注入到Lunar端中
10 |
--------------------------------------------------------------------------------
/lunarinputfix.py:
--------------------------------------------------------------------------------
1 | import frida
2 |
3 | session = frida.attach("javaw.exe")
4 | script = session.create_script("""
5 | function readMessage(p){
6 | var hwnd = p.readPointer()
7 | var message = p.add(Process.pointerSize).readUInt()
8 | var wParam = p.add(Process.pointerSize+8).readU64()
9 | var lParam = p.add(Process.pointerSize+8+8).readU64()
10 | return {
11 | "hwnd": hwnd,
12 | "message": message,
13 | "wParam": wParam,
14 | "lParam": lParam
15 | }
16 | }
17 |
18 | var msgPointer
19 | var messages = []
20 | Interceptor.attach(Module.getExportByName('user32.dll', 'PeekMessageW'), {
21 | onEnter(args) {
22 | msgPointer = args[0];
23 | },
24 | onLeave(retval) {
25 |
26 | if(retval == 0x0){
27 | if(messages.length > 0){
28 | var msg = messages.shift()
29 | msgPointer.writePointer(msg.hwnd)
30 | msgPointer.add(Process.pointerSize).writeUInt(msg.message)
31 | msgPointer.add(Process.pointerSize+8).writeU64(msg.wParam)
32 | msgPointer.add(Process.pointerSize+8+8).writeU64(msg.lParam)
33 | console.log("--> SEND", JSON.stringify(readMessage(msgPointer)))
34 | retval.replace(0x1)
35 | }
36 | return
37 | }
38 |
39 | var msg = readMessage(msgPointer)
40 |
41 | // CHAR
42 | if(msg.message == 258){
43 | var char = msg.wParam & 65535
44 | console.log("char", JSON.stringify(msg), String.fromCharCode(char))
45 | if(char > 255){
46 | retval.replace(0x0)
47 |
48 | // keydown VK_PACKET
49 | messages.push({
50 | "hwnd": msg.hwnd,
51 | "message": 0x0100,
52 | "wParam": 0xE7,
53 | "lParam": 0x1
54 | })
55 | // CHAR
56 | messages.push({
57 | "hwnd": msg.hwnd,
58 | "message": 0x0102,
59 | "wParam": char,
60 | "lParam": 0x1
61 | })
62 | // keyup VK_PACKET
63 | messages.push({
64 | "hwnd": msg.hwnd,
65 | "message": 0x0101,
66 | "wParam": 0xE7,
67 | "lParam": 0xC0000001
68 | })
69 | }
70 | }
71 |
72 | // KEYDOWN
73 | if(msg.message == 256){
74 | var keycode = msg.wParam
75 | var pervious_state = (msg.lParam >>> 30) & 0x1
76 | var state = 1 - ((msg.lParam >>> 31) & 0x1)
77 | console.log("keydown", JSON.stringify(msg), String.fromCharCode(keycode))
78 | }
79 | }
80 | })
81 | """)
82 |
83 | def on_message(message, data):
84 | print(message)
85 | script.on('message', on_message)
86 | script.load()
87 | input()
--------------------------------------------------------------------------------