The client code is terrible. . . You can rewrite GetSaveFileNameA to suit your needs, as I wasn't concerned about the client; my goal was to practice with the kernel.
Reading occurs through physical memory if the driver can't obtain the dtb or, at worst, the udtb. Unfortunately, reading occurs through MmCopyVirtualMemory (the logic is implemented in Kernel/src/main.cpp ~#L64, Client/src/main.cpp ~#123). Also, protection was added in case the driver can't obtain the dtb during IOCTL_ATTACH_PROCESS, but the client for some reason tries to read through physical memory. (The correct solution would be for the driver to automatically read through virtual memory to avoid providing too much code to the client (but I didn't think of that :))
In the driver, you just need to change DriverEntry and SLINK_NAME to SLINK_NAME_MAPPER, unless you want to load the driver, of course. Via mappers
It's also advisable to clean up after finishing work with the driver (IOCTL_DETTACH_PROCESS) to avoid leaving static references to EPROCESS (a leak). The structures are implemented in (Driver->src/def.h, Client->src/driver.h)