/******************************************************************** Minecraft: Pocket Edition - Decompilation Project Copyright (C) 2023 iProgramInCpp The following code is licensed under the BSD 1 clause license. SPDX-License-Identifier: BSD-1-Clause ********************************************************************/ #define WIN32_LEAN_AND_MEAN #include #include #include #include "GameMods.hpp" #include "AppPlatform_win32.hpp" #include "LoggerWin32.hpp" #include "thirdparty/GL/GL.hpp" #include "thirdparty/stb_image.h" #include "thirdparty/stb_image_write.h" AppPlatform_win32::AppPlatform_win32() { m_WindowTitle = "ReMinecraftPE"; // just assume an 854x480 window for now: m_ScreenWidth = C_DEFAULT_SCREEN_WIDTH; m_ScreenHeight = C_DEFAULT_SCREEN_HEIGHT; m_UserInputStatus = -1; m_bIsFocused = false; m_bGrabbedMouse = false; m_bActuallyGrabbedMouse = false; m_bWasUnfocused = false; m_bShiftPressed = false; m_MouseDiffX = 0, m_MouseDiffY = 0; // This initializes the Logger singleton to use the Windows-specific variant // If we didn't initialize it here, the Minecraft class would have our back m_pLogger = new LoggerWin32(); m_pSoundSystem = nullptr; } AppPlatform_win32::~AppPlatform_win32() { SAFE_DELETE(m_pSoundSystem); // DELETE THIS LAST SAFE_DELETE(m_pLogger); } void AppPlatform_win32::initSoundSystem() { if (!m_pSoundSystem) m_pSoundSystem = new SoundSystemDS(); else LOG_E("Trying to initialize SoundSystem more than once!"); } int AppPlatform_win32::checkLicense() { // we own the game!! return 1; } void AppPlatform_win32::buyGame() { MessageBoxA(GetHWND(), "Buying the game!", getWindowTitle(), MB_OK | MB_ICONINFORMATION); } void AppPlatform_win32::saveScreenshot(const std::string& fileName, int width, int height) { int npixels = width * height; uint32_t* pixels = new uint32_t[npixels]; glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); stbi_flip_vertically_on_write(true); // Verify if the folder exists for saving screenshots and // create it if it doesn't // Kinda inefficient but I didn't want to be too intrusive // -Vruk // https://stackoverflow.com/a/22182041 // https://stackoverflow.com/a/8901001 CHAR mypicturespath[MAX_PATH]; HRESULT result = SHGetFolderPathA(NULL, CSIDL_MYPICTURES, NULL, SHGFP_TYPE_CURRENT, mypicturespath); static char str[MAX_PATH]; if (result == S_OK) sprintf(str, "%s\\%s", mypicturespath, "MCPE"); else sprintf(str, "%s\\%s", ".", "Screenshots"); // https://stackoverflow.com/a/8233867 DWORD ftyp = GetFileAttributesA(str); DWORD error = GetLastError(); if (error == ERROR_PATH_NOT_FOUND || error == ERROR_FILE_NOT_FOUND || error == ERROR_INVALID_NAME) { // https://stackoverflow.com/a/22182041 CreateDirectoryA(str, NULL); } char fullpath[MAX_PATH]; sprintf(fullpath, "%s\\%s", str, fileName.c_str()); stbi_write_png(fullpath, width, height, 4, pixels, width * 4); delete[] pixels; } void AppPlatform_win32::createUserInput() { m_UserInput.clear(); m_UserInputStatus = -1; switch (m_DialogType) { case DLG_CREATE_WORLD: { // some placeholder for now m_UserInput.push_back("New World"); m_UserInput.push_back("123456"); m_UserInputStatus = 1; break; } } } void AppPlatform_win32::showDialog(eDialogType type) { m_DialogType = type; } std::string AppPlatform_win32::getDateString(int time) { time_t tt = time; struct tm t; // using the _s variant. For a different platform there's gmtime_r. This is not directly portable however. gmtime_s(&t, &tt); //format it with strftime char buf[2048]; strftime(buf, sizeof buf, "%b %d %Y %H:%M:%S", &t); //strftime(buf, sizeof buf, "%a %b %d %H:%M:%S %Z %Y", &t); return std::string(buf); } Texture AppPlatform_win32::loadTexture(const std::string& str, bool b) { std::string realPath = str; if (realPath.size() && realPath[0] == '/') // trim it off realPath = realPath.substr(1); realPath = "assets/" + realPath; FILE* f = fopen(realPath.c_str(), "rb"); if (!f) { LOG_E("File %s couldn't be opened", realPath.c_str()); _error: const std::string msg = "Error loading " + realPath + ". Did you unzip the Minecraft assets?"; MessageBoxA(GetHWND(), msg.c_str(), getWindowTitle(), MB_OK); if (f) fclose(f); ::exit(1); } int width = 0, height = 0, channels = 0; stbi_uc* img = stbi_load_from_file(f, &width, &height, &channels, STBI_rgb_alpha); if (!img) { LOG_E("File %s couldn't be loaded via stb_image", realPath.c_str()); goto _error; } uint32_t* img2 = new uint32_t[width * height]; memcpy(img2, img, width * height * sizeof(uint32_t)); stbi_image_free(img); img = nullptr; fclose(f); return Texture(width, height, img2, 1, 0); } bool AppPlatform_win32::isTouchscreen() { return false; } bool AppPlatform_win32::hasFileSystemAccess() { return true; } std::string AppPlatform_win32::getPatchData() { std::ifstream ifs("assets/patches/patch_data.txt"); if (!ifs.is_open()) return ""; std::stringstream ss; ss << ifs.rdbuf(); return ss.str(); } void AppPlatform_win32::setScreenSize(int width, int height) { m_ScreenWidth = width; m_ScreenHeight = height; } void AppPlatform_win32::recenterMouse() { // only recenter the mouse if it's grabbed if (!m_bGrabbedMouse) return; // If we aren't the foreground window, return if (GetForegroundWindow() != GetHWND()) { m_bWasUnfocused = true; return; } POINT oldPos = { 0, 0 }; GetCursorPos(&oldPos); RECT rect; GetClientRect(GetHWND(), &rect); POINT offs = { m_ScreenWidth / 2, m_ScreenHeight / 2 }; ClientToScreen(GetHWND(), &offs); SetCursorPos(offs.x, offs.y); // Note. The only reason we do it this way instead of // using the Mouse class is because, after SetCursorPos, // we'll get an event on our window telling us "hey, the // user has moved their cursor back to the center! Move // the camera back as well", causing a camera that just // refuses to move // If we were unfocused last frame, ignore the diff data we have. if (!m_bWasUnfocused) { m_MouseDiffX -= offs.x - oldPos.x; m_MouseDiffY -= offs.y - oldPos.y; } m_bWasUnfocused = false; } void AppPlatform_win32::setMouseGrabbed(bool b) { m_bGrabbedMouse = b; if (m_bActuallyGrabbedMouse == (b && m_bIsFocused)) return; if (!b || !m_bIsFocused) { m_bActuallyGrabbedMouse = false; //show the cursor ShowCursor(TRUE); //unconfine it ClipCursor(NULL); clearDiff(); } else { m_bActuallyGrabbedMouse = true; //hide the cursor ShowCursor(FALSE); //confine it to our client area RECT rect; GetClientRect(GetHWND(), &rect); POINT offs = { 0, 0 }; ClientToScreen(GetHWND(), &offs); rect.left += offs.x; rect.top += offs.y; rect.right += offs.x; rect.bottom += offs.y; ClipCursor(&rect); // set the cursor pos to the middle of the screen recenterMouse(); clearDiff(); } } void AppPlatform_win32::getMouseDiff(int& x, int& y) { x = m_MouseDiffX; y = m_MouseDiffY; } void AppPlatform_win32::clearDiff() { m_MouseDiffX = m_MouseDiffY = 0; } void AppPlatform_win32::updateFocused(bool focused) { m_bIsFocused = focused; setMouseGrabbed(m_bGrabbedMouse); } MouseButtonType AppPlatform_win32::GetMouseButtonType(UINT iMsg) { switch (iMsg) { case WM_LBUTTONUP: case WM_LBUTTONDOWN: return BUTTON_LEFT; case WM_RBUTTONUP: case WM_RBUTTONDOWN: return BUTTON_RIGHT; case WM_MBUTTONUP: case WM_MBUTTONDOWN: return BUTTON_MIDDLE; case WM_MOUSEWHEEL: return BUTTON_SCROLLWHEEL; default: return BUTTON_NONE; } } bool AppPlatform_win32::GetMouseButtonState(UINT iMsg, WPARAM wParam) { bool result; switch (iMsg) { case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: result = true; break; case WM_MOUSEWHEEL: { short wheelDelta = GET_WHEEL_DELTA_WPARAM(wParam); if (wheelDelta > 0) { // "A positive value indicates that the wheel was rotated forward, away from the user." result = false; } else { // "A negative value indicates that the wheel was rotated backward, toward the user." result = true; } break; } default: result = false; break; } return result; } Keyboard::KeyState AppPlatform_win32::GetKeyState(UINT iMsg) { switch (iMsg) { case WM_KEYUP: return Keyboard::KeyState::UP; case WM_KEYDOWN: default: return Keyboard::KeyState::DOWN; } }