Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System;
- using System.ComponentModel;
- using System.Diagnostics;
- using System.IO;
- using System.Runtime.InteropServices;
- using System.Text;
- using System.Threading;
- using System.Windows.Forms;
- internal static class MeishoujoKorLauncher
- {
- // 실행 중인 원본 프로세스의 메모리에 쓸 패치 단위들이다.
- // RawOffset은 파일 오프셋 기준이며, 실제 VA는 RawToVaDelta를 더해 계산한다.
- private struct PatchRange
- {
- public readonly int RawOffset;
- public readonly int Size;
- public PatchRange(int rawOffset, int size)
- {
- RawOffset = rawOffset;
- Size = size;
- }
- }
- private struct TextPatch
- {
- public readonly int RawOffset;
- public readonly int Size;
- public readonly string Text;
- public TextPatch(int rawOffset, int size, string text)
- {
- RawOffset = rawOffset;
- Size = size;
- Text = text;
- }
- }
- private struct WideTextPatch
- {
- public readonly int RawOffset;
- public readonly int Size;
- public readonly string Text;
- public WideTextPatch(int rawOffset, int size, string text)
- {
- RawOffset = rawOffset;
- Size = size;
- Text = text;
- }
- }
- private struct HexPatch
- {
- public readonly int RawOffset;
- public readonly string Hex;
- public HexPatch(int rawOffset, string hex)
- {
- RawOffset = rawOffset;
- Hex = hex;
- }
- }
- private struct TextReplacement
- {
- public readonly string Source;
- public readonly string Target;
- public TextReplacement(string source, string target)
- {
- Source = source;
- Target = target;
- }
- }
- [StructLayout(LayoutKind.Sequential)]
- private struct Rect
- {
- public int Left;
- public int Top;
- public int Right;
- public int Bottom;
- }
- private struct BytePatchPattern
- {
- public readonly byte[] Pattern;
- public readonly int PatchOffset;
- public readonly byte Value;
- public readonly string Name;
- public BytePatchPattern(byte[] pattern, int patchOffset, byte value, string name)
- {
- Pattern = pattern;
- PatchOffset = patchOffset;
- Value = value;
- Name = name;
- }
- }
- private const uint ProcessQueryInformation = 0x0400;
- private const uint ProcessVmOperation = 0x0008;
- private const uint ProcessVmRead = 0x0010;
- private const uint ProcessVmWrite = 0x0020;
- private const uint PageExecuteReadWrite = 0x40;
- private const int ImageBase = 0x400000;
- private const int ImageSize = 0x5AF000;
- private const int ScanChunkSize = 0x10000;
- // 언패킹된 메모리 배치에서 raw 파일 오프셋을 가상 주소로 바꾸기 위한 보정값.
- private const int RawToVaDelta = 0x400C00;
- private const int TimeoutMs = 10000;
- private const int PollIntervalMs = 10;
- private const int PatchDelayMs = 50;
- private const int TitleWaitMs = 2000;
- private const int ProductNamePatchTimeoutMs = 3000;
- // 창 제목 "조카소녀"는 소스 파일 인코딩에 흔들리지 않도록 유니코드 이스케이프로 보관한다.
- private const string KoreanTitle = "\uC870\uCE74\uC18C\uB140";
- private const uint EventObjectCreate = 0x8000;
- private const uint EventObjectNameChange = 0x800C;
- private const uint WinEventOutOfContext = 0x0000;
- private const int CbGetCount = 0x0146;
- private const int CbGetCurSel = 0x0147;
- private const int CbGetLbText = 0x0148;
- private const int CbGetLbTextLen = 0x0149;
- private const int CbAddString = 0x0143;
- private const int CbResetContent = 0x014B;
- private const int CbSetCurSel = 0x014E;
- private static readonly byte[] OriginalPattern = { 0xC6, 0x44, 0x24, 0x47, 0x80 };
- // Shift-JIS의 "姪少女" 원본 바이트. 저장 경로 생성 전에 CP949 바이트로 먼저 바꿔야
- // "뽂룺룛"처럼 잘못 해석된 폴더명이 만들어지는 레이스를 피할 수 있다.
- private static readonly byte[] ProductNameCp932Bytes = { 0x96, 0xC3, 0x8F, 0xAD, 0x8F, 0x97 };
- private static readonly int[] ProductNameRawOffsets = { 0x3ECF90, 0x3F3688, 0x3F370C };
- // GDI 폰트 생성부의 charset을 일본어(0x80)에서 한글(0x81)로 바꾸는 패턴.
- // 고정 오프셋보다 패턴 검색이 안정적이라 언패킹 완료 후 메모리에서 직접 찾는다.
- private static readonly BytePatchPattern[] CharsetPatterns =
- {
- new BytePatchPattern(new byte[] { 0xC6, 0x44, 0x24, 0x47, 0x80 }, 4, 0x81, "stack LOGFONTA charset"),
- new BytePatchPattern(new byte[] { 0xC6, 0x46, 0x37, 0x80 }, 3, 0x81, "font object default charset"),
- };
- // CP949로 표시되는 메뉴, 메시지, 타이틀 계열 문자열 패치.
- // 한국어 문자열은 유니코드 이스케이프로 저장하고, 실제 패치 시 CP949 바이트로 변환한다.
- private static readonly TextPatch[] TextPatches =
- {
- new TextPatch(0x3ED450, 0xD, "\uB098\uB214\uACE0\uB515"),
- new TextPatch(0x3ED750, 0x2D, "\uC2DC\uB098\uB9AC\uC624 \uC624\uB958\uB85C \uC2DC\uC791\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."),
- new TextPatch(0x3ED780, 0x3C, "\uB85C\uB4DC\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.\n\uD30C\uC77C\uC774 \uC190\uC0C1\uB418\uC5C8\uC744 \uC218 \uC788\uC2B5\uB2C8\uB2E4."),
- new TextPatch(0x3ED7D8, 0x3D, "\uC11C\uBE0C \uD0C0\uC774\uD2C0 \uBCF5\uADC0\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uD0C0\uC774\uD2C0 \uD654\uBA74\uC73C\uB85C \uB3CC\uC544\uAC11\uB2C8\uB2E4."),
- new TextPatch(0x3ED88C, 0x25, "\uD68C\uC0C1\uC744 \uC885\uB8CC\uD569\uB2C8\uB2E4. \uACC4\uC18D\uD560\uAE4C\uC694?"),
- new TextPatch(0x3ED8B4, 0x24, "\uD0C0\uC774\uD2C0\uC5D0 \uB3CC\uC544\uC635\uB2C8\uB2E4. \uC88B\uC2B5\uB2C8\uAE4C\uFF1F"),
- new TextPatch(0x3ED8DC, 0x2B, "\uD035 \uC138\uC774\uBE0C \uB370\uC774\uD130%2d\uBC88\uC5D0 \uC138\uC774\uBE0C\uD588\uC2B5\uB2C8\uB2E4."),
- new TextPatch(0x3ED908, 0x40, "\uD035 \uC138\uC774\uBE0C \uB370\uC774\uD130 %2d\uBC88\uC5D0 \uB36E\uC5B4\uC501\uB2C8\uB2E4. \uACC4\uC18D\uD560\uAE4C\uC694?"),
- new TextPatch(0x3ED958, 0x3A, "\uD035 \uC138\uC774\uBE0C \uB370\uC774\uD130 %2d\uBC88\uC744 \uB85C\uB4DC\uD569\uB2C8\uB2E4. \uACC4\uC18D\uD560\uAE4C\uC694?"),
- new TextPatch(0x3ED994, 0x26, "\uD035 \uB85C\uB4DC\uD569\uB2C8\uB2E4. \uC88B\uC2B5\uB2C8\uAE4C\uFF1F"),
- new TextPatch(0x3EDDF0, 0x25, "\uD6A8\uACFC \uC885\uB8CC\uB97C \uAE30\uB2E4\uB824\uC57C \uD569\uB2C8\uB2E4."),
- new TextPatch(0x3EF438, 0x5, "\uC0AC\uC57C"),
- new TextPatch(0x3EF440, 0x5, "\uC544\uC57C"),
- new TextPatch(0x3EF448, 0x7, "\uC8FC\uC778\uACF5"),
- new TextPatch(0x3EFE70, 0xA, "name/\uC0AC\uC57C"),
- new TextPatch(0x3EFE7C, 0xA, "name/\uC544\uC57C"),
- new TextPatch(0x3EFE88, 0x9, "\uC548\uB0AE\uCDA4"),
- new TextPatch(0x3EFE94, 0x7, "\uB0AE\uCDA4"),
- new TextPatch(0x3EFEA4, 0xD, "\uB9CE\uC774\uB0AE\uCDA4"),
- new TextPatch(0x3EFDC0, 0x1F, "\uACE0\uC815 \uB4DC\uB77C\uC774\uBE0C\uB9CC \uAC00\uB2A5\uD569\uB2C8\uB2E4."),
- new TextPatch(0x3EFDE0, 0x17, "\uC2DC\uC2A4\uD15C \uD3F4\uB354\uC785\uB2C8\uB2E4."),
- new TextPatch(0x3EFDF8, 0x19, "\uC77D\uAE30 \uC804\uC6A9 \uD3F4\uB354\uC785\uB2C8\uB2E4."),
- new TextPatch(0x3EFE14, 0x23, "\uC720\uD6A8\uD55C \uD3F4\uB354\uB97C \uC120\uD0DD\uD558\uC138\uC694."),
- new TextPatch(0x3EFE38, 0x1F, "\uD3F4\uB354 \uC18D\uC131\uC744 \uAC00\uC838\uC62C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."),
- new TextPatch(0x3EFF20, 0x11, "\uCD5C\uACE0\uD654\uC9C8"),
- new TextPatch(0x3EFF3C, 0x9, "\uACE0\uD654\uC9C8"),
- new TextPatch(0x3EFF50, 0xD, "\uD45C\uC900"),
- new TextPatch(0x3EFF6C, 0xB, "\uC808\uC57D"),
- new TextPatch(0x3EFFA0, 0x33, "\uC138\uC774\uBE0C \uB370\uC774\uD130\uB97C \uC800\uC7A5\uD560 \uD3F4\uB354\uB97C \uC9C0\uC815\uD558\uC138\uC694."),
- new TextPatch(0x3EFFD8, 0x6D, "\uC138\uC774\uBE0C \uC800\uC7A5 \uD3F4\uB354\uAC00 \uC798\uBABB\uB418\uC5C8\uAC70\uB098 \uC5C6\uC2B5\uB2C8\uB2E4.\n\uC720\uD6A8\uD55C \uD3F4\uB354\uB97C \uC9C0\uC815\uD558\uC138\uC694.\n\t%s"),
- new TextPatch(0x3F0050, 0x5, "\uB192\uC74C"),
- new TextPatch(0x3F005C, 0x5, "\uB192\uAC8C"),
- new TextPatch(0x3F006C, 0x5, "\uB0AE\uAC8C"),
- new TextPatch(0x3F0078, 0x5, "\uB0AE\uC74C"),
- new TextPatch(0x3F00A0, 0x9, "\uC74C\uC18C\uAC70"),
- new TextPatch(0x3F00B8, 0xD, "\uC790\uB3D9\uC9C4\uD589"),
- new TextPatch(0x3F00C8, 0x17, "\uB2E4\uC74C \uC120\uD0DD\uC9C0\uAE4C\uC9C0 \uC2A4\uD0B5"),
- new TextPatch(0x3F00E0, 0xE, "\uC708\uB3C4\uC6B0 \uC18C\uAC70"),
- new TextPatch(0x3F00FC, 0x11, "\uC2DC\uC2A4\uD15C \uBA54\uB274"),
- new TextPatch(0x3F011C, 0x13, "\uBA54\uC2DC\uC9C0 \uC2A4\uD0B5"),
- new TextPatch(0x3F0F60, 0x1A, "%-02d\uC6D4%02d\uC77C%02d\uC2DC%02d\uBD84"),
- new TextPatch(0x3F0140, 0xB, "\uC228\uAE30\uC9C0 \uC54A\uC74C"),
- new TextPatch(0x3F014C, 0x5, "10\uCD08"),
- new TextPatch(0x3F015C, 0x4, "5\uCD08"),
- new TextPatch(0x3F0168, 0x4, "2\uCD08"),
- new TextPatch(0x3F0238, 0x5, "\uC5C6\uC74C"),
- new TextPatch(0x3F0248, 0x5, "\uBCF4\uD1B5"),
- new TextPatch(0x3F0258, 0x5, "\uBE60\uB984"),
- new TextPatch(0x3F0278, 0xF, "\uC804\uCCB4\uD654\uBA74"),
- new TextPatch(0x3F0290, 0x11, "\uCC3D \uBAA8\uB4DC"),
- new TextPatch(0x3F02BC, 0x4, "\uC124\uC815"),
- new TextPatch(0x3F02C4, 0x8, "\uC0AC\uC6B4\uB4DC"),
- new TextPatch(0x3F02D0, 0x8, "\uC2DC\uC2A4\uD15C"),
- new TextPatch(0x3F02DC, 0x4, "\uC870\uC791"),
- new TextPatch(0x3F02E4, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F1260, 0x16, "\uC138\uC774\uBE0C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4."),
- new TextPatch(0x3F1278, 0x10, "\uC138\uC774\uBE0C\uD588\uC2B5\uB2C8\uB2E4."),
- new TextPatch(0x3F128C, 0x1C, "\uC138\uC774\uBE0C\uD574\uB3C4 \uC88B\uC2B5\uB2C8\uAE4C\uFF1F"),
- new TextPatch(0x3F12AC, 0x22, "\uB367\uC4F0\uAE30 \uC138\uC774\uBE0C\uD574\uB3C4 \uC88B\uC2B5\uB2C8\uAE4C\uFF1F"),
- new TextPatch(0x3F2E5C, 0x19, "\uC0AC\uC6A9 \uBD88\uAC00 \uC774\uB984"),
- new TextPatch(0x3F2E78, 0x15, "\uC0AC\uC6A9 \uBD88\uAC00 \uC774\uB984"),
- new TextPatch(0x3F2E90, 0x2D, "\uC774\uB984\uC5D0 \uC0AC\uC6A9 \uBD88\uAC00\uD55C \uAE30\uD638\n \uFF1C,\uFF1E,\uFF20,\uFF1D"),
- new TextPatch(0x3F2EC0, 0x15, "\uC0AC\uC6A9 \uBD88\uAC00 \uAE30\uD638"),
- new TextPatch(0x3F2ED8, 0x21, "\uC774\uB984\uC740 \uC804\uAC01\uBB38\uC790\uB85C \uC785\uB825\uD574 \uC8FC\uC138\uC694"),
- new TextPatch(0x3F2EFC, 0x19, "\uBC18\uAC01\uBB38\uC790\uAC00 \uD3EC\uD568\uB418\uC5C8\uC2B5\uB2C8\uB2E4"),
- new TextPatch(0x3F2F2C, 0x5, "\uBAA8\uB9AC"),
- new TextPatch(0x3F2F34, 0x5, "\uCE20\uB8E8"),
- new TextPatch(0x3F2F3C, 0x5, "\uD788\uCE74"),
- new TextPatch(0x3F2F44, 0x7, "\uBB18\uCF54\uC9C0"),
- new TextPatch(0x3F2F4C, 0x5, "\uC0AC\uCFE0"),
- new TextPatch(0x3F2F54, 0x7, "\uD558\uB8E8\uCE74"),
- new TextPatch(0x3F2F5C, 0x7, "\uCF00\uC774\uD1A0"),
- new TextPatch(0x3F2F64, 0x5, "\uC544\uC57C"),
- new TextPatch(0x3F2F6C, 0x7, "\uCE20\uBC14\uC0AC"),
- new TextPatch(0x3F2F74, 0x7, "\uB9C8\uB9B0"),
- new TextPatch(0x3F2F7C, 0x7, "\uC57C\uCE58\uC694"),
- new TextPatch(0x3F2F84, 0x7, "\uBBF8\uB098\uD1A0"),
- new TextPatch(0x3F2F8C, 0x7, "\uBBF8\uB77C\uC774"),
- new TextPatch(0x3F2F94, 0x21, "\uC774\uB984\uC740 %d\uC790 \uC774\uB0B4\uB85C \uC785\uB825\uD558\uC138\uC694"),
- new TextPatch(0x3F2FB8, 0x19, "\uBB38\uC790 \uC218 \uC81C\uD55C\uC744 \uB118\uC5C8\uC2B5\uB2C8\uB2E4"),
- new TextPatch(0x3F2FD4, 0x21, "\uC131\uC740 %d\uC790 \uC774\uB0B4\uB85C \uC785\uB825\uD558\uC138\uC694"),
- new TextPatch(0x3F2FF8, 0x17, "\uC774\uB984\uC744 \uC785\uB825\uD574 \uC8FC\uC138\uC694"),
- new TextPatch(0x3F34E8, 0x41, "\uD604\uC7AC\uC758 \uAC8C\uC784\uC744 \uC911\uB2E8\uD574 \uC0C8\uB86D\uAC8C \uAC8C\uC784\uC744 \uAC1C\uC2DC\uD569\uB2C8\uB2E4.\n\uC88B\uC2B5\uB2C8\uAE4C\uFF1F"),
- new TextPatch(0x3F3530, 0x2E, "\uD604\uC7AC\uC758 \uAC8C\uC784\uC744 \uC911\uB2E8\uD574 \uD0C0\uC774\uD2C0 \uD654\uBA74\uC5D0 \uB3CC\uC544\uC635\uB2C8\uB2E4."),
- new TextPatch(0x3F355F, 0x10, "\uC88B\uC2B5\uB2C8\uAE4C?"),
- new TextPatch(0x3F3570, 0x12, "\uD68C\uC0C1\uC744 \uC885\uB8CC\uD569\uB2C8\uB2E4."),
- new TextPatch(0x3F3583, 0x10, "\uC88B\uC2B5\uB2C8\uAE4C?"),
- new TextPatch(0x3F3638, 0x6, "\uB2EB\uAE30"),
- new TextPatch(0x3F3640, 0xA, "\uAC8C\uC784 \uC885\uB8CC"),
- new TextPatch(0x3F364C, 0x6, "\uB85C\uB4DC"),
- new TextPatch(0x3F3654, 0x6, "\uC138\uC774\uBE0C"),
- new TextPatch(0x3F365C, 0xC, "\uC2A4\uD0B5 \uC2DC\uC791"),
- new TextPatch(0x3F366C, 0xE, "\uC2E0 \uC7AC\uC0DD \uC911\uB2E8"),
- new TextPatch(0x3F367C, 0xA, "\uAC8C\uC784 \uC911\uB2E8"),
- new TextPatch(0x3F376C, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F3775, 0x21, "\uD654\uBA74 \uBAA8\uB4DC(&M)\\\uC804\uCCB4 \uD654\uBA74(&F)"),
- new TextPatch(0x3F3798, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F37A1, 0x1D, "\uD654\uBA74 \uBAA8\uB4DC(&M)\\\uCC3D \uBAA8\uB4DC(&W)"),
- new TextPatch(0x3F37C0, 0x1F, "\uAC8C\uC784(&G)\\\uD14D\uC2A4\uD2B8 \uB85C\uADF8(&L)"),
- new TextPatch(0x3F37F8, 0x1F, "\uAC8C\uC784(&G)\\\uC2A4\uD0B5 \uC2DC\uC791(&S)\tTab"),
- new TextPatch(0x3F3818, 0x1D, "\uAC8C\uC784(&G)\\\uC790\uB3D9 \uC9C4\uD589(&A)\tP"),
- new TextPatch(0x3F3838, 0x6, "\uB3C4\uC6C0\uB9D0"),
- new TextPatch(0x3F3843, 0x10, "\uC81C\uD488 \uC0AC\uC774\uD2B8(&S)"),
- new TextPatch(0x3F3854, 0x6, "\uB3C4\uC6C0\uB9D0"),
- new TextPatch(0x3F385F, 0x15, "\uBC84\uC804 \uC815\uBCF4(&A)..."),
- new TextPatch(0x3F3878, 0x19, "\uC0AC\uC6B4\uB4DC(&S)\\\uC74C\uB7C9 \uC870\uC808(&C)."),
- new TextPatch(0x3F3898, 0x11, "\uC0AC\uC6B4\uB4DC(&S)\\\uC74C\uC18C\uAC70"),
- new TextPatch(0x3F38B0, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F38B9, 0x17, "\uD654\uBA74 \uBAA8\uB4DC(&M)\\1:1"),
- new TextPatch(0x3F38D4, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F38DD, 0x20, "\uD14D\uC2A4\uD2B8\uCC3D \uC228\uAE40(&H)\tSPACE"),
- new TextPatch(0x3F3900, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F3909, 0xE, "\uD654\uBA74 \uBAA8\uB4DC(&M)"),
- new TextPatch(0x3F3918, 0x29, "\uAC8C\uC784(&G)\\\uCD5C\uADFC \uC790\uB3D9\uC800\uC7A5(&B)"),
- new TextPatch(0x3F3944, 0xA, "\uAC8C\uC784(&G)"),
- new TextPatch(0x3F3950, 0x15, "\uD30C\uC77C(&F)\\\uC885\uB8CC(&X)"),
- new TextPatch(0x3F3968, 0x31, "\uD30C\uC77C(&F)\\\uC2A4\uD06C\uB9B0\uC0F7(&P)\tPrintScreen"),
- new TextPatch(0x3F399C, 0x18, "\uD30C\uC77C(&F)\\\uC124\uC815(&C)..."),
- new TextPatch(0x3F39B8, 0x1F, "\uD30C\uC77C(&F)\\\uD035 \uB85C\uB4DC(&O)"),
- new TextPatch(0x3F39D8, 0x1F, "\uD30C\uC77C(&F)\\\uD035 \uC138\uC774\uBE0C(&Q)"),
- new TextPatch(0x3F39F8, 0x1A, "\uD30C\uC77C(&F)\\\uB85C\uB4DC(&L)..."),
- new TextPatch(0x3F3A14, 0x1A, "\uD30C\uC77C(&F)\\\uC138\uC774\uBE0C(&S)..."),
- new TextPatch(0x3F3A30, 0x1B, "\uD30C\uC77C(&F)\\\uAC8C\uC784 \uC911\uB2E8(&B)"),
- new TextPatch(0x3F3A4C, 0x1B, "\uD30C\uC77C(&F)\\\uC0C8 \uAC8C\uC784(&N)"),
- new TextPatch(0x3F3C08, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F3C1C, 0x4, "\uD45C\uC2DC"),
- new TextPatch(0x3F3C25, 0xF, "\uD654\uBA74 \uBAA8\uB4DC(&M)\\-"),
- new TextPatch(0x3F3C40, 0xB, "\uAC8C\uC784(&G)\\--"),
- new TextPatch(0x3F3C54, 0xD, "\uD30C\uC77C(&F)\\----"),
- new TextPatch(0x3F3A68, 0x29, "<\uCD5C\uC2E0 \uC790\uB3D9\uC800\uC7A5(&B)> [\uB370\uC774\uD130 \uC5C6\uC74C]"),
- new TextPatch(0x3F3A94, 0x3C, "<\uCD5C\uC2E0 \uC790\uB3D9\uC800\uC7A5(&B)> [%04d/%02d/%02d %02d:%02d:%02d]"),
- new TextPatch(0x3F3AD0, 0x25, "<\uC911\uB2E8\uD55C \uAC8C\uC784(&A)> [\uB370\uC774\uD130 \uC5C6\uC74C]"),
- new TextPatch(0x3F3AF8, 0x38, "<\uC911\uB2E8\uD55C \uAC8C\uC784(&A)> [%04d/%02d/%02d %02d:%02d:%02d]"),
- new TextPatch(0x3F3B60, 0x15, "no.%2d [\uC0AC\uC6A9 \uBD88\uAC00]"),
- new TextPatch(0x3F3BD4, 0x17, "no.%2d [\uB370\uC774\uD130 \uC5C6\uC74C]"),
- new TextPatch(0x3F3BEC, 0x1A, "no.%2d [\uB370\uC774\uD130 \uC5C6\uC74C]\tF1"),
- new TextPatch(0x3F4020, 0x17, "\uC790\uB3D9\uC800\uC7A5\uD588\uC2B5\uB2C8\uB2E4."),
- new TextPatch(0x3F40A0, 0x9, "\uC785\uB2C8\uB2E4."),
- new TextPatch(0x3F40B0, 0x30, "\uC8FC\uC778\uACF5 \uC774\uB984\uC744 \uACB0\uC815\uD574 \uC8FC\uC138\uC694.\n\uD604\uC7AC \uC774\uB984\uC740 \u300C"),
- new TextPatch(0x3F4214, 0x1F, "\uB9AC\uC18C\uC2A4 \uCC98\uB9AC \uC624\uB958(\uC0DD\uC131\uC2DC)"),
- new TextPatch(0x3F4234, 0x17, "\uB9AC\uC18C\uC2A4 \uB204\uC218 \uC9C0\uC810"),
- new TextPatch(0x3F424C, 0x1F, "\uB9AC\uC18C\uC2A4 \uCC98\uB9AC \uC624\uB958(\uD574\uC81C\uC2DC)"),
- new TextPatch(0x3F426C, 0x5, "\uC624\uB958"),
- new TextPatch(0x3F4308, 0x1D, "Module %s \uB85C\uB4DC \uC2E4\uD328"),
- new TextPatch(0x3F4520, 0x1A, "\uC885\uB8CC\uD574\uB3C4 \uC88B\uC2B5\uB2C8\uAE4C\uFF1F"),
- new TextPatch(0x3F456C, 0xF, "\uB098\uB214\uACE0\uB515"),
- new TextPatch(0x3F457C, 0x2C, "\uD544\uC694 \uC778\uD130\uD398\uC774\uC2A4 \uD68D\uB4DD \uC2E4\uD328(2)"),
- new TextPatch(0x3F491C, 0x15, "\uC2DC\uC2A4\uD15C\uC744 \uC885\uB8CC\uD569\uB2C8\uB2E4"),
- new TextPatch(0x3F4934, 0xF, "\uC2DC\uC2A4\uD15C \uC624\uB958"),
- new TextPatch(0x3F4950, 0x7, "\uC624\uB958"),
- // 제품명 "姪少女"는 저장 폴더명과 About 문구에 쓰이므로 일본어 원문을 유지한다.
- new TextPatch(0x577B53, 0x8, "\u59EA\u5C11\u5973"),
- new TextPatch(0x577B5C, 0x9, "\u59EA\u5C11\u5973\n"),
- new TextPatch(0x577B66, 0xD, "\u59EA\u5C11\u5973 \uC815\uBCF4"),
- };
- // 설정 다이얼로그 리소스는 UTF-16LE 문자열을 사용한다.
- // 여기의 유니코드 이스케이프는 WriteWideTextPatch에서 UTF-16LE 바이트로 바뀐다.
- // 구조체 길이를 바꾸면 컨트롤 배치가 깨지므로 기존 슬롯 크기 안에서만 바꾼다.
- private static readonly WideTextPatch[] WideTextPatches =
- {
- new WideTextPatch(0x53EAAE, 0x6, "\uD45C\uC2DC"),
- new WideTextPatch(0x53FAA6, 0x6, "\uD45C\uC2DC"),
- new WideTextPatch(0x53EE3E, 0x6, "\uC870\uC791"),
- new WideTextPatch(0x53FE36, 0x6, "\uC870\uC791"),
- new WideTextPatch(0x53F746, 0xA, "\uC2DC\uC2A4\uD15C"),
- new WideTextPatch(0x54073E, 0xA, "\uC2DC\uC2A4\uD15C"),
- new WideTextPatch(0x53F1FE, 0xA, "\uC0AC\uC6B4\uB4DC"),
- new WideTextPatch(0x5401F6, 0xA, "\uC0AC\uC6B4\uB4DC"),
- new WideTextPatch(0x53EAF0, 0xA, "\uD654\uBA74\uD45C\uC2DC"),
- new WideTextPatch(0x53FAE8, 0xA, "\uD654\uBA74\uD45C\uC2DC"),
- new WideTextPatch(0x53EB18, 0x14, "\uD654\uBA74\uBAA8\uB4DC:"),
- new WideTextPatch(0x53FB10, 0x14, "\uD654\uBA74\uBAA8\uB4DC:"),
- new WideTextPatch(0x53EB6C, 0xA, "\uD45C\uC2DC\uC18D\uB3C4"),
- new WideTextPatch(0x53FB64, 0xA, "\uD45C\uC2DC\uC18D\uB3C4"),
- new WideTextPatch(0x53EB94, 0xC, "\uD654\uBA74\uC5F0\uCD9C:"),
- new WideTextPatch(0x53FB8C, 0xC, "\uD654\uBA74\uC5F0\uCD9C:"),
- new WideTextPatch(0x53EBE0, 0xC, "\uBB38\uC790\uD45C\uC2DC:"),
- new WideTextPatch(0x53FBD8, 0xC, "\uBB38\uC790\uD45C\uC2DC:"),
- new WideTextPatch(0x53EC2C, 0x18, "\uD14D\uC2A4\uD2B8 / \uD3F0\uD2B8"),
- new WideTextPatch(0x53FC24, 0x18, "\uD14D\uC2A4\uD2B8 / \uD3F0\uD2B8"),
- new WideTextPatch(0x53EC64, 0x1A, "\uD14D\uC2A4\uD2B8\uCC3D\uB18D\uB3C4:"),
- new WideTextPatch(0x53FC5C, 0x1A, "\uD14D\uC2A4\uD2B8\uCC3D\uB18D\uB3C4:"),
- new WideTextPatch(0x53EC9C, 0x6, "\uC605\uAC8C"),
- new WideTextPatch(0x53FC94, 0x6, "\uC605\uAC8C"),
- new WideTextPatch(0x53ED00, 0x6, "\uC9C4\uD568"),
- new WideTextPatch(0x53FCF8, 0x6, "\uC9C4\uD568"),
- new WideTextPatch(0x53ED24, 0x1A, "\uBCF8\uBB38\uAE00\uAF34:"),
- new WideTextPatch(0x53FD1C, 0x1A, "\uBCF8\uBB38\uAE00\uAF34:"),
- new WideTextPatch(0x53ED7C, 0x14, "\uAE00\uC790\uD06C\uAE30\uC870\uC815:"),
- new WideTextPatch(0x53FD74, 0x14, "\uAE00\uC790\uD06C\uAE30\uC870\uC815:"),
- new WideTextPatch(0x53EE80, 0xE, "\uC2DC\uB098\uB9AC\uC624\uC870\uC791"),
- new WideTextPatch(0x53FE78, 0xE, "\uC2DC\uB098\uB9AC\uC624\uC870\uC791"),
- new WideTextPatch(0x53EEAC, 0x20, "\uC77D\uC9C0\uC54A\uC740\uAE00\uC2A4\uD0B5\uC548\uD568"),
- new WideTextPatch(0x53FEA4, 0x20, "\uC77D\uC9C0\uC54A\uC740\uAE00\uC2A4\uD0B5\uC548\uD568"),
- new WideTextPatch(0x53EEEC, 0x20, "\uC624\uD1A0\uAC1C\uD589\uB300\uAE30\uC2DC\uAC04:"),
- new WideTextPatch(0x53FEE4, 0x20, "\uC624\uD1A0\uAC1C\uD589\uB300\uAE30\uC2DC\uAC04:"),
- new WideTextPatch(0x53EF2C, 0x6, "\uC9E7\uAC8C"),
- new WideTextPatch(0x53FF24, 0x6, "\uC9E7\uAC8C"),
- new WideTextPatch(0x53EF90, 0x6, "\uAE38\uAC8C"),
- new WideTextPatch(0x53FF88, 0x6, "\uAE38\uAC8C"),
- new WideTextPatch(0x53EFB4, 0xE, "\uC2DC\uB098\uB9AC\uC624\uB85C\uADF8"),
- new WideTextPatch(0x53FFAC, 0xE, "\uC2DC\uB098\uB9AC\uC624\uB85C\uADF8"),
- new WideTextPatch(0x53EFE0, 0x10, "\uC774\uBBF8\uC9C0\uB3C4\uC7AC\uD604"),
- new WideTextPatch(0x53FFD8, 0x10, "\uC774\uBBF8\uC9C0\uB3C4\uC7AC\uD604"),
- new WideTextPatch(0x53F010, 0x2C, "\uC74C\uC131\uD398\uC774\uC9C0\uC790\uB3D9\uC7AC\uC0DD"),
- new WideTextPatch(0x540008, 0x2C, "\uC74C\uC131\uD398\uC774\uC9C0\uC790\uB3D9\uC7AC\uC0DD"),
- new WideTextPatch(0x53F05C, 0x8, "\uB9C8\uC6B0\uC2A4"),
- new WideTextPatch(0x540054, 0x8, "\uB9C8\uC6B0\uC2A4"),
- new WideTextPatch(0x53F084, 0x3C, "\uD3EC\uCEE4\uC2A4\uBC84\uD2BC\uC73C\uB85C\uCEE4\uC11C\uC790\uB3D9\uC774\uB3D9"),
- new WideTextPatch(0x54007C, 0x3C, "\uD3EC\uCEE4\uC2A4\uBC84\uD2BC\uC73C\uB85C\uCEE4\uC11C\uC790\uB3D9\uC774\uB3D9"),
- new WideTextPatch(0x53F0E0, 0x20, "\uCEE4\uC11C\uC790\uB3D9\uC228\uAE40\uC2DC\uAC04:"),
- new WideTextPatch(0x5400D8, 0x20, "\uCEE4\uC11C\uC790\uB3D9\uC228\uAE40\uC2DC\uAC04:"),
- new WideTextPatch(0x53F140, 0x1E, "\uC624\uB978\uCABD\uBC84\uD2BC\uC124\uC815:"),
- new WideTextPatch(0x540138, 0x1E, "\uC624\uB978\uCABD\uBC84\uD2BC\uC124\uC815:"),
- new WideTextPatch(0x53F19C, 0x1E, "\uAC00\uC6B4\uB370\uBC84\uD2BC\uC124\uC815:"),
- new WideTextPatch(0x540194, 0x1E, "\uAC00\uC6B4\uB370\uBC84\uD2BC\uC124\uC815:"),
- new WideTextPatch(0x53F244, 0xA, "\uC74C\uB7C9\uC870\uC808"),
- new WideTextPatch(0x54023C, 0xA, "\uC74C\uB7C9\uC870\uC808"),
- new WideTextPatch(0x53F26C, 0x14, "\uB9C8\uC2A4\uD130\uBCFC\uB968:"),
- new WideTextPatch(0x540264, 0x14, "\uB9C8\uC2A4\uD130\uBCFC\uB968:"),
- new WideTextPatch(0x53F328, 0x12, "\uC74C\uC545\uBCFC\uB968:"),
- new WideTextPatch(0x540320, 0x12, "\uC74C\uC545\uBCFC\uB968:"),
- new WideTextPatch(0x53F3E0, 0x14, "\uD6A8\uACFC\uC74C\uBCFC\uB968:"),
- new WideTextPatch(0x5403D8, 0x14, "\uD6A8\uACFC\uC74C\uBCFC\uB968:"),
- new WideTextPatch(0x53F49C, 0x12, "\uC74C\uC131\uBCFC\uB968:"),
- new WideTextPatch(0x540494, 0x12, "\uC74C\uC131\uBCFC\uB968:"),
- new WideTextPatch(0x53F554, 0x22, "\uC74C\uC131\uC7AC\uC0DD\uC2DC\uC74C\uC545\uB0AE\uCDA4:"),
- new WideTextPatch(0x54054C, 0x22, "\uC74C\uC131\uC7AC\uC0DD\uC2DC\uC74C\uC545\uB0AE\uCDA4:"),
- new WideTextPatch(0x53F5B4, 0x12, "\uCE90\uB9AD\uD130\uBCC4\uC74C\uC131\uC870\uC808"),
- new WideTextPatch(0x5405AC, 0x12, "\uCE90\uB9AD\uD130\uBCC4\uC74C\uC131\uC870\uC808"),
- new WideTextPatch(0x53F5E4, 0x8, "\uC544\uC57C:"),
- new WideTextPatch(0x5405DC, 0x8, "\uC544\uC57C:"),
- new WideTextPatch(0x53F694, 0x8, "\uC0AC\uC57C:"),
- new WideTextPatch(0x54068C, 0x8, "\uC0AC\uC57C:"),
- new WideTextPatch(0x53F78C, 0x10, "\uC131\uB2A5"),
- new WideTextPatch(0x540784, 0x10, "\uC131\uB2A5"),
- new WideTextPatch(0x53F7BC, 0x10, "CPU\uC0AC\uC6A9\uB960:"),
- new WideTextPatch(0x5407B4, 0x10, "CPU\uC0AC\uC6A9\uB960:"),
- new WideTextPatch(0x53F80C, 0x8, "\uC138\uC774\uBE0C"),
- new WideTextPatch(0x540804, 0x8, "\uC138\uC774\uBE0C"),
- new WideTextPatch(0x53F834, 0x16, "\uC138\uC774\uBE0C\uC800\uC7A5\uC704\uCE58:"),
- new WideTextPatch(0x54082C, 0x16, "\uC138\uC774\uBE0C\uC800\uC7A5\uC704\uCE58:"),
- new WideTextPatch(0x53F868, 0x12, "\uB0B4\uBB38\uC11C"),
- new WideTextPatch(0x540860, 0x12, "\uB0B4\uBB38\uC11C"),
- new WideTextPatch(0x53F898, 0x1C, "[\uC800\uC7A5\uAC8C\uC784]\uD3F4\uB354"),
- new WideTextPatch(0x540890, 0x1C, "[\uC800\uC7A5\uAC8C\uC784]\uD3F4\uB354"),
- new WideTextPatch(0x53F8D4, 0x14, "\uD3F4\uB354\uC9C0\uC815"),
- new WideTextPatch(0x5408CC, 0x14, "\uD3F4\uB354\uC9C0\uC815"),
- new WideTextPatch(0x53F908, 0xC, "\uCC3E\uAE30..."),
- new WideTextPatch(0x540900, 0xC, "\uCC3E\uAE30..."),
- new WideTextPatch(0x53F954, 0x1E, "\uC138\uC774\uBE0C\uD3F4\uB354\uC5F4\uAE30"),
- new WideTextPatch(0x54094C, 0x1E, "\uC138\uC774\uBE0C\uD3F4\uB354\uC5F4\uAE30"),
- new WideTextPatch(0x53F990, 0x16, "\uC2A4\uD06C\uB9B0\uC0F7"),
- new WideTextPatch(0x540988, 0x16, "\uC2A4\uD06C\uB9B0\uC0F7"),
- new WideTextPatch(0x53F9C4, 0xC, "\uC800\uC7A5\uD488\uC9C8:"),
- new WideTextPatch(0x5409BC, 0xC, "\uC800\uC7A5\uD488\uC9C8:"),
- new WideTextPatch(0x53FA10, 0x14, "\uC800\uC7A5\uD3F4\uB354\uC5F4\uAE30"),
- new WideTextPatch(0x540A08, 0x14, "\uC800\uC7A5\uD3F4\uB354\uC5F4\uAE30"),
- new WideTextPatch(0x53FA44, 0x40, "\u203B\uC800\uC7A5\uC704\uCE58\uB97C\uBC14\uAFC8\uB3C4\uC138\uC774\uBE0C\uB294\uC790\uB3D9\uC774\uB3D9\uB418\uC9C0\uC54A\uC2B5\uB2C8\uB2E4."),
- new WideTextPatch(0x540A3C, 0x40, "\u203B\uC800\uC7A5\uC704\uCE58\uB97C\uBC14\uAFC8\uB3C4\uC138\uC774\uBE0C\uB294\uC790\uB3D9\uC774\uB3D9\uB418\uC9C0\uC54A\uC2B5\uB2C8\uB2E4."),
- };
- // 문자열 포인터/구분자처럼 텍스트 인코딩으로 표현하기 어려운 보조 바이트 패치.
- private static readonly HexPatch[] HexPatches =
- {
- new HexPatch(0x1641, "08"),
- new HexPatch(0x1643, "538797"),
- new HexPatch(0x1651, "08"),
- new HexPatch(0x1653, "538797"),
- new HexPatch(0x6A02, "538797"),
- new HexPatch(0x4CD26, "09"),
- new HexPatch(0x4CD28, "5c8797"),
- new HexPatch(0x4CD5B, "668797"),
- new HexPatch(0x3F3893, "00"),
- new HexPatch(0x3F3C3B, "00"),
- new HexPatch(0x3F3C51, "0000"),
- new HexPatch(0x3F3C65, "00000000"),
- };
- // 리소스 패치만으로 처리되지 않는 Win32 컨트롤 텍스트는 생성 직후 SetWindowTextW로 번역한다.
- // 왼쪽 유니코드 문자열은 원본 일본어, 오른쪽 유니코드 문자열은 표시할 한국어다.
- private static readonly TextReplacement[] DialogTextReplacements =
- {
- new TextReplacement("\u8A2D\u5B9A", "\uC124\uC815"),
- new TextReplacement("\u8868\u793A", "\uD45C\uC2DC"),
- new TextReplacement("\u64CD\u4F5C", "\uC870\uC791"),
- new TextReplacement("\u30B7\u30B9\u30C6\u30E0", "\uC2DC\uC2A4\uD15C"),
- new TextReplacement("\u30B5\u30A6\u30F3\u30C9", "\uC0AC\uC6B4\uB4DC"),
- new TextReplacement("\u30B7\u30CA\u30EA\u30AA\u64CD\u4F5C", "\uC2DC\uB098\uB9AC\uC624 \uC870\uC791"),
- new TextReplacement("\u672A\u8AAD\u306E\u30C6\u30AD\u30B9\u30C8\u306F\u30B9\u30AD\u30C3\u30D7\u3057\u306A\u3044", "\uC77D\uC9C0 \uC54A\uC740 \uD14D\uC2A4\uD2B8\uB294 \uC2A4\uD0B5\uD558\uC9C0 \uC54A\uAE30"),
- new TextReplacement("\u30AA\u30FC\u30C8\u30D7\u30EC\u30A4\u6642\u306E\u6539\u884C\u5F85\u3061", "\uC624\uD1A0 \uD50C\uB808\uC774 \uC2DC \uAC1C\uD589 \uB300\uAE30"),
- new TextReplacement("\u77ED\u3044", "\uC9E7\uAC8C"),
- new TextReplacement("\u9577\u3044", "\uAE38\uAC8C"),
- new TextReplacement("\u30B7\u30CA\u30EA\u30AA\u30ED\u30B0", "\uC2DC\uB098\uB9AC\uC624 \uB85C\uADF8"),
- new TextReplacement("\u753B\u50CF\u3082\u518D\u73FE\u3059\u308B", "\uC774\uBBF8\uC9C0\uB3C4 \uC7AC\uD604"),
- new TextReplacement("\u97F3\u58F0\u306E\u3042\u308B\u30DA\u30FC\u30B8\u3067\u306F\u97F3\u58F0\u3092\u81EA\u52D5\u7684\u306B\u518D\u751F\u3059\u308B", "\uC74C\uC131\uC774 \uC788\uB294 \uD398\uC774\uC9C0\uC5D0\uC11C \uC74C\uC131 \uC790\uB3D9 \uC7AC\uC0DD"),
- new TextReplacement("\u30DE\u30A6\u30B9", "\uB9C8\uC6B0\uC2A4"),
- new TextReplacement("\u30D5\u30A9\u30FC\u30AB\u30B9\u306E\u5408\u3063\u305F\u30DC\u30BF\u30F3\u306B\u30DE\u30A6\u30B9\u30AB\u30FC\u30BD\u30EB\u3092\u81EA\u52D5\u7684\u306B\u5408\u308F\u305B\u308B", "\uD3EC\uCEE4\uC2A4\uB41C \uBC84\uD2BC\uC73C\uB85C \uB9C8\uC6B0\uC2A4 \uCEE4\uC11C \uC790\uB3D9 \uC774\uB3D9"),
- new TextReplacement("\u30D5\u30A9\u30FC\u30AB\u30B9\u306E\u5408\u3063\u305F\u30DC\u30BF\u30F3\u306B\u30DE\u30A6\u30B9\u30AB\u30FC\u30BD\u30EB\u3092\u81EA\u52D5\u7684\u306B", "\uD3EC\uCEE4\uC2A4\uB41C \uBC84\uD2BC\uC73C\uB85C \uB9C8\uC6B0\uC2A4 \uCEE4\uC11C \uC790\uB3D9 \uC774\uB3D9"),
- new TextReplacement("\u30DE\u30A6\u30B9\u30AB\u30FC\u30BD\u30EB\u306E\u81EA\u52D5\u6D88\u53BB", "\uB9C8\uC6B0\uC2A4 \uCEE4\uC11C \uC790\uB3D9 \uC228\uAE40"),
- new TextReplacement("\u30DE\u30A6\u30B9\u30AB\u30FC\u30BD\u30EB\u306E\u81EA\u52D5\u6D88\u53BB\u6642", "\uB9C8\uC6B0\uC2A4 \uCEE4\uC11C \uC790\uB3D9 \uC228\uAE40 \uC2DC\uAC04"),
- new TextReplacement("\u30DE\u30A6\u30B9\u53F3\u30DC\u30BF\u30F3\u30AB\u30B9\u30BF\u30DE\u30A4\u30BA", "\uB9C8\uC6B0\uC2A4 \uC624\uB978\uCABD \uBC84\uD2BC"),
- new TextReplacement("\u30DE\u30A6\u30B9\u53F3\u30DC\u30BF\u30F3\u30AB\u30B9\u30BF", "\uB9C8\uC6B0\uC2A4 \uC624\uB978\uCABD \uBC84\uD2BC"),
- new TextReplacement("\u30DE\u30A6\u30B9\u4E2D\u30DC\u30BF\u30F3\u30AB\u30B9\u30BF\u30DE\u30A4\u30BA", "\uB9C8\uC6B0\uC2A4 \uAC00\uC6B4\uB370 \uBC84\uD2BC"),
- new TextReplacement("\u30DE\u30A6\u30B9\u4E2D\u30DC\u30BF\u30F3\u30AB\u30B9\u30BF", "\uB9C8\uC6B0\uC2A4 \uAC00\uC6B4\uB370 \uBC84\uD2BC"),
- new TextReplacement("5\u79D2", "5\uCD08"),
- new TextReplacement("\u5C0F", "\uC791\uAC8C"),
- new TextReplacement("\u5927", "\uD06C\uAC8C"),
- new TextReplacement("\u78BA\u5B9A", "\uD655\uC778"),
- new TextReplacement("\u30AD\u30E3\u30F3\u30BB\u30EB", "\uCDE8\uC18C"),
- new TextReplacement("\u9069\u7528", "\uC801\uC6A9"),
- };
- // 콤보박스 항목은 실행 중에 코드가 채우는 경우가 있어 리소스 패치 대상이 아니다.
- // 아래 목록은 항목 개수와 왼쪽 라벨을 함께 보고 필요한 콤보박스에만 적용한다.
- // 항목명도 Win32 Unicode 메시지로 다시 넣기 때문에 유니코드 이스케이프로 보관한다.
- private static readonly string[][] ComboBoxItemSets =
- {
- new[] { "\uC804\uCCB4\uD654\uBA74", "\uCC3D\uBAA8\uB4DC" },
- new[] { "\uC5C6\uC74C", "\uBCF4\uD1B5", "\uBE60\uB984" },
- new[] { "\uC228\uAE30\uC9C0\uC54A\uC74C", "10\uCD08", "5\uCD08", "2\uCD08" },
- new[] { "\uC74C\uC18C\uAC70", "\uC790\uB3D9\uC9C4\uD589", "\uC120\uD0DD\uC9C0\uC2A4\uD0B5", "\uC708\uB3C4\uC6B0 \uC18C\uAC70", "\uC2DC\uC2A4\uD15C \uBA54\uB274", "\uBA54\uC2DC\uC9C0 \uC2A4\uD0B5" },
- new[] { "\uB0AE\uC74C", "\uB0AE\uAC8C", "\uB192\uAC8C", "\uB192\uC74C" },
- };
- private static readonly string[] AutoHideComboItems =
- { "\uC228\uAE30\uC9C0\uC54A\uC74C", "10\uCD08", "5\uCD08", "2\uCD08" };
- // 문자표시 콤보는 원본 항목이 2개뿐이라 전용 유니코드 항목표를 사용한다.
- private static readonly string[] TextDisplayComboItems =
- { "\uC21C\uAC04", "\uBCF4\uD1B5" };
- // 저장품질 콤보의 4개 한국어 항목이다.
- private static readonly string[] SaveQualityComboItems =
- { "\uCD5C\uC800", "\uB0AE\uAC8C", "\uB192\uC74C", "\uCD5C\uACE0" };
- // CPU사용률 콤보는 원본 순서 低い/低め/通常/高め/高い/MAX에 맞춰 둔다.
- private static readonly string[] CpuUsageComboItems =
- { "\uB0AE\uC74C", "\uB0AE\uAC8C", "\uBCF4\uD1B5", "\uB192\uAC8C", "\uB192\uC74C", "MAX" };
- // 음성 재생 시 음악 낮춤 콤보의 3개 한국어 항목이다.
- private static readonly string[] VoicePlaybackBgmComboItems =
- { "\uC548 \uB0AE\uCDA4", "\uC870\uAE08 \uB0AE\uCDA4", "\uB9CE\uC774 \uB0AE\uCDA4" };
- private static readonly System.Collections.Generic.HashSet<IntPtr> TranslatedComboBoxes =
- new System.Collections.Generic.HashSet<IntPtr>();
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool CloseHandle(IntPtr hObject);
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool ReadProcessMemory(
- IntPtr hProcess,
- IntPtr lpBaseAddress,
- byte[] lpBuffer,
- IntPtr nSize,
- out IntPtr lpNumberOfBytesRead);
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool WriteProcessMemory(
- IntPtr hProcess,
- IntPtr lpBaseAddress,
- byte[] lpBuffer,
- IntPtr nSize,
- out IntPtr lpNumberOfBytesWritten);
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool VirtualProtectEx(
- IntPtr hProcess,
- IntPtr lpAddress,
- IntPtr dwSize,
- uint flNewProtect,
- out uint lpflOldProtect);
- private delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
- [DllImport("user32.dll")]
- private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
- [DllImport("user32.dll")]
- private static extern bool IsWindowVisible(IntPtr hWnd);
- [DllImport("user32.dll", SetLastError = true)]
- private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
- // 설정창 컨트롤은 Windows의 Unicode 버전 API로 읽고 쓴다.
- // ANSI API를 쓰면 일본어/한국어가 현재 시스템 코드페이지에 따라 깨질 수 있다.
- [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern bool SetWindowTextW(IntPtr hWnd, string lpString);
- private delegate bool EnumChildProc(IntPtr hwnd, IntPtr lParam);
- [DllImport("user32.dll")]
- private static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildProc lpEnumFunc, IntPtr lParam);
- [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern int GetWindowTextW(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
- [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern int GetWindowTextLengthW(IntPtr hWnd);
- [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern int GetClassNameW(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr GetParent(IntPtr hWnd);
- [DllImport("user32.dll", SetLastError = true)]
- private static extern bool GetWindowRect(IntPtr hWnd, out Rect lpRect);
- // 콤보박스 항목 추가도 SendMessageW를 통해 Unicode 문자열 그대로 전달한다.
- [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern IntPtr SendMessageW(IntPtr hWnd, int msg, IntPtr wParam, string lParam);
- [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
- private static extern IntPtr SendMessageW(IntPtr hWnd, int msg, IntPtr wParam, StringBuilder lParam);
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr SendMessageW(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);
- private delegate void WinEventProc(
- IntPtr hWinEventHook,
- uint eventType,
- IntPtr hwnd,
- int idObject,
- int idChild,
- uint dwEventThread,
- uint dwmsEventTime);
- [DllImport("user32.dll", SetLastError = true)]
- private static extern IntPtr SetWinEventHook(
- uint eventMin,
- uint eventMax,
- IntPtr hmodWinEventProc,
- WinEventProc lpfnWinEventProc,
- uint idProcess,
- uint idThread,
- uint dwFlags);
- [DllImport("user32.dll", SetLastError = true)]
- private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
- [STAThread]
- private static int Main(string[] args)
- {
- // 이 파일은 단독 패치 exe가 아니라 런처다.
- // 같은 폴더의 원본 meishoujo.exe를 실행한 뒤, 언패킹된 메모리에 패치를 적용한다.
- string launcherPath = Application.ExecutablePath;
- string root = Path.GetDirectoryName(launcherPath) ?? Environment.CurrentDirectory;
- string targetExe = Path.Combine(root, "meishoujo.exe");
- try
- {
- if (!File.Exists(targetExe))
- {
- throw new FileNotFoundException("meishoujo.exe was not found next to the launcher.", targetExe);
- }
- ProcessStartInfo startInfo = new ProcessStartInfo
- {
- FileName = targetExe,
- WorkingDirectory = root,
- Arguments = JoinArguments(args),
- UseShellExecute = false
- };
- using (Process process = Process.Start(startInfo))
- {
- if (process == null)
- {
- throw new InvalidOperationException("Failed to start meishoujo.exe.");
- }
- PatchWhenUnpacked(process);
- // 설정창 같은 Win32 다이얼로그는 게임 실행 중 계속 생성되므로,
- // 원본 프로세스가 종료될 때까지 이벤트 후킹을 유지한다.
- TranslateDialogsUntilExit(process);
- }
- return 0;
- }
- catch (Exception ex)
- {
- string message = ex.GetType().Name + ": " + ex.Message;
- MessageBox.Show(message, "meishoujo_kor patch failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
- return 1;
- }
- }
- private static string PatchWhenUnpacked(Process process)
- {
- IntPtr handle = OpenProcess(
- ProcessQueryInformation | ProcessVmOperation | ProcessVmRead | ProcessVmWrite,
- false,
- process.Id);
- if (handle == IntPtr.Zero)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "OpenProcess failed");
- }
- try
- {
- Stopwatch stopwatch = Stopwatch.StartNew();
- Stopwatch productNameStopwatch = Stopwatch.StartNew();
- int productNamePatches = 0;
- bool productNamePatchComplete = false;
- while (stopwatch.ElapsedMilliseconds < TimeoutMs)
- {
- if (process.HasExited)
- {
- throw new InvalidOperationException("Process exited before the unpacked patch pattern appeared.");
- }
- if (!productNamePatchComplete)
- {
- // 저장 폴더명은 일반 문자열 패치보다 먼저 사용될 수 있다.
- // 그래서 언패킹 감지 패턴을 기다리기 전에도 제품명 슬롯만 반복해서 시도한다.
- productNamePatches += ApplyProductNamePatches(handle);
- productNamePatchComplete = productNamePatches >= ProductNameRawOffsets.Length;
- if (!productNamePatchComplete && productNameStopwatch.ElapsedMilliseconds < ProductNamePatchTimeoutMs)
- {
- Thread.Sleep(0);
- continue;
- }
- }
- if (FindPatternAddresses(handle, OriginalPattern).Length > 0)
- {
- if (process.HasExited)
- {
- throw new InvalidOperationException("Process exited before the delayed patch was applied.");
- }
- productNamePatches += ApplyProductNamePatches(handle);
- Thread.Sleep(PatchDelayMs);
- productNamePatches += ApplyProductNamePatches(handle);
- // 언패킹 완료 후에는 고정 슬롯 문자열, 다이얼로그 리소스, charset 패턴을 한 번에 적용한다.
- int applied = ApplyTranslatedPatches(handle);
- int charsetPatches = ApplyCharsetPatches(handle);
- bool titleSet = TrySetWindowTitle(process, KoreanTitle, TitleWaitMs);
- return "applied " + productNamePatches + " early product-name patches; applied " + applied + " menu/title patch ranges; applied " + charsetPatches + " charset hook patches; unicode title set=" + titleSet;
- }
- Thread.Sleep(PollIntervalMs);
- }
- throw new TimeoutException("Timed out waiting for the unpacked patch pattern.");
- }
- finally
- {
- CloseHandle(handle);
- }
- }
- private static int ApplyProductNamePatches(IntPtr handle)
- {
- int applied = 0;
- // CP949로 인코딩한 "姪少女"를 써서 한국어 로케일에서도 원래 일본어 폴더명이 유지되게 한다.
- // 원문 한자는 유니코드 이스케이프로 적어 소스 저장 형식과 무관하게 같은 문자열을 만든다.
- byte[] replacement = Encoding.GetEncoding(949).GetBytes("\u59EA\u5C11\u5973");
- foreach (int rawOffset in ProductNameRawOffsets)
- {
- IntPtr address = new IntPtr(rawOffset + RawToVaDelta);
- byte[] current = ReadBytes(handle, address, ProductNameCp932Bytes.Length);
- if (BytesEqual(current, ProductNameCp932Bytes))
- {
- WriteBytes(handle, address, replacement);
- applied++;
- }
- }
- return applied;
- }
- private static int ApplyTranslatedPatches(IntPtr handle)
- {
- int applied = 0;
- foreach (TextPatch patch in TextPatches)
- {
- byte[] bytes = EncodeCp949Padded(patch.Text, patch.Size);
- WriteBytes(handle, new IntPtr(patch.RawOffset + RawToVaDelta), bytes);
- applied++;
- }
- foreach (WideTextPatch patch in WideTextPatches)
- {
- byte[] bytes = EncodeUtf16LePadded(patch.Text, patch.Size);
- WriteBytes(handle, new IntPtr(patch.RawOffset + RawToVaDelta), bytes);
- applied++;
- }
- foreach (HexPatch patch in HexPatches)
- {
- byte[] bytes = HexToBytes(patch.Hex);
- WriteBytes(handle, new IntPtr(patch.RawOffset + RawToVaDelta), bytes);
- applied++;
- }
- return applied;
- }
- private static byte[] EncodeCp949Padded(string text, int size)
- {
- // TextPatch의 유니코드 문자열은 게임 본문/메뉴가 읽는 CP949 바이트 배열로 변환한다.
- byte[] encoded = Encoding.GetEncoding(949).GetBytes(text);
- if (encoded.Length > size)
- {
- throw new InvalidOperationException("Encoded text is too long for patch slot: " + text);
- }
- byte[] padded = new byte[size];
- Buffer.BlockCopy(encoded, 0, padded, 0, encoded.Length);
- return padded;
- }
- private static byte[] EncodeUtf16LePadded(string text, int size)
- {
- // WideTextPatch의 유니코드 문자열은 Windows 리소스 형식에 맞춰 UTF-16LE로 변환한다.
- byte[] encoded = Encoding.Unicode.GetBytes(text);
- if (encoded.Length + 2 > size)
- {
- throw new InvalidOperationException("Encoded UTF-16 text is too long for patch slot: " + text);
- }
- byte[] padded = new byte[size];
- Buffer.BlockCopy(encoded, 0, padded, 0, encoded.Length);
- // 다이얼로그 템플릿 문자열은 중간 NUL이 나오면 거기서 끊긴다.
- // 남는 영역은 공백으로 채우고 마지막 2바이트만 NUL 종료로 남긴다.
- for (int i = encoded.Length; i < size - 2; i += 2)
- {
- padded[i] = 0x20;
- padded[i + 1] = 0x00;
- }
- return padded;
- }
- private static byte[] HexToBytes(string hex)
- {
- if (hex.Length % 2 != 0)
- {
- throw new InvalidOperationException("Invalid hex patch length.");
- }
- byte[] bytes = new byte[hex.Length / 2];
- for (int i = 0; i < bytes.Length; i++)
- {
- bytes[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
- }
- return bytes;
- }
- private static int ApplyCharsetPatches(IntPtr handle)
- {
- int applied = 0;
- foreach (BytePatchPattern patchPattern in CharsetPatterns)
- {
- IntPtr[] matches = FindPatternAddresses(handle, patchPattern.Pattern);
- foreach (IntPtr match in matches)
- {
- IntPtr patchAddress = new IntPtr(match.ToInt64() + patchPattern.PatchOffset);
- WriteBytes(handle, patchAddress, new[] { patchPattern.Value });
- applied++;
- }
- }
- return applied;
- }
- private static IntPtr[] FindPatternAddresses(IntPtr handle, byte[] pattern)
- {
- if (pattern == null || pattern.Length == 0)
- {
- return new IntPtr[0];
- }
- IntPtr[] results = new IntPtr[16];
- int resultCount = 0;
- byte[] previousTail = new byte[0];
- for (int offset = 0; offset < ImageSize; offset += ScanChunkSize)
- {
- int size = Math.Min(ScanChunkSize, ImageSize - offset);
- byte[] chunk = ReadBytes(handle, new IntPtr(ImageBase + offset), size);
- if (chunk.Length == 0)
- {
- previousTail = new byte[0];
- continue;
- }
- byte[] scan = new byte[previousTail.Length + chunk.Length];
- Buffer.BlockCopy(previousTail, 0, scan, 0, previousTail.Length);
- Buffer.BlockCopy(chunk, 0, scan, previousTail.Length, chunk.Length);
- // 청크 경계에 걸친 패턴도 놓치지 않도록 이전 청크의 꼬리를 붙여서 검사한다.
- int start = 0;
- while (true)
- {
- int index = IndexOf(scan, pattern, start);
- if (index < 0)
- {
- break;
- }
- int absoluteOffset = offset - previousTail.Length + index;
- if (absoluteOffset >= 0 && absoluteOffset + pattern.Length <= ImageSize)
- {
- if (resultCount == results.Length)
- {
- IntPtr[] grown = new IntPtr[results.Length * 2];
- Array.Copy(results, grown, results.Length);
- results = grown;
- }
- results[resultCount++] = new IntPtr(ImageBase + absoluteOffset);
- }
- start = index + 1;
- }
- int tailSize = Math.Min(pattern.Length - 1, scan.Length);
- previousTail = new byte[tailSize];
- Buffer.BlockCopy(scan, scan.Length - tailSize, previousTail, 0, tailSize);
- }
- IntPtr[] trimmed = new IntPtr[resultCount];
- Array.Copy(results, trimmed, resultCount);
- return trimmed;
- }
- private static int IndexOf(byte[] haystack, byte[] needle, int start)
- {
- int limit = haystack.Length - needle.Length;
- for (int i = start; i <= limit; i++)
- {
- int j = 0;
- while (j < needle.Length && haystack[i + j] == needle[j])
- {
- j++;
- }
- if (j == needle.Length)
- {
- return i;
- }
- }
- return -1;
- }
- private static bool TrySetWindowTitle(Process process, string title, int timeoutMs)
- {
- Stopwatch stopwatch = Stopwatch.StartNew();
- while (stopwatch.ElapsedMilliseconds < timeoutMs)
- {
- if (process.HasExited)
- {
- return false;
- }
- IntPtr hwnd = FindMainWindow(process.Id);
- if (hwnd != IntPtr.Zero)
- {
- return SetWindowTextW(hwnd, title);
- }
- Thread.Sleep(50);
- }
- return false;
- }
- private static void TranslateDialogsUntilExit(Process process)
- {
- using (DialogTranslationContext context = new DialogTranslationContext(process))
- {
- Application.Run(context);
- }
- }
- private sealed class DialogTranslationContext : ApplicationContext
- {
- private readonly Process process;
- private readonly WinEventProc callback;
- private readonly EventHandler exitedHandler;
- private IntPtr hook;
- public DialogTranslationContext(Process process)
- {
- this.process = process;
- callback = OnWinEvent;
- exitedHandler = delegate { ExitThread(); };
- process.EnableRaisingEvents = true;
- process.Exited += exitedHandler;
- // CBT 후킹처럼 프로세스 안에 DLL을 넣지 않고, 외부 프로세스에서 창 생성/이름 변경 이벤트만 받는다.
- // 이 방식이면 원본 게임 코드와 충돌할 가능성이 낮고 런처 종료도 단순하다.
- hook = SetWinEventHook(
- EventObjectCreate,
- EventObjectNameChange,
- IntPtr.Zero,
- callback,
- (uint)process.Id,
- 0,
- WinEventOutOfContext);
- TranslateProcessWindows(process.Id);
- if (process.HasExited)
- {
- ExitThread();
- }
- }
- protected override void ExitThreadCore()
- {
- if (hook != IntPtr.Zero)
- {
- UnhookWinEvent(hook);
- hook = IntPtr.Zero;
- }
- base.ExitThreadCore();
- }
- protected override void Dispose(bool disposing)
- {
- if (hook != IntPtr.Zero)
- {
- UnhookWinEvent(hook);
- hook = IntPtr.Zero;
- }
- if (disposing)
- {
- process.Exited -= exitedHandler;
- }
- base.Dispose(disposing);
- }
- private void OnWinEvent(
- IntPtr hWinEventHook,
- uint eventType,
- IntPtr hwnd,
- int idObject,
- int idChild,
- uint dwEventThread,
- uint dwmsEventTime)
- {
- if (hwnd == IntPtr.Zero || process.HasExited)
- {
- return;
- }
- uint windowProcessId;
- GetWindowThreadProcessId(hwnd, out windowProcessId);
- if (windowProcessId == process.Id)
- {
- TranslateWindowAndChildren(hwnd);
- }
- }
- }
- private static void TranslateProcessWindows(int processId)
- {
- EnumWindows(delegate(IntPtr hwnd, IntPtr lParam)
- {
- uint windowProcessId;
- GetWindowThreadProcessId(hwnd, out windowProcessId);
- if (windowProcessId == processId)
- {
- TranslateWindowAndChildren(hwnd);
- }
- return true;
- }, IntPtr.Zero);
- }
- private static void TranslateWindowAndChildren(IntPtr hwnd)
- {
- TranslateWindowText(hwnd);
- TranslateComboBoxItems(hwnd);
- EnumChildWindows(hwnd, delegate(IntPtr child, IntPtr lParam)
- {
- TranslateWindowText(child);
- TranslateComboBoxItems(child);
- return true;
- }, IntPtr.Zero);
- }
- private static void TranslateWindowText(IntPtr hwnd)
- {
- int length = GetWindowTextLengthW(hwnd);
- if (length <= 0)
- {
- return;
- }
- StringBuilder builder = new StringBuilder(length + 1);
- GetWindowTextW(hwnd, builder, builder.Capacity);
- string current = builder.ToString();
- string translated = TranslateDialogText(current);
- if (!String.Equals(current, translated, StringComparison.Ordinal))
- {
- SetWindowTextW(hwnd, translated);
- }
- }
- private static string TranslateDialogText(string current)
- {
- string translated = current;
- while (true)
- {
- TextReplacement best = default(TextReplacement);
- int bestLength = 0;
- foreach (TextReplacement replacement in DialogTextReplacements)
- {
- // 긴 원문을 먼저 치환해야 부분 문자열 치환이 먼저 일어나 문장이 깨지는 문제를 막을 수 있다.
- if (translated.IndexOf(replacement.Source, StringComparison.Ordinal) >= 0 &&
- replacement.Source.Length > bestLength)
- {
- best = replacement;
- bestLength = replacement.Source.Length;
- }
- }
- if (bestLength == 0)
- {
- return translated;
- }
- translated = translated.Replace(best.Source, best.Target);
- }
- }
- private static void TranslateComboBoxItems(IntPtr hwnd)
- {
- if (!IsComboBox(hwnd))
- {
- return;
- }
- if (TranslatedComboBoxes.Contains(hwnd))
- {
- return;
- }
- int count = SendMessageW(hwnd, CbGetCount, IntPtr.Zero, IntPtr.Zero).ToInt32();
- if (count <= 0)
- {
- return;
- }
- string[] items = FindComboBoxItemSet(hwnd, count);
- if (items == null)
- {
- return;
- }
- // 항목 값 자체가 설정값으로 저장되는 구조가 아니므로, 같은 인덱스의 한국어 문자열로만 교체한다.
- int current = SendMessageW(hwnd, CbGetCurSel, IntPtr.Zero, IntPtr.Zero).ToInt32();
- SendMessageW(hwnd, CbResetContent, IntPtr.Zero, IntPtr.Zero);
- foreach (string item in items)
- {
- SendMessageW(hwnd, CbAddString, IntPtr.Zero, item);
- }
- if (current >= 0 && current < items.Length)
- {
- SendMessageW(hwnd, CbSetCurSel, new IntPtr(current), IntPtr.Zero);
- }
- TranslatedComboBoxes.Add(hwnd);
- }
- private static bool IsComboBox(IntPtr hwnd)
- {
- StringBuilder className = new StringBuilder(64);
- if (GetClassNameW(hwnd, className, className.Capacity) <= 0)
- {
- return false;
- }
- return String.Equals(className.ToString(), "ComboBox", StringComparison.OrdinalIgnoreCase);
- }
- private static string[] FindComboBoxItemSet(IntPtr hwnd, int count)
- {
- // 항목 개수만으로는 CPU/마우스/저장품질 콤보를 구분할 수 없다.
- // 콤보박스 왼쪽의 라벨 텍스트를 같이 확인해 잘못된 번역 세트를 넣지 않게 한다.
- // 라벨 비교 문자열은 앞에서 한국어로 바꾼 Unicode 컨트롤 텍스트와 같은 값을 사용한다.
- string label = NormalizeLabel(FindNearestLeftLabelText(hwnd));
- if (label.IndexOf("\uD654\uBA74\uBAA8\uB4DC", StringComparison.Ordinal) >= 0 && count == 2)
- {
- return ComboBoxItemSets[0];
- }
- if (label.IndexOf("\uD654\uBA74\uC5F0\uCD9C", StringComparison.Ordinal) >= 0 && count == 3)
- {
- return ComboBoxItemSets[1];
- }
- if (label.IndexOf("\uBB38\uC790\uD45C\uC2DC", StringComparison.Ordinal) >= 0 && count == 2)
- {
- return TextDisplayComboItems;
- }
- if (label.IndexOf("\uCEE4\uC11C\uC790\uB3D9\uC228\uAE40\uC2DC\uAC04", StringComparison.Ordinal) >= 0 && count == 4)
- {
- return AutoHideComboItems;
- }
- if ((label.IndexOf("\uC624\uB978\uCABD\uBC84\uD2BC", StringComparison.Ordinal) >= 0 ||
- label.IndexOf("\uAC00\uC6B4\uB370\uBC84\uD2BC", StringComparison.Ordinal) >= 0) &&
- count == 6)
- {
- return ComboBoxItemSets[3];
- }
- if (label.IndexOf("\uC800\uC7A5\uD488\uC9C8", StringComparison.Ordinal) >= 0 && count == 4)
- {
- return SaveQualityComboItems;
- }
- if (label.IndexOf("CPU\uC0AC\uC6A9\uB960", StringComparison.Ordinal) >= 0 && count == 6)
- {
- return CpuUsageComboItems;
- }
- if (label.IndexOf("\uC74C\uC131\uC7AC\uC0DD\uC2DC\uC74C\uC545\uB0AE\uCDA4", StringComparison.Ordinal) >= 0 && count == 3)
- {
- return VoicePlaybackBgmComboItems;
- }
- // 일부 2항목 콤보는 라벨 탐지가 실패할 수 있어, 깨져 보이는 원본 항목명으로 문자표시 콤보를 보조 판별한다.
- if (count == 2 && ContainsAnyComboBoxItem(hwnd, "\uBE15\uBBC5", "\uB8B7\uB4E9"))
- {
- return TextDisplayComboItems;
- }
- return null;
- }
- private static bool ContainsAnyComboBoxItem(IntPtr hwnd, params string[] fragments)
- {
- int count = SendMessageW(hwnd, CbGetCount, IntPtr.Zero, IntPtr.Zero).ToInt32();
- for (int i = 0; i < count; i++)
- {
- string text = ReadComboBoxItemText(hwnd, i);
- foreach (string fragment in fragments)
- {
- if (text.IndexOf(fragment, StringComparison.Ordinal) >= 0)
- {
- return true;
- }
- }
- }
- return false;
- }
- private static string FindNearestLeftLabelText(IntPtr hwnd)
- {
- Rect comboRect;
- if (!GetWindowRect(hwnd, out comboRect))
- {
- return string.Empty;
- }
- IntPtr parent = GetParent(hwnd);
- if (parent == IntPtr.Zero)
- {
- return string.Empty;
- }
- string bestText = string.Empty;
- int bestScore = int.MaxValue;
- EnumChildWindows(parent, delegate(IntPtr child, IntPtr lParam)
- {
- if (child == hwnd || IsComboBox(child))
- {
- return true;
- }
- Rect childRect;
- if (!GetWindowRect(child, out childRect))
- {
- return true;
- }
- int verticalDistance = Math.Abs(((childRect.Top + childRect.Bottom) / 2) - ((comboRect.Top + comboRect.Bottom) / 2));
- if (verticalDistance > 40 || childRect.Left > comboRect.Left)
- {
- return true;
- }
- string text = GetWindowText(child);
- if (String.IsNullOrEmpty(text))
- {
- return true;
- }
- int horizontalDistance = Math.Abs(comboRect.Left - childRect.Right);
- int score = verticalDistance * 1000 + horizontalDistance;
- // 같은 행에 있고 콤보박스 왼쪽에 가장 가까운 텍스트 컨트롤을 라벨로 본다.
- if (score < bestScore)
- {
- bestScore = score;
- bestText = text;
- }
- return true;
- }, IntPtr.Zero);
- return TranslateDialogText(bestText);
- }
- private static string NormalizeLabel(string value)
- {
- if (String.IsNullOrEmpty(value))
- {
- return string.Empty;
- }
- return value
- .Replace(" ", string.Empty)
- .Replace("\t", string.Empty)
- .Replace(":", string.Empty)
- .Replace("\uFF1A", string.Empty);
- }
- private static string GetWindowText(IntPtr hwnd)
- {
- int length = GetWindowTextLengthW(hwnd);
- if (length <= 0)
- {
- return string.Empty;
- }
- StringBuilder builder = new StringBuilder(length + 1);
- GetWindowTextW(hwnd, builder, builder.Capacity);
- return builder.ToString();
- }
- private static string ReadComboBoxItemText(IntPtr hwnd, int index)
- {
- int length = SendMessageW(hwnd, CbGetLbTextLen, new IntPtr(index), IntPtr.Zero).ToInt32();
- if (length <= 0)
- {
- return string.Empty;
- }
- StringBuilder builder = new StringBuilder(length + 1);
- SendMessageW(hwnd, CbGetLbText, new IntPtr(index), builder);
- return builder.ToString();
- }
- private static IntPtr FindMainWindow(int processId)
- {
- IntPtr result = IntPtr.Zero;
- EnumWindows(delegate(IntPtr hwnd, IntPtr lParam)
- {
- uint windowProcessId;
- GetWindowThreadProcessId(hwnd, out windowProcessId);
- if (windowProcessId == processId && IsWindowVisible(hwnd))
- {
- result = hwnd;
- return false;
- }
- return true;
- }, IntPtr.Zero);
- return result;
- }
- private static byte[] ReadBytes(IntPtr handle, IntPtr address, int size)
- {
- byte[] buffer = new byte[size];
- IntPtr bytesRead;
- bool ok = ReadProcessMemory(handle, address, buffer, new IntPtr(size), out bytesRead);
- if (!ok || bytesRead.ToInt64() != size)
- {
- return Array.Empty<byte>();
- }
- return buffer;
- }
- private static void WriteBytes(IntPtr handle, IntPtr address, byte[] buffer)
- {
- uint oldProtect;
- if (!VirtualProtectEx(handle, address, new IntPtr(buffer.Length), PageExecuteReadWrite, out oldProtect))
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "VirtualProtectEx failed");
- }
- try
- {
- IntPtr written;
- bool ok = WriteProcessMemory(handle, address, buffer, new IntPtr(buffer.Length), out written);
- if (!ok || written.ToInt64() != buffer.Length)
- {
- throw new Win32Exception(Marshal.GetLastWin32Error(), "WriteProcessMemory failed");
- }
- }
- finally
- {
- uint ignored;
- VirtualProtectEx(handle, address, new IntPtr(buffer.Length), oldProtect, out ignored);
- }
- }
- private static bool BytesEqual(byte[] left, byte[] right)
- {
- if (left.Length != right.Length)
- {
- return false;
- }
- for (int i = 0; i < left.Length; i++)
- {
- if (left[i] != right[i])
- {
- return false;
- }
- }
- return true;
- }
- private static string JoinArguments(string[] args)
- {
- if (args == null || args.Length == 0)
- {
- return string.Empty;
- }
- string[] quoted = new string[args.Length];
- for (int i = 0; i < args.Length; i++)
- {
- quoted[i] = QuoteArgument(args[i]);
- }
- return string.Join(" ", quoted);
- }
- private static string QuoteArgument(string value)
- {
- if (string.IsNullOrEmpty(value))
- {
- return "\"\"";
- }
- bool needsQuotes = value.IndexOfAny(new[] { ' ', '\t', '\n', '\v', '"' }) >= 0;
- if (!needsQuotes)
- {
- return value;
- }
- StringBuilder builder = new StringBuilder();
- builder.Append('"');
- int backslashes = 0;
- foreach (char c in value)
- {
- if (c == '\\')
- {
- backslashes++;
- continue;
- }
- if (c == '"')
- {
- builder.Append('\\', backslashes * 2 + 1);
- builder.Append('"');
- backslashes = 0;
- continue;
- }
- builder.Append('\\', backslashes);
- backslashes = 0;
- builder.Append(c);
- }
- builder.Append('\\', backslashes * 2);
- builder.Append('"');
- return builder.ToString();
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment