#include "avs/game.h" #include "popn.h" #include "bi3a_hook.h" #include #include #include "hooks/graphics/graphics.h" #include "rawinput/rawinput.h" #include "util/detour.h" #include "util/fileutils.h" #include "util/libutils.h" #include "util/utils.h" #include "cfg/button.h" #include "cfg/api.h" #include "hooks/setupapihook.h" #include "hooks/sleephook.h" #include "launcher/launcher.h" #include "launcher/logger.h" #include "misc/eamuse.h" #include "util/sysutils.h" #include "io.h" #include "util/deferlog.h" #include "misc/nativetouchhook.h" #include "misc/wintouchemu.h" namespace games::popn { bool SHOW_PIKA_MONITOR_WARNING = false; bool NATIVE_TOUCH = false; #if SPICE64 && !SPICE_XP const HMONITOR FAKE_MONITOR_HMONITOR = (HMONITOR)-1; constexpr LONG FAKE_MONITOR_OFFSET_X_Y = -999999; constexpr LONG FAKE_MONITOR_WIDTH = 1280; constexpr LONG FAKE_MONITOR_HEIGHT = 800; // pin 0 = buttons 1-3, size 9 (but it's actually triple that in reality - game passes in wrong length), values 0-0xFF // pin 1 = left pop (red pop kun), size 16, values 0-0xFF // pin 2 = right pop (blue pop kun), size 16, values 0-0xFF // pin 3 = title, size 44, values 0-0xFF // pin 4 = speaker, size 40, values 0-0x7F - U region only // pin 5 = monitor, size 18, values 0-0x7F - U region only // pin 6 = control panel, size 28, values 0-0x7F - U region only // pin 7 = cabinet, size 22, values 0-0x7F - U region only tapeledutils::tape_led TAPELED_MAPPING[POPN_TAPELED_TOTAL] = { { // [0] button lights: these are button lights and handled specially 0, 0, 0, 0, 0, "Invalid" }, { // [1] red pop-kun 5, // 5*3; game reports 16 but make it 15 Lights::popn_lights_t::RedPopKun_R, Lights::popn_lights_t::RedPopKun_G, Lights::popn_lights_t::RedPopKun_B, 0xFF, "Red Pop-Kun" }, { // [2] blue pop-kun 5, // 5*3; game reports 16 but make it 15 Lights::popn_lights_t::BluePopKun_R, Lights::popn_lights_t::BluePopKun_G, Lights::popn_lights_t::BluePopKun_B, 0xFF, "Blue Pop-Kun" }, { // [3] top LED 14, // 14*3; game reports 44 but make it 42 Lights::popn_lights_t::TopLED_R, Lights::popn_lights_t::TopLED_G, Lights::popn_lights_t::TopLED_B, 0xFF, "Top" }, { // [4] speakers 13, // 13*3; game reports 40 but make it 39 Lights::popn_lights_t::Speaker_G, Lights::popn_lights_t::Speaker_R, Lights::popn_lights_t::Speaker_B, 0x7F, "Speakers" }, { // [5] monitor 6, Lights::popn_lights_t::Monitor_G, Lights::popn_lights_t::Monitor_R, Lights::popn_lights_t::Monitor_B, 0x7F, "Monitor" }, { // [6] control panel 9, // 9*3; game reports 28 Lights::popn_lights_t::ControlPanel_G, Lights::popn_lights_t::ControlPanel_R, Lights::popn_lights_t::ControlPanel_B, 0x7F, "Control Panel" }, { // [7] Cabinet 7, // 7*3; game reports 22 Lights::popn_lights_t::Cabinet_G, Lights::popn_lights_t::Cabinet_R, Lights::popn_lights_t::Cabinet_B, 0x7F, "Cabinet" }, }; static decltype(GetDisplayConfigBufferSizes) *GetDisplayConfigBufferSizes_orig = nullptr; static decltype(QueryDisplayConfig) *QueryDisplayConfig_orig = nullptr; static decltype(DisplayConfigGetDeviceInfo) *DisplayConfigGetDeviceInfo_orig = nullptr; static UINT32 pNumPathArrayElements_original = 0; static UINT32 pNumModeInfoArrayElements_original = 0; static LONG WINAPI GetDisplayConfigBufferSizes_hook( UINT32 Flags, UINT32 *pNumPathArrayElements, UINT32 *pNumModeInfoArrayElements) { const auto ret = GetDisplayConfigBufferSizes_orig(Flags, pNumPathArrayElements, pNumModeInfoArrayElements); if (FAKE_SUBSCREEN_ADAPTER) { log_misc("popn", "GetDisplayConfigBufferSizes returning fake monitor paths and modes"); pNumPathArrayElements_original = *pNumPathArrayElements; pNumModeInfoArrayElements_original = *pNumModeInfoArrayElements; *pNumPathArrayElements += 1; *pNumModeInfoArrayElements += 2; } return ret; } static LONG WINAPI QueryDisplayConfig_hook( UINT32 flags, UINT32* numPathArrayElements, DISPLAYCONFIG_PATH_INFO* pathArray, UINT32* numModeInfoArrayElements, DISPLAYCONFIG_MODE_INFO* modeInfoArray, DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId) { if (!FAKE_SUBSCREEN_ADAPTER) { return QueryDisplayConfig_orig( flags, numPathArrayElements, pathArray, numModeInfoArrayElements, modeInfoArray, currentTopologyId); } // call original to fill in real monitor info UINT32 num_paths = pNumPathArrayElements_original; UINT32 num_modes = pNumModeInfoArrayElements_original; const auto ret = QueryDisplayConfig_orig( flags, &num_paths, pathArray, &num_modes, modeInfoArray, currentTopologyId); if (ret != ERROR_SUCCESS) { log_warning("popn", "QueryDisplayConfig failed with error code {}", ret); return ret; } log_misc("popn", "QueryDisplayConfig returning fake monitor paths and modes"); // insert a fake path DISPLAYCONFIG_PATH_INFO *path = &pathArray[pNumPathArrayElements_original]; *path = {}; path->flags = DISPLAYCONFIG_PATH_ACTIVE; path->sourceInfo.adapterId.HighPart = -1; path->sourceInfo.adapterId.LowPart = -1; path->sourceInfo.id = -1; path->sourceInfo.modeInfoIdx = pNumModeInfoArrayElements_original; path->sourceInfo.statusFlags = DISPLAYCONFIG_SOURCE_IN_USE; path->targetInfo.adapterId.HighPart = -1; path->targetInfo.adapterId.LowPart = -1; path->targetInfo.id = -2; path->targetInfo.modeInfoIdx = pNumModeInfoArrayElements_original + 1; path->targetInfo.outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI; path->targetInfo.rotation = DISPLAYCONFIG_ROTATION_IDENTITY; path->targetInfo.scaling = DISPLAYCONFIG_SCALING_IDENTITY; path->targetInfo.refreshRate.Numerator = 60000; path->targetInfo.refreshRate.Denominator = 1000; path->targetInfo.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE; path->targetInfo.targetAvailable = true; path->targetInfo.statusFlags = DISPLAYCONFIG_TARGET_IN_USE; // insert fake mode source DISPLAYCONFIG_MODE_INFO *mode_source = &modeInfoArray[pNumModeInfoArrayElements_original]; *mode_source = {}; mode_source->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_SOURCE; mode_source->id = -1; mode_source->adapterId.HighPart = -1; mode_source->adapterId.LowPart = -1; mode_source->sourceMode.width = FAKE_MONITOR_WIDTH; mode_source->sourceMode.height = FAKE_MONITOR_HEIGHT; mode_source->sourceMode.pixelFormat = DISPLAYCONFIG_PIXELFORMAT_32BPP; mode_source->sourceMode.position.x = FAKE_MONITOR_OFFSET_X_Y; mode_source->sourceMode.position.y = FAKE_MONITOR_OFFSET_X_Y; // insert fake mode target DISPLAYCONFIG_MODE_INFO *mode_target = &modeInfoArray[pNumModeInfoArrayElements_original+1]; *mode_target = {}; mode_target->infoType = DISPLAYCONFIG_MODE_INFO_TYPE_TARGET; mode_target->id = -2; mode_target->adapterId.HighPart = -1; mode_target->adapterId.LowPart = -1; return ret; } static LONG WINAPI DisplayConfigGetDeviceInfo_hook(DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket) { if (requestPacket == nullptr) { return DisplayConfigGetDeviceInfo_orig(requestPacket); } // fake monitor if (FAKE_SUBSCREEN_ADAPTER && (requestPacket->id == static_cast(-1) || requestPacket->id == static_cast(-2)) && requestPacket->adapterId.HighPart == static_cast(-1) && requestPacket->adapterId.LowPart == static_cast(-1)) { log_misc( "popn", "DisplayConfigGetDeviceInfo hook hit for fake monitor, type={}, size={}, id={}, luid={}/{}", static_cast(requestPacket->type), requestPacket->size, requestPacket->id, requestPacket->adapterId.HighPart, requestPacket->adapterId.LowPart); if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) { const auto target = reinterpret_cast(requestPacket); target->flags.value = 0; target->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI; target->connectorInstance = 0; wcscpy(target->monitorFriendlyDeviceName, L"Spice Fake Monitor"); wcscpy(target->monitorDevicePath, L"\\\\?\\SpiceFakeMonitor"); } else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) { const auto source = reinterpret_cast(requestPacket); // value must match WrappedIDirect3D9::GetAdapterIdentifier wcscpy(source->viewGdiDeviceName, L"\\\\.\\DISPLAY_SPICE_FAKE_1"); } else { log_fatal( "popn", "unexpected device info type {} for fake monitor", static_cast(requestPacket->type)); } return ERROR_SUCCESS; } const auto ret = DisplayConfigGetDeviceInfo_orig(requestPacket); log_misc( "popn", "DisplayConfigGetDeviceInfo returned {}, type={}, size={}, id={}, luid={}/{}", ret, static_cast(requestPacket->type), requestPacket->size, requestPacket->id, requestPacket->adapterId.HighPart, requestPacket->adapterId.LowPart); if (ret == ERROR_SUCCESS) { if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME) { const auto sourceName = reinterpret_cast(requestPacket); log_misc( "popn", "... name={}", ws2s(sourceName->viewGdiDeviceName)); } else if (requestPacket->type == DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME) { const auto targetName = reinterpret_cast(requestPacket); log_misc( "popn", "... flags={}, outputTechnology: {}, connectorInstance={}, friendlyname={} path={}", targetName->flags.value, static_cast(targetName->outputTechnology), targetName->connectorInstance, ws2s(targetName->monitorFriendlyDeviceName), ws2s(targetName->monitorDevicePath)); // need to fix up some values for the primary monitor... const auto monitors = sysutils::enumerate_monitors(); for (const auto& monitor : monitors) { if (monitor.id == targetName->header.id && monitor.adapter_id_HighPart == targetName->header.adapterId.HighPart && monitor.adapter_id_LowPart == targetName->header.adapterId.LowPart) { if (monitor.is_primary) { targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL; targetName->connectorInstance = 2; log_info( "popn", "... overriding primary monitor ({}) to pretend to be DP port #2", monitor.display_name); } else { // TODO: is this what the game expects for subscreen? // TODO: what if there are 3+ monitors? targetName->outputTechnology = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI; targetName->connectorInstance = 0; } break; } } } } return ret; } #endif #if !SPICE64 static int __cdecl usbCheckAlive() { return 1; } static int __cdecl usbCheckSecurityNew() { return 0; } static int __cdecl usbCoinGet() { return eamuse_coin_get_stock(); } static int __cdecl usbCoinMode() { return 0; } static int __cdecl usbEnd() { return 0; } static int __cdecl usbFirmResult() { return 0; } static int __cdecl usbGetKEYID() { return 0; } static int __cdecl usbGetSecurity() { return 0; } static int __cdecl usbLamp(uint32_t lamp_bits) { // get lights auto &lights = get_lights(); // bit scan static const size_t mapping[] = { Lights::Button1, Lights::Button2, Lights::Button3, Lights::Button4, Lights::Button5, Lights::Button6, Lights::Button7, Lights::Button8, Lights::Button9, Lights::HiLamp1, Lights::HiLamp2, Lights::HiLamp3, Lights::HiLamp4, Lights::HiLamp5, Lights::LeftLamp1, Lights::LeftLamp2, Lights::RightLamp1, Lights::RightLamp2, }; // sw lamps for (size_t i = 0; i < 9; i++) { float value = (lamp_bits & (1 << (i + 23))) ? 1.f : 0.f; GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value); } // neon lamps for (size_t i = 0; i < 5; i++) { float value = (lamp_bits & (1 << i)) ? 1.f : 0.f; GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i + 9]), value); } // screen lamps for (size_t i = 0; i < 4; i++) { float value = (lamp_bits & (1 << (i + 8))) ? 1.f : 0.f; GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i + 14]), value); } // flush output RI_MGR->devices_flush_output(); // return no error return 0; } static int __cdecl usbPadRead(unsigned int *pad_bits) { // get buttons auto &buttons = get_buttons(); // reset *pad_bits = 0; if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Service))) { *pad_bits |= 1 << 6; } if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::Test))) { *pad_bits |= 1 << 7; } if (GameAPI::Buttons::getState(RI_MGR, buttons.at(Buttons::CoinMech))) { *pad_bits |= 1 << 22; } // buttons static const size_t mapping[] = { Buttons::Button1, Buttons::Button2, Buttons::Button3, Buttons::Button4, Buttons::Button5, Buttons::Button6, Buttons::Button7, Buttons::Button8, Buttons::Button9, }; for (size_t i = 0; i < 9; i++) { if (GameAPI::Buttons::getState(RI_MGR, buttons.at(mapping[i]))) { *pad_bits |= 1 << (8 + i); } } // return no error return 0; } static int __cdecl usbPadReadLast(uint8_t *a1) { memset(a1, 0, 40); return 0; } static int __cdecl usbSecurityInit() { return 0; } static int __cdecl usbSecurityInitDone() { return 0; } static int __cdecl usbSecuritySearch() { return 0; } static int __cdecl usbSecuritySearchDone() { return 0; } static int __cdecl usbSecuritySelect() { return 0; } static int __cdecl usbSecuritySelectDone() { return 0; } static int __cdecl usbSetExtIo(uint32_t rgb_bits) { // get lights auto &lights = get_lights(); // bit scan static const size_t mapping[] = { Lights::WooferLED_R, Lights::WooferLED_G, Lights::WooferLED_B, }; for (size_t i = 0; i < 3; i++) { float value = (rgb_bits & (1 << (i + 1))); GameAPI::Lights::writeLight(RI_MGR, lights.at(mapping[i]), value); } // flush output RI_MGR->devices_flush_output(); return 0; } static int __cdecl usbStart() { return 0; } static int __cdecl usbWdtReset() { return 0; } static int __cdecl usbWdtStart() { return 0; } static int __cdecl usbWdtStartDone() { return 0; } static decltype(DeleteFileA)* DeleteFileA_real = nullptr; static BOOL WINAPI DeleteFileA_hook(LPCSTR lpFileName) { // fantasia deletes a bunch of drmfs files in dll_entry_init if (string_begins_with(lpFileName, "D:\\L39\\contents")) { return true; } return DeleteFileA_real(lpFileName); } static decltype(mixerSetControlDetails)* mixerSetControlDetails_real = nullptr; static MMRESULT WINAPI mixerSetControlDetails_hook(HMIXEROBJ hmxobj, LPMIXERCONTROLDETAILS pmxcd, DWORD fdwDetails) { mixerSetControlDetails_real(hmxobj, pmxcd, fdwDetails); return MMSYSERR_NOERROR; } #endif static bool log_hook(void *user, const std::string &data, logger::Style style, std::string &out) { // get rid of the log spam if (data.find(" I:CabinetSettingFile: label:") != std::string::npos) { out.clear(); return true; } else { return false; } } POPNGame::POPNGame() : Game("Pop'n Music") { logger::hook_add(log_hook, this); } POPNGame::~POPNGame() { logger::hook_remove(log_hook, this); } void POPNGame::pre_attach() { Game::pre_attach(); #if !SPICE64 // only needed for older versions (19-21, inclusive) but create them anyway since 21 and above are all M39 fileutils::dir_create_recursive_log("popn", "dev\\raw\\bookkeeping"); fileutils::dir_create_recursive_log("popn", "dev\\raw\\ranking"); fileutils::dir_create_recursive_log("popn", "dev\\raw\\settings"); // load without resolving references // makes game not trigger DLLMain which results in an error due to missing EZUSB device LoadLibraryExW((MODULE_PATH / "ezusb.dll").c_str(), nullptr, DONT_RESOLVE_DLL_REFERENCES); #endif } void POPNGame::attach() { Game::attach(); #if !SPICE64 /* * Fast Boot (TM) Patch * Game tries to create some directories and if it fails it will sleep for 1 second * We make it sleep 1ms each time instead */ hooks::sleep::init(999, 1); DeleteFileA_real = detour::iat_try("DeleteFileA", DeleteFileA_hook, avs::game::DLL_INSTANCE); mixerSetControlDetails_real = detour::iat_try("mixerSetControlDetails", mixerSetControlDetails_hook, avs::game::DLL_INSTANCE); // EZUSB hooks HINSTANCE ezusb = libutils::try_library("ezusb.dll"); if (ezusb) { detour::inline_hook((void *) usbCheckAlive, libutils::try_proc(ezusb, "?usbCheckAlive@@YAHXZ")); detour::inline_hook((void *) usbCheckSecurityNew, libutils::try_proc(ezusb, "?usbCheckSecurityNew@@YAHH@Z")); detour::inline_hook((void *) usbCoinGet, libutils::try_proc(ezusb, "?usbCoinGet@@YAHH@Z")); detour::inline_hook((void *) usbCoinMode, libutils::try_proc(ezusb, "?usbCoinMode@@YAHH@Z")); detour::inline_hook((void *) usbEnd, libutils::try_proc(ezusb, "?usbEnd@@YAHXZ")); detour::inline_hook((void *) usbFirmResult, libutils::try_proc(ezusb, "?usbFirmResult@@YAHXZ")); detour::inline_hook((void *) usbGetKEYID, libutils::try_proc(ezusb, "?usbGetKEYID@@YAHPAEH@Z")); detour::inline_hook((void *) usbGetSecurity, libutils::try_proc(ezusb, "?usbGetSecurity@@YAHHPAE@Z")); detour::inline_hook((void *) usbLamp, libutils::try_proc(ezusb, "?usbLamp@@YAHH@Z")); detour::inline_hook((void *) usbPadRead, libutils::try_proc(ezusb, "?usbPadRead@@YAHPAK@Z")); detour::inline_hook((void *) usbPadReadLast, libutils::try_proc(ezusb, "?usbPadReadLast@@YAHPAE@Z")); detour::inline_hook((void *) usbSecurityInit, libutils::try_proc(ezusb, "?usbSecurityInit@@YAHXZ")); detour::inline_hook((void *) usbSecurityInitDone, libutils::try_proc(ezusb, "?usbSecurityInitDone@@YAHXZ")); detour::inline_hook((void *) usbSecuritySearch, libutils::try_proc(ezusb, "?usbSecuritySearch@@YAHXZ")); detour::inline_hook((void *) usbSecuritySearchDone, libutils::try_proc(ezusb, "?usbSecuritySearchDone@@YAHXZ")); detour::inline_hook((void *) usbSecuritySelect, libutils::try_proc(ezusb, "?usbSecuritySelect@@YAHH@Z")); detour::inline_hook((void *) usbSecuritySelectDone, libutils::try_proc(ezusb, "?usbSecuritySelectDone@@YAHXZ")); detour::inline_hook((void *) usbSetExtIo, libutils::try_proc(ezusb, "?usbSetExtIo@@YAHH@Z")); detour::inline_hook((void *) usbStart, libutils::try_proc(ezusb, "?usbStart@@YAHH@Z")); detour::inline_hook((void *) usbWdtReset, libutils::try_proc(ezusb, "?usbWdtReset@@YAHXZ")); detour::inline_hook((void *) usbWdtStart, libutils::try_proc(ezusb, "?usbWdtStart@@YAHH@Z")); detour::inline_hook((void *) usbWdtStartDone, libutils::try_proc(ezusb, "?usbWdtStartDone@@YAHXZ")); } #endif #if SPICE64 && !SPICE_XP if (!GRAPHICS_WINDOWED && D3D9_ADAPTER.has_value()) { SHOW_PIKA_MONITOR_WARNING = true; log_warning( "popn", "\n\n" "!!! using -dxmainadapter option is NOT recommended due to known !!!\n" "!!! compatibility issues with the game !!!\n" "!!! !!!\n" "!!! * game may launch in wrong resolution or refresh rate !!!\n" "!!! * touch / mouse input may stop working in subscreen / overlay !!!\n" "!!! !!!\n" "!!! recommendation is to use the Change Main Monitor (-mainmonitor) !!!\n" "!!! option instead of -dxmainadapter !!!\n\n" ); deferredlogs::defer_error_messages({ "-dxmainadapter option is NOT recommended due to known compatibility ", " issues with the game!", " * game may launch in wrong resolution or refresh rate", " * touch / mouse input may stop working in subscreen / overlay", " use Change Main Monitor (-mainmonitor) option instead" }); } // monitor hook GetDisplayConfigBufferSizes_orig = detour::iat_try("GetDisplayConfigBufferSizes", GetDisplayConfigBufferSizes_hook, avs::game::DLL_INSTANCE); QueryDisplayConfig_orig = detour::iat_try("QueryDisplayConfig", QueryDisplayConfig_hook, avs::game::DLL_INSTANCE); DisplayConfigGetDeviceInfo_orig = detour::iat_try("DisplayConfigGetDeviceInfo", DisplayConfigGetDeviceInfo_hook, avs::game::DLL_INSTANCE); // GUID_DEVCLASS_USB = {86E0D1E0-11D0-89B0-00A0C9054129} SETUPAPI_SETTINGS settings{}; settings.class_guid[0] = 0x86E0D1E0; settings.class_guid[1] = 0x11D08089; settings.class_guid[2] = 0x0008E49C; settings.class_guid[3] = 0x731F303E; // BIO3 - 8058 (maybe U region cabs with more lights?) // BIO3 - 8054 (probably J region?) // BIO2 - 8050 (need bi2x_hook, everything will work except buttons 1-9) const char property[] = "1CCF(8058)_000"; const char property_hardwareid[] = "USB\\VID_1CCF&PID_8058&MI_00\\000"; memcpy(settings.property_devicedesc, property, sizeof(property)); memcpy(settings.property_hardwareid, property_hardwareid, sizeof(property_hardwareid)); setupapihook_init(avs::game::DLL_INSTANCE); setupapihook_add(settings); bi3a_hook_init(); // note: if for whatever reason you want to use BIO2, the game needs to be patched // check popn.dll and look for static table that looks like this: // 00000001 00000003 00000001 (button 1) // 00000002 00000004 00000001 (button 2) // 00000004 00000005 00000001 (button 3) // 00000008 00000006 00000001 (button 4) // 00000010 00000007 00000001 (button 5) // 00000020 00000008 00000001 (button 6) // 00000040 00000009 00000001 (button 7) // 00000080 0000000A 00000001 (button 8) // 00000100 0000000B 00000001 (button 9) // set third column to 0 and it will work with BIO2 if (!GRAPHICS_WINDOWED) { if (NATIVE_TOUCH) { nativetouchhook::hook(avs::game::DLL_INSTANCE); } else { wintouchemu::FORCE = true; wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true; wintouchemu::hook_title_ends("", "Main Screen", avs::game::DLL_INSTANCE); } } sysutils::hook_EnumDisplayDevicesA(); #endif } }