├── .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() --------------------------------------------------------------------------------