diff --git a/Build/rNotepad3.ini b/Build/rNotepad3.ini
new file mode 100644
index 000000000..9f5be05d4
--- /dev/null
+++ b/Build/rNotepad3.ini
@@ -0,0 +1,4 @@
+[Notepad3]
+Notepad3.ini=%USERPROFILE%\Notepad3\Notepad3.ini
+[Settings2]
+DefaultDirectory=%CSIDL:MYDOCUMENTS%
diff --git a/minipath/plans/minipath-longpath-migration.md b/minipath/plans/minipath-longpath-migration.md
new file mode 100644
index 000000000..dda84b17b
--- /dev/null
+++ b/minipath/plans/minipath-longpath-migration.md
@@ -0,0 +1,472 @@
+# MiniPath: Migration from MAX_PATH to HPATHL Long Path Support
+
+## Context
+
+MiniPath is Notepad3's companion file browser (Ctrl+M). It currently uses fixed `WCHAR[MAX_PATH]` (260 char) buffers for all path operations, while Notepad3 core has been fully migrated to dynamic `HPATHL` handles (up to 32,768 chars via PathLib/DynStrg). MiniPath's `longPathAware` manifest entry was removed because the code cannot handle long paths.
+
+The goal is to migrate MiniPath to use `HPATHL`/`HSTRINGW` consistently, matching Notepad3's architecture, then re-enable `longPathAware` in the manifest.
+
+## Key Libraries
+
+- **PathLib** (`src/PathLib.h`, `src/PathLib.c`): Provides `HPATHL` opaque handle for dynamic long paths. Built on DynStrg. Max capacity: `PATHLONG_MAX_CCH` = 0x8000 (32,768 chars).
+- **DynStrg** (`src/DynStrg.h`, `src/DynStrg.c`): Provides `HSTRINGW` opaque handle for dynamic wide strings.
+- Both are already compiled into the MiniPath project (shared `src/` modules).
+- `HPATHL` is a type-safe cast of `HSTRINGW` — PathLib wraps DynStrg with path-specific operations.
+
+## HPATHL API Quick Reference
+
+```c
+// Lifecycle
+HPATHL Path_Allocate(LPCWSTR path); // Create (NULL for empty)
+void Path_Release(HPATHL hpth); // Free
+HPATHL Path_Copy(const HPATHL hpth); // Duplicate
+
+// Read access
+LPCWSTR Path_Get(HPATHL hpth); // Get LPCWSTR pointer
+size_t Path_GetLength(HPATHL hpth); // String length
+size_t Path_GetBufCount(HPATHL hpth); // Allocated buffer size
+bool Path_IsEmpty(HPATHL hpth);
+bool Path_IsNotEmpty(HPATHL hpth);
+
+// Write access
+void Path_Reset(HPATHL hpth, LPCWSTR path); // Replace contents
+void Path_Empty(HPATHL hpth, bool truncate); // Clear (keep handle)
+LPWSTR Path_WriteAccessBuf(HPATHL hpth, size_t len); // Get writable buffer (min MAX_PATH_EXPLICIT)
+void Path_Sanitize(HPATHL hpth); // Recalc length after external buffer write
+void Path_Swap(HPATHL h1, HPATHL h2);
+
+// Path operations
+bool Path_Append(HPATHL hpth, LPCWSTR more);
+bool Path_RemoveFileSpec(HPATHL hpth);
+void Path_StripPath(HPATHL hpth);
+bool Path_RenameExtension(HPATHL hpth, LPCWSTR ext);
+LPCWSTR Path_FindFileName(HPATHL hpth);
+LPCWSTR Path_FindExtension(HPATHL hpth);
+bool Path_Canonicalize(HPATHL hpth);
+bool Path_CanonicalizeEx(HPATHL hpth, HPATHL hbase);
+void Path_ExpandEnvStrings(HPATHL hpth);
+bool Path_IsRoot(HPATHL hpth);
+bool Path_IsRelative(HPATHL hpth);
+bool Path_IsUNC(HPATHL hpth);
+bool Path_IsExistingFile(HPATHL hpth);
+bool Path_IsExistingDirectory(HPATHL hpth);
+bool Path_IsLnkFile(HPATHL hpth);
+DWORD Path_GetFileAttributes(HPATHL hpth);
+void Path_QuoteSpaces(HPATHL hpth, bool force);
+void Path_UnQuoteSpaces(HPATHL hpth);
+size_t Path_GetLongPathNameEx(HPATHL hpth);
+size_t Path_ToShortPathName(HPATHL hpth);
+void Path_GetModuleFilePath(HPATHL hpth);
+void Path_GetAppDirectory(HPATHL hpth);
+bool Path_GetCurrentDirectory(HPATHL hpth);
+void Path_FreeExtra(HPATHL hpth, size_t keep);
+bool Path_BrowseDirectory(HWND hwnd, LPCWSTR title, HPATHL hpth_io, HPATHL hbase, bool newStyle);
+void Path_RelativeToApp(HPATHL hpth, bool isFile, bool unexpandEnv, bool unexpandDocs);
+int Path_StrgComparePath(HPATHL h1, HPATHL h2, HPATHL wrkdir, bool normalize);
+void Path_NormalizeEx(HPATHL hpth, HPATHL wrkdir, bool real, bool search);
+```
+
+---
+
+## PART 1: WHAT NEEDS TO CHANGE
+
+### 1.1 Global Variables (minipath/src/minipath.c:88-109)
+
+| Current declaration | Line | Purpose | Migration |
+|---|---|---|---|
+| `WCHAR g_wchIniFile[MAX_PATH]` | 88 | MiniPath INI file path | `HPATHL g_hIniFile = NULL;` |
+| `WCHAR g_wchIniFile2[MAX_PATH]` | 89 | Fallback/redirect INI path | `HPATHL g_hIniFile2 = NULL;` |
+| `WCHAR g_wchNP3IniFile[MAX_PATH]` | 90 | Notepad3 INI file path | `HPATHL g_hNP3IniFile = NULL;` |
+| `WCHAR szTargetApplication[MAX_PATH]` | 107 | Target app executable path | `HPATHL g_hTargetApplication = NULL;` |
+| `WCHAR szTargetApplicationParams[MAX_PATH]` | 108 | Target app parameters | `HSTRINGW g_hTargetAppParams = NULL;` |
+| `WCHAR szTargetApplicationWndClass[MAX_PATH]` | 109 | Target app window class | `HSTRINGW g_hTargetAppWndClass = NULL;` |
+
+All `extern` declarations must be updated in: Config.cpp:30-32, Dialogs.c:607, Helpers.c:47, and Dialogs.c:2426-2428.
+
+### 1.2 SETTINGS_T Struct (minipath/src/minipath.h:104-154)
+
+Current fields to replace:
+
+| Current field | Line | Purpose | Migration |
+|---|---|---|---|
+| `WCHAR szCurDir[MAX_PATH + 40]` | 143 | Current browsed directory | `HPATHL hCurDir;` |
+| `WCHAR szQuickview[MAX_PATH]` | 144 | Quickview app path | `HPATHL hQuickview;` |
+| `WCHAR szQuickviewParams[MAX_PATH]` | 145 | Quickview parameters | `HSTRINGW hQuickviewParams;` |
+| `WCHAR tchFavoritesDir[MAX_PATH]` | 146 | Favorites directory | `HPATHL hFavoritesDir;` |
+| `WCHAR tchOpenWithDir[MAX_PATH]` | 147 | Open-with directory | `HPATHL hOpenWithDir;` |
+| `WCHAR tchToolbarBitmap[MAX_PATH]` | 149 | Toolbar bitmap path | `HPATHL hToolbarBitmap;` |
+| `WCHAR tchToolbarBitmapHot[MAX_PATH]` | 150 | Toolbar hot bitmap | `HPATHL hToolbarBitmapHot;` |
+| `WCHAR tchToolbarBitmapDisabled[MAX_PATH]` | 151 | Toolbar disabled bitmap | `HPATHL hToolbarBitmapDisabled;` |
+
+Non-path fields to keep as-is: `tchToolbarButtons[512]`, `tchFilter[DL_FILTER_BUFSIZE]`, all `int`/`bool`/`RECT` fields.
+
+Note: There is both a `Settings` and a `Defaults` instance of this struct. Both need HPATHL allocation/release.
+
+### 1.3 DLITEM Struct (minipath/src/Dlapi.h:102-109)
+
+```c
+typedef struct tagDLITEM {
+ UINT mask;
+ WCHAR szFileName[MAX_PATH]; // Line 105 — full file path
+ WCHAR szDisplayName[MAX_PATH]; // Line 106 — display name
+ int ntype;
+} DLITEM, *LPDLITEM;
+```
+
+Populated by `DirList_GetItem()` (Dlapi.c:714-721) via `IL_GetDisplayName()` with `SHGDN_FORPARSING` and `SHGDN_INFOLDER`.
+
+Migration options:
+- **Option A (recommended):** Replace with HPATHL members. Callers must allocate/release.
+- **Option B (simpler):** Increase buffer to `PATHLONG_MAX_CCH` (wastes 64KB per DLITEM on stack).
+
+### 1.4 DLDATA Internal Struct (minipath/src/Dlapi.c:38)
+
+```c
+typedef struct tagDLDATA {
+ // ...
+ WCHAR szPath[MAX_PATH]; // Current directory path
+} DLDATA;
+```
+
+Replace with `HPATHL hPath;` — allocate in `DirList_Init()`, release in `DirList_Destroy()`.
+
+---
+
+## PART 2: INITIALIZATION AND CLEANUP PATTERNS
+
+### How Notepad3 Does It (model to follow)
+
+**Allocation (src/Notepad3.c:715-750):**
+```c
+// In WinMain / initialization:
+Paths.CurrentFile = Path_Allocate(NULL);
+Paths.ModuleDirectory = Path_Allocate(NULL);
+Paths.WorkingDirectory = Path_Allocate(NULL);
+Paths.IniFile = Path_Allocate(NULL);
+Paths.IniFileDefault = Path_Allocate(NULL);
+
+Settings.OpenWithDir = Path_Allocate(NULL);
+Defaults.OpenWithDir = Path_Allocate(NULL);
+Settings.FavoritesDir = Path_Allocate(NULL);
+Defaults.FavoritesDir = Path_Allocate(NULL);
+```
+
+**Deallocation (src/Notepad3.c:839-868):**
+```c
+// In WM_DESTROY / cleanup (reverse order):
+Path_Release(Settings.FavoritesDir);
+Path_Release(Defaults.FavoritesDir);
+Path_Release(Settings.OpenWithDir);
+Path_Release(Defaults.OpenWithDir);
+Path_Release(Paths.IniFileDefault);
+Path_Release(Paths.IniFile);
+// ... etc
+```
+
+### MiniPath Initialization Order
+
+```
+WinMain()
+ +-- [INIT HPATHL globals here] <-- NEW: allocate g_hIniFile etc.
+ +-- [INIT Settings/Defaults HPATHL] <-- NEW: allocate Settings.hCurDir etc.
+ +-- ParseCommandLine() Uses lpPathArg (GlobalAlloc) -> migrate to HPATHL
+ +-- FindIniFile() Config.cpp Fills g_wchIniFile -> use Path_Reset(g_hIniFile, ...)
+ +-- TestIniFile() Config.cpp Validates INI path
+ +-- LoadFlags() Config.cpp Loads INI cache
+ +-- LoadSettings() Config.cpp Fills Settings struct fields
+ +-- ChangeDirectory() Sets Settings.szCurDir -> Path_Reset(Settings.hCurDir, ...)
+ +-- DirList_Init/Fill() Uses Settings.szCurDir -> Path_Get(Settings.hCurDir)
+ :
+ +-- WM_DESTROY
+ +-- SaveSettings() Config.cpp
+ +-- [RELEASE all HPATHL globals] <-- NEW: Path_Release(g_hIniFile) etc.
+ +-- [RELEASE Settings/Defaults] <-- NEW: Path_Release(Settings.hCurDir) etc.
+```
+
+---
+
+## PART 3: MAX_PATH OCCURRENCES BY FILE (127 total)
+
+### minipath.c — 48 occurrences
+
+**Local buffers in functions (replace with HPATHL or PATHLONG_MAX_CCH):**
+
+| Line(s) | Variable | Context | Replacement |
+|---|---|---|---|
+| 594 | `WCHAR tch[MAX_PATH]` | WM_COMMAND path manipulation | HPATHL local |
+| 638-639 | `wchMenuEntry[MAX_PATH]`, `wchTargetAppName[MAX_PATH]` | Menu generation | HSTRINGW |
+| 870 | `szBuf[MAX_PATH+40]` | WM_DROPFILES handler | HPATHL + Path_WriteAccessBuf |
+| 1252 | `szTmp[MAX_PATH]` | File operations | HPATHL |
+| 2007, 2010, 2023 | `szNewFile[MAX_PATH]`, `szPath[MAX_PATH]`, `ofn.nMaxFile=MAX_PATH` | Save-As dialog | HPATHL + PATHLONG_MAX_CCH |
+| 2069, 2083 | `tchNewDir[MAX_PATH]`, `tchLinkDestination[MAX_PATH]` | Directory/link creation | HPATHL |
+| 2102-2103 | `szNewFile[MAX_PATH]`, `tch[MAX_PATH]` | File dialog | HPATHL |
+| 2258-2259 | `szModuleName[MAX_PATH]`, `szParameters[MAX_PATH+64]` | New window launch | HPATHL + HSTRINGW |
+| 2612-2613, 2670, 2685, 2729, 2770 | Various `szFullPath`, `szDir`, `tch` | File operations | HPATHL |
+| 3108-3109 | `szTest[MAX_PATH]`, `szWinDir[MAX_PATH]` | INI file search | HPATHL |
+| 3292 | `GlobalAlloc(GPTR, sizeof(WCHAR)*(MAX_PATH+2))` | Command-line path | HPATHL |
+| 3313-3314, 3387-3388 | `szPath[MAX_PATH]`, `szTmp[MAX_PATH]` | DisplayPath() | HPATHL |
+| 3554 | `tchTmp[MAX_PATH]` | Error messages | HSTRINGW |
+| 3733-3736, 3800-3801, 3836 | `szFile`, `szParam`, `szTmp` | Shell execute / WM_COPYDATA | HPATHL + HSTRINGW |
+| 3767, 3825 | `GetShortPathName(..., MAX_PATH)` | Short path conversion | Path_ToShortPathName(HPATHL) |
+
+### Config.cpp — 15 occurrences
+
+| Line(s) | Variable | Context | Replacement |
+|---|---|---|---|
+| 30-32 | `extern g_wchIniFile[MAX_PATH]` etc. | Extern declarations | Update to HPATHL |
+| 118, 124 | `msg[MAX_PATH + 128]` | Error messages | HSTRINGW or larger buffer |
+| 659 | `tchDir[MAX_PATH]` | CreateIniFileEx() | HPATHL |
+| 721-722 | `tchFileExpanded[MAX_PATH]`, `tchBuild[MAX_PATH]` | CheckIniFile() | HPATHL |
+| 768, 775 | `tch[MAX_PATH]`, `tchFileExpanded[MAX_PATH]` | CheckIniFileRedirect() | HPATHL |
+| 795-796 | `tchTest[MAX_PATH]`, `tchModule[MAX_PATH]` | FindIniFile() | HPATHL |
+| 870, 884 | `wchModule[MAX_PATH]` | TestIniFile() | HPATHL |
+| 1152 | `wchTmp[MAX_PATH]` | LoadSettings() | HSTRINGW |
+
+### Dialogs.c — 53 occurrences
+
+| Line(s) | Variable | Context | Replacement |
+|---|---|---|---|
+| 72, 79 | `szBase[MAX_PATH]`, `GetCurrentDirectory(MAX_PATH, ...)` | Browse directory | Path_GetCurrentDirectory(HPATHL) |
+| 174, 369 | `EM_LIMITTEXT, MAX_PATH-1` | Edit control limits | Increase to 512+ |
+| 193-195 | `szArgs[MAX_PATH]`, `szArg2[MAX_PATH]`, `szFile[MAX_PATH*2]` | Run dialog | HPATHL + HSTRINGW |
+| 235, 237, 249-252 | Various dialog text buffers | Command-line dialog | HSTRINGW |
+| 469, 471 | `tch[MAX_PATH]` | GoTo dialog | HPATHL |
+| 607 | `extern g_wchIniFile[MAX_PATH]` | Extern declaration | Update to HPATHL |
+| 1060, 1075, 1079 | `tch[MAX_PATH]`, `EM_LIMITTEXT` | Preferences dialog | HSTRINGW, increase limits |
+| 1097-1099 | `tchBuf[MAX_PATH]`, `szFile[MAX_PATH]`, `szParams[MAX_PATH]` | Browse button | HPATHL + HSTRINGW |
+| 1137, 1160-1164, 1174 | Various settings read/write buffers | Settings dialog saves | HPATHL |
+| 1466-1467 | `szSource[MAX_PATH]`, `szDestination[MAX_PATH]` | Rename dialog | HPATHL |
+| 1494 | `EM_LIMITTEXT, MAX_PATH-1` | Rename edit limit | Increase |
+| 1549-1551 | `szFullDestination[MAX_PATH]`, `tchSource[MAX_PATH+4]`, `tchDestination[MAX_PATH+4]` | Copy/move dialog (SHFileOperation) | HPATHL |
+| 1665 | `CB_LIMITTEXT, MAX_PATH-1` | Destination combo limit | Increase |
+| 1763, 1825-1826 | `tch[MAX_PATH]`, `tchSource/tchDestination[MAX_PATH+4]` | Delete dialog | HPATHL |
+| 1861-1862 | `wszDir[MAX_PATH]`, `GetCurrentDirectory(...)` | Recycle bin | Path_GetCurrentDirectory() |
+| 1907 | `tch[MAX_PATH]` | Delete progress | HSTRINGW |
+| 2121, 2128 | `szDestination[MAX_PATH+4]`, `szSource[MAX_PATH+4]` | Open-With dialog | HPATHL |
+| 2156 | `szParam[MAX_PATH]` | Shell parameter | HSTRINGW |
+| 2207 | `EM_LIMITTEXT, MAX_PATH-1` | New dir edit limit | Increase |
+| 2388 | `tch[MAX_PATH]` | Status message | HSTRINGW |
+| 2426-2428 | extern declarations | Target app declarations | Update to HPATHL/HSTRINGW |
+| 2446 | `wch[MAX_PATH]` | Target app dialog | HSTRINGW |
+| 2487 | `EM_LIMITTEXT, MAX_PATH-1` | Target path limit | Increase |
+| 2538-2540 | `tchBuf[MAX_PATH]`, `szFile[MAX_PATH]`, `szParams[MAX_PATH]` | Target app browse | HPATHL + HSTRINGW |
+| 2652 | `tch[MAX_PATH]` | Message buffer | HSTRINGW |
+
+### Dlapi.c — 12 occurrences
+
+| Line(s) | Variable | Context | Replacement |
+|---|---|---|---|
+| 38 | `szPath[MAX_PATH]` in DLDATA struct | Current directory | HPATHL member |
+| 203 | `wszDir[MAX_PATH]` | DirList_Fill() | HPATHL |
+| 255 | `lvi.cchTextMax = MAX_PATH` | ListView display name | Update with DLITEM |
+| 715, 721 | `IL_GetDisplayName(..., MAX_PATH)` | DirList_GetItem() | Depends on DLITEM migration |
+| 937 | `tch[MAX_PATH]` | DirList_GetLongPathName() | HPATHL |
+| 960, 971, 988 | `szShortPath[MAX_PATH]`, `GetShortPathName(..., MAX_PATH)` | Short path ops | Path_ToShortPathName() |
+| 977 | `StringCchCopyN(..., MAX_PATH)` | SHFILEINFO display | Keep (shell limitation) |
+| 1155 | `cbei.cchTextMax = MAX_PATH` | ComboBoxEx | Keep (UI control) |
+
+### Helpers.c — 23 occurrences
+
+| Line(s) | Variable | Context | Replacement |
+|---|---|---|---|
+| 47 | `extern g_wchIniFile[MAX_PATH]` | Extern declaration | Update to HPATHL |
+| 347, 359 | `szTitle[MAX_PATH+120]`, `tchPath[MAX_PATH]` | SetWindowPathTitle() | HSTRINGW + HPATHL |
+| 589-593 | `wchAppPath`, `wchWinDir`, `wchUserFiles`, `wchPath`, `wchResult` [all MAX_PATH] | PathRelativeToApp() | All HPATHL (use Path_RelativeToApp pattern) |
+| 626, 628 | `lstrcpyn(..., MAX_PATH)` | Copy with fallback | StringCchCopy or HPATHL |
+| 640-641 | `wchPath[MAX_PATH]`, `wchResult[MAX_PATH]` | PathAbsoluteFromApp() | HPATHL |
+| 668, 670 | `lstrcpyn(..., MAX_PATH)` | Copy with fallback | StringCchCopy or HPATHL |
+| 747 | `WORD wsz[MAX_PATH]` | Link file path (NOTE: should be WCHAR, not WORD) | Fix type + HPATHL |
+| 789, 831 | `tchResPath[MAX_PATH]`, `tchLnkFileName[MAX_PATH]` | GetLinkPath() | HPATHL |
+| 848 | `WORD wsz[MAX_PATH]` | Link file path (same type bug) | Fix type + HPATHL |
+| 1034 | `szDst[MAX_PATH]` | CopyShortPath() with PathCanonicalize | HPATHL + Path_Canonicalize() |
+
+---
+
+## PART 4: DEPRECATED SHLWAPI FUNCTIONS TO REPLACE
+
+| Deprecated function | Occurrences | PathLib replacement |
+|---|---|---|
+| `PathRemoveFileSpec(szPath)` | 11 calls | `Path_RemoveFileSpec(hpth)` |
+| `PathAppend(szPath, more)` | 8 calls | `Path_Append(hpth, more)` |
+| `PathFindFileName(szPath)` | 10 calls | `Path_FindFileName(hpth)` |
+| `PathIsRelative(szPath)` | 7 calls | `Path_IsRelative(hpth)` (via `Path_IsRelative`) |
+| `PathIsRoot(szPath)` | 6 calls | `Path_IsRoot(hpth)` |
+| `PathRenameExtension(szPath, ext)` | 5 calls | `Path_RenameExtension(hpth, ext)` |
+| `PathCanonicalize(szDst, szSrc)` | 1 call | `Path_Canonicalize(hpth)` or `Path_CanonicalizeEx(hpth, hbase)` |
+| `SHGetFolderPath(hwnd, csidl, ...)` | 5 calls | `Path_GetKnownFolder(&FOLDERID_xxx, hpth)` |
+| `GetShortPathName(src, dst, MAX_PATH)` | 7 calls | `Path_ToShortPathName(hpth)` |
+| `ExpandEnvironmentStrings(src, dst, MAX_PATH)` | 6 calls | `Path_ExpandEnvStrings(hpth)` |
+| `GetCurrentDirectory(MAX_PATH, buf)` | 4 calls | `Path_GetCurrentDirectory(hpth)` |
+| `GetModuleFileName(NULL, buf, MAX_PATH)` | 5 calls | `Path_GetModuleFilePath(hpth)` |
+| `SHGetPathFromIDList(pidl, szPath)` | 5 calls | Keep but use PATHLONG_MAX_CCH buffer |
+
+---
+
+## PART 5: INI FILE HANDLING PATTERNS
+
+### Notepad3 Pattern (model to follow)
+
+**Loading a path setting from INI (src/Config/Config.cpp:1386-1398):**
+```c
+WCHAR pPathBuffer[PATHLONG_MAX_CCH] = { L'\0' };
+IniSectionGetStringNoQuotes(section, L"DefaultDirectory", L"", pPathBuffer, PATHLONG_MAX_CCH);
+if (StrIsNotEmpty(pPathBuffer)) {
+ Path_Reset(Settings2.DefaultDirectory, pPathBuffer);
+ Path_ExpandEnvStrings(Settings2.DefaultDirectory);
+}
+```
+
+**Saving a path setting to INI (src/Config/Config.cpp:2071-2085):**
+```c
+HPATHL hpth = Path_Allocate(NULL);
+if (StringCchCompareXI(Path_Get(Settings.OpenWithDir), Path_Get(Defaults.OpenWithDir)) != 0) {
+ Path_Reset(hpth, Path_Get(Settings.OpenWithDir));
+ Path_RelativeToApp(hpth, false, true, Flags.PortableMyDocs);
+ IniSectionSetString(section, L"OpenWithDir", Path_Get(hpth));
+} else {
+ IniSectionDelete(section, L"OpenWithDir", false);
+}
+Path_Release(hpth);
+```
+
+**FindIniFile flow (src/Config/Config.cpp:1045-1088):**
+```c
+// 1. Try module path with .ini extension
+Path_GetModuleFilePath(Paths.IniFile);
+Path_RenameExtension(Paths.IniFile, L".ini");
+bFound = _CheckAndSetIniFile(Paths.IniFile);
+
+// 2. Try app directory + name
+if (!bFound) {
+ Path_GetAppDirectory(Paths.IniFile);
+ Path_Append(Paths.IniFile, L"AppName.ini");
+ bFound = _CheckAndSetIniFile(Paths.IniFile);
+}
+
+// 3. Handle redirects
+// 4. Normalize result
+Path_NormalizeEx(Paths.IniFile, Paths.ModuleDirectory, true, false);
+```
+
+**IniFileGetString/SetString (src/Config/Config.cpp:608-666):**
+Both accept `const HPATHL hpthIniFile` as first parameter. Internally call `Path_Get(hpthIniFile)` to get the LPCWSTR for file operations. MiniPath's versions currently take `LPCWSTR lpFilePath` — update signature to take `HPATHL`.
+
+---
+
+## PART 6: FILE DIALOG PATTERN
+
+### Notepad3 Open/Save Dialog (src/Dialogs.c:7037-7074)
+
+```c
+bool OpenFileDlg(HWND hwnd, HPATHL hfile_pth_io, const HPATHL hinidir_pth)
+{
+ OPENFILENAME ofn = { sizeof(OPENFILENAME) };
+ ofn.lpstrFile = Path_WriteAccessBuf(hfile_pth_io, PATHLONG_MAX_CCH);
+ ofn.nMaxFile = (DWORD)Path_GetBufCount(hfile_pth_io);
+ ofn.lpstrInitialDir = Path_IsNotEmpty(hpth_dir) ? Path_Get(hpth_dir) : NULL;
+ // ... other setup ...
+
+ bool const res = GetOpenFileNameW(&ofn);
+ Path_Sanitize(hfile_pth_io); // Recalc length after dialog writes to buffer
+ Path_FreeExtra(hfile_pth_io, MAX_PATH_EXPLICIT); // Trim excess allocation
+ return res;
+}
+```
+
+Key pattern: `Path_WriteAccessBuf()` -> dialog writes to buffer -> `Path_Sanitize()` -> `Path_FreeExtra()`.
+
+### Notepad3 DragQueryFile (src/Notepad3.c:3607-3621)
+
+```c
+HPATHL hdrop_pth = Path_Allocate(NULL);
+wchar_t* const drop_buf = Path_WriteAccessBuf(hdrop_pth, STRINGW_MAX_URL_LENGTH);
+DragQueryFileW(hDrop, i, drop_buf, (UINT)Path_GetBufCount(hdrop_pth));
+Path_Sanitize(hdrop_pth);
+// ... use hdrop_pth ...
+Path_Release(hdrop_pth);
+```
+
+---
+
+## PART 7: MIGRATION PHASES
+
+### Phase 1: Infrastructure (low risk)
+
+1. Verify `#include "PathLib.h"` and `#include "DynStrg.h"` are available in MiniPath build
+2. Add HPATHL global declarations alongside existing WCHAR globals (dual mode)
+3. Add allocation in WinMain init, release in WM_DESTROY cleanup
+4. Add `#include` directives to all MiniPath source files that need them
+
+### Phase 2: Global Variables (medium risk)
+
+1. Replace `g_wchIniFile[MAX_PATH]` with `HPATHL g_hIniFile`
+2. Replace `g_wchIniFile2[MAX_PATH]` with `HPATHL g_hIniFile2`
+3. Replace `g_wchNP3IniFile[MAX_PATH]` with `HPATHL g_hNP3IniFile`
+4. Replace `szTargetApplication[MAX_PATH]` with `HPATHL g_hTargetApplication`
+5. Replace `szTargetApplicationParams[MAX_PATH]` with `HSTRINGW g_hTargetAppParams`
+6. Replace `szTargetApplicationWndClass[MAX_PATH]` with `HSTRINGW g_hTargetAppWndClass`
+7. Update all extern declarations (Config.cpp:30-32, Dialogs.c:607, Helpers.c:47, Dialogs.c:2426-2428)
+8. Update Config.cpp: FindIniFile(), TestIniFile(), IniFileGetString/SetString signatures
+
+### Phase 3: SETTINGS_T Struct (high risk — most dependencies)
+
+1. Replace WCHAR fields with HPATHL/HSTRINGW in minipath.h
+2. Add allocation in initialization for both `Settings` and `Defaults` instances
+3. Update LoadSettings() (Config.cpp) to use Path_Reset + Path_ExpandEnvStrings
+4. Update SaveSettings() (Config.cpp) to use Path_Get + Path_RelativeToApp
+5. Update every `Settings.szCurDir` access (dozens of locations) to `Path_Get(Settings.hCurDir)` for reads and `Path_Reset(Settings.hCurDir, ...)` for writes
+6. Same for all other replaced fields
+
+### Phase 4: DLITEM Struct (high risk — API change)
+
+1. Replace `WCHAR szFileName[MAX_PATH]` with `HPATHL hFileName` (or increase to PATHLONG_MAX_CCH)
+2. Update `DirList_GetItem()` to populate HPATHL
+3. Update all callers in Dialogs.c and minipath.c
+4. Update `IL_GetDisplayName()` calls
+
+### Phase 5: Local Buffers (medium risk — many but mechanical)
+
+For each function, replace local `WCHAR xxx[MAX_PATH]` with either:
+- `HPATHL hxxx = Path_Allocate(NULL);` ... `Path_Release(hxxx);` — for path operations
+- `HSTRINGW hxxx = StrgCreate(NULL);` ... `StrgDestroy(hxxx);` — for general strings
+- `WCHAR xxx[PATHLONG_MAX_CCH];` — for simple Win32 API output buffers (acceptable for stack)
+
+### Phase 6: Deprecated API Replacement (low risk per call)
+
+Replace each deprecated shlwapi function with its PathLib equivalent per the table in Part 4.
+
+### Phase 7: Re-enable Manifest and Test
+
+1. Add back `true` to `minipath/res/MiniPath.exe.manifest`
+2. Test with paths > 260 characters
+3. Test INI file in long-path directory
+4. Test file operations (copy/move/rename/delete) with long paths
+5. Test drag-drop with long paths
+6. Test backward compatibility with existing INI files
+
+---
+
+## PART 8: TYPE BUG TO FIX
+
+In `minipath/src/Helpers.c` lines 747 and 848:
+```c
+WORD wsz[MAX_PATH]; // BUG: should be WCHAR, not WORD
+```
+This is a type error — `WORD` is `unsigned short` (same size as `wchar_t` on Windows, so it works, but it's semantically wrong). Fix when migrating.
+
+---
+
+## FILES TO MODIFY
+
+| File | Changes | Effort |
+|---|---|---|
+| `minipath/src/minipath.h` | SETTINGS_T struct migration | Medium |
+| `minipath/src/minipath.c` | 48 MAX_PATH replacements, global vars, init/cleanup | Large |
+| `minipath/src/Config.cpp` | 15 MAX_PATH replacements, INI function signatures, load/save | Large |
+| `minipath/src/Dialogs.c` | 53 MAX_PATH replacements, dialog buffers, edit limits | Large |
+| `minipath/src/Dlapi.h` | DLITEM struct migration | Small |
+| `minipath/src/Dlapi.c` | 12 MAX_PATH replacements, DLDATA struct | Medium |
+| `minipath/src/Helpers.c` | 23 MAX_PATH replacements, type bug fix | Medium |
+| `minipath/res/MiniPath.exe.manifest` | Re-add longPathAware (Phase 7 only) | Trivial |
+
+Total: ~161 MAX_PATH occurrences across 7 source files.
diff --git a/minipath/res/MiniPath.exe.manifest b/minipath/res/MiniPath.exe.manifest
index a8f3d4ede..d00fa0e39 100644
--- a/minipath/res/MiniPath.exe.manifest
+++ b/minipath/res/MiniPath.exe.manifest
@@ -17,7 +17,7 @@
true/PM
PerMonitorV2,PerMonitor
- true
+
false
SegmentHeap
diff --git a/src/DynStrg.c b/src/DynStrg.c
index def411d8b..003deb842 100644
--- a/src/DynStrg.c
+++ b/src/DynStrg.c
@@ -165,12 +165,18 @@ static void ReAllocW(STRINGW* pstr, size_t len)
}
}
else if (pstr->alloc_length < alloc_len) {
- pstr->data = ReAllocBuffer(pstr->data, alloc_len, false);
- pstr->alloc_length = LengthOfBuffer(pstr->data);
- assert("inconsistent data 1" && (alloc_len == pstr->alloc_length));
- /// original memory block is moved, so data_length is not touched
- assert("inconsistent data 2" && (alloc_len > pstr->data_length));
- pstr->data[pstr->data_length] = WCHR_NULL; // ensure terminating zero
+ // apply 1.5x growth factor to amortize repeated reallocations
+ size_t const grow_len = pstr->alloc_length + (pstr->alloc_length >> 1);
+ size_t const new_alloc = min_s(max_s(alloc_len, grow_len), STRINGW_MAX_CCH);
+ LPWSTR new_data = ReAllocBuffer(pstr->data, new_alloc, false);
+ if (new_data) {
+ pstr->data = new_data;
+ pstr->alloc_length = LengthOfBuffer(pstr->data);
+ assert("inconsistent data 1" && (pstr->alloc_length >= alloc_len));
+ /// original memory block is moved, so data_length is not touched
+ assert("inconsistent data 2" && (pstr->alloc_length > pstr->data_length));
+ pstr->data[pstr->data_length] = WCHR_NULL; // ensure terminating zero
+ }
}
else {
ZeroMemory(&(pstr->data[pstr->data_length]), (pstr->alloc_length - pstr->data_length) * sizeof(wchar_t));
@@ -185,7 +191,7 @@ static void AllocCopyW(STRINGW* pstr, STRINGW* pDest, size_t copy_len, size_t co
{
ReAllocW(pDest, new_len);
StringCchCopyNW(pDest->data, pDest->alloc_length, (pstr->data + copy_index), copy_len);
- pDest->data_length = StrlenW(pstr->data);
+ pDest->data_length = StrlenW(pDest->data);
}
}
// ----------------------------------------------------------------------------
@@ -801,7 +807,7 @@ size_t STRAPI StrgRemoveCh(HSTRINGW hstr, const wchar_t chRemove)
}
if (dest)
*dest = WCHR_NULL;
- count = (int)(ptrdiff_t)(source - dest);
+ count = (size_t)(source - dest);
pstr->data_length -= count;
return count;
@@ -862,7 +868,7 @@ void STRAPI StrgToUpper(HSTRINGW hstr)
ReAllocW(pstr, 0);
}
if (pstr->data)
- _wcsupr_s(pstr->data, pstr->data_length);
+ _wcsupr_s(pstr->data, pstr->data_length + 1);
}
// ----------------------------------------------------------------------------
@@ -876,7 +882,7 @@ void STRAPI StrgToLower(HSTRINGW hstr)
ReAllocW(pstr, 0);
}
if (pstr->data)
- _wcslwr_s(pstr->data, pstr->data_length);
+ _wcslwr_s(pstr->data, pstr->data_length + 1);
}
// ----------------------------------------------------------------------------
diff --git a/src/PathLib.c b/src/PathLib.c
index 4b1a2200f..51f8c8052 100644
--- a/src/PathLib.c
+++ b/src/PathLib.c
@@ -1269,7 +1269,7 @@ LPCWSTR PTHAPI Path_FindExtension(const HPATHL hpth)
if (!hstr)
return NULL;
- LPWSTR wbuf = StrgWriteAccessBuf(hstr, 0);
+ StrgWriteAccessBuf(hstr, 0);
//size_t const cch = StrgGetAllocLength(hstr);
///PathXCchFindExtension(StrgGet(hstr), StrgGetAllocLength(hstr), &pext);
@@ -1277,7 +1277,7 @@ LPCWSTR PTHAPI Path_FindExtension(const HPATHL hpth)
LPWSTR const pdot = pfile ? wcsrchr(pfile, L'.') : NULL;
StrgSanitize(hstr);
- return pdot ? pdot : &wbuf[StrgGetLength(hstr)];
+ return pdot ? pdot : &StrgGet(hstr)[StrgGetLength(hstr)];
}
// ----------------------------------------------------------------------------
@@ -1371,10 +1371,16 @@ bool PTHAPI Path_IsUNC(const HPATHL hpth)
if (!hstr || StrgIsEmpty(hstr))
return false;
- WCHAR maxPath[MAX_PATH];
- StringCchCopy(maxPath, COUNTOF(maxPath), StrgGet(hstr));
+ LPCWSTR const p = StrgGet(hstr);
+ if (!p || p[0] != L'\\' || p[1] != L'\\')
+ return false;
+
+ // extended-length UNC prefix (\\?\UNC\ or \\.\UNC\)
+ if (wcsstr(p, PATHUNC_PREFIX1) == p || wcsstr(p, PATHUNC_PREFIX2) == p)
+ return true;
- return PathIsUNC(maxPath);
+ // regular UNC (\\server\share), but not \\?\ or \\.\ long path prefix
+ return (p[2] != L'?' && p[2] != L'.');
}
// ----------------------------------------------------------------------------
@@ -1405,14 +1411,17 @@ bool PTHAPI Path_SetFileAttributes(HPATHL hpth, DWORD dwAttributes)
bool PTHAPI Path_StripToRoot(HPATHL hpth_in_out)
{
HSTRINGW hstr_io = ToHStrgW(hpth_in_out);
- if (!hstr_io)
+ if (!hstr_io || StrgIsEmpty(hstr_io))
return false;
- WCHAR maxPath[MAX_PATH];
- StringCchCopy(maxPath, COUNTOF(maxPath), StrgGet(hstr_io));
+ LPCWSTR const root_end = _Path_SkipRoot(hpth_in_out);
+ LPCWSTR const path_start = StrgGet(hstr_io);
+ if (!root_end || !path_start || root_end == path_start)
+ return false;
- if (PathStripToRoot(maxPath)) {
- Path_Reset(hpth_in_out, maxPath);
+ size_t const root_len = (size_t)(root_end - path_start);
+ if (root_len > 0 && root_len <= StrgGetLength(hstr_io)) {
+ StrgDelete(hstr_io, root_len, StrgGetLength(hstr_io) - root_len);
return true;
}
return false;
@@ -1538,14 +1547,20 @@ void PTHAPI Path_GetDisplayName(LPWSTR lpszDisplayName, const DWORD cchDisplayNa
size_t const fnam_len = Path_GetLength(hfnam_pth);
if (fnam_len >= cchDisplayName) {
- // Explorer like display name ???
- HPATHL hpart_pth = Path_Allocate(PathGet(hfnam_pth));
- HSTRINGW hpart_str = ToHStrgW(hpart_pth);
- size_t const split_idx = (cchDisplayName >> 1) - wcslen(PATHDSPL_INFIX);
- StrgDelete(hpart_str, split_idx, (fnam_len - cchDisplayName + (wcslen(PATHDSPL_INFIX) << 1)));
- StrgInsert(hpart_str, split_idx, PATHDSPL_INFIX);
- StringCchCopyW(lpszDisplayName, cchDisplayName, StrgGet(hpart_str));
- Path_Release(hpart_pth);
+ // Explorer like display name
+ size_t const infix_len = wcslen(PATHDSPL_INFIX);
+ if (cchDisplayName > infix_len * 2 + 2) {
+ HPATHL hpart_pth = Path_Allocate(PathGet(hfnam_pth));
+ HSTRINGW hpart_str = ToHStrgW(hpart_pth);
+ size_t const split_idx = (cchDisplayName >> 1) - infix_len;
+ StrgDelete(hpart_str, split_idx, (fnam_len - cchDisplayName + (infix_len << 1)));
+ StrgInsert(hpart_str, split_idx, PATHDSPL_INFIX);
+ StringCchCopyW(lpszDisplayName, cchDisplayName, StrgGet(hpart_str));
+ Path_Release(hpart_pth);
+ }
+ else {
+ StringCchCopyW(lpszDisplayName, cchDisplayName, PathGet(hfnam_pth));
+ }
}
else {
StringCchCopyW(lpszDisplayName, cchDisplayName, PathGet(hfnam_pth));