1 /**
2     Win32 Implementation for nulib.system.mod
3 
4     Copyright:
5         Copyright © 2025, Kitsunebi Games
6         Copyright © 2025, Inochi2D Project
7     
8     License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9     Authors:   Luna Nielsen
10 */
11 module nulib.win32.mod;
12 import numem;
13 
14 extern(Windows):
15 
16 /**
17     A symbol within a section.
18 */
19 struct Symbol {
20 @nogc:
21 
22     /**
23         Name of the symbol
24     */
25     string name;
26     
27     /**
28         Pointer to the symbol
29     */
30     void* ptr;
31 }
32 
33 /**
34     Information about sections.
35 */
36 struct SectionInfo {
37 @nogc:
38     
39     /**
40         Segment of the section, if applicable.
41     */
42     string segment;
43 
44     /**
45         Name of the section
46     */
47     string section;
48 
49     /**
50         Start address of the section
51     */
52     void* start;
53 
54     /**
55         End address of the section
56     */
57     void* end;
58 }
59 
60 //
61 //          FOR IMPLEMENTORS
62 //
63 
64 private extern (C):
65 
66 /*
67     Optional helper that defines whether paths are in UTF-16 format.
68 */
69 bool _nu_module_utf16_paths() @nogc nothrow { 
70     return true;
71 }
72 
73 /*
74     Function which loads a module from the given path.
75 
76     This is implemented by backends to abstract away the OS intricaices
77     of loading modules and finding symbols within.
78 */
79 export
80 void* _nu_module_open(void* path) @nogc nothrow {
81 
82     // NOTE:    This if check simulates dlopen on UNIX.
83     //          We want UNIX semantics here because they're nice.
84     if (!path)
85         return GetModuleHandleW(null);
86     
87     return LoadLibraryW(cast(const(wchar)*)path);
88 }
89 
90 /*
91     Function which loads a module from the given path.
92 
93     This is implemented by backends to abstract away the OS intricaices
94     of loading modules and finding symbols within.
95 */
96 void _nu_module_close(void* module_) @nogc nothrow {
97     cast(void) FreeLibrary(module_);
98 }
99 
100 /*
101     Function which finds a symbol within a given module
102 
103     This is implemented by backends to abstract away the OS intricaices
104     of loading modules and finding symbols within.
105 */
106 void* _nu_module_get_symbol(void* module_, const(char)* symbol) @nogc nothrow {
107     return GetProcAddress(module_, symbol);
108 }
109 
110 /*
111     Function which gets the "base address" of the module.
112 
113     This is backend implementation defined; but is what should allow
114     $(D _nu_module_enumerate_sections) and $(D _nu_module_enumerate_symbols)
115     to function.
116 */
117 void* _nu_module_get_base_address(void* module_) @nogc nothrow {
118     
119     // Just in case we should also just verify the headers
120     // here.
121     void* handle = _nu_module_win32_verify_pe(cast(void*)module_);
122     if (!handle)
123         return null;
124 
125     return cast(void*)module_;
126 }
127 
128 /*
129     Function which enumerates all of the sections within a module.
130 
131     This is implemented by backends to abstract away the OS intricaices
132     of loading modules and finding symbols within.
133 */
134 SectionInfo[] _nu_module_enumerate_sections(void* base) @nogc nothrow {
135     void* handle = _nu_module_win32_verify_pe(base);
136     if (!handle)
137         return null;
138 
139     // NOTE:    If a file has no sections we're probably reading a very messed up
140     //          PE file, so we'll stop there.
141     //
142     // NOTE:    shdrs is a slice into existing memory, DO NOT FREE.
143     PeSectionHeader[] shdrs = _nu_module_win32_get_section_headers(handle);
144     if (shdrs.length == 0)
145         return null;
146     
147     SectionInfo[] sections;
148     sections = sections.nu_resize(shdrs.length);
149     nogc_initialize(sections[]);
150 
151     foreach(i, ref PeSectionHeader shdr; shdrs) {
152         sections[i] = SectionInfo(
153             null,
154             _nu_module_get_sect_name(shdr.mName),
155             base+shdr.mVirtualAddress,
156             base+shdr.mVirtualAddress+shdr.mVirtualSize 
157         );
158     }
159     return sections;
160 }
161 
162 /*
163     Function which enumerates all of the exported symbols.
164 
165     This is implemented by backends to abstract away the OS intricaices
166     of loading modules and finding symbols within.
167 */
168 Symbol[] _nu_module_enumerate_symbols(void* base) @nogc nothrow {
169     void* handle = _nu_module_win32_verify_pe(base);
170     if (!handle)
171         return null;
172     
173     // No exports since we don't have a table for it.
174     PEHeader* nthdr = cast(PEHeader*) handle;
175     if (nthdr.mSizeOfOptionalHeader == 0)
176         return null;
177 
178     uint[2] exportsRVA;
179     ushort pMagic = *cast(ushort*)(handle+PEHeader.sizeof);
180     if (pMagic == 0x010b) {
181         
182         // PE32 (32-bit)
183         exportsRVA = *cast(uint[2]*)(handle+PEHeader.sizeof+Pe32OptionalHeader.sizeof);
184     } else if (pMagic == 0x020b) {
185         
186         // PE32+ (64-bit)
187         exportsRVA = *cast(uint[2]*)(handle+PEHeader.sizeof+Pe32PlusOptionalHeader.sizeof);
188 
189     } else {
190 
191         // Unrecognized PE format.
192         return null;
193     }
194 
195     PeExportsTableHeader* exportsTable = cast(PeExportsTableHeader*)(base+exportsRVA[0]);
196 
197     // NOTE:    Create and *zero initialize* the symbols array.
198     //          we wouldn't want to point to garbage data.
199     Symbol[] syms;
200     syms = syms.nu_resize(exportsTable.mNamePointerEntryCount);
201     nogc_initialize(syms[]);
202 
203     uint* nameOffsetTable = cast(uint*)(base+exportsTable.mNamePointerRVA);
204     uint* addressTable = cast(uint*)(base+exportsTable.mAddressTableRVA);
205     ushort* ordinalTable = cast(ushort*)(base+exportsTable.mNameOrdinalTableRVA);
206     foreach(i; 0..exportsTable.mNamePointerEntryCount) {
207         const(char)* name = cast(const(char)*)(base+nameOffsetTable[i]);
208         uint ordinal = ordinalTable[i]+exportsTable.mOrdinalBase;
209 
210         // Out of range.
211         if (ordinal >= exportsTable.mAddressTableEntryCount)
212             continue;
213 
214         // NOTE:    Foreign exports should be nulled.
215         //          They are denoted by referencing strings *within*
216         //          the export table.
217         void* paddr = base+addressTable[ordinal];
218         if (paddr >= base+exportsRVA[0] && paddr <= base+exportsRVA[0]+exportsRVA[1])
219             paddr = null;
220 
221         syms[i] = Symbol(_fstrz(name), paddr);
222     }
223     return syms;
224 }
225 
226 //
227 //          SYSTEM SPECIFIC IMPLEMENTATIONS
228 //
229 
230 void* _nu_module_win32_verify_pe(void* base) @nogc nothrow {
231     if (!base) return null;
232     ushort magic = *cast(ushort*) base;
233 
234     // 'MZ' DOS Header
235     if (magic != 0x5A4D)
236         return null;
237 
238 
239     // NOTE:    e_lfanew is at offset 0x3C, always.
240     //          it being reserved in the DOS header, means we can make our
241     //          life simpler by simply just passing that in from the start.
242     int e_lfanew = *cast(int*)(base + 0x3C);
243     void* handle = base + e_lfanew;
244     PEHeader* nthdr = cast(PEHeader*) handle;
245 
246     // Not a PE file?
247     if (nthdr.mMagic != 0x00004550)
248         return null;
249 
250     // No sections? what?
251     if (nthdr.mNumberOfSections == 0)
252         return null;
253 
254     return handle;
255 }
256 
257 string _nu_module_get_sect_name(ref char[8] name) @nogc nothrow {
258     foreach(i; 0..name.length) {
259         if (name[i] == '\0')
260             return cast(string)name[0..i];
261     }
262     return cast(string)name;
263 }
264 
265 PeSectionHeader[] _nu_module_win32_get_section_headers(void* handle) @nogc nothrow {
266     PEHeader* nthdr = cast(PEHeader*) handle;
267     PeSectionHeader* start = cast(PeSectionHeader*)(handle+PEHeader.sizeof+nthdr.mSizeOfOptionalHeader);
268     return start[0..nthdr.mNumberOfSections];
269 }
270 
271 
272 //
273 //          HELPERS
274 //
275 
276 extern(D)
277 string _fstrz(const(char)* str) @nogc nothrow {
278     size_t i = 0;
279     while(str[i] != '\0') i++;
280     return cast(string)str[0..i];
281 }
282 
283 //
284 //          LOCAL BINDINGS
285 //
286 
287 extern (Windows) extern void* GetModuleHandleW(const(wchar)*) @nogc nothrow;
288 extern (Windows) extern void* LoadLibraryW(const(wchar)*) @nogc nothrow;
289 extern (Windows) extern void* GetProcAddress(void*, const(char)*) @nogc nothrow;
290 extern (Windows) extern bool FreeLibrary(void*) @nogc nothrow;
291 
292 // Info taken from https://wiki.osdev.org/PE
293 struct PEHeader {
294 align(1):
295     uint mMagic;
296     ushort mMachine;
297     ushort mNumberOfSections;
298     uint mTimeDateStamp;
299     uint mPointerToSymbolTable;
300     uint mNumberOfSymbols;
301     ushort mSizeOfOptionalHeader;
302     ushort mCharacteristics;
303 }
304 
305 // 0x010b - PE32
306 static assert(Pe32OptionalHeader.sizeof == 96);
307 align(1) struct Pe32OptionalHeader {
308 align(1):
309 	ushort mMagic;
310 	ubyte  mMajorLinkerVersion;
311 	ubyte  mMinorLinkerVersion;
312 	uint mSizeOfCode;
313 	uint mSizeOfInitializedData;
314 	uint mSizeOfUninitializedData;
315 	uint mAddressOfEntryPoint;
316 	uint mBaseOfCode;
317 	uint mBaseOfData;
318 	uint mImageBase;
319 	uint mSectionAlignment;
320 	uint mFileAlignment;
321 	ushort mMajorOperatingSystemVersion;
322 	ushort mMinorOperatingSystemVersion;
323 	ushort mMajorImageVersion;
324 	ushort mMinorImageVersion;
325 	ushort mMajorSubsystemVersion;
326 	ushort mMinorSubsystemVersion;
327 	uint mWin32VersionValue;
328 	uint mSizeOfImage;
329 	uint mSizeOfHeaders;
330 	uint mCheckSum;
331 	ushort mSubsystem;
332 	ushort mDllCharacteristics;
333 	uint mSizeOfStackReserve;
334 	uint mSizeOfStackCommit;
335 	uint mSizeOfHeapReserve;
336 	uint mSizeOfHeapCommit;
337 	uint mLoaderFlags;
338 	uint mNumberOfRvaAndSizes;
339 }
340 
341 // 0x020b - PE32+ (64 bit)
342 static assert(Pe32PlusOptionalHeader.sizeof == 112);
343 align(1) struct Pe32PlusOptionalHeader {
344 align(1):
345 	ushort mMagic; 
346 	ubyte  mMajorLinkerVersion;
347 	ubyte  mMinorLinkerVersion;
348 	uint mSizeOfCode;
349 	uint mSizeOfInitializedData;
350 	uint mSizeOfUninitializedData;
351 	uint mAddressOfEntryPoint;
352 	uint mBaseOfCode;
353 	ulong mImageBase;
354 	uint mSectionAlignment;
355 	uint mFileAlignment;
356 	ushort mMajorOperatingSystemVersion;
357 	ushort mMinorOperatingSystemVersion;
358 	ushort mMajorImageVersion;
359 	ushort mMinorImageVersion;
360 	ushort mMajorSubsystemVersion;
361 	ushort mMinorSubsystemVersion;
362 	uint mWin32VersionValue;
363 	uint mSizeOfImage;
364 	uint mSizeOfHeaders;
365 	uint mCheckSum;
366 	ushort mSubsystem;
367 	ushort mDllCharacteristics;
368 	ulong mSizeOfStackReserve;
369 	ulong mSizeOfStackCommit;
370 	ulong mSizeOfHeapReserve;
371 	ulong mSizeOfHeapCommit;
372 	uint mLoaderFlags;
373 	uint mNumberOfRvaAndSizes;
374 }
375 
376 // size 40 bytes
377 static assert(PeExportsTableHeader.sizeof == 40);
378 struct PeExportsTableHeader {
379     uint mExportFlags;
380     uint mTimeDateStamp;
381     ushort mMajorVersion;
382     ushort mMinorVersion;
383     uint mNameRVA;
384     uint mOrdinalBase;
385     uint mAddressTableEntryCount;
386     uint mNamePointerEntryCount;
387     uint mAddressTableRVA;
388     uint mNamePointerRVA;
389     uint mNameOrdinalTableRVA;
390 }
391 
392 // size 40 bytes
393 static assert(PeSectionHeader.sizeof == 40);
394 struct PeSectionHeader {
395     char[8] mName;
396     uint mVirtualSize;
397     uint mVirtualAddress;
398     uint mSizeOfRawData;
399     uint mPointerToRawData;
400     uint mPointerToRelocations;
401     uint mPointerToLinenumbers;
402     ushort mNumberOfRelocations;
403     ushort mNumberOfLinenumbers;
404     uint mCharacteristics;
405 }