diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c89248fd..22422081 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -36,7 +36,7 @@ jobs:
uses: actions/checkout@v4
- name: Dump Registry
- run: src/grab-registry.bat
+ run: src/tools/grab-registry.bat
- name: Upload Artifacts
uses: actions/upload-artifact@v4
diff --git a/README.md b/README.md
index e2a94c99..8a8cd688 100644
--- a/README.md
+++ b/README.md
@@ -87,7 +87,7 @@ cmake --workflow --preset=release
## Dumping the Registry
The emulator needs a registry dump to run, otherwise it will print `Bad hive file` errors.
-You can create one by running the src/grab-registry.bat script as administrator.
+You can create one by running the src/tools/grab-registry.bat script as administrator.
This will create a `registry` folder that needs to be placed in the working directory of the emulator.
## Running Tests
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index ff79e18f..70bef540 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,4 +1,5 @@
add_subdirectory(common)
+add_subdirectory(tools)
add_subdirectory(emulator)
add_subdirectory(unicorn-emulator)
add_subdirectory(windows-emulator)
diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt
new file mode 100644
index 00000000..d6d59c3a
--- /dev/null
+++ b/src/tools/CMakeLists.txt
@@ -0,0 +1,4 @@
+if(WIN32)
+ add_subdirectory(dump-apiset)
+endif()
+
diff --git a/src/tools/dump-apiset/CMakeLists.txt b/src/tools/dump-apiset/CMakeLists.txt
new file mode 100644
index 00000000..9a3bf180
--- /dev/null
+++ b/src/tools/dump-apiset/CMakeLists.txt
@@ -0,0 +1,17 @@
+file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
+ *.cpp
+ *.hpp
+ *.rc
+)
+
+list(SORT SRC_FILES)
+
+add_executable(dump-apiset ${SRC_FILES})
+
+momo_assign_source_group(${SRC_FILES})
+
+target_link_libraries(dump-apiset PRIVATE
+ emulator-common
+)
+
+momo_strip_target(dump-apiset)
diff --git a/src/tools/dump-apiset/dump-apiset.cpp b/src/tools/dump-apiset/dump-apiset.cpp
new file mode 100644
index 00000000..e4f3f33e
--- /dev/null
+++ b/src/tools/dump-apiset/dump-apiset.cpp
@@ -0,0 +1,118 @@
+#include
+#include "platform/platform.hpp"
+#include "utils/compression.hpp"
+#include "utils/io.hpp"
+#include
+#include
+
+void print_apiset(PAPI_SET_NAMESPACE apiSetMap);
+void create_header_file(const std::vector& data);
+
+__forceinline PVOID GetCurrentProcessPeb()
+{
+#ifdef _WIN64
+ return (PVOID)__readgsqword(0x60);
+#else
+ return (PVOID)__readfsdword(0x30);
+#endif
+}
+
+int main()
+{
+ printf("Dump API-SET\n");
+ printf("------------\n\n");
+
+ const auto peb = (PPEB64)GetCurrentProcessPeb();
+ const auto apiSetMap = (PAPI_SET_NAMESPACE)(peb->ApiSetMap);
+
+ printf("APISET: 0x%p\n", apiSetMap);
+ printf("Version: %d\n", apiSetMap->Version);
+ printf("Size: %08X\n", apiSetMap->Size);
+ printf("Flags: %08X\n", apiSetMap->Flags);
+ printf("Count: %d\n", apiSetMap->Count);
+ printf("EntryOffset: %08X\n", apiSetMap->EntryOffset);
+ printf("HashOffset: %08X\n", apiSetMap->HashOffset);
+ printf("HashFactor: %08X\n", apiSetMap->HashFactor);
+ // print_apiset(apiSetMap);
+
+ // Compress the API-SET binary blob
+ const auto* dataPtr = reinterpret_cast(apiSetMap);
+ std::vector buffer(dataPtr, dataPtr + apiSetMap->Size);
+ auto compressed = utils::compression::zlib::compress(buffer);
+ if (compressed.empty())
+ {
+ printf("Failed to compress API-SET\n");
+ return 1;
+ }
+
+ // Dump the API-SET binary blob to disk
+ utils::io::write_file("api-set.bin", compressed, false);
+ printf("\nWrote API-SET to api-set.bin\n");
+ // create_header_file(compressed);
+
+ return 0;
+}
+
+void print_apiset(PAPI_SET_NAMESPACE apiSetMap)
+{
+ for (ULONG i = 0; i < apiSetMap->Count; i++)
+ {
+ auto entry = (PAPI_SET_NAMESPACE_ENTRY)((ULONG_PTR)apiSetMap + apiSetMap->EntryOffset +
+ i * sizeof(API_SET_NAMESPACE_ENTRY));
+ // printf(" Flags: %08X\n", entry->Flags);
+ // printf(" NameOffset: %08X\n", entry->NameOffset);
+ // printf(" NameLength: %08X\n", entry->NameLength);
+ // printf(" HashedLength: %08X\n", entry->HashedLength);
+ // printf(" ValueOffset: %08X\n", entry->ValueOffset);
+ // printf(" ValueCount: %08X\n", entry->ValueCount);
+
+ std::wstring name((wchar_t*)((ULONG_PTR)apiSetMap + entry->NameOffset), entry->NameLength / sizeof(wchar_t));
+ printf("-----------\n[%05d]: Contract Name: %ls\n", i, name.data());
+
+ for (ULONG x = 0; x < entry->ValueCount; x++)
+ {
+ auto value =
+ (PAPI_SET_VALUE_ENTRY)((ULONG_PTR)apiSetMap + entry->ValueOffset + x * sizeof(API_SET_VALUE_ENTRY));
+ // printf(" Value %d\n", x);
+ // printf(" Flags: %08X\n", value->Flags);
+ // printf(" NameOffset: %08X\n", value->NameOffset);
+ // printf(" NameLength: %08X\n", value->NameLength);
+ // printf(" ValueOffset: %08X\n", value->ValueOffset);
+ // printf(" ValueLength: %08X\n", value->ValueLength);
+
+ std::wstring hostName((wchar_t*)((ULONG_PTR)apiSetMap + value->NameOffset),
+ value->NameLength / sizeof(wchar_t));
+ std::wstring altName((wchar_t*)((ULONG_PTR)apiSetMap + value->ValueOffset),
+ value->ValueLength / sizeof(wchar_t));
+ printf(" HostName: %ls - AltName: %ls\n", hostName.empty() ? L"" : hostName.data(),
+ altName.empty() ? L"" : altName.data());
+ }
+ }
+}
+
+// Internal
+void create_header_file(const std::vector& data)
+{
+ FILE* output;
+ fopen_s(&output, "api-set.h", "w");
+ if (!output)
+ {
+ printf("Failed to create output file\n");
+ return;
+ }
+
+ fprintf(output, "#pragma once\n\n");
+ fprintf(output, "#include \n\n");
+ fprintf(output, "const uint8_t api_set_blob[] = {\n");
+ for (ULONG i = 0; i < data.size(); i++)
+ {
+ fprintf(output, "0x%02X, ", data[i]);
+ if (i % 16 == 15)
+ {
+ fprintf(output, "\n");
+ }
+ }
+
+ fprintf(output, "};\n");
+ fclose(output);
+}
\ No newline at end of file
diff --git a/src/grab-registry.bat b/src/tools/grab-registry.bat
similarity index 100%
rename from src/grab-registry.bat
rename to src/tools/grab-registry.bat