commit 70b00c5c386c1954a0d3025022b3ce306386daf0 Author: fallenoak Date: Mon Jan 2 13:17:18 2023 -0600 chore: initial commit diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..53eba03 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,17 @@ +root = true + +[src/**/*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[test/**/*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 0000000..87162da --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,35 @@ +name: PR + +on: [pull_request] + +jobs: + build: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + + strategy: + fail-fast: false + matrix: + config: + - name: Ubuntu Latest (GCC) + os: ubuntu-latest + build_type: Release + cc: "gcc" + cxx: "g++" + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Prepare + run: mkdir build + + - name: Configure + run: cd build && cmake .. -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} + + - name: Build + run: cd build && make install + + - name: Test + run: cd build && ./dist/bin/WhoaTest diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..3abf2b3 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,38 @@ +name: Push + +on: + push: + branches: + - master + +jobs: + build: + name: ${{ matrix.config.name }} + runs-on: ${{ matrix.config.os }} + + strategy: + fail-fast: false + matrix: + config: + - name: Ubuntu Latest (GCC) + os: ubuntu-latest + build_type: Release + cc: "gcc" + cxx: "g++" + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Prepare + run: mkdir build + + - name: Configure + run: cd build && cmake .. -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} + + - name: Build + run: cd build && make install + + - name: Test + run: cd build && ./dist/bin/WhoaTest diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..767470e --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.vscode +.idea + +/build +/cmake-build-* + diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f76ddac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,12 @@ +[submodule "lib/common"] + path = lib/common + url = https://github.com/whoahq/common.git +[submodule "lib/squall"] + path = lib/squall + url = https://github.com/whoahq/squall.git +[submodule "lib/typhoon"] + path = lib/typhoon + url = https://github.com/whoahq/typhoon.git +[submodule "lib/system"] + path = lib/system + url = https://github.com/whoahq/system.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..1c2e992 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,50 @@ +cmake_minimum_required(VERSION 3.1) + +if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) + message(FATAL_ERROR + "In-source builds not allowed. + Please make a new directory (called a build directory) and run CMake from there. + You may need to remove CMakeCache.txt." + ) +endif() + +# OS variables +set(CMAKE_OSX_DEPLOYMENT_TARGET "10.14" CACHE STRING "macOS Deployment Target" FORCE) + +# Project +project(whoa) + +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "dist" CACHE PATH "Installation prefix" FORCE) +endif() + +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + +set(CMAKE_CXX_STANDARD 11) + +set(CMAKE_BUILD_TYPE Debug) + +# Some templates abuse offsetof +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof") + +# OS defines +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + add_definitions( + -DGL_SILENCE_DEPRECATION + ) +endif() + +include(lib/system/cmake/system.cmake) + +# Threads +if(WHOA_SYSTEM_LINUX OR WHOA_SYSTEM_MAC) + set(CMAKE_THREAD_PREFER_PTHREAD TRUE) + find_package(Threads REQUIRED) +endif() + +add_subdirectory(lib) +add_subdirectory(src) +add_subdirectory(test) +add_subdirectory(vendor) diff --git a/README.md b/README.md new file mode 100644 index 0000000..7dd7737 --- /dev/null +++ b/README.md @@ -0,0 +1,47 @@ +# Whoa + +Welcome to Whoa, a reimplementation of World of Warcraft 3.3.5a (build 12340) in C++11. + +## Supported Platforms + +Currently, macOS 10.14+ is supported, including recent versions of macOS on M1 processors. Support for Linux is in progress. Support for Windows is coming soonâ„¢. + +## Building + +To build, ensure you have a recent version of CMake installed, and run the following from the `whoa` directory: + +``` +mkdir build && cd build +cmake .. +make install +``` + +Assuming all went well, you should see a `dist/bin` directory appear in the `build` directory. The `bin` directory will contain a `whoa` executable. + +## Running + +Whoa doesn't currently support reading from MPQ archives. Instead, it assumes you are launching the Whoa executable from the root of a fully extracted MPQ archive set for World of Warcraft 3.3.5a (build 12340). You can obtain a valid set of MPQ archives to extract by installing World of Warcraft 3.3.5a from legally purchased original install media. Whoa does not provide any copy of game data. + +Assuming all goes well, you should be greeted by the login screen, complete with its flying dragon animation loop. + +Whoa is very much a work-in-progress: it does not fully connect to a login server, does not play back sound or music, and does not support customizing settings. These things will be supported over time. + +## FAQ + +**Why?** + +It's fascinating to explore the development practices used to build a modern major video game. + +**Why 3.3.5a?** + +The game and its libraries have become significantly more complex in the intervening 10+ years. By picking 3.3.5a, it's possible to imagine this implementation will eventually be complete. + +**Can I use this in my own development projects?** + +It's probably a bad idea. The game is closed source, and this project is in no way official. + +## Legal + +This project is released into the public domain. + +World of Warcraft: Wrath of the Lich King ©2008 Blizzard Entertainment, Inc. All rights reserved. Wrath of the Lich King is a trademark, and World of Warcraft, Warcraft and Blizzard Entertainment are trademarks or registered trademarks of Blizzard Entertainment, Inc. in the U.S. and/or other countries. diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt new file mode 100644 index 0000000..54bc291 --- /dev/null +++ b/lib/CMakeLists.txt @@ -0,0 +1,4 @@ +add_subdirectory(common) +add_subdirectory(squall) +add_subdirectory(system) +add_subdirectory(typhoon) diff --git a/lib/common b/lib/common new file mode 160000 index 0000000..ff3c28b --- /dev/null +++ b/lib/common @@ -0,0 +1 @@ +Subproject commit ff3c28bbd910983147ddc1d99d83c482ae23df7d diff --git a/lib/squall b/lib/squall new file mode 160000 index 0000000..e3b0c35 --- /dev/null +++ b/lib/squall @@ -0,0 +1 @@ +Subproject commit e3b0c356adc0abb4ad5c549951a5423b6edaf64d diff --git a/lib/system b/lib/system new file mode 160000 index 0000000..f886ab5 --- /dev/null +++ b/lib/system @@ -0,0 +1 @@ +Subproject commit f886ab5bc35b2e0d968baa8dec3faaccf385fbc3 diff --git a/lib/typhoon b/lib/typhoon new file mode 160000 index 0000000..36d1777 --- /dev/null +++ b/lib/typhoon @@ -0,0 +1 @@ +Subproject commit 36d177759d273ae599e7c99920466e4886d57a41 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..f47fd48 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,13 @@ +add_subdirectory(app) +add_subdirectory(async) +add_subdirectory(client) +add_subdirectory(event) +add_subdirectory(glue) +add_subdirectory(gx) +add_subdirectory(math) +add_subdirectory(model) +add_subdirectory(net) +add_subdirectory(sound) +add_subdirectory(ui) +add_subdirectory(util) +add_subdirectory(world) diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt new file mode 100644 index 0000000..acc0c2a --- /dev/null +++ b/src/app/CMakeLists.txt @@ -0,0 +1,48 @@ +if(WHOA_SYSTEM_MAC) + file(GLOB PRIVATE_SOURCES "mac/*.cpp" "mac/*.mm") + + set_source_files_properties(${PRIVATE_SOURCES} + PROPERTIES COMPILE_FLAGS "-x objective-c++" + ) + + add_executable(Whoa ${PRIVATE_SOURCES}) + + target_link_libraries(Whoa + PRIVATE + client + event + gx + net + util + "-framework AppKit" + "-framework Carbon" + "-framework IOKit" + ) + + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/mac/MainMenu.nib DESTINATION "bin") +endif() + +if(WHOA_SYSTEM_LINUX) + file(GLOB PRIVATE_SOURCES "linux/*.cpp") + + add_executable(Whoa ${PRIVATE_SOURCES}) + + target_link_libraries(Whoa + PRIVATE + client + event + gx + net + util + ) +endif() + +target_include_directories(Whoa + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +# Windows executables yet to be done +if(WHOA_SYSTEM_MAC OR WHOA_SYSTEM_LINUX) + install(TARGETS Whoa DESTINATION "bin") +endif() diff --git a/src/app/linux/Wowre.cpp b/src/app/linux/Wowre.cpp new file mode 100644 index 0000000..0f49342 --- /dev/null +++ b/src/app/linux/Wowre.cpp @@ -0,0 +1,11 @@ +#include "client/Client.hpp" + +int main(int argc, char* argv[]) { + // TODO + + CommonMain(); + + // TODO + + return 0; +} diff --git a/src/app/mac/EngineGLLayerView.h b/src/app/mac/EngineGLLayerView.h new file mode 100644 index 0000000..b36d95d --- /dev/null +++ b/src/app/mac/EngineGLLayerView.h @@ -0,0 +1,16 @@ +#ifndef APP_MAC_ENGINE_GL_LAYER_VIEW_H +#define APP_MAC_ENGINE_GL_LAYER_VIEW_H + +#include "gx/gll/GLLayerView.h" +#include +#include + +@interface EngineGLLayerView : GLLayerView + +- (void)insertText:(id)string; + +- (void)keyDown:(NSEvent*)event; + +@end + +#endif diff --git a/src/app/mac/EngineGLLayerView.mm b/src/app/mac/EngineGLLayerView.mm new file mode 100644 index 0000000..c44170b --- /dev/null +++ b/src/app/mac/EngineGLLayerView.mm @@ -0,0 +1,41 @@ +#include "app/mac/EngineGLLayerView.h" +#include "app/mac/MacClient.h" +#include "event/Input.hpp" +#include + +@implementation EngineGLLayerView + +- (void)insertText:(id)string { + // TODO +} + +- (void)keyDown:(NSEvent*)event { + uint32_t keyCode = event.keyCode; + + MacClient::CheckKeyboardLayout(); + + if (keyCode <= 0x7F) { + uint32_t key = MacClient::s_keyConversion[keyCode]; + + if (key != KEY_NONE) { + OsQueuePut(OS_INPUT_KEY_DOWN, key, 0, 0, 0); + } + } + + if (MacClient::GetTextInputEnabled()) { + auto events = [NSArray arrayWithObject:event]; + [self interpretKeyEvents:events]; + } else { + EventRef eventRef = static_cast(const_cast([event eventRef])); + + uint8_t chr; + + if (GetEventParameter(eventRef, 'kchr', 'TEXT', 0, 1, 0, &chr) == noErr) { + if (chr > 0x1F && chr <= 0x7E) { + OsQueuePut(OS_INPUT_CHAR, chr, 1, 0, 0); + } + } + } +} + +@end diff --git a/src/app/mac/MacClient.h b/src/app/mac/MacClient.h new file mode 100644 index 0000000..cfc7b39 --- /dev/null +++ b/src/app/mac/MacClient.h @@ -0,0 +1,30 @@ +#ifndef APP_MAC_MAC_CLIENT_H +#define APP_MAC_MAC_CLIENT_H + +#include "event/Event.hpp" +#include + +class MacClient { + public: + enum ClipboardAction { + ClipboardUndo = 1, + ClipboardCut = 2, + ClipboardCopy = 3, + ClipboardPaste = 4, + ClipboardSelectAll = 5 + }; + + static void* s_currentKeyboardLayout; + static KEY s_specialKeyConversion[128]; + static KEY s_keyConversion[128]; + + static void CheckKeyboardLayout(void); + static double GetMouseSpeed(void); + static bool GetTextInputEnabled(void); + static void InitializeKeyConversion(void); + static bool IsUsingGLLayer(void); + static void PostClipboardKeyEvents(MacClient::ClipboardAction); + static void SetMouseCoalescingEnabled(bool); +}; + +#endif diff --git a/src/app/mac/MacClient.mm b/src/app/mac/MacClient.mm new file mode 100644 index 0000000..c59fa4c --- /dev/null +++ b/src/app/mac/MacClient.mm @@ -0,0 +1,375 @@ +#include "app/mac/MacClient.h" +#include "event/Input.hpp" +#include "os/Compat.hpp" +#include +#include +#include +#include +#include +#include + +void* MacClient::s_currentKeyboardLayout = nullptr; + +KEY MacClient::s_specialKeyConversion[128] = { + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_1, // kVK_ANSI_1 (0x12) + KEY_2, // kVK_ANSI_2 (0x13) + KEY_3, // kVK_ANSI_3 (0x14) + KEY_4, // kVK_ANSI_4 (0x15) + KEY_6, // kVK_ANSI_6 (0x16) + KEY_5, // kVK_ANSI_5 (0x17) + KEY_NONE, + KEY_9, // kVK_ANSI_9 (0x19) + KEY_7, // kVK_ANSI_7 (0x1A) + KEY_NONE, + KEY_8, // kVK_ANSI_8 (0x1C) + KEY_0, // kVK_ANSI_0 (0x1D) + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_ENTER, // kVK_Return (0x24) + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_TAB, // kVK_Tab (0x30) + KEY_SPACE, // kVK_Space (0x31) + KEY_NONE, + KEY_BACKSPACE, // kVK_Delete (0x33) + KEY_NONE, + KEY_ESCAPE, // kVK_Escape (0x35) + KEY_NONE, + KEY_NONE, + KEY_LSHIFT, // kVK_Shift (0x38) + KEY_CAPSLOCK, // kVK_CapsLock (0x39) + KEY_LALT, // kVK_Option (0x3A) + KEY_LCONTROL, // kVK_Control (0x3B) + KEY_RSHIFT, // kVK_RightShift (0x3C) + KEY_RALT, // kVK_RightOption (0x3D) + KEY_RCONTROL, // kVK_RightControl (0x3E) + KEY_NONE, + KEY_F17, // kVK_F17 (0x40) + KEY_NUMPAD_DECIMAL, // kVK_ANSI_KeypadDecimal (0x41) + KEY_NONE, + KEY_NUMPAD_MULTIPLY, // kVK_ANSI_KeypadMultiply (0x43) + KEY_NONE, + KEY_NUMPAD_PLUS, // kVK_ANSI_KeypadPlus (0x45) + KEY_NONE, + KEY_NUMLOCK, // kVK_ANSI_KeypadClear (0x47) + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_NUMPAD_DIVIDE, // kVK_ANSI_KeypadDivide (0x4B) + KEY_ENTER, // kVK_ANSI_KeypadEnter (0x4C) + KEY_NONE, + KEY_NUMPAD_MINUS, // kVK_ANSI_KeypadMinus (0x4E) + KEY_F18, // kVK_F18 (0x4F) + KEY_F19, // kVK_F19 (0x50) + KEY_NUMPAD_EQUALS, // kVK_ANSI_KeypadEquals (0x51) + KEY_NUMPAD0, // kVK_ANSI_Keypad0 (0x52) + KEY_NUMPAD1, // kVK_ANSI_Keypad1 (0x53) + KEY_NUMPAD2, // kVK_ANSI_Keypad2 (0x54) + KEY_NUMPAD3, // kVK_ANSI_Keypad3 (0x55) + KEY_NUMPAD4, // kVK_ANSI_Keypad4 (0x56) + KEY_NUMPAD5, // kVK_ANSI_Keypad5 (0x57) + KEY_NUMPAD6, // kVK_ANSI_Keypad6 (0x58) + KEY_NUMPAD7, // kVK_ANSI_Keypad7 (0x59) + KEY_NONE, + KEY_NUMPAD8, // kVK_ANSI_Keypad8 (0x5B) + KEY_NUMPAD9, // kVK_ANSI_Keypad9 (0x5C) + KEY_NONE, + KEY_NONE, + KEY_NONE, + KEY_F5, // kVK_F5 (0x60) + KEY_F6, // kVK_F6 (0x61) + KEY_F7, // kVK_F7 (0x62) + KEY_F3, // kVK_F3 (0x63) + KEY_F8, // kVK_F8 (0x64) + KEY_F9, // kVK_F9 (0x65) + KEY_NONE, + KEY_F11, // kVK_F11 (0x67) + KEY_NONE, + KEY_F13, // kVK_F13 (0x69) + KEY_F16, // kVK_F16 (0x6A) + KEY_F14, // kVK_F14 (0x6B) + KEY_NONE, + KEY_F10, // kVK_F10 (0x6D) + KEY_NONE, + KEY_F12, // kVK_F12 (0x6F) + KEY_NONE, + KEY_F15, // kVK_F15 (0x71) + KEY_INSERT, // kVK_Help (0x72) + KEY_HOME, // kVK_Home (0x73) + KEY_PAGEUP, // kVK_PageUp (0x74) + KEY_DELETE, // kVK_ForwardDelete (0x75) + KEY_F4, // kVK_F4 (0x76) + KEY_END, // kVK_End (0x77) + KEY_F2, // kVK_F2 (0x78) + KEY_PAGEDOWN, // kVK_PageDown (0x79) + KEY_F1, // kVK_F1 (0x7A) + KEY_LEFT, // kVK_LeftArrow (0x7B) + KEY_RIGHT, // kVK_RightArrow (0x7C) + KEY_DOWN, // kVK_DownArrow (0x7D) + KEY_UP, // kVK_UpArrow (0x7E) + KEY_NONE +}; + +KEY MacClient::s_keyConversion[128]; + +void MacClient::CheckKeyboardLayout() { + #if WHOA_SYSTEM_VERSION < WHOA_MACOS_10_6 + void* KCHR = reinterpret_cast(GetScriptManagerVariable(smKCHRCache)); + + if (MacClient::s_currentKeyboardLayout != KCHR) { + MacClient::InitializeKeyConversion(); + MacClient::s_currentKeyboardLayout = KCHR; + } + #endif + + #if WHOA_SYSTEM_VERSION >= WHOA_MACOS_10_6 + TISInputSourceRef inputSrc = TISCopyCurrentKeyboardLayoutInputSource(); + CFDataRef layoutData = static_cast(TISGetInputSourceProperty(inputSrc, kTISPropertyUnicodeKeyLayoutData)); + const UCKeyboardLayout* keyboardLayout = reinterpret_cast(CFDataGetBytePtr(layoutData)); + + if (MacClient::s_currentKeyboardLayout != keyboardLayout) { + MacClient::InitializeKeyConversion(); + MacClient::s_currentKeyboardLayout = const_cast(keyboardLayout); + } + #endif +} + +double MacClient::GetMouseSpeed() { + #if WHOA_SYSTEM_VERSION < WHOA_MACOS_10_12 + double mouseSpeed = 1.0; + + NXEventHandle handle = NXOpenEventStatus(); + + if (handle) { + IOHIDGetAccelerationWithKey(handle, CFSTR(kIOHIDMouseAccelerationType), &mouseSpeed); + NXCloseEventStatus(handle); + } + + return mouseSpeed; + #endif + + #if WHOA_SYSTEM_VERSION >= WHOA_MACOS_10_12 + double mouseSpeed = 1.0; + + io_service_t service = IORegistryEntryFromPath(kIOMasterPortDefault, kIOServicePlane ":/IOResources/IOHIDSystem"); + + if (service != MACH_PORT_NULL) { + CFDictionaryRef parameters = static_cast(IORegistryEntryCreateCFProperty(service, CFSTR(kIOHIDParametersKey), kCFAllocatorDefault, kNilOptions)); + CFNumberRef speedParameter = static_cast(CFDictionaryGetValue(parameters, CFSTR(kIOHIDMouseAccelerationType))); + + if (speedParameter) { + int32_t number; + + if (CFNumberGetValue(static_cast(speedParameter), kCFNumberSInt32Type, &number)) { + mouseSpeed = static_cast(number) / 65536.0; + } + } + + CFRelease(parameters); + IOObjectRelease(service); + } + + return mouseSpeed; + #endif +} + +bool MacClient::GetTextInputEnabled() { + // TODO + return false; +} + +void MacClient::InitializeKeyConversion() { + memcpy(MacClient::s_keyConversion, MacClient::s_specialKeyConversion, sizeof(MacClient::s_specialKeyConversion)); + + #if WHOA_SYSTEM_VERSION < WHOA_MACOS_10_6 + void* KCHR = reinterpret_cast(GetScriptManagerVariable(smKCHRCache)); + + if (KCHR) { + for (uint16_t i = 0; i < 128; i++) { + if (MacClient::s_keyConversion[i] == KEY_NONE) { + uint16_t translate = i | (1 << 7); + uint32_t state = 0; + + uint32_t value = KeyTranslate(KCHR, translate, &state); + + if (state) { + value = KeyTranslate(KCHR, translate, &state); + } + + if (value) { + value = value - 97 <= 25 ? value - 32 : value; + + auto string = CFStringCreateWithBytes( + nullptr, + reinterpret_cast(&value), + 1, + 0, + false + ); + + if (string) { + CFIndex len; + CFRange range = CFRangeMake(0, 1); + + CFStringGetBytes( + string, + range, + kCFStringEncodingISOLatin1, + 0, + 0, + reinterpret_cast(&value), + 1, + &len + ); + + if (len && value) { + MacClient::s_keyConversion[i] = static_cast(value); + } + } + } + } + } + } + #endif + + #if WHOA_SYSTEM_VERSION >= WHOA_MACOS_10_6 + TISInputSourceRef inputSrc = TISCopyCurrentKeyboardLayoutInputSource(); + CFDataRef layoutData = static_cast(TISGetInputSourceProperty(inputSrc, kTISPropertyUnicodeKeyLayoutData)); + const UCKeyboardLayout* keyboardLayout = reinterpret_cast(CFDataGetBytePtr(layoutData)); + const uint32_t keyboardType = LMGetKbdType(); + + if (keyboardLayout) { + for (uint16_t i = 0; i < 128; i++) { + if (MacClient::s_keyConversion[i] == KEY_NONE) { + uint16_t vkey = i; + uint32_t state = 0; + UniChar buf[1]; + UniCharCount len; + + OSStatus res = UCKeyTranslate( + keyboardLayout, + vkey, + kUCKeyActionUp, + 0, + keyboardType, + 0, + &state, + 1, + &len, + buf + ); + + if (res != noErr) { + continue; + } + + if (state) { + res = UCKeyTranslate( + keyboardLayout, + vkey, + kUCKeyActionUp, + 0, + keyboardType, + 0, + &state, + 1, + &len, + buf + ); + + if (res != noErr) { + continue; + } + } + + uint16_t value = buf[0]; + + if (len && value) { + value = value >= 97 && value <= 122 ? value - 32 : value; + MacClient::s_keyConversion[i] = static_cast(value); + } + } + } + } + #endif +} + +bool MacClient::IsUsingGLLayer() { + return true; +} + +void MacClient::PostClipboardKeyEvents(MacClient::ClipboardAction action) { + int32_t v1; + + switch (action) { + case ClipboardUndo: + v1 = 90; + break; + + case ClipboardCut: + v1 = 88; + break; + + case ClipboardCopy: + v1 = 67; + break; + + case ClipboardPaste: + v1 = 86; + break; + + case ClipboardSelectAll: + v1 = 65; + break; + + default: + return; + } + + OsInputPostEvent(OS_INPUT_KEY_DOWN, 2, 0, 0, 0); + OsInputPostEvent(OS_INPUT_KEY_DOWN, v1, 0, 0, 0); + OsInputPostEvent(OS_INPUT_KEY_UP, v1, 0, 0, 0); + OsInputPostEvent(OS_INPUT_KEY_UP, 2, 0, 0, 0); +} + +void MacClient::SetMouseCoalescingEnabled(bool enabled) { + #if WHOA_SYSTEM_VERSION < WHOA_MACOS_10_5 + bool prevEnabled = false; + SetMouseCoalescingEnabled(true, &prevEnabled); + #endif + + #if WHOA_SYSTEM_VERSION >= WHOA_MACOS_10_5 + [NSEvent setMouseCoalescingEnabled: enabled]; + #endif +} diff --git a/src/app/mac/Main.nib/classes.nib b/src/app/mac/Main.nib/classes.nib new file mode 100755 index 0000000..c4b887e --- /dev/null +++ b/src/app/mac/Main.nib/classes.nib @@ -0,0 +1,8 @@ + + + + + IBVersion + 1 + + diff --git a/src/app/mac/Main.nib/info.nib b/src/app/mac/Main.nib/info.nib new file mode 100755 index 0000000..d8ac9df --- /dev/null +++ b/src/app/mac/Main.nib/info.nib @@ -0,0 +1,18 @@ + + + + + IBFramework Version + 677 + IBLastKnownRelativeProjectPath + ../../../WoW.xcodeproj + IBOldestOS + 3 + IBOpenObjects + + IBSystem Version + 9J61 + targetFramework + IBCarbonFramework + + diff --git a/src/app/mac/Main.nib/objects.xib b/src/app/mac/Main.nib/objects.xib new file mode 100755 index 0000000..e4e1703 --- /dev/null +++ b/src/app/mac/Main.nib/objects.xib @@ -0,0 +1,131 @@ + + + + + + + Paste + v + past + + + TRUE + + + Copy + c + copy + + + About World of Warcraft... + 0 + abou + + + World of Warcraft + _NSAppleMenu + + + + TRUE + + + Switch To Full Screen Mode + m + Full + + + + + Edit + + Edit + 128 + + + Undo + z + undo + + + + Cut + x + cut + + + + + Select All + a + sall + + + + + + Untitled + _NSMainMenu + + + World of Warcraft + + + + + + + + + Show BatchViewer + TRUE + BVwr + + + + + Show GL Layer Setup + TRUE + GLLs + + + Batch Viewer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + BatchViewerMenu + + File's Owner + + MenuBar + + + IBCarbonFramework + 200 + diff --git a/src/app/mac/Main.xib b/src/app/mac/Main.xib new file mode 100644 index 0000000..efa7e4d --- /dev/null +++ b/src/app/mac/Main.xib @@ -0,0 +1,597 @@ + + + + 1030 + 15G19009 + 11000 + 1404.47 + 807.20 + + + + + + + Untitled + + + + World of Warcraft + + 1048576 + 2147483647 + + NSMenuCheckmark + -1396703232 + + + NSMenuMixedState + -1396703232 + + submenuAction: + + + World of Warcraft + + + + About World of Warcraft... + + 2147483647 + + + About World of Warcraft... + + + + + + + + + + + + + + + + + + + + + + + YES + + + 1048576 + 2147483647 + + + + + + + + + + + + + + + + + + + + + + + + + + Switch To Full Screen Mode + m + 1048576 + 2147483647 + + + Switch To Full Screen Mode + m + + + + + + + + + + + + + + + + + + + + + _NSAppleMenu + World of Warcraft + + + + + + + + World of Warcraft + + + + + + + + + + + + + + + + + + + + + + + Edit + + 1048576 + 2147483647 + + + submenuAction: + + + Edit + + + + Undo + z + 1048576 + 2147483647 + + + Undo + z + + + + + + + + + + + + + + + + + + + + + + YES + + + 1048576 + 2147483647 + + + + + + + + + + + + + + + + + + + + + + + + + + Cut + x + 1048576 + 2147483647 + + + Cut + x + + + + + + + + + + + + + + + + + + + + + + Copy + c + 1048576 + 2147483647 + + + Copy + c + + + + + + + + + + + + + + + + + + + + + + Paste + v + 1048576 + 2147483647 + + + Paste + v + + + + + + + + + + + + + + + + + + + + + + Select All + a + 1048576 + 2147483647 + + + Select All + a + + + + + + + + + + + + + + + + + + + + + Edit + + + + + + + + Edit + + + + + + + + + + + + + + + + + + + + + + _NSMainMenu + Untitled + + + + + + + + + Batch Viewer + + + + Show BatchViewer + + 1048576 + 2147483647 + + + Show BatchViewer + + + + + + + + + + + + + + + + + + + + + + + Show GL Layer Setup + + 1048576 + 2147483647 + + + Show GL Layer Setup + + + + + + + + + + + + + + + + + + + + + + Batch Viewer + + + + + + + + + + + + + + 0 + + + + + + 29 + + + + + + + MenuBar + + + 197 + + + + + + + BatchViewerMenu + + + 152 + + + + + + + + 182 + + + + + + + + 198 + + + + + 199 + + + + + 147 + + + + + + + + + + + + + 181 + + + + + + + + + + 144 + + + + + 142 + + + + + 149 + + + + + 141 + + + + + 143 + + + + + 148 + + + + + 183 + + + + + 187 + + + + + 186 + + + + + + + + + + + 200 + + + 0 + IBCarbonFramework + + com.apple.InterfaceBuilder.CarbonPlugin.macosx + + + YES + ../../../WoW.xcodeproj + 3 + + diff --git a/src/app/mac/MainApp.h b/src/app/mac/MainApp.h new file mode 100644 index 0000000..8e02454 --- /dev/null +++ b/src/app/mac/MainApp.h @@ -0,0 +1,19 @@ +#ifndef APP_MAC_MAIN_APP_H +#define APP_MAC_MAIN_APP_H + +#include "app/mac/MacClient.h" +#include +#include + +@interface MainApp : NSObject + +@property (retain) NSTimer* m_pollTimer; +@property bool isPolling; + +@property (retain) IBOutlet NSMenuItem* captureFrameMenuItem; +@property (retain) IBOutlet NSMenuItem* showBatchViewerMenuItem; +@property (retain) IBOutlet NSMenuItem* showGLLayerSetupMenuItem; + +@end + +#endif diff --git a/src/app/mac/MainApp.mm b/src/app/mac/MainApp.mm new file mode 100644 index 0000000..87ebc5c --- /dev/null +++ b/src/app/mac/MainApp.mm @@ -0,0 +1,90 @@ +#include "app/mac/MainApp.h" +#include "event/Event.hpp" +#include "event/Scheduler.hpp" +#include "os/Compat.hpp" + +@implementation MainApp + ++ (void)initialize { + [[NSUserDefaults standardUserDefaults] + registerDefaults: [NSDictionary + dictionaryWithObject: @"YES" + forKey: @"NSDisabledCharacterPaletteMenuItem"]]; + + [NSApp + setActivationPolicy: NSApplicationActivationPolicyRegular]; +} + +- (void)applicationDidFinishLaunching:(id)a1 { + self.m_pollTimer = [NSTimer + timerWithTimeInterval: 0.0001 + target: self + selector: @selector(poll:) + userInfo: nil + repeats: true]; + + [[NSRunLoop currentRunLoop] + addTimer: self.m_pollTimer + forMode: NSDefaultRunLoopMode]; +} + +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender { + // TODO + // OsQueuePut(5, 0, 0, 0, 0); + + return NSTerminateCancel; +} + +- (void)captureFrame:(id)a1 { +} + +- (void)copy:(id)a3 { + MacClient::PostClipboardKeyEvents(MacClient::ClipboardCopy); +} + +- (void)cut:(id)a3 { + MacClient::PostClipboardKeyEvents(MacClient::ClipboardCut); +} + +- (void)paste:(id)a3 { + MacClient::PostClipboardKeyEvents(MacClient::ClipboardPaste); +} + +- (void)poll:(id)a1 { + if (!Event::s_shouldLoopTerminate) { + Event::s_shouldLoopTerminate = SchedulerMainProcess(); + + if (Event::s_shouldLoopTerminate) { + [self.m_pollTimer invalidate]; + self.m_pollTimer = nil; + + [NSApp stop:self]; + + [NSApp + postEvent: + [NSEvent + otherEventWithType: NSEventTypeApplicationDefined + location: NSMakePoint(0, 0) + modifierFlags: 0 + timestamp: 0 + windowNumber: 0 + context: 0 + subtype: 0 + data1: 0 + data2: 0] + atStart: 0]; + } + } +} + +- (void)showBatchViewer:(id)a1 { +} + +- (void)showGLLayerSetup:(id)a1 { +} + +- (void)toggleFullscreenMode:(id)a1 { + // TODO +} + +@end diff --git a/src/app/mac/MainMenu.nib/designable.nib b/src/app/mac/MainMenu.nib/designable.nib new file mode 100755 index 0000000..a1a3ef1 --- /dev/null +++ b/src/app/mac/MainMenu.nib/designable.nib @@ -0,0 +1,901 @@ + + + + 1030 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + + + YES + + YES + + + YES + + + + YES + + NSApplication + + + FirstResponder + + + NSApplication + + + AMainMenu + + YES + + + World of Warcraft + + 2147483647 + + NSImage + NSMenuCheckmark + + + NSImage + NSMenuMixedState + + submenuAction: + + World of Warcraft + + YES + + + MENU_ABOUT + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + MENU_SWITCH_TO_FULLSCREEN + m + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + MENU_HIDE + h + 1048576 + 2147483647 + + + + + + MENU_HIDE_OTHERS + h + 1572864 + 2147483647 + + + + + + MENU_SHOW_ALL + + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + MENU_QUIT + q + 1572864 + 2147483647 + + + + + _NSAppleMenu + + + + + MENU_EDIT + + 2147483647 + + + submenuAction: + + MENU_EDIT + + YES + + + MENU_UNDO + z + 1048576 + 2147483647 + + + + + + YES + YES + + + 2147483647 + + + + + + MENU_EDIT_CUT + x + 1048576 + 2147483647 + + + + + + MENU_EDIT_COPY + c + 1048576 + 2147483647 + + + + + + MENU_EDIT_PASTE + v + 1048576 + 2147483647 + + + + + + MENU_EDIT_SELECT_ALL + a + 1048576 + 2147483647 + + + + + + + + + MENU_WINDOW + + 2147483647 + + + submenuAction: + + MENU_WINDOW + + YES + + + Show Batch Viewer + + 1048576 + 2147483647 + + + + + + Show GL Layer Setup + + 1048576 + 2147483647 + + + + + + Capture Frame + r + 1048576 + 2147483647 + + + + + _NSWindowsMenu + + + + _NSMainMenu + + + MainApp + + + + + YES + + + orderFrontStandardAboutPanel: + + + + 142 + + + + undo: + + + + 223 + + + + copy: + + + + 224 + + + + paste: + + + + 226 + + + + cut: + + + + 228 + + + + selectAll: + + + + 232 + + + + hide: + + + + 367 + + + + hideOtherApplications: + + + + 368 + + + + terminate: + + + + 369 + + + + unhideAllApplications: + + + + 370 + + + + delegate + + + + 452 + + + + showGLLayerSetup: + + + + 454 + + + + showBatchViewer: + + + + 455 + + + + showBatchViewerMenuItem + + + + 456 + + + + showGLLayerSetupMenuItem + + + + 457 + + + + toggleFullscreenMode: + + + + 460 + + + + captureFrameMenuItem + + + + 462 + + + + captureFrame: + + + + 463 + + + + + YES + + 0 + + YES + + + + + + -2 + + + RmlsZSdzIE93bmVyA + + + -1 + + + First Responder + + + -3 + + + Application + + + 29 + + + YES + + + + + + Main Menu + + + 19 + + + YES + + + + + + 56 + + + YES + + + + + + 217 + + + YES + + + + + + 205 + + + YES + + + + + + + + + + + 198 + + + + + 207 + + + + + 199 + + + + + 203 + + + + + 197 + + + + + 206 + + + + + 57 + + + YES + + + + + + + + + + + + + + 58 + + + + + 134 + + + + + 150 + + + + + 136 + + + + + 236 + + + + + 149 + + + + + 145 + + + + + 24 + + + YES + + + + + + + + 449 + + + + + 450 + + + + + 451 + + + MainApp + + + 458 + + + + + 459 + + + + + 461 + + + + + + + YES + + YES + -1.IBPluginDependency + -2.IBPluginDependency + -3.IBPluginDependency + 134.IBPluginDependency + 134.ImportedFromIB2 + 136.IBPluginDependency + 136.ImportedFromIB2 + 145.IBPluginDependency + 145.ImportedFromIB2 + 149.IBPluginDependency + 149.ImportedFromIB2 + 150.IBPluginDependency + 150.ImportedFromIB2 + 19.IBPluginDependency + 19.ImportedFromIB2 + 197.IBPluginDependency + 197.ImportedFromIB2 + 198.IBPluginDependency + 198.ImportedFromIB2 + 199.IBPluginDependency + 199.ImportedFromIB2 + 203.IBPluginDependency + 203.ImportedFromIB2 + 205.IBEditorWindowLastContentRect + 205.IBPluginDependency + 205.ImportedFromIB2 + 205.editorWindowContentRectSynchronizationRect + 206.IBPluginDependency + 206.ImportedFromIB2 + 207.IBPluginDependency + 207.ImportedFromIB2 + 217.IBPluginDependency + 217.ImportedFromIB2 + 236.IBPluginDependency + 236.ImportedFromIB2 + 24.IBEditorWindowLastContentRect + 24.IBPluginDependency + 24.ImportedFromIB2 + 24.editorWindowContentRectSynchronizationRect + 29.IBEditorWindowLastContentRect + 29.IBPluginDependency + 29.ImportedFromIB2 + 29.WindowOrigin + 29.editorWindowContentRectSynchronizationRect + 449.IBPluginDependency + 449.ImportedFromIB2 + 450.IBPluginDependency + 450.ImportedFromIB2 + 451.IBPluginDependency + 458.IBPluginDependency + 458.ImportedFromIB2 + 459.IBPluginDependency + 459.ImportedFromIB2 + 461.IBPluginDependency + 56.IBPluginDependency + 56.ImportedFromIB2 + 57.IBEditorWindowLastContentRect + 57.IBPluginDependency + 57.ImportedFromIB2 + 57.editorWindowContentRectSynchronizationRect + 58.IBPluginDependency + 58.ImportedFromIB2 + + + YES + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{1422, 1180}, {243, 113}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{197, 734}, {243, 243}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + {{1548, 966}, {218, 63}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{525, 802}, {197, 73}} + {{1295, 1029}, {390, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + + {74, 862} + {{11, 977}, {478, 20}} + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + + com.apple.InterfaceBuilder.CocoaPlugin + com.apple.InterfaceBuilder.CocoaPlugin + + {{49, 388}, {312, 153}} + com.apple.InterfaceBuilder.CocoaPlugin + + {{23, 794}, {245, 183}} + com.apple.InterfaceBuilder.CocoaPlugin + + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 463 + + + + YES + + MainApp + NSObject + + YES + + YES + captureFrame: + cut: + paste: + selectAll: + showBatchViewer: + showGLLayerSetup: + toggleFullscreenMode: + undo: + + + YES + id + id + id + id + id + id + id + id + + + + YES + + YES + captureFrameMenuItem + showBatchViewerMenuItem + showGLLayerSetupMenuItem + + + YES + NSMenuItem + NSMenuItem + NSMenuItem + + + + IBProjectSource + Source/MainApp.h + + + + MainApp + NSObject + + IBUserSource + + + + + NSObject + + YES + + YES + didAdjustSubviews: + willAdjustSubviews: + + + YES + RBSplitView + RBSplitView + + + + IBProjectSource + ../../../Engine/Source/Gx/CGxDeviceGLL/GLLayer/RBSplitView.h + + + + RBSplitSubview + NSView + + IBProjectSource + ../../../Engine/Source/Gx/CGxDeviceGLL/GLLayer/RBSplitSubview.h + + + + RBSplitSubview + + IBProjectSource + ../../../Engine/Source/Gx/CGxDeviceGLL/GLLayer/RBSplitViewPrivateDefines.h + + + + RBSplitView + RBSplitSubview + + delegate + id + + + + + RBSplitView + + + + StackTableView + NSTableView + + copy: + id + + + IBProjectSource + ../../../Engine/Source/Gx/CGxDeviceGLL/GLLayer/StackCrawlViewer.h + + + + + 0 + ../../WoW.xcodeproj + 3 + + diff --git a/src/app/mac/MainMenu.nib/keyedobjects.nib b/src/app/mac/MainMenu.nib/keyedobjects.nib new file mode 100755 index 0000000..09db3a4 Binary files /dev/null and b/src/app/mac/MainMenu.nib/keyedobjects.nib differ diff --git a/src/app/mac/MainMenu.xib b/src/app/mac/MainMenu.xib new file mode 100644 index 0000000..f886aeb --- /dev/null +++ b/src/app/mac/MainMenu.xib @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/mac/View.h b/src/app/mac/View.h new file mode 100644 index 0000000..c7c250d --- /dev/null +++ b/src/app/mac/View.h @@ -0,0 +1,12 @@ +#ifndef APP_MAC_VIEW_H +#define APP_MAC_VIEW_H + +#include + +struct GLWindowCallbacks; + +void AssignEngineViewCallbacks(GLWindowCallbacks*); + +Class GetEngineViewClass(void); + +#endif diff --git a/src/app/mac/View.mm b/src/app/mac/View.mm new file mode 100644 index 0000000..729115b --- /dev/null +++ b/src/app/mac/View.mm @@ -0,0 +1,27 @@ +#include "app/mac/View.h" +#include "app/mac/EngineGLLayerView.h" +#include "app/mac/WindowCallbacks.h" +#include "gx/gll/GLWindow.h" + +GLWindowCallbacks EngineViewCallbacks = { + &MacOnResized, + &MacOnMouseDown, + &MacOnMouseMoved, + &MacOnMouseUp, + &MacOnKeyDown, + &MacOnKeyUp +}; + +void AssignEngineViewCallbacks(GLWindowCallbacks* callbacks) { + *callbacks = EngineViewCallbacks; + + // TODO + // (callbacks + 100) = 0; + + // TODO + // dword_12B9F54 = sub_A15850; +} + +Class GetEngineViewClass() { + return [EngineGLLayerView class]; +} diff --git a/src/app/mac/Whoa.mm b/src/app/mac/Whoa.mm new file mode 100644 index 0000000..5554aa8 --- /dev/null +++ b/src/app/mac/Whoa.mm @@ -0,0 +1,41 @@ +#include "app/mac/WoWApplication.h" +#include "app/mac/MacClient.h" +#include "client/Client.hpp" +#include +#include + +int32_t main(int32_t argc, char* argv[]) { + // TODO + // MacClient::SetupCommandLine(argc, argv, v10); + + if (MacClient::IsUsingGLLayer()) { + // TODO + // GxSetRequestedApi(3); + + // TODO + // OsInputSetIsUsingCocoaEventLoop(1); + + [WoWApplication sharedApplication]; + + #if WHOA_SYSTEM_VERSION < WHOA_MACOS_10_8 + [NSBundle + loadNibNamed: @"MainMenu" + owner: NSApp]; + #endif + + #if WHOA_SYSTEM_VERSION >= WHOA_MACOS_10_8 + [[NSBundle mainBundle] + loadNibNamed: @"MainMenu" + owner: NSApp + topLevelObjects: nil]; + #endif + + [NSRunLoop currentRunLoop]; + + [NSApp mainMenu]; + + CommonMain(); + } + + return 0; +} diff --git a/src/app/mac/WindowCallbacks.h b/src/app/mac/WindowCallbacks.h new file mode 100644 index 0000000..f9d4d7b --- /dev/null +++ b/src/app/mac/WindowCallbacks.h @@ -0,0 +1,19 @@ +#ifndef APP_MAC_WINDOW_CALLBACKS_H +#define APP_MAC_WINDOW_CALLBACKS_H + +#include +#include + +void MacOnKeyDown(NSEvent*); + +void MacOnKeyUp(NSEvent*); + +void MacOnMouseDown(int16_t, int32_t, int32_t); + +void MacOnMouseMoved(int32_t, int32_t); + +void MacOnMouseUp(int16_t, int32_t, int32_t); + +void MacOnResized(int32_t width, int32_t height, bool a3); + +#endif diff --git a/src/app/mac/WindowCallbacks.mm b/src/app/mac/WindowCallbacks.mm new file mode 100644 index 0000000..662f071 --- /dev/null +++ b/src/app/mac/WindowCallbacks.mm @@ -0,0 +1,188 @@ +#include "app/mac/WindowCallbacks.h" +#include "app/mac/MacClient.h" +#include "event/Input.hpp" +#include "gx/gll/CGxDeviceGLL.hpp" +#include "gx/Device.hpp" +#include "gx/Window.hpp" +#include "util/BlizzardCore.hpp" + +void MacOnKeyDown(NSEvent* event) { + BLIZZARD_ASSERT(false); +} + +void MacOnKeyUp(NSEvent* event) { + uint32_t keyCode = event.keyCode; + + MacClient::CheckKeyboardLayout(); + + if (keyCode <= 0x7F) { + uint32_t key = MacClient::s_keyConversion[keyCode]; + + if (key != KEY_NONE) { + OsQueuePut(OS_INPUT_KEY_UP, key, 0, 0, 0); + } + } +} + +void MacOnMouseDown(int16_t button, int32_t x, int32_t y) { + NSEvent* event = [NSApp currentEvent]; + uint32_t modifierFlags = event.modifierFlags; + + uint32_t v8 = 0; + + if (modifierFlags & NSShiftKeyMask) { + v8 |= 0x2; + } + + if (modifierFlags & NSAlternateKeyMask) { + v8 |= 0x8; + } + + if (modifierFlags & NSCommandKeyMask) { + v8 |= 0x1; + } + + if (modifierFlags & NSControlKeyMask) { + v8 |= 0x10; + } + + if (modifierFlags & NSAlphaShiftKeyMask) { + v8 |= 0x4; + } + + int16_t buttonNumber = button + 1; + int16_t buttonIndex = button; + + if (buttonNumber > 0xF) { + return; + } + + if (Input::s_buttonDown[buttonIndex]) { + return; + } + + int16_t effectiveButtonNumber = buttonNumber; + + Input::s_buttonDown[buttonIndex] = 1; + + if (buttonNumber == 1) { + effectiveButtonNumber = 1; + + if (v8 & 0x1) { + Input::s_simulatedRightButtonClick = 1; + + // TODO + // if (Input::byte_12B94E2) { + // return; + // } + + effectiveButtonNumber = 2; + } + } + + if (buttonNumber == 2) { + if (Input::s_simulatedRightButtonClick) { + return; + } else { + effectiveButtonNumber = 2; + } + } + + MOUSEBUTTON mouseButton = ConvertButtonNumberToMOUSEBUTTON(effectiveButtonNumber); + + if (mouseButton) { + if (Input::s_mouseMode == 0) { + Input::s_currentMouse = { x, y }; + } + + OsQueuePut(OS_INPUT_MOUSE_DOWN, mouseButton, x, y, 0); + } +} + +void MacOnMouseMoved(int32_t x, int32_t y) { + if (Input::s_mouseMode == 1) { + NSEvent* event = [NSApp currentEvent]; + + int32_t deltaX = static_cast(floor(event.deltaX)); + int32_t deltaY = static_cast(floor(event.deltaY)); + + OsQueuePut(OS_INPUT_MOUSE_MOVE_RELATIVE, 0, deltaX, deltaY, 0); + } else { + Input::s_currentMouse = { x, y }; + + OsQueuePut(OS_INPUT_MOUSE_MOVE, 0, x, y, 0); + } +} + +void MacOnMouseUp(int16_t button, int32_t x, int32_t y) { + NSEvent* event = [NSApp currentEvent]; + uint32_t modifierFlags = event.modifierFlags; + + int16_t buttonNumber = button + 1; + int16_t buttonIndex = button; + + if (buttonNumber > 0xF) { + return; + } + + if (!Input::s_buttonDown[buttonIndex]) { + return; + } + + int16_t effectiveButtonNumber = buttonNumber; + + Input::s_buttonDown[buttonIndex] = 0; + + if (buttonNumber == 1) { + effectiveButtonNumber = 1; + + if (Input::s_simulatedRightButtonClick) { + effectiveButtonNumber = 2; + Input::s_simulatedRightButtonClick = 0; + + // TODO + // if (Input::byte_12B94E2) { + // return; + // } + } + } + + if (buttonNumber == 2) { + if (Input::s_simulatedRightButtonClick) { + return; + } else { + effectiveButtonNumber = 2; + } + } + + MOUSEBUTTON mouseButton = ConvertButtonNumberToMOUSEBUTTON(effectiveButtonNumber); + + if (mouseButton) { + if (Input::s_mouseMode != 0) { + Input::s_currentMouse = { x, y }; + } + + OsQueuePut(OS_INPUT_MOUSE_UP, mouseButton, x, y, 0); + } +} + +void MacOnResized(int32_t width, int32_t height, bool a3) { + if (a3) { + return; + } + + static_cast(g_theGxDevicePtr)->Resize(width, height); + + OsQueuePut(OS_INPUT_SIZE, width, height, 0, 0); + + // TODO SubA61E60(width, height) + + auto bounds = GetSavedWindowBounds(); + Rect newBounds = { + bounds->top, + bounds->left, + static_cast(bounds->top + height), + static_cast(bounds->left + width) + }; + SetSavedWindowBounds(newBounds); +} diff --git a/src/app/mac/WoWApplication.h b/src/app/mac/WoWApplication.h new file mode 100644 index 0000000..10ba0ad --- /dev/null +++ b/src/app/mac/WoWApplication.h @@ -0,0 +1,11 @@ +#ifndef APP_MAC_WOW_APPLICATION_H +#define APP_MAC_WOW_APPLICATION_H + +#include +#include + +@interface WoWApplication : NSApplication + +@end + +#endif diff --git a/src/app/mac/WoWApplication.mm b/src/app/mac/WoWApplication.mm new file mode 100644 index 0000000..badca94 --- /dev/null +++ b/src/app/mac/WoWApplication.mm @@ -0,0 +1,24 @@ +#include "app/mac/WoWApplication.h" +#include "gx/gll/GLLayerView.h" + +@implementation WoWApplication + +- (void)sendEvent:(NSEvent*)event { + auto responder = [[NSApp keyWindow] firstResponder]; + + if (responder && [responder isKindOfClass: [GLLayerView class]]) { + NSEventType type = [event type]; + + if (type == NSKeyDown && (event.keyCode == 114 || (event.keyCode == 48 && event.modifierFlags & NSControlKeyMask))) { + [responder keyDown: event]; + return; + } else if (type == NSKeyUp) { + [responder keyUp: event]; + return; + } + } + + [super sendEvent:event]; +} + +@end diff --git a/src/async/AsyncFile.cpp b/src/async/AsyncFile.cpp new file mode 100644 index 0000000..1ffdddc --- /dev/null +++ b/src/async/AsyncFile.cpp @@ -0,0 +1,143 @@ +#include "async/AsyncFile.hpp" +#include "async/AsyncFileRead.hpp" +#include "async/CAsyncQueue.hpp" +#include "event/Event.hpp" +#include "util/SFile.hpp" +#include +#include + +CAsyncObject* AsyncFileReadAllocObject() { + AsyncFileRead::s_queueLock.Enter(); + + CAsyncObject* object = AsyncFileRead::s_asyncFileReadFreeList.Head(); + + if (!object) { + object = AsyncFileRead::s_asyncFileReadFreeList.NewNode(1, 0, 0x0); + } + + object->link.Unlink(); + + AsyncFileRead::s_queueLock.Leave(); + + object->file = nullptr; + object->buffer = nullptr; + object->size = 0; + object->userArg = nullptr; + object->userPostloadCallback = nullptr; + object->userFailedCallback = nullptr; + object->queue = nullptr; + object->isProcessed = 0; + object->isRead = 0; + object->isCurrent = 0; + object->char24 = 0; + object->char25 = 0; + object->ptr1C = 0; + object->priority = 126; + + return object; +} + +void AsyncFileReadDestroyObject(CAsyncObject* object) { + AsyncFileRead::s_queueLock.Enter(); + + if (object->isCurrent) { + // TODO + // nullsub_3(); + + AsyncFileRead::s_queueLock.Leave(); + + while (object->isCurrent) { + OsSleep(1); + } + + AsyncFileRead::s_queueLock.Enter(); + } + + object->link.Unlink(); + + if (object->file) { + SFile::Close(object->file); + } + + AsyncFileRead::s_asyncFileReadFreeList.LinkToHead(object); + + AsyncFileRead::s_queueLock.Leave(); +} + +void AsyncFileReadInitialize(uint32_t threadSleep, uint32_t handlerTimeout) { + AsyncFileRead::s_threadSleep = std::min(threadSleep, 100u); + AsyncFileRead::s_handlerTimeout = std::max(handlerTimeout, 20u); + + EventRegisterEx(EVENT_ID_POLL, &AsyncFileReadPollHandler, nullptr, 0.0f); + if (SFile::IsStreamingMode()) { + // TODO + // EventRegisterEx(EVENT_ID_IDLE, &Sub4B9F40, nullptr, -1.0f); + } + + AsyncFileRead::s_asyncWaitObject = nullptr; + AsyncFileRead::s_progressCallback = nullptr; + AsyncFileRead::s_progressParam = nullptr; + AsyncFileRead::s_ingameProgressCallback = nullptr; + AsyncFileRead::s_ingameStartCallback = nullptr; + AsyncFileRead::s_propContext = PropGetSelectedContext(); + + AsyncFileRead::s_shutdownEvent.Reset(); + + int32_t numQueues = SFile::IsStreamingMode() != 0 ? 3 : 1; + for (int32_t i = 0; i < numQueues; i++) { + CAsyncQueue* queue = AsyncFileReadCreateQueue(); + const char* queueName = AsyncFileRead::s_asyncQueueNames[i]; + + AsyncFileRead::s_asyncQueues[i] = queue; + AsyncFileReadCreateThread(queue, queueName); + } + + if (SFile::IsStreamingMode()) { + AsyncFileRead::s_asyncQueues[2]->int20 = 1; + } + + // TODO + // - Something related to AsyncFileRead::s_userQueueLock +} + +void AsyncFileReadObject(CAsyncObject* object, int32_t a2) { + CAsyncQueue* queue = AsyncFileRead::s_asyncQueues[0]; + + if (SFile::IsStreamingMode()) { + // TODO + // int32_t v3 = SFile::FileIsLocal(object->file, 6); + // + // if (!v3 || v3 == 2) { + // int32_t v4 = object->priority <= 127; + // object->char24 = 1; + // queue = AsyncFileRead::s_asyncQueues[1]; + // + // if (!v4) { + // queue = AsyncFileRead::s_asyncQueues[2]; + // } + // } + } + + AsyncFileRead::s_queueLock.Enter(); + + object->queue = queue; + + if (AsyncFileRead::s_asyncWaitObject == object) { + object->priority = object->priority > 127 ? 128 : 0; + // TODO + // object->ptr1C = g_theGxDevicePtr + 3944; + object->char25 = 0; + } else if (queue->int20) { + // TODO + // Sub4BA530(object, a2); + } else { + AsyncFileReadLinkObject(object, a2); + } + + AsyncFileRead::s_queueLock.Leave(); + + if (SFile::IsStreamingMode()) { + // TODO + // SFile::LogFileAccess(object->file, 0, 0); + } +} diff --git a/src/async/AsyncFile.hpp b/src/async/AsyncFile.hpp new file mode 100644 index 0000000..6591e3e --- /dev/null +++ b/src/async/AsyncFile.hpp @@ -0,0 +1,14 @@ +#ifndef ASYNC_ASYNC_FILE_HPP +#define ASYNC_ASYNC_FILE_HPP + +#include "async/CAsyncObject.hpp" + +CAsyncObject* AsyncFileReadAllocObject(void); + +void AsyncFileReadDestroyObject(CAsyncObject* object); + +void AsyncFileReadInitialize(uint32_t threadSleep, uint32_t handlerTimeout); + +void AsyncFileReadObject(CAsyncObject* object, int32_t a2); + +#endif diff --git a/src/async/AsyncFileRead.cpp b/src/async/AsyncFileRead.cpp new file mode 100644 index 0000000..0196509 --- /dev/null +++ b/src/async/AsyncFileRead.cpp @@ -0,0 +1,196 @@ +#include "async/AsyncFileRead.hpp" +#include "util/SFile.hpp" +#include +#include + +uint32_t AsyncFileRead::s_threadSleep; +uint32_t AsyncFileRead::s_handlerTimeout = 100; +CAsyncObject* AsyncFileRead::s_asyncWaitObject; +void* AsyncFileRead::s_progressCallback; +void* AsyncFileRead::s_progressParam; +int32_t AsyncFileRead::s_progressCount; +void* AsyncFileRead::s_ingameProgressCallback; +void* AsyncFileRead::s_ingameStartCallback; +void* AsyncFileRead::s_propContext; +SEvent AsyncFileRead::s_shutdownEvent = SEvent(1, 0); +const char* AsyncFileRead::s_asyncQueueNames[NUM_ASYNC_QUEUES] = { + "Disk Queue", + "Net Geometry Queue", + "Net Texture Queue" +}; +CAsyncQueue* AsyncFileRead::s_asyncQueues[NUM_ASYNC_QUEUES]; +SCritSect AsyncFileRead::s_queueLock; +SCritSect AsyncFileRead::s_userQueueLock; +TSList> AsyncFileRead::s_asyncQueueList; +TSList> AsyncFileRead::s_asyncThreadList; +STORM_EXPLICIT_LIST(CAsyncObject, link) AsyncFileRead::s_asyncFileReadPostList; +STORM_EXPLICIT_LIST(CAsyncObject, link) AsyncFileRead::s_asyncFileReadFreeList; + +CAsyncQueue* AsyncFileReadCreateQueue() { + CAsyncQueue* queue = AsyncFileRead::s_asyncQueueList.NewNode(0, 2, 0x8); + return queue; +} + +void AsyncFileReadCreateThread(CAsyncQueue* queue, const char* queueName) { + CAsyncThread* thread = AsyncFileRead::s_asyncThreadList.NewNode(0, 2, 0x8); + + thread->queue = queue; + thread->currentObject = nullptr; + + SThread::Create(AsyncFileReadThread, thread, thread->thread, const_cast(queueName), 0); +} + +void AsyncFileReadLinkObject(CAsyncObject* object, int32_t a2) { + if (!object->queue) { + return; + } + + object->link.Unlink(); + + auto& readList = object->queue->readList; + + for (auto currentObject = readList.Head(); currentObject; currentObject = readList.Link(currentObject)->Next()) { + uint8_t priority = object->priority; + uint8_t currentPriority = currentObject->priority; + + if (priority <= currentPriority && (a2 || priority != currentPriority)) { + readList.LinkNode(object, 2, currentObject); + object->char25 = 0; + + return; + } + } + + readList.LinkToTail(object); + object->char25 = 0; +} + +int32_t AsyncFileReadPollHandler(const void*, void*) { + uint32_t start = OsGetAsyncTimeMsPrecise(); + + while (1) { + AsyncFileRead::s_queueLock.Enter(); + + CAsyncObject* object = AsyncFileRead::s_asyncFileReadPostList.Head(); + + if (!object) { + AsyncFileRead::s_queueLock.Leave(); + break; + } + + AsyncFileRead::s_asyncFileReadPostList.UnlinkNode(object); + + if (AsyncFileRead::s_asyncWaitObject == object) { + AsyncFileRead::s_asyncWaitObject = nullptr; + } + + object->isProcessed = 1; + + AsyncFileRead::s_queueLock.Leave(); + + object->userPostloadCallback(object->userArg); + + AsyncFileRead::s_progressCount--; + + // Check if we're exceeded the allowed running time + if (OsGetAsyncTimeMsPrecise() - start > AsyncFileRead::s_handlerTimeout) { + break; + } + } + + // TODO + // for (int32_t i = 0; i < DwordB4A224; i++) { + // DwordB4A228[i](); + // } + + return 1; +} + +uint32_t AsyncFileReadThread(void* param) { + CAsyncThread* thread = static_cast(param); + + PropSelectContext(AsyncFileRead::s_propContext); + + while (AsyncFileRead::s_shutdownEvent.Wait(0)) { + uint32_t sleep = 0; + CAsyncObject* object; + + while (1) { + AsyncFileRead::s_queueLock.Enter(); + + object = thread->queue->readList.Head(); + + if (object && thread->queue->int20 && /* TODO */ true) { + // TODO + // Sub4BA530(object, 1); + + AsyncFileRead::s_queueLock.Leave(); + continue; + } + + if (!object) { + object = thread->queue->list14.Head(); + } + + if (!object) { + AsyncFileRead::s_queueLock.Leave(); + break; + } + + object->link.Unlink(); + object->queue = nullptr; + object->isCurrent = 1; + thread->currentObject = object; + + AsyncFileRead::s_queueLock.Leave(); + + int32_t tries = 10; + while (1) { + if (SFile::IsStreamingMode() && object->file) { + // TODO + // Sub421820(object->file, (object->priority > 127) + 1, 1); + } + + if (SFile::Read(object->file, object->buffer, object->size, nullptr, nullptr, nullptr)) { + break; + } + + tries--; + + // Handle failure + if (tries == 0) { + // TODO + // Sub421850((object->file, v17, 512); + // v10 = Sub7717E0(); + // Sub771A80(v10, v18, 512); + // nullsub_3(v17); + + break; + } + } + + AsyncFileRead::s_queueLock.Enter(); + + AsyncFileRead::s_asyncFileReadPostList.LinkToTail(object); + + thread->currentObject = nullptr; + object->isCurrent = 0; + object->isRead = 1; + + AsyncFileRead::s_queueLock.Leave(); + + if (AsyncFileRead::s_threadSleep) { + sleep++; + + if (sleep == AsyncFileRead::s_threadSleep) { + OsSleep(1); + sleep = 0; + } + } + } + + OsSleep(1); + } + + return 0; +} diff --git a/src/async/AsyncFileRead.hpp b/src/async/AsyncFileRead.hpp new file mode 100644 index 0000000..d405de7 --- /dev/null +++ b/src/async/AsyncFileRead.hpp @@ -0,0 +1,46 @@ +#ifndef ASYNC_ASYNC_FILE_READ_HPP +#define ASYNC_ASYNC_FILE_READ_HPP + +#include "async/CAsyncQueue.hpp" +#include "async/CAsyncThread.hpp" +#include +#include + +#define NUM_ASYNC_QUEUES 3 + +class CAsyncObject; + +class AsyncFileRead { + public: + // Static variables + static uint32_t s_threadSleep; + static uint32_t s_handlerTimeout; + static CAsyncObject* s_asyncWaitObject; + static void* s_progressCallback; + static void* s_progressParam; + static int32_t s_progressCount; + static void* s_ingameProgressCallback; + static void* s_ingameStartCallback; + static HPROPCONTEXT s_propContext; + static SEvent s_shutdownEvent; + static const char* s_asyncQueueNames[]; + static CAsyncQueue* s_asyncQueues[]; + static SCritSect s_queueLock; + static SCritSect s_userQueueLock; + static TSList> s_asyncQueueList; + static TSList> s_asyncThreadList; + static STORM_EXPLICIT_LIST(CAsyncObject, link) s_asyncFileReadPostList; + static STORM_EXPLICIT_LIST(CAsyncObject, link) s_asyncFileReadFreeList; +}; + +CAsyncQueue* AsyncFileReadCreateQueue(void); + +void AsyncFileReadCreateThread(CAsyncQueue* queue, const char* queueName); + +void AsyncFileReadLinkObject(CAsyncObject* object, int32_t a2); + +int32_t AsyncFileReadPollHandler(const void*, void*); + +uint32_t AsyncFileReadThread(void* thread); + +#endif diff --git a/src/async/CAsyncObject.hpp b/src/async/CAsyncObject.hpp new file mode 100644 index 0000000..af91472 --- /dev/null +++ b/src/async/CAsyncObject.hpp @@ -0,0 +1,30 @@ +#ifndef ASYNC_C_ASYNC_OBJECT_HPP +#define ASYNC_C_ASYNC_OBJECT_HPP + +#include + +class SFile; +class CAsyncQueue; + +class CAsyncObject { + public: + // Member variables + SFile* file; + void* buffer; + uint32_t size; + void* userArg; + void (*userPostloadCallback)(void*); + void (*userFailedCallback)(void*); + CAsyncQueue* queue; + void* ptr1C; + uint8_t priority; + uint8_t isProcessed; + uint8_t isRead; + uint8_t isCurrent; + uint8_t char24; + uint8_t char25; + uint8_t padding[2]; + TSLink link; +}; + +#endif diff --git a/src/async/CAsyncQueue.hpp b/src/async/CAsyncQueue.hpp new file mode 100644 index 0000000..abcc1bc --- /dev/null +++ b/src/async/CAsyncQueue.hpp @@ -0,0 +1,16 @@ +#ifndef ASYNC_C_ASYNC_QUEUE_HPP +#define ASYNC_C_ASYNC_QUEUE_HPP + +#include "async/CAsyncObject.hpp" +#include +#include + +class CAsyncQueue : public TSLinkedNode { + public: + // Member variables + STORM_EXPLICIT_LIST(CAsyncObject, link) readList; + STORM_EXPLICIT_LIST(CAsyncObject, link) list14; + int32_t int20; +}; + +#endif diff --git a/src/async/CAsyncThread.hpp b/src/async/CAsyncThread.hpp new file mode 100644 index 0000000..f2c42fd --- /dev/null +++ b/src/async/CAsyncThread.hpp @@ -0,0 +1,19 @@ +#ifndef ASYNC_C_ASYNC_THREAD_HPP +#define ASYNC_C_ASYNC_THREAD_HPP + +#include +#include +#include + +class CAsyncObject; +class CAsyncQueue; + +class CAsyncThread : public TSLinkedNode { + public: + // Member variables + SThread thread; + CAsyncQueue* queue; + CAsyncObject* currentObject; +}; + +#endif diff --git a/src/async/CMakeLists.txt b/src/async/CMakeLists.txt new file mode 100644 index 0000000..e84aac4 --- /dev/null +++ b/src/async/CMakeLists.txt @@ -0,0 +1,19 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(async STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(async + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(async + PRIVATE + event + util + PUBLIC + common + storm +) diff --git a/src/client/CMakeLists.txt b/src/client/CMakeLists.txt new file mode 100644 index 0000000..1bace55 --- /dev/null +++ b/src/client/CMakeLists.txt @@ -0,0 +1,25 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(client STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(client + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(client + PRIVATE + async + event + gx + model + net + ui + util + world + PUBLIC + common + storm +) diff --git a/src/client/Client.cpp b/src/client/Client.cpp new file mode 100644 index 0000000..5104105 --- /dev/null +++ b/src/client/Client.cpp @@ -0,0 +1,437 @@ +#include "client/Client.hpp" +#include "client/ClientServices.hpp" +#include "client/Console.hpp" +#include "async/AsyncFile.hpp" +#include "glue/CGlueMgr.hpp" +#include "gx/Device.hpp" +#include "gx/Screen.hpp" +#include "gx/Texture.hpp" +#include "model/Model2.hpp" +#include "ui/FrameScript.hpp" +#include "ui/FrameXML.hpp" +#include "util/BlizzardCore.hpp" +#include "util/CVar.hpp" +#include "world/World.hpp" +#include +#include +#include + +CVar* Client::g_accountListVar; +HEVENTCONTEXT Client::g_clientEventContext; + +void AsyncFileInitialize() { + // TODO + AsyncFileReadInitialize(0, 100); +} + +void BaseInitializeGlobal() { + PropInitialize(); +} + +void ClientMiscInitialize() { + // TODO + + Client::g_accountListVar = CVar::Register( + "accountList", + "List of wow accounts for saved Blizzard account", + 0, + "", + nullptr, + 4, + false, + nullptr, + false + ); + + // TODO +} + +void ClientPostClose(int32_t a1) { + // TODO s_finalDialog = a1; + EventPostCloseEx(nullptr); +} + +int32_t DestroyEngineCallback(const void* a1, void* a2) { + // TODO + + return 1; +} + +int32_t InitializeEngineCallback(const void* a1, void* a2) { + // TODO + // sub_4D2A30(); + + AsyncFileInitialize(); + TextureInitialize(); + + // ModelBlobLoad("world\\model.blob"); + + // if (SFile::IsStreamingMode()) { + // TextureLoadBlob("world\\liquid.tex"); + // } + + ScrnInitialize(0); + // ConsoleScreenInitialize(a2); + + // s_cvarTextureFilteringMode = CVar::Register( + // "textureFilteringMode", + // "Texture filtering mode", + // 1, + // "1", + // &TextureFilteringCallback, + // 1, + // 0, + // 0, + // 0 + // ); + + // s_cvarUIFaster = CVar::Register( + // "UIFaster", + // "UI acceleration option", + // 0, + // "3", + // &UIFasterCalllback, + // 1, + // 0, + // 0, + // 0 + // ); + + // s_cvarTextureCacheSize = CVar::Register( + // "textureCacheSize", + // "Texture cache size in bytes", + // 1, + // "32", + // &TextureCacheSizeCallback, + // 1, + // 0, + // 0, + // 0 + // ); + + // sub_4B6580(*(_DWORD *)(dword_B2F9FC + 48) << 20); + + // AddConsoleDeviceDefaultCallback(SetDefaults); + + // if (ConsoleDeviceHardwareChanged()) { + // v3 = 0; + + // do { + // SetDefaults(v3++); + // } while (v3 < 3); + // } + + auto m2Flags = M2RegisterCVars(); + M2Initialize(m2Flags, 0); + + // v4 = *(_DWORD *)(dword_B2FA00 + 48); + // sub_4B61C0(dword_AB6128[v4]); + // sub_4B6230(dword_AB6140[v4]); + + WowClientInit(); + + return 1; +} + +int32_t InitializeGlobal() { + // TODO + + // SCmdRegisterArgList(&ProcessCommandLine(void)::s_wowArgList, 17u); + + // CmdLineProcess(); + + // sub_403600("WoW.mfil"); + + // if (dword_B2FA10 != 2) { + // sub_403560(); + // } + + // LOBYTE(v24) = 0; + + // if (sub_422140()) { + // LOBYTE(v24) = OsDirectoryExists((int)"WTF/Account") == 0; + // } + + // ClientServices::LoadCDKey(); + + ConsoleInitializeClientCommand(); + + ConsoleInitializeClientCVar("Config.wtf"); + + // sub_7663F0(); + + // v18 = 0; + // v19 = 0; + // ptr = 0; + // v21 = 0; + + // sub_406740(&v18, &CVar::Load); + + // if (ptr) { + // SMemFree(ptr, a_pad, -2, 0); + // } + + // CVar::Register("dbCompress", "Database compression", 0, "-1", 0, 5, 0, 0, 0); + + // v2 = CVar::Register("locale", "Set the game locale", 0, "****", &LocaleChangedCallback, 5, 0, 0, 0); + + // if (!SStrCmp(v2->m_stringValue.m_str, "****", 0x7FFFFFFFu)) { + // CVar::Set(v2, "enUS", 1, 0, 0, 1); + // } + + // CVar::Register("useEnglishAudio", "override the locale and use English audio", 0, "0", 0, 5, 0, 0, 0); + + // if (sub_422140()) { + // sub_4036B0(v24, 0, a2, (int)v2, (char)v24); + // } + + // SStrCopy(&a1a, v2->m_stringValue.m_str, 5); + + // sub_402D50(&a1a); + + // CVar::Set(v2, &a1a, 1, 0, 0, 1); + + // SStrPrintf(dest, 260, "%s%s", *(_DWORD *)off_AB6158, v2->m_stringValue.m_str); + + // sub_421B50(dest); + + // sub_423D70(); + + // sub_405DD0(); + + // CVar* v3 = CVar::Register( + // "processAffinityMask", + // "Sets which core(s) WoW may execute on - changes require restart to take effect", + // 2, + // "0", + // &sub_4022E0, + // 0, + // 0, + // 0, + // 0 + // ); + + // CVar* v4 = CVar::Lookup("videoOptionsVersion"); + + // if (!v4 || v4->m_intValue < 3) { + // SStrPrintf(v23, 8, "%u", 0); + // CVar::Set(v3, v23, 1, 0, 0, 1); + // CVar::Update((int)v3); + // } + + // v5 = v3->m_intValue; + + // if (v5) { + // SSetCurrentProcessAffinityMask(v5); + // } + + BaseInitializeGlobal(); + + EventInitialize(1, 0); + + // CVar* v6 = CVar::Register( + // "timingTestError", + // "Error reported by the timing validation system", + // 6, + // "0", + // 0, + // 5, + // 0, + // 0, + // 0 + // ); + // v7 = v6; + + // CVar* v8 = CVar::Register( + // "timingMethod", + // "Desired method for game timing", + // 2, + // "0", + // &sub_403200, + // 5, + // 0, + // v6, + // 0 + // ); + + // sub_86D430(v8->m_intValue); + + // ConsoleCommandRegister("timingInfo", (int)sub_4032A0, 0, 0); + + // v9 = sub_86AD50(); + + // v10 = v9 == v7->m_intValue; + + // dword_B2F9D8 = v9; + + // if (!v10) { + // sprintf(&v17, "%d", v9); + // CVar::SetReadOnly((int)v7, 0); + // CVar::Set(v7, &v17, 1, 0, 0, 1); + // CVar::Update((int)v7); + // CVar::SetReadOnly((int)v7, 1); + // ConsolePrintf("Timing test error: %d", (int)v7); + // } + + // WowClientDB::Load(&g_Startup_StringsDB, 0, ".\\Client.cpp", 0x12E3u); + // Startup_StringsRec* v11 = g_Startup_StringsDB.GetRecordByIndex(1); + // const char* v12; + + // if (v11) { + // v12 = v11->m_text; + // } else { + // v12 = "World of Warcraft"; + // } + + // TODO + // - replace with above logic for loading from Startup_Strings.dbc + const char* v12 = "World of Warcraft"; + + char v15[260]; + + SStrCopy(v15, v12, 0x7FFFFFFF); + + ConsoleDeviceInitialize(v15); + + // OsIMEInitialize(); + + // uint32_t v13 = OsGetAsyncTimeMs(); + // g_rndSeed.SetSeed(v13); + + Client::g_clientEventContext = EventCreateContextEx( + 1, + &InitializeEngineCallback, + &DestroyEngineCallback, + 0, + 0 + ); + + return 1; +} + +void CommonMain() { + StormInitialize(); + + // TODO + // - error log setup + // - misc other setup + + if (InitializeGlobal()) { + EventDoMessageLoop(); + + // TODO + // sub_406B70(); + } + + // TODO + // - misc cleanup +} + +void BlizzardAssertCallback(const char* a1, const char* a2, const char* a3, uint32_t a4) { + if (*a2) { + SErrDisplayError(0, a3, a4, a2, 0, 1, 0x11111111); + } else { + SErrDisplayError(0, a3, a4, a1, 0, 1, 0x11111111); + } +} + +void StormInitialize() { + // TODO + // SStrInitialize(); + // SErrInitialize(); + // SLogInitialize(); + // SFile::Initialize(); + + Blizzard::Debug::SetAssertHandler(BlizzardAssertCallback); +} + +void WowClientInit() { + // TODO + // EventRegister(EVENT_ID_5, (int)sub_4020E0); + // _cfltcvt_init_0(); + + ClientMiscInitialize(); + + // sub_401B60(); + // ClientDBInitialize(); + // LoadingScreenInitialize(); + + FrameScript_Initialize(0); + + // TODO + // SI2::Init(0); + // sub_6F66B0(); + + FrameXML_RegisterDefault(); + GlueScriptEventsInitialize(); + ScriptEventsInitialize(); + + // TODO + // sub_6F75E0(); + // sub_401FF0(); + + ClientServices::Initialize(); + // TODO ClientServices::SetMessageHandler(SMSG_TUTORIAL_FLAGS, (int)sub_530920, 0); + + // TODO + // v2 = CVar::Lookup("EnableVoiceChat"); + // if (v2 && *(_DWORD *)(v2 + 48)) { + // ComSatClient_Init(); + // } + + // TODO + // DBCache_RegisterHandlers(); + // DBCache_Initialize(a1); + + // TODO + // sub_78E400(); + + CWorld::Initialize(); + + // TODO + // ShadowInit(); + // GxuLightInitialize(); + // GxuLightBucketSizeSet(16.665001); + // InputControlInitialize(); + + CGlueMgr::Initialize(); + + // TODO + // if (GetConsoleMessage()) { + // v3 = (const char *)GetConsoleMessage(); + // CGlueMgr::AddChangedOptionWarning(v3); + // SetConsoleMessage(0); + // } + + // TODO + // if (sub_422140()) { + // sub_421630(); + // } + + // TODO + // if (byte_B2F9E1 != 1) { + // if ((g_playIntroMovie + 48) == 1) { + // CVar::Set(g_playIntroMovie, "0", 1, 0, 0, 1); + // CGlueMgr::SetScreen("movie"); + // } else { + // CGlueMgr::SetScreen("login"); + // } + // } else { + // if ((dword_B2F980 + 48) == 1) { + // CVar::Set(dword_B2F980, "0", 1, 0, 0, 1); + // CVar::Set(g_playIntroMovie, "0", 1, 0, 0, 1); + // CGlueMgr::SetScreen("movie"); + // } else { + // CGlueMgr::SetScreen("login"); + // } + // } + + // TODO + // - temporary until above logic is implemented + CGlueMgr::SetScreen("login"); + + // TODO + // CGlueMgr::m_pendingTimerAlert = dword_B2F9D8; + // sub_7FC5A0(); + // EventRegister(EVENT_ID_POLL, &PollNet); +} diff --git a/src/client/Client.hpp b/src/client/Client.hpp new file mode 100644 index 0000000..acc469e --- /dev/null +++ b/src/client/Client.hpp @@ -0,0 +1,22 @@ +#ifndef CLIENT_CLIENT_HPP +#define CLIENT_CLIENT_HPP + +#include "event/Event.hpp" +#include + +class CVar; + +namespace Client { + extern CVar* g_accountListVar; + extern HEVENTCONTEXT g_clientEventContext; +} + +void ClientPostClose(int32_t); + +void CommonMain(void); + +void StormInitialize(void); + +void WowClientInit(void); + +#endif diff --git a/src/client/ClientRealmResponseAdapter.hpp b/src/client/ClientRealmResponseAdapter.hpp new file mode 100644 index 0000000..8386310 --- /dev/null +++ b/src/client/ClientRealmResponseAdapter.hpp @@ -0,0 +1,10 @@ +#ifndef CLIENT_CLIENT_REALM_RESPONSE_ADAPTER_HPP +#define CLIENT_CLIENT_REALM_RESPONSE_ADAPTER_HPP + +#include "net/Connection.hpp" + +class ClientRealmResponseAdapter : public RealmResponse { + public: +}; + +#endif diff --git a/src/client/ClientServices.cpp b/src/client/ClientServices.cpp new file mode 100644 index 0000000..8972f1b --- /dev/null +++ b/src/client/ClientServices.cpp @@ -0,0 +1,92 @@ +#include "client/ClientServices.hpp" +#include "client/ClientRealmResponseAdapter.hpp" +#include "net/Connection.hpp" +#include "net/Login.hpp" +#include +#include +#include + +ClientConnection* g_clientConnection; + +RealmResponse* ClientServices::s_clientRealmResponse; +ClientConnection* ClientServices::s_currentConnection; +ClientServices* ClientServices::s_instance; +Login* ClientServices::s_loginObj; +bool ClientServices::s_newLogin; + +ClientConnection* ClientServices::Connection() { + // TODO assertion? + + return ClientServices::s_currentConnection; +} + +ClientServices* ClientServices::GetInstance() { + if (ClientServices::s_instance) { + return ClientServices::s_instance; + } + + auto instanceMem = SMemAlloc(sizeof(ClientServices), __FILE__, __LINE__, 0x0); + auto instance = new (instanceMem) ClientServices(); + ClientServices::s_instance = instance; + + return ClientServices::s_instance; +} + +void ClientServices::Initialize() { + if (!g_clientConnection) { + auto adapterMem = SMemAlloc(sizeof(ClientRealmResponseAdapter), __FILE__, __LINE__, 0x0); + auto clientRealmResponse = new (adapterMem) ClientRealmResponseAdapter(); + ClientServices::s_clientRealmResponse = clientRealmResponse; + + auto connectionMem = SMemAlloc(sizeof(ClientConnection), __FILE__, __LINE__, 0x0); + auto clientConnection = new (connectionMem) ClientConnection(ClientServices::s_clientRealmResponse); + g_clientConnection = clientConnection; + } + + ClientServices::s_currentConnection = g_clientConnection; + + // TODO ConsoleCommandRegister("logout", &Sub6B2030, 5, nullptr); +} + +Login* ClientServices::LoginConnection() { + return ClientServices::s_loginObj; +} + +void ClientServices::Logon(const char* accountName, const char* password) { + if (ClientServices::s_newLogin) { + if (ClientServices::s_loginObj) { + // TODO + // ClientServices::s_loginObj->Vfunc48(1); + + ClientServices::s_loginObj = nullptr; + } + + ClientServices::s_newLogin = false; + } + + auto useBattlenet = SStrChr(accountName, '@') != 0; + + Login* loginObj; + + if (useBattlenet) { + // TODO + } else { + auto loginMem = SMemAlloc(sizeof(GruntLogin), __FILE__, __LINE__, 0x0); + loginObj = new (loginMem) GruntLogin(); + } + + ClientServices::s_loginObj = loginObj; + ClientServices::s_loginObj->Init(ClientServices::GetInstance()); + + // TODO + + ClientServices::s_loginObj->SetLogonCreds(accountName, password); + + // TODO + + ClientServices::s_loginObj->Logon(nullptr, nullptr); +} + +void ClientServices::LoginServerStatus(LOGIN_STATE state, LOGIN_RESULT result, const char* addrStr, const char* stateStr, const char* resultStr, uint16_t a7) { + // TODO +} diff --git a/src/client/ClientServices.hpp b/src/client/ClientServices.hpp new file mode 100644 index 0000000..9ce86e9 --- /dev/null +++ b/src/client/ClientServices.hpp @@ -0,0 +1,30 @@ +#ifndef CLIENT_CLIENT_SERVICES_HPP +#define CLIENT_CLIENT_SERVICES_HPP + +#include "net/login/LoginResponse.hpp" + +class ClientConnection; +class Login; +class RealmResponse; + +class ClientServices : public LoginResponse { + public: + // Static variables + static RealmResponse* s_clientRealmResponse; + static ClientConnection* s_currentConnection; + static ClientServices* s_instance; + static Login* s_loginObj; + static bool s_newLogin; + + // Static functions + static ClientConnection* Connection(); + static ClientServices* GetInstance(); + static void Initialize(); + static Login* LoginConnection(); + static void Logon(const char* accountName, const char* password); + + // Virtual member functions + virtual void LoginServerStatus(LOGIN_STATE state, LOGIN_RESULT result, const char* addrStr, const char* stateStr, const char* resultStr, uint16_t a7); +}; + +#endif diff --git a/src/client/Console.cpp b/src/client/Console.cpp new file mode 100644 index 0000000..580bba3 --- /dev/null +++ b/src/client/Console.cpp @@ -0,0 +1,191 @@ +#include "client/Console.hpp" +#include "gx/Device.hpp" +#include "util/CVar.hpp" +#include + +CVar* s_cvGxMaximize; +CVar* s_cvGxResolution; +CVar* s_cvGxWidescreen; +CVar* s_cvGxWindow; +DefaultSettings s_defaults; +bool s_hwDetect; +bool s_hwChanged; +CGxFormat s_requestedFormat; + +bool CVGxMaximizeCallback(CVar*, const char*, const char*, void*) { + // TODO + return true; +} + +bool CVGxResolutionCallback(CVar*, const char*, const char*, void*) { + // TODO + return true; +} + +bool CVGxWindowCallback(CVar*, const char*, const char*, void*) { + // TODO + return true; +} + +void RegisterGxCVars() { + auto& format = s_defaults.format; + + // TODO CURRENT_LANGUAGE check? + auto v1 = true; + + s_cvGxWidescreen = CVar::Register( + "widescreen", + "Allow widescreen support", + 0x0, + "1", + nullptr, + 1, + false, + nullptr, + false + ); + + s_cvGxWindow = CVar::Register( + "gxWindow", + "toggle fullscreen/window", + 0x1 | 0x2, + v1 ? "1" : "0", + &CVGxWindowCallback, + 1, + 0, + 0, + 0 + ); + + s_cvGxMaximize = CVar::Register( + "gxMaximize", + "maximize game window", + 0x1 | 0x2, + v1 ? "1" : "0", + &CVGxMaximizeCallback, + 1, + 0, + 0, + 0 + ); + + // TODO s_cvGxColorBits + // TODO s_cvGxDepthBits + + char resolution[260]; + SStrPrintf(resolution, 260, "%dx%d", format.size.x, format.size.y); + s_cvGxResolution = CVar::Register( + "gxResolution", + "resolution", + 0x1 | 0x2, + resolution, + &CVGxResolutionCallback, + 1, + false, + nullptr, + false + ); + + // TODO s_cvGxRefresh + // TODO s_cvGxTripleBuffer + // TODO s_cvGxApi + // TODO s_cvGxVSync + // TODO s_cvGxAspect + // TODO s_cvGxCursor + // TODO s_cvGxMultisample + // TODO s_cvGxFixLag + // TODO s_cvGxStereoEnabled + // TODO s_cvGxOverride + // TODO s_cvGxAspect + // TODO s_cvGxMaxFPS + // TODO s_cvGxMaxFPSBk + // TODO s_cvWindowResizeLock + // TODO s_cvFixedFunction +} + +void UpdateGxCVars() { + // TODO others + + s_cvGxWindow->Update(); + s_cvGxResolution->Update(); + + // TODO others + + s_cvGxMaximize->Update(); + + // TODO others +} + +void SetGxCVars(const CGxFormat& format) { + char value[1024]; + + // TODO s_cvGxColorBits + // TODO s_cvGxDepthBits + + SStrPrintf(value, sizeof(value), "%d", format.window); + s_cvGxWindow->Set(value, true, false, false, true); + + SStrPrintf(value, sizeof(value), "%dx%d", format.size.x, format.size.y); + s_cvGxResolution->Set(value, true, false, false, true); + + // TODO s_cvGxRefresh + // TODO others + + SStrPrintf(value, sizeof(value), "%d", format.maximize); + s_cvGxMaximize->Set(value, true, false, false, true); + + // TODO others + + UpdateGxCVars(); +} + +void ConsoleInitializeClientCommand() { + // TODO +} + +void ConsoleInitializeClientCVar(const char* a1) { + // TODO +} + +void ConsoleDeviceInitialize(const char* title) { + // TODO + + // TODO proper logic + s_hwDetect = true; + + // TODO + + RegisterGxCVars(); + + // TODO ConsoleCommandRegister("gxRestart", &CCGxRestart, 1, nullptr); + + // TODO + + // TODO + // - source the size values correctly + s_requestedFormat.size.x = 1024; + s_requestedFormat.size.y = 768; + s_requestedFormat.colorFormat = CGxFormat::Fmt_Argb8888; + s_requestedFormat.depthFormat = CGxFormat::Fmt_Ds248; + + if (s_hwDetect || s_hwChanged) { + // TODO Sub76B3F0(&UnkCABAF0, &UnkCABB38); + // TODO s_cvFixedFunction->Set("0", 1, 0, 0, 1); + // TODO memcpy(&s_requestedFormat, &s_defaults.format, sizeof(s_requestedFormat)); + + s_requestedFormat.window = s_cvGxWindow->GetInt() != 0; + s_requestedFormat.maximize = s_cvGxMaximize->GetInt() != 0; + + // TODO temporary override + s_requestedFormat.maximize = 0; + + SetGxCVars(s_requestedFormat); + } + + CGxFormat format; + memcpy(&format, &s_requestedFormat, sizeof(s_requestedFormat)); + + CGxDevice* device = GxDevCreate(GxApi_GLL, nullptr, format); + + // TODO +} diff --git a/src/client/Console.hpp b/src/client/Console.hpp new file mode 100644 index 0000000..a54f82d --- /dev/null +++ b/src/client/Console.hpp @@ -0,0 +1,16 @@ +#ifndef CLIENT_CONSOLE_HPP +#define CLIENT_CONSOLE_HPP + +#include "gx/CGxFormat.hpp" + +struct DefaultSettings { + CGxFormat format; +}; + +void ConsoleInitializeClientCommand(); + +void ConsoleInitializeClientCVar(const char* a1); + +void ConsoleDeviceInitialize(const char* title); + +#endif diff --git a/src/event/CEvent.cpp b/src/event/CEvent.cpp new file mode 100644 index 0000000..a6bdffd --- /dev/null +++ b/src/event/CEvent.cpp @@ -0,0 +1,40 @@ +#include "event/CEvent.hpp" +#include "gx/Coordinate.hpp" + +CCharEvent& CCharEvent::operator=(const EVENT_DATA_CHAR& data) { + this->ch = data.ch; + this->metaKeyState = data.metaKeyState; + this->repeat = data.repeat; + + return *this; +} + +CKeyEvent& CKeyEvent::operator=(const EVENT_DATA_KEY& data) { + this->key = data.key; + this->metaKeyState = data.metaKeyState; + this->repeat = data.repeat; + this->time = data.time; + + return *this; +} + +CMouseEvent& CMouseEvent::operator=(const EVENT_DATA_MOUSE& data) { + this->mode = data.mode; + this->button = data.button; + this->buttonState = data.buttonState; + this->metaKeyState = data.metaKeyState; + this->flags = data.flags; + this->time = data.time; + this->wheelDistance = data.wheelDistance; + + NDCToDDC(data.x, data.y, &this->x, &this->y); + + return *this; +} + +CSizeEvent& CSizeEvent::operator=(const EVENT_DATA_SIZE& data) { + this->w = data.w; + this->h = data.h; + + return *this; +} diff --git a/src/event/CEvent.hpp b/src/event/CEvent.hpp new file mode 100644 index 0000000..b061182 --- /dev/null +++ b/src/event/CEvent.hpp @@ -0,0 +1,39 @@ +#ifndef EVENT_C_EVENT_HPP +#define EVENT_C_EVENT_HPP + +#include "event/Types.hpp" +#include +#include + +class CEvent : public TRefCnt { + public: + // Member variables + uint32_t id; + void* param; +}; + +class CCharEvent : public CEvent, public EVENT_DATA_CHAR { + public: + // Member functions + CCharEvent& operator=(const EVENT_DATA_CHAR&); +}; + +class CKeyEvent : public CEvent, public EVENT_DATA_KEY { + public: + // Member functions + CKeyEvent& operator=(const EVENT_DATA_KEY&); +}; + +class CMouseEvent : public CEvent, public EVENT_DATA_MOUSE { + public: + // Member functions + CMouseEvent& operator=(const EVENT_DATA_MOUSE&); +}; + +class CSizeEvent : public CEvent, public EVENT_DATA_SIZE { + public: + // Member functions + CSizeEvent& operator=(const EVENT_DATA_SIZE&); +}; + +#endif diff --git a/src/event/CMakeLists.txt b/src/event/CMakeLists.txt new file mode 100644 index 0000000..159bcd0 --- /dev/null +++ b/src/event/CMakeLists.txt @@ -0,0 +1,27 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +if(WHOA_SYSTEM_MAC) + file(GLOB MAC_SOURCES + "mac/*.cpp" + "mac/*.mm" + ) + list(APPEND PRIVATE_SOURCES ${MAC_SOURCES}) +endif() + +add_library(event STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(event + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(event + PRIVATE + gx + PUBLIC + common + storm + tempest +) diff --git a/src/event/Context.cpp b/src/event/Context.cpp new file mode 100644 index 0000000..7d0e005 --- /dev/null +++ b/src/event/Context.cpp @@ -0,0 +1,112 @@ +#include "event/Context.hpp" +#include "event/Event.hpp" +#include "event/EvtThread.hpp" +#include +#include + +HEVENTCONTEXT AttachContextToThread(EvtContext* context) { + SInterlockedIncrement(&Event::s_threadListContention); + Event::s_threadListCritsect.Enter(); + + // Select the thread with the highest weight total + EvtThread* thread = nullptr; + EvtThread* t = Event::s_threadList.Head(); + + while (t) { + if (!thread || t->m_weightTotal < thread->m_weightTotal) { + thread = t; + } + + t = t->Next(); + } + + if (thread) { + TSingletonInstanceId::s_idTable.Insert(context); + + uint32_t v13 = OsGetAsyncTimeMs(); + + if (v13 != context->m_schedNextWakeTime.m_val) { + context->m_schedNextWakeTime.m_val = v13; + context->m_schedNextWakeTime.Relink(); + } + + Event::s_threadSlotCritsects[thread->m_threadSlot].Enter(); + + thread->m_contextQueue.Enqueue(context); + + Event::s_threadSlotCritsects[thread->m_threadSlot].Leave(); + + thread->m_wakeEvent.Set(); + + uint32_t v14 = context->m_schedWeight + thread->m_weightTotal; + uint32_t v15 = thread->m_contextCount + 1; + thread->m_contextCount = v15; + thread->m_weightTotal = v14; + thread->m_weightAvg = v14 / v15; + } else { + // TODO + } + + Event::s_threadListCritsect.Leave(); + SInterlockedDecrement(&Event::s_threadListContention); + + return context; +} + +void DetachContextFromThread(uint32_t a1, EvtContext* a2) { + // TODO +} + +EvtContext* GetNextContext(uint32_t hThread) { + EvtContext* context; + + Event::s_threadSlotCritsects[hThread].Enter(); + + context = Event::s_threadSlots[hThread]->m_contextQueue.Dequeue(); + + Event::s_threadSlotCritsects[hThread].Leave(); + + if (hThread == Event::s_mainThread) { + Event::s_currentEvtContext = context; + } + + return context; +} + +void PutContext(uint32_t nextWakeTime, uint32_t newSmoothWeight, EvtContext* context, uint32_t hThread) { + if (nextWakeTime != context->m_schedNextWakeTime.m_val) { + context->m_schedNextWakeTime.m_val = nextWakeTime; + context->m_schedNextWakeTime.Relink(); + } + + if (context->m_schedSmoothWeight != newSmoothWeight) { + uint32_t v8 = context->m_schedWeight; + context->m_schedSmoothWeight = newSmoothWeight; + + int32_t v9; + + if (newSmoothWeight <= v8) { + v9 = v8 - newSmoothWeight; + } else { + v9 = newSmoothWeight - v8; + } + + context->m_schedRebalance = v9 >= v8 >> 3; + } + + if (!SInterlockedIncrement(&Event::s_threadListContention)) { + Event::s_threadListCritsect.Enter(); + + // TODO + + Event::s_threadListCritsect.Leave(); + } + + SInterlockedDecrement(&Event::s_threadListContention); + + if (hThread < Event::s_threadSlotCount) { + Event::s_threadSlotCritsects[hThread].Enter(); + Event::s_threadSlots[hThread]->m_contextQueue.Enqueue(context); + Event::s_threadSlotCritsects[hThread].Leave(); + } +} diff --git a/src/event/Context.hpp b/src/event/Context.hpp new file mode 100644 index 0000000..76c70f6 --- /dev/null +++ b/src/event/Context.hpp @@ -0,0 +1,17 @@ +#ifndef EVENT_CONTEXT_HPP +#define EVENT_CONTEXT_HPP + +#include "event/Types.hpp" +#include + +class EvtContext; + +HEVENTCONTEXT AttachContextToThread(EvtContext* context); + +void DetachContextFromThread(uint32_t a1, EvtContext* a2); + +EvtContext* GetNextContext(uint32_t hThread); + +void PutContext(uint32_t nextWakeTime, uint32_t newSmoothWeight, EvtContext* context, uint32_t hThread); + +#endif diff --git a/src/event/Event.cpp b/src/event/Event.cpp new file mode 100644 index 0000000..e27d367 --- /dev/null +++ b/src/event/Event.cpp @@ -0,0 +1,136 @@ +#include "event/Event.hpp" +#include "event/EvtContext.hpp" +#include "event/EvtThread.hpp" +#include "event/Input.hpp" +#include "event/Queue.hpp" +#include "event/Scheduler.hpp" +#include +#include +#include +#include + +SEvent Event::s_startEvent = SEvent(1, 0); +SEvent Event::s_shutdownEvent = SEvent(1, 0); +int32_t Event::s_netServer; +int32_t Event::s_threadSlotCount; +SCritSect* Event::s_threadSlotCritsects; +EvtThread** Event::s_threadSlots; +uint32_t Event::s_mainThread; +TSGrowableArray Event::s_schedulerThreads; +ATOMIC32 Event::s_threadListContention { -1 }; +SCritSect Event::s_threadListCritsect; +TSList> Event::s_threadList; +EvtContext* Event::s_currentEvtContext; +ATOMIC32 Event::s_interactiveCount; + +#if defined(WHOA_SYSTEM_MAC) + bool Event::s_shouldLoopTerminate; +#endif + +void OsNetPump(uint32_t timeout) { + // TODO +} + +void EventInitialize(int32_t threadCount, int32_t netServer) { + IEvtInputInitialize(); + + int32_t v2 = threadCount; + + if (threadCount < 1) { + v2 = 1; + } + + IEvtSchedulerInitialize(v2, netServer); + + // TODO + // OsInputSetEventPollProc(&sub_47DCA0); +} + +int32_t EventIsControlKeyDown() { + return EventIsKeyDown(KEY_LCONTROL) || EventIsKeyDown(KEY_RCONTROL); +} + +int32_t EventIsKeyDown(KEY key) { + // TODO + return 0; +} + +int32_t EventIsShiftKeyDown() { + return EventIsKeyDown(KEY_LSHIFT) || EventIsKeyDown(KEY_RSHIFT); +} + +HEVENTCONTEXT EventCreateContextEx(int32_t interactive, int32_t (*initializeHandler)(const void*, void*), int32_t (*destroyHandler)(const void*, void*), uint32_t idleTime, uint32_t debugFlags) { + return IEvtSchedulerCreateContext(interactive, initializeHandler, destroyHandler, idleTime, debugFlags); +} + +void EventDoMessageLoop() { + IEvtSchedulerProcess(); +} + +void EventPostCloseEx(HEVENTCONTEXT contextHandle) { + if (!contextHandle) { + contextHandle = PropGet(PROP_EVENTCONTEXT); + } + + if (contextHandle) { + uint32_t contextId = *reinterpret_cast(contextHandle); + int32_t findMask; + EvtContext* context = TSingletonInstanceId::s_idTable.Ptr( + contextId, + 0, + &findMask + ); + + if (context) { + context->m_critsect.Enter(); + + if (context->m_schedState == EvtContext::SCHEDSTATE_ACTIVE) { + context->m_schedState = EvtContext::SCHEDSTATE_CLOSED; + } + + context->m_critsect.Leave(); + + if (findMask != -1) { + TSingletonInstanceId::s_idTable.Unlock( + findMask & (INSTANCE_TABLE_SLOT_COUNT - 1), + findMask >= INSTANCE_TABLE_SLOT_COUNT + ); + } + } + } +} + +void EventRegister(EVENTID id, EVENTHANDLERFUNC handler) { + EventRegisterEx(id, handler, nullptr, 0.0f); +} + +void EventRegisterEx(EVENTID id, EVENTHANDLERFUNC handler, void* param, float priority) { + if (id < 0 || id > EVENTIDS || handler == nullptr) { + // TODO + // SErrSetLastError(0x57u); + + return; + } + + HEVENTCONTEXT hContext = PropGet(PROP_EVENTCONTEXT); + + uint32_t contextId = *reinterpret_cast(hContext); + int32_t findMask; + + EvtContext* context = TSingletonInstanceId::s_idTable.Ptr( + contextId, + 0, + &findMask + ); + + if (context) { + IEvtQueueRegister(context, id, handler, param, priority); + + if (findMask != -1) { + TSingletonInstanceId::s_idTable.Unlock( + findMask & (INSTANCE_TABLE_SLOT_COUNT - 1), + findMask >= INSTANCE_TABLE_SLOT_COUNT + ); + } + } +} diff --git a/src/event/Event.hpp b/src/event/Event.hpp new file mode 100644 index 0000000..22cc108 --- /dev/null +++ b/src/event/Event.hpp @@ -0,0 +1,56 @@ +#ifndef EVENT_EVENT_HPP +#define EVENT_EVENT_HPP + +#include "event/CEvent.hpp" +#include "event/Types.hpp" +#include +#include +#include +#include +#include + +class EvtContext; +class EvtThread; + +namespace Event { + extern SEvent s_startEvent; + extern SEvent s_shutdownEvent; + extern int32_t s_netServer; + extern int32_t s_originalThreadPriority; + extern int32_t s_threadSlotCount; + extern SCritSect* s_threadSlotCritsects; + extern EvtThread** s_threadSlots; + extern uint32_t s_mainThread; + extern TSGrowableArray s_schedulerThreads; + extern ATOMIC32 s_threadListContention; + extern SCritSect s_threadListCritsect; + extern TSList> s_threadList; + extern EvtContext* s_currentEvtContext; + extern ATOMIC32 s_interactiveCount; + + #if defined(WHOA_SYSTEM_MAC) + extern bool s_shouldLoopTerminate; + #endif +} + +HEVENTCONTEXT EventCreateContextEx(int32_t interactive, int32_t (*initializeHandler)(const void*, void*), int32_t (*destroyHandler)(const void*, void*), uint32_t idleTime, uint32_t debugFlags); + +void EventDoMessageLoop(); + +void EventInitialize(int32_t threadCount, int32_t netServer); + +int32_t EventIsControlKeyDown(); + +int32_t EventIsKeyDown(KEY key); + +int32_t EventIsShiftKeyDown(); + +void EventPostCloseEx(HEVENTCONTEXT contextHandle); + +void EventRegister(EVENTID id, int32_t (*handler)(const void*, void*)); + +void EventRegisterEx(EVENTID id, int32_t (*handler)(const void*, void*), void* param, float priority); + +void OsNetPump(uint32_t timeout); + +#endif diff --git a/src/event/EvtContext.cpp b/src/event/EvtContext.cpp new file mode 100644 index 0000000..a717b87 --- /dev/null +++ b/src/event/EvtContext.cpp @@ -0,0 +1,30 @@ +#include "event/EvtContext.hpp" +#include + +EvtContext::EvtContext(uint32_t flags, uint32_t idleTime, uint32_t schedWeight, void* callContext, int32_t startWatchdog) : + TSingletonInstanceId, m_id)>(), + m_critsect(), + m_schedNextWakeTime(), + m_queueHandlerList(), + m_queueMessageList(), + m_queueSyncKeyDownList() + // TODO + // m_timerIdTable() +{ + this->m_currTime = 0; + this->m_schedState = SCHEDSTATE_ACTIVE; + this->m_schedLastIdle = OsGetAsyncTimeMs(); + this->m_schedFlags = flags; + this->m_schedIdleTime = idleTime; + this->m_schedInitialIdleTime = idleTime; + this->m_schedWeight = schedWeight; + this->m_schedSmoothWeight = schedWeight; + this->m_schedRebalance = 0; + this->m_queueSyncButtonState = 0; + this->m_propContext = PropCreateContext(); + this->m_callContext = callContext; + this->m_startWatchdog = startWatchdog; +} + +EvtContextQueue::EvtContextQueue() : TSPriorityQueue(offsetof(EvtContext, m_schedNextWakeTime)) { +} diff --git a/src/event/EvtContext.hpp b/src/event/EvtContext.hpp new file mode 100644 index 0000000..119c2db --- /dev/null +++ b/src/event/EvtContext.hpp @@ -0,0 +1,57 @@ +#ifndef EVENT_EVT_CONTEXT_HPP +#define EVENT_EVT_CONTEXT_HPP + +#include "event/EvtHandler.hpp" +#include "event/EvtKeyDown.hpp" +#include "event/EvtMessage.hpp" +#include "event/EvtTimer.hpp" +#include "event/Types.hpp" +#include +#include +#include +#include +#include + +class EvtContext : public TSingletonInstanceId, m_id)> { + public: + // Types + enum SCHEDSTATE { + SCHEDSTATE_ACTIVE = 0x0, + SCHEDSTATE_CLOSED = 0x1, + SCHEDSTATE_DESTROYED = 0x2, + _UNIQUE_SYMBOL_SCHEDSTATE_96 = 0xFFFFFFFF, + }; + + // Member variables + SCritSect m_critsect; + uint32_t m_currTime; + SCHEDSTATE m_schedState; + TSTimerPriority m_schedNextWakeTime; + uint32_t m_schedLastIdle; + uint32_t m_schedFlags; + uint32_t m_schedIdleTime; + uint32_t m_schedInitialIdleTime; + uint32_t m_schedWeight; + uint32_t m_schedSmoothWeight; + int32_t m_schedRebalance; + TSExplicitList m_queueHandlerList[EVENTIDS]; + TSExplicitList m_queueMessageList; + uint32_t m_queueSyncButtonState; + TSExplicitList m_queueSyncKeyDownList; + // TODO + // EvtIdTable m_timerIdTable; + EvtTimerQueue m_timerQueue; + HPROPCONTEXT m_propContext; + void* m_callContext; + uint32_t m_startWatchdog; + + // Member functions + EvtContext(uint32_t, uint32_t, uint32_t, void*, int32_t); +}; + +class EvtContextQueue : public TSPriorityQueue { + public: + EvtContextQueue(); +}; + +#endif diff --git a/src/event/EvtHandler.hpp b/src/event/EvtHandler.hpp new file mode 100644 index 0000000..ed63ed4 --- /dev/null +++ b/src/event/EvtHandler.hpp @@ -0,0 +1,17 @@ +#ifndef EVENT_EVT_HANDLER_HPP +#define EVENT_EVT_HANDLER_HPP + +#include +#include + +class EvtHandler { + public: + // Member variables + TSLink link; + int32_t (*func)(const void*, void*); + void* param; + float priority; + int32_t marker; +}; + +#endif diff --git a/src/event/EvtKeyDown.hpp b/src/event/EvtKeyDown.hpp new file mode 100644 index 0000000..c42ba19 --- /dev/null +++ b/src/event/EvtKeyDown.hpp @@ -0,0 +1,14 @@ +#ifndef EVENT_EVT_KEY_DOWN_HPP +#define EVENT_EVT_KEY_DOWN_HPP + +#include "event/Types.hpp" +#include + +class EvtKeyDown { + public: + // Member variables + TSLink link; + KEY key; +}; + +#endif diff --git a/src/event/EvtMessage.hpp b/src/event/EvtMessage.hpp new file mode 100644 index 0000000..8d825f9 --- /dev/null +++ b/src/event/EvtMessage.hpp @@ -0,0 +1,16 @@ +#ifndef EVENT_EVT_MESSAGE_HPP +#define EVENT_EVT_MESSAGE_HPP + +#include "event/Types.hpp" +#include +#include + +class EvtMessage : public TExtraInstanceRecyclable { + public: + // Member variables + TSLink link; + EVENTID id; + char data[4]; +}; + +#endif diff --git a/src/event/EvtThread.cpp b/src/event/EvtThread.cpp new file mode 100644 index 0000000..38b72d3 --- /dev/null +++ b/src/event/EvtThread.cpp @@ -0,0 +1,4 @@ +#include "event/EvtThread.hpp" + +EvtThread::EvtThread() : TSLinkedNode() { +} diff --git a/src/event/EvtThread.hpp b/src/event/EvtThread.hpp new file mode 100644 index 0000000..45f87c5 --- /dev/null +++ b/src/event/EvtThread.hpp @@ -0,0 +1,25 @@ +#ifndef EVENT_EVT_THREAD_HPP +#define EVENT_EVT_THREAD_HPP + +#include "event/EvtContext.hpp" +#include +#include +#include + +class EvtThread : public TSLinkedNode { + public: + // Member variables + uint32_t m_threadSlot; + uint32_t m_threadCount; + uint32_t m_weightTotal; + uint32_t m_weightAvg; + uint32_t m_contextCount; + uint32_t m_rebalance; + SEvent m_wakeEvent = SEvent(0, 0); + EvtContextQueue m_contextQueue; + + // Member functions + EvtThread(); +}; + +#endif diff --git a/src/event/EvtTimer.hpp b/src/event/EvtTimer.hpp new file mode 100644 index 0000000..d94fd5b --- /dev/null +++ b/src/event/EvtTimer.hpp @@ -0,0 +1,27 @@ +#ifndef EVENT_EVT_TIMER_HPP +#define EVENT_EVT_TIMER_HPP + +#include +#include + +class EvtTimer { + public: + // Member variables + uint32_t id; + TSTimerPriority targetTime; + float timeout; + int32_t (*handler)(const void*, void*); + void* param; + int32_t (*guidHandler)(const void*, uint64_t, void*); + uint64_t guidParam; + void* guidParam2; +}; + +class EvtTimerQueue : public TSPriorityQueue { + public: + EvtTimerQueue() + : TSPriorityQueue(offsetof(EvtTimer, targetTime)) + {}; +}; + +#endif diff --git a/src/event/Input.cpp b/src/event/Input.cpp new file mode 100644 index 0000000..e3f3fef --- /dev/null +++ b/src/event/Input.cpp @@ -0,0 +1,686 @@ +#include "event/Input.hpp" +#include "event/EvtContext.hpp" +#include "event/Queue.hpp" +#include "gx/Window.hpp" +#include +#include +#include +#include +#include + +#if defined(WHOA_SYSTEM_MAC) + #include "app/mac/MacClient.h" +#endif + +namespace Input { + CRect s_boundingRect; + int32_t s_queueHead; + int32_t s_queueTail; + OSEVENT s_queue[32]; + + MOUSEBUTTON s_buttonConversion[16] = { + MOUSE_BUTTON_NONE, + MOUSE_BUTTON_LEFT, + MOUSE_BUTTON_RIGHT, + MOUSE_BUTTON_MIDDLE, + MOUSE_BUTTON_XBUTTON1, + MOUSE_BUTTON_XBUTTON2, + MOUSE_BUTTON_XBUTTON3, + MOUSE_BUTTON_XBUTTON4, + MOUSE_BUTTON_XBUTTON5, + MOUSE_BUTTON_XBUTTON6, + MOUSE_BUTTON_XBUTTON7, + MOUSE_BUTTON_XBUTTON8, + MOUSE_BUTTON_XBUTTON9, + MOUSE_BUTTON_XBUTTON10, + MOUSE_BUTTON_XBUTTON11, + MOUSE_BUTTON_XBUTTON12, + }; +} + +int32_t Input::s_buttonDown[16]; +uint32_t Input::s_buttonState; +C2iVector Input::s_currentMouse; +uint32_t Input::s_mouseHoldButton; +MOUSEMODE Input::s_mouseMode; +int32_t Input::s_numlockState; +int32_t Input::s_simulatedRightButtonClick; +uint32_t Input::s_metaKeyState; + +#if defined(WHOA_SYSTEM_WIN) + int32_t Input::s_savedMouseSpeed; +#endif + +#if defined(WHOA_SYSTEM_MAC) + double Input::s_savedMouseSpeed; +#endif + +void PostChar(EvtContext* context, int32_t ch, int32_t repeat) { + EVENT_DATA_CHAR data; + + data.ch = ch; + data.metaKeyState = Input::s_metaKeyState; + data.repeat = repeat; + + IEvtQueueDispatch(context, EVENT_ID_CHAR, &data); +} + +void PostKeyDown(EvtContext* context, int32_t key, int32_t repeat, int32_t time) { + if (key <= KEY_LASTMETAKEY) { + if ((1 << key) & Input::s_metaKeyState) { + return; + } + + Input::s_metaKeyState |= 1 << key; + } + + EVENT_DATA_KEY data; + + data.key = static_cast(key); + data.metaKeyState = Input::s_metaKeyState; + data.repeat = repeat; + data.time = time; + + IEvtQueueDispatch(context, EVENT_ID_KEYDOWN, &data); +} + +void PostKeyUp(EvtContext* context, int32_t key, int32_t repeat, int32_t time) { + if (key <= KEY_LASTMETAKEY) { + if ( !((1 << key) & Input::s_metaKeyState) ) { + return; + } + + Input::s_metaKeyState &= ~(1 << key); + } + + EVENT_DATA_KEY data; + + data.key = static_cast(key); + data.metaKeyState = Input::s_metaKeyState; + data.repeat = repeat; + data.time = time; + + IEvtQueueDispatch(context, EVENT_ID_KEYUP, &data); +} + +void PostMouseDown(EvtContext* context, MOUSEBUTTON button, int32_t x, int32_t y, int32_t time) { + Input::s_buttonState |= button; + + EVENT_DATA_MOUSE data; + + data.mode = Input::s_mouseMode; + data.button = button; + data.buttonState = Input::s_buttonState; + data.metaKeyState = Input::s_metaKeyState; + data.flags = GenerateMouseFlags(); + data.time = time; + + ConvertPosition(x, y, &data.x, &data.y); + + IEvtQueueDispatch(context, EVENT_ID_MOUSEDOWN, &data); +} + +void PostMouseMove(EvtContext* context, int32_t x, int32_t y, int32_t time) { + EVENT_DATA_MOUSE data; + + data.mode = Input::s_mouseMode; + data.button = MOUSE_BUTTON_NONE; + data.buttonState = Input::s_buttonState; + data.metaKeyState = Input::s_metaKeyState; + data.flags = GenerateMouseFlags(); + data.time = time; + + ConvertPosition(x, y, &data.x, &data.y); + + IEvtQueueDispatch(context, EVENT_ID_MOUSEMOVE, &data); +} + +void PostMouseUp(EvtContext* context, MOUSEBUTTON button, int32_t x, int32_t y, uint32_t flags, int32_t time) { + Input::s_buttonState &= ~button; + + EVENT_DATA_MOUSE data; + + data.mode = Input::s_mouseMode; + data.button = button; + data.buttonState = Input::s_buttonState; + data.metaKeyState = Input::s_metaKeyState; + data.flags = flags | GenerateMouseFlags(); + data.time = time; + + ConvertPosition(x, y, &data.x, &data.y); + + IEvtQueueDispatch(context, EVENT_ID_MOUSEUP, &data); + + CheckMouseModeState(); +} + +void PostSize(EvtContext* context, int32_t w, int32_t h) { + EVENT_DATA_SIZE data; + + data.w = w; + data.h = h; + + IEvtQueueDispatch(context, EVENT_ID_SIZE, &data); +} + +void ProcessInput(const int32_t param[], OSINPUT id, int32_t* shutdown, EvtContext* context) { + if (!context) { + // TODO + // nullsub_3(); + // SErrSetLastError(0x57u); + } + + switch (id) { + case OS_INPUT_CAPTURE_CHANGED: + // TODO + break; + + case OS_INPUT_CHAR: + PostChar( + context, + param[0], + param[1] + ); + + break; + + case OS_INPUT_STRING: + // TODO + break; + + case OS_INPUT_IME: + // TODO + break; + + case OS_INPUT_SIZE: + PostSize( + context, + param[0], + param[1] + ); + + break; + + case OS_INPUT_CLOSE: + // TODO + break; + + case OS_INPUT_FOCUS: + // TODO + break; + + case OS_INPUT_KEY_DOWN: + PostKeyDown( + context, + param[0], + param[1], + param[3] + ); + + break; + + case OS_INPUT_KEY_UP: + PostKeyUp( + context, + param[0], + param[1], + param[3] + ); + + break; + + case OS_INPUT_MOUSE_DOWN: + PostMouseDown( + context, + static_cast(param[0]), + param[1], + param[2], + param[3] + ); + + break; + + case OS_INPUT_MOUSE_MOVE: + PostMouseMove( + context, + param[1], + param[2], + param[3] + ); + + break; + + case OS_INPUT_MOUSE_WHEEL: + // TODO + break; + + case OS_INPUT_MOUSE_MOVE_RELATIVE: + // TODO + break; + + case OS_INPUT_MOUSE_UP: + PostMouseUp( + context, + static_cast(param[0]), + param[1], + param[2], + 0, + param[3] + ); + + break; + + case OS_INPUT_14: + // TODO + break; + + case OS_INPUT_15: + // TODO + break; + + case OS_INPUT_16: + // TODO + break; + + case OS_INPUT_17: + // TODO + break; + + case OS_INPUT_18: + // TODO + break; + + case OS_INPUT_SHUTDOWN: + // TODO + break; + } +} + +void CheckMouseModeState() { + if (Input::s_mouseHoldButton) { + if (Input::s_mouseHoldButton != (Input::s_mouseHoldButton & Input::s_buttonState)) { + // TODO + // EventSetMouseMode(0, 0); + } + } +} + +MOUSEBUTTON ConvertButtonNumberToMOUSEBUTTON(int32_t buttonNumber) { + return Input::s_buttonConversion[buttonNumber]; +} + +void ConvertPosition(int32_t clientx, int32_t clienty, float* x, float* y) { + if (Input::s_boundingRect.maxX - Input::s_boundingRect.minX != 0.0 && Input::s_boundingRect.maxY - Input::s_boundingRect.minY != 0.0) { + C2Vector pt = { + static_cast(clientx), + static_cast(clienty) + }; + + if (!Input::s_boundingRect.IsPointInside(pt)) { + // TODO + // - handle out of bounds positions + } + } + + tagRECT windowDim; + OsGetDefaultWindowRect(&windowDim); + + *x = static_cast(clientx) / static_cast(windowDim.right - windowDim.left); + *y = 1.0 - (static_cast(clienty) / static_cast(windowDim.bottom - windowDim.top)); +} + +uint32_t GenerateMouseFlags() { + uint32_t flags = 0; + + if (Input::s_mouseMode == MOUSE_MODE_RELATIVE) { + flags |= 0x2; + } + + return flags; +} + +const char* GetButtonName(int32_t button) { + switch (button) { + case 0x1: + return "LeftButton"; + case 0x2: + return "MiddleButton"; + case 0x4: + return "RightButton"; + case 0x8: + return "Button4"; + case 0x10: + return "Button5"; + case 0x20: + return "Button6"; + case 0x40: + return "Button7"; + case 0x80: + return "Button8"; + case 0x100: + return "Button9"; + case 0x200: + return "Button10"; + case 0x400: + return "Button11"; + case 0x800: + return "Button12"; + case 0x1000: + return "Button13"; + case 0x2000: + return "Button14"; + case 0x4000: + return "Button15"; + case 0x8000: + return "Button16"; + case 0x10000: + return "Button17"; + case 0x20000: + return "Button18"; + case 0x40000: + return "Button19"; + case 0x80000: + return "Button20"; + case 0x100000: + return "Button21"; + case 0x200000: + return "Button22"; + case 0x400000: + return "Button23"; + case 0x800000: + return "Button24"; + case 0x1000000: + return "Button25"; + case 0x2000000: + return "Button26"; + case 0x4000000: + return "Button27"; + case 0x8000000: + return "Button28"; + case 0x10000000: + return "Button29"; + case 0x20000000: + return "Button30"; + case 0x40000000: + return "Button31"; + default: + return "UNKNOWN"; + } +} + +void IEvtInputInitialize() { + OsInputInitialize(); +} + +int32_t IEvtInputProcess(EvtContext* context, int32_t* shutdown) { + if (context) { + // TODO + // nullsub_3(); + + int32_t v4 = 0; + OSINPUT id; + int32_t param[4]; + + while (OsInputGet(&id, ¶m[0], ¶m[1], ¶m[2], ¶m[3])) { + v4 = 1; + ProcessInput(param, id, shutdown, context); + } + + return v4; + } else { + // TODO + // nullsub_3(); + // SErrSetLastError(0x57u); + + return 0; + } +} + +const char* KeyCodeToString(KEY key) { + static char charBuf[8]; + + if (key - 33 <= 222) { + SUniSPutUTF8(key, charBuf); + return charBuf; + } + + if (key <= KEY_SPACE) { + switch (key) { + case KEY_NONE: + return "NONE"; + case KEY_LSHIFT: + return "LSHIFT"; + case KEY_RSHIFT: + return "RSHIFT"; + case KEY_LCONTROL: + return "LCTRL"; + case KEY_RCONTROL: + return "RCTRL"; + case KEY_LALT: + return "LALT"; + case KEY_RALT: + return "RALT"; + case KEY_SPACE: + return "SPACE"; + default: + return "UNKNOWN"; + } + } + + if (key <= KEY_ESCAPE) { + switch (key) { + case KEY_NUMPAD0: + case KEY_NUMPAD1: + case KEY_NUMPAD2: + case KEY_NUMPAD3: + case KEY_NUMPAD4: + case KEY_NUMPAD5: + case KEY_NUMPAD6: + case KEY_NUMPAD7: + case KEY_NUMPAD8: + case KEY_NUMPAD9: + SStrPrintf(charBuf, sizeof(charBuf), "NUMPAD%d", key); + return charBuf; + case KEY_NUMPAD_PLUS: + return "NUMPADPLUS"; + case KEY_NUMPAD_MINUS: + return "NUMPADMINUS"; + case KEY_NUMPAD_MULTIPLY: + return "NUMPADMULTIPLY"; + case KEY_NUMPAD_DIVIDE: + return "NUMPADDIVIDE"; + case KEY_NUMPAD_DECIMAL: + return "NUMPADDECIMAL"; + case KEY_ESCAPE: + return "ESCAPE"; + default: + return "UNKNOWN"; + } + } + + if (key <= KEY_PRINTSCREEN) { + switch (key) { + case KEY_ENTER: + return "ENTER"; + case KEY_BACKSPACE: + return "BACKSPACE"; + case KEY_TAB: + return "TAB"; + case KEY_LEFT: + return "LEFT"; + case KEY_UP: + return "UP"; + case KEY_RIGHT: + return "RIGHT"; + case KEY_DOWN: + return "DOWN"; + case KEY_INSERT: + return "INSERT"; + case KEY_DELETE: + return "DELETE"; + case KEY_HOME: + return "HOME"; + case KEY_END: + return "END"; + case KEY_PAGEUP: + return "PAGEUP"; + case KEY_PAGEDOWN: + return "PAGEDOWN"; + case KEY_CAPSLOCK: + return "CAPSLOCK"; + case KEY_NUMLOCK: + return "NUMLOCK"; + case KEY_PRINTSCREEN: + return "PRINTSCREEN"; + default: + return "UNKNOWN"; + } + } + + if (key <= KEY_F12) { + switch (key) { + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + case KEY_F11: + case KEY_F12: + SStrPrintf(charBuf, sizeof(charBuf), "F%d", key - KEY_F1 + 1); + return charBuf; + default: + return "UNKNOWN"; + } + } + + if (key == KEY_NUMPAD_EQUALS) { + return "NUMPADEQUALS"; + } + + return "UNKNOWN"; +} + +int32_t OsInputGet(OSINPUT* id, int32_t* param0, int32_t* param1, int32_t* param2, int32_t* param3) { + #if defined(WHOA_SYSTEM_WIN) + // TODO + #endif + + #if defined(WHOA_SYSTEM_MAC) + // TODO + // Unknown logic + + if (!OsInputIsUsingCocoaEventLoop()) { + // Legacy Carbon input handling + return 0; + } + + // TODO + // Steelseries WoW Mouse logic + + if (Input::s_queueTail == Input::s_queueHead) { + return 0; + } + + OsQueueSetParam(3, OsGetAsyncTimeMs()); + + return OsQueueGet(id, param0, param1, param2, param3); + #endif +} + +void OsInputInitialize() { + #if defined(WHOA_SYSTEM_WIN) + Input::s_numlockState = GetAsyncKeyState(144); + PVOID pvParam = 10; + SystemParametersInfoA(SPI_GETMOUSESPEED, 0, &pvParam, 0); + Input::s_savedMouseSpeed = pvParam; + #endif + + #if defined(WHOA_SYSTEM_MAC) + // Legacy Carbon input handling + // if (!byte_143EFE0) { + // Carbon_OsInputRegisterHICommandHandler(0x71756974, sub_A4F230); + // } + + MacClient::SetMouseCoalescingEnabled(true); + Input::s_savedMouseSpeed = MacClient::GetMouseSpeed(); + #endif +} + +bool OsInputIsUsingCocoaEventLoop() { + // TODO + + return true; +} + +void OsInputPostEvent(OSINPUT id, int32_t param0, int32_t param1, int32_t param2, int32_t param3) { + // TODO +} + +int32_t OsQueueGet(OSINPUT* id, int32_t* param0, int32_t* param1, int32_t* param2, int32_t* param3) { + if (Input::s_queueTail == Input::s_queueHead) { + return 0; + } + + OSEVENT event = Input::s_queue[Input::s_queueTail]; + + *id = event.id; + *param0 = event.param[0]; + *param1 = event.param[1]; + *param2 = event.param[2]; + *param3 = event.param[3]; + + if (Input::s_queueTail == OS_QUEUE_SIZE - 1) { + Input:: s_queueTail = 0; + } else { + ++Input::s_queueTail; + } + + return 1; +} + +void OsQueuePut(OSINPUT id, int32_t param0, int32_t param1, int32_t param2, int32_t param3) { + int32_t nextTail = 0; + int32_t nextHead = 0; + + if (Input::s_queueHead != OS_QUEUE_SIZE - 1) { + nextHead = Input::s_queueHead + 1; + } + + if (nextHead == Input::s_queueTail) { + if (nextHead != OS_QUEUE_SIZE - 1) { + nextTail = nextHead + 1; + } + + Input::s_queueTail = nextTail; + } + + OSEVENT* event = &Input::s_queue[Input::s_queueHead]; + + event->id = id; + event->param[0] = param0; + event->param[1] = param1; + event->param[2] = param2; + event->param[3] = param3; + + Input::s_queueHead = nextHead; +} + +void OsQueueSetParam(int32_t index, int32_t param) { + int32_t pos = Input::s_queueTail; + + while (pos != Input::s_queueHead) { + OSEVENT* event = &Input::s_queue[pos]; + event->param[index] = param; + + if (pos == OS_QUEUE_SIZE - 1) { + pos = 0; + } else { + ++pos; + } + } +} diff --git a/src/event/Input.hpp b/src/event/Input.hpp new file mode 100644 index 0000000..1407a63 --- /dev/null +++ b/src/event/Input.hpp @@ -0,0 +1,62 @@ +#ifndef EVENT_INPUT_HPP +#define EVENT_INPUT_HPP + +#include "event/Types.hpp" +#include + +#define OS_QUEUE_SIZE 32 + +class C2iVector; +class CRect; +class EvtContext; + +namespace Input { + extern int32_t s_buttonDown[16]; + extern uint32_t s_buttonState; + extern C2iVector s_currentMouse; + extern uint32_t s_mouseHoldButton; + extern MOUSEMODE s_mouseMode; + extern int32_t s_numlockState; + extern int32_t s_simulatedRightButtonClick; + extern uint32_t s_metaKeyState; + + #if defined(WHOA_SYSTEM_WIN) + extern int32_t s_savedMouseSpeed; + #endif + + #if defined(WHOA_SYSTEM_MAC) + extern double s_savedMouseSpeed; + #endif +} + +void CheckMouseModeState(void); + +MOUSEBUTTON ConvertButtonNumberToMOUSEBUTTON(int32_t); + +void ConvertPosition(int32_t, int32_t, float*, float*); + +uint32_t GenerateMouseFlags(void); + +const char* GetButtonName(int32_t); + +void IEvtInputInitialize(); + +int32_t IEvtInputProcess(EvtContext* context, int32_t* shutdown); + +const char* KeyCodeToString(KEY); + +int32_t OsInputGet(OSINPUT*, int32_t*, int32_t*, int32_t*, int32_t*); + +void OsInputInitialize(void); + +bool OsInputIsUsingCocoaEventLoop(void); + +void OsInputPostEvent(OSINPUT, int32_t, int32_t, int32_t, int32_t); + +int32_t OsQueueGet(OSINPUT*, int32_t*, int32_t*, int32_t*, int32_t*); + +void OsQueuePut(OSINPUT, int32_t, int32_t, int32_t, int32_t); + +void OsQueueSetParam(int32_t, int32_t); + +#endif diff --git a/src/event/Queue.cpp b/src/event/Queue.cpp new file mode 100644 index 0000000..74d4735 --- /dev/null +++ b/src/event/Queue.cpp @@ -0,0 +1,74 @@ +#include "event/Queue.hpp" +#include "event/EvtContext.hpp" +#include "event/EvtHandler.hpp" +#include + +void IEvtQueueDispatch(EvtContext* context, EVENTID id, const void* data) { + STORM_ASSERT(context); + + // TODO + // UpdateSyncState(data, &id, context, v3); + + // TODO + // if (SErrIsDisplayingError()) { + // return; + // } + + auto handlerList = &context->m_queueHandlerList[id]; + + EvtHandler marker; + marker.marker = 1; + + handlerList->LinkNode(&marker, 1, nullptr); + + EvtHandler* handler; + + while (1) { + handler = marker.link.Next(); + + if (!handler) { + break; + } + + handlerList->LinkNode(&marker, 1, marker.link.Next()); + + if (!handler->marker && !handler->func(data, handler->param)) { + break; + } + } + + handlerList->UnlinkNode(&marker); +} + +void IEvtQueueDispatchAll(EvtContext* context) { + // TODO +} + +void IEvtQueueRegister(EvtContext* context, EVENTID id, int32_t (*handler)(const void*, void*), void* param, float priority) { + STORM_ASSERT(context); + + auto handlerList = &context->m_queueHandlerList[id]; + + EvtHandler* evtHandler; + + void* m = SMemAlloc(sizeof(EvtHandler), __FILE__, __LINE__, 0x8); + + if (m) { + evtHandler = new (m) EvtHandler(); + } else { + evtHandler = nullptr; + } + + evtHandler->priority = priority; + evtHandler->param = param; + evtHandler->func = handler; + evtHandler->marker = 0; + + EvtHandler* h = handlerList->Head(); + + while (h && (priority < h->priority || h->marker)) { + h = handlerList->Link(h)->Next(); + } + + handlerList->LinkNode(evtHandler, 1, h); +} diff --git a/src/event/Queue.hpp b/src/event/Queue.hpp new file mode 100644 index 0000000..f916f0c --- /dev/null +++ b/src/event/Queue.hpp @@ -0,0 +1,14 @@ +#ifndef EVENT_QUEUE_HPP +#define EVENT_QUEUE_HPP + +#include "event/Types.hpp" + +class EvtContext; + +void IEvtQueueDispatch(EvtContext* context, EVENTID id, const void* data); + +void IEvtQueueDispatchAll(EvtContext* context); + +void IEvtQueueRegister(EvtContext* context, EVENTID id, int32_t (*handler)(const void*, void*), void* param, float priority); + +#endif diff --git a/src/event/Scheduler.cpp b/src/event/Scheduler.cpp new file mode 100644 index 0000000..0fe2023 --- /dev/null +++ b/src/event/Scheduler.cpp @@ -0,0 +1,382 @@ +#include "event/Scheduler.hpp" +#include "event/Context.hpp" +#include "event/Event.hpp" +#include "event/EvtContext.hpp" +#include "event/EvtThread.hpp" +#include "event/Input.hpp" +#include "event/Queue.hpp" +#include "event/Synthesize.hpp" +#include "event/Timer.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(WHOA_SYSTEM_MAC) + #include "event/mac/Event.h" +#endif + +void DestroySchedulerThread(uint32_t a1) { + // TODO +} + +HEVENTCONTEXT IEvtSchedulerCreateContext(int32_t interactive, int32_t (*initializeHandler)(const void*, void*), int32_t (*destroyHandler)(const void*, void*), uint32_t idleTime, uint32_t debugFlags) { + if (idleTime < 1) { + idleTime = 1; + } + + char contextName[256]; + void* callContext = nullptr; + + if (debugFlags & 0x1) { + SStrPrintf(contextName, 256, "Context: interactive = %u, idleTime = %u", interactive, idleTime); + callContext = OsCallInitializeContext(contextName); + } + + void* m = SMemAlloc(sizeof(EvtContext), __FILE__, __LINE__, 0); + + EvtContext* context; + + if (m) { + context = new (m) EvtContext( + interactive != 0 ? 2 : 0, + idleTime, + interactive != 0 ? 1000 : 1, + callContext, + (debugFlags >> 1) & 1 + ); + } else { + context = nullptr; + } + + if (interactive) { + SInterlockedIncrement(&Event::s_interactiveCount); + } + + if (initializeHandler) { + IEvtQueueRegister(context, EVENT_ID_INITIALIZE, initializeHandler, 0, 1000.0); + } + + if (destroyHandler) { + IEvtQueueRegister(context, EVENT_ID_DESTROY, destroyHandler, 0, 1000.0); + } + + return AttachContextToThread(context); +} + +void IEvtSchedulerInitialize(int32_t threadCount, int32_t netServer) { + if (Event::s_threadSlotCount) { + // SErrDisplayAppFatal("IEvtScheduler already initialized"); + } + + Event::s_netServer = netServer; + + // TODO + // Thread::s_originalThreadPriority = SGetCurrentThreadPriority(); + + int32_t threadSlotCount = 1; + + while (threadSlotCount < threadCount) { + threadSlotCount *= 2; + } + + Event::s_threadSlotCount = threadSlotCount; + + // Allocate SCritSects for each thread slot + int32_t v4 = sizeof(SCritSect) * threadSlotCount; + + void* v5 = SMemAlloc((v4 + 4), ".\\EvtSched.cpp", 791, 0); + + if (v5) { + Event::s_threadSlotCritsects = new (v5) SCritSect[threadSlotCount]; + } else { + Event::s_threadSlotCritsects = nullptr; + } + + // Allocate EvtThread pointers for each thread slot + Event::s_threadSlots = static_cast(SMemAlloc(sizeof(EvtThread*) * threadSlotCount, __FILE__, __LINE__, 0)); + memset(Event::s_threadSlots, 0, sizeof(EvtThread*) * threadSlotCount); + + Event::s_startEvent.Reset(); + Event::s_shutdownEvent.Reset(); + + Event::s_mainThread = InitializeSchedulerThread(); + + for (int32_t i = 0; i < threadCount - 1; ++i) { + void* m = SMemAlloc(sizeof(SThread), __FILE__, __LINE__, 0); + + SThread* thread; + + if (m) { + thread = new (m) SThread(); + } else { + thread = nullptr; + } + + Event::s_schedulerThreads.SetCount(Event::s_schedulerThreads.Count() + 1); + + Event::s_schedulerThreads[Event::s_schedulerThreads.Count() - 1] = thread; + + char threadName[16]; + SStrPrintf(threadName, 16, "EvtSched#%d", i); + + if (!SThread::Create(&SchedulerThreadProc, 0, *thread, threadName, 0)) { + // TODO + } + } +} + +void IEvtSchedulerProcess() { + #if defined(WHOA_SYSTEM_WIN) + Event::s_startEvent.Set(); + + SchedulerThreadProc(1); + + Event::s_mainThread = 0; + #endif + + #if defined(WHOA_SYSTEM_MAC) + Event::s_startEvent.Set(); + + if (OsInputIsUsingCocoaEventLoop()) { + PropSelectContext(0); + + Event::s_startEvent.Wait(0xFFFFFFFF); + + uintptr_t v0 = SGetCurrentThreadId(); + char v2[64]; + SStrPrintf(v2, 64, "Engine %x", v0); + + OsCallInitialize(v2); + + RunCocoaEventLoop(); + + DestroySchedulerThread(Event::s_mainThread); + OsCallDestroy(); + + Event::s_mainThread = 0; + } else { + // Legacy + // sub_890180(1); + // dword_141B3C8 = 0; + } + #endif +} + +void IEvtSchedulerShutdown() { + // TODO +} + +uint32_t InitializeSchedulerThread() { + SInterlockedIncrement(&Event::s_threadListContention); + + Event::s_threadListCritsect.Enter(); + + uint32_t slot = Event::s_threadSlotCount; + + for (int32_t i = 0; i < Event::s_threadSlotCount; ++i) { + if (slot == Event::s_threadSlotCount + || Event::s_threadSlots[i] == nullptr + || Event::s_threadSlots[i]->m_threadCount < Event::s_threadSlots[slot]->m_threadCount) + { + slot = i; + + if (!Event::s_threadSlots[i]) { + break; + } + } + } + + EvtThread* v4 = Event::s_threadSlots[slot]; + + if (!v4) { + v4 = Event::s_threadList.NewNode(1, 0, 0x8); + + v4->m_threadCount = 0; + v4->m_weightTotal = 0; + v4->m_weightAvg = 0; + v4->m_contextCount = 0; + v4->m_rebalance = 0; + v4->m_threadSlot = slot; + + Event::s_threadSlotCritsects[slot].Enter(); + + Event::s_threadSlots[slot] = v4; + + Event::s_threadSlotCritsects[slot].Leave(); + } + + ++v4->m_threadCount; + + Event::s_threadListCritsect.Leave(); + + SInterlockedDecrement(&Event::s_threadListContention); + + return slot; +} + +bool SchedulerMainProcess() { + return SchedulerThreadProcProcess(Event::s_mainThread) != 0; +} + +uint32_t SchedulerThreadProc(void* mainThread) { + uint32_t v1; + + if (mainThread) { + v1 = Event::s_mainThread; + } else { + v1 = InitializeSchedulerThread(); + } + + PropSelectContext(0); + + Event::s_startEvent.Wait(0xFFFFFFFF); + + uintptr_t v2 = SGetCurrentThreadId(); + char v4[64]; + SStrPrintf(v4, 64, "Engine %x", v2); + + OsCallInitialize(v4); + + while (!SchedulerThreadProcProcess(v1)); + + DestroySchedulerThread(v1); + OsCallDestroy(); + + return 0; +} + +int32_t SchedulerThreadProcProcess(uint32_t a1) { + // TODO + // if (SGetCurrentThreadPriority() != Event::s_originalThreadPriority) { + // SSetCurrentThreadPriority(Event::s_originalThreadPriority); + // } + + if (!Event::s_shutdownEvent.Wait(0)) { + return 1; + } + + EvtContext* context = GetNextContext(a1); + + int32_t v11; + + if (context) { + v11 = context->m_schedNextWakeTime.m_val - OsGetAsyncTimeMs(); + + if (v11 < 0) { + v11 = 0; + } + } + + uint32_t v14; + + if (Event::s_netServer) { + if (v11 == -1) { + v11 = 100; + } + + OsNetPump(v11); + + v14 = 258; + } else { + v14 = Event::s_threadSlots[a1]->m_wakeEvent.Wait(v11); + } + + if (!context) { + return 0; + } + + PropSelectContext(context->m_propContext); + PropSet(PROP_EVENTCONTEXT, &context->m_id); + OsCallSetContext(context->m_callContext); + + uint32_t currTime = OsGetAsyncTimeMs(); + uint32_t v19 = context->m_id; + + if (v14 == 258) { + if (SynthesizeInitialize(context)) { + if (context->m_startWatchdog) { + // nullsub_5(20, 1); + // *a2 = 1; + } + } + + uint32_t v9 = (currTime - context->m_schedLastIdle); + context->m_schedLastIdle = currTime; + double elapsedSec = v9 * 0.001; + + // TODO + // FrameTime::Update(currTime, elapsedSec); + + IEvtTimerDispatch(context); + + if (context->m_schedFlags & 0x2) { + int32_t shutdown = 0; + IEvtInputProcess(context, &shutdown); + + if (shutdown) { + context->m_critsect.Enter(); + + if (context->m_schedState == EvtContext::SCHEDSTATE_ACTIVE) { + context->m_schedState = EvtContext::SCHEDSTATE_CLOSED; + } + + context->m_critsect.Leave(); + + IEvtSchedulerShutdown(); + } + } + + SynthesizePoll(context); + IEvtQueueDispatchAll(context); + SynthesizeIdle(context, currTime, elapsedSec); + SynthesizePaint(context); + } + + if (a1 == Event::s_mainThread) { + // TODO + // dword_B417C4 = 0; + } + + context->m_critsect.Enter(); + + uint32_t closed = context->m_schedState == EvtContext::SCHEDSTATE_CLOSED; + + context->m_critsect.Leave(); + + if (closed) { + DetachContextFromThread(a1, context); + SynthesizeDestroy(context); + return 0; + } + + uint32_t nextDelay; + + if (context->m_schedFlags & 0x4) { + nextDelay = 0; + } else { + int32_t v15 = IEvtTimerGetNextTime(context, currTime); + int32_t v16 = context->m_schedIdleTime; + + nextDelay = v15; + + if (v16 != context->m_schedInitialIdleTime) { + nextDelay = context->m_schedIdleTime; + } + + nextDelay = std::min( + nextDelay, + std::max((uint32_t)0, v16 + context->m_schedLastIdle - currTime) + ); + } + + OsCallResetContext(context->m_callContext); + PropSelectContext(nullptr); + PutContext(nextDelay + currTime, context->m_schedSmoothWeight, context, a1); + + return 0; +} diff --git a/src/event/Scheduler.hpp b/src/event/Scheduler.hpp new file mode 100644 index 0000000..5871d0c --- /dev/null +++ b/src/event/Scheduler.hpp @@ -0,0 +1,25 @@ +#ifndef EVENT_SCHEDULER_HPP +#define EVENT_SCHEDULER_HPP + +#include "event/Types.hpp" +#include + +void DestroySchedulerThread(uint32_t a1); + +HEVENTCONTEXT IEvtSchedulerCreateContext(int32_t interactive, int32_t (*initializeHandler)(const void*, void*), int32_t (*destroyHandler)(const void*, void*), uint32_t idleTime, uint32_t debugFlags); + +void IEvtSchedulerInitialize(int32_t threadCount, int32_t netServer); + +void IEvtSchedulerProcess(); + +void IEvtSchedulerShutdown(); + +uint32_t InitializeSchedulerThread(); + +bool SchedulerMainProcess(); + +uint32_t SchedulerThreadProc(void* mainThread); + +int32_t SchedulerThreadProcProcess(uint32_t a1); + +#endif diff --git a/src/event/Synthesize.cpp b/src/event/Synthesize.cpp new file mode 100644 index 0000000..1ee8b72 --- /dev/null +++ b/src/event/Synthesize.cpp @@ -0,0 +1,81 @@ +#include "event/Synthesize.hpp" +#include "event/EvtContext.hpp" +#include "event/Queue.hpp" +#include + +void SynthesizeDestroy(EvtContext* context) { + // TODO + exit(0); +} + +void SynthesizeIdle(EvtContext* context, uint32_t currTime, float elapsedSec) { + bool closed; + + context->m_critsect.Enter(); + closed = context->m_schedState == EvtContext::SCHEDSTATE_CLOSED; + context->m_critsect.Leave(); + + if (closed) { + return; + } + + uint32_t schedFlags = context->m_schedFlags; + + if (schedFlags & 0x2) { + context->m_schedFlags = schedFlags | 0x4; + } + + EVENT_DATA_IDLE data; + data.elapsedSec = elapsedSec; + data.time = currTime; + + IEvtQueueDispatch(context, EVENT_ID_IDLE, &data); +} + +int32_t SynthesizeInitialize(EvtContext* context) { + uint32_t schedFlags = context->m_schedFlags; + + if (schedFlags & 0x1) { + return 0; + } + + context->m_schedFlags = schedFlags | 0x1; + context->m_schedLastIdle = OsGetAsyncTimeMs(); + + IEvtQueueDispatch(context, EVENT_ID_INITIALIZE, nullptr); + + return 1; +} + +void SynthesizePaint(EvtContext* context) { + bool closed; + + context->m_critsect.Enter(); + closed = context->m_schedState == EvtContext::SCHEDSTATE_CLOSED; + context->m_critsect.Leave(); + + if (closed) { + return; + } + + uint32_t schedFlags = context->m_schedFlags; + + if (schedFlags & 0x4) { + context->m_schedFlags = schedFlags & ~0x4; + IEvtQueueDispatch(context, EVENT_ID_PAINT, nullptr); + } +} + +void SynthesizePoll(EvtContext* context) { + bool closed; + + context->m_critsect.Enter(); + closed = context->m_schedState == EvtContext::SCHEDSTATE_CLOSED; + context->m_critsect.Leave(); + + if (closed) { + return; + } + + IEvtQueueDispatch(context, EVENT_ID_POLL, nullptr); +} diff --git a/src/event/Synthesize.hpp b/src/event/Synthesize.hpp new file mode 100644 index 0000000..3b6f1ba --- /dev/null +++ b/src/event/Synthesize.hpp @@ -0,0 +1,18 @@ +#ifndef EVENT_SYNTHESIZE_HPP +#define EVENT_SYNTHESIZE_HPP + +#include + +class EvtContext; + +void SynthesizeDestroy(EvtContext* context); + +void SynthesizeIdle(EvtContext* context, uint32_t currTime, float elapsedSec); + +int32_t SynthesizeInitialize(EvtContext* context); + +void SynthesizePaint(EvtContext* context); + +void SynthesizePoll(EvtContext* context); + +#endif diff --git a/src/event/Timer.cpp b/src/event/Timer.cpp new file mode 100644 index 0000000..8861686 --- /dev/null +++ b/src/event/Timer.cpp @@ -0,0 +1,29 @@ +#include "event/Timer.hpp" +#include "event/EvtContext.hpp" +#include "event/EvtTimer.hpp" +#include + +int32_t IEvtTimerDispatch(EvtContext* context) { + // TODO + + return 0; +} + +uint32_t IEvtTimerGetNextTime(EvtContext* context, uint32_t currTime) { + STORM_ASSERT(context); + + context->m_critsect.Enter(); + + uint32_t nextTime = -1; + + if (context->m_timerQueue.Count()) { + auto queue = static_cast(context->m_timerQueue[0]); + auto targetTime = queue->targetTime.m_val; + nextTime = targetTime - currTime; + nextTime = nextTime < 0 ? 0 : nextTime; + } + + context->m_critsect.Leave(); + + return nextTime; +} diff --git a/src/event/Timer.hpp b/src/event/Timer.hpp new file mode 100644 index 0000000..f75cc1f --- /dev/null +++ b/src/event/Timer.hpp @@ -0,0 +1,12 @@ +#ifndef EVENT_TIMER_HPP +#define EVENT_TIMER_HPP + +#include + +class EvtContext; + +int32_t IEvtTimerDispatch(EvtContext* context); + +uint32_t IEvtTimerGetNextTime(EvtContext* context, uint32_t currTime); + +#endif diff --git a/src/event/Types.hpp b/src/event/Types.hpp new file mode 100644 index 0000000..e78054d --- /dev/null +++ b/src/event/Types.hpp @@ -0,0 +1,252 @@ +#ifndef EVENT_TYPES_HPP +#define EVENT_TYPES_HPP + +#include + +typedef void* HEVENTCONTEXT; +typedef int32_t (*EVENTHANDLERFUNC)(const void*, void*); + +enum EVENTID { + EVENT_ID_0 = 0, + EVENT_ID_CHAR = 1, + EVENT_ID_FOCUS = 2, + EVENT_ID_3 = 3, + EVENT_ID_DESTROY = 4, + EVENT_ID_5 = 5, + EVENT_ID_IDLE = 6, + EVENT_ID_POLL = 7, + EVENT_ID_INITIALIZE = 8, + EVENT_ID_KEYDOWN = 9, + EVENT_ID_KEYUP = 10, + EVENT_ID_KEYDOWN_REPEATING = 11, + EVENT_ID_MOUSEDOWN = 12, + EVENT_ID_MOUSEMOVE = 13, + EVENT_ID_MOUSEMOVE_RELATIVE = 14, + EVENT_ID_MOUSEUP = 15, + EVENT_ID_MOUSEMODE_CHANGED = 16, + EVENT_ID_MOUSEWHEEL = 17, + EVENT_ID_18 = 18, + EVENT_ID_19 = 19, + EVENT_ID_20 = 20, + EVENT_ID_21 = 21, + EVENT_ID_22 = 22, + EVENT_ID_PAINT = 23, + EVENT_ID_24 = 24, + EVENT_ID_25 = 25, + EVENT_ID_26 = 26, + EVENT_ID_27 = 27, + EVENT_ID_28 = 28, + EVENT_ID_29 = 29, + EVENT_ID_30 = 30, + EVENT_ID_31 = 31, + EVENT_ID_32 = 32, + EVENT_ID_33 = 33, + EVENT_ID_IME = 34, + EVENT_ID_SIZE = 35, + EVENTIDS = 36 +}; + +enum KEY { + KEY_NONE = 0xFFFFFFFF, + KEY_LSHIFT = 0x0, + KEY_RSHIFT = 0x1, + KEY_LCONTROL = 0x2, + KEY_RCONTROL = 0x3, + KEY_LALT = 0x4, + KEY_RALT = 0x5, + KEY_LASTMETAKEY = 0x5, + KEY_SPACE = 0x20, + KEY_0 = 0x30, + KEY_1 = 0x31, + KEY_2 = 0x32, + KEY_3 = 0x33, + KEY_4 = 0x34, + KEY_5 = 0x35, + KEY_6 = 0x36, + KEY_7 = 0x37, + KEY_8 = 0x38, + KEY_9 = 0x39, + KEY_A = 0x41, + KEY_B = 0x42, + KEY_C = 0x43, + KEY_D = 0x44, + KEY_E = 0x45, + KEY_F = 0x46, + KEY_G = 0x47, + KEY_H = 0x48, + KEY_I = 0x49, + KEY_J = 0x4A, + KEY_K = 0x4B, + KEY_L = 0x4C, + KEY_M = 0x4D, + KEY_N = 0x4E, + KEY_O = 0x4F, + KEY_P = 0x50, + KEY_Q = 0x51, + KEY_R = 0x52, + KEY_S = 0x53, + KEY_T = 0x54, + KEY_U = 0x55, + KEY_V = 0x56, + KEY_W = 0x57, + KEY_X = 0x58, + KEY_Y = 0x59, + KEY_Z = 0x5A, + KEY_TILDE = 0x60, + KEY_NUMPAD0 = 0x100, + KEY_NUMPAD1 = 0x101, + KEY_NUMPAD2 = 0x102, + KEY_NUMPAD3 = 0x103, + KEY_NUMPAD4 = 0x104, + KEY_NUMPAD5 = 0x105, + KEY_NUMPAD6 = 0x106, + KEY_NUMPAD7 = 0x107, + KEY_NUMPAD8 = 0x108, + KEY_NUMPAD9 = 0x109, + KEY_NUMPAD_PLUS = 0x10A, + KEY_NUMPAD_MINUS = 0x10B, + KEY_NUMPAD_MULTIPLY = 0x10C, + KEY_NUMPAD_DIVIDE = 0x10D, + KEY_NUMPAD_DECIMAL = 0x10E, + KEY_NUMPAD_EQUALS = 0x30C, + KEY_PLUS = 0x3D, + KEY_MINUS = 0x2D, + KEY_BRACKET_OPEN = 0x5B, + KEY_BRACKET_CLOSE = 0x5D, + KEY_SLASH = 0x2F, + KEY_BACKSLASH = 0x5C, + KEY_SEMICOLON = 0x3B, + KEY_APOSTROPHE = 0x27, + KEY_COMMA = 0x2C, + KEY_PERIOD = 0x2E, + KEY_ESCAPE = 0x200, + KEY_ENTER = 0x201, + KEY_BACKSPACE = 0x202, + KEY_TAB = 0x203, + KEY_LEFT = 0x204, + KEY_UP = 0x205, + KEY_RIGHT = 0x206, + KEY_DOWN = 0x207, + KEY_INSERT = 0x208, + KEY_DELETE = 0x209, + KEY_HOME = 0x20A, + KEY_END = 0x20B, + KEY_PAGEUP = 0x20C, + KEY_PAGEDOWN = 0x20D, + KEY_CAPSLOCK = 0x20E, + KEY_NUMLOCK = 0x20F, + KEY_SCROLLLOCK = 0x210, + KEY_PAUSE = 0x211, + KEY_PRINTSCREEN = 0x212, + KEY_F1 = 0x300, + KEY_F2 = 0x301, + KEY_F3 = 0x302, + KEY_F4 = 0x303, + KEY_F5 = 0x304, + KEY_F6 = 0x305, + KEY_F7 = 0x306, + KEY_F8 = 0x307, + KEY_F9 = 0x308, + KEY_F10 = 0x309, + KEY_F11 = 0x30A, + KEY_F12 = 0x30B, + KEY_F13 = 0x212, + KEY_F14 = 0x30D, + KEY_F15 = 0x30E, + KEY_F16 = 0x30F, + KEY_F17 = 0x310, + KEY_F18 = 0x311, + KEY_F19 = 0x312, + KEY_LAST = 0x313 +}; + +enum MOUSEBUTTON { + MOUSE_BUTTON_NONE = 0x0, + MOUSE_BUTTON_LEFT = 0x1, + MOUSE_BUTTON_MIDDLE = 0x2, + MOUSE_BUTTON_RIGHT = 0x4, + MOUSE_BUTTON_XBUTTON1 = 0x8, + MOUSE_BUTTON_XBUTTON2 = 0x10, + MOUSE_BUTTON_XBUTTON3 = 0x20, + MOUSE_BUTTON_XBUTTON4 = 0x40, + MOUSE_BUTTON_XBUTTON5 = 0x80, + MOUSE_BUTTON_XBUTTON6 = 0x100, + MOUSE_BUTTON_XBUTTON7 = 0x200, + MOUSE_BUTTON_XBUTTON8 = 0x400, + MOUSE_BUTTON_XBUTTON9 = 0x800, + MOUSE_BUTTON_XBUTTON10 = 0x1000, + MOUSE_BUTTON_XBUTTON11 = 0x2000, + MOUSE_BUTTON_XBUTTON12 = 0x4000, + MOUSE_BUTTON_ALL = 0xFFFFFFFF +}; + +enum MOUSEMODE { + MOUSE_MODE_NORMAL = 0x0, + MOUSE_MODE_RELATIVE = 0x1, + MOUSE_MODES = 0x2 +}; + +enum OSINPUT { + OS_INPUT_CAPTURE_CHANGED = 0, + OS_INPUT_CHAR = 1, + OS_INPUT_STRING = 2, + OS_INPUT_IME = 3, + OS_INPUT_SIZE = 4, + OS_INPUT_CLOSE = 5, + OS_INPUT_FOCUS = 6, + OS_INPUT_KEY_DOWN = 7, + OS_INPUT_KEY_UP = 8, + OS_INPUT_MOUSE_DOWN = 9, + OS_INPUT_MOUSE_MOVE = 10, + OS_INPUT_MOUSE_WHEEL = 11, + OS_INPUT_MOUSE_MOVE_RELATIVE = 12, + OS_INPUT_MOUSE_UP = 13, + OS_INPUT_14 = 14, + OS_INPUT_15 = 15, + OS_INPUT_16 = 16, + OS_INPUT_17 = 17, + OS_INPUT_18 = 18, + OS_INPUT_SHUTDOWN = 19 +}; + +struct OSEVENT { + OSINPUT id; + int32_t param[4]; +}; + +struct EVENT_DATA_CHAR { + int32_t ch; + uint32_t metaKeyState; + uint32_t repeat; +}; + +struct EVENT_DATA_IDLE { + float elapsedSec; + uint32_t time; +}; + +struct EVENT_DATA_KEY { + KEY key; + uint32_t metaKeyState; + uint32_t repeat; + uint32_t time; +}; + +struct EVENT_DATA_MOUSE { + MOUSEMODE mode; + MOUSEBUTTON button; + uint32_t buttonState; + uint32_t metaKeyState; + uint32_t flags; + float x; + float y; + int32_t wheelDistance; + uint32_t time; +}; + +struct EVENT_DATA_SIZE { + int32_t w; + int32_t h; +}; + +#endif diff --git a/src/event/mac/Event.h b/src/event/mac/Event.h new file mode 100644 index 0000000..0205cb5 --- /dev/null +++ b/src/event/mac/Event.h @@ -0,0 +1,6 @@ +#ifndef EVENT_MAC_EVENT_H +#define EVENT_MAC_EVENT_H + +void RunCocoaEventLoop(); + +#endif diff --git a/src/event/mac/Event.mm b/src/event/mac/Event.mm new file mode 100644 index 0000000..fa0455f --- /dev/null +++ b/src/event/mac/Event.mm @@ -0,0 +1,9 @@ +#include "event/mac/Event.h" +#include "event/Event.hpp" +#include + +void RunCocoaEventLoop() { + if (!Event::s_shouldLoopTerminate) { + [NSApp run]; + } +} diff --git a/src/glue/CCharacterSelection.cpp b/src/glue/CCharacterSelection.cpp new file mode 100644 index 0000000..d32b260 --- /dev/null +++ b/src/glue/CCharacterSelection.cpp @@ -0,0 +1,3 @@ +#include "glue/CCharacterSelection.hpp" + +TSGrowableArray CCharacterSelection::s_characterList; diff --git a/src/glue/CCharacterSelection.hpp b/src/glue/CCharacterSelection.hpp new file mode 100644 index 0000000..f4e766e --- /dev/null +++ b/src/glue/CCharacterSelection.hpp @@ -0,0 +1,16 @@ +#ifndef GLUE_C_CHARACTER_SELECTION_HPP +#define GLUE_C_CHARACTER_SELECTION_HPP + +#include + +struct CharacterSelectionDisplay { + // TODO +}; + +class CCharacterSelection { + public: + // Static variables + static TSGrowableArray s_characterList; +}; + +#endif diff --git a/src/glue/CGlueMgr.cpp b/src/glue/CGlueMgr.cpp new file mode 100644 index 0000000..bc415a7 --- /dev/null +++ b/src/glue/CGlueMgr.cpp @@ -0,0 +1,452 @@ +#include "glue/CGlueMgr.hpp" +#include "client/Client.hpp" +#include "client/ClientServices.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Device.hpp" +#include "math/Utils.hpp" +#include "net/Connection.hpp" +#include "net/Login.hpp" +#include "sound/SI2.hpp" +#include "ui/CSimpleModelFFX.hpp" +#include "ui/CSimpleTop.hpp" +#include "ui/FrameScript.hpp" +#include "ui/FrameXML.hpp" +#include "ui/Interface.hpp" +#include "ui/ScriptFunctions.hpp" +#include "util/CVar.hpp" +#include "util/Filesystem.hpp" +#include "util/Log.hpp" +#include +#include + +unsigned char InterfaceKey[256] = { + 0xC3, 0x5B, 0x50, 0x84, 0xB9, 0x3E, 0x32, 0x42, 0x8C, 0xD0, 0xC7, 0x48, 0xFA, 0x0E, 0x5D, 0x54, + 0x5A, 0xA3, 0x0E, 0x14, 0xBA, 0x9E, 0x0D, 0xB9, 0x5D, 0x8B, 0xEE, 0xB6, 0x84, 0x93, 0x45, 0x75, + 0xFF, 0x31, 0xFE, 0x2F, 0x64, 0x3F, 0x3D, 0x6D, 0x07, 0xD9, 0x44, 0x9B, 0x40, 0x85, 0x59, 0x34, + 0x4E, 0x10, 0xE1, 0xE7, 0x43, 0x69, 0xEF, 0x7C, 0x16, 0xFC, 0xB4, 0xED, 0x1B, 0x95, 0x28, 0xA8, + 0x23, 0x76, 0x51, 0x31, 0x57, 0x30, 0x2B, 0x79, 0x08, 0x50, 0x10, 0x1C, 0x4A, 0x1A, 0x2C, 0xC8, + 0x8B, 0x8F, 0x05, 0x2D, 0x22, 0x3D, 0xDB, 0x5A, 0x24, 0x7A, 0x0F, 0x13, 0x50, 0x37, 0x8F, 0x5A, + 0xCC, 0x9E, 0x04, 0x44, 0x0E, 0x87, 0x01, 0xD4, 0xA3, 0x15, 0x94, 0x16, 0x34, 0xC6, 0xC2, 0xC3, + 0xFB, 0x49, 0xFE, 0xE1, 0xF9, 0xDA, 0x8C, 0x50, 0x3C, 0xBE, 0x2C, 0xBB, 0x57, 0xED, 0x46, 0xB9, + 0xAD, 0x8B, 0xC6, 0xDF, 0x0E, 0xD6, 0x0F, 0xBE, 0x80, 0xB3, 0x8B, 0x1E, 0x77, 0xCF, 0xAD, 0x22, + 0xCF, 0xB7, 0x4B, 0xCF, 0xFB, 0xF0, 0x6B, 0x11, 0x45, 0x2D, 0x7A, 0x81, 0x18, 0xF2, 0x92, 0x7E, + 0x98, 0x56, 0x5D, 0x5E, 0x69, 0x72, 0x0A, 0x0D, 0x03, 0x0A, 0x85, 0xA2, 0x85, 0x9C, 0xCB, 0xFB, + 0x56, 0x6E, 0x8F, 0x44, 0xBB, 0x8F, 0x02, 0x22, 0x68, 0x63, 0x97, 0xBC, 0x85, 0xBA, 0xA8, 0xF7, + 0xB5, 0x40, 0x68, 0x3C, 0x77, 0x86, 0x6F, 0x4B, 0xD7, 0x88, 0xCA, 0x8A, 0xD7, 0xCE, 0x36, 0xF0, + 0x45, 0x6E, 0xD5, 0x64, 0x79, 0x0F, 0x17, 0xFC, 0x64, 0xDD, 0x10, 0x6F, 0xF3, 0xF5, 0xE0, 0xA6, + 0xC3, 0xFB, 0x1B, 0x8C, 0x29, 0xEF, 0x8E, 0xE5, 0x34, 0xCB, 0xD1, 0x2A, 0xCE, 0x79, 0xC3, 0x9A, + 0x0D, 0x36, 0xEA, 0x01, 0xE0, 0xAA, 0x91, 0x20, 0x54, 0xF0, 0x72, 0xD8, 0x1E, 0xC7, 0x89, 0xD2 +}; + +int32_t CGlueMgr::m_acceptedEULA = 1; // TODO +int32_t CGlueMgr::m_acceptedTerminationWithoutNotice; +int32_t CGlueMgr::m_acceptedTOS = 1; // TODO +char CGlueMgr::m_accountName[1280]; +float CGlueMgr::m_aspect; +bool CGlueMgr::m_authenticated; +char CGlueMgr::m_currentScreen[64]; +CGlueMgr::GLUE_IDLE_STATE CGlueMgr::m_idleState; +int32_t CGlueMgr::m_initialized; +int32_t CGlueMgr::m_lastLoginResult; +int32_t CGlueMgr::m_lastLoginState; +int32_t CGlueMgr::m_loginResult; +int32_t CGlueMgr::m_loginState; +int32_t CGlueMgr::m_matrixRemaining; +int32_t CGlueMgr::m_reload; +int32_t CGlueMgr::m_scandllOkayToLogIn = 1; // TODO +float CGlueMgr::m_screenHeight; +float CGlueMgr::m_screenWidth; +int32_t CGlueMgr::m_showedDisconnect; +CSimpleTop* CGlueMgr::m_simpleTop; +int32_t CGlueMgr::m_suspended; + +float CalculateAspectRatio() { + auto widescreenVar = CVar::Lookup("widescreen"); + auto resolutionVar = CVar::Lookup("gxResolution"); + + int32_t width = 4; + int32_t height = 3; + + if (widescreenVar && widescreenVar->GetInt() && resolutionVar) { + char separator; + sscanf(resolutionVar->GetString(), "%d%c%d", &width, &separator, &height); + } + + return static_cast(width) / static_cast(height); +} + +int32_t CGlueMgr::HandleDisplaySizeChanged(const CSizeEvent& event) { + if ( + CGlueMgr::m_screenWidth > 0 + && CGlueMgr::m_screenWidth == event.w + && CGlueMgr::m_screenHeight > 0 + && CGlueMgr::m_screenHeight == event.h + ) { + return 1; + } + + CGlueMgr::m_screenWidth = event.w; + CGlueMgr::m_screenHeight = event.h; + float aspect = CalculateAspectRatio(); + + auto glueParent = static_cast(CScriptObject::GetScriptObjectByName("GlueParent", CSimpleFrame::GetObjectType())); + if (glueParent) { + glueParent->SetFrameScale(1.0f, true); + CLayoutFrame::ResizePending(); + } + + if (AreEqual(CGlueMgr::m_aspect, aspect, 0.0099999998)) { + return 1; + } + + CGlueMgr::m_aspect = aspect; + CGlueMgr::m_reload = 1; + + return 1; +} + +// TODO a1: const EVENT_DATA_IDLE* +int32_t CGlueMgr::Idle(const void* a1, void* a2) { + // TODO + + if (CGlueMgr::m_idleState == IDLE_NONE) { + if (CGlueMgr::m_reload) { + if (!CGlueMgr::m_suspended) { + // TODO CGlueMgr::Suspend(); + // TODO CGlueMgr::Resume(); + // TODO Sub4DA360(); + // TODO CGlueMgr::SetScreen(ByteB6A9E0); + } + + CGlueMgr::m_reload = 0; + } + + // TODO + // if (CGlueMgr::m_accountMsgAvailable) { + // FrameScript_SignalEvent(0x22u, 0); + // CGlueMgr::m_accountMsgAvailable = 0; + // } + + // TODO sub_4D84A0(); + + return 1; + } + + // TODO + + return 1; +} + +void CGlueMgr::Initialize() { + CGlueMgr::m_initialized = 1; + + // TODO + // - cvar stuff (tou, etc) + + CRect windowSize; + g_theGxDevicePtr->CapsWindowSize(windowSize); + + CSizeEvent sizeEvent; + sizeEvent.id = -1; + sizeEvent.w = windowSize.maxX - windowSize.minX; + sizeEvent.h = windowSize.maxY - windowSize.minY; + + CGlueMgr::HandleDisplaySizeChanged(sizeEvent); + + FrameXML_RegisterFactory("ModelFFX", &CSimpleModelFFX::Create, 0); + + CGlueMgr::Resume(); + + // TODO + // CRealmList::Initialize(); + + EventRegisterEx(EVENT_ID_IDLE, &CGlueMgr::Idle, 0, 0.0); + + // TODO + // CGLCD::Initialize(); + // sub_552380(); + + // TODO + // - options changed warning stuff + + // TODO + // AccountDataInitializeBasicSystem(); +} + +void CGlueMgr::LoginServerLogin(const char* accountName, const char* password) { + if (!CGlueMgr::m_scandllOkayToLogIn || !CGlueMgr::m_acceptedTOS || !CGlueMgr::m_acceptedEULA || CGlueMgr::m_idleState != IDLE_NONE) { + return; + } + + if (!accountName || !*accountName) { + auto prompt = FrameScript_GetText("LOGIN_ENTER_NAME", -1, GENDER_NOT_APPLICABLE); + FrameScript_SignalEvent(3, "%s%s", "OKAY", prompt); + + return; + } + + if (!password || !*password) { + auto prompt = FrameScript_GetText("LOGIN_ENTER_PASSWORD", -1, GENDER_NOT_APPLICABLE); + FrameScript_SignalEvent(3, "%s%s", "OKAY", prompt); + + return; + } + + CGlueMgr::m_loginResult = -1; + CGlueMgr::m_loginState = -1; + CGlueMgr::m_lastLoginResult = -1; + CGlueMgr::m_lastLoginState = -1; + CGlueMgr::m_authenticated = false; + CGlueMgr::m_matrixRemaining = 0; + CGlueMgr::m_idleState = IDLE_ACCOUNT_LOGIN; + CGlueMgr::m_showedDisconnect = 0; + + char* dest = CGlueMgr::m_accountName; + for (const char* src = accountName; *src && dest < CGlueMgr::m_accountName + sizeof(CGlueMgr::m_accountName); src++, dest++) { + if (src[0] == '|' && src[1] == '|') { + src++; + } + + *dest = *src; + } + *dest = '\0'; + + SStrUpper(CGlueMgr::m_accountName); + + // Show prompt + FrameScript_SignalEvent(3, "%s", "CANCEL"); + + ClientServices::Logon(CGlueMgr::m_accountName, password); + + // Zero out password + memset(const_cast(password), 0, SStrLen(password)); +} + +void CGlueMgr::QuitGame() { + ClientPostClose(0); +} + +void CGlueMgr::Resume() { + // TODO + // CGlueMgr::m_disconnectPending = 0; + // CGlueMgr::m_reconnect = 0; + + CGlueMgr::m_idleState = IDLE_NONE; + + // TODO + // CGlueMgr::m_showedDisconnect = 0; + // CGlueMgr::m_characterInfo = 0; + + CGlueMgr::m_suspended = 0; + CGlueMgr::m_reload = 0; + + CRect screenRect; + g_theGxDevicePtr->CapsWindowSizeInScreenCoords(screenRect); + CGlueMgr::m_screenWidth = screenRect.maxX - screenRect.minX; + CGlueMgr::m_screenHeight = screenRect.maxY - screenRect.minY; + CGlueMgr::m_aspect = CalculateAspectRatio(); + CoordinateSetAspectRatio(CGlueMgr::m_aspect); + + // Create CSimpleTop + CSimpleTop* top; + + void* m = SMemAlloc(sizeof(CSimpleTop), __FILE__, __LINE__, 0); + + if (m) { + top = new (m) CSimpleTop(); + } else { + top = nullptr; + } + + CGlueMgr::m_simpleTop = top; + CGlueMgr::m_simpleTop->m_displaySizeCallback = &CGlueMgr::HandleDisplaySizeChanged; + + // TODO + // - setting cursor texture + // - setting mouse mode + + FrameScript_Flush(); + + SystemRegisterFunctions(); + RegisterSimpleFrameScriptMethods(); + GlueScriptEventsRegisterFunctions(); + CharSelectRegisterScriptFunctions(); + CharacterCreateRegisterScriptFunctions(); + + // TODO + // RealmListRegisterScriptFunctions(); + + SI2::RegisterScriptFunctions(); + + // TODO + // AccountMsg_RegisterScriptFunctions(); + // CGVideoOptions::RegisterScriptFunctions(); + + // TODO + // FrameScript::s_scriptFunctionsLoaded = 1; + + FrameScript_CreateEvents(g_glueScriptEvents, NUM_GLUE_SCRIPT_EVENTS); + + OsCreateDirectory("Logs", 0); + + CWOWClientStatus status; + + if (!SLogCreate("Logs\\GlueXML.log", 0, status.m_logFile)) { + SysMsgPrintf(SYSMSG_WARNING, "Cannot create WOWClient log file \"%s\"!", "Logs\\GlueXML.log"); + } + + DeleteInterfaceFiles(); + + MD5_CTX md5; + unsigned char digest1[16]; + unsigned char digest2[16]; + + int32_t v8; + unsigned char* v9; + unsigned char* v10; + + MD5Init(&md5); + + switch (FrameXML_CheckSignature("Interface\\GlueXML\\GlueXML.toc", 0, InterfaceKey, digest1)) { + case 0: + status.Add(STATUS_WARNING, "GlueXML missing signature"); + ClientPostClose(9); + return; + + case 1: + status.Add(STATUS_WARNING, "GlueXML has corrupt signature"); + ClientPostClose(9); + return; + + case 2: + status.Add(STATUS_WARNING, "GlueXML is modified or corrupt"); + ClientPostClose(9); + return; + + case 3: + FrameXML_FreeHashNodes(); + FrameXML_CreateFrames("Interface\\GlueXML\\GlueXML.toc", 0, &md5, &status); + + MD5Final(digest2, &md5); + + v8 = 16; + v9 = digest2; + v10 = digest1; + + break; + + default: + ClientPostClose(9); + return; + } + + // TODO + // - some kind of digest validation? + + FrameScript_SignalEvent(22, nullptr); + + // TODO + // CGlueMgr::InitializeFFX(); + + // TODO + // ClientServices::SetMessageHandler(SMSG_CHARACTER_RENAME_RESULT, CGlueMgr::OnCharRenameResult, 0); + // ClientServices::SetMessageHandler(SMSG_SET_PLAYER_DECLINED_NAMES_RESULT, CGlueMgr::OnCharDeclineResult, 0); + // ClientServices::SetMessageHandler(SMSG_CHAR_CUSTOMIZE, CGlueMgr::OnCharCustomizeResult, 0); + // ClientServices::SetMessageHandler(SMSG_REALM_SPLIT, CGlueMgr::OnRealmSplitMsg, 0); + // ClientServices::SetMessageHandler(SMSG_KICK_REASON, CGlueMgr::OnKickReasonMsg, 0); + // ClientServices::SetMessageHandler(SMSG_CHAR_FACTION_CHANGE, CGlueMgr::OnCharFactionChangeResult, 0); + + // TODO + // CGlueMgr::m_pendingServerAlert = 1; + // CGlueMgr::m_processServerAlert = 0; + // CGlueMgr::m_serverAlert[0] = 0; + // v21 = (const char *)ClientServices::GetServerAlertURL(); + // v22 = (const CHAR *)FrameScript_GetText(v21, -1, 0); + // if (!OsURLDownload(v22, CGlueMgr::ServerAlertURLCallback, 0)) { + // CGlueMgr::m_pendingServerAlert = 0; + // } +} + +void CGlueMgr::SetScreen(const char* screen) { + FrameScript_SignalEvent(0, "%s", screen); +} + +void CGlueMgr::StatusDialogClick() { + if (!SStrCmpI(CGlueMgr::m_currentScreen, "patchdownload", STORM_MAX_STR)) { + CGlueMgr::SetScreen("login"); + } + + switch (CGlueMgr::m_idleState) { + case IDLE_NONE: { + ClientServices::Connection()->Cleanup(); + + break; + } + + case IDLE_ACCOUNT_LOGIN: { + ClientServices::LoginConnection()->Logoff(); + + CGlueMgr::m_showedDisconnect = 0; + CGlueMgr::m_idleState = IDLE_NONE; + + break; + } + + case IDLE_2: + case IDLE_3: { + ClientServices::Connection()->Cancel(2); + + break; + } + + case IDLE_4: + case IDLE_5: + case IDLE_6: + case IDLE_10: { + ClientServices::Connection()->Cancel(2); + + CGlueMgr::m_showedDisconnect = 0; + CGlueMgr::m_idleState = IDLE_NONE; + + break; + } + + case IDLE_7: + case IDLE_8: + case IDLE_9: { + CGlueMgr::m_showedDisconnect = 0; + CGlueMgr::m_idleState = IDLE_NONE; + + break; + } + + case IDLE_11: { + CGlueMgr::m_showedDisconnect = 0; + CGlueMgr::m_idleState = IDLE_NONE; + + // TODO + // CGlueMgr::GetCharacterList(); + + break; + } + + case IDLE_12: + case IDLE_13: { + // TODO + + break; + } + } +} + +void CGlueMgr::Suspend() { + // TODO +} + +void CGlueMgr::UpdateCurrentScreen(const char* screen) { + // TODO + + SStrCopy(CGlueMgr::m_currentScreen, screen, sizeof(CGlueMgr::m_currentScreen)); + + // TODO +} diff --git a/src/glue/CGlueMgr.hpp b/src/glue/CGlueMgr.hpp new file mode 100644 index 0000000..c1a2395 --- /dev/null +++ b/src/glue/CGlueMgr.hpp @@ -0,0 +1,66 @@ +#ifndef GLUE_C_GLUE_MGR_HPP +#define GLUE_C_GLUE_MGR_HPP + +#include "event/Event.hpp" +#include + +class CSimpleTop; + +class CGlueMgr { + public: + // Types + enum GLUE_IDLE_STATE { + IDLE_NONE = 0, + IDLE_ACCOUNT_LOGIN = 1, + IDLE_2 = 2, + IDLE_3 = 3, + IDLE_4 = 4, + IDLE_5 = 5, + IDLE_6 = 6, + IDLE_7 = 7, + IDLE_8 = 8, + IDLE_9 = 9, + IDLE_10 = 10, + IDLE_11 = 11, + IDLE_12 = 12, + IDLE_13 = 13 + }; + + // Static variables + static int32_t m_acceptedEULA; + static int32_t m_acceptedTerminationWithoutNotice; + static int32_t m_acceptedTOS; + static char m_accountName[]; + static float m_aspect; + static bool m_authenticated; + static char m_currentScreen[]; + static GLUE_IDLE_STATE m_idleState; + static int32_t m_initialized; + static int32_t m_lastLoginResult; + static int32_t m_lastLoginState; + static int32_t m_loginResult; + static int32_t m_loginState; + static int32_t m_matrixRemaining; + static int32_t m_reload; + static int32_t m_scandllOkayToLogIn; + static float m_screenHeight; + static float m_screenWidth; + static int32_t m_showedDisconnect; + static CSimpleTop* m_simpleTop; + static int32_t m_suspended; + + // Static functions + // TODO a1: const EVENT_DATA_IDLE* + static int32_t HandleDisplaySizeChanged(const CSizeEvent& event); + static int32_t Idle(const void*, void*); + static void Initialize(); + static void LoginServerLogin(const char* accountName, const char* password); + static void QuitGame(); + static void Resume(); + static void SetScreen(const char*); + static void StatusDialogClick(); + static void Suspend(); + static void UpdateCurrentScreen(const char* screen); +}; + +#endif diff --git a/src/glue/CMakeLists.txt b/src/glue/CMakeLists.txt new file mode 100644 index 0000000..c4f5115 --- /dev/null +++ b/src/glue/CMakeLists.txt @@ -0,0 +1,23 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(glue STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(glue + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(glue + PRIVATE + client + event + gx + net + sound + ui + util + PUBLIC + storm +) diff --git a/src/gx/Blit.cpp b/src/gx/Blit.cpp new file mode 100644 index 0000000..51aa483 --- /dev/null +++ b/src/gx/Blit.cpp @@ -0,0 +1,236 @@ +#include "gx/Blit.hpp" +#include +#include +#include + +int32_t initBlit = 0; +BLIT_FUNCTION s_blits[BlitFormats_Last][BlitFormats_Last][BlitAlphas_Last]; + +BlitFormat GxGetBlitFormat(EGxTexFormat format) { + static BlitFormat blitTable[] = { + BlitFormat_Unknown, // GxTex_Unknown + BlitFormat_Abgr8888, // GxTex_Abgr8888 + BlitFormat_Argb8888, // GxTex_Argb8888 + BlitFormat_Argb4444, // GxTex_Argb4444 + BlitFormat_Argb1555, // GxTex_Argb1555 + BlitFormat_Rgb565, // GxTex_Rgb565 + BlitFormat_Dxt1, // GxTex_Dxt1 + BlitFormat_Dxt3, // GxTex_Dxt3 + BlitFormat_Dxt5, // GxTex_Dxt5 + BlitFormat_Uv88, // GxTex_Uv88 + BlitFormat_Gr1616F, // GxTex_Gr1616F + BlitFormat_R32F, // GxTex_R32F + BlitFormat_D24X8 // GxTex_D24X8 + }; + + return blitTable[format]; +} + +void Blit_uint16_uint16(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + if (inStride == 2 * size.x && outStride == 2 * size.x) { + memcpy(out, in, 2 * size.x * size.y); + return; + } + + const char* in_ = reinterpret_cast(in); + char* out_ = reinterpret_cast(out); + + for (int32_t i = 0; i < size.y; i++) { + memcpy(out, in, 2 * size.x); + in_ += inStride; + out_ += outStride; + } +} + +void Blit_uint32_uint32(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + if (inStride == 4 * size.x && outStride == 4 * size.x) { + memcpy(out, in, 4 * size.x * size.y); + return; + } + + const char* in_ = reinterpret_cast(in); + char* out_ = reinterpret_cast(out); + + for (int32_t i = 0; i < size.y; i++) { + memcpy(out, in, 4 * size.x); + in_ += inStride; + out_ += outStride; + } +} + +void Blit_Argb8888_Abgr8888(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Argb8888_Argb8888(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + Blit_uint32_uint32(size, in, inStride, out, outStride); +} + +void Blit_Argb8888_Argb8888_A1(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Argb8888_Argb8888_A8(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Argb8888_Argb4444(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Argb8888_Argb1555(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Argb8888_Rgb565(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Argb4444_Abgr8888(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Argb4444_Argb4444(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + Blit_uint16_uint16(size, in, inStride, out, outStride); +} + +void Blit_Argb1555_Argb1555(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Rgb565_Rgb565(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Dxt1_Argb8888(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Dxt1_Argb1555(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Dxt1_Rgb565(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Dxt1_Dxt1(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + const char* in_ = static_cast(in); + char* out_ = static_cast(out); + + int32_t v6 = std::max(size.x, 4); + int32_t v7 = std::max(size.y, 4); + + memcpy(out_, in_, (4 * v6 * v7) >> 3); +} + +void Blit_Dxt3_Argb8888(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Dxt3_Argb4444(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Dxt35_Dxt35(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + int32_t v5 = std::max(size.x, 4); + int32_t v6 = std::max(size.y / 4, 1); + + const char* in_ = static_cast(in); + char* out_ = static_cast(out); + + if (inStride == v5 * 4 && outStride == v5 * 4) { + memcpy(out_, in_, v5 * v6 * 4); + return; + } + + for (int32_t i = v6; i > 0; i--) { + memcpy(out_, in_, v5 * 4); + in_ += inStride; + out_ += outStride; + } +} + +void Blit_Dxt5_Argb8888(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Dxt5_Argb4444(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Uv88_Uv88(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_Gr1616F_Gr1616F(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_R32F_R32F(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void Blit_D24X8_D24X8(const C2iVector& size, const void* in, uint32_t inStride, void* out, uint32_t outStride) { + // TODO +} + +void InitBlit() { + // Source: Argb8888 + s_blits [BlitFormat_Argb8888] [BlitFormat_Abgr8888] [BlitAlpha_0] = &Blit_Argb8888_Abgr8888; + s_blits [BlitFormat_Argb8888] [BlitFormat_Argb8888] [BlitAlpha_0] = &Blit_Argb8888_Argb8888; + s_blits [BlitFormat_Argb8888] [BlitFormat_Argb8888] [BlitAlpha_1] = &Blit_Argb8888_Argb8888_A1; + s_blits [BlitFormat_Argb8888] [BlitFormat_Argb8888] [BlitAlpha_8] = &Blit_Argb8888_Argb8888_A8; + s_blits [BlitFormat_Argb8888] [BlitFormat_Argb4444] [BlitAlpha_0] = &Blit_Argb8888_Argb4444; + s_blits [BlitFormat_Argb8888] [BlitFormat_Argb1555] [BlitAlpha_0] = &Blit_Argb8888_Argb1555; + s_blits [BlitFormat_Argb8888] [BlitFormat_Rgb565] [BlitAlpha_0] = &Blit_Argb8888_Rgb565; + + // Source: Argb4444 + s_blits [BlitFormat_Argb4444] [BlitFormat_Abgr8888] [BlitAlpha_0] = &Blit_Argb4444_Abgr8888; + s_blits [BlitFormat_Argb4444] [BlitFormat_Argb4444] [BlitAlpha_0] = &Blit_Argb4444_Argb4444; + + // Source: Argb1555 + s_blits [BlitFormat_Argb1555] [BlitFormat_Argb1555] [BlitAlpha_0] = &Blit_Argb1555_Argb1555; + + // Source: Rgb565 + s_blits [BlitFormat_Rgb565] [BlitFormat_Rgb565] [BlitAlpha_0] = &Blit_Rgb565_Rgb565; + + // Source: Dxt1 + s_blits [BlitFormat_Dxt1] [BlitFormat_Argb8888] [BlitAlpha_0] = &Blit_Dxt1_Argb8888; + s_blits [BlitFormat_Dxt1] [BlitFormat_Argb1555] [BlitAlpha_0] = &Blit_Dxt1_Argb1555; + s_blits [BlitFormat_Dxt1] [BlitFormat_Rgb565] [BlitAlpha_0] = &Blit_Dxt1_Rgb565; + s_blits [BlitFormat_Dxt1] [BlitFormat_Dxt1] [BlitAlpha_0] = &Blit_Dxt1_Dxt1; + + // Source: Dxt3 + s_blits [BlitFormat_Dxt3] [BlitFormat_Argb8888] [BlitAlpha_0] = &Blit_Dxt3_Argb8888; + s_blits [BlitFormat_Dxt3] [BlitFormat_Argb4444] [BlitAlpha_0] = &Blit_Dxt3_Argb4444; + s_blits [BlitFormat_Dxt3] [BlitFormat_Dxt3] [BlitAlpha_0] = &Blit_Dxt35_Dxt35; + + // Source: Dxt5 + s_blits [BlitFormat_Dxt5] [BlitFormat_Argb8888] [BlitAlpha_0] = &Blit_Dxt5_Argb8888; + s_blits [BlitFormat_Dxt5] [BlitFormat_Argb4444] [BlitAlpha_0] = &Blit_Dxt5_Argb4444; + s_blits [BlitFormat_Dxt5] [BlitFormat_Dxt5] [BlitAlpha_0] = &Blit_Dxt35_Dxt35; + + // Source: Uv88 + s_blits [BlitFormat_Uv88] [BlitFormat_Uv88] [BlitAlpha_0] = &Blit_Uv88_Uv88; + + // Source: Gr1616F + s_blits [BlitFormat_Gr1616F] [BlitFormat_Gr1616F] [BlitAlpha_0] = &Blit_Gr1616F_Gr1616F; + + // Source: R32F + s_blits [BlitFormat_R32F] [BlitFormat_R32F] [BlitAlpha_0] = &Blit_R32F_R32F; + + // Source: D24X8 + s_blits [BlitFormat_D24X8] [BlitFormat_D24X8] [BlitAlpha_0] = &Blit_D24X8_D24X8; +} + +void Blit(const C2iVector& size, BlitAlpha alpha, const void* src, uint32_t srcStride, BlitFormat srcFmt, void* dst, uint32_t dstStride, BlitFormat dstFmt) { + if (!initBlit) { + InitBlit(); + initBlit = 1; + } + + BLIT_FUNCTION blit = s_blits[srcFmt][dstFmt][alpha]; + + blit(size, src, srcStride, dst, dstStride); +} diff --git a/src/gx/Blit.hpp b/src/gx/Blit.hpp new file mode 100644 index 0000000..3a3c7bc --- /dev/null +++ b/src/gx/Blit.hpp @@ -0,0 +1,15 @@ +#ifndef GX_BLIT_HPP +#define GX_BLIT_HPP + +#include "gx/Types.hpp" +#include + +class C2iVector; + +typedef void (*BLIT_FUNCTION)(const C2iVector&, const void*, uint32_t, void*, uint32_t); + +void Blit(const C2iVector&, BlitAlpha, const void*, uint32_t, BlitFormat, void*, uint32_t, BlitFormat); + +BlitFormat GxGetBlitFormat(EGxTexFormat); + +#endif diff --git a/src/gx/Buffer.cpp b/src/gx/Buffer.cpp new file mode 100644 index 0000000..7642ff4 --- /dev/null +++ b/src/gx/Buffer.cpp @@ -0,0 +1,213 @@ +#include "gx/Buffer.hpp" +#include "gx/Device.hpp" + +CGxVertexAttrib vertexAttribsP[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_P, GxVA_Position), 12 } +}; + +CGxVertexAttrib vertexAttribsPN[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PN, GxVA_Position), 24 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PN, GxVA_Normal), 24 } +}; + +CGxVertexAttrib vertexAttribsPNC[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PNC, GxVA_Position), 28 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PNC, GxVA_Normal), 28 }, + { GxVA_Color0, 0, GxVertexAttribOffset(GxVBF_PNC, GxVA_Color0), 28 } +}; + +CGxVertexAttrib vertexAttribsPNT[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PNT, GxVA_Position), 32 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PNT, GxVA_Normal), 32 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PNT, GxVA_TexCoord0), 32 } +}; + +CGxVertexAttrib vertexAttribsPNCT[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PNCT, GxVA_Position), 36 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PNCT, GxVA_Normal), 36 }, + { GxVA_Color0, 0, GxVertexAttribOffset(GxVBF_PNCT, GxVA_Color0), 36 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PNCT, GxVA_TexCoord0), 36 } +}; + +CGxVertexAttrib vertexAttribsPNT2[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PNT2, GxVA_Position), 40 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PNT2, GxVA_Normal), 40 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PNT2, GxVA_TexCoord0), 40 }, + { GxVA_TexCoord1, 3, GxVertexAttribOffset(GxVBF_PNT2, GxVA_TexCoord1), 40 } +}; + +CGxVertexAttrib vertexAttribsPNCT2[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PNCT2, GxVA_Position), 44 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PNCT2, GxVA_Normal), 44 }, + { GxVA_Color0, 0, GxVertexAttribOffset(GxVBF_PNCT2, GxVA_Color0), 44 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PNCT2, GxVA_TexCoord0), 44 }, + { GxVA_TexCoord1, 3, GxVertexAttribOffset(GxVBF_PNCT2, GxVA_TexCoord1), 44 } +}; + +CGxVertexAttrib vertexAttribsPC[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PC, GxVA_Position), 16 }, + { GxVA_Color0, 0, GxVertexAttribOffset(GxVBF_PC, GxVA_Color0), 16 } +}; + +CGxVertexAttrib vertexAttribsPCT[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PCT, GxVA_Position), 24 }, + { GxVA_Color0, 0, GxVertexAttribOffset(GxVBF_PCT, GxVA_Color0), 24 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PCT, GxVA_TexCoord0), 24 } +}; + +CGxVertexAttrib vertexAttribsPCT2[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PCT2, GxVA_Position), 32 }, + { GxVA_Color0, 0, GxVertexAttribOffset(GxVBF_PCT2, GxVA_Color0), 32 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PCT2, GxVA_TexCoord0), 32 }, + { GxVA_TexCoord1, 3, GxVertexAttribOffset(GxVBF_PCT2, GxVA_TexCoord1), 32 } +}; + +CGxVertexAttrib vertexAttribsPT[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PT, GxVA_Position), 20 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PT, GxVA_TexCoord0), 20 } +}; + +CGxVertexAttrib vertexAttribsPT2[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PT2, GxVA_Position), 28 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PT2, GxVA_TexCoord0), 28 }, + { GxVA_TexCoord1, 3, GxVertexAttribOffset(GxVBF_PT2, GxVA_TexCoord1), 28 } +}; + +CGxVertexAttrib vertexAttribsPBNT2[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PBNT2, GxVA_Position), 48 }, + { GxVA_BlendWeight, 2, GxVertexAttribOffset(GxVBF_PBNT2, GxVA_BlendWeight), 48 }, + { GxVA_BlendIndices, 1, GxVertexAttribOffset(GxVBF_PBNT2, GxVA_BlendIndices), 48 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PBNT2, GxVA_Normal), 48 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PBNT2, GxVA_TexCoord0), 48 }, + { GxVA_TexCoord1, 3, GxVertexAttribOffset(GxVBF_PBNT2, GxVA_TexCoord1), 48 } +}; + +CGxVertexAttrib vertexAttribsPNC2T2[] = { + { GxVA_Position, 4, GxVertexAttribOffset(GxVBF_PNC2T2, GxVA_Position), 48 }, + { GxVA_Normal, 4, GxVertexAttribOffset(GxVBF_PNC2T2, GxVA_Normal), 48 }, + { GxVA_Color0, 0, GxVertexAttribOffset(GxVBF_PNC2T2, GxVA_Color0), 48 }, + { GxVA_Color1, 0, GxVertexAttribOffset(GxVBF_PNC2T2, GxVA_Color0), 48 }, + { GxVA_TexCoord0, 3, GxVertexAttribOffset(GxVBF_PNC2T2, GxVA_TexCoord0), 48 }, + { GxVA_TexCoord1, 3, GxVertexAttribOffset(GxVBF_PNC2T2, GxVA_TexCoord1), 48 } +}; + +VertexBufDesc Buffer::s_vertexBufDesc[] = { + // GxVBF_P + { vertexAttribsP, 1, 12, GxPrim_Position }, + + // GxVBF_PN + { vertexAttribsPN, 2, 24, GxPrim_Position | GxPrim_Normal }, + + // GxVBF_PNC + { vertexAttribsPNC, 3, 28, GxPrim_Position | GxPrim_Normal | GxPrim_Color0 }, + + // GxVBF_PNT + { vertexAttribsPNT, 3, 32, GxPrim_Position | GxPrim_Normal | GxPrim_TexCoord0 }, + + // GxVBF_PNCT + { vertexAttribsPNCT, 4, 36, GxPrim_Position | GxPrim_Normal | GxPrim_Color0 | GxPrim_TexCoord0 }, + + // GxVBF_PNT2 + { vertexAttribsPNT2, 4, 40, GxPrim_Position | GxPrim_Normal | GxPrim_TexCoord0 | GxPrim_TexCoord1 }, + + // GxVBF_PNCT2 + { vertexAttribsPNCT2, 5, 44, GxPrim_Position | GxPrim_Normal | GxPrim_Color0 | GxPrim_TexCoord0 | GxPrim_TexCoord1 }, + + // GxVBF_PC + { vertexAttribsPC, 2, 16, GxPrim_Position | GxPrim_Color0 }, + + // GxVBF_PCT + { vertexAttribsPCT, 3, 24, GxPrim_Position | GxPrim_Color0 | GxPrim_TexCoord0 }, + + // GxVBF_PCT2 + { vertexAttribsPCT2, 4, 32, GxPrim_Position | GxPrim_Color0 | GxPrim_TexCoord0 | GxPrim_TexCoord1 }, + + // GxVBF_PT + { vertexAttribsPT, 2, 20, GxPrim_Position | GxPrim_TexCoord0 }, + + // GxVBF_PT2 + { vertexAttribsPT2, 3, 28, GxPrim_Position | GxPrim_TexCoord0 | GxPrim_TexCoord1 }, + + // GxVBF_PBNT2 + { vertexAttribsPBNT2, 6, 48, GxPrim_Position | GxPrim_BlendWeight | GxPrim_BlendIndices | GxPrim_Normal | GxPrim_TexCoord0 | GxPrim_TexCoord1 }, + + // GxVBF_PNC2T2 + { vertexAttribsPNC2T2, 6, 48, GxPrim_Position | GxPrim_Normal | GxPrim_Color0 | GxPrim_Color1 | GxPrim_TexCoord0 | GxPrim_TexCoord1 } +}; + +int32_t Buffer::s_vertexBufOffset[GxVertexBufferFormats_Last][GxVAs_Last] = { + // GxVBF_P + { 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PN + { 0, -1, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PNC + { 0, -1, -1, 12, 24, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PNT + { 0, -1, -1, 12, -1, -1, 24, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PNCT + { 0, -1, -1, 12, 24, -1, 28, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PNT2 + { 0, -1, -1, 12, -1, -1, 24, 32, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PNCT2 + { 0, -1, -1, 12, 24, -1, 28, 36, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PC + { 0, -1, -1, -1, 12, -1, -1, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PCT + { 0, -1, -1, -1, 12, -1, 16, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PCT2 + { 0, -1, -1, -1, 12, -1, 16, 24, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PT + { 0, -1, -1, -1, -1, -1, 12, -1, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PT2 + { 0, -1, -1, -1, -1, -1, 12, 20, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PBNT2 + { 0, 12, 16, 20, -1, -1, 32, 40, -1, -1, -1, -1, -1, -1 }, + + // GxVBF_PNC2T2 + { 0, -1, -1, 12, 24, 28, 32, 40, -1, -1, -1, -1, -1, -1 } +}; + +uint32_t GxVertexAttribOffset(EGxVertexBufferFormat format, EGxVertexAttrib attrib) { + return Buffer::s_vertexBufOffset[format][attrib]; +} + +CGxBuf* GxBufCreate(CGxPool* pool, uint32_t itemSize, uint32_t itemCount, uint32_t index) { + return g_theGxDevicePtr->BufCreate(pool, itemSize, itemCount, index); +} + +char* GxBufLock(CGxBuf* buf) { + return g_theGxDevicePtr->BufLock(buf); +} + +void GxBufUnlock(CGxBuf* buf, uint32_t size) { + g_theGxDevicePtr->BufUnlock(buf, size); + buf->unk1C = 1; +} + +CGxPool* GxPoolCreate(EGxPoolTarget target, EGxPoolUsage usage, uint32_t size, EGxPoolHintBits hint, char* name) { + return g_theGxDevicePtr->PoolCreate(target, usage, size, hint, name); +} + +void GxPrimIndexPtr(CGxBuf* buf) { + g_theGxDevicePtr->PrimIndexPtr(buf); +} + +void GxPrimVertexPtr(CGxBuf* buf, EGxVertexBufferFormat format) { + auto desc = &Buffer::s_vertexBufDesc[format]; + + g_theGxDevicePtr->PrimVertexFormat(buf, desc->attribs, desc->attribCount); + g_theGxDevicePtr->PrimVertexMask(desc->mask); + g_theGxDevicePtr->PrimVertexPtr(buf, format); +} diff --git a/src/gx/Buffer.hpp b/src/gx/Buffer.hpp new file mode 100644 index 0000000..9b77a77 --- /dev/null +++ b/src/gx/Buffer.hpp @@ -0,0 +1,38 @@ +#ifndef GX_BUFFER_HPP +#define GX_BUFFER_HPP + +#include "gx/buffer/CGxBuf.hpp" +#include "gx/buffer/CGxPool.hpp" +#include "gx/buffer/Types.hpp" +#include + +class CGxBuf; +class CGxPool; + +struct VertexBufDesc { + CGxVertexAttrib* attribs; + uint32_t attribCount; + uint32_t size; + uint32_t mask; +}; + +namespace Buffer { + extern VertexBufDesc s_vertexBufDesc[GxVertexBufferFormats_Last]; + extern int32_t s_vertexBufOffset[GxVertexBufferFormats_Last][GxVAs_Last]; +} + +uint32_t GxVertexAttribOffset(EGxVertexBufferFormat, EGxVertexAttrib); + +CGxBuf* GxBufCreate(CGxPool*, uint32_t, uint32_t, uint32_t); + +char* GxBufLock(CGxBuf* buf); + +void GxBufUnlock(CGxBuf*, uint32_t); + +CGxPool* GxPoolCreate(EGxPoolTarget, EGxPoolUsage, uint32_t, EGxPoolHintBits, char*); + +void GxPrimIndexPtr(CGxBuf*); + +void GxPrimVertexPtr(CGxBuf*, EGxVertexBufferFormat); + +#endif diff --git a/src/gx/CCamera.cpp b/src/gx/CCamera.cpp new file mode 100644 index 0000000..f3a4428 --- /dev/null +++ b/src/gx/CCamera.cpp @@ -0,0 +1,58 @@ +#include "gx/CCamera.hpp" +#include "gx/Shader.hpp" +#include "gx/Transform.hpp" +#include +#include +#include +#include + +const C3Vector CCamera::DEFAULT_POSITION = { 100.0f, 0.0f, 0.0f }; +const float CCamera::DEFAULT_DIST = 100.0f; +const float CCamera::DEFAULT_FARZ = 5000.0f; +const float CCamera::DEFAULT_NEARZ = 8.0f; + +float CAngle::ClampTo2Pi(float angle) { + double v1 = floor(static_cast(CMath::OO_TWO_PI) * angle) * CMath::TWO_PI; + + if (angle < 0.0f) { + v1 -= CMath::TWO_PI; + } + + return angle - v1; +} + +void CAngle::Set(const float& angle) { + float clampedAngle = CAngle::ClampTo2Pi(angle); + + if (this->m_data != clampedAngle) { + this->m_flags |= CBaseManaged::UPDATED; + this->m_data = clampedAngle; + } + + this->m_cos = cos(this->m_data); + this->m_sin = sin(this->m_data); +} + +void CCamera::SetupWorldProjection(const CRect& projectionRect, uint32_t flags) { + C44Matrix projMat; + float aspect = (projectionRect.maxX - projectionRect.minX) / (projectionRect.maxY - projectionRect.minY); + GxuXformCreateProjection_SG(this->m_fov.m_data, aspect, this->m_zNear.m_data, this->m_zFar.m_data, projMat); + GxXformSetProjection(projMat); + + C44Matrix viewMat; + C3Vector cameraPos = { 0.0f, 0.0f, 0.0f }; + C3Vector cameraVec = { + this->m_target.m_data.x - this->m_position.m_data.x, + this->m_target.m_data.y - this->m_position.m_data.y, + this->m_target.m_data.z - this->m_position.m_data.z + }; + C3Vector upVec = { + this->m_rotation.m_sin * this->m_roll.m_sin, + -(this->m_rotation.m_cos * this->m_roll.m_sin), + this->m_roll.m_cos + }; + GxuXformCreateLookAtSgCompat(cameraPos, cameraVec, upVec, viewMat); + GxXformSetView(viewMat); + + CShaderEffect::UpdateProjMatrix(); +} diff --git a/src/gx/CCamera.hpp b/src/gx/CCamera.hpp new file mode 100644 index 0000000..1654e37 --- /dev/null +++ b/src/gx/CCamera.hpp @@ -0,0 +1,70 @@ +#ifndef GX_C_CAMERA_HPP +#define GX_C_CAMERA_HPP + +#include + +class CRect; + +class CAngle : public TManaged { + public: + // Static functions + static float ClampTo2Pi(float angle); + + // Member variables + float m_cos; + float m_sin; + + // Virtual member functions + virtual void Set(const float& angle); + + // Member functions + CAngle(float angle) { + this->Set(angle); + }; +}; + +class CCamera : public CDataMgr { + public: + static const C3Vector DEFAULT_POSITION; + static const float DEFAULT_DIST; + static const float DEFAULT_FARZ; + static const float DEFAULT_NEARZ; + + // Member variables + TManaged m_position; + TManaged m_target; + TManaged m_distance; + TManaged m_zFar; + TManaged m_zNear; + CAngle m_aoa; + CAngle m_fov; + CAngle m_roll; + CAngle m_rotation; + + // Member functions + CCamera() + : m_position(DEFAULT_POSITION) + , m_distance(DEFAULT_DIST) + , m_zFar(DEFAULT_FARZ) + , m_zNear(DEFAULT_NEARZ) + , m_aoa(0.0f) + , m_fov(0.0f) + , m_roll(0.0f) + , m_rotation(0.0f) + { + this->m_managedArray.SetCount(9); + + this->AddManaged(&this->m_position, 7, 0x0); + this->AddManaged(&this->m_target, 8, 0x0); + this->AddManaged(&this->m_distance, 1, 0x0); + this->AddManaged(&this->m_zFar, 2, 0x0); + this->AddManaged(&this->m_zNear, 3, 0x0); + this->AddManaged(&this->m_aoa, 0, 0x0); + this->AddManaged(&this->m_fov, 4, 0x0); + this->AddManaged(&this->m_roll, 5, 0x0); + this->AddManaged(&this->m_rotation, 6, 0x0); + } + void SetupWorldProjection(const CRect& projectionRect, uint32_t flags); +}; + +#endif diff --git a/src/gx/CGxBatch.hpp b/src/gx/CGxBatch.hpp new file mode 100644 index 0000000..6b93d27 --- /dev/null +++ b/src/gx/CGxBatch.hpp @@ -0,0 +1,17 @@ +#ifndef GX_C_GX_BATCH_HPP +#define GX_C_GX_BATCH_HPP + +#include "gx/Types.hpp" +#include + +class CGxBatch { + public: + // Member variables + EGxPrim m_primType; + uint32_t m_start; + uint32_t m_count; + uint16_t m_minIndex; + uint16_t m_maxIndex; +}; + +#endif diff --git a/src/gx/CGxCaps.hpp b/src/gx/CGxCaps.hpp new file mode 100644 index 0000000..bc2313e --- /dev/null +++ b/src/gx/CGxCaps.hpp @@ -0,0 +1,28 @@ +#ifndef GX_C_GX_CAPS_HPP +#define GX_C_GX_CAPS_HPP + +#include "gx/Types.hpp" +#include + +class CGxCaps { + public: + int32_t m_pixelCenterOnEdge = 0; + int32_t m_texelCenterOnEdge = 0; + EGxColorFormat m_colorFormat = GxCF_argb; + int32_t m_generateMipMaps = 0; + uint32_t m_maxTextureSize = 0; + int32_t m_texFmtDxt1 = 0; + int32_t m_texFmtDxt3 = 0; + int32_t m_texFmtDxt5 = 0; + EGxShVS m_vertexShaderTarget = GxShVS_none; + EGxShPS m_pixelShaderTarget = GxShPS_none; + int32_t m_texFilterAnisotropic = 0; + uint32_t m_maxTexAnisotropy = 0; + int32_t m_texTarget[GxTexTargets_Last]; + uint32_t m_texMaxSize[GxTexTargets_Last]; + int32_t int130 = 1; + int32_t int134 = 0; + int32_t int138 = 0; +}; + +#endif diff --git a/src/gx/CGxDevice.cpp b/src/gx/CGxDevice.cpp new file mode 100644 index 0000000..9adced0 --- /dev/null +++ b/src/gx/CGxDevice.cpp @@ -0,0 +1,1020 @@ +#include "gx/CGxDevice.hpp" +#include "gx/Gx.hpp" +#include "gx/Shader.hpp" +#include "gx/texture/CGxTex.hpp" +#include "util/SFile.hpp" +#include +#include +#include +#include +#include +#include + +#if defined(WHOA_SYSTEM_MAC) + #include "gx/gll/CGxDeviceGLL.hpp" +#endif + +uint32_t CGxDevice::s_alphaRef[] = { + 0, // GxBlend_Opaque + 224, // GxBlend_AlphaKey + 1, // GxBlend_Alpha + 1, // GxBlend_Add + 1, // GxBlend_Mod + 1, // GxBlend_Mod2x + 1, // GxBlend_ModAdd + 0, // GxBlend_InvSrcAlphaAdd + 0, // GxBlend_InvSrcAlphaOpaque + 0, // GxBlend_SrcAlphaOpaque + 0, // GxBlend_NoAlphaAdd + 0 // GxBlend_ConstantAlpha +}; + +C3Vector CGxDevice::s_pointScaleIdentity = { 1.0f, 0.0f, 0.0f }; + +ShaderConstants CGxDevice::s_shadowConstants[2]; + +uint32_t CGxDevice::s_streamPoolSize[] = { + 0x2C0000, // GxPoolTarget_Vertex + 0x40000 // GxPoolTarget_Index +}; + +uint32_t CGxDevice::s_texFormatBitDepth[] = { + 0, // GxTex_Unknown + 32, // GxTex_Abgr8888 + 32, // GxTex_Argb8888 + 16, // GxTex_Argb4444 + 16, // GxTex_Argb1555 + 16, // GxTex_Rgb565 + 4, // GxTex_Dxt1 + 8, // GxTex_Dxt3 + 8, // GxTex_Dxt5 + 16, // GxTex_Uv88 + 32, // GxTex_Gr1616F + 32, // GxTex_R32F + 32 // GxTex_D24X8 +}; + +uint32_t CGxDevice::s_texFormatBytesPerBlock[] = { + 0, // GxTex_Unknown + 4, // GxTex_Abgr8888 + 4, // GxTex_Argb8888 + 2, // GxTex_Argb4444 + 2, // GxTex_Argb1555 + 2, // GxTex_Rgb565 + 8, // GxTex_Dxt1 + 16, // GxTex_Dxt3 + 16, // GxTex_Dxt5 + 2, // GxTex_Uv88 + 4, // GxTex_Gr1616F + 4, // GxTex_R32F + 4 // GxTex_D24X8 +}; + +CGxDevice* CGxDevice::NewGLL() { + #if defined(WHOA_SYSTEM_MAC) + void* m = SMemAlloc(sizeof(CGxDeviceGLL), __FILE__, __LINE__, 0); + + if (m) { + return new (m) CGxDeviceGLL(); + } else { + return nullptr; + } + #endif +} + +CGxDevice* CGxDevice::NewOpenGl() { + // TODO + // void* m = SMemAlloc(sizeof(CGxDeviceOpenGl), __FILE__, __LINE__, 0); + + // if (m) { + // return new (m) CGxDeviceOpenGl(); + // } else { + // return nullptr; + // } + + return nullptr; +} + +CGxDevice::CGxDevice() { + // TODO + // - implement rest of constructor + + this->IRsInit(); + + // Set default viewport + this->m_viewport.x = { 0.0f, 1.0f }; + this->m_viewport.y = { 0.0f, 1.0f }; + this->m_viewport.z = { 0.0f, 1.0f }; + + // Turn on all master enables + this->m_appMasterEnables = 511; + this->m_hwMasterEnables = 511; + + this->ShaderConstantsClear(); +} + +CGxBuf* CGxDevice::BufCreate(CGxPool* pool, uint32_t itemSize, uint32_t itemCount, uint32_t index) { + void* m = SMemAlloc(sizeof(CGxBuf), __FILE__, __LINE__, 0); + CGxBuf* buf = new (m) CGxBuf(pool, itemSize, itemCount, index); + + pool->m_bufList.LinkToTail(buf); + + return buf; +} + +char* CGxDevice::BufLock(CGxBuf* buf) { + buf->unk1E = 1; + buf->unk1F = 0; + + this->m_bufLocked[buf->m_pool->m_target] = buf; + + return nullptr; +} + +CGxBuf* CGxDevice::BufStream(EGxPoolTarget target, uint32_t itemSize, uint32_t itemCount) { + CGxBuf* buf = this->m_streamBufs[target]; + CGxPool* pool = buf->m_pool; + + if (pool && pool->m_size < itemSize * itemCount) { + this->PoolSizeSet(pool, itemSize * itemCount); + } + + buf->m_itemSize = itemSize; + buf->m_itemCount = itemCount; + buf->m_size = itemSize * itemCount; + buf->unk1C = 0; + + return buf; +} + +int32_t CGxDevice::BufUnlock(CGxBuf* buf, uint32_t size) { + this->m_bufLocked[buf->m_pool->m_target] = nullptr; + + return 1; +} + +int32_t CGxDevice::DeviceCreate(long (*windowProc)(void*, uint32_t, uint32_t, long), const CGxFormat& format) { + // TODO + // this->m_windowProc = windowProc; + + return this->DeviceSetFormat(format); +} + +void CGxDevice::DeviceCreatePools() { + this->m_vertexPool = this->PoolCreate( + GxPoolTarget_Vertex, + GxPoolUsage_Stream, + CGxDevice::s_streamPoolSize[GxPoolTarget_Vertex], + GxPoolHintBit_Unk3, + "Stream_vtx" + ); + + this->m_indexPool = this->PoolCreate( + GxPoolTarget_Index, + GxPoolUsage_Stream, + CGxDevice::s_streamPoolSize[GxPoolTarget_Index], + GxPoolHintBit_Unk3, + "Stream_idx" + ); +} + +void CGxDevice::DeviceCreateStreamBufs() { + this->m_streamBufs[GxPoolTarget_Vertex] = this->BufCreate(this->m_vertexPool, 0, 0, 0); + this->m_streamBufs[GxPoolTarget_Index] = this->BufCreate(this->m_indexPool, 0, 0, 0); +} + +const CRect& CGxDevice::DeviceCurWindow() { + return this->m_curWindowRect; +} + +int32_t CGxDevice::DeviceSetFormat(const CGxFormat& format) { + memcpy(&this->m_format, &format, sizeof(this->m_format)); + + return 1; +} + +void CGxDevice::DeviceSetCurWindow(const CRect& rect) { + this->m_curWindowRect = rect; +} + +void CGxDevice::DeviceSetDefWindow(CRect const& rect) { + this->m_defWindowRect = rect; + this->DeviceSetCurWindow(rect); +} + +const CRect& CGxDevice::DeviceDefWindow() { + return this->m_defWindowRect; +} + +void CGxDevice::ICursorCreate(const CGxFormat& format) { + // TODO +} + +int32_t CGxDevice::IDevIsWindowed() { + return this->m_format.window; +} + +void CGxDevice::IRsDirty(EGxRenderState which) { + auto rs = &this->m_appRenderStates[which]; + + if (!rs->m_dirty) { + auto ds = this->m_dirtyStates.New(); + *ds = which; + + rs->m_dirty = 1; + } + + if (rs->m_stackDepth != this->m_stackOffsets.Count()) { + auto ps = this->m_pushedStates.New(); + ps->m_which = which; + ps->m_value = rs->m_value; + ps->m_stackDepth = rs->m_stackDepth; + + rs->m_stackDepth = this->m_stackOffsets.Count(); + } +} + +void CGxDevice::IRsForceUpdate() { + for (int32_t which = 0; which < GxRenderStates_Last; which++) { + auto& rs = this->m_appRenderStates[which]; + auto& hs = this->m_hwRenderStates[which]; + + auto ds = this->m_dirtyStates.New(); + *ds = static_cast(which); + + rs.m_dirty = 1; + + // TODO manage this through operators + hs.m_data.i[0] = ~rs.m_value.m_data.i[0]; + hs.m_data.i[1] = ~rs.m_value.m_data.i[1]; + hs.m_data.i[2] = ~rs.m_value.m_data.i[2]; + hs.filler = ~rs.m_value.filler; + } +} + +void CGxDevice::IRsForceUpdate(EGxRenderState which) { + if (!this->m_context) { + return; + } + + auto& rs = this->m_appRenderStates[which]; + auto& hs = this->m_hwRenderStates[which]; + + auto ds = this->m_dirtyStates.New(); + *ds = which; + + rs.m_dirty = 1; + + // TODO manage this through operators + hs.m_data.i[0] = ~rs.m_value.m_data.i[0]; + hs.m_data.i[1] = ~rs.m_value.m_data.i[1]; + hs.m_data.i[2] = ~rs.m_value.m_data.i[2]; + hs.filler = ~rs.m_value.filler; +} + +void CGxDevice::IRsInit() { + this->m_appRenderStates.SetCount(GxRenderStates_Last); + this->m_hwRenderStates.SetCount(GxRenderStates_Last); + + memset(this->m_appRenderStates.m_data, 0, sizeof(CGxAppRenderState) * GxRenderStates_Last); + memset(this->m_hwRenderStates.m_data, 0, sizeof(CGxStateBom) * GxRenderStates_Last); + + this->m_appRenderStates[GxRs_PolygonOffset].m_value = 0; + this->m_appRenderStates[GxRs_MatDiffuse].m_value = 0xFFFFFFFF; + this->m_appRenderStates[GxRs_MatEmissive].m_value = 0; + this->m_appRenderStates[GxRs_MatSpecular].m_value = 0; + this->m_appRenderStates[GxRs_MatSpecularExp].m_value = 0.0f; + this->m_appRenderStates[GxRs_NormalizeNormals].m_value = 0; + this->m_appRenderStates[GxRs_BlendingMode].m_value = GxBlend_Opaque; + this->m_appRenderStates[GxRs_FogStart].m_value = 1.0f; + this->m_appRenderStates[GxRs_FogEnd].m_value = 0.0f; + this->m_appRenderStates[GxRs_FogColor].m_value = 0xFF808080; + this->m_appRenderStates[GxRs_Lighting].m_value = 1; + this->m_appRenderStates[GxRs_Fog].m_value = 1; + this->m_appRenderStates[GxRs_DepthTest].m_value = 1; + this->m_appRenderStates[GxRs_DepthFunc].m_value = 0; + this->m_appRenderStates[GxRs_DepthWrite].m_value = 1; + this->m_appRenderStates[GxRs_ColorWrite].m_value = 15; + this->m_appRenderStates[GxRs_Culling].m_value = 1; + this->m_appRenderStates[GxRs_ClipPlaneMask].m_value = 0; + this->m_appRenderStates[GxRs_Multisample].m_value = 1; + this->m_appRenderStates[GxRs_ScissorTest].m_value = 0; + + this->m_appRenderStates[GxRs_Texture0].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp0].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp0].m_value = 0; + this->m_appRenderStates[GxRs_TexGen0].m_value = 0; + this->m_appRenderStates[GxRs_Unk61].m_value = 0; + this->m_appRenderStates[GxRs_Unk69].m_value = 0; + + this->m_appRenderStates[GxRs_Texture1].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp1].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp1].m_value = 0; + this->m_appRenderStates[GxRs_TexGen1].m_value = 0; + this->m_appRenderStates[GxRs_Unk62].m_value = 0; + this->m_appRenderStates[GxRs_Unk70].m_value = 1; + + this->m_appRenderStates[GxRs_Texture2].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp2].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp2].m_value = 0; + this->m_appRenderStates[GxRs_TexGen2].m_value = 0; + this->m_appRenderStates[GxRs_Unk63].m_value = 0; + this->m_appRenderStates[GxRs_Unk71].m_value = 2; + + this->m_appRenderStates[GxRs_Texture3].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp3].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp3].m_value = 0; + this->m_appRenderStates[GxRs_TexGen3].m_value = 0; + this->m_appRenderStates[GxRs_Unk64].m_value = 0; + this->m_appRenderStates[GxRs_Unk72].m_value = 3; + + this->m_appRenderStates[GxRs_Texture4].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp4].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp4].m_value = 0; + this->m_appRenderStates[GxRs_TexGen4].m_value = 0; + this->m_appRenderStates[GxRs_Unk65].m_value = 0; + this->m_appRenderStates[GxRs_Unk73].m_value = 4; + + this->m_appRenderStates[GxRs_Texture5].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp5].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp5].m_value = 0; + this->m_appRenderStates[GxRs_TexGen5].m_value = 0; + this->m_appRenderStates[GxRs_Unk66].m_value = 0; + this->m_appRenderStates[GxRs_Unk74].m_value = 5; + + this->m_appRenderStates[GxRs_Texture6].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp6].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp6].m_value = 0; + this->m_appRenderStates[GxRs_TexGen6].m_value = 0; + this->m_appRenderStates[GxRs_Unk67].m_value = 0; + this->m_appRenderStates[GxRs_Unk75].m_value = 6; + + this->m_appRenderStates[GxRs_Texture7].m_value = nullptr; + this->m_appRenderStates[GxRs_ColorOp7].m_value = 0; + this->m_appRenderStates[GxRs_AlphaOp7].m_value = 0; + this->m_appRenderStates[GxRs_TexGen7].m_value = 0; + this->m_appRenderStates[GxRs_Unk68].m_value = 0; + this->m_appRenderStates[GxRs_Unk76].m_value = 7; + + this->m_appRenderStates[GxRs_Texture8].m_value = nullptr; + this->m_appRenderStates[GxRs_Texture9].m_value = nullptr; + this->m_appRenderStates[GxRs_Texture10].m_value = nullptr; + this->m_appRenderStates[GxRs_Texture11].m_value = nullptr; + this->m_appRenderStates[GxRs_Texture12].m_value = nullptr; + this->m_appRenderStates[GxRs_Texture13].m_value = nullptr; + this->m_appRenderStates[GxRs_Texture14].m_value = nullptr; + this->m_appRenderStates[GxRs_Texture15].m_value = nullptr; + + this->m_appRenderStates[GxRs_PixelShader].m_value = nullptr; + this->m_appRenderStates[GxRs_VertexShader].m_value = nullptr; + + this->m_appRenderStates[GxRs_PointScale].m_value = 1.0f; + this->m_appRenderStates[GxRs_PointScaleAttenuation].m_value = CGxDevice::s_pointScaleIdentity; + this->m_appRenderStates[GxRs_PointScaleMin].m_value = 0.0f; + this->m_appRenderStates[GxRs_PointScaleMax].m_value = 1.0f; + this->m_appRenderStates[GxRs_PointSprite].m_value = 0; + + this->m_appRenderStates[GxRs_Unk84].m_value = 0.0f; + this->m_appRenderStates[GxRs_ColorMaterial].m_value = 0; +} + +void CGxDevice::IRsSync(int32_t force) { + if (force) { + this->IRsForceUpdate(); + } + + for (int32_t i = 0; i < this->m_dirtyStates.Count(); i++) { + auto ds = this->m_dirtyStates[i]; + auto rs = &this->m_appRenderStates[ds]; + auto hs = &this->m_hwRenderStates[ds]; + + if (rs->m_dirty && rs->m_value != *hs) { + this->IRsSendToHw(ds); + } + + *hs = rs->m_value; + rs->m_dirty = 0; + } + + this->m_dirtyStates.SetCount(0); +} + +void CGxDevice::IShaderLoad(CGxShader* shaders[], EGxShTarget target, const char* a4, const char* a5, int32_t permutations) { + int32_t profile = this->m_shaderProfiles[target]; + + if (!profile) { + return; + } + + char path[260]; + SFile* file; + + while (true) { + sprintf(path, "%s\\%s\\%s.bls", a4, g_gxShaderProfileNames[target][profile], a5); + SFile::Open(path, &file); + + // Opened shader file + if (file) { + break; + } + + // Fallback + if (target == GxSh_Vertex) { + // vs_3_0 -> vs_2_0 + // vs_2_0 -> vs_1_1 + + if (profile == 3) { + profile = 2; + } else if (profile == 2) { + profile = 1; + } else { + break; + } + } else if (target == GxSh_Pixel) { + // nvts3 -> nvts + // nvts2 -> nvts + // ps_3_0 -> ps_2_0 + // ps_2_0 -> ps_1_4 + // ps_1_4 -> ps_1_1 + + if (profile == 9 || profile == 10) { + profile = 8; + } else if (profile == 4) { + profile = 3; + } else if (profile == 3) { + profile = 2; + } else if (profile == 2) { + profile = 1; + } else { + break; + } + } + } + + if (!file) { + return; + } + + uint32_t signature; + uint32_t version; + uint32_t permutationCount; + + if (file) { + SFile::Read(file, &signature, 4, nullptr, nullptr, nullptr); + + if (signature == 'GXSH') { + SFile::Read(file, &version, 4, nullptr, nullptr, nullptr); + + if (version == 0x10003) { + SFile::Read(file, &permutationCount, 4, nullptr, nullptr, nullptr); + + // TODO + // assert(permutationCount == permutations); + + if (permutations > 0) { + for (int32_t p = 0; p < permutations; p++) { + shaders[p]->Load(file); + + shaders[p]->loaded = 0; + shaders[p]->int34 = 0; + } + } + } + } + + SFile::Close(file); + } +} + +void CGxDevice::ITexMarkAsUpdated(CGxTex* texId) { + if (!texId->m_needsUpdate) { + return; + } + + texId->m_updateRect = { + static_cast(texId->m_height), + static_cast(texId->m_width), + 0, + 0 + }; + + texId->m_needsUpdate = 0; +} + +void CGxDevice::ITexWHDStartEnd(CGxTex* texId, uint32_t& width, uint32_t& height, uint32_t& baseMip, uint32_t& mipCount) { + width = texId->m_width; + height = texId->m_height; + + if ((texId->m_flags.m_filter == GxTex_Nearest || texId->m_flags.m_filter == GxTex_Linear || texId->m_flags.m_generateMipMaps) && !texId->m_flags.m_forceMipTracking) { + baseMip = 0; + mipCount = 1; + + return; + } + + mipCount = 1; + + if (texId->m_format == GxTex_Dxt1 || texId->m_format == GxTex_Dxt3 || texId->m_format == GxTex_Dxt5) { + uint32_t shortEdge = std::min(texId->m_width, texId->m_height); + + while (shortEdge > 4) { + shortEdge /= 2; + mipCount++; + } + } else { + uint32_t longEdge = std::max(texId->m_width, texId->m_height); + + while (longEdge > 1) { + longEdge /= 2; + mipCount++; + } + } + + baseMip = std::min(this->m_baseMipLevel, mipCount - 1); + + width >>= baseMip; + height >>= baseMip; + + if (texId->m_flags.m_forceMipTracking) { + mipCount = baseMip + 1; + } +} + +int32_t CGxDevice::MasterEnable(EGxMasterEnables state) { + return ((1 << state) & this->m_appMasterEnables) != 0; +} + +void CGxDevice::MasterEnableSet(EGxMasterEnables state, int32_t enable) { + this->m_appMasterEnables = ((enable & 1) << state) | (this->m_appMasterEnables & ~(1 << state)); + + switch (state) { + case GxMasterEnable_Lighting: + this->IRsForceUpdate(GxRs_Lighting); + break; + + case GxMasterEnable_Fog: + this->IRsForceUpdate(GxRs_Fog); + break; + + case GxMasterEnable_DepthTest: + this->IRsForceUpdate(GxRs_DepthTest); + break; + + case GxMasterEnable_DepthWrite: + this->IRsForceUpdate(GxRs_DepthWrite); + break; + + case GxMasterEnable_ColorWrite: + this->IRsForceUpdate(GxRs_ColorWrite); + break; + + case GxMasterEnable_Culling: + this->IRsForceUpdate(GxRs_Culling); + break; + + default: + break; + } +} + +void CGxDevice::PrimIndexPtr(CGxBuf* buf) { + if (buf->unk1E || this->m_primIndexBuf != buf) { + buf->unk1E = 0; + this->m_primIndexDirty = 1; + this->m_primIndexBuf = buf; + } +} + +void CGxDevice::PrimVertexFormat(CGxBuf* buf, CGxVertexAttrib* attribs, uint32_t count) { + for (int32_t i = 0; i < count; i++) { + int32_t attrib = attribs->attrib; + + int32_t dirty = buf->unk1E + || this->m_primVertexFormatBuf[attrib] != buf + || this->m_primVertexFormatAttrib[attrib].type != attribs->type + || this->m_primVertexFormatAttrib[attrib].offset != attribs->offset + || this->m_primVertexFormatAttrib[attrib].bufSize != attribs->bufSize; + + if (dirty) { + this->m_primVertexDirty |= 1 << attrib; + } + + this->m_primVertexFormatBuf[attrib] = buf; + + this->m_primVertexFormatAttrib[attrib].attrib = attribs->attrib; + this->m_primVertexFormatAttrib[attrib].type = attribs->type; + this->m_primVertexFormatAttrib[attrib].offset = attribs->offset; + this->m_primVertexFormatAttrib[attrib].bufSize = attribs->bufSize; + + attribs++; + } + + buf->unk1E = 0; + this->m_primVertexFormat = GxVertexBufferFormats_Last; +} + +void CGxDevice::PrimVertexMask(uint32_t mask) { + this->m_primVertexDirty |= mask ^ this->m_primVertexMask; + this->m_primVertexMask = mask; + this->m_primVertexFormat = GxVertexBufferFormats_Last; +} + +void CGxDevice::PrimVertexPtr(CGxBuf* buf, EGxVertexBufferFormat format) { + this->m_primVertexFormat = format; + this->m_primVertexBuf = buf; + this->m_primVertexSize = Buffer::s_vertexBufDesc[format].size; +} + +CGxPool* CGxDevice::PoolCreate(EGxPoolTarget target, EGxPoolUsage usage, uint32_t size, EGxPoolHintBits hint, const char* name) { + void* m = SMemAlloc(sizeof(CGxPool), __FILE__, __LINE__, 0); + CGxPool* pool = new (m) CGxPool(target, usage, size, hint, name); + + this->m_poolList.LinkToTail(pool); + + return pool; +} + +void CGxDevice::RsGet(EGxRenderState which, int32_t& value) { + value = static_cast(this->m_appRenderStates[which].m_value); +} + +void CGxDevice::RsSet(EGxRenderState which, int32_t value) { + if (!this->m_context) { + return; + } + + if (this->m_appRenderStates[which].m_value != value) { + this->IRsDirty(which); + this->m_appRenderStates[which].m_value = value; + } +} + +void CGxDevice::RsSet(EGxRenderState which, void* value) { + if (!this->m_context) { + return; + } + + if (this->m_appRenderStates[which].m_value != value) { + this->IRsDirty(which); + this->m_appRenderStates[which].m_value = value; + + if (value) { + if (which >= GxRs_Texture0 && which <= GxRs_Texture15) { + CGxTex* texture = static_cast(value); + + if (texture->m_flags.m_renderTarget && texture->m_needsUpdate) { + this->ITexMarkAsUpdated(texture); + } + } + } + } +} + +void CGxDevice::RsSetAlphaRef() { + if (!this->m_context) { + return; + } + + int32_t blendingMode; + this->RsGet(GxRs_BlendingMode, blendingMode); + + this->RsSet(GxRs_AlphaRef, CGxDevice::s_alphaRef[blendingMode]); +} + +void CGxDevice::RsPop() { + auto topOfStack = this->m_stackOffsets[this->m_stackOffsets.Count() - 1]; + + if (this->m_pushedStates.Count() > topOfStack) { + for (int32_t i = this->m_pushedStates.Count() - 1; i > topOfStack; i--) { + auto ps = &this->m_pushedStates[i]; + auto rs = &this->m_appRenderStates[ps->m_which]; + + if (!rs->m_dirty) { + auto ds = this->m_dirtyStates.New(); + *ds = ps->m_which; + + rs->m_dirty = 1; + } + + rs->m_value = ps->m_value; + rs->m_stackDepth = ps->m_stackDepth; + } + } + + this->m_pushedStates.SetCount(topOfStack); + this->m_stackOffsets.SetCount(this->m_stackOffsets.Count() - 1); +} + +void CGxDevice::RsPush() { + auto offset = this->m_stackOffsets.New(); + *offset = this->m_pushedStates.Count(); +} + +void CGxDevice::ScenePresent() { + // TODO +} + +void CGxDevice::ShaderConstantsClear() { + for (int32_t i = 0; i < 256; i++) { + CGxDevice::s_shadowConstants[0].constants[i] = { + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max() + }; + + CGxDevice::s_shadowConstants[1].constants[i] = { + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max(), + std::numeric_limits::max() + }; + } + + CGxDevice::s_shadowConstants[0].unk1 = 0; + CGxDevice::s_shadowConstants[0].unk2 = 255; + + CGxDevice::s_shadowConstants[1].unk1 = 0; + CGxDevice::s_shadowConstants[1].unk2 = 255; +} + +char* CGxDevice::ShaderConstantsLock(EGxShTarget target) { + return target == GxSh_Vertex + ? reinterpret_cast(&CGxDevice::s_shadowConstants[1].constants) + : reinterpret_cast(&CGxDevice::s_shadowConstants[0].constants); +} + +void CGxDevice::ShaderConstantsSet(EGxShTarget target, uint32_t index, const float* constants, uint32_t count) { + STORM_ASSERT((index + count - 1) <= 255); + + if (!count) { + return; + } + + ShaderConstants* dst; + + if (target == GxSh_Vertex) { + dst = &CGxDevice::s_shadowConstants[1]; + } else if (target == GxSh_Pixel) { + dst = &CGxDevice::s_shadowConstants[0]; + } else { + STORM_ASSERT(false); + } + + const float* c = constants; + + for (int32_t i = index; i < index + count; i++, c += 4) { + int32_t dirty = 0; + + if (dst->constants[i].x != c[0]) { + dirty = 1; + dst->constants[i].x = c[0]; + } + + if (dst->constants[i].y != c[1]) { + dirty = 1; + dst->constants[i].y = c[1]; + } + + if (dst->constants[i].z != c[2]) { + dirty = 1; + dst->constants[i].z = c[2]; + } + + if (dst->constants[i].w != c[3]) { + dirty = 1; + dst->constants[i].w = c[3]; + } + + if (dirty) { + if (dst->unk2 > i) { + dst->unk2 = i; + } + + if (dst->unk1 < i) { + dst->unk1 = i; + } + } + } +} + +void CGxDevice::ShaderConstantsUnlock(EGxShTarget target, uint32_t index, uint32_t count) { + if (target == GxSh_Pixel) { + ShaderConstants& dst = CGxDevice::s_shadowConstants[0]; + dst.unk2 = std::min(dst.unk2, index); + dst.unk1 = std::max(dst.unk1, index + count - 1); + } else { + ShaderConstants& dst = CGxDevice::s_shadowConstants[1]; + dst.unk2 = std::min(dst.unk2, index); + dst.unk1 = std::max(dst.unk1, index + count - 1); + } +} + +void CGxDevice::ShaderCreate(CGxShader* shaders[], EGxShTarget target, const char* a4, const char* a5, int32_t permutations) { + auto shaderList = &this->m_shaderList[target]; + + if (permutations == 0) { + return; + } + + if (permutations == 1) { + auto shader = shaderList->Ptr(a5); + + if (shader) { + shaders[0] = shader; + shader->refCount++; + return; + } + + shader = shaderList->New(a5, 0, 0); + shaders[0] = shader; + + shader->refCount++; + shader->target = target; + + this->IShaderLoad(shaders, target, a4, a5, permutations); + + return; + } + + memset(shaders, 0, permutations * sizeof(void*)); + + char key[256]; + + int32_t p = 0; + + while (p < permutations) { + sprintf(key, "%s:%d", a5, p); + + auto shader = shaderList->Ptr(key); + shaders[p] = shader; + + if (!shader) { + break; + } + + p++; + } + + // Fully loaded + if (p == permutations) { + for (p = 0; p < permutations; p++) { + shaders[p]->refCount++; + } + + return; + } + + for (p = 0; p < permutations; p++) { + sprintf(key, "%s:%d", a5, p); + + auto shader = shaderList->New(key, 0, 0); + shaders[p] = shader; + + shader->refCount++; + shader->target = target; + } + + this->IShaderLoad(shaders, target, a4, a5, permutations); +} + +int32_t CGxDevice::TexCreate(EGxTexTarget target, uint32_t width, uint32_t height, uint32_t depth, EGxTexFormat format, EGxTexFormat dataFormat, CGxTexFlags flags, void* userArg, void (*userFunc)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char* name, CGxTex*& texId) { + void* m = SMemAlloc(sizeof(CGxTex), __FILE__, __LINE__, 0); + + CGxTex* tex = nullptr; + + if (m) { + tex = new (m) CGxTex( + target, + width, + height, + depth, + format, + dataFormat, + flags, + userArg, + userFunc, + name + ); + } + + texId = tex; + + // TODO + // - link tex to list in device + + return 1; +} + +void CGxDevice::TexDestroy(CGxTex* texId) { + // TODO + + if (texId) { + delete texId; + } +} + +void CGxDevice::TexMarkForUpdate(CGxTex* texId, const CiRect& updateRect, int32_t immediate) { + texId->m_needsUpdate = 1; + + // If the bounds of the updateRect are invalid, default to { 0, 0, height, width } + if (updateRect.minY >= updateRect.maxY || updateRect.minX >= updateRect.maxX) { + texId->m_updateRect = { + 0, + 0, + static_cast(texId->m_height), + static_cast(texId->m_width) + }; + } else { + texId->m_updateRect = updateRect; + } + + if (immediate) { + this->ITexMarkAsUpdated(texId); + } +} + +void CGxDevice::TexSetWrap(CGxTex* texId, EGxTexWrapMode wrapU, EGxTexWrapMode wrapV) { + // TODO +} + +void CGxDevice::ValidateDraw(CGxBatch* batch, int32_t count) { + // TODO +} + +void CGxDevice::XformPop(EGxXform xf) { + this->m_xforms[xf].Pop(); +} + +void CGxDevice::XformProjection(C44Matrix& matrix) { + matrix = this->m_projection; +} + +void CGxDevice::XformProjNative(C44Matrix& matrix) { + matrix = this->m_projNative; + + if (this->m_api == GxApi_OpenGl) { + matrix.c0 *= -1.0f; + matrix.c1 *= -1.0f; + matrix.c2 *= -1.0f; + matrix.c3 *= -1.0f; + } +} + +void CGxDevice::XformPush(EGxXform xf) { + this->m_xforms[xf].Push(); +} + +void CGxDevice::XformSet(EGxXform xf, const C44Matrix& matrix) { + this->m_xforms[xf].Top() = matrix; +} + +void CGxDevice::XformSetProjection(const C44Matrix& matrix) { + this->m_projection = matrix; +} + +void CGxDevice::XformSetView(const C44Matrix& matrix) { + this->m_xforms[GxXform_View].Top() = matrix; + + for (int32_t i = GxRs_TexGen0; i < GxRs_TexGen7; i++) { + if (static_cast(this->m_appRenderStates[i].m_value) - 1 <= 1) { + this->IRsForceUpdate(static_cast(i)); + } + } +} + +void CGxDevice::XformSetViewport(float minX, float maxX, float minY, float maxY, float minZ, float maxZ) { + if ( + minX == this->m_viewport.x.l + && maxX == this->m_viewport.x.h + && minY == this->m_viewport.y.l + && maxY == this->m_viewport.y.h + && minZ == this->m_viewport.z.l + && maxZ == this->m_viewport.z.h + ) { + return; + } + + // TODO + // this->unk4[4] = 1; + + this->m_viewport.x.l = minX; + this->m_viewport.x.h = maxX; + this->m_viewport.y.l = minY; + this->m_viewport.y.h = maxY; + this->m_viewport.z.l = minZ; + this->m_viewport.z.h = maxZ; +} + +void CGxDevice::XformView(C44Matrix& matrix) { + matrix = this->m_xforms[GxXform_View].m_mtx[this->m_xforms[GxXform_View].m_level]; +} + +void CGxDevice::XformViewport(float& minX, float& maxX, float& minY, float& maxY, float& minZ, float& maxZ) { + minX = this->m_viewport.x.l; + maxX = this->m_viewport.x.h; + minY = this->m_viewport.y.l; + maxY = this->m_viewport.y.h; + minZ = this->m_viewport.z.l; + maxZ = this->m_viewport.z.h; +} diff --git a/src/gx/CGxDevice.hpp b/src/gx/CGxDevice.hpp new file mode 100644 index 0000000..8c31fa5 --- /dev/null +++ b/src/gx/CGxDevice.hpp @@ -0,0 +1,160 @@ +#ifndef GX_C_GX_DEVICE_HPP +#define GX_C_GX_DEVICE_HPP + +#include "gx/Buffer.hpp" +#include "gx/CGxCaps.hpp" +#include "gx/CGxFormat.hpp" +#include "gx/CGxMatrixStack.hpp" +#include "gx/CGxStateBom.hpp" +#include "gx/Types.hpp" +#include "gx/Shader.hpp" +#include +#include +#include +#include + +class CGxBatch; +class CGxTex; +class CGxTexFlags; + +struct CGxAppRenderState { + CGxStateBom m_value; + uint32_t m_stackDepth; + int32_t m_dirty; +}; + +struct CGxPushedRenderState { + EGxRenderState m_which; + CGxStateBom m_value; + uint32_t m_stackDepth; +}; + +struct ShaderConstants { + C4Vector constants[256]; + uint32_t unk1; + uint32_t unk2; +}; + +class CGxDevice { + public: + // Static variables + static uint32_t s_alphaRef[]; + static C3Vector s_pointScaleIdentity; + static ShaderConstants s_shadowConstants[2]; + static uint32_t s_streamPoolSize[]; + static uint32_t s_texFormatBitDepth[]; + static uint32_t s_texFormatBytesPerBlock[]; + + // Static functions + static CGxDevice* NewGLL(void); + static CGxDevice* NewOpenGl(void); + + // Member variables + TSGrowableArray m_pushedStates; + TSGrowableArray m_stackOffsets; + TSGrowableArray m_dirtyStates; + CRect m_defWindowRect; + CRect m_curWindowRect; + EGxApi m_api = GxApis_Last; + CGxFormat m_format; + CGxCaps m_caps; + int32_t m_shaderProfiles[GxShTargets_Last] = { 6, 0, 0, 0, 12, 0 }; // TODO placeholder + TSHashTable m_shaderList[GxShTargets_Last]; + int32_t m_context = 0; + CBoundingBox m_viewport; + C44Matrix m_projection; + C44Matrix m_projNative; + CGxMatrixStack m_xforms[GxXforms_Last]; + uint32_t m_appMasterEnables = 0; + uint32_t m_hwMasterEnables = 0; + TSList> m_poolList; + CGxBuf* m_bufLocked[GxPoolTargets_Last]; + CGxPool* m_vertexPool = nullptr; + CGxPool* m_indexPool = nullptr; + CGxBuf* m_streamBufs[GxPoolTargets_Last]; + CGxVertexAttrib m_primVertexFormatAttrib[GxVertexBufferFormats_Last]; + CGxBuf* m_primVertexFormatBuf[GxVertexBufferFormats_Last]; + uint32_t m_primVertexMask = 0; + uint32_t m_primVertexDirty = 0; + EGxVertexBufferFormat m_primVertexFormat = GxVertexBufferFormats_Last; + CGxBuf* m_primVertexBuf = nullptr; + uint32_t m_primVertexSize; + CGxBuf* m_primIndexBuf = nullptr; + int32_t m_primIndexDirty = 0; + TSFixedArray m_appRenderStates; + TSFixedArray m_hwRenderStates; + uint32_t m_baseMipLevel = 0; // TODO placeholder + + // Virtual member functions + virtual void ITexMarkAsUpdated(CGxTex*) = 0; + virtual void IRsSendToHw(EGxRenderState) = 0; + virtual void ICursorCreate(const CGxFormat& format); + virtual int32_t DeviceCreate(long (*)(void*, uint32_t, uint32_t, long), const CGxFormat&); + virtual int32_t DeviceSetFormat(const CGxFormat&); + virtual void CapsWindowSize(CRect&) = 0; + virtual void CapsWindowSizeInScreenCoords(CRect& dst) = 0; + virtual void ScenePresent(void); + virtual void SceneClear(uint32_t, CImVector) {}; + virtual void XformSetProjection(const C44Matrix&); + virtual void XformSetView(const C44Matrix&); + virtual void Draw(CGxBatch*, int32_t) {}; + virtual void ValidateDraw(CGxBatch*, int32_t); + virtual void MasterEnableSet(EGxMasterEnables, int32_t); + virtual void PoolSizeSet(CGxPool*, uint32_t) = 0; + virtual char* BufLock(CGxBuf*); + virtual int32_t BufUnlock(CGxBuf*, uint32_t); + virtual int32_t TexCreate(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexFormat, EGxTexFormat, CGxTexFlags, void*, void (*)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char*, CGxTex*&); + virtual void TexDestroy(CGxTex* texId); + virtual void ShaderCreate(CGxShader*[], EGxShTarget, const char*, const char*, int32_t); + virtual void ShaderConstantsSet(EGxShTarget, uint32_t, const float*, uint32_t); + virtual void IShaderCreate(CGxShader*) = 0; + virtual int32_t StereoEnabled(void) = 0; + + // Member functions + CGxDevice(); + CGxBuf* BufCreate(CGxPool*, uint32_t, uint32_t, uint32_t); + CGxBuf* BufStream(EGxPoolTarget, uint32_t, uint32_t); + void DeviceCreatePools(void); + void DeviceCreateStreamBufs(void); + const CRect& DeviceCurWindow(void); + void DeviceSetCurWindow(const CRect&); + void DeviceSetDefWindow(CRect const&); + const CRect& DeviceDefWindow(void); + int32_t IDevIsWindowed(); + void IRsDirty(EGxRenderState); + void IRsForceUpdate(void); + void IRsForceUpdate(EGxRenderState); + void IRsInit(void); + void IRsSync(int32_t); + void IShaderBind(void) {}; + void IShaderLoad(CGxShader*[], EGxShTarget, const char*, const char*, int32_t); + void ITexBind(void) {}; + void ITexWHDStartEnd(CGxTex*, uint32_t&, uint32_t&, uint32_t&, uint32_t&); + int32_t MasterEnable(EGxMasterEnables); + CGxPool* PoolCreate(EGxPoolTarget, EGxPoolUsage, uint32_t, EGxPoolHintBits, const char*); + void PrimIndexPtr(CGxBuf*); + void PrimVertexFormat(CGxBuf*, CGxVertexAttrib*, uint32_t); + void PrimVertexMask(uint32_t); + void PrimVertexPtr(CGxBuf*, EGxVertexBufferFormat); + void RsGet(EGxRenderState, int32_t&); + void RsSet(EGxRenderState, int32_t); + void RsSet(EGxRenderState, void*); + void RsSetAlphaRef(void); + void RsPop(void); + void RsPush(void); + void ShaderConstantsClear(void); + char* ShaderConstantsLock(EGxShTarget target); + void ShaderConstantsUnlock(EGxShTarget target, uint32_t index, uint32_t count); + void TexMarkForUpdate(CGxTex*, const CiRect&, int32_t); + void TexSetWrap(CGxTex* texId, EGxTexWrapMode wrapU, EGxTexWrapMode wrapV); + void XformPop(EGxXform xf); + void XformProjection(C44Matrix&); + void XformProjNative(C44Matrix&); + void XformPush(EGxXform xf); + void XformSet(EGxXform xf, const C44Matrix& matrix); + void XformSetViewport(float, float, float, float, float, float); + void XformView(C44Matrix&); + void XformViewport(float&, float&, float&, float&, float&, float&); +}; + +#endif diff --git a/src/gx/CGxFormat.hpp b/src/gx/CGxFormat.hpp new file mode 100644 index 0000000..7aeea3a --- /dev/null +++ b/src/gx/CGxFormat.hpp @@ -0,0 +1,32 @@ +#ifndef GX_C_GX_FORMAT_HPP +#define GX_C_GX_FORMAT_HPP + +#include +#include + +class CGxFormat { + public: + // Types + enum Format { + Fmt_Rgb565 = 0, + Fmt_ArgbX888 = 1, + Fmt_Argb8888 = 2, + Fmt_Argb2101010 = 3, + Fmt_Ds160 = 4, + Fmt_Ds24X = 5, + Fmt_Ds248 = 6, + Fmt_Ds320 = 7, + Formats_Last = 8 + }; + + // Member variables + int8_t window; + int32_t maximize; + Format depthFormat; + C2iVector size; + uint32_t sampleCount; + Format colorFormat; + uint32_t refreshRate; +}; + +#endif diff --git a/src/gx/CGxMatrixStack.cpp b/src/gx/CGxMatrixStack.cpp new file mode 100644 index 0000000..804bf1e --- /dev/null +++ b/src/gx/CGxMatrixStack.cpp @@ -0,0 +1,30 @@ +#include "gx/CGxMatrixStack.hpp" + +CGxMatrixStack::CGxMatrixStack() { + this->m_flags[0] = 0x1; +} + +void CGxMatrixStack::Pop() { + if (this->m_level > 0) { + this->m_level--; + } + + this->m_dirty = 1; +} + +void CGxMatrixStack::Push() { + if (this->m_level < 3) { + this->m_level++; + } + + this->m_mtx[this->m_level] = this->m_mtx[this->m_level - 1]; + this->m_flags[this->m_level] = this->m_flags[this->m_level - 1]; + + this->m_dirty = 1; +} + +C44Matrix& CGxMatrixStack::Top() { + this->m_dirty = 1; + this->m_flags[this->m_level] &= 0xFFFFFFFE; + return this->m_mtx[this->m_level]; +} diff --git a/src/gx/CGxMatrixStack.hpp b/src/gx/CGxMatrixStack.hpp new file mode 100644 index 0000000..a0eef62 --- /dev/null +++ b/src/gx/CGxMatrixStack.hpp @@ -0,0 +1,22 @@ +#ifndef GX_C_GX_MATRIX_STACK_HPP +#define GX_C_GX_MATRIX_STACK_HPP + +#include +#include + +class CGxMatrixStack { + public: + // Member variables + uint32_t m_level = 0; + int8_t m_dirty = 0; + C44Matrix m_mtx[4]; + uint32_t m_flags[4] = {}; + + // Member functions + CGxMatrixStack(); + void Pop(void); + void Push(void); + C44Matrix& Top(void); +}; + +#endif diff --git a/src/gx/CGxStateBom.cpp b/src/gx/CGxStateBom.cpp new file mode 100644 index 0000000..fc9d4cf --- /dev/null +++ b/src/gx/CGxStateBom.cpp @@ -0,0 +1,85 @@ +#include "gx/CGxStateBom.hpp" +#include + +const CGxStateBom& CGxStateBom::operator=(int32_t value) { + this->m_data.i[0] = value; + + return *this; +} + +const CGxStateBom& CGxStateBom::operator=(uint32_t value) { + this->m_data.i[0] = value; + + return *this; +} + +const CGxStateBom& CGxStateBom::operator=(float value) { + this->m_data.f[0] = value; + + return *this; +} + +const CGxStateBom& CGxStateBom::operator=(void* value) { + this->m_data.p = value; + + return *this; +} + +const CGxStateBom& CGxStateBom::operator=(C3Vector& value) { + this->m_data.f[0] = value.x; + this->m_data.f[1] = value.y; + this->m_data.f[2] = value.z; + + return *this; +} + +bool CGxStateBom::operator!=(int32_t value) { + return this->m_data.i[0] != value; +} + +bool CGxStateBom::operator!=(uint32_t value) { + return this->m_data.i[0] != value; +} + +bool CGxStateBom::operator!=(float value) { + return this->m_data.f[0] != value; +} + +bool CGxStateBom::operator!=(void* value) { + return this->m_data.p != value; +} + +bool CGxStateBom::operator!=(C3Vector& value) { + return this->m_data.f[0] != value.x + || this->m_data.f[1] != value.y + || this->m_data.f[2] != value.z; +} + +bool CGxStateBom::operator!=(CGxStateBom& value) { + return (this->m_data.i[0] - value.m_data.i[0]) + | (this->m_data.i[1] - value.m_data.i[1]) + | (this->m_data.i[2] - value.m_data.i[2]) + | (this->filler - value.filler); +} + +CGxStateBom::operator CImVector() const { + CImVector color; + color.value = this->m_data.i[0]; + return color; +} + +CGxStateBom::operator float() const { + return this->m_data.f[0]; +} + +CGxStateBom::operator int32_t() const { + return this->m_data.i[0]; +} + +CGxStateBom::operator uint32_t() const { + return this->m_data.i[0]; +} + +CGxStateBom::operator void*() const { + return this->m_data.p; +} diff --git a/src/gx/CGxStateBom.hpp b/src/gx/CGxStateBom.hpp new file mode 100644 index 0000000..bff7163 --- /dev/null +++ b/src/gx/CGxStateBom.hpp @@ -0,0 +1,39 @@ +#ifndef GX_C_GX_STATE_BOM_HPP +#define GX_C_GX_STATE_BOM_HPP + +#include + +class C3Vector; +class CImVector; + +class CGxStateBom { + public: + // Member variables + union { + int32_t i[3]; + float f[3]; + void* p; + } m_data; + + int32_t filler; + + // Member functions + const CGxStateBom& operator=(float); + const CGxStateBom& operator=(int32_t); + const CGxStateBom& operator=(uint32_t); + const CGxStateBom& operator=(void*); + const CGxStateBom& operator=(C3Vector&); + bool operator!=(float); + bool operator!=(int32_t); + bool operator!=(uint32_t); + bool operator!=(void*); + bool operator!=(C3Vector&); + bool operator!=(CGxStateBom&); + explicit operator CImVector() const; + explicit operator float() const; + explicit operator int32_t() const; + explicit operator uint32_t() const; + explicit operator void*() const; +}; + +#endif diff --git a/src/gx/CMakeLists.txt b/src/gx/CMakeLists.txt new file mode 100644 index 0000000..8e08f0c --- /dev/null +++ b/src/gx/CMakeLists.txt @@ -0,0 +1,43 @@ +file(GLOB GX_SOURCES + "*.cpp" + "buffer/*.cpp" + "font/*.cpp" + "shader/*.cpp" + "texture/*.cpp" +) + +if(WHOA_SYSTEM_MAC) + file(GLOB GLL_SOURCES "gll/*.cpp" "gll/*.mm") + set_source_files_properties(${GLL_SOURCES} + PROPERTIES COMPILE_FLAGS "-x objective-c++" + ) + list(APPEND GX_SOURCES ${GLL_SOURCES}) +endif() + +add_library(gx STATIC ${GX_SOURCES}) + +target_include_directories(gx + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(gx + PRIVATE + event + math + model + ui + util + PUBLIC + freetype-2.0 + storm + tempest +) + +if(WHOA_SYSTEM_MAC) + target_link_libraries(gx + PRIVATE + "-framework AppKit" + "-framework OpenGL" + ) +endif() diff --git a/src/gx/Camera.cpp b/src/gx/Camera.cpp new file mode 100644 index 0000000..9a0a2da --- /dev/null +++ b/src/gx/Camera.cpp @@ -0,0 +1,70 @@ +#include "gx/Camera.hpp" +#include "gx/CCamera.hpp" +#include "gx/Device.hpp" +#include "gx/Transform.hpp" +#include +#include + +HCAMERA CameraCreate() { + void* m = SMemAlloc(sizeof(CCamera), __FILE__, __LINE__, 0x0); + auto camera = new (m) CCamera(); + return HandleCreate(camera); +} + +void CameraSetupScreenProjection(const CRect& projectionRect, const C2Vector& screenPoint, float depth, int32_t a4) { + float offsetX = (projectionRect.minX + projectionRect.maxX) * 0.5f; + float offsetY = (projectionRect.minY + projectionRect.maxY) * 0.5f; + + CRect frustumRect = { + projectionRect.minY - offsetY, + projectionRect.minX - offsetX, + projectionRect.maxY - offsetY, + projectionRect.maxX - offsetX + }; + + float minZ = -500.0f; + float maxZ = 500.0f; + + C44Matrix proj; + + if (a4 || !g_theGxDevicePtr->StereoEnabled()) { + GxuXformCreateOrtho( + frustumRect.minX, + frustumRect.maxX, + frustumRect.minY, + frustumRect.maxY, + minZ, + maxZ, + proj + ); + + C3Vector v50 = { 1.0f, 1.0f, -1.0f }; + proj.Scale(v50); + } else { + GxuXformCreateOrthoDepth( + frustumRect.minX, + frustumRect.maxX, + frustumRect.minY, + frustumRect.maxY, + minZ, + maxZ, + proj + ); + } + + // TODO some caps stuff + + GxXformSetProjection(proj); + + C44Matrix view; + C3Vector move = { screenPoint.x - offsetX, screenPoint.y - offsetY, 0.0f }; + view.Translate(move); + + GxXformSetView(view); +} + +void CameraSetupWorldProjection(HCAMERA camera, const CRect& projectionRect, uint32_t flags) { + auto cameraPtr = reinterpret_cast(camera); + STORM_ASSERT(cameraPtr); + cameraPtr->SetupWorldProjection(projectionRect, flags); +} diff --git a/src/gx/Camera.hpp b/src/gx/Camera.hpp new file mode 100644 index 0000000..efc78c6 --- /dev/null +++ b/src/gx/Camera.hpp @@ -0,0 +1,18 @@ +#ifndef GX_CAMERA_HPP +#define GX_CAMERA_HPP + +#include +#include + +typedef HDATAMGR HCAMERA; + +class CRect; +class C2Vector; + +HCAMERA CameraCreate(void); + +void CameraSetupScreenProjection(const CRect&, const C2Vector&, float, int32_t); + +void CameraSetupWorldProjection(HCAMERA camera, const CRect& projectionRect, uint32_t flags); + +#endif diff --git a/src/gx/Coordinate.cpp b/src/gx/Coordinate.cpp new file mode 100644 index 0000000..87534fb --- /dev/null +++ b/src/gx/Coordinate.cpp @@ -0,0 +1,58 @@ +#include "gx/Coordinate.hpp" +#include + +namespace Coordinate { + float s_aspect = 1.333333f; + float s_aspectCompensation = 1.0f; + float s_y = 0.6f; + float s_x = 0.8f; +} + +float CoordinateGetAspectCompensation() { + return Coordinate::s_aspectCompensation; +} + +void CoordinateSetAspectRatio(float aspect) { + Coordinate::s_aspect = aspect; + Coordinate::s_aspectCompensation = aspect * 0.75; + + float v1 = 1.0 / sqrt(aspect * aspect + 1.0); + Coordinate::s_y = v1; + Coordinate::s_x = aspect * v1; +} + +void DDCToNDC(float ddcx, float ddcy, float* ndcx, float* ndcy) { + if (ndcx) { + *ndcx = ddcx / Coordinate::s_x; + } + + if (ndcy) { + *ndcy = ddcy / Coordinate::s_y; + } +} + +float DDCToNDCHeight(float ddcy) { + return ddcy / Coordinate::s_y; +} + +float DDCToNDCWidth(float ddcx) { + return ddcx / Coordinate::s_x; +} + +void NDCToDDC(float ndcx, float ndcy, float* ddcx, float* ddcy) { + if (ddcx) { + *ddcx = Coordinate::s_x * ndcx; + } + + if (ddcy) { + *ddcy = Coordinate::s_y * ndcy; + } +} + +float NDCToDDCHeight(float ndcy) { + return Coordinate::s_y * ndcy; +} + +float NDCToDDCWidth(float ndcx) { + return Coordinate::s_x * ndcx; +} diff --git a/src/gx/Coordinate.hpp b/src/gx/Coordinate.hpp new file mode 100644 index 0000000..a936770 --- /dev/null +++ b/src/gx/Coordinate.hpp @@ -0,0 +1,20 @@ +#ifndef GX_COORDINATE_HPP +#define GX_COORDINATE_HPP + +float CoordinateGetAspectCompensation(void); + +void CoordinateSetAspectRatio(float); + +void DDCToNDC(float, float, float*, float*); + +float DDCToNDCHeight(float); + +float DDCToNDCWidth(float); + +void NDCToDDC(float, float, float*, float*); + +float NDCToDDCHeight(float); + +float NDCToDDCWidth(float); + +#endif diff --git a/src/gx/Device.cpp b/src/gx/Device.cpp new file mode 100644 index 0000000..799eb41 --- /dev/null +++ b/src/gx/Device.cpp @@ -0,0 +1,51 @@ +#include "gx/Device.hpp" +#include "gx/CGxDevice.hpp" +#include "gx/Gx.hpp" + +CGxDevice* g_theGxDevicePtr = nullptr; + +CGxDevice* GxDevCreate(EGxApi api, long (*windowProc)(void*, uint32_t, uint32_t, long), const CGxFormat& format) { + CGxDevice* device; + + #if defined(WHOA_SYSTEM_WIN) + if (api == GxApi_OpenGl) { + device = CGxDevice::NewOpenGl(); + } else if (api == GxApi_D3d9) { + device = CGxDevice::NewD3d(); + } else if (api == GxApi_D3d9Ex) { + device = CGxDevice::NewD3d9Ex(); + } else { + // Error + } + #endif + + #if defined(WHOA_SYSTEM_MAC) + if (api == GxApi_OpenGl) { + device = CGxDevice::NewOpenGl(); + } else if (api == GxApi_GLL) { + device = CGxDevice::NewGLL(); + } else { + // Error + } + #endif + + g_theGxDevicePtr = device; + + if (g_theGxDevicePtr->DeviceCreate(windowProc, format)) { + return g_theGxDevicePtr; + } else { + if (g_theGxDevicePtr) { + delete g_theGxDevicePtr; + } + + return nullptr; + } +} + +EGxApi GxDevApi() { + return g_theGxDevicePtr->m_api; +} + +int32_t GxMasterEnable(EGxMasterEnables state) { + return g_theGxDevicePtr->MasterEnable(state); +} diff --git a/src/gx/Device.hpp b/src/gx/Device.hpp new file mode 100644 index 0000000..db2e9ab --- /dev/null +++ b/src/gx/Device.hpp @@ -0,0 +1,18 @@ +#ifndef GX_DEVICE_HPP +#define GX_DEVICE_HPP + +#include "gx/CGxDevice.hpp" +#include "gx/Types.hpp" +#include + +class CGxFormat; + +extern CGxDevice* g_theGxDevicePtr; + +CGxDevice* GxDevCreate(EGxApi, long (*)(void*, uint32_t, uint32_t, long), const CGxFormat&); + +EGxApi GxDevApi(void); + +int32_t GxMasterEnable(EGxMasterEnables state); + +#endif diff --git a/src/gx/Draw.cpp b/src/gx/Draw.cpp new file mode 100644 index 0000000..82e63bf --- /dev/null +++ b/src/gx/Draw.cpp @@ -0,0 +1,21 @@ +#include "gx/Draw.hpp" +#include "gx/Device.hpp" + +void GxDraw(CGxBatch* batches, int32_t count) { + g_theGxDevicePtr->Draw(batches, count); +} + +void GxSceneClear(uint32_t mask, CImVector color) { + g_theGxDevicePtr->SceneClear(mask, color); +} + +void GxScenePresent() { + C3Vector v2 = { 0.0f, 0.0f, 0.0f }; + GxuFlushDrawList(GxuCat_2, v2); + + g_theGxDevicePtr->ScenePresent(); +} + +void GxuFlushDrawList(EGxuDrawListCategory a1, const C3Vector& a2) { + // TODO +} diff --git a/src/gx/Draw.hpp b/src/gx/Draw.hpp new file mode 100644 index 0000000..040ab84 --- /dev/null +++ b/src/gx/Draw.hpp @@ -0,0 +1,19 @@ +#ifndef GX_DRAW_HPP +#define GX_DRAW_HPP + +#include "gx/CGxBatch.hpp" +#include "gx/Types.hpp" +#include + +class C3Vector; +class CImVector; + +void GxDraw(CGxBatch*, int32_t); + +void GxSceneClear(uint32_t, CImVector); + +void GxScenePresent(void); + +void GxuFlushDrawList(EGxuDrawListCategory, const C3Vector&); + +#endif diff --git a/src/gx/Font.cpp b/src/gx/Font.cpp new file mode 100644 index 0000000..952f31f --- /dev/null +++ b/src/gx/Font.cpp @@ -0,0 +1,996 @@ +#include "gx/Font.hpp" +#include "gx/font/CGxFont.hpp" +#include "gx/font/CGxString.hpp" +#include "gx/font/CGxStringBatch.hpp" +#include "gx/font/Wrap.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Device.hpp" +#include "gx/FontInternal.hpp" +#include "gx/Gx.hpp" +#include "gx/Shader.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +CGxShader* g_fontPixelShader[1]; +CGxShader* g_fontVertexShader[2]; +TSList> g_fonts; +TSHashTable s_fontHash; +uint32_t g_heightPixels; +uint32_t g_widthPixels; +float g_indentPixelWidth; +float g_indentNormWidth; +TSList> s_unusedBatches; +STORM_LIST(CGxString) g_freeStrings; +STORM_LIST(CGxString) g_strings; + +FONTHASHOBJ::~FONTHASHOBJ() { + if (this->font) { + GxuFontDestroyFont(this->font); + } +} + +TEXTBLOCK::~TEXTBLOCK() { + GxuFontDestroyString(this->string); +} + +void CalculateYOffset(uint32_t pixelHeight, uint32_t a2, FT_Face face, uint32_t glyphHeight, int32_t* yOffset, int32_t* yStart) { + uint32_t v6 = 0; + int32_t v8 = 0; + + if (glyphHeight <= pixelHeight) { + uint32_t v9 = face->glyph->bitmap_top; + + if (v9 <= a2) { + v8 = a2 - v9; + } else { + v8 = 0; + v6 = v9 - a2; + } + } + + uint32_t v10 = v8 <= 0 ? 0 : v8; + uint32_t v11 = pixelHeight - glyphHeight; + + if (v11 >= v10) { + *yOffset = v6; + *yStart = v10; + } else { + *yOffset = pixelHeight - v10 - glyphHeight; + *yStart = v11; + } +} + +uint32_t ConvertStringFlags(uint32_t flags) { + uint32_t convertedFlags = 0x0; + + if (flags & 0x100) { + convertedFlags |= 0x1; + } + + if (flags & 0x200) { + convertedFlags |= 0x4; + } + + if (flags & 0x400) { + convertedFlags |= 0x8; + } + + if (flags & 0x800) { + convertedFlags |= 0x10; + } + + if (flags & 0x40) { + convertedFlags |= 0x2; + } + + if (flags & 0x1000) { + convertedFlags |= 0x40; + } + + if (flags & 0x2000) { + convertedFlags |= 0x100; + } + + if (flags & 0x4000) { + convertedFlags |= 0x200; + } + + if (flags & 0x8000) { + convertedFlags |= 0x400; + } + + if (flags & 0x80) { + convertedFlags |= 0x800; + } + + if (flags & 0x10000) { + convertedFlags |= 0x1000; + } + + if (flags & 0x20000) { + convertedFlags |= 0x2000; + } + + return convertedFlags; +} + +float GetCharacterWidth(const char* text, uint32_t flags, uint32_t prevCode, CGxFont* font, float a5) { + if (!prevCode) { + return 0.0f; + } + + float width = 0.0f; + + while (*text) { + int32_t advance; + uint32_t code; + QUOTEDCODE quotedCode = GxuDetermineQuotedCode(text, advance, nullptr, flags, code); + + text += advance; + + if (quotedCode == CODE_INVALIDCODE) { + if (flags & 0x10) { + // TODO + // width = font->ComputeStepFixedWidth(prevCode, code); + } else { + width = font->ComputeStep(prevCode, code); + } + + break; + } + + if (quotedCode == CODE_NEWLINE) { + break; + } + } + + if (width == 0.0f) { + auto glyph = font->NewCodeDesc(prevCode); + + if (glyph) { + width = font->GetGlyphBearing(glyph, flags & 0x80, a5) + glyph->bitmapData.m_glyphCellWidth; + } + } + + return width; +} + +float GetIndentNormWidth() { + return g_indentNormWidth; +} + +float GetIndentPixelWidth() { + return g_indentPixelWidth; +} + +uint32_t GetScreenPixelHeight() { + return g_heightPixels; +} + +uint32_t GetScreenPixelWidth() { + return g_widthPixels; +} + +QUOTEDCODE GxuDetermineQuotedCode(const char* text, int32_t& advance, CImVector* color, uint32_t flags, uint32_t& wide) { + wide = SUniSGetUTF8(reinterpret_cast(text), &advance); + + switch (wide) { + case 0x0: + case 0xFFFFFFFF: + return CODE_INVALIDCODE; + + case '\r': + advance = 2 - (SUniSGetUTF8(reinterpret_cast(text + 1), &advance) != '\n'); + return CODE_NEWLINE; + + case '\n': + advance = 1; + return CODE_NEWLINE; + } + + if (wide != '|' || flags & 0x800) { + return CODE_INVALIDCODE; + } + + auto quotedCode = text[advance]; + + if (!quotedCode) { + return CODE_INVALIDCODE; + } + + switch (quotedCode) { + case 'N': + case 'n': { + if (flags & 0x200) { + return CODE_INVALIDCODE; + } + + advance = 2; + return CODE_NEWLINE; + } + + // TODO handle other control codes + } + + // TODO remainder of function + + return CODE_INVALIDCODE; +} + +int32_t GxuFontAddToBatch(CGxStringBatch* batch, CGxString* string) { + if (batch && string) { + batch->AddString(string); + return 1; + } else { + return 0; + } +} + +void GxuFontAddShadow(CGxString* string, const CImVector& color, const C2Vector& offset) { + if (string) { + if (!(string->m_flags & 0x80)) { + string->AddShadow(offset, color); + } + } +} + +CGxStringBatch* GxuFontCreateBatch(bool a1, bool a2) { + CGxStringBatch* batch; + + if (s_unusedBatches.Head()) { + batch = s_unusedBatches.Head(); + s_unusedBatches.UnlinkNode(batch); + } else { + void* m = SMemAlloc(sizeof(CGxStringBatch), __FILE__, __LINE__, 0x8); + batch = new (m) CGxStringBatch(); + } + + if (a1) { + batch->m_flags |= 0x1; + } else { + batch->m_flags &= ~0x1; + } + + if (a2) { + batch->m_flags |= 0x2; + } else { + batch->m_flags &= ~0x2; + } + + return batch; +} + +int32_t GxuFontCreateFont(const char* name, float fontHeight, CGxFont*& face, uint32_t flags) { + STORM_ASSERT(name); + STORM_ASSERT(*name); + STORM_ASSERT((fontHeight <= 1.0f) && (fontHeight > 0.0f)); + + CGxFont* newFace = g_fonts.NewNode(2, 0, 0); + + uint32_t v12 = flags; + + if (flags & 0x8) { + v12 |= 0x1; + } + + int32_t v13 = newFace->Initialize(name, v12, fontHeight); + + if (!v13) { + g_fonts.DeleteNode(newFace); + newFace = nullptr; + } + + face = newFace; + + return v13; +} + +int32_t GxuFontCreateString(CGxFont* face, const char* text, float fontHeight, const C3Vector& position, float blockWidth, float blockHeight, float spacing, CGxString*& string, EGxFontVJusts vertJustification, EGxFontHJusts horzJustification, uint32_t flags, const CImVector& color, float charSpacing, float scale) { + STORM_ASSERT(face); + STORM_ASSERT(text); + // TODO + // STORM_ASSERT(fontHeight || (flags & EGxStringFlags_FixedSize)); + STORM_ASSERT(vertJustification < GxVJ_Last); + STORM_ASSERT(horzJustification < GxHJ_Last); + + auto newString = CGxString::GetNewString(1); + + int32_t result = newString->Initialize( + fontHeight, + position, + blockWidth, + blockHeight, + face, + text, + vertJustification, + horzJustification, + spacing, + flags & ~0x1, + color, + scale + ); + + if (result) { + string = newString; + } else { + GxuFontDestroyString(newString); + } + + return result; +} + +int32_t GxuFontDestroyBatch(CGxStringBatch* batch) { + if (!batch) { + return 0; + } + + batch->m_fontBatch.Clear(); + + s_unusedBatches.LinkToTail(batch); + + return 1; +} + +void GxuFontDestroyFont(CGxFont*& font) { + if (font) { + g_fonts.DeleteNode(font); + } + + font = nullptr; +} + +void GxuFontDestroyString(CGxString*& string) { + if (string) { + string->Unlink(); + string->Recycle(); + string = nullptr; + } +} + +uint32_t GxuFontGetFontFlags(CGxFont* font) { + if (font) { + return font->m_flags; + } + + return 0; +} + +const char* GxuFontGetFontName(CGxFont* font) { + return font + ? font->GetName() + : nullptr; +} + +uint32_t GxuFontGetMaxCharsWithinWidth(CGxFont* font, const char* text, float height, float maxWidth, uint32_t lineBytes, float* extent, float a7, float scale, float a9, uint32_t flags) { + return InternalGetMaxCharsWithinWidth( + font, + text, + height, + maxWidth, + lineBytes, + extent, + flags, + a7, + scale, + nullptr, + nullptr, + nullptr + ); +} + +uint32_t GxuFontGetMaxCharsWithinWidthAndHeight(CGxFont* font, const char* text, float fontHeight, float maxWidth, float maxHeight, uint32_t lineBytes, float a7, float scale, float a9, uint32_t flags) { + if (!font) { + return 0; + } + + if (!text || !*text) { + return 0; + } + + if (fontHeight == 0.0f || maxWidth == 0.0f || maxHeight == 0.0f || g_heightPixels == 0.0f) { + return 0; + } + + if (lineBytes == 0) { + return 0; + } + + if (flags & 0x4) { + fontHeight = GxuFontGetOneToOneHeight(font); + } + + auto v24 = static_cast(g_heightPixels) * a9; + auto v22 = CMath::fuint_pi(v24); + auto v12 = static_cast(v22) / static_cast(g_heightPixels); + bool v26 = true; + auto v27 = v12; + v24 = 0.0f; + auto v13 = fontHeight; + const char* nextText; + + uint32_t maxChars = 0; + + auto currentText = text; + int32_t v25 = 0; + while (*currentText) { + if (flags & 0x2000 && v25 == 1) { + maxWidth -= g_indentNormWidth; + } + + float v21; + CalcWrapPoint( + font, + currentText, + fontHeight, + maxWidth, + &v22, + &v21, + &nextText, + a7, + flags, + &v26, + 0, + scale + ); + + v22 = nextText - currentText; + if (nextText == currentText) { + break; + } + + auto v15 = v24; + auto v16 = v25 + 1 < 0; + auto v17 = v24 + fontHeight; + + v25++; + + float v18 = v25; + + if (v16) { + v18 = v18 + 4294967300.0; + } + + if (v18 * 0.00000095367431640625 + maxHeight < v17) { + break; + } + + maxChars += nextText - currentText; + currentText = nextText; + v24 = fontHeight + v15 + v27; + + if (!v26 || !nextText) { + break; + } + } + + return maxChars; +} + +float GxuFontGetOneToOneHeight(CGxFont* font) { + STORM_ASSERT(font); + + return PixelToScreenHeight(font->GetPixelSize()); +} + +void GxuFontGetTextExtent(CGxFont* font, const char* text, uint32_t numBytes, float a4, float* extent, float a6, float a7, float a8, uint32_t flags) { + InternalGetTextExtent(font, text, numBytes, a4, extent, flags, a6, a7); +} + +float GxuFontGetWrappedTextHeight(CGxFont* font, const char* text, float a3, float a4, const C2Vector& a5, float a6, float a7, uint32_t flags) { + STORM_ASSERT(font); + STORM_ASSERT(text); + + if (flags & 0x04) { + a3 = GxuFontGetOneToOneHeight(font); + } + + int32_t advance; + uint32_t numBytes; + uint32_t code; + int32_t v8 = 0; + float extent = 0.0f; + float v17 = 0.0f; + float v18 = 0.0f; + const char* currentText = text; + const char* nextText = nullptr; + bool v21 = true; + + while (currentText && *currentText) { + QUOTEDCODE quotedCode = GxuDetermineQuotedCode(currentText, advance, nullptr, flags, code); + + if (flags & 0x2000 && v8 == 1) { + a4 = a4 - g_indentNormWidth; + } + + if (quotedCode == CODE_NEWLINE) { + nextText = currentText + advance; + } else { + CalcWrapPoint( + font, + currentText, + a3, + a4, + &numBytes, + &extent, + &nextText, + a5.x, + flags, + &v21, + &v17, + a6 + ); + + if (currentText == nextText) { + break; + } + } + + v8++; + currentText = nextText; + + float v14 = (float)GetScreenPixelHeight() * a3; + if (v14 < v17) { + v18 = v17 - v14 + v18; + } + + if (flags & 0x02) { + break; + } + } + + if (!(flags & 0x02) && currentText > text && GxuDetermineQuotedCode(currentText - 1, advance, nullptr, flags, code) == CODE_NEWLINE) { + v8++; + } + + if (!v8) { + return 0.0f; + } + + float v22 = GetScreenPixelHeight(); + float v14 = v22 * a7; + float v13 = v14 + 0.99994999f; + float v23 = v13 / v22; + + return (v18 / v22 + v23 * (float)(v8 - 1) + (float)v8 * a3); +} + +void GxuFontInitialize() { + g_theGxDevicePtr->ShaderCreate(g_fontVertexShader, GxSh_Vertex, "Shaders\\Vertex", "UI", 2); + g_theGxDevicePtr->ShaderCreate(g_fontPixelShader, GxSh_Pixel, "Shaders\\Pixel", "UI", 1); + + BATCHEDRENDERFONTDESC::Initialize(); + + FreeTypeInitialize(); + + GxuFontWindowSizeChanged(); + + // TODO + // sub_6BD160(); +} + +void GxuFontRenderBatch(CGxStringBatch* batch) { + if (batch) { + batch->RenderBatch(); + } +} + +int32_t GxuFontSetStringColor(CGxString* string, const CImVector& color) { + STORM_ASSERT(string); + + string->SetColor(color); + + return 1; +} + +void GxuFontSetStringPosition(CGxString* string, const C3Vector& position) { + STORM_ASSERT(string); + + string->SetStringPosition(position); +} + +void GxuFontUpdate() { + for (auto string = g_strings.Head(); string; string = g_strings.Link(string)->Next()) { + string->Tick(); + } + + // TODO +} + +void GxuFontWindowSizeChanged() { + static CRect s_currentRect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + CRect rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + GxCapsWindowSize(rect); + + if (rect.maxY - rect.minY == 0.0f || rect.maxX - rect.minX == 0.0f) { + rect = { 0.0f, 0.0f, 480.0f, 640.0f }; + } + + if (s_currentRect == rect) { + return; + } + + s_currentRect = rect; + + g_widthPixels = rect.maxX - rect.minX; + g_heightPixels = rect.maxY - rect.minY; + g_indentPixelWidth = 15.0f; + g_indentNormWidth = 15.0f / (rect.maxX - rect.minX); + + // TODO + // - walk s_fonts and trigger HandleScreenSizeChange +} + +int32_t IGxuFontGlyphRenderGlyph(FT_Face face, uint32_t pixelHeight, uint32_t code, uint32_t baseline, GLYPHBITMAPDATA* dataPtr, int32_t monochrome, uint32_t a7) { + STORM_ASSERT(face); + STORM_ASSERT(pixelHeight); + STORM_ASSERT(dataPtr); + + if (!FT_Get_Char_Index(face, code)) { + return 0; + } + + if (!FREETYPE_RenderGlyph(code, monochrome != 0, face)) { + return 0; + } + + auto glyph = face->glyph; + + uint32_t width = glyph->bitmap.width; + uint32_t height = std::min(static_cast(glyph->bitmap.rows), pixelHeight); + size_t dataSize = glyph->bitmap.pitch * glyph->bitmap.rows; + auto srcData = glyph->bitmap.buffer; + uint32_t pitch = glyph->bitmap.pitch; + + int32_t dummyGlyph = 0; + + if (!width || !height || !srcData || !pitch || !dataSize) { + width = (pixelHeight + 3) >> 2; + height = pixelHeight; + + if (!width) { + width = pixelHeight; + } + + if (monochrome) { + pitch = (width + 7) & 0xFFFFFFF8; + } else { + pitch = width; + } + + dataSize = pixelHeight * pitch; + dummyGlyph = 1; + } + + void* data = SMemAlloc(dataSize, __FILE__, __LINE__, 0x0); + + if (data) { + memset(data, 0, dataSize); + } + + if (srcData) { + memcpy(data, srcData, dataSize); + } + + dataPtr->m_data = data; + dataPtr->m_dataSize = dataSize; + dataPtr->m_glyphPitch = pitch; + dataPtr->m_glyphWidth = width; + dataPtr->m_glyphHeight = height; + dataPtr->m_glyphCellWidth = width + a7; + dataPtr->m_glyphAdvance = (double)(face->glyph->metrics.horiAdvance / 64) + 1.0; + dataPtr->m_glyphBearing = (double)face->glyph->metrics.horiBearingX * 0.015625; + dataPtr->m_yOffset = 0; + dataPtr->m_yStart = 0; + + if (width && height && data && !dummyGlyph) { + CalculateYOffset(pixelHeight, baseline, face, height, &dataPtr->m_yOffset, &dataPtr->m_yStart); + } + + return 1; +} + +float PixelToScreenHeight(int32_t height) { + return (double)height / (double)g_heightPixels; +} + +float PixelToScreenWidth(int32_t width) { + return (double)width / (double)g_widthPixels; +} + +float PixelToScreenWidth(float width) { + return width / (double)g_widthPixels; +} + +float ScreenToPixelHeight(int32_t billboarded, float height) { + if (billboarded) { + return height; + } + + float pixelCoords = static_cast(g_heightPixels) * height; + return CMath::fint_n(pixelCoords); +} + +float ScreenToPixelWidth(int32_t billboarded, float width) { + if (billboarded) { + return width; + } + + float pixelCoords = static_cast(g_widthPixels) * width; + return CMath::fint_n(pixelCoords); +} + +float Sub6C2280(FT_Face face, float height) { + int32_t numFixedSizes = face->num_fixed_sizes; + + if (numFixedSizes == 0) { + return height; + } + + int32_t v4 = INT_MAX; + + auto availableSizes = face->available_sizes; + + for (int32_t i = 0; i < numFixedSizes; i++) { + if (v4 >= availableSizes->height) { + v4 = availableSizes->height; + } + + availableSizes++; + } + + float v7 = PixelToScreenHeight(v4); + + return v7 >= height ? v7 : height; +} + +void TextBlockAddShadow(HTEXTBLOCK string, CImVector color, const C2Vector& offset) { + STORM_ASSERT(string); + + C2Vector ndcOffset; + DDCToNDC(offset.x, offset.y, &ndcOffset.x, &ndcOffset.y); + + GxuFontAddShadow(TextBlockGetStringPtr(string), color, offset); +} + +HTEXTBLOCK TextBlockCreate(HTEXTFONT font, const char* text, const CImVector& color, const C3Vector& pos, float fontHeight, float blockWidth, float blockHeight, uint32_t flags, float charSpacing, float lineSpacing, float scale) { + STORM_ASSERT(font); + STORM_ASSERT(text); + + void* m = SMemAlloc(sizeof(TEXTBLOCK), __FILE__, __LINE__, 0x0); + auto textBlock = new (m) TEXTBLOCK(); + + C3Vector position = { 0.0f, 0.0f, pos.z }; + DDCToNDC(pos.x, pos.y, &position.x, &position.y); + + EGxFontHJusts hjust = GxHJ_Center; + + if (flags & 0x4) { + hjust = GxHJ_Right; + } else if (!(flags & 0x2) && flags & 0x1) { + hjust = GxHJ_Left; + } + + EGxFontVJusts vjust = GxVJ_Middle; + + if (flags & 0x8) { + vjust = GxVJ_Top; + } else if (flags & 0x20) { + vjust = GxVJ_Bottom; + } + + uint32_t v16 = ConvertStringFlags(flags); + + float v15 = DDCToNDCWidth(charSpacing); + float v20 = DDCToNDCHeight(lineSpacing); + float v21 = DDCToNDCHeight(blockHeight); + float v22 = DDCToNDCWidth(blockWidth); + float v23 = DDCToNDCHeight(fontHeight); + + GxuFontCreateString( + reinterpret_cast(font)->font, + text, + v23, + position, + v22, + v21, + v20, + textBlock->string, + vjust, + hjust, + v16, + color, + v15, + scale + ); + + return HandleCreate(textBlock); +} + +HTEXTFONT TextBlockGenerateFont(const char* fontName, uint32_t fontFlags, float fontHeight) { + STORM_ASSERT(fontName); + STORM_ASSERT(*fontName); + + float fontHeightNDC = DDCToNDCHeight(fontHeight); + + if (fontHeightNDC >= 1.0) { + fontHeightNDC = 1.0; + } + + char hashKey[276]; + + SStrPrintf(hashKey, 276, "%s-%d-%f", fontName, fontFlags, fontHeightNDC); + + auto v7 = s_fontHash.Ptr(hashKey); + + if (v7) { + return HandleCreate(v7); + } + + v7 = s_fontHash.New(hashKey, 0, 0); + + uint32_t v14 = 0; + + if (fontFlags & FONT_OUTLINE) { + v14 |= 0x1; + } + + if (fontFlags & FONT_THICKOUTLINE) { + v14 |= 0x8; + } + + if (fontFlags & FONT_MONOCHROME) { + v14 |= 0x2; + } + + if (GxuFontCreateFont(fontName, fontHeightNDC, v7->font, v14)) { + return HandleCreate(v7); + } else { + // TODO + // sub_723270(v12); + // (s_fontHash)(&s_fontHash, v12); + + return nullptr; + } +} + +uint32_t TextBlockGetFontFlags(HTEXTFONT fontHandle) { + STORM_ASSERT(fontHandle); + + uint32_t gxFlags = GxuFontGetFontFlags(TextBlockGetFontPtr(fontHandle)); + uint32_t flags = 0; + + if (gxFlags & 0x1) { + flags |= 0x1; + } + + if (gxFlags & 0x2) { + flags |= 0x2; + } + + if (gxFlags & 0x8) { + flags |= 0x4; + } + + return flags; +} + +const char* TextBlockGetFontName(HTEXTFONT fontHandle) { + STORM_ASSERT(fontHandle); + + return GxuFontGetFontName(reinterpret_cast(fontHandle)->font); +} + +CGxFont* TextBlockGetFontPtr(HTEXTFONT fontHandle) { + STORM_ASSERT(fontHandle); + + return reinterpret_cast(fontHandle)->font; +} + +uint32_t TextBlockGetMaxCharsWithinWidth(HTEXTFONT fontHandle, const char* text, float height, float maxWidth, uint32_t lineBytes, float* extent, float a7, float scale, float a9, uint32_t flags) { + STORM_ASSERT(fontHandle); + STORM_ASSERT(text); + + return GxuFontGetMaxCharsWithinWidth( + TextBlockGetFontPtr(fontHandle), + text, + DDCToNDCHeight(height), + DDCToNDCWidth(maxWidth), + lineBytes, + extent, + DDCToNDCWidth(a7), + scale, + DDCToNDCWidth(a9), + ConvertStringFlags(flags) + ); +} + +uint32_t TextBlockGetMaxCharsWithinWidthAndHeight(HTEXTFONT fontHandle, const char* text, float height, float maxWidth, float maxHeight, uint32_t lineBytes, float a7, float scale, float a9, uint32_t flags) { + STORM_ASSERT(fontHandle); + STORM_ASSERT(text); + + return GxuFontGetMaxCharsWithinWidthAndHeight( + TextBlockGetFontPtr(fontHandle), + text, + DDCToNDCHeight(height), + DDCToNDCWidth(maxWidth), + DDCToNDCHeight(maxHeight), + lineBytes, + DDCToNDCWidth(a7), + scale, + DDCToNDCWidth(a9), + ConvertStringFlags(flags) + ); +} + +CGxString* TextBlockGetStringPtr(HTEXTBLOCK stringHandle) { + STORM_ASSERT(stringHandle); + + return reinterpret_cast(stringHandle)->string; +} + +void TextBlockGetTextExtent(HTEXTFONT fontHandle, const char* text, uint32_t numChars, float fontHeight, float* extent, float a6, float scale, float a8, uint32_t flags) { + STORM_ASSERT(fontHandle); + STORM_ASSERT(text); + STORM_ASSERT(extent); + + *extent = 0.0f; + + GxuFontGetTextExtent( + TextBlockGetFontPtr(fontHandle), + text, + numChars, + DDCToNDCHeight(fontHeight), + extent, + DDCToNDCWidth(a6), + scale, + DDCToNDCWidth(a8), + ConvertStringFlags(flags) + ); + + NDCToDDC(*extent, 0.0f, extent, nullptr); +} + +float TextBlockGetWrappedTextHeight(HTEXTFONT fontHandle, const char* text, float a3, float a4, const C2Vector& a5, float a6, float a7, uint32_t flags) { + STORM_ASSERT(fontHandle); + STORM_ASSERT(text); + + float shadowWidth; + float shadowHeight; + DDCToNDC(a5.x, a5.y, &shadowWidth, &shadowHeight); + C2Vector shadowSize = { shadowWidth, shadowHeight }; + + float height = GxuFontGetWrappedTextHeight( + TextBlockGetFontPtr(fontHandle), + text, + DDCToNDCHeight(a3), + DDCToNDCWidth(a4), + shadowSize, + a6, + DDCToNDCHeight(a7), + ConvertStringFlags(flags) + ); + + return NDCToDDCHeight(height); +} + +void TextBlockSetStringPos(HTEXTBLOCK stringHandle, const C3Vector& pos) { + STORM_ASSERT(stringHandle); + + C3Vector ndcPos = { 0.0f, 0.0f, pos.z }; + DDCToNDC(pos.x, pos.y, &ndcPos.x, &ndcPos.y); + GxuFontSetStringPosition(TextBlockGetStringPtr(stringHandle), ndcPos); +} + +void TextBlockUpdateColor(HTEXTBLOCK stringHandle, const CImVector& color) { + STORM_ASSERT(stringHandle); + + GxuFontSetStringColor(TextBlockGetStringPtr(stringHandle), color); +} diff --git a/src/gx/Font.hpp b/src/gx/Font.hpp new file mode 100644 index 0000000..7a6acee --- /dev/null +++ b/src/gx/Font.hpp @@ -0,0 +1,140 @@ +#ifndef GX_FONT_HPP +#define GX_FONT_HPP + +#include "gx/font/FreeType.hpp" +#include "gx/font/Types.hpp" +#include "gx/Types.hpp" +#include +#include +#include + +#define FONT_OUTLINE 0x1 +#define FONT_MONOCHROME 0x2 +#define FONT_THICKOUTLINE 0x4 + +class C2Vector; +class C3Vector; +class CGxFont; +class CGxShader; +class CGxString; +class CGxStringBatch; +class CImVector; +struct GLYPHBITMAPDATA; + +class FONTHASHOBJ : public CHandleObject, public TSHashObject { + public: + CGxFont* font = nullptr; + virtual ~FONTHASHOBJ(); +}; + +class TEXTBLOCK : public CHandleObject { + public: + CGxString* string = nullptr; + virtual ~TEXTBLOCK(); +}; + +extern CGxShader* g_fontPixelShader[1]; + +extern CGxShader* g_fontVertexShader[2]; + +extern STORM_LIST(CGxString) g_freeStrings; + +extern STORM_LIST(CGxString) g_strings; + +void CalculateYOffset(uint32_t, uint32_t, FT_Face, uint32_t, int32_t*, int32_t*); + +float GetCharacterWidth(const char*, uint32_t, uint32_t, CGxFont*, float); + +float GetIndentNormWidth(); + +float GetIndentPixelWidth(void); + +uint32_t GetScreenPixelHeight(void); + +uint32_t GetScreenPixelWidth(void); + +QUOTEDCODE GxuDetermineQuotedCode(const char*, int32_t&, CImVector*, uint32_t, uint32_t&); + +int32_t GxuFontAddToBatch(CGxStringBatch*, CGxString*); + +void GxuFontAddShadow(CGxString* string, const CImVector& color, const C2Vector& offset); + +CGxStringBatch* GxuFontCreateBatch(bool, bool); + +int32_t GxuFontCreateFont(const char*, float, CGxFont*&, uint32_t); + +int32_t GxuFontCreateString(CGxFont*, const char*, float, const C3Vector&, float, float, float, CGxString*&, EGxFontVJusts, EGxFontHJusts, uint32_t, const CImVector&, float, float); + +int32_t GxuFontDestroyBatch(CGxStringBatch*); + +void GxuFontDestroyFont(CGxFont*& font); + +void GxuFontDestroyString(CGxString*&); + +uint32_t GxuFontGetFontFlags(CGxFont*); + +const char* GxuFontGetFontName(CGxFont*); + +uint32_t GxuFontGetMaxCharsWithinWidth(CGxFont*, const char*, float, float, uint32_t, float*, float, float, float, uint32_t); + +uint32_t GxuFontGetMaxCharsWithinWidthAndHeight(CGxFont*, const char*, float, float, float, uint32_t, float, float, float, uint32_t); + +float GxuFontGetOneToOneHeight(CGxFont*); + +void GxuFontGetTextExtent(CGxFont*, const char*, uint32_t, float, float*, float, float, float, uint32_t); + +float GxuFontGetWrappedTextHeight(CGxFont*, const char*, float, float, const C2Vector&, float, float, uint32_t); + +void GxuFontInitialize(void); + +void GxuFontRenderBatch(CGxStringBatch*); + +int32_t GxuFontSetStringColor(CGxString*, const CImVector&); + +void GxuFontSetStringPosition(CGxString* string, const C3Vector& position); + +void GxuFontUpdate(); + +void GxuFontWindowSizeChanged(void); + +int32_t IGxuFontGlyphRenderGlyph(FT_Face, uint32_t, uint32_t, uint32_t, GLYPHBITMAPDATA*, int32_t, uint32_t); + +void TextBlockAddShadow(HTEXTBLOCK, CImVector, const C2Vector&); + +HTEXTBLOCK TextBlockCreate(HTEXTFONT, const char*, const CImVector&, const C3Vector&, float, float, float, uint32_t, float, float, float); + +HTEXTFONT TextBlockGenerateFont(const char*, uint32_t, float); + +uint32_t TextBlockGetFontFlags(HTEXTFONT); + +const char* TextBlockGetFontName(HTEXTFONT); + +CGxFont* TextBlockGetFontPtr(HTEXTFONT); + +uint32_t TextBlockGetMaxCharsWithinWidth(HTEXTFONT, const char*, float, float, uint32_t, float*, float, float, float, uint32_t); + +uint32_t TextBlockGetMaxCharsWithinWidthAndHeight(HTEXTFONT, const char*, float, float, float, uint32_t, float, float, float, uint32_t); + +CGxString* TextBlockGetStringPtr(HTEXTBLOCK); + +void TextBlockGetTextExtent(HTEXTFONT, const char*, uint32_t, float, float*, float, float, float, uint32_t); + +float TextBlockGetWrappedTextHeight(HTEXTFONT, const char*, float, float, const C2Vector&, float, float, uint32_t); + +void TextBlockSetStringPos(HTEXTBLOCK stringHandle, const C3Vector& pos); + +void TextBlockUpdateColor(HTEXTBLOCK, const CImVector&); + +float PixelToScreenHeight(int32_t); + +float PixelToScreenWidth(int32_t); + +float PixelToScreenWidth(float); + +float ScreenToPixelHeight(int32_t, float); + +float ScreenToPixelWidth(int32_t, float); + +float Sub6C2280(FT_Face, float); + +#endif diff --git a/src/gx/FontInternal.cpp b/src/gx/FontInternal.cpp new file mode 100644 index 0000000..6979fbe --- /dev/null +++ b/src/gx/FontInternal.cpp @@ -0,0 +1,200 @@ +#include "gx/FontInternal.hpp" +#include "gx/Font.hpp" +#include "gx/font/CGxFont.hpp" +#include +#include +#include + +uint32_t InternalGetMaxCharsWithinWidth(CGxFont* face, const char* text, float height, float maxWidth, uint32_t lineBytes, float* extent, uint32_t flags, float a8, float scale, uint32_t* bytesInString, float* widthArray, float* widthArrayGuard) { + STORM_ASSERT(face); + STORM_ASSERT(text); + STORM_ASSERT(extent); + + uint32_t numChars = 0; + int32_t billboarded = (flags & 0x80) != 0; + + if (!billboarded && (height == 0.0f || flags & 0x4)) { + height = GxuFontGetOneToOneHeight(face); + } + + float width = 0.0f; + + if (a8 > 0.0f) { + width += ceil(ScreenToPixelWidth(0, a8)); + } + + if (face->m_flags & 0x8) { + width += 4.0f; + } else if (face->m_flags & 0x1) { + width += 2.0f; + } + + float pixelSize = face->GetPixelSize(); + float pixelHeight = ScreenToPixelHeight(billboarded, height); + float pixelScale = pixelHeight / pixelSize; + float pixelMaxWidth = CMath::fuint_pi((pixelSize * GetScreenPixelWidth() * maxWidth) / std::max(pixelHeight, 1.0f)); + + const char* originalText = text; + float lastWidth = 0.0f; + int32_t advance; + uint32_t code; + uint32_t prevCode = 0; + + while (*text && lineBytes) { + QUOTEDCODE quotedCode = GxuDetermineQuotedCode(text, advance, nullptr, flags, code); + + text += advance; + lineBytes -= advance; + + switch (quotedCode) { + case CODE_COLORON: + case CODE_COLORRESTORE: + case CODE_HYPERLINKSTART: + case CODE_HYPERLINKSTOP: + case CODE_TEXTURESTOP: + break; + + case CODE_NEWLINE: { + goto DONE; + } + + case CODE_TEXTURESTART: { + // TODO + } + + default: { + if (face->NewCodeDesc(code)) { + float stepWidth = 0.0f; + + if (prevCode) { + stepWidth = flags & 0x10 + ? face->ComputeStepFixedWidth(prevCode, code) + : face->ComputeStep(prevCode, code); + } + + float characterWidth = GetCharacterWidth(text, flags, code, face, height); + + if (pixelMaxWidth < width + stepWidth + characterWidth) { + text -= advance; + goto DONE; + } + + width += stepWidth; + numChars++; + + prevCode = code; + lastWidth = characterWidth; + + if (widthArray) { + float v34 = (lastWidth + width) * pixelScale; + + if (billboarded) { + *widthArray = v34; + } else { + *widthArray = v34 / GetScreenPixelWidth(); + } + + widthArray++; + } + } else { + numChars++; + } + } + } + } + + DONE: + + float v26 = (lastWidth + width) * pixelScale; + + if (billboarded) { + *extent = v26; + } else { + *extent = v26 / GetScreenPixelWidth(); + } + + if (bytesInString) { + *bytesInString = text - originalText; + } + + return numChars; +} + +void InternalGetTextExtent(CGxFont* font, const char* text, uint32_t numBytes, float height, float* extent, uint32_t flags, float a7, float a8) { + STORM_ASSERT(font); + STORM_ASSERT(text); + STORM_ASSERT(extent); + + int32_t billboarded = (flags & 0x80) != 0; + + if (height == 0.0f || flags & 0x4) { + height = GxuFontGetOneToOneHeight(font); + } + + float width = 0.0f; + float lastWidth = 0.0f; + float maxWidth = 0.0f; + + if (a7 > 0.0f) { + width = ceil(ScreenToPixelWidth(0, a7)); + } + + if (font->m_flags & 0x8) { + width += 4.0f; + } else if (font->m_flags & 0x1) { + width += 2.0f; + } + + uint32_t prevCode = 0; + + while (numBytes && *text) { + int32_t advance; + uint32_t code; + CImVector quotedColor; + + QUOTEDCODE quotedCode = GxuDetermineQuotedCode(text, advance, nullptr, flags, code); + + numBytes -= advance; + text += advance; + + if ( + quotedCode == CODE_COLORON + || quotedCode == CODE_COLORRESTORE + || quotedCode == CODE_HYPERLINKSTART + || quotedCode == CODE_HYPERLINKSTOP + || quotedCode == CODE_TEXTURESTOP + ) { + continue; + } + + if (quotedCode == CODE_NEWLINE || code == '\n') { + maxWidth = std::max(maxWidth, lastWidth + width); + lastWidth = 0.0f; + width = 0.0f; + } else if (quotedCode == CODE_TEXTURESTART) { + // TODO + } else if (font->NewCodeDesc(code)) { + float stepWidth = 0.0f; + + if (prevCode) { + stepWidth = flags & 0x10 + ? font->ComputeStepFixedWidth(prevCode, code) + : font->ComputeStep(prevCode, code); + } + + lastWidth = GetCharacterWidth(text, flags, code, font, height); + width += stepWidth; + + prevCode = code; + } + } + + float pixelSize = font->GetPixelSize(); + float pixelHeight = ScreenToPixelHeight(billboarded, height); + float pixelScale = pixelHeight / pixelSize; + + maxWidth = std::max(maxWidth, lastWidth + width); + maxWidth *= pixelScale; + + *extent = billboarded ? maxWidth : PixelToScreenWidth(maxWidth); +} diff --git a/src/gx/FontInternal.hpp b/src/gx/FontInternal.hpp new file mode 100644 index 0000000..0679539 --- /dev/null +++ b/src/gx/FontInternal.hpp @@ -0,0 +1,12 @@ +#ifndef GX_FONT_INTERNAL_HPP +#define GX_FONT_INTERNAL_HPP + +#include + +class CGxFont; + +uint32_t InternalGetMaxCharsWithinWidth(CGxFont*, const char*, float, float, uint32_t, float*, uint32_t, float, float, uint32_t*, float*, float*); + +void InternalGetTextExtent(CGxFont*, const char*, uint32_t, float, float*, uint32_t, float, float); + +#endif diff --git a/src/gx/Gx.cpp b/src/gx/Gx.cpp new file mode 100644 index 0000000..d58a567 --- /dev/null +++ b/src/gx/Gx.cpp @@ -0,0 +1,96 @@ +#include "gx/Gx.hpp" +#include "gx/Device.hpp" + +// TODO +// - remove placeholder after proper implementation +static CGxCaps* g_placeholderGxCaps = new CGxCaps(); + +const char* vsProfileNames[] = { + "none", "vs_1_1", "vs_2_0", "vs_3_0", "vs_4_0", "vs_5_0", "arbvp1", "arbvp1_cg12", "nvvp", "nvvp2", "nvvp3", "glsl" +}; + +const char* hsProfileNames[] = { + "none", "hs_5_0" +}; + +const char* dsProfileNames[] = { + "none", "ds_5_0" +}; + +const char* gsProfileNames[] = { + "none", "gs_4_0", "gs_5_0" +}; + +const char* psProfileNames[] = { + "none", "ps_1_1", "ps_1_4", "ps_2_0", "ps_3_0", "ps_4_0", "ps_5_0", "nvrc", "nvts", "nvts2", "nvts3", "nvfp2", "arbfp1", "glsl" +}; + +const char* csProfileNames[] = { + "none", "cs_5_0", "cs_cuda", "cs_ocl" +}; + +const char** g_gxShaderProfileNames[GxShTargets_Last] = { + vsProfileNames, + hsProfileNames, + dsProfileNames, + gsProfileNames, + psProfileNames, + csProfileNames +}; + +CGxCaps* GxCaps() { + // TODO + + g_placeholderGxCaps->m_pixelCenterOnEdge = 1; + g_placeholderGxCaps->m_texelCenterOnEdge = 1; + + g_placeholderGxCaps->m_colorFormat = GxCF_rgba; + + g_placeholderGxCaps->m_generateMipMaps = 1; + + g_placeholderGxCaps->m_maxTextureSize = 4096; + + g_placeholderGxCaps->m_texFmtDxt1 = 1; + g_placeholderGxCaps->m_texFmtDxt3 = 1; + g_placeholderGxCaps->m_texFmtDxt5 = 1; + + g_placeholderGxCaps->m_vertexShaderTarget = GxShVS_arbvp1; + g_placeholderGxCaps->m_pixelShaderTarget = GxShPS_arbfp1; + + g_placeholderGxCaps->m_texFilterAnisotropic = 1; + g_placeholderGxCaps->m_maxTexAnisotropy = 16; + + g_placeholderGxCaps->m_texTarget[GxTex_2d] = 1; + g_placeholderGxCaps->m_texTarget[GxTex_CubeMap] = 1; + g_placeholderGxCaps->m_texTarget[GxTex_Rectangle] = 1; + g_placeholderGxCaps->m_texTarget[GxTex_NonPow2] = 1; + + g_placeholderGxCaps->m_texMaxSize[GxTex_2d] = 4096; + g_placeholderGxCaps->m_texMaxSize[GxTex_CubeMap] = 4096; + g_placeholderGxCaps->m_texMaxSize[GxTex_Rectangle] = 4096; + g_placeholderGxCaps->m_texMaxSize[GxTex_NonPow2] = 4096; + + return g_placeholderGxCaps; +} + +bool GxCapsWindowHasFocus(int32_t a1) { + // TODO + return true; +} + +void GxCapsWindowSize(CRect& rect) { + g_theGxDevicePtr->CapsWindowSize(rect); +} + +void GxFormatColor(CImVector& color) { + if (GxCaps()->m_colorFormat == GxCF_rgba) { + CImVector formattedColor = { + color.r, + color.g, + color.b, + color.a + }; + + color = formattedColor; + } +} diff --git a/src/gx/Gx.hpp b/src/gx/Gx.hpp new file mode 100644 index 0000000..e0c2a3c --- /dev/null +++ b/src/gx/Gx.hpp @@ -0,0 +1,21 @@ +#ifndef GX_GX_HPP +#define GX_GX_HPP + +#include "gx/CGxCaps.hpp" +#include "gx/CGxFormat.hpp" +#include "gx/Types.hpp" +#include + +class CRect; + +extern const char** g_gxShaderProfileNames[GxShTargets_Last]; + +CGxCaps* GxCaps(void); + +bool GxCapsWindowHasFocus(int32_t); + +void GxCapsWindowSize(CRect&); + +void GxFormatColor(CImVector&); + +#endif diff --git a/src/gx/RenderState.cpp b/src/gx/RenderState.cpp new file mode 100644 index 0000000..94c2435 --- /dev/null +++ b/src/gx/RenderState.cpp @@ -0,0 +1,31 @@ +#include "gx/RenderState.hpp" +#include "gx/Shader.hpp" +#include "gx/Device.hpp" +#include "gx/Types.hpp" +#include "gx/texture/CGxTex.hpp" +#include + +void GxRsPop() { + g_theGxDevicePtr->RsPop(); +} + +void GxRsPush() { + g_theGxDevicePtr->RsPush(); +} + +void GxRsSet(EGxRenderState which, int32_t value) { + STORM_ASSERT(which < GxRenderStates_Last); + g_theGxDevicePtr->RsSet(which, value); +} + +void GxRsSet(EGxRenderState which, CGxShader* value) { + g_theGxDevicePtr->RsSet(which, value); +} + +void GxRsSet(EGxRenderState which, CGxTex* value) { + g_theGxDevicePtr->RsSet(which, value); +} + +void GxRsSetAlphaRef() { + g_theGxDevicePtr->RsSetAlphaRef(); +} diff --git a/src/gx/RenderState.hpp b/src/gx/RenderState.hpp new file mode 100644 index 0000000..0e9e249 --- /dev/null +++ b/src/gx/RenderState.hpp @@ -0,0 +1,22 @@ +#ifndef GX_RENDER_STATE_HPP +#define GX_RENDER_STATE_HPP + +#include "gx/Types.hpp" +#include + +class CGxShader; +class CGxTex; + +void GxRsPop(void); + +void GxRsPush(void); + +void GxRsSet(EGxRenderState, int32_t); + +void GxRsSet(EGxRenderState, CGxShader*); + +void GxRsSet(EGxRenderState, CGxTex*); + +void GxRsSetAlphaRef(void); + +#endif diff --git a/src/gx/Screen.cpp b/src/gx/Screen.cpp new file mode 100644 index 0000000..68bcaa4 --- /dev/null +++ b/src/gx/Screen.cpp @@ -0,0 +1,237 @@ +#include "gx/Screen.hpp" +#include "event/Event.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Draw.hpp" +#include "gx/Font.hpp" +#include "gx/Gx.hpp" +#include "gx/Transform.hpp" +#include "util/Filesystem.hpp" +#include + +int32_t Screen::s_captureScreen = 0; +float Screen::s_elapsedSec = 0.0f; +int32_t Screen::s_presentDisable = 0; +HOBJECT Screen::s_stockObjects[]; +float Screen::s_stockObjectHeights[] = { 0.01953125f, 0.01953125f }; +STORM_EXPLICIT_LIST(CILayer, zorderlink) Screen::s_zorderlist; + +int32_t OnIdle(const EVENT_DATA_IDLE* data, void* a2) { + Screen::s_elapsedSec = data->elapsedSec + Screen::s_elapsedSec; + + return 1; +} + +int32_t OnPaint(const void* a1, void* a2) { + // TODO + // if (!g_theGxDevicePtr || !g_theGxDevicePtr->CapsHasContext(-1) || !g_theGxDevicePtr->CapsIsWindowVisible(-1)) { + // // TODO + // // - sound engine logic + // + // return 1; + // } + + CILayer* layer; + + CSRgn rgn; + + SRgnCreate(&rgn.m_handle, 0); + + RECTF baseRect; + + baseRect.left = 0.0f; + baseRect.bottom = 0.0f; + baseRect.right = 1.0f; + baseRect.top = 1.0f; + + SRgnCombineRectf(&rgn.m_handle, &baseRect, 0, 2); + + layer = Screen::s_zorderlist.Head(); + + while (layer) { + SRgnGetBoundingRectf(&rgn.m_handle, &layer->visible); + + layer->visible.left = std::max(layer->visible.left, layer->rect.left); + layer->visible.bottom = std::max(layer->visible.bottom, layer->rect.bottom); + layer->visible.right = std::max(layer->visible.right, layer->rect.right); + layer->visible.top = std::max(layer->visible.top, layer->rect.top); + + if (layer->flags & 0x1) { + SRgnCombineRectf(&rgn.m_handle, &layer->rect, 0, 4); + } + + layer = layer->zorderlink.Next(); + } + + SRgnDelete(&rgn.m_handle); + + // Save viewport + float minX, maxX, minY, maxY, minZ, maxZ; + GxXformViewport(minX, maxX, minY, maxY, minZ, maxZ); + + layer = Screen::s_zorderlist.Head(); + + while (layer) { + if (layer->visible.right > layer->visible.left && layer->visible.top > layer->visible.bottom) { + if (layer->flags & 0x4) { + GxXformSetViewport( + 0.0f, + 1.0f, + 0.0f, + 1.0f, + 0.0f, + 1.0f + ); + } else { + GxXformSetViewport( + layer->visible.left, + layer->visible.right, + layer->visible.bottom, + layer->visible.top, + 0.0f, + 1.0f + ); + } + + if (layer->flags & 0x2) { + C44Matrix identity; + GxXformSetView(identity); + + C44Matrix orthoProj; + GxuXformCreateOrtho( + layer->visible.left, + layer->visible.right, + layer->visible.bottom, + layer->visible.top, + 0.0f, + 500.0f, + orthoProj + ); + GxXformSetProjection(orthoProj); + } + + layer->paintfunc( + layer->param, + &layer->rect, + &layer->visible, + Screen::s_elapsedSec + ); + } + + layer = layer->zorderlink.Next(); + } + + // Restore viewport + GxXformSetViewport(minX, maxX, minY, maxY, minZ, maxZ); + + GxuFontUpdate(); + + if (!Screen::s_presentDisable) { + if (Screen::s_captureScreen) { + // TODO + + GxScenePresent(); + + // TODO + + return 1; + } + + GxScenePresent(); + } + + Screen::s_elapsedSec = 0.0f; + + return 1; +} + +void ILayerInitialize() { + EventRegister(EVENT_ID_IDLE, reinterpret_cast(OnIdle)); + EventRegister(EVENT_ID_PAINT, &OnPaint); +} + +void IStockInitialize() { + GxuFontInitialize(); + + char fontFile[260]; + + OsBuildFontFilePath("FRIZQT__.TTF", fontFile, 260); + + if (*fontFile) { + ScrnSetStockFont(STOCK_SYSFONT, fontFile); + } else { + // TODO + // SErrSetLastError(0x57u); + } + + if (*fontFile) { + ScrnSetStockFont(STOCK_PERFFONT, fontFile); + } else { + // TODO + // SErrSetLastError(0x57u); + } +} + +void ScrnInitialize(int32_t a1) { + ILayerInitialize(); + + // TODO + // consoleInitialized = a1; + + IStockInitialize(); +} + +void ScrnLayerCreate(const RECTF* rect, float zorder, unsigned long flags, void* param, void (*paintFunc)(void*, const RECTF*, const RECTF*, float), HLAYER* layer) { + static RECTF defaultrect = { 0.0f, 0.0f, 1.0f, 1.0f }; + + const RECTF* r = rect ? rect : &defaultrect; + + void* m = SMemAlloc(sizeof(CILayer), __FILE__, __LINE__, 0); + + CILayer* l = new (m) CILayer(); + + l->rect.left = r->left; + l->rect.bottom = r->bottom; + l->rect.right = r->right; + l->rect.top = r->top; + + l->zorder = zorder; + l->flags = flags; + l->param = param; + l->paintfunc = paintFunc; + + auto node = Screen::s_zorderlist.Head(); + + while (node && zorder < node->zorder) { + node = node->zorderlink.Next(); + } + + Screen::s_zorderlist.LinkNode(l, 1, node); + + *layer = HandleCreate(l); +} + +void ScrnSetStockFont(SCRNSTOCK stockID, const char* fontTexturePath) { + if (Screen::s_stockObjects[stockID]) { + HandleClose(Screen::s_stockObjects[stockID]); + } + + float fontHeight = NDCToDDCHeight(Screen::s_stockObjectHeights[stockID]); + HTEXTFONT font = TextBlockGenerateFont(fontTexturePath, 0, fontHeight); + Screen::s_stockObjects[stockID] = font; +} + +void SRgnCombineRectf(HSRGN* handle, RECTF* rect, void* param, int32_t combinemode) { + // TODO +} + +void SRgnCreate(HSRGN* handle, uint32_t reserved) { + // TODO +} + +void SRgnDelete(HSRGN* handle) { + // TODO +} + +void SRgnGetBoundingRectf(HSRGN* handle, RECTF* rect) { + // TODO +} diff --git a/src/gx/Screen.hpp b/src/gx/Screen.hpp new file mode 100644 index 0000000..2476c5e --- /dev/null +++ b/src/gx/Screen.hpp @@ -0,0 +1,67 @@ +#ifndef GX_SCREEN_HPP +#define GX_SCREEN_HPP + +#include +#include +#include + +typedef HOBJECT HLAYER; +typedef HOBJECT HSRGN; + +enum SCRNSTOCK { + STOCK_SYSFONT = 0, + STOCK_PERFFONT = 1, + SCRNSTOCKOBJECTS = 2 +}; + +struct RECTF { + float left; + float bottom; + float right; + float top; +}; + +class CILayer : public CHandleObject { + public: + RECTF rect; + RECTF visible; + float zorder; + uint32_t flags; + void* param; + void (*paintfunc)(void*, const RECTF*, const RECTF*, float); + TSLink zorderlink; +}; + +class CSRgn { + public: + HSRGN m_handle; +}; + +namespace Screen { + extern int32_t s_captureScreen; + extern float s_elapsedSec; + extern int32_t s_presentDisable; + extern HOBJECT s_stockObjects[SCRNSTOCKOBJECTS]; + extern float s_stockObjectHeights[SCRNSTOCKOBJECTS]; + extern STORM_EXPLICIT_LIST(CILayer, zorderlink) s_zorderlist; +} + +void ILayerInitialize(void); + +void IStockInitialize(void); + +void ScrnInitialize(int32_t); + +void ScrnLayerCreate(const RECTF*, float, unsigned long, void*, void (*)(void*, const RECTF*, const RECTF*, float), HLAYER*); + +void ScrnSetStockFont(SCRNSTOCK, const char*); + +void SRgnCombineRectf(HSRGN*, RECTF*, void*, int32_t); + +void SRgnCreate(HSRGN*, uint32_t); + +void SRgnDelete(HSRGN*); + +void SRgnGetBoundingRectf(HSRGN*, RECTF*); + +#endif diff --git a/src/gx/Shader.cpp b/src/gx/Shader.cpp new file mode 100644 index 0000000..3e4610d --- /dev/null +++ b/src/gx/Shader.cpp @@ -0,0 +1,14 @@ +#include "gx/Shader.hpp" +#include "gx/Device.hpp" + +char* GxShaderConstantsLock(EGxShTarget target) { + return g_theGxDevicePtr->ShaderConstantsLock(target); +} + +void GxShaderConstantsSet(EGxShTarget target, uint32_t index, const float* constants, uint32_t count) { + g_theGxDevicePtr->ShaderConstantsSet(target, index, constants, count); +} + +void GxShaderConstantsUnlock(EGxShTarget target, uint32_t index, uint32_t count) { + g_theGxDevicePtr->ShaderConstantsUnlock(target, index, count); +} diff --git a/src/gx/Shader.hpp b/src/gx/Shader.hpp new file mode 100644 index 0000000..153c1f3 --- /dev/null +++ b/src/gx/Shader.hpp @@ -0,0 +1,16 @@ +#ifndef GX_SHADER_HPP +#define GX_SHADER_HPP + +#include "gx/shader/CGxShader.hpp" +#include "gx/shader/CShaderEffect.hpp" +#include "gx/shader/CShaderEffectManager.hpp" +#include "gx/Types.hpp" +#include + +char* GxShaderConstantsLock(EGxShTarget target); + +void GxShaderConstantsSet(EGxShTarget, uint32_t, const float*, uint32_t); + +void GxShaderConstantsUnlock(EGxShTarget target, uint32_t index, uint32_t count); + +#endif diff --git a/src/gx/Texture.cpp b/src/gx/Texture.cpp new file mode 100644 index 0000000..35fdc7d --- /dev/null +++ b/src/gx/Texture.cpp @@ -0,0 +1,1096 @@ +#include "gx/Texture.hpp" +#include "gx/Device.hpp" +#include "gx/Gx.hpp" +#include "gx/texture/CBLPFile.hpp" +#include "util/Filesystem.hpp" +#include "util/SFile.hpp" +#include +#include +#include +#include +#include +#include + +namespace Texture { + int32_t s_createBlpAsync; // Invented name + MipBits* s_mipBits; + int32_t s_mipBitsValid; + TSHashTable s_textureCache; + + EGxTexFormat s_pixelFormatToGxTexFormat[10] = { + GxTex_Dxt1, // PIXEL_DXT1 + GxTex_Dxt3, // PIXEL_DXT3 + GxTex_Argb8888, // PIXEL_ARGB8888 + GxTex_Argb1555, // PIXEL_ARGB1555 + GxTex_Argb4444, // PIXEL_ARGB4444 + GxTex_Rgb565, // PIXEL_RGB565 + GxTex_Unknown, // PIXEL_A8 + GxTex_Dxt5, // PIXEL_DXT5 + GxTex_Unknown, // PIXEL_UNSPECIFIED + GxTex_Unknown // PIXEL_ARGB2565 + }; +} + +void AsyncTextureWait(CTexture* texture) { + // TODO +} + +uint32_t CalcLevelCount(uint32_t width, uint32_t height) { + uint32_t v2 = width; + uint32_t v3 = height; + uint32_t v4 = 1; + uint32_t v5; + uint32_t v6; + + if (width == 6 * height) { + v2 = width / 6; + } + + while (v2 > 1 || v3 > 1) { + v5 = v2 >> 1; + + ++v4; + + v6 = v2 >> 1 < 1; + v2 = 1; + + if (!v6) { + v2 = v5; + } + + if ( v3 >> 1 >= 1 ) { + v3 >>= 1; + } else { + v3 = 1; + } + } + + return v4; +} + +uint32_t CalcLevelOffset(uint32_t level, uint32_t width, uint32_t height, uint32_t fourCC) { + uint32_t offset = 0; + + for (int32_t i = 0; i < level; i++) { + offset += CalcLevelSize(i, width, height, fourCC); + } + + return offset; +} + +uint32_t CalcLevelSize(uint32_t level, uint32_t width, uint32_t height, uint32_t fourCC) { + uint32_t v4 = std::max(width >> level, 1u); + uint32_t v5 = std::max(height >> level, 1u); + + if (fourCC == 0 || fourCC == 1 || fourCC == 7) { + if (v4 == 6 * v5) { + if (v5 <= 4) { + v5 = 4; + } + + v4 = 6 * v5; + } else { + if (v4 <= 4) { + v4 = 4; + } + + if (v5 <= 4) { + v5 = 4; + } + } + } + + uint32_t size; + + if (fourCC == 9) { + uint32_t v6 = v5 * v4; + uint32_t v7 = v5 * v4 >> 2; + + if (v7 < 1) { + v7 = 1; + } + + size = v7 + 2 * v6; + } else { + uint32_t v9 = GetBitDepth(fourCC); + + size = (v4 * v5 * v9) >> 3; + } + + return size; +} + +void FillInSolidTexture(const CImVector& color, CTexture* texture) { + // Treat the value of color as a nonsense pointer to ensure the value remains available + // when GxuUpdateSingleColorTexture is called + void* userArg = reinterpret_cast(color.value); + + CGxTexFlags gxTexFlags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1); + + texture->gxTex = TextureAllocGxTex( + GxTex_2d, + 8, + 8, + 0, + GxTex_Argb8888, + gxTexFlags, + userArg, + GxuUpdateSingleColorTexture, + GxTex_Argb8888 + ); + + if (color.a < 0xFE) { + texture->flags &= ~0x1; + } else { + texture->flags |= 0x1; + } + + texture->dataFormat = GxTex_Argb8888; + texture->gxTexFormat = GxTex_Argb8888; + texture->gxTexTarget = GxTex_2d; + texture->gxWidth = 8; + texture->gxHeight = 8; + texture->gxTexFlags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1); + + SStrCopy(texture->filename, "SolidTexture", STORM_MAX_PATH); +} + +uint32_t GetBitDepth(uint32_t fourCC) { + switch (fourCC) { + case 0: + return 4; + + case 1: + case 6: + case 7: + return 8; + + case 2: + return 32; + + case 3: + case 4: + case 5: + return 16; + + default: + return 0; + } +} + +uint32_t GxCalcTexelStrideInBytes(EGxTexFormat format, uint32_t width) { + static uint16_t word9F103C[] = { + 0, // GxTex_Unknown + 32, // GxTex_Abgr8888 + 32, // GxTex_Argb8888 + 16, // GxTex_Argb4444 + 16, // GxTex_Argb1555 + 16, // GxTex_Rgb565 + 4, // GxTex_Dxt1 + 8, // GxTex_Dxt3 + 8, // GxTex_Dxt5 + 16, // GxTex_Uv88 + 32, // GxTex_Gr1616F + 32, // GxTex_R32F + 32, // GxTex_D24X8 + 0 // GxTexFormats_Last + }; + + static uint16_t word9F1058[] = { + 0, // GxTex_Unknown + 0, // GxTex_Abgr8888 + 0, // GxTex_Argb8888 + 0, // GxTex_Argb4444 + 0, // GxTex_Argb1555 + 0, // GxTex_Rgb565 + 8, // GxTex_Dxt1 + 16, // GxTex_Dxt3 + 16, // GxTex_Dxt5 + 0, // GxTex_Uv88 + 0, // GxTex_Gr1616F + 0, // GxTex_R32F + 0, // GxTex_D24X8 + 0 // GxTexFormats_Last + }; + + if (format == GxTex_Dxt1 || format == GxTex_Dxt3 || format == GxTex_Dxt5) { + uint32_t v11 = (width >> 2) * word9F1058[format]; + return std::max(v11, static_cast(word9F1058[format])); + } else { + return width * word9F103C[format] >> 3; + } +} + +int32_t GxTexCreate(const CGxTexParms& parms, CGxTex*& texId) { + return GxTexCreate( + parms.target, + parms.width, + parms.height, + parms.depth, + parms.format, + parms.dataFormat, + parms.flags, + parms.userArg, + parms.userFunc, + "", + texId + ); +} + +int32_t GxTexCreate(EGxTexTarget target, uint32_t width, uint32_t height, uint32_t depth, EGxTexFormat format, EGxTexFormat dataFormat, CGxTexFlags flags, void* userArg, TEXTURE_CALLBACK* userFunc, const char* name, CGxTex*& texId) { + texId = nullptr; + + STORM_ASSERT(target <= GxTexTargets_Last); + STORM_ASSERT(GxCaps()->m_texTarget[target] == 1); + STORM_ASSERT(width >= 8); + STORM_ASSERT(height >= 8); + STORM_ASSERT(width <= GxCaps()->m_texMaxSize[target]); + STORM_ASSERT(height <= GxCaps()->m_texMaxSize[target]); + STORM_ASSERT((target != GxTex_Rectangle && target != GxTex_NonPow2) ? (width & (width - 1)) == 0 : 1); + STORM_ASSERT((target != GxTex_Rectangle && target != GxTex_NonPow2) ? (height & (height - 1)) == 0 : 1); + STORM_ASSERT((target == GxTex_Rectangle) ? flags.m_filter <= GxTex_Linear : 1); + STORM_ASSERT(format <= GxTexFormats_Last); + STORM_ASSERT((flags.m_generateMipMaps) ? (GxCaps()->m_generateMipMaps && !(format >= GxTex_Dxt1 && format <= GxTex_Dxt5)) : 1); + STORM_ASSERT((flags.m_filter == GxTex_Anisotropic) ? GxCaps()->m_texFilterAnisotropic : 1); + STORM_ASSERT(dataFormat <= GxTexFormats_Last); + STORM_ASSERT(userFunc != nullptr); + + return g_theGxDevicePtr->TexCreate( + target, + width, + height, + depth, + format, + dataFormat, + flags, + userArg, + userFunc, + name, + texId + ); +} + +void GxTexDestroy(CGxTex* texId) { + g_theGxDevicePtr->TexDestroy(texId); +} + +void GxTexParameters(const CGxTex* texId, CGxTexParms& parms) { + // TODO +} + +bool GxTexReusable(CGxTexParms& parms) { + // TODO + + return false; +} + +void GxTexSetWrap(CGxTex* texId, EGxTexWrapMode wrapU, EGxTexWrapMode wrapV) { + g_theGxDevicePtr->TexSetWrap(texId, wrapU, wrapV); +} + +int32_t ReloadMips(const char* filename, uint32_t a2, MipBits*& mipBits) { + // TODO + + return 0; +} + +void TextureFreeGxTex(CGxTex* texId) { + STORM_ASSERT(texId); + + CGxTexParms gxTexParms; + GxTexParameters(texId, gxTexParms); + + if (GxTexReusable(gxTexParms)) { + // TODO + + return; + } + + GxTexDestroy(texId); +} + +CGxTex* TextureAllocGxTex(EGxTexTarget target, uint32_t width, uint32_t height, uint32_t depth, EGxTexFormat format, CGxTexFlags flags, void* userArg, TEXTURE_CALLBACK* userFunc, EGxTexFormat dataFormat) { + CGxTexParms gxTexParms; + + gxTexParms.height = height; + gxTexParms.depth = depth; + gxTexParms.target = target; + gxTexParms.dataFormat = dataFormat; + gxTexParms.userArg = userArg; + gxTexParms.format = format; + gxTexParms.width = width; + gxTexParms.userFunc = userFunc; + gxTexParms.flags = flags; + gxTexParms.flags.m_generateMipMaps = 0; + + CGxTexParms gxTexParms2; + + if (!GxTexReusable(gxTexParms) || width > 512 || height > 512) { + CGxTex* texture = nullptr; + memcpy(&gxTexParms2, &gxTexParms, sizeof(gxTexParms2)); + GxTexCreate(gxTexParms2, texture); + + return texture; + } + + uint32_t v9 = width >> 5; + uint32_t v10 = height >> 5; + + for (int32_t i = 0; !(v9 & 1); i++) { + v9 >>= 1; + } + + for (int32_t j = 0; !(v10 & 1); j++) { + v10 >>= 1; + } + + // TODO + + return nullptr; +} + +void GxTexUpdate(CGxTex* texId, int32_t minX, int32_t minY, int32_t maxX, int32_t maxY, int32_t immediate) { + CiRect rect = { minY, minX, maxY, maxX }; + GxTexUpdate(texId, rect, immediate); +} + +void GxTexUpdate(CGxTex* texId, CiRect& updateRect, int32_t immediate) { + g_theGxDevicePtr->TexMarkForUpdate(texId, updateRect, immediate); +} + +void GxuUpdateSingleColorTexture(EGxTexCommand cmd, uint32_t w, uint32_t h, uint32_t d, uint32_t mipLevel, void* userArg, uint32_t& texelStrideInBytes, const void*& texels) { + static uint8_t image[256] = { 0 }; + + switch (cmd) { + case GxTex_Lock: { + // Treat the userArg pointer as the literal color to use while filling the texture + uint32_t color = reinterpret_cast(userArg); + + for (int32_t i = 0; i < sizeof(image) / sizeof(color); i++) { + reinterpret_cast(image)[i] = color; + } + + return; + } + + case GxTex_Latch: { + texelStrideInBytes = 4 * w; + texels = image; + + return; + } + + default: + return; + } +} + +void GetDefaultTexture(uint32_t height, uint32_t width) { + // TODO +} + +void GetTextureFormats(PIXEL_FORMAT* pixFormat, EGxTexFormat* gxTexFormat, PIXEL_FORMAT preferredFormat, int32_t alphaBits) { + switch (preferredFormat) { + case PIXEL_DXT1: + if (GxCaps()->m_texFmtDxt1) { + *gxTexFormat = GxTex_Dxt1; + *pixFormat = PIXEL_DXT1; + } else if (alphaBits) { + *gxTexFormat = GxTex_Argb1555; + *pixFormat = PIXEL_ARGB1555;; + } else { + *gxTexFormat = GxTex_Rgb565; + *pixFormat = PIXEL_RGB565; + } + + break; + + case PIXEL_DXT3: + if (GxCaps()->m_texFmtDxt3) { + *gxTexFormat = GxTex_Dxt3; + *pixFormat = PIXEL_DXT3; + } else { + *gxTexFormat = GxTex_Argb4444; + *pixFormat = PIXEL_ARGB4444; + } + + break; + + case PIXEL_ARGB8888: + *gxTexFormat = GxTex_Argb8888; + *pixFormat = PIXEL_ARGB8888; + + break; + + case PIXEL_ARGB1555: + *gxTexFormat = GxTex_Argb1555; + *pixFormat = PIXEL_ARGB8888; + + break; + + case PIXEL_ARGB4444: + *gxTexFormat = GxTex_Argb4444; + *pixFormat = PIXEL_ARGB8888; + + break; + + case PIXEL_RGB565: + *gxTexFormat = GxTex_Rgb565; + *pixFormat = PIXEL_ARGB8888; + + break; + + case PIXEL_DXT5: + if (GxCaps()->m_texFmtDxt5) { + *gxTexFormat = GxTex_Dxt5; + *pixFormat = PIXEL_DXT5; + } else { + *gxTexFormat = GxTex_Argb4444; + *pixFormat = PIXEL_ARGB4444; + } + + break; + + case PIXEL_UNSPECIFIED: + if (alphaBits > 0) { + if (alphaBits == 1) { + *gxTexFormat = GxTex_Argb1555; + *pixFormat = PIXEL_ARGB8888; + } else if (alphaBits == 4) { + *gxTexFormat = GxTex_Argb4444; + *pixFormat = PIXEL_ARGB8888; + } else { + *gxTexFormat = GxTex_Argb8888; + *pixFormat = PIXEL_ARGB8888; + } + } else { + *gxTexFormat = GxTex_Rgb565; + *pixFormat = PIXEL_ARGB8888; + } + + break; + + default: + break; + } +} + +MipBits* MippedImgAllocA(uint32_t fourCC, uint32_t width, uint32_t height, const char* fileName, int32_t lineNumber) { + uint32_t levelCount = CalcLevelCount(width, height); + uint32_t levelDataSize = CalcLevelOffset(levelCount, width, height, fourCC); + + MipBits* images = reinterpret_cast(SMemAlloc(levelDataSize + sizeof(void*) * levelCount + 16, fileName, lineNumber, 0)); + + uintptr_t v10 = (reinterpret_cast(images) + sizeof(void*) * levelCount + 15) & static_cast(-sizeof(void*)); + uintptr_t offset = v10 - reinterpret_cast(images); + + MipBits** ptr = reinterpret_cast(images); + + for (int32_t level = 0; level < levelCount; level++) { + ptr[level] = images + offset; + offset += CalcLevelSize(level, width, height, fourCC); + } + + return images; +} + +uint32_t MippedImgCalcSize(uint32_t fourCC, uint32_t width, uint32_t height) { + uint32_t levelCount = CalcLevelCount(width, height); + uint32_t levelDataSize = CalcLevelOffset(levelCount, width, height, fourCC); + uint32_t imgSize = levelDataSize + (sizeof(void*) * levelCount); + + return imgSize; +} + +// TODO +// - order: width, height or height, width? +void RequestImageDimensions(uint32_t* width, uint32_t* height, uint32_t* bestMip) { + CGxCaps systemCaps; + + memcpy(&systemCaps, GxCaps(), sizeof(systemCaps)); + + uint32_t maxTextureSize = systemCaps.m_maxTextureSize; + + if (maxTextureSize) { + while (*height > maxTextureSize || *width > maxTextureSize) { + *height >>= 1; + *width >>= 1; + + ++*bestMip; + + if (!*height) { + *height = 1; + } + + if (!*width) { + *width = 1; + } + } + } else { + // TODO + // SErrSetLastError(0x57u); + } +} + +void UpdateBlpTextureAsync(EGxTexCommand cmd, uint32_t w, uint32_t h, uint32_t d, uint32_t mipLevel, void* userArg, uint32_t& texelStrideInBytes, const void*& texels) { + CTexture* texture = static_cast(userArg); + + switch (cmd) { + case GxTex_Lock: + if (Texture::s_mipBitsValid) { + return; + } + + if (!ReloadMips(texture->filename, texture->flags & 0x2, Texture::s_mipBits)) { + GetDefaultTexture(h, w); + + // TODO + // GetGlobalStatusObj()->Add( + // STATUS_ERROR, + // "Texture %s not loaded -- replaced with default.\n", + // texture->filename + // ); + } + + return; + + case GxTex_Latch: + texelStrideInBytes = GxCalcTexelStrideInBytes(texture->dataFormat, w); + + if (texture->gxTexTarget == GxTex_CubeMap) { + // TODO + } else { + texels = reinterpret_cast(Texture::s_mipBits)[mipLevel]; + } + + return; + + default: + return; + } +} + +int32_t PumpBlpTextureAsync(CTexture* texture, void* buf) { + CBLPFile image; + + if (!image.Source(buf)) { + texture->loadStatus.Add( + STATUS_FATAL, + "BLP Texture failure: \"%s\" invalid file version\n", + texture->filename + ); + + image.Close(); + + return 0; + } + + if (texture->flags & 0x4 && !(image.m_header.hasMips & 0x10)) { + texture->flags &= 0xFFFB; + } + + texture->alphaBits = image.m_header.alphaSize; + + if (image.m_header.alphaSize == 0) { + texture->flags |= 0x1; + } + + uint32_t width = image.m_header.width; + uint32_t height = image.m_header.height; + uint32_t bestMip = 0; + + RequestImageDimensions(&width, &height, &bestMip); + + texture->bestMip = bestMip; + + PIXEL_FORMAT pixFormat; + EGxTexFormat gxTexFormat; + PIXEL_FORMAT preferredFormat = static_cast(image.m_header.preferredFormat); + int32_t alphaSize = image.m_header.alphaSize; + + GetTextureFormats(&pixFormat, &gxTexFormat, preferredFormat, alphaSize); + + int32_t mipLevel = texture->bestMip; + + Texture::s_mipBitsValid = 1; + + if (!image.LockChain2(texture->filename, pixFormat, Texture::s_mipBits, mipLevel, 1)) { + Texture::s_mipBitsValid = 0; + + texture->loadStatus.Add( + STATUS_FATAL, + "BLP Texture failure: \"%s\" decompression failed.\n", + texture->filename + ); + + image.Close(); + + return 0; + } + + uint32_t gxHeight = height; + uint32_t gxWidth = width; + EGxTexTarget gxTexTarget = GxTex_2d; + + // Check if texture dimensions indicate cube mapping + if (width == 6 * height) { + gxHeight = height; + gxWidth = width / 6u; + gxTexTarget = GxTex_CubeMap; + } + + texture->gxHeight = gxHeight; + texture->gxWidth = gxWidth; + texture->gxTexTarget = gxTexTarget; + + EGxTexFormat dataFormat = Texture::s_pixelFormatToGxTexFormat[pixFormat]; + texture->dataFormat = dataFormat; + texture->gxTexFormat = gxTexFormat; + + if (gxWidth < 256 && image.m_numLevels == 1) { + if (!texture->gxTexFlags.m_generateMipMaps) { + texture->gxTexFlags.m_filter = 0; + } + } + + if (texture->flags & 0x4) { + // TODO + + // CTextureAtlas* atlas = CTextureAtlas::Get(v2); + + // texture->atlas = atlas; + + // if (atlas) { + // sub_4B50D0(atlas, v2); + // } else { + // texture->flags &= 0xFFFBu; + // } + } + + if (!texture->atlas) { + if (texture->gxTex) { + TextureFreeGxTex(texture->gxTex); + texture->gxTex = nullptr; + } + + CGxTex* gxTex = TextureAllocGxTex( + texture->gxTexTarget, + texture->gxWidth, + texture->gxHeight, + 0, + texture->gxTexFormat, + texture->gxTexFlags, + texture, + &UpdateBlpTextureAsync, + texture->dataFormat + ); + + texture->gxTex = gxTex; + + if (!gxTex) { + Texture::s_mipBitsValid = 0; + + texture->loadStatus.Add( + STATUS_FATAL, + "BLP Texture failure: \"%s\" allocating %dx%d texture failed.\n", + texture->filename, + gxWidth, + gxHeight + ); + + image.Close(); + + return 0; + } + + GxTexUpdate(gxTex, 0, 0, gxWidth, gxHeight, 1); + } + + Texture::s_mipBitsValid = 0; + + image.Close(); + + return 1; +} + +int32_t FindSubstitution(const char* a1, char* a2) { + // TODO + + return 0; +} + +CTexture* CreateBlpAsync(char* fileExt, char* fileName, int32_t createFlags, CGxTexFlags texFlags) { + // TODO + + return nullptr; +} + +CTexture* CreateBlpSync(int32_t createFlags, char* fileName, char* fileExt, CGxTexFlags texFlags) { + SFile* file = nullptr; + + // TODO + // SErrSetLastError(0); + + if (!SFile::OpenEx(nullptr, fileName, (createFlags >> 1) & 1, &file)) { + // TODO + // if (!sub_7717E0()) { + // SErrSetLastError(2u); + // } + + return nullptr; + } + + if (!file) { + return nullptr; + } + + void* v8 = SMemAlloc(sizeof(CTexture), "HTEXTURE", -2, 0); + + CTexture* texture; + + if (v8) { + texture = new (v8) CTexture(); + } + + texture->gxTexFlags = texFlags; + + if (createFlags & 0x2) { + texture->flags |= 0x2; + } + + // TODO + // if (createFlags & 0x4 && dword_B49C84) { + // texture->flags |= 0x4; + // } + + if (fileExt) { + *fileExt = 0; + } + + SStrCopy(texture->filename, fileName, 0x7FFFFFFF); + + size_t fileSize = SFile::GetFileSize(file, 0); + + void* buf = SMemAlloc(fileSize, __FILE__, __LINE__, 0); + + if (!SFile::Read(file, buf, fileSize, nullptr, nullptr, nullptr)) { + // nullsub_3(); + } + + if (!PumpBlpTextureAsync(texture, buf)) { + // TODO + // FillInSolidTexture((int)&CRAPPY_GREEN, (int)v4); + } + + SFile::Close(file); + + SMemFree(buf, __FILE__, __LINE__, 0); + + return texture; +} + +HTEXTURE CreateBlpTexture(char* fileExt, char* fileName, int32_t createFlags, CGxTexFlags texFlags) { + if (fileExt) { + SStrCopy(fileExt, ".blp", 0x7FFFFFFF); + } + + char* fileExtFinal = fileExt; + char* fileNameFinal = fileName; + + char fileNameSub[260]; + + if (FindSubstitution(fileNameSub, fileName)) { + fileNameFinal = fileNameSub; + fileExtFinal = OsPathFindExtensionWithDot(fileNameSub); + } + + CTexture* texture; + + if (Texture::s_createBlpAsync) { + texture = CreateBlpAsync(fileExtFinal, fileNameFinal, createFlags, texFlags); + } else { + texture = CreateBlpSync(createFlags, fileNameFinal, fileExtFinal, texFlags); + } + + HTEXTURE handle = texture ? HandleCreate(texture) : nullptr; + + return handle; +} + +HTEXTURE CreateTgaTexture(const char* fileName, const char* fileExt, int32_t a3, CGxTexFlags texFlags, CStatus* status) { + // TODO + + return nullptr; +} + +HTEXTURE TextureCacheGetTexture(char* fileName, char* fileExt, CGxTexFlags texFlags) { + if (fileExt) { + *fileExt = '\0'; + } + + auto hashval = SStrHashHT(fileName); + HASHKEY_TEXTUREFILE key = { fileName, texFlags }; + + auto texture = Texture::s_textureCache.Ptr(hashval, key); + + if (fileExt) { + *fileExt = '.'; + } + + if (texture) { + return HandleCreate(texture); + } + + return nullptr; +} + +HTEXTURE TextureCacheGetTexture(const CImVector& color) { + // TODO + + return nullptr; +} + +void TextureCacheNewTexture(CTexture* texture, CGxTexFlags texFlags) { + auto hashval = SStrHashHT(texture->filename); + HASHKEY_TEXTUREFILE key = { texture->filename, texFlags }; + + Texture::s_textureCache.Insert(texture, hashval, key); +} + +void TextureCacheNewTexture(CTexture* texture, const CImVector& color) { + // TODO +} + +HTEXTURE TextureCreate(const char* fileName, CGxTexFlags texFlags, CStatus* status, int32_t createFlags) { + STORM_ASSERT(fileName); + STORM_ASSERT(*fileName); + STORM_ASSERT(status); + + if (createFlags & 0x1) { + texFlags.m_filter = CTexture::s_filterMode; + } + + if (texFlags.m_filter == 5) { + texFlags.m_maxAnisotropy = CTexture::s_maxAnisotropy; + } else { + texFlags.m_maxAnisotropy = 1; + } + + int32_t v16 = 2; + int32_t v8 = 1; + + char tmpFileName[260]; + + SStrCopy(tmpFileName, fileName, 260); + + char* fileExt = OsPathFindExtensionWithDot(tmpFileName); + + char* v10 = fileExt; + + if (createFlags & 0x8) { + if (fileExt) { + v16 = 1; + v8 = SStrCmpI(fileExt, ".blp", 0x7FFFFFFFu) == 0; + v10 = 0; + } + } + + HTEXTURE texture = TextureCacheGetTexture(tmpFileName, v10, texFlags); + + if (texture) { + return texture; + } + + for (int32_t i = 0; i < v16; ++i) { + if (v8) { + if (v8 != 1) { + v8 = (v8 + 1) % 2; + continue; + } + + texture = CreateBlpTexture(v10, tmpFileName, createFlags, texFlags); + } else { + texture = CreateTgaTexture(tmpFileName, v10, createFlags & 0x2, texFlags, status); + } + + if (texture) { + TextureCacheNewTexture(TextureGetTexturePtr(texture), texFlags); + return texture; + } + + v8 = (v8 + 1) % 2; + } + + // TODO + // FileError(status, "texture", fileName); + // return TextureCreateSolid(&CRAPPY_GREEN); + + return nullptr; +} + +HTEXTURE TextureCreate(uint32_t width, uint32_t height, EGxTexFormat format, EGxTexFormat dataFormat, CGxTexFlags texFlags, void* userArg, TEXTURE_CALLBACK* userFunc, const char* a8, int32_t a9) { + return TextureCreate( + GxTex_2d, + width, + height, + 0, + format, + dataFormat, + texFlags, + userArg, + userFunc, + a8, + a9 + ); +} + +HTEXTURE TextureCreate(EGxTexTarget target, uint32_t width, uint32_t height, uint32_t depth, EGxTexFormat format, EGxTexFormat dataFormat, CGxTexFlags texFlags, void* userArg, TEXTURE_CALLBACK* userFunc, const char* a10, int32_t a11) { + void* m = SMemAlloc(sizeof(CTexture), __FILE__, __LINE__, 0x0); + CTexture* texture = new (m) CTexture(); + + if (a11) { + texFlags.m_filter = CTexture::s_filterMode; + } + + texFlags.m_maxAnisotropy = texFlags.m_filter == GxTex_Anisotropic ? CTexture::s_maxAnisotropy : 1; + + texture->gxTex = TextureAllocGxTex(target, width, height, depth, format, texFlags, userArg, userFunc, dataFormat); + texture->dataFormat = dataFormat; + texture->gxWidth = width; + texture->gxHeight = height; + texture->gxTexFormat = format; + texture->gxTexTarget = target; + texture->gxTexFlags = texFlags; + texture->asyncObject = nullptr; + + const char* filename = a10 ? a10 : "UniqueTexture"; + SStrCopy(texture->filename, filename, STORM_MAX_PATH); + + return HandleCreate(texture); +} + +HTEXTURE TextureCreateSolid(const CImVector& color) { + HTEXTURE textureHandle = TextureCacheGetTexture(color); + + if (textureHandle) { + return textureHandle; + } + + void* m = SMemAlloc(sizeof(CTexture), __FILE__, __LINE__, 0x0); + CTexture* texture = new (m) CTexture(); + + FillInSolidTexture(color, texture); + textureHandle = HandleCreate(texture); + TextureCacheNewTexture(texture, color); + + return textureHandle; +} + +int32_t TextureGetDimensions(CTexture* texture, uint32_t* width, uint32_t* height, int32_t force) { + if (texture->asyncObject) { + if (!force) { + return 0; + } + + // TODO + } + + if (width) { + *width = texture->gxWidth; + } + + if (height) { + *height = texture->gxHeight; + } + + return 1; +} + +int32_t TextureGetDimensions(HTEXTURE textureHandle, uint32_t* width, uint32_t* height, int32_t force) { + return TextureGetDimensions(TextureGetTexturePtr(textureHandle), width, height, force); +} + +CGxTex* TextureGetGxTex(CTexture* texture, int32_t a2, CStatus* status) { + STORM_ASSERT(texture); + + if (texture->flags & 0x4) { + if (texture->asyncObject) { + if (a2 != 1 && (a2 != 2 || texture->asyncObject->char24)) { + TextureIncreasePriority(texture); + return nullptr; + } + + AsyncTextureWait(texture); + + if (status) { + status->Add(texture->loadStatus); + } + } + + if (texture->atlas) { + // TODO + // atlas->Reload(); + + // TODO + // - pull texture out of atlas + + return nullptr; + } + } + + if (texture->asyncObject) { + TextureIncreasePriority(texture); + } + + if (!texture->gxTex) { + if (a2 != 1 && (a2 != 2 || texture->asyncObject->char24)) { + return nullptr; + } + + AsyncTextureWait(texture); + + if (status) { + status->Add(texture->loadStatus); + } + } + + return texture->gxTex; +} + +CGxTex* TextureGetGxTex(HTEXTURE handle, int32_t a2, CStatus* status) { + return TextureGetGxTex(reinterpret_cast(handle), a2, status); +} + +CTexture* TextureGetTexturePtr(HTEXTURE handle) { + return reinterpret_cast(handle); +} + +void TextureIncreasePriority(CTexture* texture) { + // TODO +} + +void TextureInitialize() { + uint32_t v0 = MippedImgCalcSize(2, 1024, 1024); + Texture::s_mipBits = reinterpret_cast(SMemAlloc(v0, __FILE__, __LINE__, 0)); + + // TODO + // - rest of function +} + +int32_t TextureIsSame(HTEXTURE textureHandle, const char* fileName) { + char buf[STORM_MAX_PATH]; + uint32_t len = SStrCopy(buf, fileName, sizeof(buf)); + + if (len >= 4 && buf[len - 4] == '.') { + len -= 4; + } + auto v3 = &buf[len]; + if (*v3 == '.') { + SStrLower(v3 + 1); + *v3 = '\0'; + } + + STORM_ASSERT(textureHandle); + + return SStrCmpI(buf, TextureGetTexturePtr(textureHandle)->filename, sizeof(buf)) == 0; +} diff --git a/src/gx/Texture.hpp b/src/gx/Texture.hpp new file mode 100644 index 0000000..cb68564 --- /dev/null +++ b/src/gx/Texture.hpp @@ -0,0 +1,82 @@ +#ifndef GX_TEXTURE_HPP +#define GX_TEXTURE_HPP + +#include "gx/Types.hpp" +#include "gx/texture/CGxTex.hpp" +#include "gx/texture/CTexture.hpp" + +typedef HOBJECT HTEXTURE; + +typedef void (TEXTURE_CALLBACK)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&); + +class CImVector; + +void AsyncTextureWait(CTexture*); + +uint32_t CalcLevelCount(uint32_t, uint32_t); + +uint32_t CalcLevelOffset(uint32_t, uint32_t, uint32_t, uint32_t); + +uint32_t CalcLevelSize(uint32_t, uint32_t, uint32_t, uint32_t); + +uint32_t GetBitDepth(uint32_t); + +uint32_t GxCalcTexelStrideInBytes(EGxTexFormat, uint32_t); + +int32_t GxTexCreate(CGxTexParms const&, CGxTex*&); + +int32_t GxTexCreate(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexFormat, EGxTexFormat, CGxTexFlags, void*, void (*)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char*, CGxTex*&); + +void GxTexDestroy(CGxTex* texId); + +void GxTexParameters(const CGxTex* texId, CGxTexParms& parms); + +bool GxTexReusable(CGxTexParms&); + +void GxTexSetWrap(CGxTex* texId, EGxTexWrapMode wrapU, EGxTexWrapMode wrapV); + +void GxTexUpdate(CGxTex*, int32_t, int32_t, int32_t, int32_t, int32_t); + +void GxTexUpdate(CGxTex*, CiRect&, int32_t); + +TEXTURE_CALLBACK GxuUpdateSingleColorTexture; + +MipBits* MippedImgAllocA(uint32_t, uint32_t, uint32_t, const char*, int32_t); + +uint32_t MippedImgCalcSize(uint32_t, uint32_t, uint32_t); + +CGxTex* TextureAllocGxTex(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexFormat, CGxTexFlags, void*, void (*userFunc)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), EGxTexFormat); + +HTEXTURE TextureCacheGetTexture(char*, char*, CGxTexFlags); + +HTEXTURE TextureCacheGetTexture(const CImVector&); + +void TextureCacheNewTexture(CTexture*, CGxTexFlags); + +void TextureCacheNewTexture(CTexture*, const CImVector&); + +HTEXTURE TextureCreate(const char*, CGxTexFlags, CStatus*, int32_t); + +HTEXTURE TextureCreate(uint32_t, uint32_t, EGxTexFormat, EGxTexFormat, CGxTexFlags, void*, void (*)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char*, int32_t); + +HTEXTURE TextureCreate(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexFormat, EGxTexFormat, CGxTexFlags, void*, void (*)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char*, int32_t); + +HTEXTURE TextureCreateSolid(const CImVector&); + +int32_t TextureGetDimensions(HTEXTURE, uint32_t*, uint32_t*, int32_t); + +void TextureIncreasePriority(CTexture*); + +void TextureInitialize(void); + +int32_t TextureIsSame(HTEXTURE textureHandle, const char* fileName); + +void TextureFreeGxTex(CGxTex* texId); + +CGxTex* TextureGetGxTex(CTexture*, int32_t, CStatus*); + +CGxTex* TextureGetGxTex(HTEXTURE, int32_t, CStatus*); + +CTexture* TextureGetTexturePtr(HTEXTURE); + +#endif diff --git a/src/gx/Transform.cpp b/src/gx/Transform.cpp new file mode 100644 index 0000000..ca94049 --- /dev/null +++ b/src/gx/Transform.cpp @@ -0,0 +1,217 @@ +#include "gx/Transform.hpp" +#include "gx/Device.hpp" +#include "gx/Types.hpp" +#include +#include +#include +#include +#include +#include + +void GxXformPop(EGxXform xf) { + g_theGxDevicePtr->XformPop(xf); +} + +void GxXformProjection(C44Matrix& matrix) { + g_theGxDevicePtr->XformProjection(matrix); +} + +void GxXformProjNative(C44Matrix& matrix) { + g_theGxDevicePtr->XformProjNative(matrix); +} + +void GxXformProjNativeTranspose(C44Matrix& matrix) { + C44Matrix p; + GxXformProjNative(p); + + matrix = p.Transpose(); +} + +void GxXformPush(EGxXform xf) { + g_theGxDevicePtr->XformPush(xf); +} + +void GxXformSet(EGxXform xf, const C44Matrix& matrix) { + g_theGxDevicePtr->XformSet(xf, matrix); +} + +void GxXformSetProjection(const C44Matrix& matrix) { + g_theGxDevicePtr->XformSetProjection(matrix); +} + +void GxXformSetView(const C44Matrix& matrix) { + g_theGxDevicePtr->XformSetView(matrix); +} + +void GxXformSetViewport(float minX, float maxX, float minY, float maxY, float minZ, float maxZ) { + minX = std::max(minX, 0.0f); + maxX = std::min(maxX, 1.0f); + minY = std::max(minY, 0.0f); + maxY = std::min(maxY, 1.0f); + + STORM_ASSERT(minX < maxX); + STORM_ASSERT(minY < maxY); + STORM_ASSERT(minZ <= maxZ); + + g_theGxDevicePtr->XformSetViewport(minX, maxX, minY, maxY, minZ, maxZ); +} + +void GxXformView(C44Matrix& matrix) { + g_theGxDevicePtr->XformView(matrix); +} + +void GxXformViewport(float& minX, float& maxX, float& minY, float& maxY, float& minZ, float& maxZ) { + g_theGxDevicePtr->XformViewport(minX, maxX, minY, maxY, minZ, maxZ); +} + +void GxXformViewProjNativeTranspose(C44Matrix& matrix) { + C44Matrix v; + GxXformView(v); + + C44Matrix p; + GxXformProjNative(p); + + matrix = (v * p).Transpose(); +} + +void GxuXformCreateLookAtSgCompat(const C3Vector& eye, const C3Vector& center, const C3Vector& up, C44Matrix& dst) { + C3Vector v16 = { + center.x - eye.x, + center.y - eye.y, + center.z - eye.z + }; + + if (v16.SquaredMag() >= 0.01f && up.SquaredMag() >= 0.01f) { + v16.Normalize(); + + C3Vector v15 = { + up.z * v16.y - up.y * v16.z, + up.x * v16.z - up.z * v16.x, + up.y * v16.x - up.x * v16.y + }; + + v15.Normalize(); + + C3Vector v14 = { + v16.z * v15.y - v16.y * v15.z, + v16.x * v15.z - v16.z * v15.x, + v16.y * v15.x - v16.x * v15.y + }; + + v14.Normalize(); + + dst.a0 = v15.x; + dst.a1 = v14.x; + dst.a2 = v16.x; + + dst.b0 = v15.y; + dst.b1 = v14.y; + dst.b2 = v16.y; + + dst.c0 = v15.z; + dst.c1 = v14.z; + dst.c2 = v16.z; + + C3Vector move = { -eye.x, -eye.y, -eye.z }; + dst.Translate(move); + } +} + +void GxuXformCreateOrtho(float minX, float maxX, float minY, float maxY, float minZ, float maxZ, C44Matrix& dst) { + STORM_ASSERT(minX != maxX); + STORM_ASSERT(minY != maxY); + STORM_ASSERT(minZ < maxZ); + + double v10 = maxX - minX; + double v11 = maxY - minY; + double v12 = maxZ - minZ; + + dst.a0 = 2.0 / v10; + dst.a1 = 0.0f; + dst.a2 = 0.0f; + dst.a3 = 0.0f; + + dst.b0 = 0.0f; + dst.b1 = 2.0 / v11; + dst.b2 = 0.0f; + dst.b3 = 0.0f; + + dst.c0 = 0.0f; + dst.c1 = 0.0f; + dst.c2 = 2.0 / v12; + dst.c3 = 0.0f; + + dst.d0 = 0.0f; + dst.d1 = 0.0f; + dst.d2 = 0.0f; + dst.d3 = 1.0f; +} + +void GxuXformCreateOrthoDepth(float minX, float maxX, float minY, float maxY, float minZ, float maxZ, C44Matrix& dst) { + STORM_ASSERT(minX != maxX); + STORM_ASSERT(minY != maxY); + STORM_ASSERT(minZ < maxZ); + + int32_t stereoEnabled = g_theGxDevicePtr->StereoEnabled(); + + dst.a0 = 2.0 / (maxX - minX); + dst.a1 = 0.0f; + dst.a2 = 0.0f; + dst.a3 = 0.0f; + + dst.b0 = 0.0f; + dst.b1 = 2.0 / (maxY - minY); + dst.b2 = 0.0f; + dst.b3 = 0.0f; + + dst.c0 = 0.0f; + dst.c1 = 0.0f; + dst.c2 = stereoEnabled ? 1.00008f : 2.0 / (maxZ - minZ); + dst.c3 = stereoEnabled ? 1.0f : 0.0f; + + dst.d0 = -((maxX + minX) / (maxX - minX)); + dst.d1 = -((maxY + minY) / (maxY - minY)); + dst.d2 = stereoEnabled ? -0.40001601f : -((maxZ + minZ) / (maxZ - minZ)); + dst.d3 = stereoEnabled ? 0.0f : 1.0f; +} + +void GxuXformCreateProjection_Exact(float fovyInRadians, float aspect, float minZ, float maxZ, C44Matrix& dst) { + STORM_ASSERT(fovyInRadians > 0.0f && fovyInRadians < CMath::PI); + STORM_ASSERT(aspect > 0.0f); + STORM_ASSERT(minZ < maxZ); + + float v8 = fovyInRadians * 0.5; + float v9 = tan(v8); + float v10 = v9 * minZ; + float v11 = v10 * aspect; + float v7 = maxZ - minZ; + + dst.a0 = minZ / v11; + dst.b0 = 0.0f; + dst.c0 = 0.0f; + dst.d0 = 0.0f; + + dst.a1 = 0.0f; + dst.b1 = minZ / v10; + dst.c1 = 0.0f; + dst.d1 = 0.0f; + + dst.a2 = 0.0f; + dst.b2 = 0.0f; + dst.c2 = (minZ + maxZ) / v7; + dst.d2 = minZ * (maxZ * -2.0f) / v7; + + dst.a3 = 0.0f; + dst.b3 = 0.0f; + dst.c3 = 1.0f; + dst.d3 = 0.0f; +} + +void GxuXformCreateProjection_SG(float fov, float aspect, float minZ, float maxZ, C44Matrix& dst) { + float v6 = aspect * aspect + 1.0f; + float v7 = sqrt(v6); + float v8 = 1.0f / v7; + float v9 = v8 * fov; + + GxuXformCreateProjection_Exact(v9, aspect, minZ, maxZ, dst); +} diff --git a/src/gx/Transform.hpp b/src/gx/Transform.hpp new file mode 100644 index 0000000..66bfb13 --- /dev/null +++ b/src/gx/Transform.hpp @@ -0,0 +1,43 @@ +#ifndef GX_TRANSFORM_HPP +#define GX_TRANSFORM_HPP + +#include "gx/Types.hpp" + +class C3Vector; +class C44Matrix; + +void GxXformPop(EGxXform xf); + +void GxXformProjection(C44Matrix&); + +void GxXformProjNative(C44Matrix&); + +void GxXformProjNativeTranspose(C44Matrix&); + +void GxXformPush(EGxXform xf); + +void GxXformSet(EGxXform xf, const C44Matrix& matrix); + +void GxXformSetProjection(const C44Matrix&); + +void GxXformSetView(const C44Matrix&); + +void GxXformSetViewport(float, float, float, float, float, float); + +void GxXformView(C44Matrix&); + +void GxXformViewport(float&, float&, float&, float&, float&, float&); + +void GxXformViewProjNativeTranspose(C44Matrix&); + +void GxuXformCreateLookAtSgCompat(const C3Vector& eye, const C3Vector& center, const C3Vector& up, C44Matrix& dst); + +void GxuXformCreateOrtho(float, float, float, float, float, float, C44Matrix&); + +void GxuXformCreateOrthoDepth(float, float, float, float, float, float, C44Matrix&); + +void GxuXformCreateProjection_Exact(float fovyInRadians, float aspect, float minZ, float maxZ, C44Matrix& dst); + +void GxuXformCreateProjection_SG(float fov, float aspect, float minZ, float maxZ, C44Matrix& dst); + +#endif diff --git a/src/gx/Types.hpp b/src/gx/Types.hpp new file mode 100644 index 0000000..d0b0895 --- /dev/null +++ b/src/gx/Types.hpp @@ -0,0 +1,343 @@ +#ifndef GX_TYPES_HPP +#define GX_TYPES_HPP + +#include + +enum BlitAlpha { + BlitAlpha_0 = 0, + BlitAlpha_1 = 1, + BlitAlpha_8 = 2, + BlitAlpha_Filler = 3, + BlitAlphas_Last = 4 +}; + +enum BlitFormat { + BlitFormat_Unknown = 0, + BlitFormat_Abgr8888 = 1, + BlitFormat_Argb8888 = 2, + BlitFormat_Argb4444 = 3, + BlitFormat_Argb1555 = 4, + BlitFormat_Rgb565 = 5, + BlitFormat_Dxt1 = 6, + BlitFormat_Dxt3 = 7, + BlitFormat_Dxt5 = 8, + BlitFormat_Uv88 = 9, + BlitFormat_Gr1616F = 10, + BlitFormat_R32F = 11, + BlitFormat_D24X8 = 12, + BlitFormats_Last = 13 +}; + +enum EGxApi { + GxApi_OpenGl = 0, + GxApi_D3d9 = 1, + GxApi_D3d9Ex = 2, + GxApi_D3d10 = 3, + GxApi_D3d11 = 4, + GxApi_GLL = 5, + GxApis_Last = 6 +}; + +enum EGxBlend { + GxBlend_Opaque = 0, + GxBlend_AlphaKey = 1, + GxBlend_Alpha = 2, + GxBlend_Add = 3, + GxBlend_Mod = 4, + GxBlend_Mod2x = 5, + GxBlend_ModAdd = 6, + GxBlend_InvSrcAlphaAdd = 7, + GxBlend_InvSrcAlphaOpaque = 8, + GxBlend_SrcAlphaOpaque = 9, + GxBlend_NoAlphaAdd = 10, + GxBlend_ConstantAlpha = 11, + GxBlends_Last = 12 +}; + +enum EGxColorFormat { + GxCF_argb = 0, + GxCF_rgba = 1, + GxColorFormats_Last = 2, +}; + +enum EGxFontHJusts { + GxHJ_Left = 0, + GxHJ_Center = 1, + GxHJ_Right = 2, + GxHJ_Last = 3 +}; + +enum EGxFontVJusts { + GxVJ_Top = 0, + GxVJ_Middle = 1, + GxVJ_Bottom = 2, + GxVJ_Last = 3 +}; + +enum EGxMasterEnables { + GxMasterEnable_Lighting = 0, + GxMasterEnable_Fog = 1, + GxMasterEnable_DepthTest = 2, + GxMasterEnable_DepthWrite = 3, + GxMasterEnable_ColorWrite = 4, + GxMasterEnable_Culling = 5, + GxMasterEnable_DoubleBuffering = 6, + GxMasterEnable_NormalProjection = 7, + GxMasterEnable_PolygonFill = 8, + GxMasterEnables_Last = 9 +}; + +enum EGxPrim { + GxPrim_Points = 0, + GxPrim_Lines = 1, + GxPrim_LineStrip = 2, + GxPrim_Triangles = 3, + GxPrim_TriangleStrip = 4, + GxPrim_TriangleFan = 5, + GxPrims_Last = 6 +}; + +enum EGxPrimMask { + GxPrim_Position = 0x1, + GxPrim_BlendWeight = 0x2, + GxPrim_BlendIndices = 0x4, + GxPrim_Normal = 0x8, + GxPrim_Color0 = 0x10, + GxPrim_Color1 = 0x20, + GxPrim_TexCoord0 = 0x40, + GxPrim_TexCoord1 = 0x80 +}; + +enum EGxRenderState { + GxRs_PolygonOffset = 0, + GxRs_MatDiffuse = 1, + GxRs_MatEmissive = 2, + GxRs_MatSpecular = 3, + GxRs_MatSpecularExp = 4, + GxRs_NormalizeNormals = 5, + GxRs_BlendingMode = 6, + GxRs_AlphaRef = 7, + GxRs_FogStart = 8, + GxRs_FogEnd = 9, + GxRs_FogColor = 10, + GxRs_Lighting = 11, + GxRs_Fog = 12, + GxRs_DepthTest = 13, + GxRs_DepthFunc = 14, + GxRs_DepthWrite = 15, + GxRs_ColorWrite = 16, + GxRs_Culling = 17, + GxRs_ClipPlaneMask = 18, + GxRs_Multisample = 19, + GxRs_ScissorTest = 20, + GxRs_Texture0 = 21, + GxRs_Texture1 = 22, + GxRs_Texture2 = 23, + GxRs_Texture3 = 24, + GxRs_Texture4 = 25, + GxRs_Texture5 = 26, + GxRs_Texture6 = 27, + GxRs_Texture7 = 28, + GxRs_Texture8 = 29, + GxRs_Texture9 = 30, + GxRs_Texture10 = 31, + GxRs_Texture11 = 32, + GxRs_Texture12 = 33, + GxRs_Texture13 = 34, + GxRs_Texture14 = 35, + GxRs_Texture15 = 36, + GxRs_ColorOp0 = 37, + GxRs_ColorOp1 = 38, + GxRs_ColorOp2 = 39, + GxRs_ColorOp3 = 40, + GxRs_ColorOp4 = 41, + GxRs_ColorOp5 = 42, + GxRs_ColorOp6 = 43, + GxRs_ColorOp7 = 44, + GxRs_AlphaOp0 = 45, + GxRs_AlphaOp1 = 46, + GxRs_AlphaOp2 = 47, + GxRs_AlphaOp3 = 48, + GxRs_AlphaOp4 = 49, + GxRs_AlphaOp5 = 50, + GxRs_AlphaOp6 = 51, + GxRs_AlphaOp7 = 52, + GxRs_TexGen0 = 53, + GxRs_TexGen1 = 54, + GxRs_TexGen2 = 55, + GxRs_TexGen3 = 56, + GxRs_TexGen4 = 57, + GxRs_TexGen5 = 58, + GxRs_TexGen6 = 59, + GxRs_TexGen7 = 60, + GxRs_Unk61 = 61, + GxRs_Unk62 = 62, + GxRs_Unk63 = 63, + GxRs_Unk64 = 64, + GxRs_Unk65 = 65, + GxRs_Unk66 = 66, + GxRs_Unk67 = 67, + GxRs_Unk68 = 68, + GxRs_Unk69 = 69, + GxRs_Unk70 = 70, + GxRs_Unk71 = 71, + GxRs_Unk72 = 72, + GxRs_Unk73 = 73, + GxRs_Unk74 = 74, + GxRs_Unk75 = 75, + GxRs_Unk76 = 76, + GxRs_VertexShader = 77, + GxRs_PixelShader = 78, + GxRs_PointScale = 79, + GxRs_PointScaleAttenuation = 80, + GxRs_PointScaleMin = 81, + GxRs_PointScaleMax = 82, + GxRs_PointSprite = 83, + GxRs_Unk84 = 84, + GxRs_ColorMaterial = 85, + GxRenderStates_Last = 86 +}; + +enum EGxShPS { + GxShPS_none = 0, + GxShPS_ps_1_1 = 1, + GxShPS_ps_1_4 = 2, + GxShPS_ps_2_0 = 3, + GxShPS_ps_3_0 = 4, + GxShPS_ps_4_0 = 5, + GxShPS_ps_5_0 = 6, + GxShPS_nvrc = 7, + GxShPS_nvts = 8, + GxShPS_nvts2 = 9, + GxShPS_nvts3 = 10, + GxShPS_nvfp2 = 11, + GxShPS_arbfp1 = 12, + GxShPS_glsl = 13, +}; + +enum EGxShTarget { + GxSh_Vertex = 0, + GxSh_Hull = 1, + GxSh_Domain = 2, + GxSh_Geometry = 3, + GxSh_Pixel = 4, + GxSh_Compute = 5, + GxShTargets_Last = 6 +}; + +enum EGxShVS { + GxShVS_none = 0, + GxShVS_vs_1_1 = 1, + GxShVS_vs_2_0 = 2, + GxShVS_vs_3_0 = 3, + GxShVS_vs_4_0 = 4, + GxShVS_vs_5_0 = 5, + GxShVS_arbvp1 = 6, + GxShVS_arbvp1_cg12 = 7, + GxShVS_nvvp = 8, + GxShVS_nvvp2 = 9, + GxShVS_nvvp3 = 10, + GxShVS_glsl = 11 +}; + +enum EGxTexCommand { + GxTex_Lock = 0, + GxTex_Latch = 1, + GxTex_Unlock = 2, + GxTexCommands_Last = 3 +}; + +enum EGxTexFilter { + GxTex_Nearest = 0x0, + GxTex_Linear = 0x1, + GxTex_NearestMipNearest = 0x2, + GxTex_LinearMipNearest = 0x3, + GxTex_LinearMipLinear = 0x4, + GxTex_Anisotropic = 0x5, + GxTexFilters_Last = 0x6, +}; + +enum EGxTexFormat { + GxTex_Unknown = 0x0, + GxTex_Abgr8888 = 0x1, + GxTex_Argb8888 = 0x2, + GxTex_Argb4444 = 0x3, + GxTex_Argb1555 = 0x4, + GxTex_Rgb565 = 0x5, + GxTex_Dxt1 = 0x6, + GxTex_Dxt3 = 0x7, + GxTex_Dxt5 = 0x8, + GxTex_Uv88 = 0x9, + GxTex_Gr1616F = 0xA, + GxTex_R32F = 0xB, + GxTex_D24X8 = 0xC, + GxTexFormats_Last = 0xD, +}; + +enum EGxTexTarget { + GxTex_2d = 0x0, + GxTex_CubeMap = 0x1, + GxTex_Rectangle = 0x2, + GxTex_NonPow2 = 0x3, + GxTexTargets_Last = 0x4 +}; + +enum EGxTexWrapMode { + GxTex_WrapMode0 = 0, + GxTex_WrapMode1 = 1 +}; + +enum EGxXform { + GxXform_Tex0 = 0, + GxXform_Tex1 = 1, + GxXform_Tex2 = 2, + GxXform_Tex3 = 3, + GxXform_Tex4 = 4, + GxXform_Tex5 = 5, + GxXform_Tex6 = 6, + GxXform_Tex7 = 7, + GxXform_World = 8, + GxXform_Projection = 9, + GxXform_View = 10, + GxXforms_Last = 11 +}; + +enum EGxuDrawListCategory { + GxuCat_0 = 0, + GxuCat_1 = 1, + GxuCat_2 = 2 +}; + +enum COLOR_FILE_FORMAT { + COLOR_JPEG = 0, + COLOR_PAL = 1, + COLOR_DXT = 2, + COLOR_3 = 3 +}; + +enum PIXEL_FORMAT { + PIXEL_DXT1 = 0x0, + PIXEL_DXT3 = 0x1, + PIXEL_ARGB8888 = 0x2, + PIXEL_ARGB1555 = 0x3, + PIXEL_ARGB4444 = 0x4, + PIXEL_RGB565 = 0x5, + PIXEL_A8 = 0x6, + PIXEL_DXT5 = 0x7, + PIXEL_UNSPECIFIED = 0x8, + PIXEL_ARGB2565 = 0x9, + NUM_PIXEL_FORMATS = 0xA +}; + +struct C4Pixel { + char b; + char g; + char r; + char a; +}; + +struct MipBits { + C4Pixel* mip[1]; +}; + +#endif diff --git a/src/gx/Window.cpp b/src/gx/Window.cpp new file mode 100644 index 0000000..b00f488 --- /dev/null +++ b/src/gx/Window.cpp @@ -0,0 +1,35 @@ +#include "gx/Window.hpp" +#include "gx/Device.hpp" + +bool s_forceOnscreen; +Rect s_savedWindowRect; +Rect s_savedWindowZoomedRect; + +int32_t OsGetDefaultWindowRect(tagRECT* rect) { + auto window = g_theGxDevicePtr->DeviceDefWindow(); + + rect->left = window.minX; + rect->top = window.minY; + rect->right = window.maxX; + rect->bottom = window.maxY; + + return 1; +} + +Rect* GetSavedWindowBounds() { + return &s_savedWindowRect; +} + +Rect* GetSavedZoomedWindowBounds() { + return &s_savedWindowZoomedRect; +} + +void SetSavedWindowBounds(Rect rect) { + s_forceOnscreen = true; + s_savedWindowRect = rect; +} + +void SetSavedZoomedWindowBounds(Rect rect) { + s_forceOnscreen = true; + s_savedWindowZoomedRect = rect; +} diff --git a/src/gx/Window.hpp b/src/gx/Window.hpp new file mode 100644 index 0000000..0a188be --- /dev/null +++ b/src/gx/Window.hpp @@ -0,0 +1,36 @@ +#ifndef GX_WINDOW_HPP +#define GX_WINDOW_HPP + +#include + +#if defined(WHOA_SYSTEM_MAC) + #include +#endif + +#if defined(WHOA_SYSTEM_LINUX) || defined(WHOA_SYSTEM_WIN) +struct Rect { + int16_t top; + int16_t left; + int16_t bottom; + int16_t right; +}; +#endif + +struct tagRECT { + int32_t left; + int32_t top; + int32_t right; + int32_t bottom; +}; + +int32_t OsGetDefaultWindowRect(tagRECT* rect); + +Rect* GetSavedWindowBounds(); + +Rect* GetSavedZoomedWindowBounds(); + +void SetSavedWindowBounds(Rect rect); + +void SetSavedZoomedWindowBounds(Rect rect); + +#endif diff --git a/src/gx/buffer/CGxBuf.hpp b/src/gx/buffer/CGxBuf.hpp new file mode 100644 index 0000000..204dce2 --- /dev/null +++ b/src/gx/buffer/CGxBuf.hpp @@ -0,0 +1,37 @@ +#ifndef GX_BUFFER_C_GX_BUF_HPP +#define GX_BUFFER_C_GX_BUF_HPP + +#include +#include + +class CGxPool; + +class CGxBuf : public TSLinkedNode { + public: + // Member variables + CGxPool* m_pool; + uint32_t m_itemSize; + uint32_t m_itemCount; + uint32_t m_size; + uint32_t m_index; + uint8_t unk1C; // TODO + uint8_t unk1D; // TODO + uint8_t unk1E; // TODO + uint8_t unk1F; // TODO + + // Member functions + CGxBuf() = default; + CGxBuf(CGxPool* pool, uint32_t itemSize, uint32_t itemCount, uint32_t index) + : m_pool(pool) + , m_itemSize(itemSize) + , m_itemCount(itemCount) + , m_size(itemSize * itemCount) + , m_index(index) + , unk1C(0) + , unk1D(1) + , unk1E(0) + , unk1F(0) + {}; +}; + +#endif diff --git a/src/gx/buffer/CGxPool.cpp b/src/gx/buffer/CGxPool.cpp new file mode 100644 index 0000000..838a5d4 --- /dev/null +++ b/src/gx/buffer/CGxPool.cpp @@ -0,0 +1,9 @@ +#include "gx/buffer/CGxPool.hpp" + +void CGxPool::Discard() { + for (auto buf = this->m_bufList.Head(); buf; buf = this->m_bufList.Link(buf)->Next()) { + buf->unk1C = 0; + } + + this->unk1C = 0; +} diff --git a/src/gx/buffer/CGxPool.hpp b/src/gx/buffer/CGxPool.hpp new file mode 100644 index 0000000..6271435 --- /dev/null +++ b/src/gx/buffer/CGxPool.hpp @@ -0,0 +1,36 @@ +#ifndef GX_BUFFER_C_GX_POOL_HPP +#define GX_BUFFER_C_GX_POOL_HPP + +#include "gx/buffer/CGxBuf.hpp" +#include "gx/buffer/Types.hpp" +#include +#include + +class CGxPool : public TSLinkedNode { + public: + // Member variables + EGxPoolTarget m_target; + EGxPoolUsage m_usage; + int32_t m_size; + void* m_apiSpecific; + void* m_mem; + int32_t unk1C; // TODO + TSList> m_bufList; + EGxPoolHintBits m_hint; + const char* m_name; + + // Member functions + CGxPool() = default; + CGxPool(EGxPoolTarget target, EGxPoolUsage usage, uint32_t size, EGxPoolHintBits hint, const char* name) + : m_target(target) + , m_usage(usage) + , m_size(size) + , m_apiSpecific(nullptr) + , unk1C(0) + , m_hint(hint) + , m_name(name) + {}; + void Discard(void); +}; + +#endif diff --git a/src/gx/buffer/Types.hpp b/src/gx/buffer/Types.hpp new file mode 100644 index 0000000..93240f3 --- /dev/null +++ b/src/gx/buffer/Types.hpp @@ -0,0 +1,91 @@ +#ifndef GX_BUFFER_TYPES_HPP +#define GX_BUFFER_TYPES_HPP + +#include +#include + +enum EGxPoolHintBits { + GxPoolHintBit_Unk0 = 0, + GxPoolHintBit_Unk1 = 1, + GxPoolHintBit_Unk2 = 2, + GxPoolHintBit_Unk3 = 3 +}; + +enum EGxPoolTarget { + GxPoolTarget_Vertex = 0, + GxPoolTarget_Index = 1, + GxPoolTargets_Last = 2 +}; + +enum EGxPoolUsage { + GxPoolUsage_Static = 0, + GxPoolUsage_Dynamic = 1, + GxPoolUsage_Stream = 2, + GxPoolUsages_Last = 3 +}; + +enum EGxVertexAttrib { + GxVA_Position = 0, + GxVA_BlendWeight = 1, + GxVA_BlendIndices = 2, + GxVA_Normal = 3, + GxVA_Color0 = 4, + GxVA_Color1 = 5, + GxVA_TexCoord0 = 6, + GxVA_TexCoord1 = 7, + GxVA_TexCoord2 = 8, + GxVA_TexCoord3 = 9, + GxVA_TexCoord4 = 10, + GxVA_TexCoord5 = 11, + GxVA_TexCoord6 = 12, + GxVA_TexCoord7 = 13, + GxVAs_Last = 14 +}; + +enum EGxVertexBufferFormat { + GxVBF_P = 0, + GxVBF_PN = 1, + GxVBF_PNC = 2, + GxVBF_PNT = 3, + GxVBF_PNCT = 4, + GxVBF_PNT2 = 5, + GxVBF_PNCT2 = 6, + GxVBF_PC = 7, + GxVBF_PCT = 8, + GxVBF_PCT2 = 9, + GxVBF_PT = 10, + GxVBF_PT2 = 11, + GxVBF_PBNT2 = 12, + GxVBF_PNC2T2 = 13, + GxVertexBufferFormats_Last = 14 +}; + +struct ubyte4 { + union { + uint8_t b[4]; + uint32_t u; + }; +}; + +struct CGxVertexAttrib { + EGxVertexAttrib attrib; + uint32_t type; + uint32_t offset; + uint32_t bufSize; +}; + +struct CGxVertexPBNT2 { + C3Vector p; + ubyte4 bw; + ubyte4 bi; + C3Vector n; + C2Vector tc[2]; +}; + +struct CGxVertexPCT { + C3Vector p; + CImVector c; + C2Vector tc[1]; +}; + +#endif diff --git a/src/gx/font/CGxFont.cpp b/src/gx/font/CGxFont.cpp new file mode 100644 index 0000000..43ce140 --- /dev/null +++ b/src/gx/font/CGxFont.cpp @@ -0,0 +1,928 @@ +#include "gx/font/CGxFont.hpp" +#include "gx/font/FontFace.hpp" +#include "gx/Texture.hpp" +#include +#include +#include +#include +#include + +uint16_t TEXTURECACHE::s_textureData[256 * 256]; + +GLYPHBITMAPDATA::~GLYPHBITMAPDATA() { + if (this->m_data) { + SMemFree(this->m_data, __FILE__, __LINE__, 0x0); + } + + this->m_data = nullptr; +} + +void GLYPHBITMAPDATA::CopyFrom(GLYPHBITMAPDATA* data) { + if (this->m_data) { + SMemFree(this->m_data, __FILE__, __LINE__, 0); + } + + this->m_data = nullptr; + *this = *data; + data->m_data = nullptr; +} + +uint32_t CHARCODEDESC::GapToNextTexture() { + CHARCODEDESC* next = this->textureRowLink.Next(); + + return next + ? next->glyphStartPixel - this->glyphEndPixel - 1 + : 255 - this->glyphEndPixel; +} + +uint32_t CHARCODEDESC::GapToPreviousTexture() { + CHARCODEDESC* previous = this->textureRowLink.Prev(); + + return previous + ? this->glyphStartPixel - previous->glyphEndPixel - 1 + : this->glyphStartPixel; +} + +void CHARCODEDESC::GenerateTextureCoords(uint32_t rowNumber, uint32_t glyphSide) { + this->bitmapData.m_textureCoords.minY = (glyphSide * rowNumber) / 256.0f; + this->bitmapData.m_textureCoords.minX = this->glyphStartPixel / 256.0f; + this->bitmapData.m_textureCoords.maxY = ((glyphSide * rowNumber) + glyphSide) / 256.0f; + this->bitmapData.m_textureCoords.maxX = (this->glyphStartPixel + this->bitmapData.m_glyphCellWidth) / 256.0f; +} + +KERNINGHASHKEY& KERNINGHASHKEY::operator=(const KERNINGHASHKEY& rhs) { + if (this->code != rhs.code) { + this->code = rhs.code; + } + + return *this; +} + +bool KERNINGHASHKEY::operator==(const KERNINGHASHKEY& rhs) { + return this->code == rhs.code; +} + +CHARCODEDESC* TEXTURECACHEROW::CreateNewDesc(GLYPHBITMAPDATA* data, uint32_t rowNumber, uint32_t glyphCellHeight) { + uint32_t glyphWidth = data->m_glyphCellWidth; + + if (this->widestFreeSlot < glyphWidth) { + return nullptr; + } + + if (!this->glyphList.Head()) { + CHARCODEDESC* newCode = this->glyphList.NewNode(2, 0, 0); + + newCode->glyphStartPixel = 0; + newCode->glyphEndPixel = glyphWidth - 1; + newCode->rowNumber = rowNumber; + newCode->bitmapData.CopyFrom(data); + newCode->GenerateTextureCoords(rowNumber, glyphCellHeight); + + this->widestFreeSlot -= glyphWidth; + + return newCode; + } + + uint32_t gapToPrevious = this->glyphList.Head()->GapToPreviousTexture(); + this->widestFreeSlot = gapToPrevious; + + if (gapToPrevious >= glyphWidth) { + void* m = SMemAlloc(sizeof(CHARCODEDESC), __FILE__, __LINE__, 0); + CHARCODEDESC* newCode = new (m) CHARCODEDESC(); + + this->glyphList.LinkNode(newCode, 2, this->glyphList.Head()); + + newCode->glyphEndPixel = this->glyphList.Head()->glyphStartPixel - 1; + newCode->glyphStartPixel = newCode->glyphEndPixel - glyphWidth + 1; + newCode->rowNumber = rowNumber; + newCode->bitmapData.CopyFrom(data); + newCode->GenerateTextureCoords(rowNumber, glyphCellHeight); + + this->widestFreeSlot = this->glyphList.Head() + ? this->glyphList.Head()->GapToPreviousTexture() + : 0; + + for (auto code = this->glyphList.Head(); code; code = this->glyphList.Link(code)->Next()) { + uint32_t gapToNext = code->GapToNextTexture(); + if (gapToNext > this->widestFreeSlot) { + this->widestFreeSlot = gapToNext; + } + } + + return newCode; + } + + int32_t inserted = 0; + CHARCODEDESC* newCode = nullptr; + + for (auto code = this->glyphList.Head(); code; code = this->glyphList.Link(code)->Next()) { + uint32_t gapToNext = code->GapToNextTexture(); + + if (inserted) { + if (gapToNext > this->widestFreeSlot) { + this->widestFreeSlot = gapToNext; + } + + continue; + } + + if (gapToNext >= glyphWidth) { + void* m = SMemAlloc(sizeof(CHARCODEDESC), __FILE__, __LINE__, 0); + newCode = new (m) CHARCODEDESC(); + + this->glyphList.LinkNode(newCode, 1, code); + + newCode->glyphStartPixel = code->glyphEndPixel + 1; + newCode->glyphEndPixel = code->glyphEndPixel + glyphWidth; + newCode->rowNumber = rowNumber; + newCode->bitmapData.CopyFrom(data); + newCode->GenerateTextureCoords(rowNumber, glyphCellHeight); + + inserted = 1; + } + } + + return newCode; +} + +void TEXTURECACHEROW::EvictGlyph(CHARCODEDESC* desc) { + // TODO +} + +void TEXTURECACHE::TextureCallback(EGxTexCommand cmd, uint32_t w, uint32_t h, uint32_t d, uint32_t mipLevel, void* userArg, uint32_t& texelStrideInBytes, const void*& texels) { + TEXTURECACHE* cache = static_cast(userArg); + + switch (cmd) { + case GxTex_Latch: { + for (int32_t i = cache->m_textureRows.Count() - 1; i >= 0; i--) { + auto& cacheRow = cache->m_textureRows[i]; + + for (auto glyph = cacheRow.glyphList.Head(); glyph; glyph = cacheRow.glyphList.Next(glyph)) { + cache->WriteGlyphToTexture(glyph); + } + } + + texelStrideInBytes = 512; + texels = TEXTURECACHE::s_textureData; + + break; + }; + } +} + +CHARCODEDESC* TEXTURECACHE::AllocateNewGlyph(GLYPHBITMAPDATA* data) { + for (int32_t i = 0; i < this->m_textureRows.Count(); i++) { + auto& cacheRow = this->m_textureRows[i]; + CHARCODEDESC* glyph = cacheRow.CreateNewDesc(data, i, this->m_theFace->m_cellHeight); + + if (glyph) { + return glyph; + } + } + + return nullptr; +} + +void TEXTURECACHE::CreateTexture(int32_t filter) { + CGxTexFlags flags = CGxTexFlags(filter ? GxTex_Linear : GxTex_Nearest, 0, 0, 0, 0, 0, 1); + + HTEXTURE texture = TextureCreate( + 256, + 256, + GxTex_Argb4444, + GxTex_Argb4444, + flags, + this, + TEXTURECACHE::TextureCallback, + "GxuFont", + 0 + ); + + this->m_texture = texture; +} + +void TEXTURECACHE::Initialize(CGxFont* face, uint32_t pixelSize) { + this->m_theFace = face; + + uint32_t rowCount = 256 / pixelSize; + this->m_textureRows.SetCount(rowCount); + + for (int32_t i = 0; i < rowCount; i++) { + this->m_textureRows[i].widestFreeSlot = 256; + } +} + +void TEXTURECACHE::PasteGlyph(const GLYPHBITMAPDATA& data, uint16_t* dst) { + if (this->m_theFace->m_flags & FONT_OUTLINE) { + if (this->m_theFace->m_flags & FONT_MONOCHROME) { + this->PasteGlyphOutlinedMonochrome(data, dst); + } else { + this->PasteGlyphOutlinedAA(data, dst); + } + } else if (this->m_theFace->m_flags & FONT_MONOCHROME) { + this->PasteGlyphNonOutlinedMonochrome(data, dst); + } else { + this->PasteGlyphNonOutlinedAA(data, dst); + } +} + +void TEXTURECACHE::PasteGlyphNonOutlinedAA(const GLYPHBITMAPDATA& glyphData, uint16_t* dst) { + auto src = reinterpret_cast(glyphData.m_data); + auto pitch = glyphData.m_glyphPitch; + auto dstCellStride = glyphData.m_glyphCellWidth * 2; + + for (int32_t y = 0; y < glyphData.m_yStart; y++) { + memset(dst, 0, dstCellStride); + dst += 256; + } + + for (int32_t y = 0; y < glyphData.m_glyphHeight; y++) { + for (int32_t x = 0; x < glyphData.m_glyphWidth; x++) { + dst[x] = ((src[x] & 0xF0) << 8) | 0xFFF; + } + + src += pitch; + dst += 256; + } + + auto glyphHeight = glyphData.m_glyphHeight; + auto yStart = glyphData.m_yStart; + if (this->m_theFace->m_cellHeight - glyphHeight - yStart > 0 && this->m_theFace->m_cellHeight - glyphHeight != yStart) { + for (int32_t y = 0; y < this->m_theFace->m_cellHeight - glyphHeight - yStart; y++) { + memset(dst, 0, dstCellStride); + dst += 256; + } + } +} + +void TEXTURECACHE::PasteGlyphNonOutlinedMonochrome(const GLYPHBITMAPDATA& data, uint16_t* dst) { + // TODO +} + +void TEXTURECACHE::PasteGlyphOutlinedAA(const GLYPHBITMAPDATA& glyphData, uint16_t* dst) { + uint32_t v6; + uint32_t v7; + uint16_t* v8; + int32_t v9; + int32_t v15; + int32_t v16; + uint32_t v17; + uint32_t v18; + uint32_t v19; + uint32_t v20; + int32_t v21; + int32_t v22; + int32_t v23; + int32_t v24; + int32_t v25; + uint32_t v26; + uint32_t v27; + uint32_t v28; + bool v29; + bool v30; + int32_t v31; + int32_t v32; + int32_t v33; + int32_t v34; + int32_t v35; + int32_t v36; + bool v37; + int32_t v38; + int32_t v39; + int32_t v40; + uint32_t v41; + uint8_t v42; + bool v43; + uint16_t v44[9216]; + uint16_t v45[9216]; + uint16_t v46[9216]; + uint32_t v49; + uint16_t* v52; + uint32_t v52_2; + uint32_t v52_3; + int32_t v53; + + static uint8_t pixelsLitLevels[] = { + 0, 1, 1, 3, 5, 7, 9, 0xB, 0xD, 0xF, 0, 0 + }; + + const char* src = reinterpret_cast(glyphData.m_data); + uint32_t thick = this->m_theFace->m_flags & 0x8; + + memset(v45, 0, sizeof(v45)); + memset(v46, 0, sizeof(v46)); + + uint32_t ofs = thick + ? 256 * glyphData.m_yStart + 258 + : 256 * glyphData.m_yStart + 257; + + for (int32_t y = 0; y < glyphData.m_glyphHeight; y++) { + for (int32_t x = 0; x < glyphData.m_glyphWidth; x++) { + uint8_t v10 = src[(y * glyphData.m_glyphPitch) + x]; + + if (v10) { + v45[ofs + (y * 256) + x] = v10; + v46[ofs + (y * 256) + x] = 1; + } + } + } + + v52_2 = 0; + + v15 = glyphData.m_yStart - 1; + + for (int32_t i = 0; i < (thick != 0) + 1; i++) { + v49 = 2 * (i != 0) + 2; + v16 = 2 * (i != 0) + 1; + v17 = v15 < 0 ? 0 : v15; + v53 = v15 < 0 ? 0 : v15; + + if (v17 >= this->m_theFace->m_cellHeight) { + continue; + } + + do { + v18 = glyphData.m_glyphCellWidth; + v19 = v17 << 8; + v20 = 0; + + if (!v18) { + goto LABEL_68; + } + + do { + v21 = v19 + v20; + + if (v46[v19 + v20] & v16) { + goto LABEL_66; + } + + if (v53) { + if (v53 == this->m_theFace->m_cellHeight - 1) { + if (v20) { + if (v20 == v18 - 1) { + if ( + v46[v21 - 1] & v16 + || v46[v19 + v20 - 257] & v16 + ) { + goto LABEL_65; + } + + v22 = v46[v19 + v20 - 256]; + } else { + if ( + v46[v19 + v20 - 257] & v16 + || v46[v19 + v20 - 256] & v16 + || v46[v19 + v20 - 255] & v16 + || v46[v21 - 1] & v16 + ) { + goto LABEL_65; + } + + v22 = v46[v21 + 1]; + } + } else { + if ( + v46[v19 - 256] & v16 + || v46[v19 - 255] & v16 + ) { + goto LABEL_65; + } + + v22 = v46[v19 + 1]; + } + } else if (v20) { + v24 = v19 + v20; + + if (v20 == v18 - 1) { + if ( + v46[v24 - 256] & v16 + || v46[v19 + v20 - 257] & v16 + || v46[v21 - 1] & v16 + || v46[v21 + 255] & v16 + ) { + goto LABEL_65; + } + + v22 = v46[v21 + 256]; + } else { + if ( + v46[v24 - 257] & v16 + || v46[v19 + v20 - 256] & v16 + || v46[v19 + v20 - 255] & v16 + || v46[v21 - 1] & v16 + || v46[v21 + 1] & v16 + || v46[v21 + 255] & v16 + || v46[v21 + 256] & v16 + ) { + goto LABEL_65; + } + + v22 = v46[v21 + 257]; + } + } else { + if ( + v46[v19 - 256] & v16 + || v46[v19 - 255] & v16 + || v46[v19 + 1] & v16 + || v46[v19 + 257] & v16 + ) { + goto LABEL_65; + } + + v22 = v46[v19 + 256]; + } +LABEL_64: + if (!(v22 & v16)) { + goto LABEL_66; + } + + goto LABEL_65; + } + + if (v20) { + v23 = v46[v20 - 1]; + + if (v20 == v18 - 1) { + if (!(v23 & v16) && !(v46[v20 + 255] & v16)) { + v22 = v46[v20 + 256]; + goto LABEL_64; + } + } else if (!(v23 & v16) && !(v46[v20 + 1] & v16) && !(v46[v20 + 255] & v16) && !(v46[v20 + 256] & v16)) { + v22 = v46[v20 + 257]; + goto LABEL_64; + } + } else if (!(v46[1] & v16) && !(v46[257] & v16)) { + v22 = v46[256]; + goto LABEL_64; + } +LABEL_65: + v46[v21] = v49; +LABEL_66: + ++v20; + } while (v20 < v18); + + v17 = v53; +LABEL_68: + v53 = ++v17; + } while (v17 < this->m_theFace->m_cellHeight); + } + + memset(v44, 0, sizeof(v44)); + + v26 = 0; + v53 = 0; + + if (!this->m_theFace->m_cellHeight) { + goto LABEL_95; + } + + while (2) { + v27 = glyphData.m_glyphCellWidth; + v28 = v26 << 8; + v25 = 0; + + if (!v27) { + goto LABEL_94; + } + + while (2) { + v37 = v46[v28 + v25] == 0; + v52 = &v46[v28 + v25]; + v29 = !v37; + v30 = !v37; + + if (v26) { + if (v53 == this->m_theFace->m_cellHeight - 1) { + if (!v25) { + v31 = v30 + (v46[v28 + 1] != 0) + (v46[v28 - 255] != 0) + (v46[v28 - 256] != 0); + goto LABEL_92; + } + + v37 = v25 == v27 - 1; + v34 = v28 + v25; + + if (v37) { + v31 = (v46[v28 + v25 - 256] != 0) + (v46[v34 - 257] != 0) + v30 + (*(v52 - 1) != 0); + goto LABEL_92; + } + + v35 = (v46[v34 - 256] != 0) + (v46[v28 + v25 - 257] != 0); + v36 = 0; + v37 = v46[v28 + v25 - 255] == 0; + v38 = v28 + v25; + } else { + if (!v25) { + v31 = v30 + + (v46[v28 + 256] != 0) + + (v46[v28 + 1] != 0) + + (v46[v28 + 257] != 0) + + (v46[v28 - 255] != 0) + + (v46[v28 - 256] != 0); + + goto LABEL_92; + } + + v37 = v25 == v27 - 1; + v39 = v28 + v25; + + if (v37) { + v31 = (v46[v28 + v25 + 256] != 0) + + (v46[v28 + v25 + 255] != 0) + + (v46[v28 + v25 - 256] != 0) + + (v46[v39 - 257] != 0) + + v30 + + (*(v52 - 1) != 0); + + goto LABEL_92; + } + + v37 = v46[v39 - 256] == 0; + v38 = v28 + v25; + v35 = (v46[v28 + v25 + 256] != 0) + + (v46[v28 + v25 + 255] != 0) + + (v46[v28 + v25 - 255] != 0) + + !v37 + + (v46[v28 + v25 - 257] != 0); + v36 = 0; + v37 = v46[v28 + v25 + 257] == 0; + } + + v36 = !v37; + v31 = (v46[v38 + 1] != 0) + v36 + v35 + v30 + (*(v52 - 1) != 0); + } else if (v25) { + if (v25 == v27 - 1) { + v32 = (v46[v25 + 255] != 0) + (v46[v25 - 1] != 0); + v33 = v29 + (v46[v25 + 256] != 0); + } else { + v32 = (v46[v25 + 257] != 0) + + (v46[v25 + 256] != 0) + + (v46[v25 + 255] != 0) + + (v46[v25 - 1] != 0); + v33 = v29 + (v46[v25 + 1] != 0); + } + + v31 = v33 + v32; + } else { + v31 = v29 + (v46[1] != 0) + (v46[257] != 0) + (v46[256] != 0); + } +LABEL_92: + v26 = v53; + + v44[v28 + v25] = pixelsLitLevels[v31]; + + v27 = glyphData.m_glyphCellWidth; + + if (++v25 < v27) { + continue; + } + + break; + } + +LABEL_94: + v53 = ++v26; + + if (v26 < this->m_theFace->m_cellHeight) { + continue; + } + + break; + } + +LABEL_95: + v40 = 0; + + for (int32_t y = 0; y < this->m_theFace->m_cellHeight; y++) { + for (int32_t x = 0; x < glyphData.m_glyphCellWidth; x++) { + if (v46[v40 + x]) { + if (v45[v40 + x]) { + v42 = (255 * v45[v40 + x]) >> 12; + dst[v40 + x] = v42 | (16 * (v42 | (16 * (v42 | (16 * v44[v40 + x]))))); + } else { + dst[v40 + x] = v44[v40 + x] << 12; + } + } else { + dst[v40 + x] = 0; + } + } + + v40 += 256; + } +} + +void TEXTURECACHE::PasteGlyphOutlinedMonochrome(const GLYPHBITMAPDATA& data, uint16_t* dst) { + // TODO +} + +void TEXTURECACHE::UpdateDirty() { + if (this->m_anyDirtyGlyphs && this->m_texture) { + CGxTex* gxTex = TextureGetGxTex(this->m_texture, 1, nullptr); + CiRect updateRect = { 0, 0, 256, 256 }; + GxTexUpdate(gxTex, updateRect, 1); + + this->m_anyDirtyGlyphs = 0; + } +} + +void TEXTURECACHE::WriteGlyphToTexture(CHARCODEDESC* glyph) { + if (!this->m_texture || !this->m_theFace || !this->m_theFace->m_cellHeight) { + return; + } + + uint32_t ofs = glyph->glyphStartPixel + (glyph->rowNumber * this->m_theFace->m_cellHeight << 8); + uint16_t* ptr = &TEXTURECACHE::s_textureData[ofs]; + + this->PasteGlyph(glyph->bitmapData, ptr); +} + +CGxFont::~CGxFont() { + this->Clear(); +} + +int32_t CGxFont::CheckStringGlyphs(const char* string) { + if (!string || !*string) { + return 1; + } + + while (*string) { + int32_t advance; + auto code = SUniSGetUTF8(reinterpret_cast(string), &advance); + + HASHKEY_NONE key = {}; + + if (code != '\r' && code != '\n' && !this->m_activeCharacters.Ptr(code, key)) { + return 0; + } + + string += advance; + } + + return 1; +} + +void CGxFont::Clear() { + if (this->m_faceHandle) { + FontFaceCloseHandle(this->m_faceHandle); + } + + this->m_faceHandle = nullptr; + + this->ClearGlyphs(); +} + +void CGxFont::ClearGlyphs() { + for (int32_t i = 0; i < 8; i++) { + auto& cache = this->m_textureCache[i]; + + if (cache.m_texture) { + HandleClose(this->m_textureCache[i].m_texture); + } + + cache.m_texture = nullptr; + + // TODO + } + + this->m_activeCharacters.Clear(); + + this->m_activeCharacterCache.DeleteAll(); + + this->m_kernInfo.Clear(); +} + +float CGxFont::ComputeStep(uint32_t currentCode, uint32_t nextCode) { + KERNINGHASHKEY kernKey = { nextCode | (currentCode << 16) }; + KERNNODE* kern = this->m_kernInfo.Ptr(currentCode, kernKey); + + if (kern && kern->flags & 0x02) { + return kern->proporportionalSpacing; + } + + auto face = FontFaceGetFace(this->m_faceHandle); + auto currentIndex = FT_Get_Char_Index(face, currentCode); + auto nextIndex = FT_Get_Char_Index(face, nextCode); + + FT_Vector vector; + vector.x = 0; + + if (face->face_flags & FT_FACE_FLAG_KERNING) { + FT_Get_Kerning(face, currentIndex, nextIndex, ft_kerning_unscaled, &vector); + vector.x &= (vector.x >= 0) - 1; + } + + HASHKEY_NONE charKey = {}; + auto activeChar = this->m_activeCharacters.Ptr(currentCode, charKey); + + float advance = 0.0f; + + if (activeChar) { + advance = this->m_flags & 0x08 + ? activeChar->bitmapData.m_glyphAdvance + 1.0f + : activeChar->bitmapData.m_glyphAdvance; + } + + float spacing = (this->m_pixelsPerUnit * vector.x) + advance; + + if (!kern) { + kern = this->m_kernInfo.New(currentCode, kernKey, 0, 0); + } + + kern->flags |= 0x02; + kern->proporportionalSpacing = ceil(spacing); + + return kern->proporportionalSpacing; +} + +float CGxFont::ComputeStepFixedWidth(uint32_t currentCode, uint32_t nextCode) { + // TODO + return 0.0f; +} + +float CGxFont::GetGlyphBearing(const CHARCODEDESC* glyph, bool billboarded, float height) { + if (billboarded) { + float v8 = ScreenToPixelHeight(1, height) / this->GetPixelSize(); + return glyph->bitmapData.m_glyphBearing * v8; + } + + return ceil(glyph->bitmapData.m_glyphBearing); +} + +int32_t CGxFont::GetGlyphData(GLYPHBITMAPDATA* glyphData, uint32_t code) { + FT_Face face = FontFaceGetFace(this->m_faceHandle); + FT_Set_Pixel_Sizes(face, this->m_pixelSize, 0); + + uint32_t v6 = 0; + + if (this->m_flags & 0x8) { + v6 = 4; + } else if (this->m_flags & FONT_OUTLINE) { + v6 = 2; + } + + return IGxuFontGlyphRenderGlyph( + face, + this->m_pixelSize, + code, + this->m_baseline, + glyphData, + this->m_flags & FONT_MONOCHROME, + v6 + ); +} + +const char* CGxFont::GetName(void) const { + STORM_ASSERT(this->m_faceHandle); + + return FontFaceGetFontName(this->m_faceHandle); +} + +uint32_t CGxFont::GetPixelSize() { + return this->m_pixelSize; +} + +int32_t CGxFont::Initialize(const char* name, uint32_t newFlags, float fontHeight) { + SStrPrintf(this->m_fontName, 260, "%s", name); + + this->m_requestedFontHeight = fontHeight; + + this->Clear(); + + this->m_flags = newFlags; + + this->m_faceHandle = FontFaceGetHandle(name, GetFreeTypeLibrary()); + + if (this->m_faceHandle) { + return this->UpdateDimensions(); + } else { + return 0; + } +} + +const CHARCODEDESC* CGxFont::NewCodeDesc(uint32_t code) { + HASHKEY_NONE key = {}; + CHARCODEDESC* charDesc = this->m_activeCharacters.Ptr(code, key); + + if (charDesc) { + this->m_activeCharacterCache.LinkToHead(charDesc); + return charDesc; + } + + GLYPHBITMAPDATA data; + + if (!CGxFont::GetGlyphData(&data, code)) { + return nullptr; + } + + // Attempt to allocate the character off of texture caches + for (uint32_t textureNumber = 0; textureNumber < 8; textureNumber++) { + TEXTURECACHE* textureCache = &this->m_textureCache[textureNumber]; + + if (textureCache->m_texture && TextureGetGxTex(reinterpret_cast(textureCache->m_texture), 1, nullptr)) { + charDesc = textureCache->AllocateNewGlyph(&data); + + if (charDesc) { + charDesc->textureNumber = textureNumber; + break; + } + } else { + textureCache->CreateTexture(this->m_flags & 0x4); + textureCache->Initialize(this, this->m_cellHeight); + + charDesc = textureCache->AllocateNewGlyph(&data); + + if (charDesc) { + charDesc->textureNumber = textureNumber; + } + + break; + } + } + + // No character was allocated from the texture caches, so evict the oldest character and + // attempt to allocate from that character's texture cache row + if (!charDesc) { + CHARCODEDESC* oldestDesc = this->m_activeCharacterCache.Tail(); + + if (oldestDesc) { + uint32_t textureNumber = oldestDesc->textureNumber; + uint32_t rowNumber = oldestDesc->rowNumber; + + TEXTURECACHE* textureCache = &this->m_textureCache[textureNumber]; + TEXTURECACHEROW* cacheRow = &textureCache->m_textureRows[rowNumber]; + cacheRow->EvictGlyph(oldestDesc); + + this->RegisterEvictNotice(textureNumber); + + charDesc = cacheRow->CreateNewDesc(&data, rowNumber, this->m_cellHeight); + + if (charDesc) { + charDesc->rowNumber = rowNumber; + charDesc->textureNumber = textureNumber; + } + } + } + + if (charDesc) { + this->m_activeCharacters.Insert(charDesc, code, key); + this->m_activeCharacterCache.LinkToHead(charDesc); + this->m_textureCache[charDesc->textureNumber].m_anyDirtyGlyphs = 1; + } + + return charDesc; +} + +void CGxFont::RegisterEvictNotice(uint32_t a2) { + // TODO +} + +int32_t CGxFont::UpdateDimensions() { + FT_Face theFace = FontFaceGetFace(this->m_faceHandle); + + float v11 = Sub6C2280(theFace, this->m_requestedFontHeight); + float v3 = 2.0 / GetScreenPixelHeight(); + + if (v11 > v3) { + v3 = v11; + } + + float height = v3; + + uint32_t pixelSize = ScreenToPixelHeight(0, height); + + if (pixelSize <= 32) { + if (!pixelSize) { + // TODO + // nullsub_3(); + } + } else { + pixelSize = 32; + } + + this->m_pixelSize = pixelSize; + + uint32_t v10 = theFace->ascender + abs(theFace->descender); + + if (!v10) { + return 0; + } + + this->m_cellHeight = pixelSize; + float baseline = (double)(pixelSize * theFace->ascender) / (double)v10; + + this->m_baseline = (int64_t)(baseline + 0.5); + + uint32_t flags = this->m_flags; + + if (flags & 0x8) { + this->m_cellHeight = pixelSize + 4; + } else if (flags & 0x1) { + this->m_cellHeight = pixelSize + 2; + } + + int32_t result = FT_Set_Pixel_Sizes(theFace, pixelSize, 0) == FT_Err_Ok; + + this->m_pixelsPerUnit = (double)theFace->size->metrics.x_ppem / (double)theFace->units_per_EM; + + return result; +} diff --git a/src/gx/font/CGxFont.hpp b/src/gx/font/CGxFont.hpp new file mode 100644 index 0000000..2818033 --- /dev/null +++ b/src/gx/font/CGxFont.hpp @@ -0,0 +1,141 @@ +#ifndef GX_C_GX_FONT_HPP +#define GX_C_GX_FONT_HPP + +#include "gx/font/CGxString.hpp" +#include "gx/Font.hpp" +#include "gx/Texture.hpp" +#include +#include +#include + +class CTexture; + +class GLYPHBITMAPDATA { + public: + // Member variables + void* m_data = nullptr; + uint32_t m_dataSize = 0; + uint32_t m_glyphWidth; + uint32_t m_glyphHeight; + uint32_t m_glyphCellWidth; + float m_glyphAdvance; + float m_glyphBearing; + uint32_t m_glyphPitch; + int32_t m_yOffset; + int32_t m_yStart; + CRect m_textureCoords; + + // Member functions + ~GLYPHBITMAPDATA(); + void CopyFrom(GLYPHBITMAPDATA*); +}; + +class CHARCODEDESC : public TSHashObject { + public: + // Member variables + TSLink textureRowLink; + TSLink fontGlyphLink; + uint32_t textureNumber = -1; + uint32_t rowNumber = -1; + uint32_t glyphStartPixel = -1; + uint32_t glyphEndPixel = 0; + GLYPHBITMAPDATA bitmapData; + + // Member functions + uint32_t GapToNextTexture(void); + uint32_t GapToPreviousTexture(void); + void GenerateTextureCoords(uint32_t, uint32_t); +}; + +class KERNINGHASHKEY { + public: + // Member variables + uint32_t code; + + // Member functions + KERNINGHASHKEY& operator=(const KERNINGHASHKEY&); + bool operator==(const KERNINGHASHKEY&); +}; + +class KERNNODE : public TSHashObject { + public: + // Member variables + uint32_t flags = 0x0; + float proporportionalSpacing = 0.0f; + float fixedWidthSpacing = 0.0f; +}; + +class TEXTURECACHEROW { + public: + // Member variables + uint32_t widestFreeSlot = 0; + STORM_EXPLICIT_LIST(CHARCODEDESC, textureRowLink) glyphList; + + // Member functions + CHARCODEDESC* CreateNewDesc(GLYPHBITMAPDATA*, uint32_t, uint32_t); + void EvictGlyph(CHARCODEDESC*); +}; + +class TEXTURECACHE { + public: + // Static variables + static uint16_t s_textureData[256 * 256]; + + // Static functions + static void TextureCallback(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&); + + // Member variables + HTEXTURE m_texture = nullptr; + CGxFont* m_theFace = nullptr; + int8_t m_anyDirtyGlyphs = 0; + int8_t pad[3]; + TSFixedArray m_textureRows; + + // Member functions + CHARCODEDESC* AllocateNewGlyph(GLYPHBITMAPDATA*); + void CreateTexture(int32_t); + void Initialize(CGxFont*, uint32_t); + void PasteGlyph(const GLYPHBITMAPDATA&, uint16_t*); + void PasteGlyphNonOutlinedAA(const GLYPHBITMAPDATA&, uint16_t*); + void PasteGlyphNonOutlinedMonochrome(const GLYPHBITMAPDATA&, uint16_t*); + void PasteGlyphOutlinedAA(const GLYPHBITMAPDATA&, uint16_t*); + void PasteGlyphOutlinedMonochrome(const GLYPHBITMAPDATA&, uint16_t*); + void UpdateDirty(void); + void WriteGlyphToTexture(CHARCODEDESC*); +}; + +class CGxFont : public TSLinkedNode { + public: + // Member variables + STORM_EXPLICIT_LIST(CGxString, m_fontStringLink) m_strings; + TSHashTable m_activeCharacters; + TSHashTable m_kernInfo; + STORM_EXPLICIT_LIST(CHARCODEDESC, fontGlyphLink) m_activeCharacterCache; + HFACE m_faceHandle; + char m_fontName[260]; + uint32_t m_cellHeight = 0; + uint32_t m_baseline; + uint32_t m_flags; + float m_requestedFontHeight; + float m_pixelsPerUnit = 0.0f; + TEXTURECACHE m_textureCache[8]; + uint32_t m_pixelSize; + + // Member functions + ~CGxFont(); + int32_t CheckStringGlyphs(const char*); + void Clear(void); + void ClearGlyphs(void); + float ComputeStep(uint32_t, uint32_t); + float ComputeStepFixedWidth(uint32_t, uint32_t); + float GetGlyphBearing(const CHARCODEDESC*, bool, float); + int32_t GetGlyphData(GLYPHBITMAPDATA*, uint32_t); + const char* GetName(void) const; + uint32_t GetPixelSize(void); + int32_t Initialize(const char*, uint32_t, float); + const CHARCODEDESC* NewCodeDesc(uint32_t); + void RegisterEvictNotice(uint32_t); + int32_t UpdateDimensions(void); +}; + +#endif diff --git a/src/gx/font/CGxString.cpp b/src/gx/font/CGxString.cpp new file mode 100644 index 0000000..1e7a0a1 --- /dev/null +++ b/src/gx/font/CGxString.cpp @@ -0,0 +1,638 @@ +#include "gx/font/CGxString.hpp" +#include "gx/Buffer.hpp" +#include "gx/font/CGxFont.hpp" +#include "gx/font/Wrap.hpp" +#include "gx/Font.hpp" +#include "gx/Gx.hpp" +#include +#include +#include +#include +#include +#include + +TEXTLINETEXTURE* TEXTLINETEXTURE::NewTextLineTexture() { + // TODO + // Allocate off of TEXTLINETEXTURE::s_freeTextLineTextures + + void* m = SMemAlloc(sizeof(TEXTLINETEXTURE), __FILE__, __LINE__, 0x0); + return new (m) TEXTLINETEXTURE(); +} + +void TEXTLINETEXTURE::Recycle(TEXTLINETEXTURE* ptr) { + // TODO if (TEXTLINETEXTURE::s_recycledBytes <= 0x80000) + + if (ptr) { + delete ptr; + } +} + +void TEXTLINETEXTURE::WriteGeometry(CGxVertexPCT* buf, const CImVector& fontColor, const C2Vector& shadowOffset, const CImVector& shadowColor, const C3Vector& viewTranslation, bool a7, bool a8, int32_t ofs, int32_t size) { + if (!size || !this->m_vert.Count()) { + return; + } + + uint32_t colorCount = this->m_vert.Count() == this->m_colors.Count() + ? this->m_colors.Count() + : 0; + + if (ofs >= this->m_vert.Count()) { + return; + } + + uint32_t v24 = this->m_vert.Count() - ofs; + + if (size >= v24) { + size = v24; + } + + if (a7) { + C3Vector shadowTranslation = { + viewTranslation.x + floor(ScreenToPixelWidth(0, shadowOffset.x)), + viewTranslation.y + floor(ScreenToPixelHeight(0, shadowOffset.y)), + viewTranslation.z + }; + + auto color = colorCount ? this->m_colors[ofs] : fontColor; + + for (int32_t i = 0; i < size; i++) { + auto& vert = this->m_vert[i + ofs]; + + C3Vector p = { + vert.vc.x + shadowTranslation.x, + vert.vc.y + shadowTranslation.y, + vert.vc.z + shadowTranslation.z + }; + + buf->p = p; + buf->tc[0] = vert.tc; + + auto formattedShadowColor = shadowColor; + if (a8 && colorCount) { + formattedShadowColor.a = static_cast( + (static_cast(formattedShadowColor.a) * static_cast(color.a)) / 65536.0f + ); + } + GxFormatColor(formattedShadowColor); + buf->c = formattedShadowColor; + + buf++; + } + } + + // if (BATCHEDRENDERFONTDESC::s_billboarded) { + // // TODO + // } + + for (int32_t i = 0; i < size; i++) { + auto& vert = this->m_vert[i + ofs]; + + auto color = colorCount ? this->m_colors[i + ofs] : fontColor; + GxFormatColor(color); + + // if (BATCHEDRENDERFONTDESC::s_billboarded) { + // // TODO + // continue; + // } + + C3Vector p = { + vert.vc.x + viewTranslation.x, + vert.vc.y + viewTranslation.y, + vert.vc.z + viewTranslation.z + }; + + buf->p = p; + buf->tc[0] = vert.tc; + + buf->c = color; + + buf++; + } +} + +CGxString* CGxString::GetNewString(int32_t linkOnList) { + CGxString* string = g_freeStrings.Head(); + + if (string) { + g_strings.LinkToTail(string); + return string; + } + + void* m = SMemAlloc(sizeof(CGxString), __FILE__, __LINE__, 0x8); + string = new (m) CGxString(); + + if (linkOnList) { + g_strings.LinkToTail(string); + } + + return string; +} + +CGxString::~CGxString() { + // TODO +} + +void CGxString::AddShadow(const C2Vector& offset, const CImVector& color) { + this->m_shadowColor = color; + this->m_shadowColor.a = std::min(this->m_shadowColor.a, this->m_fontColor.a); + this->m_shadowOffset = offset; + + this->m_flags |= 0x1; + + if (this->m_flags & 0x20) { + this->ClearInstanceData(); + } +} + +uint32_t CGxString::CalculateVertsNeeded(int32_t line) { + if (this->m_flags & 0x01) { + return 2 * this->m_textLines[line]->m_vert.Count(); + } + + return this->m_textLines[line]->m_vert.Count(); +} + +bool CGxString::CheckGeometry() { + if (this->m_textureEvicted) { + if (!this->m_currentFace->CheckStringGlyphs(this->m_text)) { + this->ClearInstanceData(); + } + + this->m_textureEvicted = 0; + } + + this->CreateGeometry(); + this->m_intD4 = 0; + + return this->m_intB0 != 0; +} + +void CGxString::ClearInstanceData() { + // TODO this->m_hyperlinkInfo->SetCount(0); + // TODO this->m_textureInfo->SetCount(0); + + // TODO this->dwordA4 = 0; + this->m_lastGradientStart = -1; + this->m_lastGradientLength = -1; + this->m_intB0 = 0; + + for (int32_t i = 0; i < 8; i++) { + if (this->m_textLines[i]) { + TEXTLINETEXTURE::Recycle(this->m_textLines[i]); + this->m_textLines[i] = nullptr; + } + } +} + +void CGxString::CreateGeometry() { + if (this->m_intB0 || !this->m_text || !*this->m_text) { + return; + } + + EMBEDDEDPARSEINFO info; + uint32_t flags = this->m_flags; + uint32_t numBytes = 0; + const char* currentText = this->m_text; + const char* nextText = nullptr; + int32_t advance = 0; + uint32_t wide = 0; + int32_t gStart = this->m_lastGradientStart; + int32_t gLength = this->m_lastGradientLength; + CImVector lineColor = this->m_fontColor; + float height = -this->m_currentFontHeight; + C3Vector linePos = { + 0.0f, + this->m_flags & 0x80 ? 0.0f : ScreenToPixelHeight(0, -this->m_currentFontHeight), + 0.0f + }; + bool a10 = true; + float blockWidth = this->m_blockWidth; + + auto v611 = static_cast(CMath::fuint_pi(GetScreenPixelHeight() * this->m_spacing)); + auto v51 = ScreenToPixelHeight(0, this->m_currentFontHeight); + auto spacing = this->m_flags & 0x80 ? this->m_spacing : v611 / static_cast(GetScreenPixelHeight()); + auto v59 = this->m_flags & 0x80 ? this->m_currentFontHeight + this->m_spacing : v611 + v51; + + float v49 = 0.0f; + + while (*currentText) { + if (this->m_blockHeight <= v49) { + break; + } + + if (this->m_flags & 0x2000 && this->m_intB0 == 1) { + blockWidth -= GetIndentNormWidth(); + } + + v49 += this->m_currentFontHeight + spacing; + + QUOTEDCODE code = GxuDetermineQuotedCode(currentText, advance, 0, flags, wide); + + if (code == CODE_NEWLINE) { + currentText += advance; + this->m_intB0++; + linePos.y -= v59; + + // TODO + // info.dword0C -= v59; + // info.dword04 -= v59; + // info.dword34 -= v59; + // info.dword2C -= v59; + + continue; + } + + float extent = 0.0f; + float a8 = 0.0f; + float a11 = 0.0f; + + if (this->m_flags & 0x1) { + a8 = this->m_shadowOffset.x; + } + + CalcWrapPoint( + this->m_currentFace, + currentText, + this->m_currentFontHeight, + blockWidth, + &numBytes, + &extent, + &nextText, + a8, + this->m_flags, + &a10, + &a11, + this->m_scale + ); + + if (nextText == currentText || !nextText || (!numBytes && !*nextText)) { + break; + } + + if (this->m_horzJust == GxHJ_Right) { + float indent = this->m_flags & 0x2000 && this->m_intB0 ? GetIndentPixelWidth() : 0.0f; + linePos.x = ScreenToPixelWidth(this->m_flags & 0x80, -extent) - indent; + } else if (this->m_horzJust == GxHJ_Center) { + linePos.x = ScreenToPixelWidth(this->m_flags & 0x80, -(extent * 0.5f)); + } else if (this->m_flags & 0x2000 && this->m_intB0) { + linePos.x = GetIndentPixelWidth(); + } else { + linePos.x = 0.0f; + } + + float offsetY = 0.0f; + if (v51 < a11) { + if (this->m_vertJust == GxVJ_Bottom) { + offsetY = a11 - v51; + } else if (this->m_vertJust == GxVJ_Middle) { + offsetY = floor((a11 - v51) * 0.5); + } + } + linePos.y -= offsetY; + + // TODO + // info.dword0C -= v62; + // info.dword04 -= v62; + // info.dword34 = linePos.y; + // info.dword2C = linePos.y + v59; + // if (info.dword00) { + // info.dword08 = linePos.x; + // } + + uint32_t texturePagesUsedFlag = 0; + + this->InitializeTextLine(currentText, numBytes, lineColor, linePos, &texturePagesUsedFlag, info); + this->m_intB0++; + + this->m_texturePagesUsed |= texturePagesUsedFlag; + + currentText = nextText; + + linePos.y -= offsetY + v59; + + if (this->m_flags & 0x2) { + break; + } + + // TODO + // info.dword0C -= offsetY + v59; + // info.dword04 -= offsetY + v59; + // info.dword34 = linePos.y; + }; + + this->InitializeViewTranslation(); + + if (this->m_flags & 0x20 && (gStart != -1 || gLength != -1)) { + this->SetGradient(gStart, gLength); + } +} + +int32_t CGxString::Initialize(float fontHeight, const C3Vector& position, float blockWidth, float blockHeight, CGxFont* face, const char* text, EGxFontVJusts vertJust, EGxFontHJusts horzJust, float spacing, uint32_t flags, const CImVector& color, float scale) { + uint32_t textLen = SStrLen(text) + 1; + if (textLen > this->m_textLen) { + if (this->m_text) { + SMemFree(this->m_text, __FILE__, __LINE__, 0x0); + } + + this->m_textLen = textLen; + this->m_text = static_cast(SMemAlloc(textLen, __FILE__, __LINE__, 0x0)); + } + + SStrCopy(this->m_text, text, this->m_textLen); + + this->m_blockWidth = blockWidth; + this->m_blockHeight = blockHeight; + this->m_spacing = spacing; + this->m_position = position; + this->m_horzJust = horzJust; + this->m_vertJust = vertJust; + this->m_flags = flags; + this->m_fontColor = color; + this->m_scale = scale; + this->m_currentFace = face; + + face->m_strings.LinkToTail(this); + + float requestedFontHeight = this->m_flags & 0x4 && !(this->m_flags & 0x80) + ? GxuFontGetOneToOneHeight(face) + : fontHeight; + this->m_requestedFontHeight = requestedFontHeight; + + this->m_currentFontHeight = std::max(this->m_requestedFontHeight, 2.0f / GetScreenPixelHeight()); + + return 1; +} + +void CGxString::InitializeTextLine(const char* currentText, uint32_t numBytes, CImVector& workingColor, const C3Vector& position, uint32_t* texturePagesUsedFlag, EMBEDDEDPARSEINFO& info) { + if (this->m_flags & 0x08) { + // TODO + } + + C3Vector curPos = position; + + float screenPixelHeight = ScreenToPixelHeight(this->m_flags & 0x80, this->m_currentFontHeight); + float glyphToScreenPixels = screenPixelHeight / this->m_currentFace->GetPixelSize(); + float glyphPixelHeight = ScreenToPixelHeight(this->m_flags & 0x80, this->m_currentFontHeight); + + if (this->m_currentFace->m_flags & 0x08) { + glyphPixelHeight += 4.0f; + } else if (this->m_currentFace->m_flags & 0x01) { + glyphPixelHeight += 2.0f; + } + + // TODO + // - billboard adjustment table init + // - EMBEDDEDPARSEINFO update + + float stepGlyph = 0.0f; + float stepScreen = 0.0f; + uint32_t prevCode = 0; + CImVector color; + + while (numBytes && *currentText) { + int32_t advance; + uint32_t code; + CImVector quotedColor; + + QUOTEDCODE quotedCode = GxuDetermineQuotedCode(currentText, advance, "edColor, this->m_flags, code); + + currentText += advance; + numBytes -= advance; + + if (prevCode) { + if (this->m_flags & 0x10) { + // TODO + // stepGlyph = this->m_currentFace->ComputeStepFixedWidth(prevCode, code); + } else { + stepGlyph = this->m_currentFace->ComputeStep(prevCode, code); + } + } + + stepScreen = stepGlyph * glyphToScreenPixels; + + switch (quotedCode) { + case CODE_COLORON: + if (!(this->m_flags & 0x08)) { + color = quotedColor; + color.a = this->m_fontColor.a; + } + + // TODO + // EMBEDDEDPARSEINFO update + + continue; + + case CODE_COLORRESTORE: + color = this->m_fontColor; + + continue; + + case CODE_NEWLINE: + continue; + + case CODE_HYPERLINKSTART: + // TODO + + continue; + + case CODE_HYPERLINKSTOP: + // TODO + + continue; + + case CODE_TEXTURESTART: + // TODO + + continue; + + case CODE_TEXTURESTOP: + continue; + + default: { + auto glyph = this->m_currentFace->NewCodeDesc(code); + + if (!glyph) { + glyph = this->m_currentFace->NewCodeDesc('?'); + } + + if (!glyph) { + continue; + } + + *texturePagesUsedFlag |= 1 << glyph->textureNumber; + + if (!this->m_textLines[glyph->textureNumber]) { + this->m_textLines[glyph->textureNumber] = TEXTLINETEXTURE::NewTextLineTexture(); + } + + auto line = this->m_textLines[glyph->textureNumber]; + + if (!(this->m_flags & 0x08)) { + uint32_t index = line->m_colors.Add(4, 0, &color); + + if (this->m_flags & 0x20) { + // TODO + } + } + + if (!(this->m_flags & 0x80)) { + stepScreen = floor(stepScreen + 0.5f); + } + + curPos.x += stepScreen; + + VERT vert; + vert.vc = { curPos.x, curPos.y, curPos.z }; + vert.tc = { 0.0f, 0.0f }; + + vert.vc.x += this->m_currentFace->GetGlyphBearing( + glyph, + this->m_flags & 0x80, + this->m_currentFontHeight + ); + + if (this->m_currentFace->m_flags & 0x08) { + vert.vc.y -= 2.0f; + } else if (this->m_currentFace->m_flags & 0x01) { + vert.vc.y -= 1.0f; + } + + vert.vc.y += (glyph->bitmapData.m_yOffset * glyphToScreenPixels); + + uint32_t index = line->m_vert.Add(4, 0, &vert); + VERT* verts = &line->m_vert[index]; + + float width = this->m_flags & 0x80 + ? glyph->bitmapData.m_glyphCellWidth * glyphToScreenPixels + : floor(glyph->bitmapData.m_glyphCellWidth * glyphToScreenPixels); + float height = this->m_flags & 0x80 + ? screenPixelHeight + : glyphPixelHeight; + + verts[0].vc.x = verts[1].vc.x; + verts[3].vc.x = verts[3].vc.x + width; + verts[2].vc.x = verts[3].vc.x; + verts[3].vc.y = verts[3].vc.y + height; + verts[1].vc.y = verts[3].vc.y; + verts[0].vc.y = verts[2].vc.y; + + verts[2].tc.y = glyph->bitmapData.m_textureCoords.maxY; + verts[0].tc.y = verts[2].tc.y; + verts[3].tc.y = glyph->bitmapData.m_textureCoords.minY; + verts[1].tc.y = verts[3].tc.y; + verts[1].tc.x = glyph->bitmapData.m_textureCoords.minX; + verts[0].tc.x = verts[1].tc.x; + verts[3].tc.x = glyph->bitmapData.m_textureCoords.maxX; + verts[2].tc.x = verts[3].tc.x; + + if (this->m_flags & 0x80) { + // TODO + // verts[3].tc.y += flt_C7D2E8; + // verts[1].tc.y += flt_C7D2E8; + // verts[1].tc.x += flt_C7D2EC; + // verts[0].tc.x += flt_C7D2EC; + // verts[2].tc.y += flt_C7D2F0; + // verts[0].tc.y += flt_C7D2F0; + // verts[3].tc.x += flt_C7D2F4; + // verts[2].tc.x += flt_C7D2F4; + } + + prevCode = code; + } + } + } + + // TODO + // info.dword10 = curPos.x + stepScreen; +} + +void CGxString::InitializeViewTranslation() { + this->m_viewTranslation = this->m_position; + + if (!this->m_intB0 || this->m_flags & 0x80) { + return; + } + + if (this->m_horzJust == GxHJ_Right) { + this->m_viewTranslation.x += this->m_blockWidth; + } else if (this->m_horzJust == GxHJ_Center) { + this->m_viewTranslation.x += (this->m_blockWidth * 0.5f); + } + + float v13 = ((this->m_spacing * (float)GetScreenPixelHeight()) + 0.9999499917030334f) / (float)GetScreenPixelHeight(); + float v24 = v13 * (float)(this->m_intB0 - 1) + (float)this->m_intB0 * this->m_currentFontHeight; + + if (this->m_intD4 >= 4) { + // TODO + } + + // TODO + // - current font height logic + + if (this->m_vertJust == GxVJ_Bottom) { + this->m_viewTranslation.y += v24; + } else if (this->m_vertJust == GxVJ_Middle) { + this->m_viewTranslation.y += 0.5f * (this->m_blockHeight - v24) + v24; + } else { + this->m_viewTranslation.y += this->m_blockHeight; + } + + this->m_viewTranslation.x = floor((float)GetScreenPixelWidth() * this->m_viewTranslation.x); + this->m_viewTranslation.y = floor((float)GetScreenPixelHeight() * this->m_viewTranslation.y); +} + +void CGxString::Recycle() { + this->m_batchedStringLink.Unlink(); + g_freeStrings.LinkToTail(this); + this->ClearInstanceData(); +} + +void CGxString::SetColor(const CImVector& color) { + if (this->m_fontColor == color) { + return; + } + + this->m_fontColor = color; + this->m_shadowColor.a = std::min(color.a, this->m_shadowColor.a); + + if (!(this->m_flags & 0x8) || this->m_flags & 0x20 || this->m_intD4) { + this->ClearInstanceData(); + } +} + +int32_t CGxString::SetGradient(int32_t startCharacter, int32_t length) { + // TODO +} + +void CGxString::SetStringPosition(const C3Vector& position) { + this->m_position = position; + this->InitializeViewTranslation(); +} + +void CGxString::Tick() { + if (this->m_intB0) { + this->m_intD4++; + + if (this->m_intD4 - 1 > 30) { + this->ClearInstanceData(); + } + } +} + +void CGxString::WriteGeometry(CGxVertexPCT* buf, int32_t line, int32_t ofs, int32_t size) { + auto textLine = this->m_textLines[line]; + + if (textLine) { + textLine->WriteGeometry( + buf, + this->m_fontColor, + this->m_shadowOffset, + this->m_shadowColor, + this->m_viewTranslation, + this->m_flags & 0x01, + this->m_flags & 0x20, + ofs, + size + ); + } +} diff --git a/src/gx/font/CGxString.hpp b/src/gx/font/CGxString.hpp new file mode 100644 index 0000000..ddd4707 --- /dev/null +++ b/src/gx/font/CGxString.hpp @@ -0,0 +1,84 @@ +#ifndef GX_C_GX_STRING_HPP +#define GX_C_GX_STRING_HPP + +#include "gx/Types.hpp" +#include +#include +#include +#include + +struct EMBEDDEDPARSEINFO; +class CGxFont; +class CGxVertexPCT; + +struct VERT { + C3Vector vc; + C2Vector tc; +}; + +class TEXTLINETEXTURE { + public: + // Static functions + static TEXTLINETEXTURE* NewTextLineTexture(void); + static void Recycle(TEXTLINETEXTURE* ptr); + + // Member variables + TSGrowableArray m_vert; + TSGrowableArray m_colors; + + // Member functions + void WriteGeometry(CGxVertexPCT*, const CImVector&, const C2Vector&, const CImVector&, const C3Vector&, bool, bool, int32_t, int32_t); +}; + +class CGxString : public TSLinkedNode { + public: + // Static functions + static CGxString* GetNewString(int32_t linkOnList); + + // Member variables + TSLink m_fontStringLink; + TSLink m_batchedStringLink; + float m_requestedFontHeight = 0.02f; + float m_currentFontHeight = 0.02f; + C3Vector m_position; + CImVector m_fontColor = { 0xFF, 0xFF, 0xFF, 0xFF }; + CImVector m_shadowColor = { 0x00, 0x00, 0x00, 0xFF }; + C2Vector m_shadowOffset = { 0.00125f, -0.00125f }; + float m_blockWidth = 1.0f; + float m_blockHeight = 1.0f; + CGxFont* m_currentFace = nullptr; + char* m_text = nullptr; + int32_t m_textLen = 0; + EGxFontVJusts m_vertJust = GxVJ_Top; + EGxFontHJusts m_horzJust = GxHJ_Left; + float m_spacing = 0.0f; + uint32_t m_flags = 0; + uint32_t m_texturePagesUsed = 0; + int32_t m_textureEvicted = 0; + int32_t m_lastGradientStart = -1; + int32_t m_lastGradientLength = -1; + C3Vector m_viewTranslation; + float m_scale = 1.0f; + int32_t m_intB0 = 0; + TEXTLINETEXTURE* m_textLines[8] = {}; + int32_t m_intD4 = 0; + + // Member functions + ~CGxString(); + void AddShadow(const C2Vector& offset, const CImVector& color); + uint32_t CalculateVertsNeeded(int32_t); + bool CheckGeometry(void); + void ClearInstanceData(void); + void CreateGeometry(void); + int32_t Initialize(float, const C3Vector&, float, float, CGxFont*, const char*, EGxFontVJusts, EGxFontHJusts, float, uint32_t, const CImVector&, float); + void InitializeTextLine(const char*, uint32_t, CImVector&, const C3Vector&, uint32_t*, EMBEDDEDPARSEINFO&); + void InitializeViewTranslation(void); + void Recycle(); + void SetColor(const CImVector&); + int32_t SetGradient(int32_t, int32_t); + void SetStringPosition(const C3Vector& position); + void Tick(); + void WriteGeometry(CGxVertexPCT*, int32_t, int32_t, int32_t); +}; + +#endif diff --git a/src/gx/font/CGxStringBatch.cpp b/src/gx/font/CGxStringBatch.cpp new file mode 100644 index 0000000..f12e17d --- /dev/null +++ b/src/gx/font/CGxStringBatch.cpp @@ -0,0 +1,289 @@ +#include "gx/font/CGxStringBatch.hpp" +#include "gx/font/CGxFont.hpp" +#include "gx/Buffer.hpp" +#include "gx/CGxBatch.hpp" +#include "gx/Device.hpp" +#include "gx/Draw.hpp" +#include "gx/Font.hpp" +#include "gx/Gx.hpp" +#include "gx/RenderState.hpp" +#include "gx/Shader.hpp" +#include "gx/Texture.hpp" +#include "gx/Transform.hpp" +#include +#include + +bool BATCHEDRENDERFONTDESC::s_billboarded; +CGxBuf* BATCHEDRENDERFONTDESC::s_indexBuf; +CGxPool* BATCHEDRENDERFONTDESC::s_indexPool; + +int32_t SetProjection() { + float minX, maxX, minY, maxY, minZ, maxZ; + GxXformViewport(minX, maxX, minY, maxY, minZ, maxZ); + + float v14 = 0.0f; + float v15 = 0.0f; + + if (!GxCaps()->m_pixelCenterOnEdge) { + v14 = -0.5f; + v15 = 0.5f; + } + + C44Matrix proj; + + uint32_t pixelWidth = GetScreenPixelWidth(); + float pixelMinX = floor(minX * pixelWidth); + float pixelMaxX = floor(maxX * pixelWidth); + + if (pixelMinX >= pixelMaxX) { + return 0; + } + + uint32_t pixelHeight = GetScreenPixelHeight(); + float pixelMinY = floor(minY * pixelHeight); + float pixelMaxY = floor(maxY * pixelHeight); + + if (pixelMinY >= pixelMaxY) { + return 0; + } + + if (pixelHeight > pixelMaxY) { + // TODO + } + + pixelMinX += v14; + pixelMaxX += v14; + pixelMinY += v15; + pixelMaxY += v15; + + GxuXformCreateOrthoDepth(pixelMinX, pixelMaxX, pixelMinY, pixelMaxY, -5000.0f, 5000.0f, proj); + GxXformSetProjection(proj); + + return 1; +} + +void BATCHEDRENDERFONTDESC::Initialize() { + CGxPool* indexPool = GxPoolCreate( + GxPoolTarget_Index, + GxPoolUsage_Static, + 6144, + GxPoolHintBit_Unk0, + "BATCHEDRENDERFONTDESC_idx" + ); + + CGxBuf* indexBuf = GxBufCreate( + indexPool, + 2, + 3072, + 0 + ); + + BATCHEDRENDERFONTDESC::s_indexPool = indexPool; + BATCHEDRENDERFONTDESC::s_indexBuf = indexBuf; +} + +void BATCHEDRENDERFONTDESC::InitializeIndexBuff() { + char* indexData = g_theGxDevicePtr->BufLock(BATCHEDRENDERFONTDESC::s_indexBuf); + uint16_t* indexBuf = reinterpret_cast(indexData); + + uint16_t index = 0; + + for (int32_t i = 0; i < 512; i++) { + indexBuf[i * 6 + 0] = index + 0; + indexBuf[i * 6 + 1] = index + 2; + indexBuf[i * 6 + 2] = index + 1; + indexBuf[i * 6 + 3] = index + 2; + indexBuf[i * 6 + 4] = index + 3; + indexBuf[i * 6 + 5] = index + 1; + + index += 4; + } + + GxBufUnlock(BATCHEDRENDERFONTDESC::s_indexBuf, 0); +}; + +CGxVertexPCT* BATCHEDRENDERFONTDESC::UnlockVertexPtrAndRender(CGxBuf*& buf, int32_t count) { + GxBufUnlock(buf, sizeof(CGxVertexPCT) * count); + + if (!BATCHEDRENDERFONTDESC::s_indexBuf->unk1C || !BATCHEDRENDERFONTDESC::s_indexBuf->unk1D) { + BATCHEDRENDERFONTDESC::InitializeIndexBuff(); + } + + if (BATCHEDRENDERFONTDESC::s_indexBuf->unk1C && BATCHEDRENDERFONTDESC::s_indexBuf->unk1D) { + GxPrimVertexPtr(buf, GxVBF_PCT); + GxPrimIndexPtr(BATCHEDRENDERFONTDESC::s_indexBuf); + + CGxBatch batch; + batch.m_primType = GxPrim_Triangles; + batch.m_start = 0; + batch.m_count = 6 * (count / 4); + batch.m_minIndex = 0; + batch.m_maxIndex = count - 1; + + GxDraw(&batch, 1); + } + + return reinterpret_cast(g_theGxDevicePtr->BufLock(buf)); +} + +void BATCHEDRENDERFONTDESC::RenderBatch() { + if (!BATCHEDRENDERFONTDESC::s_indexPool) { + return; + } + + for (auto string = this->m_strings.Head(); string; string = this->m_strings.Next(string)) { + string->CheckGeometry(); + } + + for (int32_t i = 0; i < 8; i++) { + this->m_face->m_textureCache[i].UpdateDirty(); + } + + int32_t maxBatchCapacity = 2048; + + CGxBuf* vertexStream = g_theGxDevicePtr->BufStream(GxPoolTarget_Vertex, 0x18, maxBatchCapacity); + char* vertexData = g_theGxDevicePtr->BufLock(vertexStream); + CGxVertexPCT* vertexBuf = reinterpret_cast(vertexData); + + for (int32_t i = 0; i < 8; i++) { + auto& textureCache = this->m_face->m_textureCache[i]; + auto texture = textureCache.m_texture; + + if (texture) { + auto gxTex = TextureGetGxTex(reinterpret_cast(texture), 1, nullptr); + + if (gxTex) { + GxRsSet(GxRs_Texture0, gxTex); + + for (auto string = this->m_strings.Head(); string; string = this->m_strings.Next(string)) { + auto line = string->m_textLines[i]; + + if (line) { + int32_t vertsNeeded = string->CalculateVertsNeeded(i); + int32_t batchOffset = 0; + int32_t batchCapacity = maxBatchCapacity; + + while (vertsNeeded) { + int32_t batchCount = std::min(vertsNeeded, batchCapacity); + + string->WriteGeometry(vertexBuf, i, batchOffset, batchCount); + + vertsNeeded -= batchCount; + batchOffset += batchCount; + batchCapacity -= batchCount; + vertexBuf += batchCount; + + if (!batchCapacity) { + vertexBuf = this->UnlockVertexPtrAndRender(vertexStream, maxBatchCapacity); + batchCapacity = maxBatchCapacity; + } + } + + if (batchCapacity != maxBatchCapacity) { + vertexBuf = this->UnlockVertexPtrAndRender(vertexStream, maxBatchCapacity - batchCapacity); + batchCapacity = maxBatchCapacity; + } + } + } + } + } + } + + g_theGxDevicePtr->BufUnlock(vertexStream, 0); +} + +CGxStringBatch::~CGxStringBatch() { + this->m_fontBatch.Clear(); +} + +void CGxStringBatch::AddString(CGxString* string) { + STORM_ASSERT(string); + STORM_ASSERT(string->m_currentFace); + + auto face = string->m_currentFace; + uint32_t hashval = reinterpret_cast(face); + HASHKEY_PTR key = { face }; + + auto batch = this->m_fontBatch.Ptr(hashval, key); + + if (!batch) { + batch = this->m_fontBatch.New(hashval, key, 0, 0); + batch->m_face = face; + } + + batch->m_strings.LinkToTail(string); +} + +void CGxStringBatch::RenderBatch() { + // TODO + // if (!g_perf) { + // return; + // } + + C44Matrix oldProjection; + C44Matrix oldView; + + GxXformProjection(oldProjection); + GxXformView(oldView); + + GxRsPush(); + + GxRsSet(GxRs_Fog, 0); + GxRsSet(GxRs_Culling, 0); + GxRsSet(GxRs_BlendingMode, GxBlend_Alpha); + GxRsSetAlphaRef(); + + int32_t setProjection; + int32_t stereoEnabled; + + if (this->m_flags & 0x1) { + BATCHEDRENDERFONTDESC::s_billboarded = true; + + GxRsSet(GxRs_DepthTest, 1); + GxRsSet(GxRs_DepthWrite, 1); + GxRsSet(GxRs_AlphaRef, 1); + + setProjection = 1; + stereoEnabled = 0; + } else { + C44Matrix view; + GxXformSetView(view); + + GxRsSet(GxRs_DepthTest, 0); + GxRsSet(GxRs_DepthWrite, 0); + + setProjection = SetProjection(); + stereoEnabled = g_theGxDevicePtr->StereoEnabled(); + } + + CGxShader* vs = g_fontVertexShader[stereoEnabled ? 1 : 0]; + CGxShader* ps = g_fontPixelShader[0]; + + if (setProjection && vs->Valid() && ps->Valid()) { + GxRsSet(GxRs_VertexShader, vs); + GxRsSet(GxRs_PixelShader, ps); + + C44Matrix viewProjMat; + GxXformViewProjNativeTranspose(viewProjMat); + GxShaderConstantsSet(GxSh_Vertex, 0, reinterpret_cast(&viewProjMat), 4); + + for (auto fontBatch = this->m_fontBatch.Head(); fontBatch; fontBatch = this->m_fontBatch.Next(fontBatch)) { + if (fontBatch->m_strings.Head()) { + fontBatch->RenderBatch(); + + if (this->m_flags & 0x2) { + // TODO + } + } else { + this->m_fontBatch.Unlink(fontBatch); + } + } + } + + BATCHEDRENDERFONTDESC::s_billboarded = false; + + GxRsPop(); + + GxXformSetView(oldView); + GxXformSetProjection(oldProjection); +} diff --git a/src/gx/font/CGxStringBatch.hpp b/src/gx/font/CGxStringBatch.hpp new file mode 100644 index 0000000..4a8b52c --- /dev/null +++ b/src/gx/font/CGxStringBatch.hpp @@ -0,0 +1,45 @@ +#ifndef GX_C_GX_STRING_BATCH_HPP +#define GX_C_GX_STRING_BATCH_HPP + +#include "gx/font/CGxString.hpp" +#include +#include +#include + +class CGxBuf; +class CGxFont; +class CGxPool; + +class BATCHEDRENDERFONTDESC : public TSHashObject { + public: + // Static variables + static bool s_billboarded; + static CGxBuf* s_indexBuf; + static CGxPool* s_indexPool; + + // Static functions + static void Initialize(void); + static void InitializeIndexBuff(void); + CGxVertexPCT* UnlockVertexPtrAndRender(CGxBuf*&, int32_t); + + // Member variables + CGxFont* m_face = nullptr; + STORM_EXPLICIT_LIST(CGxString, m_batchedStringLink) m_strings; + + // Member functions + void RenderBatch(void); +}; + +class CGxStringBatch : public TSLinkedNode { + public: + // Member variables + TSHashTable m_fontBatch; + uint32_t m_flags = 0x0; + + // Member functions + ~CGxStringBatch(); + void AddString(CGxString*); + void RenderBatch(void); +}; + +#endif diff --git a/src/gx/font/FaceData.cpp b/src/gx/font/FaceData.cpp new file mode 100644 index 0000000..ef1f05a --- /dev/null +++ b/src/gx/font/FaceData.cpp @@ -0,0 +1,12 @@ +#include "gx/font/FaceData.hpp" +#include "util/SFile.hpp" + +FACEDATA::~FACEDATA() { + if (this->face) { + FT_Done_Face(this->face); + } + + if (this->data) { + SFile::Unload(this->data); + } +} diff --git a/src/gx/font/FaceData.hpp b/src/gx/font/FaceData.hpp new file mode 100644 index 0000000..63be409 --- /dev/null +++ b/src/gx/font/FaceData.hpp @@ -0,0 +1,17 @@ +#ifndef GX_FONT_FACE_DATA_HPP +#define GX_FONT_FACE_DATA_HPP + +#include "gx/font/FreeType.hpp" +#include "gx/font/Types.hpp" +#include +#include + +class FACEDATA : public CHandleObject, public TSHashObject { + public: + void* data = nullptr; + FT_Face face = nullptr; + HFACE selfReference; + virtual ~FACEDATA(); +}; + +#endif diff --git a/src/gx/font/FontFace.cpp b/src/gx/font/FontFace.cpp new file mode 100644 index 0000000..f03a4f9 --- /dev/null +++ b/src/gx/font/FontFace.cpp @@ -0,0 +1,80 @@ +#include "gx/font/FontFace.hpp" +#include "gx/font/FaceData.hpp" +#include "util/SFile.hpp" +#include +#include + +TSHashTable s_faceHash; + +void FontFaceCloseHandle(HFACE handle) { + STORM_ASSERT(handle); + + HandleClose(handle); + + FACEDATA* dataPtr = reinterpret_cast(handle); + + if (dataPtr->m_refcount <= 1) { + HandleClose(dataPtr->selfReference); + dataPtr->selfReference = nullptr; + } +} + +FT_Face FontFaceGetFace(HFACE handle) { + STORM_ASSERT(handle); + + return reinterpret_cast(handle)->face; +} + +const char* FontFaceGetFontName(HFACE handle) { + STORM_ASSERT(handle); + + return reinterpret_cast(handle)->m_key.m_str; +} + +HFACE FontFaceGetHandle(const char* fileName, FT_Library library) { + if (!library || !fileName || !*fileName) { + return nullptr; + } + + if (auto existing = s_faceHash.Ptr(fileName)) { + return HandleDuplicate(existing->selfReference); + } + + void* data = nullptr; + size_t size; + + if (!SFile::Load(nullptr, fileName, &data, &size, 0, 0x3, nullptr) || !data) { + if (data) { + SFile::Unload(data); + } + + return nullptr; + } + + FT_Face theFace; + + if (FT_New_Memory_Face(library, (FT_Byte*)data, size, 0, &theFace) != FT_Err_Ok || !theFace) { + if (data) { + SFile::Unload(data); + } + + return nullptr; + } + + if (FT_Select_Charmap(theFace, ft_encoding_unicode) != FT_Err_Ok) { + if (data) { + SFile::Unload(data); + } + + return nullptr; + } + + auto faceData = s_faceHash.New(fileName, 0, 0); + auto handle = HandleCreate(faceData); + + faceData->data = data; + faceData->face = theFace; + faceData->selfReference = handle; + + return HandleDuplicate(handle); +} diff --git a/src/gx/font/FontFace.hpp b/src/gx/font/FontFace.hpp new file mode 100644 index 0000000..b6fdca6 --- /dev/null +++ b/src/gx/font/FontFace.hpp @@ -0,0 +1,15 @@ +#ifndef GX_FONT_FONT_FACE_HPP +#define GX_FONT_FONT_FACE_HPP + +#include "gx/font/FreeType.hpp" +#include "gx/font/Types.hpp" + +void FontFaceCloseHandle(HFACE); + +FT_Face FontFaceGetFace(HFACE); + +const char* FontFaceGetFontName(HFACE); + +HFACE FontFaceGetHandle(const char*, FT_Library); + +#endif diff --git a/src/gx/font/FreeType.cpp b/src/gx/font/FreeType.cpp new file mode 100644 index 0000000..2fad0ed --- /dev/null +++ b/src/gx/font/FreeType.cpp @@ -0,0 +1,44 @@ +#include "gx/font/FreeType.hpp" +#include "gx/font/FreeTypeInternal.hpp" +#include "freetype/ftmodule.h" +#include + +FT_Library g_FTLibrary; + +FT_MemoryRec_ s_GxuMemoryRecord = { + nullptr, + &FreeTypeAllocFunction, + &FreeTypeFreeFunction, + &FreeTypeReallocFunction +}; + +void FreeTypeInitialize() { + FT_New_Library(&s_GxuMemoryRecord, &g_FTLibrary); + FT_Add_Default_Modules(g_FTLibrary); +} + +int32_t FREETYPE_RenderGlyph(uint32_t code, bool monochrome, FT_Face face) { + FT_UInt index = FT_Get_Char_Index(face, code); + + if (!index) { + return 0; + } + + FT_Int flags = monochrome + ? FT_LOAD_NO_HINTING | FT_LOAD_CROP_BITMAP | FT_LOAD_PEDANTIC | FT_LOAD_LINEAR_DESIGN + : FT_LOAD_NO_HINTING | FT_LOAD_NO_BITMAP | FT_LOAD_PEDANTIC | FT_LOAD_LINEAR_DESIGN; + + if (FT_Load_Glyph(face, index, flags)) { + return 0; + } + + if (FT_Render_Glyph(face->glyph, monochrome ? ft_render_mode_mono : ft_render_mode_normal)) { + return 0; + } + + return 1; +} + +FT_Library GetFreeTypeLibrary() { + return g_FTLibrary; +}; diff --git a/src/gx/font/FreeType.hpp b/src/gx/font/FreeType.hpp new file mode 100644 index 0000000..59c4ef4 --- /dev/null +++ b/src/gx/font/FreeType.hpp @@ -0,0 +1,13 @@ +#ifndef GX_FONT_FREE_TYPE_HPP +#define GX_FONT_FREE_TYPE_HPP + +#include "freetype/freetype.h" +#include + +void FreeTypeInitialize(); + +int32_t FREETYPE_RenderGlyph(uint32_t, bool, FT_Face); + +FT_Library GetFreeTypeLibrary(); + +#endif diff --git a/src/gx/font/FreeTypeInternal.cpp b/src/gx/font/FreeTypeInternal.cpp new file mode 100644 index 0000000..16f7c96 --- /dev/null +++ b/src/gx/font/FreeTypeInternal.cpp @@ -0,0 +1,16 @@ +#include "gx/font/FreeTypeInternal.hpp" +#include + +void* FreeTypeAllocFunction(FT_Memory memory, long size) { + return SMemAlloc(size, __FILE__, __LINE__, 0); +}; + +void FreeTypeFreeFunction(FT_Memory memory, void* block) { + if (block) { + SMemFree(block, __FILE__, __LINE__, 0); + } +}; + +void* FreeTypeReallocFunction(FT_Memory memory, long currentSize, long newSize, void* block) { + return SMemReAlloc(block, newSize, __FILE__, __LINE__, 0); +}; diff --git a/src/gx/font/FreeTypeInternal.hpp b/src/gx/font/FreeTypeInternal.hpp new file mode 100644 index 0000000..8ed8485 --- /dev/null +++ b/src/gx/font/FreeTypeInternal.hpp @@ -0,0 +1,12 @@ +#ifndef GX_FONT_FREE_TYPE_INTERNAL_HPP +#define GX_FONT_FREE_TYPE_INTERNAL_HPP + +#include "freetype/freetype.h" + +void* FreeTypeAllocFunction(FT_Memory memory, long size); + +void FreeTypeFreeFunction(FT_Memory memory, void* block); + +void* FreeTypeReallocFunction(FT_Memory memory, long currentSize, long newSize, void* block); + +#endif diff --git a/src/gx/font/Types.hpp b/src/gx/font/Types.hpp new file mode 100644 index 0000000..6a6f39a --- /dev/null +++ b/src/gx/font/Types.hpp @@ -0,0 +1,45 @@ +#ifndef GX_FONT_TYPES_HPP +#define GX_FONT_TYPES_HPP + +#include +#include + +typedef HOBJECT HTEXTBLOCK; +typedef HOBJECT HTEXTFONT; +typedef HOBJECT HFACE; + +enum QUOTEDCODE { + CODE_INVALIDCODE = 0x0, + CODE_COLORON = 0x1, + CODE_COLORRESTORE = 0x2, + CODE_NEWLINE = 0x3, + CODE_PIPE = 0x4, + CODE_HYPERLINKSTART = 0x5, + CODE_HYPERLINKSTOP = 0x6, + CODE_TEXTURESTART = 0x7, + CODE_TEXTURESTOP = 0x8, + NUM_QUOTEDCODES = 0x9 +}; + +struct EMBEDDEDPARSEINFO { + // TODO +}; + +struct GXUEMBEDDEDTEXTUREINFO { + float float0 = 0.0f; + float float4 = 0.0f; + float float8 = 0.0f; + float floatC = 0.0f; + uint32_t dword10 = 0; + uint32_t dword14 = 0; + float float18 = 0.0f; + float float1C = 0.0f; + float float20 = 0.0f; + float float24 = 0.0f; + float float28 = 0.0f; + float float2C = 0.0f; + float float30 = 0.0f; + float float34 = 0.0f; +}; + +#endif diff --git a/src/gx/font/Wrap.cpp b/src/gx/font/Wrap.cpp new file mode 100644 index 0000000..81d41bb --- /dev/null +++ b/src/gx/font/Wrap.cpp @@ -0,0 +1,476 @@ +#include "gx/font/Wrap.hpp" +#include "gx/font/CGxFont.hpp" +#include "gx/Font.hpp" +#include +#include +#include + +void CalcWrapPoint(CGxFont* face, const char* currentText, float fontHeight, float blockWidth, uint32_t* numBytes, float* extent, const char** nextText, float a8, uint32_t flags, bool* a10, float* a11, float scale) { + if (fontHeight < 0.0f || blockWidth <= 0.0f || !currentText || !*currentText) { + *numBytes = 0; + *extent = 0.0f; + *nextText = nullptr; + + return; + } + + if (flags & 0x80) { + CalcWrapPointBillboarded( + currentText, + flags, + face, + fontHeight, + numBytes, + extent, + nextText, + a11, + scale + ); + } else { + CalcWrapPointNonBillboarded( + currentText, + face, + fontHeight, + blockWidth, + numBytes, + extent, + nextText, + a8, + flags, + a10, + a11, + scale + ); + } +} + +void CalcWrapPointBillboarded(const char* currentText, uint32_t flags, CGxFont* face, float fontHeight, uint32_t* numBytes, float* extent, const char** nextText, float* a8, float scale) { + // TODO +} + +void CalcWrapPointNonBillboarded(const char* currentText, CGxFont* face, float fontHeight, float blockWidth, uint32_t* numBytes, float* extent, const char** nextText, float a8, uint32_t flags, bool* a10, float* a11, float scale) { + if (fontHeight == 0.0f || flags & 0x4) { + fontHeight = GxuFontGetOneToOneHeight(face); + } + + float v46 = 0.0f; + + if (a8 > 0.0f) { + v46 += ceil(ScreenToPixelWidth(0, a8)); + } + + if (face->m_flags & 0x8) { + v46 += 4.0f; + } else if (face->m_flags & 0x1) { + v46 += 2.0f; + } + + float pixelFontHeight = ScreenToPixelHeight(0, fontHeight); + float v41 = static_cast(face->GetPixelSize()) / pixelFontHeight; + float pixelBlockWidth = ceil(v41 * blockWidth * static_cast(GetScreenPixelWidth())); + + if (a11) { + *a11 = 0.0f; + } + + uint32_t v18 = 0; + uint32_t prevCode = 0; + float v42 = 0.0f; + float v56 = 0.0f; + auto startText = currentText; + int32_t advance = 0; + QUOTEDCODE quotedCode = CODE_NEWLINE; + + while (*currentText) { + uint32_t code; + quotedCode = GxuDetermineQuotedCode(currentText, advance, 0, flags, code); + + if (quotedCode == CODE_NEWLINE) { + currentText += advance; + v18 = currentText - startText; + v56 = v42 + v46; + + break; + } + + if ( + quotedCode == CODE_COLORON + || quotedCode == CODE_COLORRESTORE + || quotedCode == CODE_HYPERLINKSTART + || quotedCode == CODE_HYPERLINKSTOP + || quotedCode == CODE_TEXTURESTOP + ) { + currentText += advance; + + continue; + } + + if (quotedCode == CODE_TEXTURESTART) { + GXUEMBEDDEDTEXTUREINFO textureInfo; + + // TODO + // if (!ParseEmbeddedTexture(currentText, textureInfo, pixelFontHeight, scale, fontHeight)) { + // currentText += advance; + // + // continue; + // } + + float v21 = textureInfo.float1C + v46; + + if (pixelBlockWidth >= v21) { + v46 = v21; + + if (a11 && *a11 < textureInfo.float18) { + *a11 = textureInfo.float18; + } + + currentText += advance; + + continue; + } + + prevCode = code; + advance = 0; + } + + if (face->NewCodeDesc(code)) { + float step = 0.0f; + + if (prevCode) { + if (flags & 0x10) { + step = face->ComputeStepFixedWidth(prevCode, code); + } else { + step = face->ComputeStep(prevCode, code); + } + } + + if (CanWrapBetween(code, prevCode)) { + if (*a10 || v18) { + v18 = currentText - startText; + } + + *a10 = true; + v56 = v42 + v46; + } + + float v38 = GetCharacterWidth(¤tText[advance], flags, code, face, fontHeight); + float v24 = step + v46; + if (pixelBlockWidth < v38 + v24) { + break; + } + + prevCode = code; + v46 = v24; + v42 = v38; + } + + currentText += advance; + } + + v46 = (v42 + v46) / static_cast(GetScreenPixelWidth()); + float v57 = v56 / static_cast(GetScreenPixelWidth()); + v41 = pixelFontHeight / static_cast(face->GetPixelSize()); + + if (quotedCode == CODE_NEWLINE || !*currentText) { + *extent = v46 * v41; + *nextText = currentText; + *numBytes = currentText - startText; + } else { + if (!v18 || flags & 0x2) { + *a10 = flags & 0x40; + *numBytes = currentText - startText; + *extent = v46 * v41; + } else { + *numBytes = v18; + *extent = v57 * v41; + currentText = &startText[v18]; + } + + while (*currentText) { + auto code = SUniSGetUTF8(reinterpret_cast(currentText), &advance); + + if (!iswspace(code)) { + break; + } + + currentText += advance; + } + + *nextText = currentText; + } +} + +int32_t CanWrapBetween(uint32_t codeA, uint32_t codeB) { + if (!codeB) { + return 0; + } + + if (codeB != '-' && codeB != ';' && codeB != '/' && codeA != ('|') && codeA != '\xFF\xFF\xFF\xFF') { + if (iswspace(codeB)) { + return 0; + } + + if (!iswspace(codeA)) { + bool v3, v5; + + if (codeB > 0x3008) { + if (codeB <= 0x3014) { + if (codeB != 0x3014) { + switch (codeB) { + case 0x300A: + case 0x300C: + case 0x300E: + case 0x3010: + return 0; + + default: + goto LABEL_18; + } + } + + return 0; + } + + if (codeB > 0xFF08) { + switch (codeB) { + case 0xFF3B: + case 0xFF5B: + case 0xFFE1: + case 0xFFE5: + case 0xFFE6: + return 0; + + default: + goto LABEL_18; + } + } + + if (codeB == 0xFF08) { + return 0; + } + + if (codeB > 0xFE5B) { + if (codeB == 0xFE5D) { + return 0; + } + + v3 = codeB == 0xFF04; + } else { + if (codeB == 0xFE5B || codeB == 0x301D) { + return 0; + } + + v3 = codeB == 0xFE59; + } + } else { + if (codeB == 0x3008) { + return 0; + } + + if (codeB <= '{') { + if (codeB != '{') { + switch (codeB) { + case '$': + case '(': + case '[': + case '\\': + return 0; + + default: + goto LABEL_18; + } + } + + return 0; + } + + if (codeB == 0x2018 || codeB == 0x201C) { + return 0; + } + + v3 = codeB == 0x2035; + } + + if (v3) { + return 0; + } + +LABEL_18: + if (codeA <= 0x2014) { + if (codeA < 0x2013) { + switch (codeA) { + case '!': + case '%': + case ')': + case ',': + case '.': + case ':': + case ';': + case '?': + case ']': + case '}': + case 0xB0: + case 0xB7: + return 0; + + default: + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + } + + return 0; + } + + if (codeA > 0xFE30) { + if (codeA > 0xFF5D) { + switch (codeA) { + case 0xFF70: + case 0xFF9E: + case 0xFF9F: + case 0xFFE0: + return 0; + + default: + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + } + + if (codeA != 0xFF5D) { + switch (codeA) { + case 0xFE50: + case 0xFE51: + case 0xFE52: + case 0xFE54: + case 0xFE55: + case 0xFE56: + case 0xFE57: + case 0xFE5A: + case 0xFE5C: + case 0xFE5E: + case 0xFF01: + case 0xFF05: + case 0xFF09: + case 0xFF0C: + case 0xFF0E: + case 0xFF1A: + case 0xFF1B: + case 0xFF1F: + case 0xFF3D: + return 0; + + default: + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + } + + return 0; + } + + if (codeA == 0xFE30) { + return 0; + } + + if (codeA > 0x3009) { + switch (codeA) { + case 0x300B: + case 0x300D: + case 0x300F: + case 0x3011: + case 0x3015: + case 0x301E: + case 0x30FC: + return 0; + + default: + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + } + + if (codeA == 0x3009) { + return 0; + } + + if (codeA <= 0x2027) { + if (codeA >= 0x2026 || codeA == 0x2019 || codeA == 0x201D || codeA == 0x2022) { + return 0; + } + + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + + if (codeA > 0x2103) { + if (codeA < 0x3001) { + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + + v5 = codeA <= 0x3002; + } else { + if (codeA == 0x2103) { + return 0; + } + + if (codeA < 0x2032) { + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + + v5 = codeA <= 0x2033; + } + + if (v5) { + return 0; + } + + return codeB == 0x3002 + || codeB == 0xFF0C + || (codeA >= 0x1100 && codeA <= 0x11FF) + || (codeA >= 0x3000 && codeA <= 0xD7AF) + || (codeA >= 0xF900 && codeA <= 0xFAFF) + || (codeA >= 0xFF00 && codeA <= 0xFF9F) + || codeA - 0xFFA0 <= 0x3C; + } + } + + return 1; +} diff --git a/src/gx/font/Wrap.hpp b/src/gx/font/Wrap.hpp new file mode 100644 index 0000000..535a12a --- /dev/null +++ b/src/gx/font/Wrap.hpp @@ -0,0 +1,16 @@ +#ifndef GX_FONT_WRAP_HPP +#define GX_FONT_WRAP_HPP + +#include + +class CGxFont; + +void CalcWrapPoint(CGxFont*, const char*, float, float, uint32_t*, float*, const char**, float, uint32_t, bool*, float*, float); + +void CalcWrapPointBillboarded(const char*, uint32_t, CGxFont*, float, uint32_t*, float*, const char**, float*, float); + +void CalcWrapPointNonBillboarded(const char*, CGxFont*, float, float, uint32_t*, float*, const char**, float, uint32_t, bool*, float*, float); + +int32_t CanWrapBetween(uint32_t codeA, uint32_t codeB); + +#endif diff --git a/src/gx/gll/CGxDeviceGLL.cpp b/src/gx/gll/CGxDeviceGLL.cpp new file mode 100644 index 0000000..2842d7a --- /dev/null +++ b/src/gx/gll/CGxDeviceGLL.cpp @@ -0,0 +1,1312 @@ +#include "gx/gll/CGxDeviceGLL.hpp" +#include "app/mac/View.h" +#include "event/Input.hpp" +#include "gx/Blit.hpp" +#include "gx/CGxBatch.hpp" +#include "gx/Shader.hpp" +#include "gx/Texture.hpp" +#include "gx/Window.hpp" +#include "gx/texture/CGxTex.hpp" +#include +#include +#include + +GLEnum CGxDeviceGLL::s_glCubeMapFaces[] = { + GL_TEXTURE_CUBE_MAP_POSITIVE_X, + GL_TEXTURE_CUBE_MAP_NEGATIVE_X, + GL_TEXTURE_CUBE_MAP_POSITIVE_Y, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, + GL_TEXTURE_CUBE_MAP_POSITIVE_Z, + GL_TEXTURE_CUBE_MAP_NEGATIVE_Z +}; + +GLEnum CGxDeviceGLL::s_glDstBlend[] = { + GL_ZERO, // GxBlend_Opaque + GL_ZERO, // GxBlend_AlphaKey + GL_ONE_MINUS_SRC_ALPHA, // GxBlend_Alpha + GL_ONE, // GxBlend_Add + GL_ZERO, // GxBlend_Mod + GL_SRC_COLOR, // GxBlend_Mod2x + GL_ONE, // GxBlend_ModAdd + GL_ONE, // GxBlend_InvSrcAlphaAdd + GL_ZERO, // GxBlend_InvSrcAlphaOpaque + GL_ZERO, // GxBlend_SrcAlphaOpaque + GL_ONE, // GxBlend_NoAlphaAdd + GL_ONE_MINUS_CONSTANT_ALPHA // GxBlend_ConstantAlpha +}; + +GLEnum CGxDeviceGLL::s_glSrcBlend[] = { + GL_ONE, // GxBlend_Opaque + GL_ONE, // GxBlend_AlphaKey + GL_SRC_ALPHA, // GxBlend_Alpha + GL_SRC_ALPHA, // GxBlend_Add + GL_DST_COLOR, // GxBlend_Mod + GL_DST_COLOR, // GxBlend_Mod2x + GL_DST_COLOR, // GxBlend_ModAdd + GL_ONE_MINUS_SRC_ALPHA, // GxBlend_InvSrcAlphaAdd + GL_ONE_MINUS_SRC_ALPHA, // GxBlend_InvSrcAlphaOpaque + GL_SRC_ALPHA, // GxBlend_SrcAlphaOpaque + GL_ONE, // GxBlend_NoAlphaAdd + GL_CONSTANT_ALPHA // GxBlend_ConstantAlpha +}; + +GLTextureFormat CGxDeviceGLL::s_gxTexFmtToGLLFmt[] = { + GLTF_INVALID, // GxTex_Unknown + GLTF_ABGR8888, // GxTex_Abgr8888 + GLTF_ARGB8888, // GxTex_Argb8888 + GLTF_ARGB4444, // GxTex_Argb4444 + GLTF_ARGB1555, // GxTex_Argb1555 + GLTF_RGB565, // GxTex_Rgb565 + GLTF_DXT1, // GxTex_Dxt1 + GLTF_DXT3, // GxTex_Dxt3 + GLTF_DXT5, // GxTex_Dxt5 + GLTF_INVALID, // GxTex_Uv88 + GLTF_INVALID, // GxTex_Gr1616F + GLTF_INVALID, // GxTex_R32F + GLTF_D24 // GxTex_D24X8 +}; + +GLEnum CGxDeviceGLL::s_poolTarget2BufferFormat[] = { + GL_ZERO, // GxPoolTarget_Vertex + GL_UNSIGNED_SHORT // GxPoolTarget_Index +}; + +GLEnum CGxDeviceGLL::s_poolTarget2BufferType[] = { + GL_ARRAY_BUFFER, // GxPoolTarget_Vertex + GL_ELEMENT_ARRAY_BUFFER // GxPoolTarget_Index +}; + +GLEnum CGxDeviceGLL::s_poolUsage2BufferUsage[] = { + GL_STATIC_DRAW, // GxPoolUsage_Static + GL_DYNAMIC_DRAW, // GxPoolUsage_Dynamic + GL_DYNAMIC_DRAW // GxPoolUsage_Stream +}; + +GLEnum CGxDeviceGLL::s_primitiveConversion[] = { + GL_POINTS, // GxPrim_Points + GL_LINES, // GxPrim_Lines + GL_LINE_STRIP, // GxPrim_LineStrip + GL_TRIANGLES, // GxPrim_Triangles + GL_TRIANGLE_STRIP, // GxPrim_TriangleStrip + GL_TRIANGLE_FAN, // GxPrim_TriangleFan + GL_ZERO // GxPrims_Last +}; + +CGxDeviceGLL::CGxDeviceGLL() : CGxDevice() { + // TODO + + this->m_api = GxApi_GLL; + this->m_caps.m_colorFormat = GxCF_rgba; + + // TODO + + this->DeviceCreatePools(); + this->DeviceCreateStreamBufs(); + + // TODO +} + +char* CGxDeviceGLL::BufLock(CGxBuf* buf) { + CGxDevice::BufLock(buf); + return this->IBufLock(buf); +} + +int32_t CGxDeviceGLL::BufUnlock(CGxBuf* buf, uint32_t size) { + CGxDevice::BufUnlock(buf, size); + return this->IBufUnlock(buf); +} + +void CGxDeviceGLL::CapsWindowSize(CRect& rect) { + CRect windowRect = this->DeviceCurWindow(); + + rect.minX = windowRect.minX; + rect.minY = windowRect.minY; + rect.maxX = windowRect.maxX; + rect.maxY = windowRect.maxY; +} + +void CGxDeviceGLL::CapsWindowSizeInScreenCoords(CRect& dst) { + if (this->IDevIsWindowed()) { + auto windowRect = this->DeviceCurWindow(); + auto deviceRect = this->m_glWindow.GetRect(); + + dst.minX = windowRect.minX + deviceRect.origin.x; + dst.maxX = windowRect.maxX + deviceRect.origin.x; + dst.minY = windowRect.minY + deviceRect.origin.y; + dst.maxY = windowRect.maxY + deviceRect.origin.y; + } else { + dst = this->DeviceCurWindow(); + } +} + +int32_t CGxDeviceGLL::DeviceCreate(long (*windowProc)(void*, uint32_t, uint32_t, long), const CGxFormat& format) { + CGRect rect; + Rect* bounds; + Rect* zoomedBounds = GetSavedZoomedWindowBounds(); + + if ( + zoomedBounds + && zoomedBounds->bottom - zoomedBounds->top > 599 + && zoomedBounds->right - zoomedBounds->left > 799 + ) { + bounds = GetSavedZoomedWindowBounds(); + } else { + bounds = GetSavedWindowBounds(); + } + + if ( + bounds->bottom - bounds->top > 599 + && bounds->right - bounds->left > 799 + ) { + rect.origin.x = bounds->left; + rect.origin.y = bounds->top; + rect.size.width = bounds->right - bounds->left; + rect.size.height = bounds->bottom - bounds->top; + } else { + Rect newBounds = { + 0, + 0, + static_cast(std::floor((static_cast(format.size.y) / static_cast(format.size.x)) * 1024.0f)), + 1024, + }; + + SetSavedWindowBounds(newBounds); + + rect.origin.x = newBounds.left; + rect.origin.y = newBounds.top; + rect.size.width = newBounds.right; + rect.size.height = newBounds.bottom; + } + + this->m_glWindow.SetViewClass(GetEngineViewClass()); + this->m_glWindow.Init(rect, nullptr); + this->m_glWindow.SetTitle("World of Warcraft"); + + this->m_glDevice.Init(&this->m_glWindow, "WoW", 4, GLTF_D24); + + GLDevice::SetOption(GLDevice::eShaderConstantBindings, false); + GLDevice::SetOption(GLDevice::eUseMTGL, true); + + if (CGxDevice::DeviceCreate(windowProc, format)) { + // TODO + // GLDevice::GetGammaFormula(this + 14812, this + 39004); + // sub_D9900(&GLContext::s_CurrentGLContext, (int)sub_720A0, 0, (void (*)(void *))sub_720C0); + // uint32_t v22 = 0; + // GLContext::GetOsGammaTables(v14, 0x100u, this + 2460, this + 2972, this + 3484, &v22); + + GLWindowCallbacks* v15 = new GLWindowCallbacks(); + AssignEngineViewCallbacks(v15); + this->m_glWindow.SetCallbacks(v15); + + this->m_glWindow.Show(); + + // TODO + // (this + 4008) = 1; + + GLDevice::SetOption(GLDevice::eUseHybridShader, true); + + this->ISetCaps(); + + // TODO + // CGxDevice::Log(this, this + 604); + + this->m_context = 1; + + this->ICursorCreate(format); + + return 1; + } else { + // TODO + // (this + 48)(); + + return 0; + } +} + +int32_t CGxDeviceGLL::DeviceSetFormat(const CGxFormat& format) { + static GLTextureFormat gllTexFormats[] = { + GLTF_RGB565, + GLTF_XRGB8888, + GLTF_ARGB8888, + GLTF_A2RGB10, + GLTF_D16, + GLTF_D24, + GLTF_D24S8, + GLTF_D32 + }; + + bool v7 = false; + bool v10 = false; + + Rect v15 = { + 0, + 0, + static_cast(format.size.y), + static_cast(format.size.x) + }; + + if (format.window && format.maximize != 1) { + Rect* zoomedBounds = GetSavedZoomedWindowBounds(); + Rect* bounds; + + if ( + zoomedBounds + && zoomedBounds->bottom - zoomedBounds->top > 599 + && zoomedBounds->right - zoomedBounds->left > 799 + ) { + bounds = GetSavedZoomedWindowBounds(); + } else { + bounds = GetSavedWindowBounds(); + } + + if ( + bounds->bottom - bounds->top > 599 + && bounds->right - bounds->left > 799 + ) { + v15.right = bounds->right - bounds->left; + v15.bottom = bounds->bottom - bounds->top; + } else { + Rect newBounds = { + 0, + 0, + static_cast(std::floor((static_cast(format.size.y) / static_cast(format.size.x)) * 1024.0f)), + 1024, + }; + + SetSavedWindowBounds(newBounds); + + v15.right = newBounds.right; + v15.bottom = newBounds.bottom; + } + + // TODO GxMacPrivate::SetAspectRatio(static_cast(format.size.x) / static_cast(format.size.y)); + + // TODO auto v8 = LMGetMainDevice(); + // TODO GxMacPrivate::ConstrainWindowRect(v15, v8); + + v7 = true; + v10 = true; + } + + CRect wind = { + 0.0f, + 0.0f, + static_cast(v15.bottom - v15.top), + static_cast(v15.right - v15.left) + }; + + this->DeviceSetDefWindow(wind); + + this->m_glDevice.SetDisplay( + v15.right - v15.left, + v15.bottom - v15.top, + gllTexFormats[format.colorFormat], + gllTexFormats[format.depthFormat], + format.refreshRate, + v7, + format.window ^ 1, + format.sampleCount + ); + + OsInputPostEvent(OS_INPUT_SIZE, v15.right - v15.left, v15.bottom - v15.top, 0, 0); + + // TODO + // GameFrame::SetSize(v15.right - v15.left, v15.bottom - v15.top); + + // TODO + // this->m_glDevice.SetVSync(format.vsync != 0); + + this->IStateSetGLLDefaults(); + + if (v10) { + // TODO + // this->m_glDevice.RestoreDefaultGamma(); + } else { + // TODO + // - unknown vtable call + } + + // TODO + // - this seems to belong elsewhere + CGxDevice::DeviceSetFormat(format); + + return 1; +} + +void CGxDeviceGLL::Draw(CGxBatch* batch, int32_t count) { + if (!this->m_context) { + return; + } + + // TODO + // unk conditional check early return + + this->IStateSync(); + this->ValidateDraw(batch, count); + + int32_t v4 = 0; + + // TODO + // if (!this->unk0[154]) { + // v4 = (this->unk6[56] + 24) / (this->unk6[56] + 12); + // } + + if (count) { + this->m_glDevice.DrawIndexed( + CGxDeviceGLL::s_primitiveConversion[batch->m_primType], + batch->m_minIndex, + batch->m_maxIndex, + v4, + batch->m_start + (this->m_primIndexBuf->m_index >> 1), + batch->m_count + ); + } else { + this->m_glDevice.Draw( + CGxDeviceGLL::s_primitiveConversion[batch->m_primType], + v4, + batch->m_count + ); + } +} + +char* CGxDeviceGLL::IBufLock(CGxBuf* buf) { + if (!this->m_context) { + // TODO + // EmergencyMem + return nullptr; + } + + CGxPool* pool = buf->m_pool; + + GLBuffer::eMapFlag mapFlag = GLBuffer::GLMap_None; + + if (pool->m_usage == GxPoolUsage_Stream) { + mapFlag = GLBuffer::GLMap_Unk1; + + uint32_t v7 = pool->unk1C + buf->m_itemSize - 1; + uint32_t alignedNext = v7 - v7 % buf->m_itemSize; + + if (alignedNext + buf->m_size > pool->m_size) { + pool->Discard(); + alignedNext = 0; + mapFlag = GLBuffer::GLMap_Unk2; + } + + buf->m_index = alignedNext; + pool->unk1C = alignedNext + buf->m_size; + } else { + mapFlag = pool->m_usage == GxPoolUsage_Dynamic ? GLBuffer::GLMap_Unk1 : GLBuffer::GLMap_None; + } + + GLBuffer* glBuf = reinterpret_cast(pool->m_apiSpecific); + + if (!glBuf) { + glBuf = this->m_glDevice.CreateBuffer( + CGxDeviceGLL::s_poolTarget2BufferType[pool->m_target], + pool->m_size, + nullptr, + CGxDeviceGLL::s_poolUsage2BufferUsage[pool->m_usage], + CGxDeviceGLL::s_poolTarget2BufferFormat[pool->m_target] + ); + + pool->m_apiSpecific = glBuf; + } + + if (!glBuf) { + // TODO + // EmergencyMem + return nullptr; + } + + return glBuf->Map(buf->m_index, buf->m_size, mapFlag); +} + +int32_t CGxDeviceGLL::IBufUnlock(CGxBuf* buf) { + CGxPool* pool = buf->m_pool; + + // TODO + // EmergencyMem + + auto glBuf = reinterpret_cast(pool->m_apiSpecific); + + glBuf->Unmap(0); + buf->unk1D = 1; + + return 1; +} + +void CGxDeviceGLL::IRsSendToHw(EGxRenderState which) { + auto state = &this->m_appRenderStates[which]; + + switch (which) { + // TODO + // - remaining render states + + case GxRs_BlendingMode: { + int32_t blend = static_cast(state->m_value); + + if (blend <= GxBlend_AlphaKey) { + this->m_glDevice.SetAlphaBlendEnable(0); + } else { + this->m_glDevice.SetAlphaBlendEnable(1); + this->m_glDevice.SetAlphaBlend( + CGxDeviceGLL::s_glSrcBlend[blend], + CGxDeviceGLL::s_glDstBlend[blend], + GL_FUNC_ADD + ); + } + + break; + } + + case GxRs_AlphaRef: { + int32_t alphaRef = static_cast(state->m_value); + + if (alphaRef <= 0) { + this->m_glDevice.SetAlphaTestEnable(0); + } else { + this->m_glDevice.SetAlphaTest(GL_GEQUAL, static_cast(alphaRef) / static_cast(255)); + this->m_glDevice.SetAlphaTestEnable(1); + } + + break; + } + + case GxRs_FogStart: { + auto fogStart = static_cast(state->m_value); + + this->m_glDevice.SetFogParam(GL_FOG_START, fogStart); + + break; + } + + case GxRs_FogEnd: { + auto fogEnd = static_cast(state->m_value); + + this->m_glDevice.SetFogParam(GL_FOG_END, fogEnd); + + break; + } + + case GxRs_FogColor: { + auto fogColor = static_cast(state->m_value); + + this->m_glDevice.SetFogColor( + fogColor.r / 255.0f, + fogColor.g / 255.0f, + fogColor.b / 255.0f, + fogColor.a / 255.0f + ); + + break; + } + + case GxRs_Lighting: { + int32_t lightingEnable = 0; + + if (this->MasterEnable(GxMasterEnable_Lighting)) { + lightingEnable = static_cast(state->m_value); + } + + this->m_glDevice.SetLightingEnable(lightingEnable); + + break; + } + + case GxRs_Fog: { + int32_t fogEnable = 0; + + if (this->MasterEnable(GxMasterEnable_Fog)) { + fogEnable = static_cast(state->m_value); + } + + this->m_glDevice.SetFogEnable(fogEnable); + + break; + } + + case GxRs_DepthTest: { + int32_t depthTest = 0; + + if (this->MasterEnable(GxMasterEnable_DepthTest)) { + depthTest = static_cast(state->m_value); + } + + this->m_glDevice.SetDepthTestEnable(depthTest != 0); + + break; + } + + case GxRs_DepthFunc: { + static GLEnum s_glDepthFunc[] = { + GL_LEQUAL, + GL_EQUAL, + GL_GEQUAL, + GL_LESS, + GL_NONE, + GL_NONE + }; + + GLEnum depthFunc = s_glDepthFunc[static_cast(state->m_value)]; + this->m_glDevice.SetDepthTestFunc(depthFunc); + + break; + } + + case GxRs_DepthWrite: { + int32_t depthWrite = static_cast(state->m_value); + bool depthWriteMask = this->MasterEnable(GxMasterEnable_DepthWrite) && depthWrite + ? 1 : 0; + + this->m_glDevice.SetDepthWriteMask(depthWriteMask); + + break; + } + + case GxRs_Culling: { + static GLEnum cullMode[] = { + GL_ZERO, + GL_CW, + GL_CCW + }; + + // TODO + // - only use state if unk flag 0x20 set, otherwise use 0 + + int32_t mode = static_cast(state->m_value); + + // TODO + // BLIZZARD_ASSERT(mode <= GxCullState_CCW); + + this->m_glDevice.SetCullMode(cullMode[mode]); + + break; + } + + case GxRs_Texture0: + case GxRs_Texture1: + case GxRs_Texture2: + case GxRs_Texture3: + case GxRs_Texture4: + case GxRs_Texture5: + case GxRs_Texture6: + case GxRs_Texture7: + case GxRs_Texture8: + case GxRs_Texture9: + case GxRs_Texture10: + case GxRs_Texture11: + case GxRs_Texture12: + case GxRs_Texture13: + case GxRs_Texture14: + case GxRs_Texture15: { + int32_t tmu = which - GxRs_Texture0; + + if (tmu <= 15) { + CGxTex* texture = static_cast(static_cast(state->m_value)); + + if (texture) { + this->ITexBind(); + this->ITexMarkAsUpdated(texture); + + GLTexture* glTexture = static_cast(texture->m_apiSpecificData); + this->m_glDevice.SetTexture(tmu, glTexture); + } else { + this->m_glDevice.SetTexture(tmu, nullptr); + } + } + + break; + } + + case GxRs_TexGen0: + case GxRs_TexGen1: + case GxRs_TexGen2: + case GxRs_TexGen3: + case GxRs_TexGen4: + case GxRs_TexGen5: + case GxRs_TexGen6: + case GxRs_TexGen7: { + // TODO + + break; + } + + case GxRs_VertexShader: { + auto vs = static_cast(static_cast(state->m_value)); + this->IShaderBindVertex(vs); + + break; + } + + case GxRs_PixelShader: { + auto ps = static_cast(static_cast(state->m_value)); + this->IShaderBindPixel(ps); + + break; + } + + default: { + fprintf(stderr, "Unhandled render state in CGxDeviceGLL::IRsSendToHw: %i\n", which); + + break; + } + } +} + +void CGxDeviceGLL::ISceneBegin() { + if (this->m_context) { + this->ShaderConstantsClear(); + } + + // TODO GameMovie::ReadFrame(this); +} + +void CGxDeviceGLL::ISetCaps() { + // TODO + + this->m_caps.m_pixelCenterOnEdge = 1; + this->m_caps.m_texelCenterOnEdge = 1; + + // TODO +} + +void CGxDeviceGLL::IShaderBindPixel(CGxShader* sh) { + CGxDevice::IShaderBind(); + + if (!sh) { + this->m_glDevice.SetShader(GLShader::ePixelShader, nullptr); + return; + } + + if (!sh->loaded) { + this->IShaderCreatePixel(sh); + } + + BLIZZARD_ASSERT(sh->Valid()); + + this->m_glDevice.SetShader(GLShader::ePixelShader, static_cast(sh->apiSpecific)); +} + +void CGxDeviceGLL::IShaderBindVertex(CGxShader* sh) { + CGxDevice::IShaderBind(); + + if (!sh) { + this->m_glDevice.SetShader(GLShader::eVertexShader, nullptr); + return; + } + + if (!sh->loaded) { + this->IShaderCreateVertex(sh); + } + + BLIZZARD_ASSERT(sh->Valid()); + + this->m_glDevice.SetShader(GLShader::eVertexShader, static_cast(sh->apiSpecific)); +} + +void CGxDeviceGLL::IShaderConstantsFlush() { + // Vertex shader constants + auto vsConst = &CGxDevice::s_shadowConstants[1]; + if (vsConst->unk2 <= vsConst->unk1) { + this->m_glDevice.SetShaderConstants( + GLShader::eVertexShader, + vsConst->unk2, + reinterpret_cast(&vsConst->constants[vsConst->unk2]), + vsConst->unk1 - vsConst->unk2 + 1 + ); + } + vsConst->unk2 = 255; + vsConst->unk1 = 0; + + // Pixel shader constants + auto psConst = &CGxDevice::s_shadowConstants[0]; + if (psConst->unk2 <= psConst->unk1) { + this->m_glDevice.SetShaderConstants( + GLShader::ePixelShader, + psConst->unk2, + reinterpret_cast(&psConst->constants[psConst->unk2]), + psConst->unk1 - psConst->unk2 + 1 + ); + } + psConst->unk2 = 255; + psConst->unk1 = 0; +} + +void CGxDeviceGLL::IShaderCreate(CGxShader* sh) { + if (sh->target == GxSh_Vertex) { + this->IShaderCreateVertex(sh); + } else if (sh->target == GxSh_Pixel) { + this->IShaderCreatePixel(sh); + } +} + +void CGxDeviceGLL::IShaderCreatePixel(CGxShader* ps) { + BLIZZARD_ASSERT(!ps->loaded); + + ps->loaded = 1; + ps->valid = 0; + + unsigned char* codeStr = ps->code.m_data; + uint32_t codeLen = ps->code.Count(); + + if (codeLen) { + this->PatchPixelShader(ps); + + GLShader* glShader = this->m_glDevice.CreateShader( + GLShader::ShaderType::ePixelShader, + codeStr, + codeLen, + ps->m_key.m_str + ); + + glShader->Compile(nullptr); + + ps->apiSpecific = glShader; + ps->valid = 1; + } +} + +void CGxDeviceGLL::IShaderCreateVertex(CGxShader* vs) { + BLIZZARD_ASSERT(!vs->loaded); + + vs->loaded = 1; + vs->valid = 0; + + unsigned char* code = vs->code.m_data; + uint32_t codeLen = vs->code.Count(); + + if (codeLen) { + this->PatchVertexShader(vs); + + GLShader* glShader = this->m_glDevice.CreateShader( + GLShader::ShaderType::eVertexShader, + code, + codeLen, + vs->m_key.m_str + ); + + glShader->Compile(nullptr); + + vs->apiSpecific = glShader; + vs->valid = 1; + } +} + +void CGxDeviceGLL::IStateSetGLLDefaults() { + this->IRsForceUpdate(); + this->IRsSync(0); + + // TODO a1->unk8 = -1; + // TODO a1->unk13 = 0; + + this->m_glDevice.SetIndexBuffer(nullptr); + + GLTexture* a2 = nullptr; + GLTexture* a3 = nullptr; + // TODO this->m_glDevice.GetBackBuffer(&a2, &a3, nullptr); + // TODO this->gllunk2[12] = a2->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + // TODO this->gllunk2[13] = a3->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + + this->m_glDevice.SetFogParam(GL_FOG_DENSITY, 0.0f); + // TODO this->InitLights(); + + // TODO this->gllunk2[10] = 0; + + this->ISceneBegin(); +} + +void CGxDeviceGLL::IStateSync() { + this->IShaderConstantsFlush(); + this->IRsSync(0); + this->IStateSyncXforms(); + this->IStateSyncLights(); + this->IStateSyncEnables(); + this->IStateSyncMaterial(); + // TODO clip plane + this->IStateSyncScissorRect(); + this->IStateSyncVertexPtrs(); + this->IStateSyncIndexPtr(); +} + +void CGxDeviceGLL::IStateSyncEnables() { + // TODO +} + +void CGxDeviceGLL::IStateSyncIndexPtr() { + if (this->m_primIndexDirty) { + this->m_primIndexDirty = 0; + this->m_glDevice.SetIndexBuffer(static_cast(this->m_primIndexBuf->m_pool->m_apiSpecific)); + } +} + +void CGxDeviceGLL::IStateSyncLights() { + // TODO +} + +void CGxDeviceGLL::IStateSyncMaterial() { + // TODO +} + +void CGxDeviceGLL::IStateSyncScissorRect() { + // TODO +} + +void CGxDeviceGLL::IStateSyncVertexPtrs() { + static int32_t gxAttribSlot[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 }; + + static int32_t gxAttribTypeToGllAttribType[] = { + GLVT_UBYTE4N, + GLVT_UBYTE4, + GLVT_UBYTE4N, + GLVT_FLOAT2, + GLVT_FLOAT3, + GLVT_SHORT2N, + GLVT_FLOAT1 + }; + + if (this->m_primVertexFormat < GxVertexBufferFormats_Last) { + GLVertexFormat* glFormat = &this->m_glFormats[this->m_primVertexFormat]; + + if (glFormat->m_Size) { + this->m_glDevice.SetVertexFormat(glFormat); + + this->m_glDevice.SetVertexBuffer( + 0, + static_cast(this->m_primVertexBuf->m_pool->m_apiSpecific), + this->m_primVertexBuf->m_index, + this->m_primVertexSize + ); + + return; + } + } + + GLVertexFormat* glFormat = nullptr; + + if (this->m_primVertexFormat < GxVertexBufferFormats_Last) { + glFormat = &this->m_glFormats[this->m_primVertexFormat]; + } + + for (int32_t i = 0; i < GxVAs_Last; i++) { + if ((1 << i) & this->m_primVertexMask) { + CGxVertexAttrib* attrib = &this->m_primVertexFormatAttrib[i]; + + uint32_t j = glFormat->m_Size; + + glFormat->m_Attribs[j].stream = 0; + glFormat->m_Attribs[j].slot = gxAttribSlot[attrib->attrib]; + glFormat->m_Attribs[j].type = gxAttribTypeToGllAttribType[attrib->type]; + glFormat->m_Attribs[j].offset = attrib->offset; + + glFormat->m_Size++; + } + } + + this->m_glDevice.SetVertexFormat(glFormat); + + this->m_glDevice.SetVertexBuffer( + 0, + static_cast(this->m_primVertexBuf->m_pool->m_apiSpecific), + this->m_primVertexBuf->m_index, + this->m_primVertexSize + ); +} + +void CGxDeviceGLL::IStateSyncXforms() { + // TODO this->IXformSetWorld(); + // TODO this->IXformSetTex(); + + this->IXformSetViewport(); +} + +void CGxDeviceGLL::ITexCreate(CGxTex* gxTex) { + uint32_t width, height, baseMip, mipCount; + this->ITexWHDStartEnd(gxTex, width, height, baseMip, mipCount); + + uint32_t flags = 0; + + flags |= gxTex->m_flags.m_renderTarget ? GLTFLAG_RENDERTARGET : 0x0; + flags |= gxTex->m_format == GxTex_D24X8 ? GLTFLAG_DEPTH : 0x0; + flags |= gxTex->m_flags.m_generateMipMaps ? GLTFLAG_AUTOGEN_MIPMAP : 0x0; + + // TODO + // 2 bits, appearing at m_maxAnisotropy + 1 bit + // flags |= gxTex->m_flags & 0xC000 : GLTFLAG_READ_ACCESS : 0x0; + + if (gxTex->m_target == 1) { + gxTex->m_apiSpecificData = this->m_glDevice.CreateTextureCubeMap( + width, + mipCount - baseMip, + CGxDeviceGLL::s_gxTexFmtToGLLFmt[gxTex->m_format], + flags + ); + } else { + gxTex->m_apiSpecificData = this->m_glDevice.CreateTexture2D( + width, + height, + mipCount - baseMip, + CGxDeviceGLL::s_gxTexFmtToGLLFmt[gxTex->m_format], + flags + ); + + if (gxTex->m_flags.m_renderTarget) { + gxTex->m_apiSpecificData2 = this->m_glDevice.CreateTexture2D( + width, + height, + mipCount - baseMip, + GLTF_D24, + flags | GLTFLAG_DEPTH + ); + } + } + + this->ITexSetFlags(gxTex); + + // TODO + // CGxDevice::IOnAcquireCPUTex(); + + gxTex->m_needsCreation = 0; +} + +void CGxDeviceGLL::ITexMarkAsUpdated(CGxTex* texId) { + if (texId->m_needsFlagUpdate && !texId->m_needsCreation && (texId->m_apiSpecificData || texId->m_apiSpecificData2)) { + this->ITexSetFlags(texId); + } + + if (texId->m_needsUpdate/* TODO && (this + 4000) */) { + if (texId->m_needsCreation || (!texId->m_apiSpecificData && !texId->m_apiSpecificData2)) { + this->ITexCreate(texId); + } + + if (!texId->m_needsCreation && (texId->m_apiSpecificData || texId->m_apiSpecificData2)) { + if (texId->m_userFunc) { + this->ITexUpload(texId); + } + + CGxDevice::ITexMarkAsUpdated(texId); + } + } +} + +void CGxDeviceGLL::ITexSetFlags(CGxTex* texId) { + GLTexture* v2 = static_cast(texId->m_apiSpecificData); + GLLTexSetFlags(texId, v2); + texId->m_needsFlagUpdate = 0; +} + +void CGxDeviceGLL::ITexUpload(CGxTex* texId) { + uint32_t texelStrideInBytes; + const void* texels = nullptr; + + texId->m_userFunc( + GxTex_Lock, + texId->m_width, + texId->m_height, + 0, + 0, + texId->m_userArg, + texelStrideInBytes, + texels + ); + + uint32_t width, height, baseMip, mipCount; + this->ITexWHDStartEnd(texId, width, height, baseMip, mipCount); + + int32_t numFace = texId->m_target == GxTex_CubeMap ? 6 : 1; + + for (int32_t face = 0; face < numFace; face++) { + for (int32_t mipLevel = baseMip; mipLevel < mipCount; mipLevel++) { + texels = nullptr; + + texId->m_userFunc( + GxTex_Latch, + texId->m_width >> mipLevel, + texId->m_height >> mipLevel, + face, + mipLevel, + texId->m_userArg, + texelStrideInBytes, + texels + ); + + BLIZZARD_ASSERT(texels != nullptr || texId->m_flags.m_renderTarget); + + if (!texId->m_flags.m_renderTarget) { + GLMipmap* mipmap = static_cast(texId->m_apiSpecificData)->GetMipmap(mipLevel - baseMip, CGxDeviceGLL::s_glCubeMapFaces[face]); + + CiRect lockRect = { + texId->m_updateRect.minY >> mipLevel, + texId->m_updateRect.minX >> mipLevel, + (texId->m_updateRect.maxY >> mipLevel) + 1, + (texId->m_updateRect.maxX >> mipLevel) + 1 + }; + + lockRect.maxY = std::min(lockRect.maxY, static_cast(height)); + lockRect.maxX = std::min(lockRect.maxX, static_cast(width)); + + BLIZZARD_ASSERT(lockRect.minX >= 0 && lockRect.maxX <= static_cast(width)); + BLIZZARD_ASSERT(lockRect.minY >= 0 && lockRect.maxY <= static_cast(height)); + + GLRect rect = { + lockRect.minX, + lockRect.minY, + lockRect.maxX - lockRect.minX, + lockRect.maxY - lockRect.minY + }; + + if (mipmap->GetFormatInfo().m_IsCompressed) { + int32_t v17 = rect.width + (rect.left & 3); + rect.left &= 0xFFFFFFFC; + rect.width = (v17 + 3) & 0xFFFFFFFC; + rect.width = std::max(rect.width, 4); + + int32_t v20 = rect.height + (rect.top & 3); + rect.top &= 0xFFFFFFFC; + rect.height = (v20 + 3) & 0xFFFFFFFC; + rect.height = std::max(rect.height, 4); + } + + const void* src = texels; + + if (texId->m_flags.m_bit15) { + src = texels; + } else if (texId->m_dataFormat == GxTex_Dxt1 || texId->m_dataFormat == GxTex_Dxt3 || texId->m_dataFormat == GxTex_Dxt5) { + uint32_t bytesPerBlock = CGxDevice::s_texFormatBytesPerBlock[texId->m_dataFormat]; + uint32_t offset = (bytesPerBlock * (rect.left >> 2)) + (texelStrideInBytes * (rect.top >> 2)); + + src = static_cast(texels) + offset; + } else { + uint32_t bitDepth = CGxDevice::s_texFormatBitDepth[texId->m_dataFormat]; + uint32_t offset = ((bitDepth * rect.left) >> 3) + (texelStrideInBytes * rect.top); + + src = static_cast(texels) + offset; + } + + uint32_t dstStride = mipmap->GetPitch(); + + BlitFormat dstFmt = GxGetBlitFormat(texId->m_format); + + void* dst = mipmap->Map(GL_WRITE_ONLY, &rect); + + BlitFormat srcFmt = GxGetBlitFormat(texId->m_dataFormat); + + C2iVector size = { rect.width, rect.height }; + + Blit(size, BlitAlpha_0, src, texelStrideInBytes, srcFmt, dst, dstStride, dstFmt); + + mipmap->Unmap(); + } + + width = std::max(width >> 1, 1u); + height = std::max(height >> 1, 1u); + } + } + + texId->m_userFunc( + GxTex_Unlock, + texId->m_width, + texId->m_height, + 0, + 0, + texId->m_userArg, + texelStrideInBytes, + texels + ); +} + +void CGxDeviceGLL::IXformSetProjection(const C44Matrix& matrix) { + C44Matrix gllMat = matrix; + + if (!this->MasterEnable(GxMasterEnable_NormalProjection) && matrix.d0 != 1.0f) { + C44Matrix shrink = { + 0.2f, 0.0f, 0.0f, 0.0f, + 0.0f, 0.2f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.2f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + + gllMat = gllMat * shrink; + } + + this->m_glDevice.SetTransform(GL_PROJECTION, reinterpret_cast(&gllMat)); + + this->m_projNative = gllMat; + + this->m_projNative.a1 *= -1.0f; + this->m_projNative.b1 *= -1.0f; + this->m_projNative.c1 *= -1.0f; + this->m_projNative.d1 *= -1.0f; +} + +void CGxDeviceGLL::IXformSetView(const C44Matrix& matrix) { + this->m_glDevice.SetTransform('VIEW', reinterpret_cast(&matrix)); +} + +void CGxDeviceGLL::IXformSetViewport() { + auto window = this->DeviceCurWindow(); + + GLRect viewport = { + static_cast((this->m_viewport.x.l * window.maxX) + 0.5f), + static_cast(((1.0f - this->m_viewport.y.h) * window.maxY) + 0.5f), + static_cast(((this->m_viewport.x.h - this->m_viewport.x.l) * window.maxX) + 0.5f), + static_cast(((this->m_viewport.y.h - this->m_viewport.y.l) * window.maxY) + 0.5f), + }; + + this->m_glDevice.SetViewport(viewport, this->m_viewport.z.l, this->m_viewport.z.h); + + // TODO (this + 4020) = 0; +} + +void CGxDeviceGLL::PatchPixelShader(CGxShader* ps) { + // TODO +} + +void CGxDeviceGLL::PatchVertexShader(CGxShader* vs) { + if (vs->patched) { + return; + } + + vs->patched = 1; + + // TODO properly calculate v2 + int32_t v2 = 192; + + char buf[64]; + + for (int32_t i = 0; i < vs->code.Count(); i++) { + char* str = reinterpret_cast(&vs->code[i]); + + if (!SStrCmp(str, ".local", 6)) { + memcpy(str, ".env ", 6); + + int v16, v12, n; + if (str[6] == '[' && sscanf(&str[4], " [%d..%d]%n", &v16, &v12, &n) == 2 && v12 == 93) { + SStrPrintf(buf, sizeof(buf), "[%d..%d] ", v16, v2 - 1); + memcpy(&str[4], buf, n); + } + } + + int32_t v13, n; + if (sscanf(str, "PARAM c[%d] = { %n", &v13, &n) == 1 && v13 == 94) { + SStrPrintf(buf, sizeof(buf), "PARAM c[%d]={ ", v2); + memcpy(str, buf, n); + } + } +} + +void CGxDeviceGLL::PoolSizeSet(CGxPool* pool, uint32_t size) { + if (pool->m_usage == GxPoolUsage_Stream) { + pool->unk1C = 0; + } + + GLBuffer* buffer = reinterpret_cast(pool->m_apiSpecific); + + if (buffer) { + delete buffer; + } + + pool->m_size = size; + + pool->m_apiSpecific = this->m_glDevice.CreateBuffer( + CGxDeviceGLL::s_poolTarget2BufferType[pool->m_target], + size, + nullptr, + CGxDeviceGLL::s_poolUsage2BufferUsage[pool->m_usage], + CGxDeviceGLL::s_poolTarget2BufferFormat[pool->m_target] + ); +} + +void CGxDeviceGLL::Resize(uint32_t width, uint32_t height) { + this->m_glDevice.Resize(width, height); + + CRect rect = { 0.0f, 0.0f, static_cast(height), static_cast(width) }; + this->DeviceSetDefWindow(rect); +} + +void CGxDeviceGLL::SceneClear(uint32_t mask, CImVector color) { + CGxDevice::SceneClear(mask, color); + + if (!this->m_context) { + return; + } + + uint32_t glMask = 0x0; + + if (mask & 0x1) { + glMask |= GL_COLOR_BUFFER_BIT; + } + + if (mask & 0x2) { + glMask |= GL_DEPTH_BUFFER_BIT; + } + + this->IXformSetViewport(); + this->IStateSyncScissorRect(); + + GLColor4f glColor = { + color.r / 255.0f, + color.g / 255.0f, + color.b / 255.0f, + color.a / 255.0f + }; + + this->m_glDevice.Clear(glMask, glColor, 1.0, 0); +} + +void CGxDeviceGLL::ScenePresent() { + if (this->m_context) { + // TODO + + CGxDevice::ScenePresent(); + + // TODO + + this->m_glDevice.Swap(); + + // TODO + + CGxDevice::ShaderConstantsClear(); + } + + // TODO +} + +void CGxDeviceGLL::ShaderCreate(CGxShader* shaders[], EGxShTarget target, const char* a4, const char* a5, int32_t permutations) { + CGxDevice::ShaderCreate(shaders, target, a4, a5, permutations); + + if (permutations == 1 && !shaders[0]->loaded) { + if (target == GxSh_Vertex) { + this->IShaderCreateVertex(shaders[0]); + } else if (target == GxSh_Pixel) { + this->IShaderCreatePixel(shaders[0]); + } + } +} + +int32_t CGxDeviceGLL::StereoEnabled() { + return 0; +} + +void CGxDeviceGLL::TexDestroy(CGxTex* texId) { + BLIZZARD_ASSERT(texId); + + auto texture = static_cast(texId->m_apiSpecificData); + + if (texture) { + texture->Release(); + texId->m_apiSpecificData = nullptr; + } + + // TODO CGxDevice::IOnReleaseCPUTex(texId); + CGxDevice::TexDestroy(texId); +} + +void CGxDeviceGLL::XformSetProjection(const C44Matrix& matrix) { + CGxDevice::XformSetProjection(matrix); + this->IXformSetProjection(matrix); +} + +void CGxDeviceGLL::XformSetView(const C44Matrix& matrix) { + CGxDevice::XformSetView(matrix); + this->IXformSetView(matrix); +} diff --git a/src/gx/gll/CGxDeviceGLL.hpp b/src/gx/gll/CGxDeviceGLL.hpp new file mode 100644 index 0000000..020adbc --- /dev/null +++ b/src/gx/gll/CGxDeviceGLL.hpp @@ -0,0 +1,80 @@ +#ifndef GX_GLL_C_GX_DEVICE_GLL_HPP +#define GX_GLL_C_GX_DEVICE_GLL_HPP + +#include "gx/CGxDevice.hpp" +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLWindow.h" +#include + +class CGxBatch; +class CGxShader; + +class CGxDeviceGLL : public CGxDevice { + public: + // Static variables + static GLEnum s_glCubeMapFaces[]; + static GLEnum s_glDstBlend[]; + static GLEnum s_glSrcBlend[]; + static GLTextureFormat s_gxTexFmtToGLLFmt[]; + static GLEnum s_poolTarget2BufferFormat[]; + static GLEnum s_poolTarget2BufferType[]; + static GLEnum s_poolUsage2BufferUsage[]; + static GLEnum s_primitiveConversion[]; + + // Member variables + GLDevice m_glDevice; + GLWindow m_glWindow; + GLVertexFormat m_glFormats[GxVertexBufferFormats_Last] = {}; + + // Virtual member functions + virtual void ITexMarkAsUpdated(CGxTex*); + virtual void IRsSendToHw(EGxRenderState); + virtual int32_t DeviceCreate(long (*)(void*, uint32_t, uint32_t, long), const CGxFormat&); + virtual int32_t DeviceSetFormat(const CGxFormat&); + virtual void CapsWindowSize(CRect&); + virtual void CapsWindowSizeInScreenCoords(CRect& dst); + virtual void ScenePresent(void); + virtual void SceneClear(uint32_t, CImVector); + virtual void XformSetProjection(const C44Matrix&); + virtual void XformSetView(const C44Matrix&); + virtual void Draw(CGxBatch*, int32_t); + virtual void PoolSizeSet(CGxPool*, uint32_t); + virtual char* BufLock(CGxBuf*); + virtual int32_t BufUnlock(CGxBuf*, uint32_t); + virtual void TexDestroy(CGxTex* texId); + virtual void IShaderCreate(CGxShader*); + virtual void ShaderCreate(CGxShader*[], EGxShTarget, const char*, const char*, int32_t); + virtual int32_t StereoEnabled(void); + + // Member functions + CGxDeviceGLL(); + char* IBufLock(CGxBuf*); + int32_t IBufUnlock(CGxBuf*); + void ISceneBegin(); + void ISetCaps(void); + void IShaderBindPixel(CGxShader*); + void IShaderBindVertex(CGxShader*); + void IShaderConstantsFlush(void); + void IShaderCreatePixel(CGxShader*); + void IShaderCreateVertex(CGxShader*); + void IStateSetGLLDefaults(); + void IStateSync(void); + void IStateSyncEnables(void); + void IStateSyncIndexPtr(void); + void IStateSyncLights(void); + void IStateSyncMaterial(void); + void IStateSyncScissorRect(void); + void IStateSyncVertexPtrs(void); + void IStateSyncXforms(void); + void ITexCreate(CGxTex*); + void ITexSetFlags(CGxTex*); + void ITexUpload(CGxTex*); + void IXformSetProjection(const C44Matrix&); + void IXformSetView(const C44Matrix&); + void IXformSetViewport(void); + void PatchPixelShader(CGxShader*); + void PatchVertexShader(CGxShader*); + void Resize(uint32_t width, uint32_t height); +}; + +#endif diff --git a/src/gx/gll/GL.cpp b/src/gx/gll/GL.cpp new file mode 100644 index 0000000..5dcf65d --- /dev/null +++ b/src/gx/gll/GL.cpp @@ -0,0 +1,50 @@ +#include "gx/gll/GL.h" + +TextureFormatInfo k_TextureFormatInfo[GLTF_NUM_TEXTURE_FORMATS] = { + { 0, 0, 0, 0, 0, "GLTF INVALID!!" }, + { GL_RGBA8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0, 4, "ARGB8888" }, + { GL_RGB8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0, 4, "XRGB8888" }, + { GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, 0, 4, "RGBA8888" }, + { GL_RGBA8, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, 0, 4, "ABGR8888" }, + { GL_RGB8, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, 0, 4, "ARGB0888" }, + { GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE, 0, 3, "RGB888" }, + { GL_RGB8, GL_BGR, GL_UNSIGNED_BYTE, 0, 3, "BGR888" }, + { GL_RGBA32F_ARB, GL_RGBA, GL_FLOAT, 0, 16, "RGBA32F" }, + { GL_RGBA16F_ARB, GL_RGBA, GL_HALF_FLOAT, 0, 8, "RGBA16F" }, + { GL_RGB16F_ARB, GL_RGB, GL_HALF_FLOAT, 0, 6, "RG16F" }, + { GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0, 4, "D32" }, + { GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0, 4, "D24" }, + { GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0, 2, "D16" }, + { GL_DEPTH_COMPONENT32, GL_DEPTH_COMPONENT, GL_HALF_FLOAT, 0, 4, "DF" }, + { GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, 0, 4, "D24S8" }, + { GL_ALPHA8, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, 0, 1, "S8" }, + { GL_RGBA4, GL_BGRA, GL_UNSIGNED_SHORT_4_4_4_4_REV, 0, 2, "ARGB4444" }, + { GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0, 2, "ARGB1555" }, + { GL_RGB5, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV, 0, 2, "ARGB0555" }, + { GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, 0, 2, "RGB565" }, + { GL_RGBA, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV, 0, 4, "A2RGB10" }, + { GL_RGB16, GL_RGB, GL_UNSIGNED_SHORT, 0, 6, "RGB16" }, + { GL_LUMINANCE8, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0, 1, "L8" }, + { GL_ALPHA8, GL_ALPHA, GL_UNSIGNED_BYTE, 0, 1, "A8" }, + { GL_LUMINANCE8_ALPHA8, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, 0, 2, "A8L8" }, + { GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_RGBA, GL_UNSIGNED_BYTE, 1, 8, "DXT1" }, + { GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_RGBA, GL_UNSIGNED_BYTE, 1, 16, "DXT3" }, + { GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_RGBA, GL_UNSIGNED_BYTE, 1, 16, "DXT5" } +}; + +VertexTypeInfo k_VertexTypeInfo[GLVT_NUM_VERTEX_TYPES] = { + { 0, 0, 0, 0, "INVALID" }, + { GL_FLOAT, 1, 0, 4, "FLOAT1" }, + { GL_FLOAT, 2, 0, 8, "FLOAT2" }, + { GL_FLOAT, 3, 0, 12, "FLOAT3" }, + { GL_FLOAT, 4, 0, 16, "FLOAT4" }, + { GL_UNSIGNED_BYTE, 4, 0, 4, "UBYTE4" }, + { GL_UNSIGNED_BYTE, 4, 1, 4, "UBYTE4N" }, + { GL_SHORT, 1, 0, 2, "SHORT" }, + { GL_SHORT, 2, 0, 4, "SHORT2" }, + { GL_SHORT, 4, 0, 8, "SHORT4" }, + { GL_SHORT, 2, 1, 4, "SHORT2N" }, + { GL_SHORT, 4, 1, 8, "SHORT4N" }, + { GL_UNSIGNED_SHORT, 2, 1, 4, "USHORT2N" }, + { GL_UNSIGNED_SHORT, 4, 1, 8, "USHORT4N" } +}; diff --git a/src/gx/gll/GL.h b/src/gx/gll/GL.h new file mode 100644 index 0000000..04170d5 --- /dev/null +++ b/src/gx/gll/GL.h @@ -0,0 +1,31 @@ +#ifndef GX_GLL_GL_H +#define GX_GLL_GL_H + +#include "gx/gll/GLTypes.h" +#include + +typedef GLenum GLEnum; + +#define kMAX_VERTEX_ATTRIBS 16 + +struct TextureFormatInfo { + GLenum m_InternalFormat; + GLenum m_DataFormat; + GLenum m_DataType; + int32_t m_IsCompressed; + int32_t m_BytePerPixel; + char m_Name[16]; +}; + +struct VertexTypeInfo { + GLenum m_Type; + GLint m_Size; + GLboolean m_Normalized; + GLint m_ByteSize; + const char* m_Name; +}; + +extern TextureFormatInfo k_TextureFormatInfo[GLTF_NUM_TEXTURE_FORMATS]; +extern VertexTypeInfo k_VertexTypeInfo[GLVT_NUM_VERTEX_TYPES]; + +#endif diff --git a/src/gx/gll/GLAbstractWindow.cpp b/src/gx/gll/GLAbstractWindow.cpp new file mode 100644 index 0000000..d200e23 --- /dev/null +++ b/src/gx/gll/GLAbstractWindow.cpp @@ -0,0 +1,19 @@ +#include "gx/gll/GLAbstractWindow.h" +#include +#include + +int32_t GLAbstractWindow::GetBackingWidth() { + return static_cast(std::floor(this->GetBackingRect().size.width)); +} + +int32_t GLAbstractWindow::GetBackingHeight() { + return static_cast(std::floor(this->GetBackingRect().size.height)); +} + +int32_t GLAbstractWindow::GetWidth() { + return static_cast(std::floor(this->GetRect().size.width)); +} + +int32_t GLAbstractWindow::GetHeight() { + return static_cast(std::floor(this->GetRect().size.height)); +} diff --git a/src/gx/gll/GLAbstractWindow.h b/src/gx/gll/GLAbstractWindow.h new file mode 100644 index 0000000..07ea42e --- /dev/null +++ b/src/gx/gll/GLAbstractWindow.h @@ -0,0 +1,36 @@ +#ifndef GX_GLL_GL_ABSTRACT_WINDOW_H +#define GX_GLL_GL_ABSTRACT_WINDOW_H + +#ifdef __OBJC__ + #include +#else + #include + typedef struct objc_object NSWindow; + typedef struct objc_object NSView; +#endif + +struct CGRect; +class GLContext; + +class GLAbstractWindow { + public: + // Virtual member functions + virtual int32_t GetWidth(void); + virtual int32_t GetHeight(void); + virtual CGRect GetRect(void) = 0; + virtual void Show(void) = 0; + virtual void Resize(uint32_t, uint32_t) = 0; + virtual void SetTitle(const char*) = 0; + virtual void CreateView(void) = 0; + virtual void SetOpenGLContext(GLContext*) = 0; + // virtual NSWindow* GetNSWindow(void) = 0; + virtual NSView* GetNSView(void) = 0; + virtual bool CanEnterFullscreenMode(void) = 0; + // virtual void EnterFullscreenMode(uint32_t, bool) = 0; + virtual void ExitFullscreenMode(void) = 0; + virtual CGRect GetBackingRect() = 0; + virtual int32_t GetBackingWidth(); + virtual int32_t GetBackingHeight(); +}; + +#endif diff --git a/src/gx/gll/GLBatch.h b/src/gx/gll/GLBatch.h new file mode 100644 index 0000000..c01005a --- /dev/null +++ b/src/gx/gll/GLBatch.h @@ -0,0 +1,45 @@ +#ifndef GX_GLL_GL_BATCH_H +#define GX_GLL_GL_BATCH_H + +#include "gx/gll/GLBuffer.h" +#include "gx/gll/GLShader.h" +#include "gx/gll/GLTexture.h" +#include "gx/gll/GLTypes.h" +#include "gx/gll/GLVertexFormat.h" +#include + +class GLBatch { + public: + GLShader* var0; + GLShader* var1; + GLTexture* textures[16]; + GLBuffer* var3; + GLBuffer* var4[4]; + uint32_t var5[4]; + uint32_t var6[4]; + GLVertexFormat* var7; + uint32_t var8; + uint32_t var9; + uint32_t var10; + uint32_t var11; + uint32_t var12; + uint32_t var13; + uint32_t var14; + int32_t var15; + bool var16; + bool var17; + GLStates var18; + GLTexture2D* colorBuffer[4]; + GLTexture2D* var20; + uint32_t var21[128]; + char var22[64]; + char var23[1024]; + uint32_t var24; + bool var25; + int64_t var26; + int64_t var27; + float var28[4096]; + float var29[1024]; +}; + +#endif diff --git a/src/gx/gll/GLBuffer.cpp b/src/gx/gll/GLBuffer.cpp new file mode 100644 index 0000000..5c800b9 --- /dev/null +++ b/src/gx/gll/GLBuffer.cpp @@ -0,0 +1,128 @@ +#include "gx/gll/GLBuffer.h" +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLPool.h" +#include "util/BlizzardCore.hpp" + +bool GLBuffer::m_UsingVBO = 1; + +GLEnum GLBuffer::s_FlagToAccess[] = { + GL_READ_WRITE, // GLMap_None + GL_WRITE_ONLY, // GLMap_Unk1 + GL_WRITE_ONLY, // GLMap_Unk2 + GL_READ_ONLY // GLMap_Unk3 +}; + +GLBuffer* GLBuffer::Create(GLEnum type, uint32_t size, const void* a3, GLEnum usage, GLEnum format) { + GLBuffer* buffer = GLPool::Get()->GetNextObject(); + + buffer->m_Type = type; + buffer->m_Size = size; + buffer->m_Usage = usage; + buffer->m_IndexFormat = format; + + GLDevice* device = GLDevice::Get(); + device->BindBuffer(buffer, GL_ZERO); + + if (GLBuffer::m_UsingVBO) { + glBufferData(buffer->m_Type, buffer->m_Size, a3, buffer->m_Usage); + glBufferParameteriAPPLE(buffer->m_Type, GL_BUFFER_SERIALIZED_MODIFY_APPLE, buffer->m_Usage - GL_DYNAMIC_DRAW > 1); + glBufferParameteriAPPLE(buffer->m_Type, GL_BUFFER_FLUSHING_UNMAP_APPLE, 0); + } else { + Blizzard::Memory::Free(buffer->m_Data); + + void* data = Blizzard::Memory::Allocate(size); + if (a3) { + memcpy(data, a3, size); + } + + buffer->m_Data = reinterpret_cast(data); + } + + // TODO + // buffer->m_TimeStamp = Blizzard::Time::GetTimestamp(); + + return buffer; +} + +GLBuffer::GLBuffer() : GLObject() { + if (GLBuffer::m_UsingVBO) { + this->m_BufferID = GLPool::Get()->GetNextName(); + } +} + +char* GLBuffer::Map(uint32_t offset, uint32_t size, eMapFlag flag) { + BLIZZARD_ASSERT((offset + size) <= this->m_Size); + BLIZZARD_ASSERT(this->m_Usage == GL_STATIC_DRAW || flag != GLMap_None); + BLIZZARD_ASSERT(this->m_MapFlag == GLMap_NotMapped); + BLIZZARD_ASSERT(flag >= GLMap_None && flag < GLMap_Count); + + this->m_MapOffset = offset; + this->m_MapSize = offset + size == 0 ? this->m_Size : size; + this->m_MapFlag = flag; + + if (GLBuffer::m_UsingVBO) { + GLDevice* device = GLDevice::Get(); + device->BindBuffer(this, GL_ZERO); + + if (flag == GLMap_Unk2) { + if (this->m_Usage - GL_DYNAMIC_DRAW <= 1) { + BLIZZARD_ASSERT(offset == 0); + } + + glBufferData(this->m_Type, this->m_Size, nullptr, this->m_Usage); + } + + void* data = glMapBuffer(this->m_Type, GLBuffer::s_FlagToAccess[flag]); + this->m_Data = reinterpret_cast(data); + + BLIZZARD_ASSERT(this->m_Data != nullptr); + } + + return this->m_Data + offset; +} + +void GLBuffer::ReleaseObject() { + if (GLBuffer::m_UsingVBO) { + if (this->m_Type) { + GLDevice* device = GLDevice::Get(); + device->BindBuffer(this, GL_ZERO); + + glBufferData(this->m_Type, 1, nullptr, this->m_Usage); + + // TODO GLPool::GLObjectPool::Push((GLPool::m_pool + 32776), this); + } else { + // TODO GLPool::GLObjectPool::Push((GLPool::m_pool + 32776), this); + } + } else { + Blizzard::Memory::Free(this->m_Data); + this->m_Data = nullptr; + + // TODO GLPool::GLObjectPool::Push((GLPool::m_pool + 32776), this); + } +} + +void GLBuffer::Unmap(uint32_t size) { + BLIZZARD_ASSERT((this->m_MapOffset + size) <= m_Size); + + GLDevice* device = GLDevice::Get(); + device->BindBuffer(this, GL_ZERO); + + if (this->m_MapFlag != 3) { + if (GLBuffer::m_UsingVBO) { + glFlushMappedBufferRangeAPPLE(this->m_Type, this->m_MapOffset, size ? size : this->m_MapSize); + } + + // TODO + // this->m_TimeStamp = Blizzard::Time::GetTimestamp(); + } + + if (!GLBuffer::m_UsingVBO) { + this->m_MapFlag = GLMap_NotMapped; + return; + } + + GLboolean result = glUnmapBuffer(this->m_Type); + BLIZZARD_ASSERT(result); + + this->m_MapFlag = GLMap_NotMapped; +} diff --git a/src/gx/gll/GLBuffer.h b/src/gx/gll/GLBuffer.h new file mode 100644 index 0000000..b37cee3 --- /dev/null +++ b/src/gx/gll/GLBuffer.h @@ -0,0 +1,47 @@ +#ifndef GX_GLL_GL_BUFFER_H +#define GX_GLL_GL_BUFFER_H + +#include "gx/gll/GL.h" +#include "gx/gll/GLObject.h" +#include "gx/gll/GLTypes.h" + +class GLBuffer : public GLObject { + public: + // Types + enum eMapFlag { + GLMap_NotMapped = -1, + GLMap_None = 0, + GLMap_Unk1 = 1, + GLMap_Unk2 = 2, + GLMap_Unk3 = 3, + GLMap_Count = 4 + }; + + // Static variables + static bool m_UsingVBO; + static GLEnum s_FlagToAccess[]; + + // Static functions + static GLBuffer* Create(GLEnum, uint32_t, const void*, GLEnum, GLEnum); + + // Member variables + uint32_t m_Size = 0; + GLEnum m_Type = 0; + GLEnum m_Usage = 0; + uint32_t m_BufferID = 0; + GLEnum m_IndexFormat = 0; + char* m_Data = nullptr; + uint32_t m_MapOffset = 0; + uint32_t m_MapSize = 0; + uint32_t m_MapFlag = GLMap_NotMapped; + + // Virtual member functions + virtual void ReleaseObject(); + + // Member functions + GLBuffer(); + char* Map(uint32_t, uint32_t, eMapFlag); + void Unmap(uint32_t); +}; + +#endif diff --git a/src/gx/gll/GLBufferPool.h b/src/gx/gll/GLBufferPool.h new file mode 100644 index 0000000..6fa1be5 --- /dev/null +++ b/src/gx/gll/GLBufferPool.h @@ -0,0 +1,8 @@ +#ifndef GX_GLL_GL_BUFFER_POOL_H +#define GX_GLL_GL_BUFFER_POOL_H + +class GLBufferPool { + public: +}; + +#endif diff --git a/src/gx/gll/GLCommand.cpp b/src/gx/gll/GLCommand.cpp new file mode 100644 index 0000000..8ae3de7 --- /dev/null +++ b/src/gx/gll/GLCommand.cpp @@ -0,0 +1,24 @@ +#include "gx/gll/GLCommand.h" +#include "gx/gll/GLTexture.h" + +void GLFlush::Execute(GLDevice* device) { + glFlush(); +} + +GLTexUnmap::GLTexUnmap(GLTexture* texture, GLMipmap* mipmap, GLMipmap::MapParams* mapParams) { + this->m_Texture = texture; + this->m_Mipmap = mipmap; + this->m_MapParams = mapParams; +} + +void GLTexUnmap::Execute(GLDevice* device) { + this->m_Mipmap->Unmap(this->m_MapParams); +} + +GLTexDestroy::GLTexDestroy(GLTexture* texture) { + this->m_Texture = texture; +} + +void GLTexDestroy::Execute(GLDevice* device) { + this->m_Texture->FreeTexture(); +} diff --git a/src/gx/gll/GLCommand.h b/src/gx/gll/GLCommand.h new file mode 100644 index 0000000..cfb4da6 --- /dev/null +++ b/src/gx/gll/GLCommand.h @@ -0,0 +1,46 @@ +#ifndef GX_GLL_GL_COMMAND_H +#define GX_GLL_GL_COMMAND_H + +#include "gx/gll/GLMipmap.h" + +class GLDevice; + +class GLCommand { + public: + // Virtual member functions + virtual void Execute(GLDevice*) = 0; +}; + +class GLFlush : public GLCommand { + public: + // Virtual member functions + virtual void Execute(GLDevice* device); +}; + +class GLTexUnmap : public GLCommand { + public: + // Member variables + GLTexture* m_Texture; + GLMipmap* m_Mipmap; + GLMipmap::MapParams* m_MapParams; + + // Virtual member functions + virtual void Execute(GLDevice*); + + // Member functions + GLTexUnmap(GLTexture*, GLMipmap*, GLMipmap::MapParams*); +}; + +class GLTexDestroy : public GLCommand { + public: + // Member variables + GLTexture* m_Texture; + + // Virtual member functions + virtual void Execute(GLDevice* device); + + // Member functions + GLTexDestroy(GLTexture* texture); +}; + +#endif diff --git a/src/gx/gll/GLContext.h b/src/gx/gll/GLContext.h new file mode 100644 index 0000000..ef23ad6 --- /dev/null +++ b/src/gx/gll/GLContext.h @@ -0,0 +1,96 @@ +#ifndef GX_GLL_GL_CONTEXT_H +#define GX_GLL_GL_CONTEXT_H + +#include "gx/gll/GLAbstractWindow.h" +#include "gx/gll/GLTypes.h" +#include "util/BlizzardCore.hpp" +#include +#include +#include +#include +#include + +#ifdef __OBJC__ + #include + #include +#else + typedef struct objc_object NSOpenGLContext; + typedef struct objc_object NSOpenGLPixelFormat; +#endif + +class GLDevice; + +class GLContext { + public: + // Types + struct Context { + NSOpenGLContext* context; + NSOpenGLPixelFormat* pixelFormat; + int32_t sampleCount; + + ~Context(); + }; + + struct GammaFormula { + float m_RedMin; + float m_RedMax; + float m_RedGamma; + float m_GreenMin; + float m_GreenMax; + float m_GreenGamma; + float m_BlueMin; + float m_BlueMax; + float m_BlueGamma; + }; + + // Static variables + static NSOpenGLContext* s_MainContext; + static Blizzard::Thread::TLSSlot s_CurrentContext; + static Blizzard::Thread::TLSSlot s_CurrentGLContext; + static CFDictionaryRef s_DesktopMode; + + // Static functions + static NSOpenGLContext* GetNSOpenGLCurrentContext(void); + static NSOpenGLContext* GetCurrentContext(void); + static void SetCurrentContext(NSOpenGLContext*); + static GLContext* GetCurrentGLContext(void); + static void SetCurrentGLContext(GLContext*); + + // Member variables + std::basic_string, std::allocator> m_DebugName; + std::map, std::allocator>> m_Contexts; + Context* m_Context; + GLDevice* m_Device; + GLAbstractWindow* m_Window; + bool m_Windowed; + bool m_MTGLEnabled; + bool m_VSyncEnabled; + bool m_CaptureDisplay; + uint32_t m_Width; + uint32_t m_Height; + uint32_t m_RefreshRate; + GammaFormula m_GammaFormula; + bool m_GammaTablesSet; + std::vector> m_GammaTableR; + std::vector> m_GammaTableG; + std::vector> m_GammaTableB; + std::vector> m_OsGammaTableR; + std::vector> m_OsGammaTableG; + std::vector> m_OsGammaTableB; + + // Member functions + GLContext(GLDevice*, const char*); + int32_t GetBackingWidth(); + int32_t GetBackingHeight(); + int32_t GetWidth(void); + int32_t GetHeight(void); + bool IsCurrentContext(void); + void MakeCurrent(bool); + void SetContextFormat(GLTextureFormat, uint32_t); + void SetFullscreenMode(uint32_t, uint32_t, uint32_t, bool); + void SetWindow(GLAbstractWindow*, bool); + void Swap(void); + void Update(void); +}; + +#endif diff --git a/src/gx/gll/GLContext.mm b/src/gx/gll/GLContext.mm new file mode 100644 index 0000000..395430d --- /dev/null +++ b/src/gx/gll/GLContext.mm @@ -0,0 +1,389 @@ +#include "gx/gll/GLContext.h" +#include "gx/gll/GLDevice.h" +#include "util/Autorelease.hpp" + +NSOpenGLContext* GLContext::s_MainContext; +Blizzard::Thread::TLSSlot GLContext::s_CurrentContext; +Blizzard::Thread::TLSSlot GLContext::s_CurrentGLContext; +CFDictionaryRef GLContext::s_DesktopMode; + +void* Sub2A1E0(void* ptr) { + NSOpenGLContext** ptrptr = new NSOpenGLContext*; + *ptrptr = nullptr; + return ptrptr; +} + +void Sub2A200(void* ptr) { + delete static_cast(ptr); +} + +void* Sub720A0(void* ptr) { + GLContext** ptrptr = new GLContext*; + *ptrptr = nullptr; + return ptrptr; +} + +void Sub720C0(void* ptr) { + delete static_cast(ptr); +} + +GLContext::Context::~Context() { + if (this->context) { + glFlush(); + + [this->context clearDrawable]; + + if (GLContext::GetCurrentContext() == context) { + [NSOpenGLContext clearCurrentContext]; + GLContext::SetCurrentContext(nullptr); + } + + [this->context release]; + } + + [this->pixelFormat release]; +} + +NSOpenGLContext* GLContext::GetNSOpenGLCurrentContext() { + return [NSOpenGLContext currentContext]; +} + +NSOpenGLContext* GLContext::GetCurrentContext() { + return *static_cast( + Blizzard::Thread::RegisterLocalStorage(&GLContext::s_CurrentContext, Sub2A1E0, 0, Sub2A200) + ); +} + +void GLContext::SetCurrentContext(NSOpenGLContext* context) { + *static_cast( + Blizzard::Thread::RegisterLocalStorage(&GLContext::s_CurrentContext, Sub2A1E0, 0, Sub2A200) + ) = context; +} + +GLContext* GLContext::GetCurrentGLContext() { + return *static_cast( + Blizzard::Thread::RegisterLocalStorage(&GLContext::s_CurrentGLContext, Sub720A0, 0, Sub720C0) + ); +} + +void GLContext::SetCurrentGLContext(GLContext* context) { + *static_cast( + Blizzard::Thread::RegisterLocalStorage(&GLContext::s_CurrentGLContext, Sub720A0, 0, Sub720C0) + ) = context; +} + +GLContext::GLContext(GLDevice* a2, const char* a3) { + this->m_Context = nullptr; + this->m_Window = nullptr; + this->m_Windowed = false; + this->m_MTGLEnabled = false; + this->m_Device = a2; + this->m_Width = 0; + this->m_Height = 0; + this->m_RefreshRate = 0; +} + +int32_t GLContext::GetBackingWidth() { + if (this->m_Windowed) { + return this->m_Window->GetBackingWidth(); + } else { + return this->m_Width; + } +} + +int32_t GLContext::GetBackingHeight() { + if (this->m_Windowed) { + return this->m_Window->GetBackingHeight(); + } else { + return this->m_Height; + } +} + +int32_t GLContext::GetWidth() { + if (this->m_Windowed) { + return this->m_Window->GetWidth(); + } else { + return this->m_Width; + } +} + +int32_t GLContext::GetHeight() { + if (this->m_Windowed) { + return this->m_Window->GetHeight(); + } else { + return this->m_Height; + } +} + +bool GLContext::IsCurrentContext() { + return GLContext::GetCurrentGLContext() == this; +} + +void GLContext::MakeCurrent(bool a2) { + BLIZZARD_ASSERT(this->m_Context->context != nullptr); + + if (a2) { + NSOpenGLContext* v6 = GLContext::GetNSOpenGLCurrentContext(); + GLContext::SetCurrentContext(v6); + } + + if (this->m_Context->context != GLContext::GetCurrentContext()) { + int32_t mtglEnabled = 0; + + if (GLContext::GetCurrentContext()) { + mtglEnabled = this->m_MTGLEnabled; + + // TODO + // this->ToggleMTGL(0); + // GLOcclusionQuery::DeleteAllQueries(); + // GLFence::DeleteAllFences(); + + glFlush(); + } + + [this->m_Context->context makeCurrentContext]; + + GLContext::SetCurrentContext(this->m_Context->context); + GLContext::SetCurrentGLContext(this); + + // TODO + // GLOcclusionQuery::RecreateAllQueries(); + // GLFence::RecreateAllFences(); + // this->ToggleMTGL(mtglEnabled); + + GLDevice* device = GLDevice::Get(); + + if (device && this->m_Contexts.size() > 1) { + device->ApplyGLStates(device->m_States, 1); + device->ApplyGLBindings(device->m_States, 1); + device->m_DefaultVertexArrayObject.ApplyGLStates(device->m_DefaultVertexArrayObject.m_GLStates); + } + } +} + +void GLContext::SetContextFormat(GLTextureFormat a2, uint32_t sampleCount) { + System_Autorelease::ScopedPool autorelease; + + uint32_t v61 = sampleCount | (a2 << 8); + + if (this->m_Contexts.find(v61) != this->m_Contexts.end()) { + this->m_Context = &this->m_Contexts[v61]; + + if (this->m_Window) { + this->m_Window->SetOpenGLContext(this); + } + } else { + auto& context = this->m_Contexts[v61]; + + CGDirectDisplayID v6 = CGMainDisplayID(); + + NSOpenGLPixelFormatAttribute formatAttributes[] = { + NSOpenGLPFADoubleBuffer, + NSOpenGLPFANoRecovery, + NSOpenGLPFAAccelerated, + NSOpenGLPFADepthSize, 0, + NSOpenGLPFAStencilSize, 0, + NSOpenGLPFAColorSize, 32, + NSOpenGLPFAWindow, + NSOpenGLPFAFullScreen, + NSOpenGLPFAScreenMask, CGDisplayIDToOpenGLDisplayMask(v6), + 0, 0, 0, 0, 0 + }; + + switch (a2) { + case GLTF_INVALID: + break; + + case GLTF_D32: + formatAttributes[4] = 32; + break; + + case GLTF_D24: + formatAttributes[4] = 24; + break; + + case GLTF_D16: + formatAttributes[4] = 16; + break; + + case GLTF_D24S8: + formatAttributes[4] = 24; + formatAttributes[6] = 8; + break; + + default: + BLIZZARD_ASSERT(false); + break; + } + + if (sampleCount > 1) { + BLIZZARD_ASSERT(sampleCount % 2 == 0); + BLIZZARD_ASSERT(formatAttributes[13] == 0); + BLIZZARD_ASSERT(formatAttributes[14] == 0); + BLIZZARD_ASSERT(formatAttributes[15] == 0); + BLIZZARD_ASSERT(formatAttributes[16] == 0); + BLIZZARD_ASSERT(formatAttributes[17] == 0); + + formatAttributes[13] = NSOpenGLPFASampleBuffers; + formatAttributes[14] = 1; + formatAttributes[15] = NSOpenGLPFASamples; + formatAttributes[16] = sampleCount; + } + + context.sampleCount = sampleCount; + + context.pixelFormat = [[NSOpenGLPixelFormat alloc] + initWithAttributes: formatAttributes + ]; + + BLIZZARD_ASSERT(context.pixelFormat != nullptr); + + context.context = [[NSOpenGLContext alloc] + initWithFormat: context.pixelFormat + shareContext: GLContext::s_MainContext + ]; + + BLIZZARD_ASSERT(context.context != nullptr); + + this->m_Context = &context; + + auto contextObj = [context.context CGLContextObj]; + + int32_t vsyncEnabled = this->m_VSyncEnabled; + CGLSetParameter(contextObj, kCGLCPSwapInterval, &vsyncEnabled); + + auto result = this->m_MTGLEnabled + ? CGLEnable(contextObj, kCGLCEMPEngine) + : CGLDisable(contextObj, kCGLCEMPEngine); + BLIZZARD_ASSERT(result == kCGLNoError); + + if (this->m_Window) { + this->m_Window->SetOpenGLContext(this); + } + } + + if (this->m_Context->context != GLContext::GetCurrentContext()) { + this->MakeCurrent(0); + } +} + +void GLContext::SetFullscreenMode(uint32_t, uint32_t, uint32_t, bool) { + // TODO +} + +void GLContext::SetWindow(GLAbstractWindow* a2, bool a3) { + if (!a2) { + if (this->m_Window) { + this->m_Window->SetOpenGLContext(nullptr); + } + + this->m_Window = nullptr; + + return; + } + + if (this->m_Windowed && a2 == this->m_Window) { + if (a3) { + a2->Show(); + } + + return; + } + + NSView* v5; + + if (this->m_Window) { + v5 = this->m_Window->GetNSView(); + + if (v5 && this->m_Windowed && a2 != this->m_Window) { + this->m_Window = a2; + this->m_Window->SetOpenGLContext(this); + + return; + } + } else { + v5 = nullptr; + } + + this->m_Window = a2; + + if (!a2->GetNSView()) { + this->m_Window->CreateView(); + } + + float v15; + bool v16 = false; + CGDisplayFadeReservationToken v17 = 0; + + if (v5 && GLDevice::GetRendererInfo().unk100 > 2639 && this->m_Window->CanEnterFullscreenMode()) { + if (!this->m_Windowed) { + if (!GLContext::s_DesktopMode) { + // TODO + // Blizzard::Debug::Assert( + // "s_DesktopMode", + // "/Users/Shared/BuildServer/wow2.old/work/WoW-code/branches/wow-patch-3_3_5-BNet/WoW/Source/Mac/../../../" + // "Engine/Source/Gx/CGxDeviceGLL/GLLayer/GLContext.cpp", + // 215 + // ); + } + + v15 = 1.25; + v16 = false; + CGDisplayFadeReservationToken v8 = 0; + + CGError v7 = CGAcquireDisplayFadeReservation(2.25, &v17); + + if (!v7) { + v8 = v17; + } + + v17 = v8; + + if (v8) { + v16 = CGDisplayFade(v8, 0.25, 0, 1.0, 0, 0, 0, 1) == kCGErrorSuccess; + } + + CGDirectDisplayID v10 = CGMainDisplayID(); + CGDisplaySwitchToMode(v10, GLContext::s_DesktopMode); + + this->m_Window->ExitFullscreenMode(); + + // TODO + // SetSystemUIMode(0, 0); + } + + CGReleaseAllDisplays(); + } + + if (a3) { + this->m_Window->Show(); + } + + this->m_Windowed = 1; + CGDisplayRestoreColorSyncSettings(); + + if (v17) { + if (v16) { + float v6 = v15; + + if (v15 > 1.0) { + usleep(1000); + v6 = v15 - 1.0; + } + + CGDisplayFade(v17, v6, 1.0, 0, 0, 0, 0, 0); + } + + CGReleaseDisplayFadeReservation(v17); + } + + return; +} + +void GLContext::Swap() { + [this->m_Context->context flushBuffer]; +} + +void GLContext::Update() { + [this->m_Context->context update]; +} diff --git a/src/gx/gll/GLDebugMipmap2D.h b/src/gx/gll/GLDebugMipmap2D.h new file mode 100644 index 0000000..cb87851 --- /dev/null +++ b/src/gx/gll/GLDebugMipmap2D.h @@ -0,0 +1,8 @@ +#ifndef GX_GLL_GL_DEBUG_MIPMAP_2D_H +#define GX_GLL_GL_DEBUG_MIPMAP_2D_H + +class GLDebugMipmap2D { + public: +}; + +#endif diff --git a/src/gx/gll/GLDevice.cpp b/src/gx/gll/GLDevice.cpp new file mode 100644 index 0000000..12e1bb5 --- /dev/null +++ b/src/gx/gll/GLDevice.cpp @@ -0,0 +1,2612 @@ +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLPool.h" +#include "gx/gll/GLUtil.h" +#include "util/Autorelease.hpp" +#include +#include +#include +#include +#include + +#define GL_MAX_STREAM 4 + +class GLPixelShader; +class GLVertexShader; + +Blizzard::Thread::TLSSlot GLDevice::m_CurrentDevice; +std::vector> GLDevice::m_Devices; +bool GLDevice::m_ExtColorMaskIndexed = false; +int32_t GLDevice::m_StaticResourcesRefCount = 0; +GLDevice::RendererInfo GLDevice::m_RendererInfo; +bool GLDevice::m_UseHybridShader = 0; +bool GLDevice::m_ExtARBShadow = 0; +bool GLDevice::m_ShaderConstantBindings = 1; +GLBuffer* GLDevice::m_BlitQuadVBO = nullptr; +GLShader* GLDevice::m_DeviceShaders[11] = {}; +GLTexture* GLDevice::m_DeviceTextures[4] = {}; +GLVertexFormat GLDevice::m_NormalBlitVF; +GLVertexFormat GLDevice::m_InvertedBlitVF; +GLFramebuffer* GLDevice::m_F8330C = nullptr; + +const char* gllBlitVsCode = R"(!!ARBvp1.0 +PARAM c0 = { 1.0 }; +MOV result.position.xyz, vertex.attrib[0]; +MOV result.position.w, c0.x; +MOV result.texcoord[0].xyz, vertex.attrib[1]; +END)"; + +const char* gllDummyVsCode = R"(!!ARBvp1.0 +PARAM c = {0.0, 0.0, 0.0, 1.0}; +MOV result.position, c; +END)"; + +const char* gllBlitPsCode = R"(!!ARBfp1.0 +PARAM c0 = { 1.0 }; +TEX result.color.rgba, fragment.texcoord[0], texture[0], 2D; +END)"; + +inline void COPY_TRANSFORM(GLTransform& dst, const GLTransform& src) { + if (src.isIdentity) { + dst.isIdentity = true; + dst.isDirty = true; + } else { + dst.isIdentity = false; + dst.isDirty = true; + memcpy(dst.m, src.m, sizeof(float) * 16); + } +} + +void* Sub1D210(void* ptr) { + GLDevice** ptrptr = new GLDevice*; + *ptrptr = nullptr; + return ptrptr; +} + +void Sub1D230(void* ptr) { + delete static_cast(ptr); +} + +GLDevice* GLDevice::Get() { + return *static_cast( + Blizzard::Thread::RegisterLocalStorage(&GLDevice::m_CurrentDevice, Sub1D210, 0, Sub1D230) + ); +} + +void GLDevice::Set(GLDevice* device) { + *static_cast( + Blizzard::Thread::RegisterLocalStorage(&GLDevice::m_CurrentDevice, Sub1D210, 0, Sub1D230) + ) = device; +} + +GLDevice::RendererInfo GLDevice::GetRendererInfo() { + if (!GLDevice::m_RendererInfo.init) { + GLDevice::InitRendererInfo(); + } + + return GLDevice::m_RendererInfo; +} + +void GLDevice::InitRendererInfo() { + // TODO + + GLDevice::m_RendererInfo.init = 1; +} + +void GLDevice::InitPools() { + // TODO + // - init all pools + + GLPool::Init(); + GLPool::Get()->SetNextName(1); + + GLPool::Init(); + GLPool::Get()->SetNextName(0x21); + + GLPool::Init(); + GLPool::Get()->SetNextName(0x1); + + GLPool::Init(); + GLPool::Get()->SetNextName(0x2001); + + GLPool::Init(); + GLPool::Get()->SetNextName(0x1); +} + +void GLDevice::SetOption(GLDeviceOption option, bool enable) { + switch (option) { + case eUseMTGL: { + break; + } + + case eUseVertexArray: { + break; + } + + case eUseGLSL: { + break; + } + + case eCheckGLStates: { + break; + } + + case eFlushBeforeDraw: { + break; + } + + case eDeviceOption5: { + break; + } + + case eUseHybridShader: { + break; + } + + case eDeviceOption7: { + break; + } + + case eDeviceOption8: { + break; + } + + case eShaderConstantBindings: { + GLDevice::m_ShaderConstantBindings = enable; + break; + } + + default: { + BLIZZARD_ASSERT(false); + } + } +} + +void GLDevice::StaticInit() { + static float blitQuad[] = { + -1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f + }; + + GLDevice::m_BlitQuadVBO = GLBuffer::Create( + GL_ARRAY_BUFFER, + sizeof(blitQuad), + blitQuad, + GL_STATIC_DRAW, + 0 + ); + + GLDevice::m_InvertedBlitVF.m_Attribs[0] = { 0, 0, GLVT_FLOAT3, 0 }; + GLDevice::m_InvertedBlitVF.m_Attribs[1] = { 0, 1, GLVT_FLOAT2, 12 }; + GLDevice::m_InvertedBlitVF.m_Size = 2; + + GLDevice::m_NormalBlitVF.m_Attribs[0] = { 0, 0, GLVT_FLOAT3, 0 }; + GLDevice::m_NormalBlitVF.m_Attribs[1] = { 0, 1, GLVT_FLOAT2, 20 }; + GLDevice::m_NormalBlitVF.m_Size = 2; + + GLDevice::m_F8330C = GLFramebuffer::Create(0); + + GLDevice::m_DeviceShaders[0] = GLShader::Create( + GLShader::eVertexShader, + GLDevice::m_UseHybridShader, + false, + "", + gllBlitVsCode, + strlen(gllBlitVsCode), + "", + "GLL Blit VS", + nullptr + ); + + GLDevice::m_DeviceShaders[0]->Compile(nullptr); + + GLDevice::m_DeviceShaders[1] = GLShader::Create( + GLShader::eVertexShader, + GLDevice::m_UseHybridShader, + false, + "", + gllDummyVsCode, + strlen(gllDummyVsCode), + "", + "GLL Dummy VS", + nullptr + ); + + GLDevice::m_DeviceShaders[1]->Compile(nullptr); + + GLDevice::m_DeviceShaders[2] = GLShader::Create( + GLShader::ePixelShader, + GLDevice::m_UseHybridShader, + false, + "", + gllBlitPsCode, + strlen(gllBlitPsCode), + "", + "GLL Blit PS", + nullptr + ); + + GLDevice::m_DeviceShaders[2]->Compile(nullptr); + + // TODO + // - remaining device shaders + + // Device textures + + uint32_t v30 = 0; + uint32_t v31 = 0; + + auto texture0 = GLTexture2D::Create(1, 1, 1, GLTF_RGBA8888, 0x0); + *static_cast(texture0->Map(0, nullptr, v30, GL_WRITE_ONLY)) = 0xFF; + texture0->Unmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + GLDevice::m_DeviceTextures[0] = texture0; + + // TODO + // auto texture1 = GLTexture3D::Create(1, 1, 1, 1, GLTF_RGBA8888, 0x0); + // *texture1->Map(0, nullptr, v30, v31, GL_WRITE_ONLY) = 0xFF; + // texture1->Unmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + // GLDevice::m_DeviceTextures[1] = texture1; + + // TODO texture2 + + auto texture3 = GLTexture2D::Create(1, 1, 1, GLTF_D32, 0x0); + *static_cast(texture3->Map(0, nullptr, v30, GL_WRITE_ONLY)) = 0xFF; + texture3->Unmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + GLDevice::m_DeviceTextures[3] = texture3; +} + +GLDevice::GLDevice() : m_Context(this, nullptr), m_DefaultVertexArrayObject(true) { + // TODO + // - fill in remaining initializers +} + +void GLDevice::ApplyGLBindings(const GLStates& states, bool a3) { + static GLenum texTarget[4] = { + GL_TEXTURE_2D, + GL_TEXTURE_3D, + GL_TEXTURE_CUBE_MAP, + GL_TEXTURE_RECTANGLE_EXT + }; + + for (int32_t i = 0; i < 16; ++i) { + for (int32_t t = 0; t < 4; ++t) { + if (this->m_States.binding.texture[t][i] != states.binding.texture[t][i] || a3) { + glActiveTexture(GL_TEXTURE0 + i); + glBindTexture(texTarget[t], states.binding.texture[t][i]); + } + } + } + + glActiveTexture(GL_TEXTURE0 + states.binding.currentActiveTexture); + + if (this->m_States.binding.framebuffer != states.binding.framebuffer || a3) { + glBindFramebufferEXT(GL_FRAMEBUFFER, states.binding.framebuffer); + } + + if (this->m_States.binding.renderbuffer != states.binding.renderbuffer || a3) { + glBindRenderbufferEXT(GL_RENDERBUFFER, states.binding.renderbuffer); + } + + if (this->m_States.binding.vertexArrayObject != states.binding.vertexArrayObject || a3) { + glBindVertexArrayAPPLE(states.binding.vertexArrayObject); + } + + if (this->m_States.binding.vertexProgram != states.binding.vertexProgram || a3) { + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, states.binding.vertexProgram); + } + + if (this->m_States.binding.pixelProgram != states.binding.pixelProgram || a3) { + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, states.binding.pixelProgram); + } + + if (this->m_States.binding.glslProgram != states.binding.glslProgram || a3) { + glUseProgram(states.binding.glslProgram); + } + + memcpy(&this->m_States.binding, &states.binding, sizeof(this->m_States.binding)); +} + +void GLDevice::ApplyGLStates(const GLStates& states, bool force) { + if (force) { + for (int32_t i = 0; i < 8; ++i) { + glActiveTexture(GL_TEXTURE0 + i); + + float sPlane[] = { 1.0, 0.0, 0.0, 0.0 }; + float tPlane[] = { 0.0, 1.0, 0.0, 0.0 }; + float rPlane[] = { 0.0, 0.0, 1.0, 0.0 }; + float qPlane[] = { 0.0, 0.0, 0.0, 1.0 }; + + glTexGenfv(GL_S, GL_EYE_PLANE, sPlane); + glTexGenfv(GL_T, GL_EYE_PLANE, tPlane); + glTexGenfv(GL_R, GL_EYE_PLANE, rPlane); + glTexGenfv(GL_Q, GL_EYE_PLANE, qPlane); + glTexGenfv(GL_S, GL_OBJECT_PLANE, sPlane); + glTexGenfv(GL_T, GL_OBJECT_PLANE, tPlane); + glTexGenfv(GL_R, GL_OBJECT_PLANE, rPlane); + glTexGenfv(GL_Q, GL_OBJECT_PLANE, qPlane); + + glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, 1); + glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, (GLfloat*)&GLColor4f::WHITE); + } + + glActiveTexture(GL_TEXTURE0 + states.binding.currentActiveTexture); + glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR_EXT); + glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, 1); + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + } + + if (this->m_States.depth.testEnable != states.depth.testEnable || force) { + if (states.depth.testEnable) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } + } + + if (this->m_States.depth.compareFunc != states.depth.compareFunc || force) { + glDepthFunc(states.depth.compareFunc); + } + + if (this->m_States.depth.writeMask != states.depth.writeMask || force) { + glDepthMask(states.depth.writeMask); + } + + if (this->m_States.stencil.testEnable != states.stencil.testEnable || force) { + if (states.stencil.testEnable) { + glEnable(GL_STENCIL_TEST); + } else { + glDisable(GL_STENCIL_TEST); + } + } + + if (this->m_States.stencil.front.compareFunc != states.stencil.front.compareFunc + || this->m_States.stencil.back.compareFunc != states.stencil.back.compareFunc + || this->m_States.stencil.ref != states.stencil.ref + || this->m_States.stencil.mask != states.stencil.mask + || force) + { + glStencilFuncSeparateATI( + states.stencil.front.compareFunc, + states.stencil.back.compareFunc, + states.stencil.ref, + states.stencil.mask + ); + } + + if (this->m_States.stencil.front.opFail != states.stencil.front.opFail + || this->m_States.stencil.front.opZFail != states.stencil.front.opZFail + || this->m_States.stencil.front.opZPass != states.stencil.front.opZPass + || force) + { + glStencilOpSeparateATI( + states.stencil.useTwoSidedStencil ? GL_FRONT : GL_FRONT_AND_BACK, + states.stencil.front.opFail, + states.stencil.front.opZFail, + states.stencil.front.opZPass + ); + } + + if (states.stencil.useTwoSidedStencil) { + if (this->m_States.stencil.back.opFail != states.stencil.back.opFail + || this->m_States.stencil.back.opZFail != states.stencil.back.opZFail + || this->m_States.stencil.back.opZPass != states.stencil.back.opZPass + || force) + { + glStencilOpSeparateATI( + GL_BACK, + states.stencil.back.opFail, + states.stencil.back.opZFail, + states.stencil.back.opZPass + ); + } + } + + if (this->m_States.stencil.writeMask != states.stencil.writeMask || force) { + glStencilMask(states.stencil.writeMask); + } + + if (this->m_States.rasterizer.cullFaceMode != states.rasterizer.cullFaceMode || force) { + glCullFace(states.rasterizer.cullFaceMode); + } + + if (this->m_States.rasterizer.cullMode != states.rasterizer.cullMode || force) { + if (states.rasterizer.cullMode != 0) { + glEnable(GL_CULL_FACE); + glFrontFace(states.rasterizer.cullMode); + } else { + glDisable(GL_CULL_FACE); + } + } + + if (this->m_States.rasterizer.fillMode != states.rasterizer.fillMode || force) { + glPolygonMode(GL_FRONT_AND_BACK, states.rasterizer.fillMode); + } + + if (this->m_CurrentTargetDepth + && (this->m_States.rasterizer.constantDepthBias != states.rasterizer.constantDepthBias + || this->m_States.rasterizer.slopeScaledDepthBias != states.rasterizer.slopeScaledDepthBias + || force)) + { + if (states.rasterizer.slopeScaledDepthBias == 0.0 && states.rasterizer.constantDepthBias == 0.0) { + glDisable(GL_POLYGON_OFFSET_FILL); + } else { + glEnable(GL_POLYGON_OFFSET_FILL); + } + + float units; + + if (states.rasterizer.constantDepthBias == 0.0 ) { + units = 0.0; + } else { + units = + (states.rasterizer.constantDepthBias * 2.0) + * (1 << this->m_CurrentTargetDepth->GetDepthBits()); + } + + glPolygonOffset(states.rasterizer.slopeScaledDepthBias * 2.0, units); + } + + if (this->m_States.rasterizer.viewport.left != states.rasterizer.viewport.left + || this->m_States.rasterizer.viewport.top != states.rasterizer.viewport.top + || this->m_States.rasterizer.viewport.width != states.rasterizer.viewport.width + || this->m_States.rasterizer.viewport.height != states.rasterizer.viewport.height + || force) + { + glViewport( + states.rasterizer.viewport.left, + states.rasterizer.viewport.top, + states.rasterizer.viewport.width, + states.rasterizer.viewport.height + ); + } + + if (this->m_States.rasterizer.zNear != states.rasterizer.zNear + || this->m_States.rasterizer.zFar != states.rasterizer.zFar + || force) + { + glDepthRange(states.rasterizer.zNear, states.rasterizer.zFar); + } + + if (this->m_States.rasterizer.scissorEnable != states.rasterizer.scissorEnable || force) { + if (states.rasterizer.scissorEnable) { + glEnable(GL_SCISSOR_TEST); + } else { + glDisable(GL_SCISSOR_TEST); + } + } + + if (this->m_States.rasterizer.scissor.left != states.rasterizer.scissor.left + || this->m_States.rasterizer.scissor.top != states.rasterizer.scissor.top + || this->m_States.rasterizer.scissor.width != states.rasterizer.scissor.width + || this->m_States.rasterizer.scissor.height != states.rasterizer.scissor.height + || force) + { + glScissor( + states.rasterizer.scissor.left, + states.rasterizer.scissor.top, + states.rasterizer.scissor.width, + states.rasterizer.scissor.height + ); + } + + int32_t maxClipPlaneIndex = GLDevice::GetRendererInfo().unk36; + + if (this->m_States.rasterizer.clipPlaneMask != states.rasterizer.clipPlaneMask) { + for (int32_t i = 0; i < maxClipPlaneIndex; ++i) { + if (!(states.rasterizer.clipPlaneMask & this->m_States.rasterizer.clipPlaneMask & (1 << i))) { + if ((1 << i) & states.rasterizer.clipPlaneMask) { + glEnable(GL_CLIP_PLANE0 + i); + } else { + glDisable(GL_CLIP_PLANE0 + i); + } + } + } + } + + for (int32_t i = 0; i < maxClipPlaneIndex; ++i) { + if (memcmp(&this->m_States.rasterizer.clipPlanes[i].plane, &states.rasterizer.clipPlanes[i].plane, sizeof(this->m_States.rasterizer.clipPlanes[i].plane))) { + glClipPlane(GL_CLIP_PLANE0 + i, states.rasterizer.clipPlanes[i].plane); + } + } + + int32_t maxColorMaskIndex = GLDevice::m_ExtColorMaskIndexed ? GLDevice::GetRendererInfo().max_color_attachments : 1; + + for (int32_t i = 0; i < maxColorMaskIndex; ++i) { + if (memcmp(&this->m_States.blend.colorMask[i], &states.blend.colorMask[i], sizeof(GLColor4f)) || force) { + if (GLDevice::m_ExtColorMaskIndexed) { + glColorMaskIndexedEXT( + i, + states.blend.colorMask[i].red, + states.blend.colorMask[i].green, + states.blend.colorMask[i].blue, + states.blend.colorMask[i].alpha + ); + } else { + glColorMask( + states.blend.colorMask[i].red, + states.blend.colorMask[i].green, + states.blend.colorMask[i].blue, + states.blend.colorMask[i].alpha + ); + } + } + } + + if (this->m_States.blend.alphaBlend != states.blend.alphaBlend || force) { + if (states.blend.alphaBlend) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + } + + if (this->m_States.blend.srcBlendFactor != states.blend.srcBlendFactor + || this->m_States.blend.destBlendFactor != states.blend.destBlendFactor + || force) + { + glBlendFunc(states.blend.srcBlendFactor, states.blend.destBlendFactor); + } + + if (this->m_States.blend.blendOp != states.blend.blendOp || force) { + glBlendEquation(states.blend.blendOp); + } + + if (memcmp(&this->m_States.blend.blendColor, &states.blend.blendColor, sizeof(GLColor4f)) || force) { + glBlendColor( + states.blend.blendColor.r, + states.blend.blendColor.g, + states.blend.blendColor.b, + states.blend.blendColor.a + ); + } + + if (this->m_States.fixedFunc.fogEnable != states.fixedFunc.fogEnable) { + if (states.fixedFunc.fogEnable) { + glEnable(GL_FOG); + } else { + glDisable(GL_FOG); + } + } + + if (memcmp(&this->m_States.fixedFunc.fogColor, &states.fixedFunc.fogColor, sizeof(GLColor4f)) || force) { + glFogfv(GL_FOG_COLOR, (GLfloat*)&states.fixedFunc.fogColor); + } + + if (this->m_States.fixedFunc.fogMode != states.fixedFunc.fogMode || force) { + glFogi(GL_FOG_MODE, states.fixedFunc.fogMode); + } + + if (this->m_States.fixedFunc.fogStart != states.fixedFunc.fogStart || force) { + glFogf(GL_FOG_START, states.fixedFunc.fogStart); + } + + if (this->m_States.fixedFunc.fogEnd != states.fixedFunc.fogEnd || force) { + glFogf(GL_FOG_END, states.fixedFunc.fogEnd); + } + + if (this->m_States.fixedFunc.fogDensity != states.fixedFunc.fogDensity || force) { + glFogf(GL_FOG_DENSITY, states.fixedFunc.fogDensity); + } + + if (this->m_States.fixedFunc.alphaTestEnable != states.fixedFunc.alphaTestEnable || force) { + if (states.fixedFunc.alphaTestEnable) { + glEnable(GL_ALPHA_TEST); + } else { + glDisable(GL_ALPHA_TEST); + } + } + + if (this->m_States.fixedFunc.alphaTestFunc != states.fixedFunc.alphaTestFunc + || this->m_States.fixedFunc.alphaTestRef != states.fixedFunc.alphaTestRef + || force) + { + glAlphaFunc(states.fixedFunc.alphaTestFunc, states.fixedFunc.alphaTestRef); + } + + if (this->m_States.fixedFunc.transforms.modelView.isIdentity != states.fixedFunc.transforms.modelView.isIdentity + || memcmp(this->m_States.fixedFunc.transforms.modelView.m, states.fixedFunc.transforms.modelView.m, sizeof(float) * 16) + || force) + { + glMatrixMode(GL_MODELVIEW); + + if (states.fixedFunc.transforms.modelView.isIdentity) { + glLoadIdentity(); + } else { + glLoadMatrixf(states.fixedFunc.transforms.modelView.m); + } + + const_cast(states).fixedFunc.transforms.modelView.isDirty = false; + } + + if (this->m_States.fixedFunc.transforms.projection.isIdentity != states.fixedFunc.transforms.projection.isIdentity + || memcmp(this->m_States.fixedFunc.transforms.projection.m, states.fixedFunc.transforms.projection.m, sizeof(float) * 16) + || force) + { + glMatrixMode(GL_PROJECTION); + + // TODO + // - some interesting logic to manipulate the projection matrix before loading + } + + glMatrixMode(states.fixedFunc.transforms.matrixMode); + glMatrixMode(GL_MODELVIEW); + + if (states.fixedFunc.transforms.view.isIdentity) { + glLoadIdentity(); + } else { + glLoadMatrixf(states.fixedFunc.transforms.view.m); + } + + const_cast(states).fixedFunc.transforms.view.isDirty = false; + + if (this->m_States.fixedFunc.lighting.enable != states.fixedFunc.lighting.enable || force) { + if (states.fixedFunc.lighting.enable) { + glEnable(GL_LIGHTING); + } else { + glDisable(GL_LIGHTING); + } + } + + for (int32_t i = 0; i < 8; ++i) { + // TODO + // Set up each light + } + + if (memcmp(&this->m_States.fixedFunc.lighting.sceneAmbient, &states.fixedFunc.lighting.sceneAmbient, sizeof(GLColor4f)) || force) { + glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (GLfloat*)&states.fixedFunc.lighting.sceneAmbient); + } + + if (this->m_States.fixedFunc.lighting.material.materialSource != states.fixedFunc.lighting.material.materialSource || force) { + glColorMaterial(GL_FRONT_AND_BACK, states.fixedFunc.lighting.material.materialSource); + + // TODO + // this->Sub38A20(); + } + + if (this->m_States.fixedFunc.lighting.material.colorTracking != states.fixedFunc.lighting.material.colorTracking || force) { + if (states.fixedFunc.lighting.material.colorTracking) { + glEnable(GL_COLOR_MATERIAL); + } else { + glDisable(GL_COLOR_MATERIAL); + } + + // TODO + // this->Sub38A20(); + } + + if (memcmp(&this->m_States.fixedFunc.lighting.material.ambient, &states.fixedFunc.lighting.material.ambient, sizeof(GLColor4f)) || force) { + glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, (GLfloat*)&states.fixedFunc.lighting.material.ambient); + } + + if (memcmp(&this->m_States.fixedFunc.lighting.material.diffuse, &states.fixedFunc.lighting.material.diffuse, sizeof(GLColor4f)) || force) { + glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, (GLfloat*)&states.fixedFunc.lighting.material.diffuse); + } + + if (memcmp(&this->m_States.fixedFunc.lighting.material.specular, &states.fixedFunc.lighting.material.specular, sizeof(GLColor4f)) || force) { + glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (GLfloat*)&states.fixedFunc.lighting.material.specular); + } + + if (memcmp(&this->m_States.fixedFunc.lighting.material.emission, &states.fixedFunc.lighting.material.emission, sizeof(GLColor4f)) || force) { + glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, (GLfloat*)&states.fixedFunc.lighting.material.emission); + } + + if (this->m_States.fixedFunc.lighting.material.shininess != states.fixedFunc.lighting.material.shininess || force) { + glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, states.fixedFunc.lighting.material.shininess); + } + + for (int32_t i = 0; i < 8; ++i) { + // TODO fixedFunc.transforms.texture[i] + // TODO fixedFunc.texOp[i] + // TODO fixedFunc.texGen[i] + } + + glMatrixMode(states.fixedFunc.transforms.matrixMode); + + if (this->m_States.fixedFunc.normalizeNormal != states.fixedFunc.normalizeNormal) { + if (states.fixedFunc.normalizeNormal) { + glEnable(GL_NORMALIZE); + } else { + glDisable(GL_NORMALIZE); + } + } + + if (this->m_States.fixedFunc.pointSprite.enable != states.fixedFunc.pointSprite.enable || force) { + if (states.fixedFunc.pointSprite.enable) { + glEnable(GL_POINT_SPRITE); + glEnable(GL_PROGRAM_POINT_SIZE_EXT); + } else { + glDisable(GL_POINT_SPRITE); + glDisable(GL_PROGRAM_POINT_SIZE_EXT); + } + } + + if (this->m_States.fixedFunc.pointSprite.size != states.fixedFunc.pointSprite.size || force) { + glPointSize(states.fixedFunc.pointSprite.size); + } + + if (memcmp(&this->m_States.fixedFunc.pointSprite.attenuation, &states.fixedFunc.pointSprite.attenuation, sizeof(this->m_States.fixedFunc.pointSprite.attenuation)) || force) { + glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION, states.fixedFunc.pointSprite.attenuation); + } + + if (this->m_States.fixedFunc.pointSprite.min != states.fixedFunc.pointSprite.min || force ) { + glPointParameterfARB(GL_POINT_SIZE_MIN, states.fixedFunc.pointSprite.min); + } + + if (this->m_States.fixedFunc.pointSprite.max != states.fixedFunc.pointSprite.max || force) { + glPointParameterfARB(GL_POINT_SIZE_MAX, states.fixedFunc.pointSprite.max); + } + + if (this->m_States.shader.vertexShaderEnable != states.shader.vertexShaderEnable || force) { + if (states.shader.vertexShaderEnable) { + glEnable(GL_VERTEX_PROGRAM_ARB); + } else { + glDisable(GL_VERTEX_PROGRAM_ARB); + } + } + + glProgramEnvParameters4fvEXT(GL_VERTEX_PROGRAM_ARB, 0, 256, (GLfloat*)states.shader.vertexShaderConst); + + if (this->m_States.shader.pixelShaderEnable != states.shader.pixelShaderEnable || force) { + if (states.shader.pixelShaderEnable) { + glEnable(GL_FRAGMENT_PROGRAM_ARB); + } else { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } + } + + glProgramEnvParameters4fvEXT(GL_FRAGMENT_PROGRAM_ARB, 0, 64, (GLfloat*)states.shader.pixelShaderConst); + + if (memcmp(&this->m_States.clear.clearColor, &states.clear.clearColor, sizeof(GLColor4f)) || force) { + glClearColor( + states.clear.clearColor.r, + states.clear.clearColor.g, + states.clear.clearColor.b, + states.clear.clearColor.a + ); + } + + if (this->m_States.clear.clearDepth != states.clear.clearDepth || force) { + glClearDepth(states.clear.clearDepth); + } + + if (this->m_States.clear.clearStencil != states.clear.clearStencil || force) { + glClearStencil(states.clear.clearStencil); + } + + // Copy provided states into current states + + memcpy(&this->m_States.depth, &states.depth, sizeof(this->m_States.depth)); + memcpy(&this->m_States.stencil, &states.stencil, sizeof(this->m_States.stencil)); + + if (!states.stencil.useTwoSidedStencil) { + memcpy(&this->m_States.stencil.back, &states.stencil.front, sizeof(this->m_States.stencil.back)); + } + + if (this->m_CurrentTargetDepth) { + memcpy(&this->m_States.rasterizer, &states.rasterizer, sizeof(this->m_States.rasterizer)); + } else { + float v117 = this->m_States.rasterizer.constantDepthBias; + float v118 = this->m_States.rasterizer.slopeScaledDepthBias; + memcpy(&this->m_States.rasterizer, &states.rasterizer, sizeof(this->m_States.rasterizer)); + this->m_States.rasterizer.constantDepthBias = v117; + this->m_States.rasterizer.slopeScaledDepthBias = v118; + } + + memcpy(&this->m_States.blend, &states.blend, sizeof(this->m_States.blend)); + memcpy(&this->m_States.clear, &states.clear, sizeof(this->m_States.clear)); + + this->m_States.fixedFunc.fogEnable = states.fixedFunc.fogEnable; + memcpy(&this->m_States.fixedFunc.fogColor, &states.fixedFunc.fogColor, sizeof(GLColor4f)); + this->m_States.fixedFunc.fogMode = states.fixedFunc.fogMode; + this->m_States.fixedFunc.fogStart = states.fixedFunc.fogStart; + this->m_States.fixedFunc.fogEnd = states.fixedFunc.fogEnd; + + this->m_States.fixedFunc.alphaTestEnable = states.fixedFunc.alphaTestEnable; + this->m_States.fixedFunc.alphaTestRef = states.fixedFunc.alphaTestRef; + + memcpy(this->m_States.fixedFunc.texOp, states.fixedFunc.texOp, sizeof(this->m_States.fixedFunc.texOp)); + + this->m_States.fixedFunc.lighting.enable = states.fixedFunc.lighting.enable; + memcpy(&this->m_States.fixedFunc.lighting.sceneAmbient, &states.fixedFunc.lighting.sceneAmbient, sizeof(GLColor4f)); + + for (int32_t i = 0; i < 8; ++i) { + this->m_States.fixedFunc.lighting.lights[i].enable = states.fixedFunc.lighting.lights[i].enable; + memcpy(&this->m_States.fixedFunc.lighting.lights[i].position, &states.fixedFunc.lighting.lights[i].position, sizeof(GLfloat4)); + COPY_TRANSFORM(this->m_States.fixedFunc.lighting.lights[i].view, states.fixedFunc.lighting.lights[i].view); + this->m_States.fixedFunc.lighting.lights[i].constantAttenuation = states.fixedFunc.lighting.lights[i].constantAttenuation; + this->m_States.fixedFunc.lighting.lights[i].linearAttenuation = states.fixedFunc.lighting.lights[i].linearAttenuation; + this->m_States.fixedFunc.lighting.lights[i].quadraticAttenuation = states.fixedFunc.lighting.lights[i].quadraticAttenuation; + memcpy(&this->m_States.fixedFunc.lighting.lights[i].ambient, &states.fixedFunc.lighting.lights[i].ambient, sizeof(GLColor4f)); + memcpy(&this->m_States.fixedFunc.lighting.lights[i].diffuse, &states.fixedFunc.lighting.lights[i].diffuse, sizeof(GLColor4f)); + memcpy(&this->m_States.fixedFunc.lighting.lights[i].specular, &states.fixedFunc.lighting.lights[i].specular, sizeof(GLColor4f)); + } + + memcpy(&this->m_States.fixedFunc.lighting.material, &states.fixedFunc.lighting.material, sizeof(this->m_States.fixedFunc.lighting.material)); + + COPY_TRANSFORM(this->m_States.fixedFunc.transforms.modelView, states.fixedFunc.transforms.modelView); + COPY_TRANSFORM(this->m_States.fixedFunc.transforms.world, states.fixedFunc.transforms.world); + COPY_TRANSFORM(this->m_States.fixedFunc.transforms.view, states.fixedFunc.transforms.view); + COPY_TRANSFORM(this->m_States.fixedFunc.transforms.projection, states.fixedFunc.transforms.projection); + + for (int32_t i = 0; i < 8; ++i) { + COPY_TRANSFORM(this->m_States.fixedFunc.transforms.texture[i], states.fixedFunc.transforms.texture[i]); + } + + memcpy(this->m_States.fixedFunc.texCoordIndex, states.fixedFunc.texCoordIndex, sizeof(this->m_States.fixedFunc.texCoordIndex)); + memcpy(this->m_States.fixedFunc.texGen, states.fixedFunc.texGen, sizeof(this->m_States.fixedFunc.texGen)); + + memcpy(this->m_States.samplers, states.samplers, sizeof(this->m_States.samplers)); + memcpy(&this->m_States.shader, &states.shader, sizeof(this->m_States.shader)); +} + +void GLDevice::ApplyShaderConstants() { + GLShader* vs = this->m_VertexShader; + + if (vs) { + if (vs->m_UsingGLSL) { + vs->FlushUniforms(this->m_GLSLProgram); + } else { + auto start = this->m_DirtyVertexShaderConsts.start; + auto end = this->m_DirtyVertexShaderConsts.end; + + if (start != end) { + glProgramEnvParameters4fvEXT( + GL_VERTEX_PROGRAM_ARB, + start, + end - start, + reinterpret_cast(&this->m_States.shader.vertexShaderConst[start]) + ); + + this->m_DirtyVertexShaderConsts.start = 0; + this->m_DirtyVertexShaderConsts.end = 0; + } + } + } + + GLShader* ps = this->m_PixelShader; + + if (ps) { + if (ps->m_UsingGLSL) { + ps->FlushUniforms(this->m_GLSLProgram); + } else { + auto start = this->m_DirtyPixelShaderConsts.start; + auto end = this->m_DirtyPixelShaderConsts.end; + + if (start != end) { + glProgramEnvParameters4fvEXT( + GL_FRAGMENT_PROGRAM_ARB, + start, + end - start, + reinterpret_cast(&this->m_States.shader.pixelShaderConst[start]) + ); + + this->m_DirtyPixelShaderConsts.start = 0; + this->m_DirtyPixelShaderConsts.end = 0; + } + } + } +} + +void GLDevice::ApplyTransforms() { + // TODO +} + +void GLDevice::BindBuffer(GLBuffer* buffer, GLEnum target) { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + BLIZZARD_ASSERT(buffer != nullptr || target != GL_ZERO); + + GLEnum bindTarget = target == GL_ZERO ? buffer->m_Type : target; + GLuint bindName = buffer == nullptr ? 0 : buffer->m_BufferID; + + int32_t bindIndex; + + if (bindTarget == GL_ARRAY_BUFFER) { + bindIndex = 0; + } else if (bindTarget == GL_ELEMENT_ARRAY_BUFFER) { + bindIndex = 1; + } else if (bindTarget == GL_PIXEL_PACK_BUFFER) { + bindIndex = 2; + } else if (bindTarget == GL_PIXEL_UNPACK_BUFFER) { + bindIndex = 3; + } else { + BLIZZARD_ASSERT(false); + } + + if (bindTarget == GL_ARRAY_BUFFER) { + if (this->m_States.binding.vertexArrayObject && this->m_VertexArrayObject != &this->m_DefaultVertexArrayObject) { + glBindVertexArrayAPPLE(0); + this->m_States.binding.vertexArrayObject = 0; + this->m_VertexArrayObject = &this->m_DefaultVertexArrayObject; + } + } else if (bindTarget == GL_PIXEL_PACK_BUFFER) { + this->m_VertexArrayObject->m_Properties.m_PixelPackBuffer = buffer; + } else if (bindTarget == GL_PIXEL_UNPACK_BUFFER) { + this->m_VertexArrayObject->m_Properties.m_PixelUnpackBuffer = buffer; + } + + if (this->m_VertexArrayObject->m_GLStates.buffers[bindIndex] != bindName) { + glBindBuffer(bindTarget, bindName); + this->m_VertexArrayObject->m_GLStates.buffers[bindIndex] = bindName; + } +} + +void GLDevice::BindFramebuffer(GLFramebuffer* framebuffer) { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + + GLuint v3; + + if (framebuffer) { + v3 = framebuffer->m_FramebufferID; + } else { + v3 = 0; + } + + this->m_CurrentTarget = framebuffer; + + if (this->m_States.binding.framebuffer != v3) { + glBindFramebufferEXT(GL_FRAMEBUFFER, v3); + this->m_States.binding.framebuffer = v3; + } +} + +void GLDevice::BindGLSLProgram(GLGLSLProgram* a2) { + // TODO +} + +void GLDevice::BindShader(GLShader* shader) { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + BLIZZARD_ASSERT(shader); + + if (shader->var5 == GL_FRAGMENT_PROGRAM_ARB) { + if (this->m_States.binding.pixelProgram != shader->m_ShaderID) { + glBindProgramARB(GL_FRAGMENT_PROGRAM_ARB, shader->m_ShaderID); + this->m_States.binding.pixelProgram = shader->m_ShaderID; + } + } else if (shader->var5 == GL_VERTEX_PROGRAM_ARB) { + if (this->m_States.binding.vertexProgram != shader->m_ShaderID) { + glBindProgramARB(GL_VERTEX_PROGRAM_ARB, shader->m_ShaderID); + this->m_States.binding.vertexProgram = shader->m_ShaderID; + } + } else { + BLIZZARD_ASSERT(false); + } +} + +void GLDevice::BindTexture(GLEnum textureType, GLTexture* texture) { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + BLIZZARD_ASSERT(texture == nullptr || textureType == texture->m_TextureType); + + uint32_t textureID = texture ? texture->m_TextureID : 0; + uint32_t index = GLLTextureTypeToIndex(textureType); + + BLIZZARD_ASSERT(this->m_States.binding.texture[index][this->m_States.binding.currentActiveTexture] != textureID); + BLIZZARD_ASSERT(this->m_BoundTextures[index][this->m_States.binding.currentActiveTexture] != texture); + + GLTexture* boundTexture = this->m_BoundTextures[index][this->m_States.binding.currentActiveTexture]; + if (boundTexture) { + boundTexture->Unbind(this, this->m_States.binding.currentActiveTexture); + } + + glBindTexture(textureType, textureID); + this->m_BoundTextures[index][this->m_States.binding.currentActiveTexture] = texture; + this->m_States.binding.texture[index][this->m_States.binding.currentActiveTexture] = textureID; + + if (!texture || this->m_WorkerDevice || textureType == GL_TEXTURE_RECTANGLE_EXT) { + return; + } + + GLDevice* mainDevice = GLDevice::m_Devices[0]; + + if (texture->var7 == mainDevice->m_TextureList.begin()) { + texture->var7 = mainDevice->m_TextureList.insert(mainDevice->m_TextureList.begin(), texture); + + mainDevice->m_TextureTotalSize += texture->m_Size; + texture->m_LastFrameUsed = mainDevice->m_FrameNumber; + } else if (texture->m_LastFrameUsed == 0 || texture->m_LastFrameUsed + 60 < mainDevice->m_FrameNumber) { + if (texture->m_LastFrameUsed == 0) { + texture->RecreateGLTexture(); + mainDevice->m_TextureTotalSize += texture->m_Size; + } + + // TODO + // - oldest texture logic + + // TODO + // - list transfer logic + + texture->m_LastFrameUsed = mainDevice->m_FrameNumber; + } + + if (mainDevice->m_TextureTotalSize > 384 * 1024 * 1024) { + // TODO + // - track / clean up oldest textures + } +} + +void GLDevice::BindVertexArray(GLVertexArray* a2) { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + + int32_t v4 = a2 ? a2->m_VertexArrayID : 0; + + if (this->m_States.binding.vertexArrayObject != v4) { + glBindVertexArrayAPPLE(v4); + this->m_States.binding.vertexArrayObject = v4; + this->m_VertexArrayObject = a2 ? a2 : &this->m_DefaultVertexArrayObject; + } +} + +void GLDevice::BlitFramebuffer(GLMipmap* src, const GLRect* srcRect, GLMipmap* dst, const GLRect* dstRect, GLEnum mask, GLEnum filter) { + BLIZZARD_ASSERT(mask == GL_COLOR_BUFFER_BIT); + BLIZZARD_ASSERT(src != nullptr); + + GLRect fullSrcRect = { + 0, + 0, + src->GetWidth(), + src->GetHeight() + }; + + GLRect fullDstRect = { + 0, + 0, + dst ? dst->GetWidth() : this->m_Context.GetWidth(), + dst ? dst->GetHeight() : this->m_Context.GetHeight() + }; + + BLIZZARD_ASSERT(filter == GL_NEAREST); + // TODO + // BLIZZARD_ASSERT(srcRect == nullptr || *srcRect == fullSrcRect); + + // TODO + // - non-shader code path + + glEnable(GL_VERTEX_PROGRAM_ARB); + glEnable(GL_FRAGMENT_PROGRAM_ARB); + + auto alphaTestEnable = this->m_States.fixedFunc.alphaTestEnable; + auto depthTestEnable = this->m_States.depth.testEnable; + auto alphaBlend = this->m_States.blend.alphaBlend; + auto cullEnable = this->m_States.rasterizer.cullMode != 0; + auto scissorEnable = this->m_States.rasterizer.scissorEnable; + auto stencilTestEnable = this->m_States.stencil.testEnable; + uint32_t width; + uint32_t height; + + if (alphaTestEnable) { + glDisable(GL_ALPHA_TEST); + } + + if (depthTestEnable) { + glDisable(GL_DEPTH_TEST); + } + + if (alphaBlend) { + glDisable(GL_BLEND); + } + + if (cullEnable) { + glDisable(GL_CULL_FACE); + } + + if (scissorEnable) { + glDisable(GL_SCISSOR_TEST); + } + + if (stencilTestEnable) { + glDisable(GL_STENCIL_TEST); + } + + if (GLDevice::m_ExtColorMaskIndexed) { + glColorMaskIndexedEXT(0, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } else { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } + + if (dst) { + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + dst->m_Target, + dst->GetTextureID(), + dst->m_Level + ); + + auto currentTargetDepth = this->m_CurrentTargetDepth; + if (currentTargetDepth) { + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + currentTargetDepth->m_Target, + 0, + 0 + ); + } + + auto currentTargetStencil = this->m_CurrentTargetStencil; + if (currentTargetStencil) { + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER, + GL_STENCIL_ATTACHMENT, + currentTargetStencil->m_Target, + 0, + 0 + ); + } + + dst->m_Texture->SetAddressModeS(GL_CLAMP_TO_EDGE); + dst->m_Texture->SetAddressModeT(GL_CLAMP_TO_EDGE); + dst->m_Texture->SetMinFilterMode(GL_LINEAR); + dst->m_Texture->SetMagFilterMode(GL_LINEAR); + + width = dst->m_Width; + height = dst->m_Height; + } else { + glBindFramebufferEXT(GL_FRAMEBUFFER, 0); + + width = GLContext::GetCurrentGLContext()->GetBackingWidth(); + height = GLContext::GetCurrentGLContext()->GetBackingHeight(); + } + + if ( + this->m_States.rasterizer.viewport.left != 0 + || this->m_States.rasterizer.viewport.top != 0 + || this->m_States.rasterizer.viewport.width != width + || this->m_States.rasterizer.viewport.height != height + ) { + glViewport(0, 0, width, height); + } + + this->SetActiveTexture(0); + src->m_Texture->Bind(nullptr, true); + src->m_Texture->SetAddressModeS(GL_CLAMP_TO_EDGE); + src->m_Texture->SetAddressModeT(GL_CLAMP_TO_EDGE); + src->m_Texture->SetMinFilterMode(filter); + src->m_Texture->SetMagFilterMode(filter); + + this->SetVertexBuffer(0, GLDevice::m_BlitQuadVBO, 0, 28); + + GLVertexFormat* format = dst + ? &GLDevice::m_NormalBlitVF + : &GLDevice::m_InvertedBlitVF; + + this->SetVertexFormat(format); + + auto vertexShader = this->m_VertexShader; + auto pixelShader = this->m_PixelShader; + this->BindGLSLProgram(nullptr); + + this->SetShader(GLShader::eVertexShader, GLDevice::m_DeviceShaders[0]); + this->SetShader(GLShader::ePixelShader, GLDevice::m_DeviceShaders[2]); + + this->m_DefaultVertexArrayObject.m_Properties.m_VertexBase = 0; + GLVertexArray::FindVertexArray(this, this->m_DefaultVertexArrayObject); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + this->SetShader(GLShader::eVertexShader, vertexShader); + this->SetShader(GLShader::ePixelShader, pixelShader); + + if (alphaTestEnable) { + glEnable(GL_ALPHA_TEST); + } + + if (depthTestEnable) { + glEnable(GL_DEPTH_TEST); + } + + if (alphaBlend) { + glEnable(GL_BLEND); + } + + if (cullEnable) { + glEnable(GL_CULL_FACE); + } + + if (scissorEnable) { + glEnable(GL_SCISSOR_TEST); + } + + if (stencilTestEnable) { + glEnable(GL_STENCIL_TEST); + } + + if (GLDevice::m_ExtColorMaskIndexed) { + glColorMaskIndexedEXT( + 0, + this->m_States.blend.colorMask[0].red, + this->m_States.blend.colorMask[0].green, + this->m_States.blend.colorMask[0].blue, + this->m_States.blend.colorMask[0].alpha + ); + } else { + glColorMask( + this->m_States.blend.colorMask[0].red, + this->m_States.blend.colorMask[0].green, + this->m_States.blend.colorMask[0].blue, + this->m_States.blend.colorMask[0].alpha + ); + } + + if (dst) { + auto currentTargetColor = this->m_CurrentTargetColor[0]; + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + dst->m_Target, + currentTargetColor->GetTextureID(), + 0 + ); + + auto currentTargetDepth = this->m_CurrentTargetDepth; + if (currentTargetDepth) { + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER, + GL_DEPTH_ATTACHMENT, + currentTargetDepth->m_Target, + currentTargetDepth->GetTextureID(), + currentTargetDepth->m_Level + ); + } + + auto currentTargetStencil = this->m_CurrentTargetStencil; + if (currentTargetStencil) { + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER, + GL_STENCIL_ATTACHMENT, + currentTargetStencil->m_Target, + currentTargetStencil->GetTextureID(), + currentTargetStencil->m_Level + ); + } + } + + if ( + this->m_States.rasterizer.viewport.left != 0 + || this->m_States.rasterizer.viewport.top != 0 + || this->m_States.rasterizer.viewport.width != width + || this->m_States.rasterizer.viewport.height != height + ) { + glViewport( + this->m_States.rasterizer.viewport.left, + this->m_States.rasterizer.viewport.top, + this->m_States.rasterizer.viewport.width, + this->m_States.rasterizer.viewport.height + ); + } +} + +void GLDevice::CheckDepthTarget() { + BLIZZARD_ASSERT(this->m_CurrentTargetColor[0] == nullptr || this->m_CurrentTargetColor[0]->GetTexture()->IsValid()); + BLIZZARD_ASSERT(this->m_CurrentTargetColor[1] == nullptr || this->m_CurrentTargetColor[1]->GetTexture()->IsValid()); + BLIZZARD_ASSERT(this->m_CurrentTargetColor[2] == nullptr || this->m_CurrentTargetColor[2]->GetTexture()->IsValid()); + BLIZZARD_ASSERT(this->m_CurrentTargetColor[3] == nullptr || this->m_CurrentTargetColor[3]->GetTexture()->IsValid()); + + BLIZZARD_ASSERT(this->m_CurrentTargetDepth == nullptr || this->m_CurrentDepthBuffer != nullptr); + + if (!this->m_CurrentDepthBuffer) { + return; + } + + BLIZZARD_ASSERT(this->m_CurrentDepthBuffer->GetTexture()->IsValid()); + + auto currentTargetColor = this->m_CurrentTargetColor[0]; + auto currentDepthBuffer = this->m_CurrentDepthBuffer; + + if ( + !currentTargetColor + || (currentTargetColor->m_Width == currentDepthBuffer->m_Width && currentTargetColor->m_Height == currentDepthBuffer->m_Height) + ) { + if (this->m_CurrentTargetDepth != currentDepthBuffer) { + this->Sub34BB0(GL_DEPTH_ATTACHMENT, currentDepthBuffer, 0); + this->m_CurrentTarget->Attach(currentDepthBuffer, GL_DEPTH_ATTACHMENT, 0); + auto currentTargetDepth = this->m_CurrentTargetDepth; + this->m_CurrentTargetDepth = currentDepthBuffer; + this->m_CurrentDepthBuffer = currentDepthBuffer; + + if ( + (currentTargetDepth == nullptr && currentDepthBuffer != nullptr) + || (currentDepthBuffer != nullptr && currentTargetDepth != nullptr && currentTargetDepth->m_DepthBits != currentDepthBuffer->m_DepthBits) + ) { + this->SetDepthBias(this->m_ConstantDepthBias, this->m_SlopeScaledDepthBias); + } + + if (this->m_CurrentDepthBuffer->GetFormat() == GLTF_D24S8) { + this->Sub34BB0(GL_STENCIL_ATTACHMENT, this->m_CurrentDepthBuffer, 0); + this->m_CurrentTarget->Attach(this->m_CurrentDepthBuffer, GL_STENCIL_ATTACHMENT, 0); + this->m_CurrentTargetStencil = this->m_CurrentDepthBuffer; + } + } + + return; + } + + auto currentTargetDepth = this->m_CurrentTargetDepth; + + if (currentTargetDepth == currentDepthBuffer) { + this->Sub34BB0(GL_DEPTH_ATTACHMENT, nullptr, 0); + this->m_CurrentTarget->Attach(nullptr, GL_DEPTH_ATTACHMENT, 0); + this->m_CurrentTargetDepth = nullptr; + this->m_CurrentDepthBuffer = currentTargetDepth; + + if (currentTargetDepth->GetFormat() == GLTF_D24S8) { + this->Sub34BB0(GL_STENCIL_ATTACHMENT, nullptr, 0); + this->m_CurrentTarget->Attach(nullptr, GL_STENCIL_ATTACHMENT, 0); + this->m_CurrentTargetStencil = nullptr; + } + } +} + +void GLDevice::Clear(uint32_t clearMask, const GLColor4f& clearColor, double clearDepth, int32_t clearStencil) { + this->CheckDepthTarget(); + this->RestoreTextures(); + + auto colorMask = this->m_States.blend.colorMask[0]; + + if (clearMask & 0x4000) { + for (int32_t i = 0; i < 4; i++) { + if (this->m_CurrentTargetColor[i] && this->m_CurrentTargetColor[i]->GetTextureID() == this->m_States.binding.texture[0][this->m_States.binding.currentActiveTexture]) { + this->BindTexture(this->m_CurrentTargetColor[i]->m_Target, nullptr); + } + } + + this->SetColorWriteMask(1, 1, 1, 1, 0); + this->SetClearColor(clearColor); + } + + if (clearMask & 0x100) { + if (this->m_CurrentTargetDepth) { + if (this->m_CurrentTargetDepth->GetTextureID() == this->m_States.binding.texture[0][this->m_States.binding.currentActiveTexture]) { + this->BindTexture(this->m_CurrentTargetDepth->m_Target, nullptr); + } + + if (!this->m_States.depth.writeMask) { + glDepthMask(1); + } + + this->SetClearDepth(clearDepth); + } else { + clearMask &= ~0x100; + } + } else { + if (this->m_CurrentTargetDepth) { + this->m_CurrentTarget->Detach(GL_DEPTH_ATTACHMENT); + } + } + + if (clearMask & 0x400) { + if (this->m_CurrentTargetStencil) { + if (this->m_CurrentTargetStencil->GetTextureID() == this->m_States.binding.texture[0][this->m_States.binding.currentActiveTexture]) { + this->BindTexture(this->m_CurrentTargetStencil->m_Target, nullptr); + } + + if (this->m_States.stencil.writeMask != -1) { + glStencilMask(-1); + } + + this->SetClearStencil(clearStencil); + } else { + clearMask &= ~0x400; + } + } else { + if (this->m_CurrentTargetStencil) { + this->m_CurrentTarget->Detach(GL_STENCIL_ATTACHMENT); + } + } + + auto scissorEnable = this->m_States.rasterizer.scissorEnable; + auto scissorRect = this->m_States.rasterizer.scissor; + auto currentTargetColor = this->m_CurrentTargetColor[0]; + + if ( + currentTargetColor + && ( + this->m_States.rasterizer.viewport.left + || this->m_States.rasterizer.viewport.top + || this->m_States.rasterizer.viewport.width != currentTargetColor->m_Width + || this->m_States.rasterizer.viewport.height != currentTargetColor->m_Height + ) + ) { + this->SetScissor(1, this->m_States.rasterizer.viewport); + glClear(clearMask); + this->SetScissor(scissorEnable, scissorRect); + } else { + glClear(clearMask); + } + + if (clearMask & 0x4000) { + this->SetColorWriteMask(colorMask.red, colorMask.blue, colorMask.green, colorMask.alpha, 0); + } + + if (clearMask & 0x100) { + if (!this->m_States.depth.writeMask) { + glDepthMask(0); + } + } else { + if (this->m_CurrentTargetDepth) { + this->m_CurrentTarget->Attach(this->m_CurrentTargetDepth, GL_DEPTH_ATTACHMENT, 0); + } + } + + if (clearMask & 0x400) { + if (this->m_States.stencil.writeMask != -1) { + glStencilMask(this->m_States.stencil.writeMask); + } + } else { + if (this->m_CurrentTargetStencil) { + this->m_CurrentTarget->Attach(this->m_CurrentTargetStencil, GL_STENCIL_ATTACHMENT, 0); + } + } +} + +void GLDevice::CopyTex(uint32_t a2, uint32_t a3, GLMipmap* dst, const GLRect* framebufferRect) { + BLIZZARD_ASSERT(framebufferRect->width == dst->GetWidth()); + BLIZZARD_ASSERT(framebufferRect->height == dst->GetHeight()); + + dst->m_Texture->Bind(nullptr, false); + + glCopyTexSubImage2D( + dst->m_Target, + dst->m_Level, + a2, + a3, + framebufferRect->left, + framebufferRect->top, + framebufferRect->width, + framebufferRect->height + ); +} + +GLBuffer* GLDevice::CreateBuffer(GLEnum type, uint32_t a3, const void* a4, GLEnum usage, GLEnum format) { + return GLBuffer::Create(type, a3, a4, usage, format); +} + +GLShader* GLDevice::CreateShader(GLShader::ShaderType type, const void* buf, int32_t codeLen, const char* name) { + // TODO + // Blizzard::Debug::Assert(this); + + return GLShader::Create(type, GLDevice::m_UseHybridShader, false, "", buf, codeLen, "", name, nullptr); +} + +GLTexture* GLDevice::CreateTexture2D(uint32_t width, uint32_t height, uint32_t numMipMap, GLTextureFormat format, uint32_t flags) { + return GLTexture2D::Create(width, height, numMipMap, format, flags); +} + +GLTexture* GLDevice::CreateTextureCubeMap(uint32_t size, uint32_t numMipMap, GLTextureFormat format, uint32_t flags) { + // TODO + + return nullptr; +} + +void GLDevice::Draw(GLEnum primitive, uint32_t a3, uint32_t a4) { + // TODO +} + +void GLDevice::DrawIndexed(GLEnum primitive, uint32_t a3, uint32_t a4, uint32_t a5, uint32_t a6, uint32_t count) { + this->GLLDraw(primitive, a3, a4, a5, a6, count); +} + +void GLDevice::DrawRect() { + if (!this->m_Context.m_Window) { + return; + } + + if (!this->m_UseWindowSystemBuffer || this->m_FlippedSystemBuffer) { + GLTexture2D* backBuffer = this->m_BackBufferColor; + + if (!backBuffer) { + return; + } + + GLMipmap* backBufferImage = backBuffer->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + this->BlitFramebuffer(backBufferImage, nullptr, nullptr, nullptr, GL_COLOR_BUFFER_BIT, GL_NEAREST); + + glBindFramebufferEXT(GL_FRAMEBUFFER, 0); + this->m_States.binding.framebuffer = 0; + } + + this->m_Context.Swap(); + + if (!this->m_UseWindowSystemBuffer || this->m_FlippedSystemBuffer) { + glBindFramebufferEXT(GL_FRAMEBUFFER, this->m_CurrentTarget->m_FramebufferID); + this->m_States.binding.framebuffer = this->m_CurrentTarget->m_FramebufferID; + } +} + +uint32_t GLDevice::GetID() { + return this->m_ID; +} + +GLFramebuffer* GLDevice::GetCurrentTarget() { + return this->m_CurrentTarget; +} + +GLShader* GLDevice::GetShader(GLShader::ShaderType shaderType) { + if (shaderType == GLShader::eVertexShader) { + return this->m_VertexShader; + } else if (shaderType == GLShader::ePixelShader) { + return this->m_PixelShader; + } else { + return nullptr; + } +} + +const GLStates::VertexArrayObject& GLDevice::GetVertexArrayStates() { + return this->m_VertexArrayObject->m_GLStates; +} + +void GLDevice::GLLDraw(GLEnum mode, uint32_t start, uint32_t end, uint32_t a5, uint32_t a6, uint32_t count) { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + + this->CheckDepthTarget(); + + if (!this->m_DrawCount) { + if (this->m_Context.m_MTGLEnabled) { + glFlush(); + } + + // GLFence::TestFences(); + } + + this->m_DrawCount++; + + this->RestoreTextures(); + + this->m_DefaultVertexArrayObject.m_Properties.m_VertexBase = a5; + GLVertexArray::FindVertexArray(this, this->m_DefaultVertexArrayObject); + + auto vs = this->m_VertexShader; + auto ps = this->m_PixelShader; + + if (ps) { + // TODO + // this->Sub30440(); + } + + if (this->m_TexWorker && !this->m_TexWorker->m_UnkA1) { + this->m_TexWorker->WaitOnGLObjects(); + } + + if (vs) { + this->SetShader(GLShader::eVertexShader, vs); + } else { + this->ApplyTransforms(); + } + + if (ps) { + this->SetShader(GLShader::ePixelShader, ps); + } else { + this->UpdateFFPTexturing(); + } + + if (vs && vs->m_UsingGLSL && ps && ps->m_UsingGLSL) { + this->m_GLSLProgram = GLGLSLProgram::Find(vs, ps); + } else { + this->BindGLSLProgram(nullptr); + } + + this->ApplyShaderConstants(); + + if (count) { + GLBuffer* buffer = this->m_VertexArrayObject->m_Properties.m_IndexBuffer; + GLEnum format = buffer->m_IndexFormat; + + uint32_t v18; + + if (format == GL_UNSIGNED_SHORT) { + v18 = 1; + } else if (format == GL_UNSIGNED_INT) { + v18 = 2; + } else { + BLIZZARD_ASSERT(!"buffer uses unknown format"); + } + + void* indices = GLBuffer::m_UsingVBO + ? reinterpret_cast(a6 << v18) + : buffer->m_Data + (a6 << v18); + + glDrawRangeElements(mode, start, end, count, buffer->m_IndexFormat, indices); + } else { + glDrawArrays(mode, start, end - start); + } +} + +void GLDevice::Init(GLAbstractWindow* a2, const char* a3, uint32_t a4, GLTextureFormat a5) { + if (this->m_Init) { + return; + } + + System_Autorelease::ScopedPool autorelease; + + this->m_BatchViewerEnabled = a4 & 0x1; + this->m_UseWindowSystemBuffer = a4 & (0x2 | 0x4); + this->m_FlippedSystemBuffer = a4 & 0x4; + this->m_ShaderCompiler = a4 & 0x8; + this->m_WorkerDevice = a4 & 0x10; + + this->m_DebugName.assign(a3, strlen(a3)); + + GLDevice::Set(this); + + GLTextureFormat v6 = GLTF_INVALID; + + if (this->m_UseWindowSystemBuffer) { + v6 = a5; + } + + this->m_Context.SetContextFormat(v6, 1); + this->m_Context.SetWindow(a2, false); + + // TODO + // if (dword_10329E4 == dword_10329E0) + // GLRendererInfo::InitExtensions(); + + this->LoadDefaultStates(); + + this->m_Init = true; + + if (this->m_ID == -1) { + this->m_ID = GLDevice::m_Devices.size(); + GLDevice::m_Devices.insert(GLDevice::m_Devices.end(), this); + } else { + GLDevice::m_Devices[this->m_ID] = this; + } + + if (!GLDevice::m_StaticResourcesRefCount) { + GLContext::s_MainContext = this->m_Context.m_Context->context; + GLDevice::InitPools(); + GLDevice::StaticInit(); + } + + ++GLDevice::m_StaticResourcesRefCount; + + if (!this->m_WorkerDevice) { + GLWorker* v11 = new GLWorker(this); + this->m_TexWorker = v11; + } + + glBindFramebufferEXT(GL_FRAMEBUFFER, 0); + glClear(GL_COLOR_BUFFER_BIT); + + GLFramebuffer* v12 = GLFramebuffer::Create(0); + this->m_FBOTarget = v12; + GLFramebuffer* currentTarget = v12; + + // TODO verify this check + if (this->m_UseWindowSystemBuffer) { + GLFramebuffer* v14 = GLFramebuffer::Create(1); + this->m_SystemTarget = v14; + currentTarget = v14; + } + + this->m_CurrentTarget = currentTarget; + + GLuint v15 = 0; + + if (currentTarget) { + v15 = currentTarget->m_FramebufferID; + } + + if (this->m_States.binding.framebuffer != v15) { + glBindFramebufferEXT(GL_FRAMEBUFFER, v15); + this->m_States.binding.framebuffer = v15; + } +} + +void GLDevice::LoadDefaultStates() { + // Depth + + this->m_States.depth.testEnable = false; + this->m_States.depth.compareFunc = GL_GEQUAL; + this->m_States.depth.writeMask = true; + + // Stencil + + this->m_States.stencil.testEnable = false; + this->m_States.stencil.ref = 0; + this->m_States.stencil.mask = 0xFFFFFFFF; + this->m_States.stencil.writeMask = 0xFFFFFFFF; + this->m_States.stencil.useTwoSidedStencil = false; + + this->m_States.stencil.front.compareFunc = GL_ALWAYS; + this->m_States.stencil.front.opFail = GL_KEEP; + this->m_States.stencil.front.opZFail = GL_KEEP; + this->m_States.stencil.front.opZPass = GL_KEEP; + + this->m_States.stencil.back.compareFunc = GL_ALWAYS; + this->m_States.stencil.back.opFail = GL_KEEP; + this->m_States.stencil.back.opZFail = GL_KEEP; + this->m_States.stencil.back.opZPass = GL_KEEP; + + // Rasterizer + + this->m_States.rasterizer.cullMode = GL_CCW; + this->m_States.rasterizer.cullFaceMode = GL_BACK; + this->m_States.rasterizer.fillMode = GL_FILL; + this->m_States.rasterizer.constantDepthBias = 0.0; + this->m_States.rasterizer.slopeScaledDepthBias = 0.0; + + this->m_States.rasterizer.viewport = { + 0, + 0, + this->m_Context.GetWidth(), + this->m_Context.GetHeight() + }; + + this->m_States.rasterizer.zNear = 0.0; + this->m_States.rasterizer.zFar = 1.0; + this->m_States.rasterizer.scissorEnable = false; + + this->m_States.rasterizer.scissor = { + 0, + 0, + this->m_Context.GetWidth(), + this->m_Context.GetHeight() + }; + + this->m_States.rasterizer.clipPlaneMask = 0; + memset(this->m_States.rasterizer.clipPlanes, 0, sizeof(this->m_States.rasterizer.clipPlanes)); + + // Blend + + for (int32_t i = 0; i < 4; ++i) { + this->m_States.blend.colorMask[i] = { true, true, true, true }; + } + + this->m_States.blend.alphaBlend = 0; + this->m_States.blend.srcBlendFactor = 1; + this->m_States.blend.destBlendFactor = 0; + this->m_States.blend.blendOp = GL_FUNC_ADD; + this->m_States.blend.blendColor = GLColor4f::ZERO; + + // FixedFunc + + this->m_States.fixedFunc.fogColor = { 0.0, 0.0, 0.0, 0.0 }; + this->m_States.fixedFunc.fogStart = 0.0; + this->m_States.fixedFunc.alphaTestRef = 0.0; + this->m_States.fixedFunc.fogEnable = 0; + this->m_States.fixedFunc.fogMode = GL_LINEAR; + this->m_States.fixedFunc.fogEnd = 1.0; + this->m_States.fixedFunc.fogDensity = 1.0; + this->m_States.fixedFunc.alphaTestEnable = 0; + this->m_States.fixedFunc.alphaTestFunc = GL_ALWAYS; + + for (int32_t i = 0; i < 8; ++i) { + this->m_States.fixedFunc.texOp[i].texturing = 0; + this->m_States.fixedFunc.texOp[i].constant = GLColor4f::ZERO; + this->m_States.fixedFunc.texOp[i].colorOp = GL_MODULATE; + this->m_States.fixedFunc.texOp[i].colorScale = 1.0; + this->m_States.fixedFunc.texOp[i].colorArg0 = GL_TEXTURE; + this->m_States.fixedFunc.texOp[i].colorArg1 = GL_PREVIOUS; + this->m_States.fixedFunc.texOp[i].colorArg2 = GL_CONSTANT; + this->m_States.fixedFunc.texOp[i].alphaOp = GL_MODULATE; + this->m_States.fixedFunc.texOp[i].alphaScale = 1.0; + this->m_States.fixedFunc.texOp[i].alphaArg0 = GL_TEXTURE; + this->m_States.fixedFunc.texOp[i].alphaArg1 = GL_PREVIOUS; + this->m_States.fixedFunc.texOp[i].alphaArg2 = GL_CONSTANT; + } + + this->m_States.fixedFunc.lighting.enable = true; + this->m_States.fixedFunc.lighting.sceneAmbient = { 0.0, 0.0, 0.0, 0.0 }; + + for (int32_t i = 0; i < 8; ++i) { + this->m_States.fixedFunc.lighting.lights[i].enable = false; + this->m_States.fixedFunc.lighting.lights[i].position = { 0.0, 0.0, 1.0, 0.0 }; + this->m_States.fixedFunc.lighting.lights[i].view.isDirty = true; + this->m_States.fixedFunc.lighting.lights[i].view.isIdentity = true; + this->m_States.fixedFunc.lighting.lights[i].constantAttenuation = 0.0; + this->m_States.fixedFunc.lighting.lights[i].linearAttenuation = 0.0; + this->m_States.fixedFunc.lighting.lights[i].quadraticAttenuation = 0.0; + this->m_States.fixedFunc.lighting.lights[i].ambient = GLColor4f::ZERO; + this->m_States.fixedFunc.lighting.lights[i].diffuse = GLColor4f::WHITE; + this->m_States.fixedFunc.lighting.lights[i].specular = GLColor4f::ZERO; + } + + this->m_States.fixedFunc.lighting.lights[0].enable = true; + + this->m_States.fixedFunc.lighting.material.materialSource = 4609; + this->m_States.fixedFunc.lighting.material.ambient = GLColor4f::ZERO; + this->m_States.fixedFunc.lighting.material.diffuse = GLColor4f::WHITE; + this->m_States.fixedFunc.lighting.material.specular = GLColor4f::ZERO; + this->m_States.fixedFunc.lighting.material.shininess = 0.0; + this->m_States.fixedFunc.lighting.material.emission = GLColor4f::ZERO; + + this->m_States.fixedFunc.transforms.matrixMode = 5888; + this->m_States.fixedFunc.transforms.modelviewStatus = 5888; + this->m_States.fixedFunc.transforms.modelView.isIdentity = true; + this->m_States.fixedFunc.transforms.modelView.isDirty = true; + this->m_States.fixedFunc.transforms.world.isIdentity = true; + this->m_States.fixedFunc.transforms.world.isDirty = true; + this->m_States.fixedFunc.transforms.view.isIdentity = true; + this->m_States.fixedFunc.transforms.view.isDirty = true; + this->m_States.fixedFunc.transforms.projection.isIdentity = true; + this->m_States.fixedFunc.transforms.projection.isDirty = true; + + for (int32_t i = 0; i < 8; ++i) { + this->m_States.fixedFunc.transforms.texture[i].isIdentity = true; + this->m_States.fixedFunc.transforms.texture[i].isDirty = true; + } + + for (int32_t i = 0; i < 8; ++i) { + this->m_States.fixedFunc.texCoordIndex[i] = i; + } + + for (int32_t i = 0; i < 8; ++i) { + this->m_States.fixedFunc.texGen[i].S = 0; + this->m_States.fixedFunc.texGen[i].T = 0; + this->m_States.fixedFunc.texGen[i].R = 0; + this->m_States.fixedFunc.texGen[i].Q = 0; + } + + this->m_States.fixedFunc.pointSprite.enable = false; + this->m_States.fixedFunc.pointSprite.size = 1.0; + this->m_States.fixedFunc.pointSprite.attenuation[0] = 1.0; + this->m_States.fixedFunc.pointSprite.attenuation[1] = 0.0; + this->m_States.fixedFunc.pointSprite.attenuation[2] = 0.0; + this->m_States.fixedFunc.pointSprite.min = 0.0; + this->m_States.fixedFunc.pointSprite.max = 1.0; + + this->m_States.fixedFunc.normalizeNormal = false; + + // Samplers + + for (int32_t i = 0; i < 16; ++i) { + this->m_States.samplers[i].mipmapBias = 0.0; + this->m_States.samplers[i].addressModeS = 10497; + this->m_States.samplers[i].addressModeT = 10497; + this->m_States.samplers[i].addressModeR = 10497; + this->m_States.samplers[i].magFilterMode = 9729; + this->m_States.samplers[i].minFilterMode = 9729; + this->m_States.samplers[i].maxAnisotropy = 1.0; + this->m_States.samplers[i].borderColor = { 0.0, 0.0, 0.0, 0.0 }; + } + + // Shader + + this->m_States.shader.vertexShaderEnable = false; + + for (int32_t i = 0; i < 256; ++i) { + this->m_States.shader.vertexShaderConst[i] = { 0.0, 0.0, 0.0, 1.0 }; + } + + this->m_States.shader.pixelShaderEnable = false; + + for (int32_t i = 0; i < 64; ++i) { + this->m_States.shader.pixelShaderConst[i] = { 0.0, 0.0, 0.0, 1.0 }; + } + + // Binding + + memset(&this->m_States.binding, 0, sizeof(this->m_States.binding)); + + // Clear + + this->m_States.clear.clearColor = GLColor4f::ZERO; + this->m_States.clear.clearDepth = 1.0; + this->m_States.clear.clearStencil = 0; + + // Misc + + this->m_States.misc.drawBuffers[0] = 0; + this->m_States.misc.drawBuffers[1] = 0; + this->m_States.misc.drawBuffers[2] = 0; + this->m_States.misc.drawBuffers[3] = 0; + this->m_States.misc.readBuffer = 0; + + // Assign default states + memcpy(&this->m_DefaultStates.depth, &this->m_States.depth, sizeof(this->m_DefaultStates.depth)); + memcpy(&this->m_DefaultStates.stencil, &this->m_States.stencil, sizeof(this->m_DefaultStates.stencil)); + memcpy(&this->m_DefaultStates.rasterizer, &this->m_States.rasterizer, sizeof(this->m_DefaultStates.rasterizer)); + memcpy(&this->m_DefaultStates.blend, &this->m_States.blend, sizeof(this->m_DefaultStates.blend)); + memcpy(&this->m_DefaultStates.clear, &this->m_States.clear, sizeof(this->m_DefaultStates.clear)); + memcpy(&this->m_DefaultStates.fixedFunc, &this->m_States.fixedFunc, sizeof(this->m_DefaultStates.fixedFunc)); + memcpy(this->m_DefaultStates.samplers, this->m_States.samplers, sizeof(this->m_DefaultStates.samplers)); + memcpy(&this->m_DefaultStates.shader, &this->m_States.shader, sizeof(this->m_DefaultStates.shader)); + memcpy(&this->m_DefaultStates.binding, &this->m_States.binding, sizeof(this->m_DefaultStates.binding)); + memcpy(&this->m_DefaultStates.misc, &this->m_States.misc, sizeof(this->m_DefaultStates.misc)); + + // TODO + // if (v54 != this->m_States.misc.unpackClientStorage) { + // glPixelStorei(34226, v54); + // this->m_States.misc.unpackClientStorage = v54; + // } + + this->ApplyGLBindings(this->m_States, 1); + this->ApplyGLStates(this->m_States, 1); +} + +void GLDevice::ResetBackbuffer(uint32_t width, uint32_t height, GLTextureFormat colorFormat, GLTextureFormat depthFormat, uint32_t sampleCount) { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + + if ( + this->m_BackBufferColor + && this->m_BackBufferColor->m_Width == width + && this->m_BackBufferColor->m_Height == height + && this->m_BackBufferColor->m_Format == colorFormat + && this->m_BackBufferDepth + && this->m_BackBufferDepth->m_Format == depthFormat + ) { + if (this->m_UseWindowSystemBuffer && this->m_CurrentTarget->GetSampleCount() != sampleCount) { + this->m_Context.SetContextFormat(depthFormat, sampleCount); + } + + return; + } + + auto v24 = this->m_BackBufferColor != 0; + + if (this->m_BackBufferColor) { + this->m_BackBufferColor->Release(); + this->m_BackBufferColor = nullptr; + } + + if (this->m_BackBufferStencil) { + this->m_BackBufferStencil->Release(); + this->m_BackBufferStencil = nullptr; + } + + if (this->m_BackBufferDepth) { + this->m_BackBufferDepth->Release(); + this->m_BackBufferDepth = nullptr; + } + + GLTextureFormat v10 = this->m_UseWindowSystemBuffer ? depthFormat : GLTF_INVALID; + this->m_Context.SetContextFormat(v10, sampleCount); + + this->BindFramebuffer( + this->m_UseWindowSystemBuffer + ? this->m_SystemTarget + : this->m_FBOTarget + ); + + if (this->m_UseWindowSystemBuffer && !v24) { + this->Clear(0x4500, GLColor4f::BLACK, 1.0, 0); + this->m_Context.Swap(); + } + + uint32_t v13 = GLTFLAG_SYSTEM_BUFFER; + if (!this->m_UseWindowSystemBuffer) { + v13 = 0; + } + + uint32_t v14 = GLTFLAG_RENDERTARGET; + if (!this->m_FlippedSystemBuffer) { + v14 = v13 | GLTFLAG_RENDERTARGET; + } + + auto v15 = GLTexture2D::Create(width, height, 1, colorFormat, v14); + this->m_BackBufferColor = v15; + auto v16 = v15->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + this->m_CurrentTargetColor[0] = v16; + this->m_CurrentTarget->Attach(v16, GL_COLOR_ATTACHMENT0, 0); + + GLRect v29 = { 0, 0, static_cast(width), static_cast(height) }; + this->SetViewport(v29, 0.0, 1.0); + + if (this->m_FlippedSystemBuffer) { + auto v19 = this->m_BackBufferColor->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + GLRect v25 = { 0, 0, v19->m_Width, v19->m_Height }; + this->CopyTex(0, 0, v19, &v25); + } + + if (depthFormat) { + if (depthFormat == GLTF_D24S8) { + auto v20 = GLTexture2D::Create(width, height, 1, GLTF_D24S8, v13 | GLTFLAG_RENDERTARGET | GLTFLAG_DEPTH | GLTFLAG_STENCIL); + this->m_BackBufferDepth = v20; + this->m_BackBufferStencil = v20; + + // TODO how to properly increment GLObject ref counts? + v20->AddRefTwin(); + v20->m_RefCount++; + + auto v22 = this->m_BackBufferStencil->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + this->m_CurrentTargetStencil = v22; + this->m_CurrentTarget->Attach(v22, GL_STENCIL_ATTACHMENT, 0); + } else { + this->m_BackBufferDepth = GLTexture2D::Create(width, height, 1, depthFormat, v13 | GLTFLAG_RENDERTARGET | GLTFLAG_DEPTH); + } + + auto v17 = this->m_BackBufferDepth->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + this->m_CurrentTargetDepth = v17; + this->m_CurrentTarget->Attach(v17, GL_DEPTH_ATTACHMENT, 0); + this->m_CurrentDepthBuffer = this->m_CurrentTargetDepth; + this->SetDepthTestEnable(1); + } + + BLIZZARD_ASSERT(this->m_CurrentTarget->IsValid()); + + this->Clear(0x4500, GLColor4f::BLACK, 1.0, 0); +} + +void GLDevice::Resize(uint32_t width, uint32_t height) { + auto colorFormat = this->m_BackBufferColor ? this->m_BackBufferColor->m_Format : GLTF_INVALID; + auto depthFormat = this->m_BackBufferDepth ? this->m_BackBufferDepth->m_Format : GLTF_INVALID; + + this->SetDisplay( + width, + height, + colorFormat, + depthFormat, + this->m_Context.m_RefreshRate, + this->m_Context.m_Windowed, + this->m_Context.m_CaptureDisplay, + this->m_Context.m_Context->sampleCount + ); +} + +void GLDevice::RestoreTextures() { + BLIZZARD_ASSERT(this->m_Context.IsCurrentContext()); + + for (int32_t i = 0; i < 16; i++) { + GLTexture* texture = this->m_Textures[i]; + + if (texture && !texture->IsValid()) { + this->m_Textures[i] = nullptr; + } + + this->SetTexture(i, this->m_Textures[i]); + } +} + +void GLDevice::SetActiveTexture(uint32_t a2) { + if (this->m_States.binding.currentActiveTexture != a2) { + glActiveTexture(GL_TEXTURE0 + a2); + this->m_States.binding.currentActiveTexture = a2; + } +} + +void GLDevice::SetAlphaBlend(GLEnum srcBlend, GLEnum dstBlend, GLEnum blendOp) { + if (this->m_States.blend.srcBlendFactor != srcBlend || this->m_States.blend.destBlendFactor != dstBlend) { + glBlendFunc(srcBlend, dstBlend); + this->m_States.blend.srcBlendFactor = srcBlend; + this->m_States.blend.destBlendFactor = dstBlend; + } + + if (this->m_States.blend.blendOp != blendOp) { + glBlendEquation(blendOp); + this->m_States.blend.blendOp = blendOp; + } +} + +void GLDevice::SetAlphaBlendEnable(bool enable) { + if (this->m_States.blend.alphaBlend != enable) { + if (enable) { + glEnable(GL_BLEND); + } else { + glDisable(GL_BLEND); + } + + this->m_States.blend.alphaBlend = enable; + } +} + +void GLDevice::SetAlphaTest(GLEnum func, float ref) { + if (this->m_States.fixedFunc.alphaTestFunc != func || this->m_States.fixedFunc.alphaTestRef != ref) { + BLIZZARD_ASSERT(ref <= 1.0f); + + glAlphaFunc(func, ref); + this->m_States.fixedFunc.alphaTestFunc = func; + this->m_States.fixedFunc.alphaTestRef = ref; + } +} + +void GLDevice::SetAlphaTestEnable(bool enable) { + if (this->m_States.fixedFunc.alphaTestEnable != enable) { + if (enable) { + glEnable(GL_ALPHA_TEST); + } else { + glDisable(GL_ALPHA_TEST); + } + } +} + +void GLDevice::SetClearColor(const GLColor4f& a2) { + if ( + this->m_States.clear.clearColor.r != a2.r + || this->m_States.clear.clearColor.g != a2.g + || this->m_States.clear.clearColor.b != a2.b + || this->m_States.clear.clearColor.a != a2.a + ) { + glClearColor(a2.r, a2.g, a2.b, a2.a); + this->m_States.clear.clearColor = { a2.r, a2.g, a2.b, a2.a }; + } +} + +void GLDevice::SetClearDepth(double depth) { + if (this->m_States.clear.clearDepth != depth) { + glClearDepth(depth); + this->m_States.clear.clearDepth = depth; + } +} + +void GLDevice::SetClearStencil(int32_t a2) { + // TODO +} + +void GLDevice::SetColorWriteMask(bool red, bool green, bool blue, bool alpha, uint32_t index) { + if ( + this->m_States.blend.colorMask[index].red != red + || this->m_States.blend.colorMask[index].green != green + || this->m_States.blend.colorMask[index].blue != blue + || this->m_States.blend.colorMask[index].alpha != alpha + ) { + if (GLDevice::m_ExtColorMaskIndexed) { + glColorMaskIndexedEXT(index, red, green, blue, alpha); + } else if (index == 0) { + glColorMask(red, green, blue, alpha); + } + + this->m_States.blend.colorMask[index].red = red; + this->m_States.blend.colorMask[index].green = green; + this->m_States.blend.colorMask[index].blue = blue; + this->m_States.blend.colorMask[index].alpha = alpha; + } +} + +void GLDevice::SetCullMode(GLEnum cullMode) { + if (this->m_States.rasterizer.cullMode != cullMode) { + if (cullMode) { + if (cullMode - GL_CW <= 1) { + glEnable(GL_CULL_FACE); + glFrontFace(cullMode); + } + + this->m_States.rasterizer.cullMode = cullMode; + } else { + glDisable(GL_CULL_FACE); + this->m_States.rasterizer.cullMode = 0; + } + } +} + +void GLDevice::SetDepthBias(float constantBias, float slopeScaledBias) { + // TODO +} + +void GLDevice::SetDepthTestEnable(bool enable) { + if (this->m_States.depth.testEnable != enable) { + if (enable) { + glEnable(GL_DEPTH_TEST); + } else { + glDisable(GL_DEPTH_TEST); + } + + this->m_States.depth.testEnable = enable; + } +} + +void GLDevice::SetDepthTestFunc(GLEnum func) { + if (this->m_States.depth.compareFunc != func) { + glDepthFunc(func); + this->m_States.depth.compareFunc = func; + } +} + +void GLDevice::SetDepthWriteMask(bool enable) { + if (this->m_States.depth.writeMask != enable) { + glDepthMask(enable); + this->m_States.depth.writeMask = enable; + } +} + +void GLDevice::SetDisplay(uint32_t width, uint32_t height, GLTextureFormat a4, GLTextureFormat a5, uint32_t a6, bool a7, bool a8, uint32_t a9) { + uint32_t v9 = a9; + + if (this->m_PBOPool) { + // TODO + // this->m_PBOPool->Flush(1); + } + + if (a9 > 7) { + uint32_t v10; + + if (a7) { + CGDirectDisplayID mainDisplay = CGMainDisplayID(); + CGRect bounds = CGDisplayBounds(mainDisplay); + CGFloat boundsWidth = CGRectGetWidth(bounds); + CGFloat boundsHeight = CGRectGetHeight(bounds); + + v10 = std::floor(boundsWidth) * std::floor(boundsHeight); + } else { + v10 = width * height; + } + + if (v10 >= 2304001) { + v9 = 6; + } + } + + if (this->m_Context.m_Window) { + // TODO + // - callback related... set to default callbacks? + // (this->m_Context.m_Window + 84)(); + } + + if (a7) { + if (this->m_Context.m_Window) { + this->m_Context.m_Window->Resize(width, height); + } + + this->m_Context.SetWindow(this->m_Context.m_Window, 0); + } else { + this->m_Context.SetFullscreenMode(width, height, a6, a8); + } + + this->ResetBackbuffer(width, height, a4, a5, v9); + + if (this->m_Context.m_Window) { + // TODO + // - set active callbacks to callbacks + // (this->m_Context.m_Window + 88)(); + } +} + +void GLDevice::SetFogColor(float r, float g, float b, float a) { + if ( + this->m_States.fixedFunc.fogColor.r != r + || this->m_States.fixedFunc.fogColor.g != g + || this->m_States.fixedFunc.fogColor.b != b + || this->m_States.fixedFunc.fogColor.a != a + ) { + this->m_States.fixedFunc.fogColor.r = r; + this->m_States.fixedFunc.fogColor.g = g; + this->m_States.fixedFunc.fogColor.b = b; + this->m_States.fixedFunc.fogColor.a = a; + + glFogfv(GL_FOG_COLOR, reinterpret_cast(&this->m_States.fixedFunc.fogColor)); + + // TODO logic related to a renderer info value + } +} + +void GLDevice::SetFogEnable(bool enable) { + if (this->m_States.fixedFunc.fogEnable != enable) { + if (enable) { + glEnable(GL_FOG); + } else { + glDisable(GL_FOG); + } + + this->m_States.fixedFunc.fogEnable = enable; + } +} + +void GLDevice::SetFogParam(GLEnum param, float value) { + if (param == GL_FOG_START) { + if (this->m_States.fixedFunc.fogStart != value) { + glFogf(GL_FOG_START, value); + this->m_States.fixedFunc.fogStart = value; + } + } else if (param == GL_FOG_END) { + if (this->m_States.fixedFunc.fogEnd != value) { + glFogf(GL_FOG_END, value); + this->m_States.fixedFunc.fogEnd = value; + } + } else if (param == GL_FOG_DENSITY) { + if (this->m_States.fixedFunc.fogDensity != value) { + glFogf(GL_FOG_DENSITY, value); + this->m_States.fixedFunc.fogDensity = value; + } + } else { + BLIZZARD_ASSERT(false); + } +} + +void GLDevice::SetIndexBuffer(GLBuffer* buffer) { + BLIZZARD_ASSERT(buffer == nullptr || buffer->m_IndexFormat != GL_ZERO); + this->m_DefaultVertexArrayObject.m_Properties.m_IndexBuffer = buffer; +} + +void GLDevice::SetLightingEnable(bool enable) { + if (this->m_States.fixedFunc.lighting.enable != enable) { + if (enable) { + glEnable(GL_LIGHTING); + } else { + glDisable(GL_LIGHTING); + } + + this->m_States.fixedFunc.lighting.enable = enable; + } +} + +void GLDevice::SetScissor(bool a2, const GLRect& a3) { + // TODO +} + +void GLDevice::SetShader(GLShader::ShaderType shaderType, GLShader* shader) { + if (shader) { + if (shader->var18) { + // TODO + } + + BLIZZARD_ASSERT(shader->IsEnabled()); + BLIZZARD_ASSERT(shader->GetShaderType() == shaderType); + + this->BindShader(shader); + } + + int32_t enable = shader != nullptr; + + if (shaderType == GLShader::eVertexShader) { + if (this->m_States.shader.vertexShaderEnable != enable) { + if (enable) { + glEnable(GL_VERTEX_PROGRAM_ARB); + } else { + glDisable(GL_VERTEX_PROGRAM_ARB); + } + + this->m_States.shader.vertexShaderEnable = enable; + } + + this->m_VertexShader = shader; + } else if (shaderType == GLShader::ePixelShader) { + if (this->m_States.shader.pixelShaderEnable != enable) { + if (enable) { + glEnable(GL_FRAGMENT_PROGRAM_ARB); + } else { + glDisable(GL_FRAGMENT_PROGRAM_ARB); + } + + this->m_States.shader.pixelShaderEnable = enable; + } + + this->m_PixelShader = shader; + } else { + BLIZZARD_ASSERT(!"Unknown shader type!"); + } +} + +void GLDevice::SetShaderConstants(GLShader::ShaderType shaderType, uint32_t index, const float* constants, uint32_t count) { + BLIZZARD_ASSERT(count != 0); + + GLShader* shader = nullptr; + + if (shaderType == GLShader::eVertexShader) { + shader = this->m_VertexShader; + } else if (shaderType == GLShader::ePixelShader) { + shader = this->m_PixelShader; + } + + if (GLDevice::m_ShaderConstantBindings) { + if (!shader) { + return; + } + + if (shader && shader->var10) { + shader->SetShaderConstants(shaderType, index, constants, count); + return; + } + } + + this->SetShaderConstantsInternal(shaderType, index, constants, count); +} + +void GLDevice::SetShaderConstantsInternal(GLShader::ShaderType shaderType, uint32_t index, const float* constants, uint32_t count) { + if (shaderType == GLShader::eVertexShader) { + BLIZZARD_ASSERT((index + count) <= std::extentm_States.shader.vertexShaderConst)>::value); + + memcpy(&this->m_States.shader.vertexShaderConst[index], constants, (sizeof(float) * 4) * count); + + BLIZZARD_ASSERT(index <= 0xFFFF); + BLIZZARD_ASSERT(count <= 0xFFFF); + + uint16_t start = std::min(static_cast(index), this->m_DirtyVertexShaderConsts.start); + uint16_t end = std::max(static_cast(index + count), this->m_DirtyVertexShaderConsts.end); + + this->m_DirtyVertexShaderConsts.start = start; + this->m_DirtyVertexShaderConsts.end = end; + } else if (shaderType == GLShader::ePixelShader) { + BLIZZARD_ASSERT((index + count) <= std::extentm_States.shader.pixelShaderConst)>::value); + + memcpy(&this->m_States.shader.pixelShaderConst[index], constants, (sizeof(float) * 4) * count); + + BLIZZARD_ASSERT(index <= 0xFFFF); + BLIZZARD_ASSERT(count <= 0xFFFF); + + uint16_t start = std::min(static_cast(index), this->m_DirtyPixelShaderConsts.start); + uint16_t end = std::max(static_cast(index + count), this->m_DirtyPixelShaderConsts.end); + + this->m_DirtyPixelShaderConsts.start = start; + this->m_DirtyPixelShaderConsts.end = end; + } else { + // TODO + // BLIZZARD_ASSERT(false, "Unknown shader type %d!", shaderType); + } +} + +void GLDevice::SetTexture(uint32_t stage, GLTexture* texture) { + if (stage > 15) { + BLIZZARD_ASSERT(!"setting an unsupported texture stage to a non-NULL texture"); + } + + BLIZZARD_ASSERT(texture == nullptr || texture->IsValid()); + + uint32_t textureID = 0; + GLEnum textureType = GL_TEXTURE_2D; + + if (this->m_Textures[stage]) { + textureType = this->m_Textures[stage]->m_TextureType; + } + + if (texture) { + textureID = texture->m_TextureID; + textureType = texture->m_TextureType; + } + + uint32_t index = GLLTextureTypeToIndex(textureType); + + if (this->m_States.binding.texture[index][stage] != textureID) { + this->SetActiveTexture(stage); + + if (texture) { + texture->Bind(this, 1); + } else { + this->BindTexture(textureType, nullptr); + } + } + + this->m_Textures[stage] = texture; +} + +void GLDevice::SetTransform(GLEnum transform, const float* a3) { + GLTransform* t; + + if (transform == 'VIEW') { + t = &this->m_States.fixedFunc.transforms.view; + } else if (transform == 'WRLD') { + t = &this->m_States.fixedFunc.transforms.world; + } else if (transform == GL_PROJECTION) { + t = &this->m_States.fixedFunc.transforms.projection; + } else if (transform >= GL_TEXTURE0 && transform <= GL_TEXTURE7) { + t = &this->m_States.fixedFunc.transforms.texture[transform - GL_TEXTURE0]; + } else { + BLIZZARD_ASSERT(false); + } + + // TODO + // int32_t needsUpdate = !(t == a3); // GLTransform::operator==() + // if (needsUpdate) { + // t.Set(a3); + // } + + if (t->isDirty) { + if (transform == 'VIEW' || transform == 'WRLD') { + this->m_States.fixedFunc.transforms.modelView.isDirty = 1; + } + } +} + +void GLDevice::SetUnpackClientStorage(bool enable) { + // TODO + // Blizzard::Debug::Assert(!this->IsUsingPBO() || enable == GL_FALSE); + + if (this->m_States.misc.unpackClientStorage != enable) { + glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, enable); + this->m_States.misc.unpackClientStorage = enable; + } +} + +void GLDevice::SetVertexBuffer(uint32_t index, GLBuffer* buffer, uint32_t offset, uint32_t stride) { + BLIZZARD_ASSERT(index < GL_MAX_STREAM); + BLIZZARD_ASSERT(buffer == nullptr || buffer->m_IndexFormat == GL_ZERO); + + auto properties = &this->m_DefaultVertexArrayObject.m_Properties; + properties->m_VertexBuffer[index] = buffer; + properties->m_VertexBufferOffset[index] = offset; + properties->m_VertexBufferStride[index] = stride; +} + +void GLDevice::SetVertexFormat(GLVertexFormat* format) { + BLIZZARD_ASSERT(format->m_Size <= kMAX_VERTEX_ATTRIBS); + this->m_DefaultVertexArrayObject.m_Properties.m_VertexBufferFormat = format; +} + +void GLDevice::SetViewport(const GLRect& viewport, double zNear, double zFar) { + if ( + this->m_States.rasterizer.viewport.left != viewport.left + || this->m_States.rasterizer.viewport.top != viewport.top + || this->m_States.rasterizer.viewport.width != viewport.width + || this->m_States.rasterizer.viewport.height != viewport.height + ) { + glViewport(viewport.left, viewport.top, viewport.width, viewport.height); + this->m_States.rasterizer.viewport = viewport; + } + + if ( + this->m_States.rasterizer.zNear != zNear + || this->m_States.rasterizer.zFar != zFar + ) { + glDepthRange(zNear, zFar); + this->m_States.rasterizer.zNear = zNear; + this->m_States.rasterizer.zFar = zFar; + } +} + +void GLDevice::Sub34BB0(GLEnum a2, GLMipmap* a3, uint32_t index) { + if (!a3 || !this->m_UseWindowSystemBuffer) { + return; + } + + GLFramebuffer* target; + + if (a3->GetTexture() == this->m_BackBufferColor || a3->GetTexture() == this->m_BackBufferDepth || a3->GetTexture() == this->m_BackBufferStencil) { + if (this->m_CurrentTarget == this->m_SystemTarget) { + return; + } + + switch (a2) { + case GL_DEPTH_ATTACHMENT: + this->Sub34BB0(GL_DEPTH_ATTACHMENT, nullptr, 0); + this->m_CurrentTarget->Attach(nullptr, GL_DEPTH_ATTACHMENT, 0); + this->m_CurrentTargetDepth = nullptr; + this->m_CurrentDepthBuffer = nullptr; + break; + + case GL_STENCIL_ATTACHMENT: + this->Sub34BB0(GL_STENCIL_ATTACHMENT, nullptr, 0); + this->m_CurrentTarget->Attach(nullptr, GL_STENCIL_ATTACHMENT, 0); + this->m_CurrentTargetStencil = nullptr; + break; + + case GL_COLOR_ATTACHMENT0: + // TODO this->SetColorTarget(0, index); + break; + } + + target = this->m_SystemTarget; + } else { + target = this->m_FBOTarget; + } + + this->BindFramebuffer(target); +} + +void GLDevice::Sub38460(bool a2) { + if (this->m_States.binding.framebuffer == 0) { + glDrawBuffer(GL_BACK); + this->m_States.misc.drawBuffers[0] = GL_BACK; + + glReadBuffer(GL_BACK); + this->m_States.misc.readBuffer = GL_BACK; + + return; + } + + if (a2) { + // TODO + + return; + } + + // TODO +} + +void GLDevice::Swap() { + if (this->m_Context.m_Window) { + if (this->m_FlippedSystemBuffer) { + GLRect rect = { + 0, + 0, + static_cast(this->m_BackBufferColor->m_Width), + static_cast(this->m_BackBufferColor->m_Height) + }; + + GLMipmap* image = this->m_BackBufferColor->GetMipmap(0, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + this->CopyTex(0, 0, image, &rect); + } + + this->DrawRect(); + + this->m_FrameNumber++; + this->m_DrawCount = 0; + } else { + glFlushRenderAPPLE(); + this->m_DrawCount = 0; + } +} + +void GLDevice::UpdateFFPTexturing() { + // TODO +} diff --git a/src/gx/gll/GLDevice.h b/src/gx/gll/GLDevice.h new file mode 100644 index 0000000..bdd56a5 --- /dev/null +++ b/src/gx/gll/GLDevice.h @@ -0,0 +1,191 @@ +#ifndef GX_GLL_GL_DEVICE_H +#define GX_GLL_GL_DEVICE_H + +#include "gx/gll/GL.h" +#include "gx/gll/GLAbstractWindow.h" +#include "gx/gll/GLBatch.h" +#include "gx/gll/GLBufferPool.h" +#include "gx/gll/GLContext.h" +#include "gx/gll/GLDebugMipmap2D.h" +#include "gx/gll/GLFramebuffer.h" +#include "gx/gll/GLGLSLProgram.h" +#include "gx/gll/GLMipmap.h" +#include "gx/gll/GLShader.h" +#include "gx/gll/GLTexture.h" +#include "gx/gll/GLTypes.h" +#include "gx/gll/GLVertexArray.h" +#include "gx/gll/GLWorker.h" +#include "util/BlizzardCore.hpp" +#include +#include +#include +#include + +class GLDevice { + public: + // Types + enum GLDeviceOption { + eUseMTGL = 0, + eUseVertexArray = 1, + eUseGLSL = 2, + eCheckGLStates = 3, + eFlushBeforeDraw = 4, + eDeviceOption5 = 5, + eUseHybridShader = 6, + eDeviceOption7 = 7, + eDeviceOption8 = 8, + eShaderConstantBindings = 9 + }; + + struct RendererInfo { + uint8_t init = 0; + uint32_t vendor_id; + uint32_t renderer_id; + uint32_t max_color_attachments; + uint32_t unk36; // max clip planes + uint32_t unk100; + }; + + // Static variables + static Blizzard::Thread::TLSSlot m_CurrentDevice; + static std::vector> m_Devices; + static bool m_ExtARBShadow; + static bool m_ExtColorMaskIndexed; + static RendererInfo m_RendererInfo; + static bool m_ShaderConstantBindings; + static int32_t m_StaticResourcesRefCount; + static bool m_UseHybridShader; + static GLBuffer* m_BlitQuadVBO; + static GLShader* m_DeviceShaders[]; + static GLTexture* m_DeviceTextures[]; + static GLVertexFormat m_NormalBlitVF; + static GLVertexFormat m_InvertedBlitVF; + static GLFramebuffer* m_F8330C; + + // Static functions + static GLDevice* Get(void); + static void Set(GLDevice*); + static void InitPools(void); + static RendererInfo GetRendererInfo(void); + static void InitRendererInfo(void); + static void SetOption(GLDeviceOption, bool); + static void StaticInit(void); + + // Member variables + std::basic_string, std::allocator> m_DebugName; + GLStates m_States; + GLTexture* m_Textures[16] = {}; + GLShader* m_PixelShader = nullptr; + GLShader* m_VertexShader = nullptr; + GLGLSLProgram* m_GLSLProgram = nullptr; + GLVertexArray* m_VertexArrayObject = &m_DefaultVertexArrayObject; + GLFramebuffer* m_SystemTarget = nullptr; + GLFramebuffer* m_FBOTarget = nullptr; + GLFramebuffer* m_CurrentTarget = nullptr; + GLMipmap* m_CurrentTargetColor[4] = {}; + GLMipmap* m_CurrentTargetDepth = nullptr; + GLMipmap* m_CurrentTargetStencil = nullptr; + GLMipmap* m_CurrentDepthBuffer = nullptr; + GLTexture2D* m_BackBufferColor = nullptr; + GLTexture2D* m_BackBufferDepth = nullptr; + GLTexture2D* m_BackBufferStencil = nullptr; + GLContext m_Context; + GLBufferPool* m_PBOPool = nullptr; + GLWorker* m_TexWorker = nullptr; + GLTexture* m_BoundTextures[4][16] = {}; + GLVertexArray m_DefaultVertexArrayObject; + GLDirtyRange m_DirtyVertexShaderConsts; + GLDirtyRange m_DirtyPixelShaderConsts; + float m_ConstantDepthBias = 0.0f; + float m_SlopeScaledDepthBias = 0.0f; + bool m_Init = 0; + uint32_t m_DrawCount = 0; + uint32_t m_ID = -1; + std::list> m_TextureList; + std::list::iterator m_OldestActiveTexture; + uint32_t m_TextureTotalSize = 0; + uint32_t m_FrameNumber = 1; + std::list> m_DebugMipmaps; + bool m_BatchViewerEnabled = 0; + bool m_UseWindowSystemBuffer = 0; + bool m_FlippedSystemBuffer = 0; + bool m_ShaderCompiler = 0; + bool m_WorkerDevice; + GLStates m_DefaultStates; + std::vector>* m_FrameBatches; + bool m_CaptureOnlyOneFrame = 0; + bool m_StopCapturingBatches = 0; + bool m_CaptureBatches = 0; + int32_t m_IndentLevel = 0; + GLAbstractWindow* m_FirstCapturedWindow = nullptr; + + // Member functions + GLDevice(); + void ApplyGLBindings(const GLStates&, bool); + void ApplyGLStates(const GLStates&, bool); + void ApplyShaderConstants(void); + void ApplyTransforms(void); + void BindBuffer(GLBuffer*, GLEnum); + void BindFramebuffer(GLFramebuffer*); + void BindGLSLProgram(GLGLSLProgram*); + void BindShader(GLShader*); + void BindTexture(GLEnum, GLTexture*); + void BindVertexArray(GLVertexArray*); + void BlitFramebuffer(GLMipmap*, const GLRect*, GLMipmap*, const GLRect*, GLEnum, GLEnum); + void CheckDepthTarget(void); + void Clear(uint32_t, const GLColor4f&, double, int32_t); + void CopyTex(uint32_t, uint32_t, GLMipmap*, const GLRect*); + GLBuffer* CreateBuffer(GLEnum, uint32_t, const void*, GLEnum, GLEnum); + GLShader* CreateShader(GLShader::ShaderType, const void*, int32_t, const char*); + GLTexture* CreateTexture2D(uint32_t, uint32_t, uint32_t, GLTextureFormat, uint32_t); + GLTexture* CreateTextureCubeMap(uint32_t, uint32_t, GLTextureFormat, uint32_t); + void Draw(GLEnum, uint32_t, uint32_t); + void DrawIndexed(GLEnum, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + void DrawRect(void); + GLFramebuffer* GetCurrentTarget(void); // invented name + uint32_t GetID(void); + GLShader* GetShader(GLShader::ShaderType); + const GLStates::VertexArrayObject& GetVertexArrayStates(); + void GLLDraw(GLEnum, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + void Init(GLAbstractWindow*, const char*, uint32_t, GLTextureFormat); + void LoadDefaultStates(void); + void ResetBackbuffer(uint32_t width, uint32_t height, GLTextureFormat colorFormat, GLTextureFormat depthFormat, uint32_t sampleCount); + void Resize(uint32_t width, uint32_t height); + void RestoreTextures(void); + void SetActiveTexture(uint32_t); + void SetAlphaBlend(GLEnum, GLEnum, GLEnum); + void SetAlphaBlendEnable(bool); + void SetAlphaTest(GLEnum, float); + void SetAlphaTestEnable(bool); + void SetClearColor(const GLColor4f&); + void SetClearDepth(double); + void SetClearStencil(int32_t); + void SetColorWriteMask(bool red, bool green, bool blue, bool alpha, uint32_t index); + void SetCullMode(GLEnum); + void SetDepthBias(float constantBias, float slopeScaledBias); + void SetDepthTestEnable(bool); + void SetDepthTestFunc(GLEnum); + void SetDepthWriteMask(bool); + void SetDisplay(uint32_t, uint32_t, GLTextureFormat, GLTextureFormat, uint32_t, bool, bool, uint32_t); + void SetFogColor(float r, float g, float b, float a); + void SetFogEnable(bool enable); + void SetFogParam(GLEnum param, float value); + void SetIndexBuffer(GLBuffer*); + void SetLightingEnable(bool enable); + void SetScissor(bool, const GLRect&); + void SetShader(GLShader::ShaderType, GLShader*); + void SetShaderConstants(GLShader::ShaderType, uint32_t, const float*, uint32_t); + void SetShaderConstantsInternal(GLShader::ShaderType, uint32_t, const float*, uint32_t); + void SetTexture(uint32_t, GLTexture*); + void SetTransform(GLEnum, const float*); + void SetUnpackClientStorage(bool); + void SetVertexBuffer(uint32_t, GLBuffer*, uint32_t, uint32_t); + void SetVertexFormat(GLVertexFormat*); + void SetViewport(const GLRect&, double, double); + void Sub34BB0(GLEnum a2, GLMipmap* a3, uint32_t index); + void Sub38460(bool); + void Swap(void); + void UpdateFFPTexturing(void); +}; + +#endif diff --git a/src/gx/gll/GLFramebuffer.cpp b/src/gx/gll/GLFramebuffer.cpp new file mode 100644 index 0000000..b578dc4 --- /dev/null +++ b/src/gx/gll/GLFramebuffer.cpp @@ -0,0 +1,149 @@ +#include "gx/gll/GLFramebuffer.h" +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLMipmap.h" +#include "gx/gll/GLPool.h" +#include "gx/gll/GLTypes.h" + +GLFramebuffer* GLFramebuffer::Create(bool a1) { + GLFramebuffer* framebuffer = new GLFramebuffer(a1); + + if (!a1) { + // TODO + // BLIZZARD_ASSERT(framebuffer->m_FramebufferID >= PoolStats::NAME_POOL_FIRST_NAME); + } + + BLIZZARD_ASSERT(framebuffer->m_NumAttach == 0); + + framebuffer->m_Width = 0; + framebuffer->m_Height = 0; + framebuffer->m_Device = GLDevice::Get(); + + return framebuffer; +} + +GLFramebuffer::GLFramebuffer(bool a1) : GLObject() { + if (!a1) { + this->m_FramebufferID = GLPool::Get()->GetNextName(); + } +} + +void GLFramebuffer::Attach(GLMipmap* image, GLenum a3, int32_t a4) { + BLIZZARD_ASSERT(this->m_Device == GLDevice::Get()); + + if (!image) { + this->Detach(a3); + return; + } + + if (a3 == GL_DEPTH_STENCIL) { + BLIZZARD_ASSERT(image->GetFormat() == GLTF_D24S8); + + this->Attach(image, GL_DEPTH_ATTACHMENT, 0); + this->Attach(image, GL_STENCIL_ATTACHMENT, 0); + + (*image->m_AttachPoints)[this->m_FramebufferID].point = GL_DEPTH_STENCIL; + + return; + } + + int32_t index; + + if (a3 == GL_DEPTH_ATTACHMENT) { + index = 4; + } else if (a3 == GL_STENCIL_ATTACHMENT) { + index = 5; + } else { + index = a3 - GL_COLOR_ATTACHMENT0; + } + + BLIZZARD_ASSERT(index < MAX_ATTACHMENT); + + GLMipmap* oldImage = this->m_Attachments[index]; + + if (image != oldImage) { + if (oldImage) { + oldImage->Detach(this, a3, true); + } else { + ++this->m_NumAttach; + } + + this->m_Attachments[index] = image; + + this->m_Width = image->m_Width; + this->m_Height = image->m_Height; + + image->Attach(this, a3, a4); + this->m_Device->Sub38460(0); + } + + BLIZZARD_ASSERT((*image->m_AttachPoints)[m_FramebufferID].framebuffer == this); +} + +void GLFramebuffer::Detach(GLenum a2) { + int32_t v2 = a2; + int32_t index; + + if (a2 == GL_DEPTH_STENCIL) { + index = 5; + v2 = GL_STENCIL_ATTACHMENT; + this->Detach(GL_DEPTH_ATTACHMENT); + } else if (a2 == GL_DEPTH_ATTACHMENT) { + index = 4; + } else if (a2 == GL_STENCIL_ATTACHMENT) { + index = 5; + } else { + index = a2 - GL_COLOR_ATTACHMENT0; + } + + BLIZZARD_ASSERT(index < MAX_ATTACHMENT); + + GLMipmap* oldImage = this->m_Attachments[index]; + + if (oldImage) { + oldImage->Detach(this, v2, 0); + + --this->m_NumAttach; + + this->m_Attachments[index] = 0; + + if (this->m_Device == GLDevice::Get()) { + this->m_Device->Sub38460(0); + } + + if (this->m_NumAttach == 0) { + this->m_Width = 0; + this->m_Height = 0; + } + } +} + +GLMipmap* GLFramebuffer::GetAttachment(GLEnum a2) { + int32_t index; + + if (a2 == GL_DEPTH_ATTACHMENT) { + index = 4; + } else if (a2 == GL_STENCIL_ATTACHMENT) { + index = 5; + } else { + index = a2 - GL_COLOR_ATTACHMENT0; + } + + BLIZZARD_ASSERT(index < MAX_ATTACHMENT); + + return this->m_Attachments[index]; +} + +int32_t GLFramebuffer::GetSampleCount() { + return this->m_FramebufferID + ? 1 + : this->m_Device->m_Context.m_Context->sampleCount; +} + +bool GLFramebuffer::IsValid() { + auto status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER); + return status == GL_FRAMEBUFFER_COMPLETE; +} + +void GLFramebuffer::ReleaseObject() { + // TODO +} diff --git a/src/gx/gll/GLFramebuffer.h b/src/gx/gll/GLFramebuffer.h new file mode 100644 index 0000000..7cbacdb --- /dev/null +++ b/src/gx/gll/GLFramebuffer.h @@ -0,0 +1,38 @@ +#ifndef GX_GLL_GL_FRAMEBUFFER_H +#define GX_GLL_GL_FRAMEBUFFER_H + +#include "gx/gll/GL.h" +#include "gx/gll/GLObject.h" +#include + +#define MAX_ATTACHMENT 6 + +class GLDevice; +class GLMipmap; + +class GLFramebuffer : public GLObject { + public: + // Static functions + static GLFramebuffer* Create(bool); + + // Member variables + int32_t m_Width = 0; + int32_t m_Height = 0; + uint32_t m_FramebufferID = 0; + GLDevice* m_Device; + GLMipmap* m_Attachments[6] = {}; + uint32_t m_NumAttach = 0; + + // Virtual member functions + virtual void ReleaseObject(); + + // Member functions + GLFramebuffer(bool); + void Attach(GLMipmap*, GLenum, int32_t); + void Detach(GLenum); + GLMipmap* GetAttachment(GLEnum); + int32_t GetSampleCount(void); + bool IsValid(); +}; + +#endif diff --git a/src/gx/gll/GLGLSLProgram.cpp b/src/gx/gll/GLGLSLProgram.cpp new file mode 100644 index 0000000..073cef0 --- /dev/null +++ b/src/gx/gll/GLGLSLProgram.cpp @@ -0,0 +1,7 @@ +#include "gx/gll/GLGLSLProgram.h" +#include "gx/gll/GLShader.h" + +GLGLSLProgram* GLGLSLProgram::Find(GLShader* a1, GLShader* a2) { + // TODO + return nullptr; +} diff --git a/src/gx/gll/GLGLSLProgram.h b/src/gx/gll/GLGLSLProgram.h new file mode 100644 index 0000000..4f1b8ad --- /dev/null +++ b/src/gx/gll/GLGLSLProgram.h @@ -0,0 +1,14 @@ +#ifndef GX_GLL_GL_GLSL_PROGRAM_H +#define GX_GLL_GL_GLSL_PROGRAM_H + +#include "gx/gll/GLObject.h" + +class GLShader; + +class GLGLSLProgram : public GLObject { + public: + // Static functions + static GLGLSLProgram* Find(GLShader*, GLShader*); +}; + +#endif diff --git a/src/gx/gll/GLLayerView.h b/src/gx/gll/GLLayerView.h new file mode 100644 index 0000000..1b65571 --- /dev/null +++ b/src/gx/gll/GLLayerView.h @@ -0,0 +1,39 @@ +#ifndef GX_GLL_GL_LAYER_VIEW_H +#define GX_GLL_GL_LAYER_VIEW_H + +#include +#include +#include + +class GLWindow; + +@interface GLLayerView : NSView + +@property CGDirectDisplayID m_display; +@property GLWindow* m_GLWindow; +@property (retain) NSOpenGLContext* m_savedContext; +@property (retain) NSCursor* m_cursor; + +- (BOOL)acceptsFirstResponder; +- (void)drawRect:(NSRect)dirtyRect; +- (id)initWithFrame:(NSRect)frame glWindow:(GLWindow*)window; +- (void)keyDown:(NSEvent*)event; +- (void)keyUp:(NSEvent*)event; +- (void)mouseDown:(NSEvent*)event; +- (void)mouseDragged:(NSEvent*)event; +- (void)mouseMoved:(NSEvent*)event; +- (void)mouseUp:(NSEvent*)event; +- (void)otherMouseDown:(NSEvent*)event; +- (void)otherMouseDragged:(NSEvent*)event; +- (void)otherMouseUp:(NSEvent*)event; +- (void)rightMouseDown:(NSEvent*)event; +- (void)rightMouseDragged:(NSEvent*)event; +- (void)rightMouseUp:(NSEvent*)event; +- (void)scrollWheel:(NSEvent*)event; +- (void)viewDidChangeBackingProperties; +- (void)viewDidEndLiveResize; +- (void)update; + +@end + +#endif diff --git a/src/gx/gll/GLLayerView.mm b/src/gx/gll/GLLayerView.mm new file mode 100644 index 0000000..d8db5dd --- /dev/null +++ b/src/gx/gll/GLLayerView.mm @@ -0,0 +1,179 @@ +#include "gx/gll/GLLayerView.h" +#include "gx/gll/GLContext.h" +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLWindow.h" +#include + +@implementation GLLayerView + +- (BOOL)acceptsFirstResponder { + return YES; +} + +- (GLWindowCallbacks*)callbacks { + if (self.m_GLWindow) { + return self.m_GLWindow->m_ActiveCallbacks; + } else { + // TODO + } +} + +- (void)drawRect:(NSRect)dirtyRect { + GLContext* context = self.m_GLWindow->m_Context; + + if (context && context->m_Device) { + context->m_Device->DrawRect(); + } +} + +- (id)initWithFrame:(NSRect)frame glWindow:(GLWindow*)window { + self = [super initWithFrame:frame]; + [self setWantsBestResolutionOpenGLSurface:YES]; + + if (self) { + [self setGLWindow:window]; + } + + CGSetLocalEventsSuppressionInterval(0.0); + + self.m_display = [self findMyDisplay]; + + return self; +} + +- (void)setGLWindow:(GLWindow*)window { + self.m_GLWindow = window; +} + +- (CGDirectDisplayID)findMyDisplay { + NSRect frame = self.frame; + + uint32_t displayCount; + CGDirectDisplayID displays[1]; + + CGGetDisplaysWithPoint(frame.origin, 1, displays, &displayCount); + + if (displayCount) { + return displays[0]; + } else { + return CGMainDisplayID(); + } +} + +- (void)keyDown:(NSEvent*)event { + [self callbacks]->OnKeyDown(event); +} + +- (void)keyUp:(NSEvent*)event { + [self callbacks]->OnKeyUp(event); +} + +- (void)mouseDown:(NSEvent*)event { + NSPoint location = event.locationInWindow; + NSRect frame = self.frame; + + float x = location.x; + float y = frame.size.height - location.y; + + NSInteger buttonNumber = event.buttonNumber; + + [self callbacks]->OnMouseDown( + buttonNumber, + static_cast(floor(x)), + static_cast(floor(y)) + ); +} + +- (void)mouseDragged:(NSEvent*)event { + [self mouseMoved:event]; +} + +- (void)mouseMoved:(NSEvent*)event { + float v20 = 0.0; + float v21 = 0.0; + + // TODO + // - logic to handle fullscreen mouse movement + + NSPoint location = event.locationInWindow; + NSRect frame = self.frame; + + float x = location.x + v21; + float y = (frame.size.height - location.y) + v20; + + [self callbacks]->OnMouseMoved( + static_cast(floor(x)), + static_cast(floor(y)) + ); +} + +- (void)mouseUp:(NSEvent*)event { + NSPoint location = event.locationInWindow; + NSRect frame = self.frame; + + float x = location.x; + float y = frame.size.height - location.y; + + NSInteger buttonNumber = event.buttonNumber; + + [self callbacks]->OnMouseUp( + buttonNumber, + static_cast(floor(x)), + static_cast(floor(y)) + ); +} + +- (void)otherMouseDown:(NSEvent*)event { + [self mouseDown:event]; +} + +- (void)otherMouseDragged:(NSEvent*)event { + [self mouseMoved:event]; +} + +- (void)otherMouseUp:(NSEvent*)event { + [self mouseUp:event]; +} + +- (void)rightMouseDown:(NSEvent*)event { + [self mouseDown:event]; +} + +- (void)rightMouseDragged:(NSEvent*)event { + [self mouseMoved:event]; +} + +- (void)rightMouseUp:(NSEvent*)event { + [self mouseUp:event]; +} + +- (void)scrollWheel:(NSEvent*)event { + // TODO +} + +- (void)viewDidChangeBackingProperties { + [self update]; +} + +- (void)viewDidEndLiveResize { + int32_t width = std::floor(self.frame.size.width); + int32_t height = std::floor(self.frame.size.height); + + [self update]; + + [self callbacks]->OnResized( + width, + height, + false + ); +} + +- (void)update { + self.m_display = [self findMyDisplay]; + + if (self.m_GLWindow->m_Context) { + self.m_GLWindow->m_Context->Update(); + } +} + +@end diff --git a/src/gx/gll/GLMipmap.cpp b/src/gx/gll/GLMipmap.cpp new file mode 100644 index 0000000..7758a26 --- /dev/null +++ b/src/gx/gll/GLMipmap.cpp @@ -0,0 +1,492 @@ +#include "gx/gll/GLMipmap.h" +#include "gx/gll/GLCommand.h" +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLFramebuffer.h" + +int32_t GLMipmap::GetDepthBits() { + // TODO + return 0; +} + +void GLMipmap::Attach(GLFramebuffer* framebuffer, GLenum attachPoint, int32_t a4) { + if (!this->m_AttachPoints) { + this->m_AttachPoints = new std::vector(); + } + + auto& attachPoints = *this->m_AttachPoints; + auto framebufferID = framebuffer->m_FramebufferID; + + if (framebufferID >= attachPoints.size()) { + attachPoints.resize(framebufferID + 1); + } else { + BLIZZARD_ASSERT(attachPoints[framebufferID].framebuffer != framebuffer || attachPoints[framebufferID].point != attachPoint); + + auto& attach = attachPoints[framebufferID]; + + if ( + attach.point + && (attach.point != GL_DEPTH_ATTACHMENT || attachPoint != GL_STENCIL_ATTACHMENT) + && (attach.point != GL_STENCIL_ATTACHMENT || attachPoint != GL_DEPTH_ATTACHMENT) + ) { + framebuffer->Detach(attach.point); + } + } + + GLDevice* device = GLDevice::Get(); + + auto currentTarget = device->GetCurrentTarget(); + device->BindFramebuffer(framebuffer); + + if (framebufferID) { + if (this->m_Target == GL_TEXTURE_3D) { + glFramebufferTexture3DEXT( + GL_FRAMEBUFFER, + attachPoint, + GL_TEXTURE_3D, + this->m_Texture->m_TextureID, + this->m_Level, + a4 + ); + } else { + glFramebufferTexture2DEXT( + GL_FRAMEBUFFER, + attachPoint, + this->m_Target, + this->m_Texture->m_TextureID, + this->m_Level + ); + } + } + + if (attachPoint == GL_DEPTH_ATTACHMENT && !this->m_DepthBits) { + GLint depthBits = 0; + glGetIntegerv(GL_DEPTH_BITS, &depthBits); + this->m_DepthBits = depthBits; + } + + device->BindFramebuffer(currentTarget); + + auto& attach = attachPoints[framebufferID]; + attach.framebuffer = framebuffer; + attach.zOffset = a4; + + if ( + (attach.point != GL_DEPTH_ATTACHMENT || attachPoint != GL_STENCIL_ATTACHMENT) + && (attach.point != GL_STENCIL_ATTACHMENT || attachPoint != GL_DEPTH_ATTACHMENT) + ) { + attach.point = attachPoint; + } else { + attach.point = GL_DEPTH_STENCIL; + } +} + +void GLMipmap::Detach(GLFramebuffer* framebuffer, GLenum attachPoint, bool a4) { + GLuint framebufferID = framebuffer->m_FramebufferID; + + auto& attachPoints = *this->m_AttachPoints; + + BLIZZARD_ASSERT(attachPoints.size() >= framebufferID); + BLIZZARD_ASSERT(attachPoints[framebufferID].framebuffer == framebuffer); + + if (!a4 && framebufferID) { + GLDevice* v12 = GLDevice::Get(); + GLFramebuffer* v14 = v12->GetCurrentTarget(); + v12->BindFramebuffer(framebuffer); + + if (this->m_Target == GL_TEXTURE_3D) { + glFramebufferTexture3DEXT(GL_FRAMEBUFFER, attachPoint, GL_TEXTURE_3D, 0, 0, 0); + } else { + glFramebufferTexture2DEXT(GL_FRAMEBUFFER, attachPoint, this->m_Target, 0, 0); + } + + v12->BindFramebuffer(v14); + } + + GLAttachPoint* v9 = &attachPoints[framebufferID]; + + if (v9->point == GL_DEPTH_STENCIL) { + BLIZZARD_ASSERT(this->GetFormat() == GLTF_D24S8); + + if (attachPoint == GL_DEPTH_ATTACHMENT) { + v9->point = GL_STENCIL_ATTACHMENT; + } else if (attachPoint == GL_STENCIL_ATTACHMENT) { + v9->point = GL_DEPTH_ATTACHMENT; + } else { + BLIZZARD_ASSERT(false); + } + } else { + BLIZZARD_ASSERT(attachPoints[framebufferID].point == attachPoint); + + v9->framebuffer = 0; + v9->point = 0; + v9->zOffset = 0; + + // TODO + // this->m_Texture->m_TimeStamp = Blizzard::Time::GetTimestamp(); + } +} + +void GLMipmap::DetachAll() { + if (!this->m_AttachPoints) { + return; + } + + auto& attachPoints = *this->m_AttachPoints; + for (int32_t i = 0; i < attachPoints.size(); i++) { + BLIZZARD_ASSERT(attachPoints[i].point != GL_ZERO); + BLIZZARD_ASSERT(attachPoints[i].framebuffer->m_FramebufferID == i); + + attachPoints[i].framebuffer->Detach(attachPoints[i].point); + } +} + +GLTextureFormat GLMipmap::GetFormat() { + return this->m_Texture->GetFormat(); +} + +TextureFormatInfo& GLMipmap::GetFormatInfo() { + return this->m_Texture->GetFormatInfo(); +}; + +uint16_t GLMipmap::GetHeight() { + return this->m_Height; +} + +int32_t GLMipmap::GetPitch() { + int32_t bpp = this->GetFormatInfo().m_BytePerPixel; + int32_t v4 = this->m_Texture->var12 >> this->m_Level; + return v4 >= bpp ? v4 : bpp; +} + +GLTexture* GLMipmap::GetTexture() { + return this->m_Texture; +} + +uint32_t GLMipmap::GetTextureID() { + return this->m_Texture->m_TextureID; +} + +uint16_t GLMipmap::GetWidth() { + return this->m_Width; +} + +void* GLMipmap::Map(GLEnum mode, const GLBox* area) { + BLIZZARD_ASSERT(!this->m_Texture->IsSystemBuffer()); + BLIZZARD_ASSERT(this->m_Data != nullptr); + BLIZZARD_ASSERT(!this->m_Texture->IsRenderTarget()); + BLIZZARD_ASSERT(mode != GL_ZERO); + BLIZZARD_ASSERT(this->m_MapParams == nullptr); + + if (mode != GL_READ_ONLY) { + this->m_Texture->m_MappedMipmaps++; + } + + MapParams* mapParams = new MapParams(); + this->m_MapParams = mapParams; + + if (area) { + BLIZZARD_ASSERT(area->width > 0); + BLIZZARD_ASSERT(area->height > 0); + BLIZZARD_ASSERT(area->depth > 0); + BLIZZARD_ASSERT(!this->GetFormatInfo().m_IsCompressed || ((area->top & 0x3) == 0 && (area->left & 0x3) == 0 && (area->width & 0x3) == 0 && (area->height & 0x3) == 0)); + BLIZZARD_ASSERT((area->height + area->top) <= this->m_Height); + BLIZZARD_ASSERT((area->depth + area->front) <= this->m_Depth); + BLIZZARD_ASSERT((area->width + area->left) <= this->m_Width); + + mapParams->m_MapArea = { + area->left, + area->top, + area->front, + area->width, + area->height, + area->depth + }; + + int32_t size = this->GetFormatInfo().m_BytePerPixel + * this->m_Width + * mapParams->m_MapArea.depth + * mapParams->m_MapArea.height; + + mapParams->m_Size = this->GetFormatInfo().m_IsCompressed + ? size >> 4 + : size; + + mapParams->m_Unk7 = (this->GetFormatInfo().m_BytePerPixel * mapParams->m_MapArea.left) + >> this->GetFormatInfo().m_IsCompressed ? 2 : 0; + } else { + mapParams->m_MapArea = { + 0, + 0, + 0, + this->m_Width, + this->m_Height, + this->m_Depth + }; + + mapParams->m_Size = this->m_Size; + + mapParams->m_Unk7 = 0; + } + + mapParams->m_MapMode = mode; + + int32_t rowPitch = this->GetPitch(); + + BLIZZARD_ASSERT(((mapParams->m_MapArea.top * rowPitch) + mapParams->m_MapArea.left * this->GetFormatInfo().m_BytePerPixel) < (this->GetFormatInfo().m_IsCompressed ? this->m_Size << 4 : this->m_Size)); + + int32_t v22 = rowPitch * this->m_Height; + if (this->GetFormatInfo().m_IsCompressed) { + v22 >>= 2; + } + + unsigned char* v24 = this->m_Data + + ((mapParams->m_MapArea.top * rowPitch) >> (this->GetFormatInfo().m_IsCompressed ? 2 : 0)) + + (mapParams->m_MapArea.front * v22); + + mapParams->m_Unk8 = v24; + + return v24 + mapParams->m_Unk7; +} + +void* GLMipmap::Map(GLEnum mode, const GLRect* rect) { + if (rect) { + GLBox area = { + rect->left, + rect->top, + 0, + rect->width, + rect->height, + 1 + }; + + return this->Map(mode, &area); + } else { + return this->Map(mode, static_cast(nullptr)); + } +} + +void GLMipmap::ReleaseObject() { + BLIZZARD_ASSERT(this->m_MapParams == nullptr); + + this->RemoveDebugMipmap(); + this->DetachAll(); + + if (this->m_AttachPoints) { + delete this->m_AttachPoints; + } + + this->m_AttachPoints = nullptr; + this->m_Unk24 = 0; +} + +void GLMipmap::RemoveDebugMipmap() { + // TODO +} + +void GLMipmap::ResetData(GLEnum target, int32_t level, unsigned char* data) { + BLIZZARD_ASSERT(this->m_Target != GL_TEXTURE_3D || !this->GetFormatInfo().m_IsCompressed); + + this->m_Target = target; + this->m_Level = level; + this->m_Data = data; + + BLIZZARD_ASSERT(this->GetFormat() != GLTF_INVALID); + BLIZZARD_ASSERT(this->GetFormat() < GLTF_NUM_TEXTURE_FORMATS); + + if (!this->m_Texture->IsSystemBuffer() && this->m_Texture->IsRenderTarget()) { + BLIZZARD_ASSERT(!this->GetFormatInfo().m_IsCompressed); + + this->TexImage(nullptr); + this->m_Unk24 = 1; + } +} + +void GLMipmap::ResetSize(uint32_t width, uint32_t height, uint32_t depth) { + this->m_Width = width ? width : 1; + this->m_Height = height ? height : 1; + this->m_Depth = depth ? depth : 1; + + if (this->GetFormatInfo().m_IsCompressed) { + BLIZZARD_ASSERT(this->m_Depth == 1); + + this->m_Width = (this->m_Width + 3) & 0xFFFC; + this->m_Height = (this->m_Height + 3) & 0xFFFC; + } + + uint32_t v11 = this->GetFormatInfo().m_BytePerPixel; + uint32_t v20 = this->m_Texture->var12 >> this->m_Level; + + if (v20 >= v11) { + v11 = v20; + } + + uint32_t v15 = v11 * this->m_Height; + uint32_t v12; + + if (this->GetFormatInfo().m_IsCompressed) { + v12 = v15 >> 2; + } else { + v12 = v15; + } + + this->m_Size = this->m_Depth * v12; +} + +void GLMipmap::TexImage(const void* pixels) { + BLIZZARD_ASSERT((this->m_Texture->IsRenderTarget() || pixels != nullptr) && GLDevice::Get()->GetVertexArrayStates().buffers[eGLBT_PIXEL_UNPACK] == 0); + + if (this->m_Target == GL_TEXTURE_3D) { + glTexImage3D( + GL_TEXTURE_3D, + this->m_Level, + this->GetFormatInfo().m_InternalFormat, + this->m_Width, + this->m_Height, + this->m_Depth, + 0, + this->GetFormatInfo().m_DataFormat, + this->GetFormatInfo().m_DataType, + pixels + ); + } else if (this->GetFormatInfo().m_IsCompressed) { + glCompressedTexImage2D( + this->m_Target, + this->m_Level, + this->GetFormatInfo().m_InternalFormat, + this->m_Width, + this->m_Height, + 0, + this->m_Size, + pixels + ); + } else { + glTexImage2D( + this->m_Target, + this->m_Level, + this->GetFormatInfo().m_InternalFormat, + this->m_Width, + this->m_Height, + 0, + this->GetFormatInfo().m_DataFormat, + this->GetFormatInfo().m_DataType, + pixels + ); + } +} + +void GLMipmap::TexSubImage(const GLBox& a2, int32_t size, const void* pixels) { + // TODO + // BLIZZARD_ASSERT(!this->m_Texture->IsRenderTarget() && pixels != 0 && GLDevice::Get()->GetVertexArrayStates().buffers[eGLBT_PIXEL_UNPACK] == 0); + + if (this->m_Target == GL_TEXTURE_3D) { + glPixelStorei(GL_UNPACK_ROW_LENGTH, this->m_Width); + glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, this->m_Height); + + glTexSubImage3D( + this->m_Target, + this->m_Level, + a2.left, + a2.top, + a2.front, + a2.width, + a2.height, + a2.depth, + this->GetFormatInfo().m_DataFormat, + this->GetFormatInfo().m_DataType, + pixels + ); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); + } else if (this->GetFormatInfo().m_IsCompressed) { + glCompressedTexSubImage2D( + this->m_Target, + this->m_Level, + 0, + a2.top, + this->m_Width, + a2.height, + this->GetFormatInfo().m_InternalFormat, + size, + pixels + ); + } else { + glPixelStorei(GL_UNPACK_ROW_LENGTH, this->m_Width); + + glTexSubImage2D( + this->m_Target, + this->m_Level, + a2.left, + a2.top, + a2.width, + a2.height, + this->GetFormatInfo().m_DataFormat, + this->GetFormatInfo().m_DataType, + pixels + ); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + } +} + +void GLMipmap::Unmap() { + BLIZZARD_ASSERT(!this->m_Texture->IsRenderTarget()); + BLIZZARD_ASSERT(!this->m_Texture->IsSystemBuffer()); + BLIZZARD_ASSERT(this->m_MapParams != nullptr); + + if (this->m_MapParams->m_MapMode == GL_READ_ONLY) { + delete this->m_MapParams; + this->m_MapParams = nullptr; + return; + } + + GLDevice* device = GLDevice::Get(); + + BLIZZARD_ASSERT(this->m_Texture->m_MappedMipmaps > 0); + + if (device->m_TexWorker) { + device->m_TexWorker->Lock(); + + GLTexUnmap* command = new GLTexUnmap(this->m_Texture, this, this->m_MapParams); + device->m_TexWorker->Send(command); + device->m_TexWorker->Signal(); + + device->m_TexWorker->Unlock(); + } else { + this->Unmap(this->m_MapParams); + } + + this->m_MapParams = nullptr; +} + +void GLMipmap::Unmap(MapParams* mapParams) { + BLIZZARD_ASSERT(mapParams != nullptr); + + this->m_Texture->Bind(nullptr, 0); + + if (this->m_Unk24) { + if (this->GetFormatInfo().m_IsCompressed) { + GLBox area = { + 0, + mapParams->m_MapArea.top, + mapParams->m_MapArea.front, + this->m_Width, + mapParams->m_MapArea.height, + mapParams->m_MapArea.depth + }; + + this->TexSubImage(area, mapParams->m_Size, mapParams->m_Unk8); + } else { + this->TexSubImage(mapParams->m_MapArea, mapParams->m_Size, mapParams->m_Unk8 + mapParams->m_Unk7); + } + } else { + this->TexImage(this->m_Data); + this->m_Unk24 = 1; + } + + delete mapParams; + + // TODO + + this->m_Texture->m_MappedMipmaps--; +} diff --git a/src/gx/gll/GLMipmap.h b/src/gx/gll/GLMipmap.h new file mode 100644 index 0000000..7957933 --- /dev/null +++ b/src/gx/gll/GLMipmap.h @@ -0,0 +1,62 @@ +#ifndef GX_GLL_GL_MIPMAP_H +#define GX_GLL_GL_MIPMAP_H + +#include "gx/gll/GL.h" +#include "gx/gll/GLTypes.h" +#include +#include + +class GLFramebuffer; +class GLTexture; + +class GLMipmap { + public: + // Types + struct MapParams { + GLBox m_MapArea; + int32_t m_MapMode = 0; + int32_t m_Unk7 = 0; + unsigned char* m_Unk8 = nullptr; + int32_t m_Size = 0; + }; + + // Member variables + GLTexture* m_Texture = nullptr; + uint16_t m_Width = 0; + uint16_t m_Height = 0; + uint16_t m_Depth = 0; + uint8_t m_Level = 0; + uint8_t m_DepthBits = 0; + uint32_t m_Size = 0; + int32_t m_Target = 0; + unsigned char* m_Data = nullptr; // TODO proper type + MapParams* m_MapParams = nullptr; + std::vector* m_AttachPoints = nullptr; + uint32_t m_Unk20 = 0; + uint32_t m_Unk24 = 0; + + // Member functions + void Attach(GLFramebuffer*, GLenum, int32_t); + void Detach(GLFramebuffer*, GLenum, bool); + void DetachAll(); + int32_t GetDepthBits(void); + GLTextureFormat GetFormat(void); + TextureFormatInfo& GetFormatInfo(void); + uint16_t GetHeight(void); + int32_t GetPitch(void); + GLTexture* GetTexture(); + uint32_t GetTextureID(void); + uint16_t GetWidth(void); + void* Map(GLEnum, const GLBox*); + void* Map(GLEnum, const GLRect*); + void ReleaseObject(); + void RemoveDebugMipmap(); + void ResetData(GLEnum, int32_t, unsigned char*); + void ResetSize(uint32_t, uint32_t, uint32_t); + void TexImage(const void*); + void TexSubImage(const GLBox&, int32_t, const void*); + void Unmap(void); + void Unmap(MapParams*); +}; + +#endif diff --git a/src/gx/gll/GLObject.cpp b/src/gx/gll/GLObject.cpp new file mode 100644 index 0000000..2a73049 --- /dev/null +++ b/src/gx/gll/GLObject.cpp @@ -0,0 +1,22 @@ +#include "gx/gll/GLObject.h" +#include "util/BlizzardCore.hpp" + +void GLObject::AddRefTwin() { +} + +uint32_t GLObject::Release() { + BLIZZARD_ASSERT(this->m_RefCount > 0); + + this->m_RefCount--; + + if (this->m_RefCount == 0) { + this->ReleaseObject(); + } + + this->ReleaseTwin(); + + return this->m_RefCount; +} + +void GLObject::ReleaseTwin() { +} diff --git a/src/gx/gll/GLObject.h b/src/gx/gll/GLObject.h new file mode 100644 index 0000000..f446588 --- /dev/null +++ b/src/gx/gll/GLObject.h @@ -0,0 +1,22 @@ +#ifndef GX_GLL_GL_OBJECT_H +#define GX_GLL_GL_OBJECT_H + +#include + +class GLObject { + public: + // Member variables + GLObject* m_Next; + uint32_t m_RefCount; + int64_t m_TimeStamp; + + // Virtual member functions + virtual void ReleaseObject() = 0; + virtual void AddRefTwin(); + virtual void ReleaseTwin(); + + // Member functions + uint32_t Release(); +}; + +#endif diff --git a/src/gx/gll/GLPixelShader.cpp b/src/gx/gll/GLPixelShader.cpp new file mode 100644 index 0000000..e8bc8d1 --- /dev/null +++ b/src/gx/gll/GLPixelShader.cpp @@ -0,0 +1,17 @@ +#include "gx/gll/GLPixelShader.h" +#include "gx/gll/GL.h" + +GLPixelShader* GLPixelShader::Create() { + // TODO + // GLPool stuff + + GLPixelShader* shader = new GLPixelShader(); + + shader->m_ShaderID = 0; + shader->m_RefCount = 1; + shader->m_ShaderType = ePixelShader; + shader->m_UsingGLSL = 0; + shader->var5 = GL_FRAGMENT_PROGRAM_ARB; + + return shader; +} diff --git a/src/gx/gll/GLPixelShader.h b/src/gx/gll/GLPixelShader.h new file mode 100644 index 0000000..5ed7cc7 --- /dev/null +++ b/src/gx/gll/GLPixelShader.h @@ -0,0 +1,12 @@ +#ifndef GX_GLL_GL_PIXEL_SHADER_H +#define GX_GLL_GL_PIXEL_SHADER_H + +#include "gx/gll/GLShader.h" + +class GLPixelShader : public GLShader { + public: + // Static functions + static GLPixelShader* Create(void); +}; + +#endif diff --git a/src/gx/gll/GLPool.h b/src/gx/gll/GLPool.h new file mode 100644 index 0000000..77a907c --- /dev/null +++ b/src/gx/gll/GLPool.h @@ -0,0 +1,57 @@ +#ifndef GX_GLL_GL_POOL_H +#define GX_GLL_GL_POOL_H + +#include +#include + +template +class GLPool { + public: + // Static variables + static GLPool* m_pool; + + // Static functions + static GLPool* Get(void); + static void Init(void); + + // Member variables + std::atomic m_NextName; + + // Member functions + uint32_t GetNextName(void); + T* GetNextObject(void); + void SetNextName(uint32_t); +}; + +template +GLPool* GLPool::m_pool; + +template +GLPool* GLPool::Get() { + return GLPool::m_pool; +} + +template +void GLPool::Init() { + GLPool::m_pool = new GLPool(); +} + +template +uint32_t GLPool::GetNextName() { + return this->m_NextName++; +} + +template +T* GLPool::GetNextObject() { + // TODO + // - pop off of GLObjectPool + + return new T(); +} + +template +void GLPool::SetNextName(uint32_t name) { + this->m_NextName = name; +} + +#endif diff --git a/src/gx/gll/GLShader.cpp b/src/gx/gll/GLShader.cpp new file mode 100644 index 0000000..192c8bc --- /dev/null +++ b/src/gx/gll/GLShader.cpp @@ -0,0 +1,133 @@ +#include "gx/gll/GLShader.h" +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLPixelShader.h" +#include "gx/gll/GLPool.h" +#include "gx/gll/GLVertexShader.h" + +// TODO +// - threaded compiler support +// - glsl support +// - hybrid support +GLShader* GLShader::Create(ShaderType shaderType, bool hybrid, bool usingCG, const char* a4, const void* buf, int32_t codeLen, const char* a7, const char* name, GLShaderLogInfo* logInfo) { + const char* shaderCode = reinterpret_cast(buf); + + if (*reinterpret_cast(buf) == 'GSL1') { + BLIZZARD_ASSERT(!usingCG); + + const ShaderDataHeader header = *reinterpret_cast(buf); + + BLIZZARD_ASSERT(header.shaderType == shaderType); + BLIZZARD_ASSERT(header.size == codeLen); + BLIZZARD_ASSERT(header.codePos >= sizeof(ShaderDataHeader)); + BLIZZARD_ASSERT(header.codeSize > 0); + + shaderCode = &reinterpret_cast(buf)[header.codePos]; + } + + GLShader* shader = nullptr; + + if (shaderType == ePixelShader) { + shader = GLPixelShader::Create(); + } else if (shaderType == eVertexShader) { + shader = GLVertexShader::Create(); + } else { + // TODO + // sub_1C5E0(&v38, "Unknown shader type %d!", shaderType); + } + + shader->m_UsingCG = usingCG; + + if (usingCG) { + shader->CompileCG(a4, shaderCode, codeLen, a7, logInfo); + } else { + shader->m_Code.assign(shaderCode, codeLen); + + // TODO + // sub_5CD10(shader); + } + + return shader; +} + +bool GLShader::CheckErrorsARB(GLShaderLogInfo* logInfo) { + GLint errorPos; + + glGetIntegerv(GL_PROGRAM_ERROR_POSITION_ARB, &errorPos); + const GLubyte* errorStr = glGetString(GL_PROGRAM_ERROR_STRING_ARB); + + // TODO + // Blizzard::Debug::VAssert(logInfo != 0 || errorPos == -1, errorStr); + + return errorPos == -1; +} + +void GLShader::Compile(GLShaderLogInfo* logInfo) { + this->ImmediateCompile(logInfo); +} + +void GLShader::CompileCG(const char* a2, const void* shaderCode, int32_t codeLen, const char* a5, GLShaderLogInfo* logInfo) { + // TODO +} + +void GLShader::FlushUniforms(GLGLSLProgram* program) { + // TODO +} + +std::string& GLShader::GetCode() { + return this->m_Code; +} + +int32_t GLShader::GetShaderType() { + return this->m_ShaderType; +} + +void GLShader::ImmediateCompile(GLShaderLogInfo* logInfo) { + BLIZZARD_ASSERT(!this->GetCode().empty()); + + this->m_Device = GLDevice::Get(); + + if (!this->m_UsingGLSL) { + if (!this->m_ShaderID) { + if (this->m_ShaderType == eVertexShader) { + this->m_ShaderID = GLPool::Get()->GetNextName(); + } else { + this->m_ShaderID = GLPool::Get()->GetNextName(); + } + } + + this->m_Device->BindShader(this); + + const char* arbCode = this->GetCode().c_str(); + size_t arbLen = strlen(arbCode); + + glProgramStringARB(this->var5, GL_PROGRAM_FORMAT_ASCII_ARB, arbLen, arbCode); + + BLIZZARD_ASSERT(this->CheckErrorsARB(logInfo)); + } else { + // TODO + // - handle GLSL shaders + // - handle hybrid shaders + } + + if (logInfo) { + // TODO + // this->var20 = logInfo[0]; + } else { + this->var20 = 1; + } + + // TODO + // this->m_TimeStamp = Blizzard::Time::GetTimestamp(); +} + +bool GLShader::IsEnabled() { + return this->m_Enabled; +} + +void GLShader::ReleaseObject() { + // TODO +} + +void GLShader::SetShaderConstants(ShaderType shaderType, uint32_t index, const float* constants, uint32_t count) { + // TODO +} diff --git a/src/gx/gll/GLShader.h b/src/gx/gll/GLShader.h new file mode 100644 index 0000000..cc937f8 --- /dev/null +++ b/src/gx/gll/GLShader.h @@ -0,0 +1,75 @@ +#ifndef GX_GLL_GL_SHADER_H +#define GX_GLL_GL_SHADER_H + +#include "gx/gll/GLObject.h" +#include "gx/gll/GLShaderInput.h" +#include +#include +#include + +class GLDevice; +class GLGLSLProgram; +class GLShaderLogInfo; + +class GLShader : public GLObject { + public: + // Types + enum ShaderType { + eVertexShader = 1, + ePixelShader = 2, + eShaderTypeCount = 3 + }; + + struct ShaderDataHeader { + uint32_t signature; + uint32_t size; + ShaderType shaderType; + uint32_t codePos; + uint32_t codeSize; + uint32_t unk1; + uint32_t unk2; + uint32_t unk3; + }; + + // Static functions + static GLShader* Create(ShaderType, bool, bool, const char*, const void*, int32_t, const char*, const char*, GLShaderLogInfo*); + + // Member variables + int32_t m_ShaderType = 0; + int32_t var5 = 0; + uint32_t m_ShaderID = 0; + bool m_UsingCG = false; + bool m_UsingGLSL = false; + uint32_t m_UniformRegisterCount = 0; + GLShaderInput** var10 = nullptr; + float* var11 = nullptr; + bool var12 = false; + uint32_t var13 = 0; + uint32_t var14 = 0; + std::vector> var15; + std::vector> var16; + std::vector> var17; + GLShader* var18 = nullptr; + GLDevice* m_Device = nullptr; + bool var20 = 0; + bool m_Enabled = true; + std::basic_string, std::allocator> m_Code; + std::basic_string, std::allocator> var23; + + // Virtual member functions + virtual void ReleaseObject(); + + // Member functions + bool CheckErrorsARB(GLShaderLogInfo*); + bool CheckErrorsGLSL(GLShaderLogInfo*); + void Compile(GLShaderLogInfo*); + void CompileCG(const char*, const void*, int32_t, const char*, GLShaderLogInfo*); + void FlushUniforms(GLGLSLProgram*); + std::string& GetCode(void); + int32_t GetShaderType(void); + void ImmediateCompile(GLShaderLogInfo*); + bool IsEnabled(void); + void SetShaderConstants(ShaderType, uint32_t, const float*, uint32_t); +}; + +#endif diff --git a/src/gx/gll/GLShaderInput.h b/src/gx/gll/GLShaderInput.h new file mode 100644 index 0000000..66e7c8f --- /dev/null +++ b/src/gx/gll/GLShaderInput.h @@ -0,0 +1,33 @@ +#ifndef GX_GLL_GL_SHADER_INPUT_H +#define GX_GLL_GL_SHADER_INPUT_H + +#include + +class GLShader; + +class GLShaderInput { + public: + // Member variables + GLShader *m_Shader; + std::basic_string, std::allocator> m_Name; + std::basic_string, std::allocator> var2; + int16_t m_MapIndex; + int16_t var4; + int16_t var5; + int16_t m_UsedSize; + int16_t var7; + int16_t var8; + GLShaderInput *m_Parent; + int32_t var10; + int32_t var11; + int32_t var12; + int8_t m_ChildrenCount; + int8_t var14; + uint16_t var15; + int8_t var16; + int8_t m_Variability; + int8_t var18; + int8_t var19; +}; + +#endif diff --git a/src/gx/gll/GLTexture.cpp b/src/gx/gll/GLTexture.cpp new file mode 100644 index 0000000..bca15db --- /dev/null +++ b/src/gx/gll/GLTexture.cpp @@ -0,0 +1,659 @@ +#include "gx/gll/GLTexture.h" +#include "gx/gll/GLCommand.h" +#include "gx/gll/GLDevice.h" +#include "gx/gll/GLPool.h" +#include "gx/gll/GLUtil.h" +#include "gx/texture/CGxTex.hpp" +#include + +Blizzard::Thread::TLSSlot GLTexture::m_Bindings[4]; + +void* GLTexture::CreateBindings(void* ptr) { + return new std::deque>; +} + +void GLTexture::DestroyBindings(void* ptr) { + delete static_cast>*>(ptr); +} + +void GLTexture::Bind(GLDevice* device, bool force) { + BLIZZARD_ASSERT(!this->IsSystemBuffer()); + BLIZZARD_ASSERT(this->m_Depth != 0); + + if (!device) { + device = GLDevice::Get(); + } + + BLIZZARD_ASSERT(device != nullptr); + + auto& bindings = this->GetBindings(); + uint32_t deviceID = device->GetID(); + + if (deviceID >= bindings.size()) { + bindings.resize(deviceID + 1); + } + + bindings[deviceID].device = device; + + uint32_t currentActiveTexture = device->m_States.binding.currentActiveTexture; + + if (bindings[deviceID].boundStages[currentActiveTexture]) { + return; + } + + if (force) { + bindings[deviceID].boundStages[currentActiveTexture] = 1; + device->BindTexture(this->m_TextureType, this); + return; + } + + for (int32_t i = 0; i < 16; i++) { + if (bindings[deviceID].boundStages[i]) { + device->SetActiveTexture(i); + return; + } + } + + bindings[deviceID].boundStages[currentActiveTexture] = 1; + device->BindTexture(this->m_TextureType, this); +} + +void GLTexture::FreeTexture() { + auto device = GLDevice::Get(); + + if (device->m_TexWorker) { + // TODO + + this->m_LastFrameUsed = 0; + + int32_t numFace = this->m_TextureType == GL_TEXTURE_CUBE_MAP ? 6 : 1; + for (int32_t face = 0; face < numFace; face++) { + for (int32_t level = 0; level < this->m_NumMipmap; level++) { + this->m_Mipmaps[face][level].ReleaseObject(); + } + } + + // TODO this->Sub690D0(); + // TODO device->m_TexWorker->Sub72340(this); + + device->m_TexWorker->Lock(); + + auto command = new GLTexDestroy(this); + device->m_TexWorker->Send(command); + device->m_TexWorker->Signal(); + + device->m_TexWorker->Unlock(); + + return; + } + + int32_t numFace = this->m_TextureType == GL_TEXTURE_CUBE_MAP ? 6 : 1; + for (int32_t face = 0; face < numFace; face++) { + if (this->m_Mipmaps[face]) { + delete[] this->m_Mipmaps[face]; + } + } + + if (this->m_Mipmaps) { + delete[] this->m_Mipmaps; + } + + this->m_Mipmaps = nullptr; + this->m_Depth = 0; + + // TODO this->Sub690D0(); + + glDeleteTextures(1, &this->m_TextureID); + + this->m_GenerateMipmaps = 0; + this->m_MaxMipmapLevel = 1000; + this->m_BaseMipmapLevel = 0; + this->m_CompareMode = 0; + + this->m_Sampler.mipmapBias = 0.0f; + this->m_Sampler.borderColor = { 0.0f, 0.0f, 0.0f, 0.0f }; + this->m_Sampler.addressModeS = GL_REPEAT; + this->m_Sampler.addressModeT = GL_REPEAT; + this->m_Sampler.addressModeR = GL_REPEAT; + this->m_Sampler.magFilterMode = GL_LINEAR; + this->m_Sampler.minFilterMode = GL_NEAREST_MIPMAP_LINEAR; + this->m_Sampler.maxAnisotropy = 1.0f; + + Blizzard::Memory::Free(this->m_Data); + this->m_Data = nullptr; + + switch (this->m_TextureType) { + case GL_TEXTURE_3D: + // TODO GLPool::GLObjectPool::Push(GLPool::m_pool + 264, this); + break; + + case GL_TEXTURE_CUBE_MAP: + // TODO GLPool::GLObjectPool::Push(GLPool::m_pool + 520, this); + break; + + case GL_TEXTURE_2D: + // TODO GLPool::GLObjectPool::Push(GLPool::m_pool + 131080, this); + break; + } +} + +std::vector& GLTexture::GetBindings() { + uint32_t index = GLLTextureTypeToIndex(this->m_TextureType); + + uint32_t id; + + if (index == 0) { + id = this->m_TextureID - 33; + } else if (index == 1) { + id = this->m_TextureID - 32801; + } else if (index == 2) { + id = this->m_TextureID - 32865; + } else if (index == 3) { + id = this->m_TextureID - 1; + } + + auto target = static_cast>*>( + Blizzard::Thread::RegisterLocalStorage( + &GLTexture::m_Bindings[index], + GLTexture::CreateBindings, + nullptr, + GLTexture::DestroyBindings + ) + ); + + if (id >= target->size()) { + target->resize(id + 1); + } + + return (*target)[id]; +} + +GLTextureFormat GLTexture::GetFormat() { + return this->m_Format; +} + +TextureFormatInfo& GLTexture::GetFormatInfo() { + return k_TextureFormatInfo[this->m_Format]; +} + +GLMipmap* GLTexture::GetMipmap(uint32_t level, GLEnum face) { + BLIZZARD_ASSERT(face >= GL_TEXTURE_CUBE_MAP_POSITIVE_X && face <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z); + BLIZZARD_ASSERT(level < this->m_NumMipmap); + BLIZZARD_ASSERT(this->m_Mipmaps != nullptr); + BLIZZARD_ASSERT(this->m_Mipmaps[face - GL_TEXTURE_CUBE_MAP_POSITIVE_X] != nullptr); + + return &this->m_Mipmaps[face - GL_TEXTURE_CUBE_MAP_POSITIVE_X][level]; +} + +bool GLTexture::IsRenderTarget() { + return this->m_Flags & GLTFLAG_RENDERTARGET; +} + +bool GLTexture::IsSystemBuffer() { + return this->m_Flags & GLTFLAG_SYSTEM_BUFFER; +} + +bool GLTexture::IsValid() { + return this->m_Depth; +} + +void* GLTexture::Map(uint32_t level, const GLRect* a3, uint32_t& a4, GLEnum a5) { + BLIZZARD_ASSERT(this->m_TextureType != GL_TEXTURE_3D); + + auto mipmap = this->GetMipmap(level, GL_TEXTURE_CUBE_MAP_POSITIVE_X); + a4 = mipmap->GetPitch(); + return mipmap->Map(a5, a3); +} + +void GLTexture::RecreateGLTexture() { + BLIZZARD_ASSERT(GLDevice::Get()->m_TexWorker != nullptr); + + if (this->m_TextureType == GL_TEXTURE_RECTANGLE_EXT) { + return; + } + + bool isCubeMap = this->m_TextureType == GL_TEXTURE_CUBE_MAP; + int32_t numFace = isCubeMap ? 6 : 1; + + for (int32_t face = 0; face < numFace; face++) { + for (int32_t level = 0; level < this->m_NumMipmap; level++) { + this->m_Mipmaps[face][level].Map(GL_WRITE_ONLY, static_cast(nullptr)); + this->m_Mipmaps[face][level].Unmap(); + } + } + + glTexParameterf(this->m_TextureType, GL_TEXTURE_LOD_BIAS, this->m_Sampler.mipmapBias); + glTexParameteri(this->m_TextureType, GL_TEXTURE_WRAP_S, this->m_Sampler.addressModeS); + glTexParameteri(this->m_TextureType, GL_TEXTURE_WRAP_T, this->m_Sampler.addressModeT); + glTexParameteri(this->m_TextureType, GL_TEXTURE_WRAP_R, this->m_Sampler.addressModeR); + glTexParameteri(this->m_TextureType, GL_TEXTURE_MIN_FILTER, this->m_Sampler.minFilterMode); + glTexParameteri(this->m_TextureType, GL_TEXTURE_MAG_FILTER, this->m_Sampler.magFilterMode); + glTexParameterf(this->m_TextureType, GL_TEXTURE_MAX_ANISOTROPY_EXT, this->m_Sampler.maxAnisotropy); + glTexParameterfv(this->m_TextureType, GL_TEXTURE_BORDER_COLOR, reinterpret_cast(&this->m_Sampler.borderColor)); + glTexParameteri(this->m_TextureType, GL_TEXTURE_MAX_LEVEL, this->m_MaxMipmapLevel); + glTexParameteri(this->m_TextureType, GL_TEXTURE_BASE_LEVEL, this->m_BaseMipmapLevel); + glTexParameteri(this->m_TextureType, GL_GENERATE_MIPMAP, this->m_GenerateMipmaps); +} + +void GLTexture::ResizeMipmaps() { + BLIZZARD_ASSERT(this->m_Mipmaps == nullptr); + + int32_t numFace = this->m_TextureType == GL_TEXTURE_CUBE_MAP ? 6 : 1; + + this->m_Mipmaps = new GLMipmap*[numFace]; + + for (int32_t face = 0; face < numFace; face++) { + this->m_Mipmaps[face] = new GLMipmap[this->m_NumMipmap]; + } +} + +void GLTexture::SetAddressModeR(GLEnum mode) { + // TODO +} + +void GLTexture::SetAddressModeS(GLEnum mode) { + if (this->m_Sampler.addressModeS == mode) { + return; + } + + if (mode == GL_CLAMP_TO_EDGE) { + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_WRAP_S, mode); + this->m_Sampler.addressModeS = mode; + } else { + // Workaround for buggy GPU (possibly ATI Radeon X1900) + if (GLDevice::GetRendererInfo().renderer_id == 0x21900) { + if (this->m_Width & (this->m_Width - 1)) { + return; + } + + if (this->m_Height & (this->m_Height - 1)) { + return; + } + } + + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_WRAP_S, mode); + this->m_Sampler.addressModeS = mode; + } +} + +void GLTexture::SetAddressModeT(GLEnum mode) { + if (this->m_Sampler.addressModeT == mode) { + return; + } + + if (mode == GL_CLAMP_TO_EDGE) { + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_WRAP_T, mode); + this->m_Sampler.addressModeT = mode; + } else { + // Workaround for buggy GPU (possibly ATI Radeon X1900) + if (GLDevice::GetRendererInfo().renderer_id == 0x21900) { + if (this->m_Width & (this->m_Width - 1)) { + return; + } + + if (this->m_Height & (this->m_Height - 1)) { + return; + } + } + + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_WRAP_T, mode); + this->m_Sampler.addressModeT = mode; + } +} + +void GLTexture::SetBorderColor(const GLColor4f& color) { + // TODO +} + +void GLTexture::SetCompareMode(GLEnum compareMode) { + BLIZZARD_ASSERT( + this->GetFormatInfo().m_DataFormat == GL_DEPTH_COMPONENT + || this->GetFormatInfo().m_DataFormat == GL_DEPTH_STENCIL_EXT + || compareMode == GL_NONE + ); + + if (this->m_CompareMode != compareMode) { + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_COMPARE_MODE, compareMode); + this->m_CompareMode = compareMode; + } +} + +void GLTexture::SetMagFilterMode(GLEnum mode) { + if (this->m_Sampler.magFilterMode == mode) { + return; + } + + if (GLDevice::GetRendererInfo().vendor_id == 2 && this->IsRenderTarget() && mode != GL_LINEAR) { + return; + } + + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_MAG_FILTER, mode); + this->m_Sampler.magFilterMode = mode; +} + +void GLTexture::SetMaxAnisotropy(int32_t maxAnisotropy) { + if (this->m_Sampler.maxAnisotropy == maxAnisotropy) { + return; + } + + this->Bind(nullptr, 0); + glTexParameterf(this->m_TextureType, GL_TEXTURE_MAX_ANISOTROPY_EXT, maxAnisotropy); + this->m_Sampler.maxAnisotropy = maxAnisotropy; +} + +void GLTexture::SetMinFilterMode(GLEnum mode) { + if (this->m_Sampler.minFilterMode == mode) { + return; + } + + if (GLDevice::GetRendererInfo().vendor_id == 2 && this->IsRenderTarget() && mode != GL_LINEAR) { + return; + } + + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_MIN_FILTER, mode); + this->m_Sampler.minFilterMode = mode; +} + +void GLTexture::SetupTexture() { + BLIZZARD_ASSERT(this->m_NumMipmap == 1 || (this->m_Flags & GLTFLAG_AUTOGEN_MIPMAP) == 0); + BLIZZARD_ASSERT(!this->IsRenderTarget() || this->m_NumMipmap == 1); + BLIZZARD_ASSERT(!this->IsRenderTarget() || (this->m_Flags & GLTFLAG_READ_ACCESS) == 0); + + GLDevice* device = GLDevice::Get(); + + if (this->GetFormatInfo().m_IsCompressed) { + int32_t smallestDim = std::min(this->m_Width, this->m_Height); + + BLIZZARD_ASSERT(smallestDim >= 4); + + if (smallestDim == 4) { + this->m_NumMipmap = 1; + } else if (smallestDim == 8) { + this->m_NumMipmap = 2; + } else if (smallestDim == 16) { + this->m_NumMipmap = 3; + } else if (smallestDim == 32) { + this->m_NumMipmap = 4; + } else if (smallestDim == 64) { + this->m_NumMipmap = 5; + } else if (smallestDim == 128) { + this->m_NumMipmap = 6; + } else if (smallestDim == 256) { + this->m_NumMipmap = 7; + } else if (smallestDim == 512) { + this->m_NumMipmap = 8; + } else if (smallestDim == 1024) { + this->m_NumMipmap = 9; + } else if (smallestDim == 2048) { + this->m_NumMipmap = 10; + } else if (smallestDim == 4096) { + this->m_NumMipmap = 11; + } else { + int32_t i = smallestDim >> 1; + int32_t n = 0; + + while (i) { + i >>= 1; + n++; + } + + this->m_NumMipmap = n - 1; + } + } else { + int32_t largestDim = std::max(this->m_Width, this->m_Height); + + if (largestDim == 1) { + this->m_NumMipmap = 1; + } else if (largestDim == 2) { + this->m_NumMipmap = 2; + } else if (largestDim == 4) { + this->m_NumMipmap = 3; + } else if (largestDim == 8) { + this->m_NumMipmap = 4; + } else if (largestDim == 16) { + this->m_NumMipmap = 5; + } else if (largestDim == 32) { + this->m_NumMipmap = 6; + } else if (largestDim == 64) { + this->m_NumMipmap = 7; + } else if (largestDim == 128) { + this->m_NumMipmap = 8; + } else if (largestDim == 256) { + this->m_NumMipmap = 9; + } else if (largestDim == 512) { + this->m_NumMipmap = 10; + } else if (largestDim == 1024) { + this->m_NumMipmap = 11; + } else if (largestDim == 2048) { + this->m_NumMipmap = 12; + } else if (largestDim == 4096) { + this->m_NumMipmap = 13; + } else { + int32_t i = largestDim >> 1; + int32_t n = 0; + + while (i) { + i >>= 1; + n++; + } + + this->m_NumMipmap = n + 1; + } + } + + if (!(this->m_Flags & GLTFLAG_SYSTEM_BUFFER)) { + BLIZZARD_ASSERT(this->m_RequestedNumMipmaps != 0); + + this->m_NumMipmap = std::min(this->m_NumMipmap, this->m_RequestedNumMipmaps); + } + + this->var12 = this->GetFormatInfo().m_BytePerPixel * this->m_Width; + if (this->GetFormatInfo().m_IsCompressed) { + this->var12 >>= 2; + } + + this->ResizeMipmaps(); + + bool isCubeMap = this->m_TextureType == GL_TEXTURE_CUBE_MAP; + + this->m_Size = 0; + + int32_t numFace = isCubeMap ? 6 : 1; + + for (int32_t face = 0; face < numFace; face++) { + for (int32_t level = 0; level < this->m_NumMipmap; level++) { + GLMipmap* mip = this->GetMipmap(level, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face); + + mip->m_Level = level; + mip->m_Texture = this; + mip->ResetSize(this->m_Width >> level, this->m_Height >> level, this->m_Depth >> level); + + this->m_Size += mip->m_Size; + } + } + + this->var7 = GLDevice::m_Devices[0]->m_TextureList.begin(); + + if (this->m_Flags & GLTFLAG_SYSTEM_BUFFER) { + return; + } + + BLIZZARD_ASSERT(this->m_Data == nullptr); + + if (!this->IsRenderTarget()) { + this->m_Data = static_cast(Blizzard::Memory::Allocate(this->m_Size)); + } + + this->Bind(nullptr, 0); + + if (this->IsRenderTarget()) { + this->SetAddressModeR(GL_CLAMP_TO_EDGE); + this->SetAddressModeS(GL_CLAMP_TO_EDGE); + this->SetAddressModeT(GL_CLAMP_TO_EDGE); + this->SetMinFilterMode(GL_LINEAR); + this->SetMagFilterMode(GL_LINEAR); + + glTexParameteri(this->m_TextureType, GL_TEXTURE_STORAGE_HINT_APPLE, GL_STORAGE_CACHED_APPLE); + } else { + this->SetAddressModeR(GL_REPEAT); + this->SetAddressModeS(GL_REPEAT); + this->SetAddressModeT(GL_REPEAT); + this->SetMinFilterMode(GL_NEAREST_MIPMAP_NEAREST); + this->SetMagFilterMode(GL_NEAREST); + } + + if (this->GetFormatInfo().m_DataFormat == GL_DEPTH_COMPONENT || this->GetFormatInfo().m_DataFormat == GL_DEPTH_STENCIL) { + this->SetCompareMode(GLDevice::m_ExtARBShadow >= 1 ? GL_COMPARE_R_TO_TEXTURE : 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + glTexParameteri(this->m_TextureType, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE); + } else { + this->SetCompareMode(0); + } + + this->SetMaxAnisotropy(1); + + this->SetBorderColor(GLColor4f::ZERO); + + int32_t autogenMipmap = this->m_Flags & GLTFLAG_AUTOGEN_MIPMAP; + if (autogenMipmap != this->m_GenerateMipmaps) { + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_GENERATE_MIPMAP, autogenMipmap); + this->m_GenerateMipmaps = autogenMipmap; + } + + int32_t maxMipmapLevel = this->m_NumMipmap - 1; + if (maxMipmapLevel != this->m_MaxMipmapLevel) { + this->Bind(nullptr, 0); + glTexParameteri(this->m_TextureType, GL_TEXTURE_MAX_LEVEL, maxMipmapLevel); + this->m_MaxMipmapLevel = maxMipmapLevel; + } + + if (this->m_TextureType == GL_TEXTURE_RECTANGLE_EXT) { + return; + } + + if (this->IsRenderTarget()) { + device->SetUnpackClientStorage(0); + } + + unsigned char* data = reinterpret_cast(this->m_Data); + + for (int32_t face = 0; face < numFace; face++) { + for (int32_t level = 0; level < this->m_NumMipmap; level++) { + GLMipmap* mip = this->GetMipmap(level, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face); + + if (this->m_TextureType == GL_TEXTURE_CUBE_MAP) { + mip->ResetData(face, level, data); + } else { + mip->ResetData(this->m_TextureType, level, data); + } + + if (data) { + data += mip->m_Size; + } + } + } + + // TODO + // this->m_TimeStamp = Blizzard::Time::GetTimestamp(); + + if (this->IsRenderTarget()) { + device->SetUnpackClientStorage(1); + } +} + +void GLTexture::Unbind(GLDevice* device, uint32_t stage) { + auto& bindings = this->GetBindings(); + + BLIZZARD_ASSERT(device->GetID() < bindings.size()); + BLIZZARD_ASSERT(bindings[device->GetID()].device == device); + BLIZZARD_ASSERT(bindings[device->GetID()].boundStages[stage]); + + bindings[device->GetID()].boundStages[stage] = 0; +} + +void GLTexture::Unmap(uint32_t level, GLEnum face) { + auto mipmap = this->GetMipmap(level, face); + mipmap->Unmap(); +} + +GLTexture2D* GLTexture2D::Create(uint32_t width, uint32_t height, uint32_t numMipMap, GLTextureFormat format, uint32_t flags) { + GLTexture2D* tex; + + // TODO + // tex = GLPool::GLObjectPool::Pop(GLPool::m_pool + 131080); + tex = nullptr; + + if (!tex) { + tex = new GLTexture2D(); + } + + // TODO + // Blizzard::Debug::Assert(tex->m_refCount == 0); + + tex->m_RefCount = 1; + + // TODO + // Blizzard::Debug::Assert(tex->m_TextureID >= PoolStats::NAME_POOL_FIRST_NAME); + + tex->m_TextureType = GL_TEXTURE_2D; + tex->m_Width = width; + tex->m_Depth = 1; + tex->m_Height = height; + tex->m_Format = format; + tex->m_NumMipmap = numMipMap; + tex->m_RequestedNumMipmaps = numMipMap; + tex->m_Flags = flags; + + tex->SetupTexture(); + + return tex; +} + +GLTexture2D::GLTexture2D() : GLTexture() { + this->m_TextureType = GL_TEXTURE_2D; + this->m_TextureID = GLPool::Get()->GetNextName(); + + // TODO + // Blizzard::Debug::Assert(this->m_TextureID >= PoolStats::NAME_POOL_FIRST_NAME); +} + +void GLTexture2D::ReleaseObject() { + BLIZZARD_ASSERT(this->m_TextureType == GL_TEXTURE_2D); + this->FreeTexture(); +} + +void GLLTexSetFlags(CGxTex* texId, GLTexture* a2) { + static GLEnum convertMagFilterToOgl[] = { + GL_NEAREST, + GL_LINEAR, + GL_NEAREST, + GL_LINEAR, + GL_LINEAR, + GL_LINEAR + }; + + static GLEnum convertMinFilterToOgl[] = { + GL_NEAREST, + GL_LINEAR, + GL_NEAREST_MIPMAP_NEAREST, + GL_LINEAR_MIPMAP_NEAREST, + GL_LINEAR_MIPMAP_LINEAR, + GL_LINEAR_MIPMAP_LINEAR + }; + + a2->SetMagFilterMode(convertMagFilterToOgl[texId->m_flags.m_filter]); + a2->SetMinFilterMode(convertMinFilterToOgl[texId->m_flags.m_filter]); + + a2->SetAddressModeS(texId->m_flags.m_wrapU ? GL_REPEAT : GL_CLAMP_TO_EDGE); + a2->SetAddressModeT(texId->m_flags.m_wrapV ? GL_REPEAT : GL_CLAMP_TO_EDGE); + + a2->SetMaxAnisotropy(texId->m_flags.m_maxAnisotropy); +} diff --git a/src/gx/gll/GLTexture.h b/src/gx/gll/GLTexture.h new file mode 100644 index 0000000..322dea1 --- /dev/null +++ b/src/gx/gll/GLTexture.h @@ -0,0 +1,100 @@ +#ifndef GX_GLL_GL_TEXTURE_H +#define GX_GLL_GL_TEXTURE_H + +#include "gx/gll/GL.h" +#include "gx/gll/GLObject.h" +#include "gx/gll/GLTypes.h" +#include "util/BlizzardCore.hpp" +#include +#include + +#define GLTFLAG_RENDERTARGET 0x1 +#define GLTFLAG_DEPTH 0x2 +#define GLTFLAG_STENCIL 0x4 +#define GLTFLAG_AUTOGEN_MIPMAP 0x8 +#define GLTFLAG_READ_ACCESS 0x10 +#define GLTFLAG_SYSTEM_BUFFER 0x20 + +class CGxTex; +class GLMipmap; +class GLDevice; + +class GLTexture : public GLObject { + public: + // Types + struct Binding { + uint8_t boundStages[16]; + GLDevice* device; + }; + + // Static variables + static Blizzard::Thread::TLSSlot m_Bindings[4]; + + // Static functions + static void* CreateBindings(void*); + static void DestroyBindings(void*); + + // Member variables + uint32_t m_TextureID = 0; + GLEnum m_TextureType = 0; + GLMipmap** m_Mipmaps = nullptr; + std::list::iterator var7; + uint32_t m_LastFrameUsed; + uint32_t m_Width = 0; + uint32_t m_Height = 0; + uint32_t m_Depth = 0; + uint32_t var12 = 0; + uint32_t m_Size = 0; + GLTextureFormat m_Format = GLTF_INVALID; + uint32_t m_Flags = 0; + uint32_t m_NumMipmap = 0; + uint32_t m_RequestedNumMipmaps; + char* m_Data = nullptr; + std::atomic m_MappedMipmaps = { 0 }; + GLStates::Sampler m_Sampler; + bool m_GenerateMipmaps = 0; + int32_t m_MaxMipmapLevel = 1000; + int32_t m_BaseMipmapLevel = 0; + int32_t m_CompareMode = 0; + + // Member functions + void Bind(GLDevice*, bool); + void FreeTexture(); + std::vector& GetBindings(void); // invented name + GLTextureFormat GetFormat(void); + TextureFormatInfo& GetFormatInfo(void); + GLMipmap* GetMipmap(uint32_t, GLEnum); + bool IsRenderTarget(void); + bool IsSystemBuffer(void); + bool IsValid(void); + void* Map(uint32_t, const GLRect*, uint32_t&, GLEnum); + void RecreateGLTexture(void); + void ResizeMipmaps(void); + void SetAddressModeR(GLEnum); + void SetAddressModeS(GLEnum); + void SetAddressModeT(GLEnum); + void SetBorderColor(const GLColor4f&); + void SetCompareMode(GLEnum); + void SetMagFilterMode(GLEnum); + void SetMaxAnisotropy(int32_t); + void SetMinFilterMode(GLEnum); + void SetupTexture(void); + void Unbind(GLDevice*, uint32_t); // invented name + void Unmap(uint32_t level, GLEnum face); +}; + +class GLTexture2D : public GLTexture { + public: + // Static functions + static GLTexture2D* Create(uint32_t, uint32_t, uint32_t, GLTextureFormat, uint32_t); + + // Virtual member functions + virtual void ReleaseObject(); + + // Member functions + GLTexture2D(); +}; + +void GLLTexSetFlags(CGxTex*, GLTexture*); + +#endif diff --git a/src/gx/gll/GLTypes.cpp b/src/gx/gll/GLTypes.cpp new file mode 100644 index 0000000..6875344 --- /dev/null +++ b/src/gx/gll/GLTypes.cpp @@ -0,0 +1,5 @@ +#include "gx/gll/GLTypes.h" + +GLColor4f GLColor4f::ZERO = { 0.0, 0.0, 0.0, 0.0 }; +GLColor4f GLColor4f::WHITE = { 1.0, 1.0, 1.0, 1.0 }; +GLColor4f GLColor4f::BLACK = { 0.0, 0.0, 0.0, 1.0 }; diff --git a/src/gx/gll/GLTypes.h b/src/gx/gll/GLTypes.h new file mode 100644 index 0000000..6b01706 --- /dev/null +++ b/src/gx/gll/GLTypes.h @@ -0,0 +1,370 @@ +#ifndef GX_GLL_GL_TYPES_H +#define GX_GLL_GL_TYPES_H + +#include +#include + +class GLBuffer; +class GLFramebuffer; + +enum GLTextureFormat { + GLTF_INVALID = 0, + GLTF_ARGB8888 = 1, + GLTF_XRGB8888 = 2, + GLTF_RGBA8888 = 3, + GLTF_ABGR8888 = 4, + GLTF_ARGB0888 = 5, + GLTF_RGB888 = 6, + GLTF_BGR888 = 7, + GLTF_RGBA32F = 8, + GLTF_RGBA16F = 9, + GLTF_RG16F = 10, + GLTF_D32 = 11, + GLTF_D24 = 12, + GLTF_D16 = 13, + GLTF_DF = 14, + GLTF_D24S8 = 15, + GLTF_S8 = 16, + GLTF_ARGB4444 = 17, + GLTF_ARGB1555 = 18, + GLTF_ARGB0555 = 19, + GLTF_RGB565 = 20, + GLTF_A2RGB10 = 21, + GLTF_RGB16 = 22, + GLTF_L8 = 23, + GLTF_A8 = 24, + GLTF_A8L8 = 25, + GLTF_DXT1 = 26, + GLTF_DXT3 = 27, + GLTF_DXT5 = 28, + GLTF_NUM_TEXTURE_FORMATS = 29 +}; + +enum GLVertexType { + GLVT_INVALID = 0, + GLVT_FLOAT1 = 1, + GLVT_FLOAT2 = 2, + GLVT_FLOAT3 = 3, + GLVT_FLOAT4 = 4, + GLVT_UBYTE4 = 5, + GLVT_UBYTE4N = 6, + GLVT_SHORT = 7, + GLVT_SHORT2 = 8, + GLVT_SHORT4 = 9, + GLVT_SHORT2N = 10, + GLVT_SHORT4N = 11, + GLVT_USHORT2N = 12, + GLVT_USHORT4N = 13, + GLVT_NUM_VERTEX_TYPES = 14 +}; + +enum GLBufferType { + eGLBT_PIXEL_UNPACK = 3, +}; + +struct GLAttachPoint { + GLFramebuffer* framebuffer; + int32_t point; // TODO GLenum? + int32_t zOffset; // TODO check type +}; + +struct GLBox { + int32_t left; + int32_t top; + int32_t front; + int32_t width; + int32_t height; + int32_t depth; +}; + +struct GLColor4f { + float r; + float g; + float b; + float a; + + static GLColor4f ZERO; + static GLColor4f WHITE; + static GLColor4f BLACK; +}; + +struct GLDirtyRange { + uint16_t start; + uint16_t end; +}; + +struct GLfloat4 { + float x; + float y; + float z; + float w; +}; + +struct GLRect { + int32_t left; + int32_t top; + int32_t width; + int32_t height; +}; + +struct GLTransform { + bool isDirty; + + union { + struct { + float a0; + float a1; + float a2; + float a3; + float b0; + float b1; + float b2; + float b3; + float c0; + float c1; + float c2; + float c3; + float d0; + float d1; + float d2; + float d3; + }; + + struct { + float rows[4][4]; + }; + + float m[16]; + }; + + bool isIdentity; +}; + +struct GLStates { + struct Depth { + bool testEnable; + int32_t compareFunc; + bool writeMask; + }; + + struct Stencil { + struct StencilFace { + int32_t compareFunc; + int32_t opFail; + int32_t opZFail; + int32_t opZPass; + }; + + bool testEnable; + int32_t ref; + uint32_t mask; + uint32_t writeMask; + bool useTwoSidedStencil; + StencilFace front; + StencilFace back; + }; + + struct Rasterizer { + struct ClipPlane { + double plane[4]; + }; + + int32_t cullMode; + int32_t cullFaceMode; + int32_t fillMode; + float constantDepthBias; + float slopeScaledDepthBias; + GLRect viewport; + double zNear; + double zFar; + bool scissorEnable; + GLRect scissor; + uint32_t clipPlaneMask; + ClipPlane clipPlanes[6]; + }; + + struct Blend { + struct ColorMask { + bool red; + bool green; + bool blue; + bool alpha; + }; + + ColorMask colorMask[4]; + bool alphaBlend; + int32_t srcBlendFactor; + int32_t destBlendFactor; + int32_t blendOp; + GLColor4f blendColor; + }; + + struct Clear { + GLColor4f clearColor; + double clearDepth; + int32_t clearStencil; + }; + + struct FixedFunc { + struct TexOp { + int32_t texturing; + GLColor4f constant; + int32_t colorOp; + float colorScale; + int32_t colorArg0; + int32_t colorArg1; + int32_t colorArg2; + int32_t alphaOp; + float alphaScale; + int32_t alphaArg0; + int32_t alphaArg1; + int32_t alphaArg2; + }; + + struct Light { + bool enable; + GLfloat4 position; + GLTransform view; + float constantAttenuation; + float linearAttenuation; + float quadraticAttenuation; + GLColor4f ambient; + GLColor4f diffuse; + GLColor4f specular; + }; + + struct Material { + bool colorTracking; + int32_t materialSource; + GLColor4f ambient; + GLColor4f diffuse; + GLColor4f specular; + float shininess; + GLColor4f emission; + }; + + struct Lighting { + bool enable; + GLColor4f sceneAmbient; + Light lights[8]; + Material material; + }; + + struct Transforms { + int32_t matrixMode; + int32_t modelviewStatus; + GLTransform modelView; + GLTransform world; + GLTransform view; + GLTransform projection; + GLTransform texture[8]; + }; + + struct TexGen { + int32_t S; + int32_t T; + int32_t R; + int32_t Q; + }; + + struct PointSprite { + bool enable; + float size; + float attenuation[3]; + float min; + float max; + }; + + bool fogEnable; + GLColor4f fogColor; + int32_t fogMode; + float fogStart; + float fogEnd; + float fogDensity; + bool alphaTestEnable; + int32_t alphaTestFunc; + float alphaTestRef; + TexOp texOp[8]; + Lighting lighting; + Transforms transforms; + int32_t texCoordIndex[8]; + TexGen texGen[8]; + PointSprite pointSprite; + bool normalizeNormal; + }; + + struct Sampler { + float mipmapBias; + int32_t addressModeS; + int32_t addressModeT; + int32_t addressModeR; + int32_t magFilterMode; + int32_t minFilterMode; + float maxAnisotropy; + GLColor4f borderColor; + }; + + struct Shader { + bool vertexShaderEnable; + GLfloat4 vertexShaderConst[256]; + bool pixelShaderEnable; + GLfloat4 pixelShaderConst[64]; + }; + + struct Binding { + uint32_t currentActiveTexture; + uint32_t texture[4][16]; + uint32_t framebuffer; + uint32_t renderbuffer; + uint32_t vertexProgram; + uint32_t pixelProgram; + uint32_t glslProgram; + uint32_t vertexArrayObject; + }; + + struct Misc { + bool unpackClientStorage; + int32_t drawBuffers[4]; + int32_t readBuffer; + }; + + struct VertexArrayObject { + struct VertexAttrib { + bool enable = 0; + uint32_t size = 4; + int32_t type = GL_FLOAT; + bool normalized = 0; + uint32_t stride = 0; + void* offset = nullptr; + GLBuffer* buffer = nullptr; + }; + + uint32_t buffers[4] = {}; + VertexAttrib vertexAttribs[16]; + VertexAttrib position; + VertexAttrib normal; + VertexAttrib color0; + VertexAttrib color1; + VertexAttrib texCoord[8]; + }; + + Depth depth; + Stencil stencil; + Rasterizer rasterizer; + Blend blend; + Clear clear; + FixedFunc fixedFunc; + Sampler samplers[16]; + Shader shader; + Binding binding; + Misc misc; +}; + +struct GLVertexAttrib { + uint32_t stream; + int32_t slot; + int32_t type; + uint32_t offset; +}; + +#endif diff --git a/src/gx/gll/GLUtil.cpp b/src/gx/gll/GLUtil.cpp new file mode 100644 index 0000000..29e8e5b --- /dev/null +++ b/src/gx/gll/GLUtil.cpp @@ -0,0 +1,22 @@ +#include "gx/gll/GLUtil.h" +#include "util/BlizzardCore.hpp" + +uint32_t GLLTextureTypeToIndex(GLEnum textureType) { + uint32_t index; + + if (textureType == GL_TEXTURE_2D) { + index = 0; + } else if (textureType == GL_TEXTURE_3D) { + index = 1; + } else if (textureType == GL_TEXTURE_CUBE_MAP) { + index = 2; + } else if (textureType == GL_TEXTURE_RECTANGLE_EXT) { + index = 3; + } else { + index = 5; + + BLIZZARD_ASSERT(false); + } + + return index; +} diff --git a/src/gx/gll/GLUtil.h b/src/gx/gll/GLUtil.h new file mode 100644 index 0000000..6ffdb95 --- /dev/null +++ b/src/gx/gll/GLUtil.h @@ -0,0 +1,9 @@ +#ifndef GX_GLL_GL_UTIL_H +#define GX_GLL_GL_UTIL_H + +#include "gx/gll/GL.h" +#include + +uint32_t GLLTextureTypeToIndex(GLEnum textureType); + +#endif diff --git a/src/gx/gll/GLVertexArray.cpp b/src/gx/gll/GLVertexArray.cpp new file mode 100644 index 0000000..d45f3e6 --- /dev/null +++ b/src/gx/gll/GLVertexArray.cpp @@ -0,0 +1,189 @@ +#include "gx/gll/GLVertexArray.h" +#include "gx/gll/GLDevice.h" + +bool GLVertexArray::s_VertexArrayEnable = false; + +GLVertexArray::GLVertexArray(bool a2) { + // TODO +} + +void GLVertexArray::FindVertexArray(GLDevice* a1, GLVertexArray& a2) { + if (GLVertexArray::s_VertexArrayEnable) { + // TODO + } + + a2.ApplyVertexFormat(a1); +} + +void GLVertexArray::ApplyGLStates(GLStates::VertexArrayObject& vao) { + GLDevice* device = GLDevice::Get(); + + device->BindVertexArray(this); + + for (int32_t i = 0; i < kMAX_VERTEX_ATTRIBS; i++) { + auto& attrib = vao.vertexAttribs[i]; + + if (attrib.enable) { + glBindBuffer(attrib.buffer->m_Type, attrib.buffer->m_BufferID); + + glVertexAttribPointerARB( + i, + attrib.size, + attrib.type, + attrib.normalized, + attrib.stride, + reinterpret_cast(attrib.offset) + ); + + glEnableVertexAttribArrayARB(i); + } else { + glDisableVertexAttribArrayARB(i); + } + } + + if (vao.position.enable) { + glBindBuffer(vao.position.buffer->m_Type, vao.position.buffer->m_BufferID); + glVertexPointer(vao.position.size, vao.position.type, vao.position.stride, vao.position.offset); + glEnableClientState(GL_VERTEX_ARRAY); + } else { + glDisableClientState(GL_VERTEX_ARRAY); + } + + if (vao.normal.enable) { + glBindBuffer(vao.normal.buffer->m_Type, vao.normal.buffer->m_BufferID); + glNormalPointer(vao.normal.type, vao.normal.stride, vao.normal.offset); + glEnableClientState(GL_NORMAL_ARRAY); + } else { + glDisableClientState(GL_NORMAL_ARRAY); + } + + if (vao.color0.enable) { + glBindBuffer(vao.color0.buffer->m_Type, vao.color0.buffer->m_BufferID); + glColorPointer(vao.color0.size, vao.color0.type, vao.color0.stride, vao.color0.offset); + glEnableClientState(GL_COLOR_ARRAY); + } else { + glDisableClientState(GL_COLOR_ARRAY); + } + + if (vao.color1.enable) { + glBindBuffer(vao.color1.buffer->m_Type, vao.color1.buffer->m_BufferID); + glColorPointer(vao.color1.size, vao.color1.type, vao.color1.stride, vao.color1.offset); + glEnableClientState(GL_SECONDARY_COLOR_ARRAY); + } else { + glDisableClientState(GL_SECONDARY_COLOR_ARRAY); + } + + for (int32_t i = 0; i < 8; i++) { + glClientActiveTextureARB(GL_TEXTURE0 + i); + + if (vao.texCoord[i].enable) { + glBindBuffer(vao.texCoord[i].buffer->m_Type, vao.texCoord[0].buffer->m_BufferID); + glTexCoordPointer(vao.texCoord[i].size, vao.texCoord[i].type, vao.texCoord[i].stride, vao.texCoord[i].offset); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + } else { + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + } + + glBindBuffer(GL_ARRAY_BUFFER, vao.buffers[0]); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vao.buffers[1]); + glBindBuffer(GL_PIXEL_PACK_BUFFER, vao.buffers[2]); + glBindBuffer(GL_PIXEL_UNPACK_BUFFER, vao.buffers[3]); + + this->m_GLStates = vao; +} + +void GLVertexArray::ApplyVertexFormat(GLDevice* device) { + if (GLVertexArray::s_VertexArrayEnable) { + device->BindVertexArray(this); + } + + auto indexBuffer = this->m_Properties.m_IndexBuffer; + uint32_t indexBufferID = indexBuffer ? indexBuffer->m_BufferID : 0; + + if (this->m_GLStates.buffers[1] != indexBufferID) { + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID); + this->m_GLStates.buffers[1] = indexBufferID; + } + + BLIZZARD_ASSERT(this->GetProperties().m_VertexBufferFormat != nullptr); + + auto& properties = this->GetProperties(); + bool attribEnable[16] = {}; + + bool useVertexShader = device->GetShader(GLShader::eVertexShader) != nullptr; + + for (int32_t index = 0; index < this->GetProperties().m_VertexBufferFormat->m_Size; index++) { + BLIZZARD_ASSERT(index < kMAX_VERTEX_ATTRIBS); + + auto& attrib = this->GetProperties().m_VertexBufferFormat->m_Attribs[index]; + + BLIZZARD_ASSERT(attrib.type != GLVT_INVALID); + BLIZZARD_ASSERT(attrib.type < GLVT_NUM_VERTEX_TYPES); + + auto vertexBuffer = this->GetProperties().m_VertexBuffer[attrib.stream]; + + if (useVertexShader || attrib.slot - 1 > 1) { + if (this->m_GLStates.buffers[0] != vertexBuffer->m_BufferID) { + glBindBuffer(vertexBuffer->m_Type, vertexBuffer->m_BufferID); + this->m_GLStates.buffers[0] = vertexBuffer->m_BufferID; + } + + attribEnable[attrib.slot] = 1; + + int32_t stride = properties.m_VertexBufferStride[attrib.stream]; + int32_t offset = attrib.offset + + properties.m_VertexBufferOffset[attrib.stream] + + properties.m_VertexBase * stride; + + if (useVertexShader) { + glVertexAttribPointerARB( + attrib.slot, + k_VertexTypeInfo[attrib.type].m_Size, + k_VertexTypeInfo[attrib.type].m_Type, + k_VertexTypeInfo[attrib.type].m_Normalized, + stride, + reinterpret_cast(offset) + ); + } else { + // TODO + } + } + } + + for (int32_t s = 0; s < 16; s++) { + bool* prevAttribEnable; + + if (useVertexShader) { + prevAttribEnable = &this->m_GLStates.vertexAttribs[s].enable; + } else { + // TODO + } + + if (*prevAttribEnable != attribEnable[s]) { + if (attribEnable[s]) { + if (useVertexShader) { + glEnableVertexAttribArrayARB(s); + } else { + // TODO + } + } else { + if (useVertexShader) { + glDisableVertexAttribArrayARB(s); + } else { + // TODO + } + } + + *prevAttribEnable = attribEnable[s]; + } + } +} + +GLVertexArray::Properties& GLVertexArray::GetProperties() { + return this->m_Properties; +} + +void GLVertexArray::ReleaseObject() { + // TODO +} diff --git a/src/gx/gll/GLVertexArray.h b/src/gx/gll/GLVertexArray.h new file mode 100644 index 0000000..01c25be --- /dev/null +++ b/src/gx/gll/GLVertexArray.h @@ -0,0 +1,46 @@ +#ifndef GX_GLL_GL_VERTEX_ARRAY_H +#define GX_GLL_GL_VERTEX_ARRAY_H + +#include "gx/gll/GLBuffer.h" +#include "gx/gll/GLObject.h" +#include "gx/gll/GLVertexFormat.h" +#include + +class GLDevice; + +class GLVertexArray : public GLObject { + public: + // Types + struct Properties { + GLVertexFormat* m_VertexBufferFormat = nullptr; + GLBuffer* m_IndexBuffer = nullptr; + GLBuffer* m_PixelPackBuffer = nullptr; + GLBuffer* m_PixelUnpackBuffer = nullptr; + GLBuffer* m_VertexBuffer[4] = {}; + uint32_t m_VertexBase = 0; + uint32_t m_VertexBufferOffset[4] = {}; + uint32_t m_VertexBufferStride[4] = {}; + }; + + // Static variables + static bool s_VertexArrayEnable; + + // Static functions + static void FindVertexArray(GLDevice*, GLVertexArray&); + + // Member variables + Properties m_Properties; + GLStates::VertexArrayObject m_GLStates; + uint32_t m_VertexArrayID = 0; + + // Virtual member functions + virtual void ReleaseObject(); + + // Member functions + GLVertexArray(bool); + void ApplyGLStates(GLStates::VertexArrayObject&); + void ApplyVertexFormat(GLDevice*); + Properties& GetProperties(void); +}; + +#endif diff --git a/src/gx/gll/GLVertexFormat.h b/src/gx/gll/GLVertexFormat.h new file mode 100644 index 0000000..b7e6311 --- /dev/null +++ b/src/gx/gll/GLVertexFormat.h @@ -0,0 +1,13 @@ +#ifndef GX_GLL_GL_VERTEX_FORMAT_H +#define GX_GLL_GL_VERTEX_FORMAT_H + +#include "gx/gll/GLTypes.h" +#include + +class GLVertexFormat { + public: + uint32_t m_Size; + GLVertexAttrib m_Attribs[16]; +}; + +#endif diff --git a/src/gx/gll/GLVertexShader.cpp b/src/gx/gll/GLVertexShader.cpp new file mode 100644 index 0000000..fdbd4bd --- /dev/null +++ b/src/gx/gll/GLVertexShader.cpp @@ -0,0 +1,17 @@ +#include "gx/gll/GLVertexShader.h" +#include "gx/gll/GL.h" + +GLVertexShader* GLVertexShader::Create() { + // TODO + // GLPool stuff + + GLVertexShader* shader = new GLVertexShader(); + + shader->m_ShaderID = 0; + shader->m_RefCount = 1; + shader->m_ShaderType = eVertexShader; + shader->m_UsingGLSL = 0; + shader->var5 = GL_VERTEX_PROGRAM_ARB; + + return shader; +} diff --git a/src/gx/gll/GLVertexShader.h b/src/gx/gll/GLVertexShader.h new file mode 100644 index 0000000..5d970ab --- /dev/null +++ b/src/gx/gll/GLVertexShader.h @@ -0,0 +1,12 @@ +#ifndef GX_GLL_GL_VERTEX_SHADER_H +#define GX_GLL_GL_VERTEX_SHADER_H + +#include "gx/gll/GLShader.h" + +class GLVertexShader : public GLShader { + public: + // Static functions + static GLVertexShader* Create(void); +}; + +#endif diff --git a/src/gx/gll/GLWindow.h b/src/gx/gll/GLWindow.h new file mode 100644 index 0000000..80b7787 --- /dev/null +++ b/src/gx/gll/GLWindow.h @@ -0,0 +1,66 @@ +#ifndef GX_GLL_GL_WINDOW_H +#define GX_GLL_GL_WINDOW_H + +#include "gx/gll/GLAbstractWindow.h" +#include + +#ifdef __OBJC__ + #include + + @class GLLayerView; + @class GLFullscreenWindow; + @class NSEvent; +#else + #include + + typedef struct objc_object GLLayerView; + typedef struct objc_object GLFullscreenWindow; + typedef struct objc_object NSView; + typedef struct objc_object NSWindow; + typedef struct objc_object NSEvent; +#endif + +class GLContext; + +struct GLWindowCallbacks { + void (*OnResized)(int32_t, int32_t, bool); + void (*OnMouseDown)(int16_t, int32_t, int32_t); + void (*OnMouseMoved)(int32_t, int32_t); + void (*OnMouseUp)(int16_t, int32_t, int32_t); + void (*OnKeyDown)(NSEvent*); + void (*OnKeyUp)(NSEvent*); +}; + +class GLWindow : public GLAbstractWindow { + public: + // Member variables + // void** var0; // _vptr$GLAbstractWindow + GLContext* m_Context = nullptr; + NSWindow* m_Window = nullptr; + bool m_Shown = 0; + GLLayerView* m_View = nullptr; + Class m_ViewClass; + GLFullscreenWindow* m_FullscreenWindow = nullptr; + GLWindowCallbacks* m_Callbacks; + GLWindowCallbacks* m_ActiveCallbacks; + + // Member functions + bool CanEnterFullscreenMode(void); + void CreateView(void); + CGRect GetBackingRect(); + NSView* GetNSView(void); + CGRect GetRect(void); + void ExitFullscreenMode(void); + void Init(const CGRect&, GLWindowCallbacks*); + void Resize(uint32_t, uint32_t); + void SetOpenGLContext(GLContext*); + void SetViewClass(Class); + void Show(void); + void Sub70760(void); + + // Virtual member functions + virtual void SetCallbacks(GLWindowCallbacks*); + virtual void SetTitle(const char*); +}; + +#endif diff --git a/src/gx/gll/GLWindow.mm b/src/gx/gll/GLWindow.mm new file mode 100644 index 0000000..992fadc --- /dev/null +++ b/src/gx/gll/GLWindow.mm @@ -0,0 +1,224 @@ +#include "gx/gll/GLWindow.h" +#include "gx/gll/GLContext.h" +#include "gx/gll/GLLayerView.h" +#include "util/Autorelease.hpp" +#include + +GLWindowCallbacks DefaultCallbacks = { + // TODO +}; + +bool GLWindow::CanEnterFullscreenMode() { + return true; +} + +void GLWindow::CreateView() { + System_Autorelease::ScopedPool autorelease; + + BLIZZARD_ASSERT(this->m_View == nullptr); + + GLLayerView* v1 = [this->m_ViewClass alloc]; + NSView* v2 = [this->m_Window contentView]; + + NSRect v6 = v2.frame; + + this->m_View = [v1 initWithFrame: v6 glWindow: this]; + + [this->m_View setAutoresizingMask: 18]; + + [[this->m_Window contentView] addSubview: this->m_View]; + [this->m_Window makeFirstResponder: this->m_View]; + [this->m_Window setDelegate: this->m_View]; +} + +CGRect GLWindow::GetBackingRect() { + NSRect viewFrame = [this->m_Window contentView].frame; + NSRect backingFrame = [this->m_Window convertRectToBacking:viewFrame]; + + return CGRectMake( + 0.0f, + 0.0f, + backingFrame.size.width, + backingFrame.size.height + ); +} + +NSView* GLWindow::GetNSView() { + return (NSView*)this->m_View; +} + +CGRect GLWindow::GetRect() { + NSRect screenFrame = [[NSScreen screens] objectAtIndex: 0].frame; + NSRect viewFrame = [this->m_Window contentView].frame; + NSRect windowFrame = this->m_Window.frame; + + return CGRectMake( + windowFrame.origin.x, + screenFrame.size.height - (viewFrame.size.height + windowFrame.origin.y), + viewFrame.size.width, + viewFrame.size.height + ); +} + +void GLWindow::ExitFullscreenMode() { + // TODO +} + +void GLWindow::Init(const CGRect& a2, GLWindowCallbacks* a3) { + System_Autorelease::ScopedPool autorelease; + + this->SetCallbacks(a3); + + BLIZZARD_ASSERT(this->m_Window == nullptr); + + NSScreen* primaryScreen = [[NSScreen screens] objectAtIndex:0]; + + NSRect frame = primaryScreen.frame; + + NSRect contentRect = NSMakeRect( + a2.origin.x, + (frame.origin.y + frame.size.height) - (a2.origin.y + a2.size.height), + a2.size.width, + a2.size.height + ); + + NSWindow* window = [NSWindow alloc]; + + [window + initWithContentRect: contentRect + styleMask: 15 + backing: NSBackingStoreBuffered + defer: NO]; + + this->m_Window = window; + + [window + setAcceptsMouseMovedEvents: YES]; + + [window + setReleasedWhenClosed: NO]; + + [window + setBackgroundColor: [NSColor blackColor]]; + + BLIZZARD_ASSERT(this->m_Window != nullptr); + + // TODO + // return 1; +} + +void GLWindow::Resize(uint32_t width, uint32_t height) { + auto rect = this->GetRect(); + + if (std::floor(rect.size.width) != width || std::floor(rect.size.height) != height) { + auto size = CGSizeMake( + (static_cast(width / 65536) * 65536.0f) + static_cast(width), + (static_cast(height / 65536) * 65536.0f) + static_cast(height) + ); + + [this->m_Window setContentSize: size]; + } +} + +void GLWindow::SetCallbacks(GLWindowCallbacks* callbacks) { + if (callbacks) { + this->m_Callbacks = callbacks; + this->m_ActiveCallbacks = callbacks; + } else { + this->m_Callbacks = &DefaultCallbacks; + this->m_ActiveCallbacks = &DefaultCallbacks; + } +} + +void GLWindow::SetOpenGLContext(GLContext* a2) { + BLIZZARD_ASSERT(this->m_View != nullptr); + + System_Autorelease::ScopedPool autorelease; + + if (a2) { + if ([a2->m_Context->context view]) { + [a2->m_Context->context clearDrawable]; + } + + if (this->m_Context) { + [this->m_Context->m_Context->context clearDrawable]; + } + + NSOpenGLContext* currentContext = GLContext::GetNSOpenGLCurrentContext(); + + if (currentContext) { + [currentContext clearDrawable]; + } + + [a2->m_Context->context setView: this->m_View]; + + if (![a2->m_Context->context view]) { + puts("*** INVALID VIEW ***"); + } + + a2->Update(); + + this->m_Context = a2; + } else { + if (!this->m_Context) { + this->m_Context = a2; + } else { + [this->m_Context->m_Context->context clearDrawable]; + this->m_Context = nullptr; + } + } +} + +void GLWindow::SetTitle(const char* title) { + // TODO + // sub_BD280(v5); + + [this->m_Window + setTitle: [NSString stringWithUTF8String: title]]; + + + // TODO + // sub_BD2C0(v5); +} + +void GLWindow::SetViewClass(Class viewClass) { + if (![viewClass isSubclassOfClass: [GLLayerView class]]) { + // TODO + // sub_C2080(v3); + } + + this->m_ViewClass = viewClass; +} + +void GLWindow::Show() { + if (this->m_FullscreenWindow) { + [this->m_FullscreenWindow + makeKeyAndOrderFront: 0]; + [this->m_FullscreenWindow + makeFirstResponder: (NSView*)this->m_View]; + + this->m_Shown = true; + } else { + this->Sub70760(); + } +} + +void GLWindow::Sub70760() { + // TODO + // sub_BD280(v4); + + [this->m_Window + makeKeyAndOrderFront: 0]; + + if (this->GetNSView()) { + NSView* view = this->GetNSView(); + + [this->m_Window + makeFirstResponder: view]; + } + + this->m_Shown = true; + + // TODO + // sub_BD2C0(v4); +} diff --git a/src/gx/gll/GLWorker.cpp b/src/gx/gll/GLWorker.cpp new file mode 100644 index 0000000..18093b1 --- /dev/null +++ b/src/gx/gll/GLWorker.cpp @@ -0,0 +1,117 @@ +#include "gx/gll/GLWorker.h" +#include "gx/gll/GLCommand.h" +#include "gx/gll/GLDevice.h" + +void* GLWorker::Worker(void* userArg) { + GLWorker* worker = static_cast(userArg); + + GLDevice* device = new GLDevice(); + device->Init(nullptr, "GLWorker", 0x10, GLTF_INVALID); + + auto commandsEnd = worker->m_Commands.end(); + + while (!worker->m_UnkA0) { + while (1) { + pthread_mutex_lock(&worker->m_Mutex1); + + while (worker->m_Commands.begin() == commandsEnd) { + pthread_cond_wait(&worker->m_Cond1, &worker->m_Mutex1); + } + + pthread_mutex_lock(&worker->m_Mutex2); + + if (worker->m_Commands.begin() == commandsEnd) { + break; + } + + GLCommand* command = worker->m_Commands.front(); + worker->m_Commands.pop_front(); + + pthread_mutex_unlock(&worker->m_Mutex1); + + if (command) { + command->Execute(device); + delete command; + } else { + pthread_cond_signal(&worker->m_Cond2); + } + + pthread_mutex_unlock(&worker->m_Mutex2); + + if (worker->m_UnkA0) { + goto DONE; + } + } + + pthread_mutex_unlock(&worker->m_Mutex2); + pthread_mutex_unlock(&worker->m_Mutex1); + } + +DONE: + delete device; + return nullptr; +} + +GLWorker::GLWorker(GLDevice* device): m_Commands() { + this->m_UnkA0 = 0; + this->m_UnkA1 = 0; + this->m_Device = device; + + pthread_mutex_init(&this->m_Mutex1, nullptr); + pthread_cond_init(&this->m_Cond1, nullptr); + pthread_mutex_init(&this->m_Mutex2, nullptr); + pthread_cond_init(&this->m_Cond2, nullptr); + + pthread_create(&this->m_Thread, nullptr, &Worker, this); +} + +void GLWorker::Lock() { + pthread_mutex_lock(&this->m_Mutex1); +} + +void GLWorker::Send(GLCommand* command) { + this->m_Commands.push_back(command); + this->m_UnkA1 = 0; +} + +void GLWorker::Signal() { + pthread_cond_signal(&this->m_Cond1); +} + +void GLWorker::Unlock() { + pthread_mutex_unlock(&this->m_Mutex1); +} + +void GLWorker::WaitOnGLObjects() { + pthread_mutex_lock(&this->m_Mutex1); + + if (this->m_Commands.begin() == this->m_Commands.end()) { + GLFlush* command = new GLFlush(); + this->m_Commands.push_back(command); + + this->m_Commands.push_back(nullptr); + } else { + auto it = this->m_Commands.begin(); + + // TODO some kind of reordering logic for commands + + if (this->m_Commands.begin() == it) { + GLFlush* command = new GLFlush(); + this->m_Commands.push_back(command); + + this->m_Commands.push_back(nullptr); + } else { + GLFlush* command = new GLFlush(); + this->m_Commands.insert(it, command); + + this->m_Commands.insert(it, nullptr); + } + } + + pthread_cond_signal(&this->m_Cond1); + pthread_cond_wait(&this->m_Cond2, &this->m_Mutex1); + + this->m_UnkA1 = 1; + + pthread_mutex_unlock(&this->m_Mutex1); +} diff --git a/src/gx/gll/GLWorker.h b/src/gx/gll/GLWorker.h new file mode 100644 index 0000000..ff2a685 --- /dev/null +++ b/src/gx/gll/GLWorker.h @@ -0,0 +1,35 @@ +#ifndef GX_GLL_GL_WORKER_H +#define GX_GLL_GL_WORKER_H + +#include +#include + +class GLCommand; +class GLDevice; + +class GLWorker { + public: + // Static functions + static void* Worker(void*); + + // Member variables + GLDevice* m_Device; + std::list m_Commands; + pthread_mutex_t m_Mutex1; + pthread_mutex_t m_Mutex2; + pthread_cond_t m_Cond1; + pthread_cond_t m_Cond2; + pthread_t m_Thread; + bool m_UnkA0; + bool m_UnkA1; + + // Member functions + GLWorker(GLDevice*); + void Lock(void); + void Send(GLCommand*); + void Signal(void); + void Unlock(void); + void WaitOnGLObjects(void); +}; + +#endif diff --git a/src/gx/shader/CGxShader.cpp b/src/gx/shader/CGxShader.cpp new file mode 100644 index 0000000..60e6d10 --- /dev/null +++ b/src/gx/shader/CGxShader.cpp @@ -0,0 +1,26 @@ +#include "gx/shader/CGxShader.hpp" +#include "gx/Device.hpp" +#include "util/SFile.hpp" + +void CGxShader::Load(SFile* file) { + SFile::Read(file, &this->int3C, 4, nullptr, nullptr, nullptr); + SFile::Read(file, &this->int40, 4, nullptr, nullptr, nullptr); + SFile::Read(file, &this->int44, 2, nullptr, nullptr, nullptr); + SFile::Read(file, &this->int46, 2, nullptr, nullptr, nullptr); + + int32_t codeLen; + SFile::Read(file, &codeLen, 4, nullptr, nullptr, nullptr); + this->code.SetCount(codeLen); + SFile::Read(file, this->code.m_data, codeLen, 0, 0, 0); + + int32_t pad; + SFile::Read(file, &pad, ((codeLen + 3) & 0xFFFFFFFC) - codeLen, 0, 0, 0); +} + +int32_t CGxShader::Valid() { + if (!this->loaded) { + g_theGxDevicePtr->IShaderCreate(this); + } + + return this->valid; +} diff --git a/src/gx/shader/CGxShader.hpp b/src/gx/shader/CGxShader.hpp new file mode 100644 index 0000000..ff9dc26 --- /dev/null +++ b/src/gx/shader/CGxShader.hpp @@ -0,0 +1,31 @@ +#ifndef GX_SHADER_C_GX_SHADER_HPP +#define GX_SHADER_C_GX_SHADER_HPP + +#include "gx/Types.hpp" +#include + +class SFile; + +class CGxShader : public TSHashObject { + public: + // Member variables + uint32_t refCount = 0; + void* apiSpecific = nullptr; + int32_t target = 0; + int32_t int28 = 0; + int32_t valid = 0; + int32_t loaded = 0; + int32_t int34 = 0; + int32_t patched = 0; + int32_t int3C = 0; + int32_t int40 = 0; + int16_t int44 = 0; + int16_t int46 = 0; + TSGrowableArray code; + + // Member functions + void Load(SFile*); + int32_t Valid(void); +}; + +#endif diff --git a/src/gx/shader/CShaderEffect.cpp b/src/gx/shader/CShaderEffect.cpp new file mode 100644 index 0000000..366226d --- /dev/null +++ b/src/gx/shader/CShaderEffect.cpp @@ -0,0 +1,242 @@ +#include "gx/shader/CShaderEffect.hpp" +#include "gx/Device.hpp" +#include "gx/Gx.hpp" +#include "gx/RenderState.hpp" +#include "gx/Shader.hpp" +#include "gx/Transform.hpp" +#include "model/CM2Lighting.hpp" +#include +#include + +CShaderEffect* CShaderEffect::s_curEffect; +int32_t CShaderEffect::s_enableShaders; +C4Vector CShaderEffect::s_fogColorAlphaRef; +float CShaderEffect::s_fogMul; +C4Vector CShaderEffect::s_fogParams; +int32_t CShaderEffect::s_lightEnabled; +uint32_t CShaderEffect::s_localLightCount; +CShaderEffect::LocalLights CShaderEffect::s_localLights; +C3Vector CShaderEffect::s_sunAmbient; +C3Vector CShaderEffect::s_sunDiffuse; +C3Vector CShaderEffect::s_sunDir; +int32_t CShaderEffect::s_useAlphaRef; +int32_t CShaderEffect::s_usePcfFiltering; + +void CShaderEffect::ComputeLocalLights(LocalLights* localLights, uint32_t localLightsCount, CM2Light** lights, const C3Vector* a4) { + // TODO +} + +void CShaderEffect::InitShaderSystem(int32_t enableShaders, int32_t usePcf) { + CShaderEffect::s_enableShaders = enableShaders; + CShaderEffect::s_usePcfFiltering = enableShaders && usePcf ? 1 : 0; + CShaderEffect::s_fogMul = 1.0f; + + CShaderEffect::s_useAlphaRef = GxCaps()->int130; +} + +void CShaderEffect::SetAlphaRef(float alphaRef) { + CShaderEffect::s_fogColorAlphaRef.w = alphaRef; + + if (CShaderEffect::s_useAlphaRef) { + GxRsSet(GxRs_AlphaRef, static_cast(alphaRef * 255.0f)); + } else { + GxShaderConstantsSet(GxSh_Pixel, 2, reinterpret_cast(&CShaderEffect::s_fogColorAlphaRef), 1); + } +} + +void CShaderEffect::SetDiffuse(const C4Vector& diffuse) { + if (CShaderEffect::s_enableShaders) { + GxShaderConstantsSet(GxSh_Vertex, 28, reinterpret_cast(&diffuse), 1); + return; + } + + // TODO + // - non-shader code path +} + +void CShaderEffect::SetEmissive(const C4Vector& emissive) { + if (CShaderEffect::s_enableShaders) { + GxShaderConstantsSet(GxSh_Vertex, 29, reinterpret_cast(&emissive), 1); + return; + } + + // TODO non-shader code path +} + +void CShaderEffect::SetFogEnabled(int32_t fogEnabled) { + if (fogEnabled && GxMasterEnable(GxMasterEnable_Fog)) { + if (CShaderEffect::s_enableShaders && !GxCaps()->int138) { + GxShaderConstantsSet(GxSh_Vertex, 30, reinterpret_cast(&CShaderEffect::s_fogParams), 1); + } else { + GxRsSet(GxRs_Fog, 1); + } + } else { + if (CShaderEffect::s_enableShaders && !GxCaps()->int138) { + float fogParams[] = { 0.0f, 1.0f, 1.0f, 0.0f }; + GxShaderConstantsSet(GxSh_Vertex, 30, fogParams, 1); + } else { + GxRsSet(GxRs_Fog, 0); + } + } +} + +void CShaderEffect::SetFogParams(float fogStart, float fogEnd, float fogRate, const CImVector& fogColor) { + if (CShaderEffect::s_enableShaders) { + CShaderEffect::s_fogColorAlphaRef.x = fogColor.r / 255.0f; + CShaderEffect::s_fogColorAlphaRef.y = fogColor.g / 255.0f; + CShaderEffect::s_fogColorAlphaRef.z = fogColor.b / 255.0f; + + float v4 = 1.0f / (fogEnd - fogStart); + CShaderEffect::s_fogParams.x = -(CShaderEffect::s_fogMul * v4); + CShaderEffect::s_fogParams.y = fogEnd * v4; + CShaderEffect::s_fogParams.z = fogRate; + CShaderEffect::s_fogParams.w = 0.0f; + + if (!GxCaps()->int134) { + GxShaderConstantsSet(GxSh_Pixel, 2, reinterpret_cast(&CShaderEffect::s_fogColorAlphaRef), 1); + return; + } + } else { + GxRsSet(GxRs_FogStart, fogStart); + GxRsSet(GxRs_FogEnd, fogEnd); + } + + GxRsSet(GxRs_FogColor, fogColor.value); +} + +void CShaderEffect::SetLocalLighting(CM2Lighting* lighting, int32_t lightEnabled, const C3Vector* a3) { + CShaderEffect::s_lightEnabled = lightEnabled; + + if (!CShaderEffect::s_enableShaders) { + GxRsSet(GxRs_Lighting, lightEnabled); + } + + CShaderEffect::s_localLightCount = lighting ? lighting->m_lightCount : 0; + + if (!lightEnabled) { + return; + } + + if (CShaderEffect::s_enableShaders) { + CShaderEffect::s_sunDir = lighting->m_sunDir; + + if (CShaderEffect::s_sunDir.x != 0.0f || CShaderEffect::s_sunDir.y != 0.0f || CShaderEffect::s_sunDir.z != 0.0f) { + CShaderEffect::s_sunDir.Normalize(); + } + + CShaderEffect::s_sunAmbient = lighting->m_sunAmbient; + + CShaderEffect::s_sunDiffuse = { + std::min(lighting->m_sunDiffuse.x, 1.0f), + std::min(lighting->m_sunDiffuse.y, 1.0f), + std::min(lighting->m_sunDiffuse.z, 1.0f) + }; + + GxShaderConstantsSet(GxSh_Vertex, 10, reinterpret_cast(&CShaderEffect::s_sunDiffuse), 1); + GxShaderConstantsSet(GxSh_Vertex, 11, reinterpret_cast(&CShaderEffect::s_sunAmbient), 1); + GxShaderConstantsSet(GxSh_Vertex, 12, reinterpret_cast(&CShaderEffect::s_sunDir), 1); + + if (CShaderEffect::s_localLightCount) { + CShaderEffect::ComputeLocalLights( + &CShaderEffect::s_localLights, + CShaderEffect::s_localLightCount, + lighting->m_lights, + a3 + ); + + GxShaderConstantsSet(GxSh_Vertex, 17, reinterpret_cast(&CShaderEffect::s_localLights), 11); + } + + // TODO + // CShadowCache::SetShadowMapGenericInterior(lighting->m_flags & 0x8); + } else { + lighting->SetupGxLights(a3); + } +} + +void CShaderEffect::SetShaders(uint32_t vertexPermute, uint32_t pixelPermute) { + int32_t useAlphaRef = 1; + + if (CShaderEffect::s_enableShaders) { + GxRsSet(GxRs_VertexShader, CShaderEffect::s_curEffect->m_vertexShaders[vertexPermute]); + GxRsSet(GxRs_PixelShader, CShaderEffect::s_curEffect->m_pixelShaders[pixelPermute]); + + useAlphaRef = (pixelPermute & 0x8) == 0; + } + + if (CShaderEffect::s_useAlphaRef != useAlphaRef) { + CShaderEffect::s_useAlphaRef = useAlphaRef; + + if (useAlphaRef) { + GxRsSet(GxRs_AlphaRef, static_cast(CShaderEffect::s_fogColorAlphaRef.w * 255.0f)); + } else { + GxShaderConstantsSet(GxSh_Pixel, 2, reinterpret_cast(&CShaderEffect::s_fogColorAlphaRef), 1); + GxRsSet(GxRs_AlphaRef, 0); + } + } +} + +void CShaderEffect::SetTexMtx_Identity(uint32_t a1) { + if (CShaderEffect::s_enableShaders) { + float matrix[] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f + }; + + GxShaderConstantsSet(GxSh_Vertex, 2 * a1 + 6, matrix, 2); + } else { + // TODO + // - non-shader code path + } +} + +void CShaderEffect::SetTexMtx(const C44Matrix& matrix, uint32_t a2) { + // TODO +} + +void CShaderEffect::SetTexMtx_SphereMap(uint32_t a1) { + if (CShaderEffect::s_enableShaders) { + float matrix[] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f + }; + + GxShaderConstantsSet(GxSh_Vertex, 2 * a1 + 6, matrix, 2); + } else { + // TODO + // - non-shader code path + } +} + +void CShaderEffect::UpdateProjMatrix() { + if (!CShaderEffect::s_enableShaders) { + return; + } + + C44Matrix proj; + GxXformProjNativeTranspose(proj); + + GxShaderConstantsSet(GxSh_Vertex, 2, reinterpret_cast(&proj), 4); +} + +void CShaderEffect::InitEffect(const char* vsName, const char* psName) { + memset(this->m_vertexShaders, 0, sizeof(this->m_vertexShaders)); + memset(this->m_pixelShaders, 0, sizeof(this->m_pixelShaders)); + + // TODO + // this->dword18 = 0; + + if (CShaderEffect::s_enableShaders) { + if (vsName && psName) { + g_theGxDevicePtr->ShaderCreate(this->m_vertexShaders, GxSh_Vertex, "Shaders\\Vertex", vsName, 90); + g_theGxDevicePtr->ShaderCreate(this->m_pixelShaders, GxSh_Pixel, "Shaders\\Pixel", psName, 16); + } + } +} + +void CShaderEffect::SetCurrent() { + CShaderEffect::s_curEffect = this; + + // TODO + // - non-shader code path +} diff --git a/src/gx/shader/CShaderEffect.hpp b/src/gx/shader/CShaderEffect.hpp new file mode 100644 index 0000000..d810fe5 --- /dev/null +++ b/src/gx/shader/CShaderEffect.hpp @@ -0,0 +1,61 @@ +#ifndef GX_SHADER_C_SHADER_EFFECT_HPP +#define GX_SHADER_C_SHADER_EFFECT_HPP + +#include +#include + +class C3Vector; +class C4Vector; +class C44Matrix; +class CGxShader; +class CImVector; +class CM2Light; +class CM2Lighting; + +class CShaderEffect : public TSHashObject { + public: + // Structs + struct LocalLights { + float float0[44]; + }; + + // Static variables + static CShaderEffect* s_curEffect; + static int32_t s_enableShaders; + static C4Vector s_fogColorAlphaRef; + static float s_fogMul; + static C4Vector s_fogParams; + static int32_t s_lightEnabled; + static uint32_t s_localLightCount; + static LocalLights s_localLights; + static C3Vector s_sunAmbient; + static C3Vector s_sunDiffuse; + static C3Vector s_sunDir; + static int32_t s_useAlphaRef; + static int32_t s_usePcfFiltering; + + // Static functions + static void ComputeLocalLights(LocalLights* localLights, uint32_t localLightsCount, CM2Light** lights, const C3Vector* a4); + static void InitShaderSystem(int32_t enableShaders, int32_t usePcf); + static void SetAlphaRef(float alphaRef); + static void SetDiffuse(const C4Vector& diffuse); + static void SetEmissive(const C4Vector& emissive); + static void SetFogEnabled(int32_t fogEnabled); + static void SetFogParams(float fogStart, float fogEnd, float fogRate, const CImVector& fogColor); + static void SetLocalLighting(CM2Lighting* lighting, int32_t lightEnabled, const C3Vector* a3); + static void SetShaders(uint32_t vertexPermute, uint32_t pixelPermute); + static void SetTexMtx(const C44Matrix& matrix, uint32_t a2); + static void SetTexMtx_Identity(uint32_t a1); + static void SetTexMtx_SphereMap(uint32_t a1); + static void UpdateProjMatrix(void); + + // Member variables + CGxShader* m_vertexShaders[90]; + CGxShader* m_pixelShaders[16]; + + // Member functions + void InitEffect(const char* vsName, const char* psName); + void SetCurrent(void); +}; + +#endif diff --git a/src/gx/shader/CShaderEffectManager.cpp b/src/gx/shader/CShaderEffectManager.cpp new file mode 100644 index 0000000..1f6cc03 --- /dev/null +++ b/src/gx/shader/CShaderEffectManager.cpp @@ -0,0 +1,12 @@ +#include "gx/shader/CShaderEffectManager.hpp" +#include "gx/shader/CShaderEffect.hpp" + +TSHashTable CShaderEffectManager::s_shaderList; + +CShaderEffect* CShaderEffectManager::CreateEffect(const char* effectKey) { + return CShaderEffectManager::s_shaderList.New(effectKey, 0, 0); +} + +CShaderEffect* CShaderEffectManager::GetEffect(const char* effectKey) { + return CShaderEffectManager::s_shaderList.Ptr(effectKey); +} diff --git a/src/gx/shader/CShaderEffectManager.hpp b/src/gx/shader/CShaderEffectManager.hpp new file mode 100644 index 0000000..be559ef --- /dev/null +++ b/src/gx/shader/CShaderEffectManager.hpp @@ -0,0 +1,18 @@ +#ifndef GX_SHADER_C_SHADER_EFFECT_MANAGER_HPP +#define GX_SHADER_C_SHADER_EFFECT_MANAGER_HPP + +#include + +class CShaderEffect; + +class CShaderEffectManager { + public: + // Static variables + static TSHashTable s_shaderList; + + // Static functions + static CShaderEffect* CreateEffect(const char* effectKey); + static CShaderEffect* GetEffect(const char* effectKey); +}; + +#endif diff --git a/src/gx/texture/CBLPFile.cpp b/src/gx/texture/CBLPFile.cpp new file mode 100644 index 0000000..41c4e54 --- /dev/null +++ b/src/gx/texture/CBLPFile.cpp @@ -0,0 +1,202 @@ +#include "gx/Texture.hpp" +#include "gx/texture/CBLPFile.hpp" +#include "util/SFile.hpp" +#include +#include +#include + +TSGrowableArray CBLPFile::s_blpFileLoadBuffer; + +void CBLPFile::Close() { + this->m_inMemoryImage = nullptr; + + if (this->m_images) { + SMemFree(this->m_images, __FILE__, __LINE__, 0x0); + } + + this->m_images = nullptr; +} + +int32_t CBLPFile::Lock2(const char* fileName, PIXEL_FORMAT format, uint32_t mipLevel, unsigned char* data, uint32_t& stride) { + STORM_ASSERT(this->m_inMemoryImage); + + if (mipLevel && (!(this->m_header.hasMips & 0xF) || mipLevel >= this->m_numLevels)) { + return 0; + } + + unsigned char* mipData = static_cast(this->m_inMemoryImage) + this->m_header.mipOffsets[mipLevel]; + size_t mipSize = this->m_header.mipSizes[mipLevel]; + + switch (this->m_header.colorEncoding) { + case COLOR_PAL: + // TODO + return 0; + + case COLOR_DXT: + switch (format) { + case PIXEL_DXT1: + case PIXEL_DXT3: + case PIXEL_DXT5: + memcpy(data, mipData, mipSize); + return 1; + + case PIXEL_ARGB8888: + case PIXEL_ARGB1555: + case PIXEL_ARGB4444: + case PIXEL_RGB565: + // TODO + return 0; + + case PIXEL_ARGB2565: + return 0; + + default: + return 0; + } + + case COLOR_3: + memcpy(data, mipData, mipSize); + return 1; + + default: + return 0; + } +} + +int32_t CBLPFile::LockChain2(const char* fileName, PIXEL_FORMAT format, MipBits*& images, uint32_t mipLevel, int32_t a6) { + if (mipLevel && (!(this->m_header.hasMips & 0xF) || mipLevel >= this->m_numLevels)) { + return 0; + } + + if (images) { + if (a6 && (this->m_header.colorEncoding == COLOR_DXT || this->m_header.colorEncoding == COLOR_3)) { + if (this->m_header.colorEncoding == COLOR_3 || (format != PIXEL_ARGB4444 && format != PIXEL_RGB565 && format != PIXEL_ARGB1555 && format != PIXEL_ARGB8888)) { + uint32_t* offset = this->m_header.mipOffsets; + + for (int32_t i = 0; *offset; offset++, i++) { + void* address = static_cast(this->m_inMemoryImage) + *offset; + MipBits* image = static_cast(address); + reinterpret_cast(images)[i] = image; + } + + this->m_inMemoryImage = nullptr; + return 1; + } + } + + uint32_t v13 = this->m_header.height >> mipLevel; + + if (v13 <= 1) { + v13 = 1; + } + + uint32_t v14 = this->m_header.width >> mipLevel; + + if (v14 <= 1) { + v14 = 1; + } + + // TODO + // MippedImgSet(format, v14, v13, mipLevel); + } else { + uint32_t v9 = this->m_header.height >> mipLevel; + + if (v9 <= 1) { + v9 = 1; + } + + uint32_t v10 = this->m_header.width >> mipLevel; + + if (v10 <= 1) { + v10 = 1; + } + + images = MippedImgAllocA(format, v10, v9, __FILE__, __LINE__); + + if (!images) { + return 0; + } + } + + MipBits** ptr = reinterpret_cast(images); + + for (int32_t level = mipLevel, i = 0; level < this->m_numLevels; level++, i++) { + if (!this->Lock2(fileName, format, level, reinterpret_cast(ptr[i]), mipLevel)) { + return 0; + } + } + + this->m_inMemoryImage = nullptr; + + return 1; +} + +int32_t CBLPFile::Open(const char* filename, int32_t a3) { + if (!filename) { + // TODO + // nullsub_3(); + // SErrSetLastError(0x57u); + + return 0; + } + + this->m_inMemoryImage = nullptr; + + if (this->m_images) { + SMemFree(this->m_images, __FILE__, __LINE__, 0); + } + + size_t v8 = a3 != 0; + + this->m_images = nullptr; + + SFile* fileptr; + + if (!SFile::OpenEx(nullptr, filename, v8, &fileptr)) { + return 0; + } + + int32_t blpSize = SFile::GetFileSize(fileptr, 0); + CBLPFile::s_blpFileLoadBuffer.SetCount(blpSize); + + size_t bytesRead; + + SFile::Read( + fileptr, + CBLPFile::s_blpFileLoadBuffer.m_data, + CBLPFile::s_blpFileLoadBuffer.Count(), + &bytesRead, + 0, + 0 + ); + + SFile::Close(fileptr); + + return this->Source(CBLPFile::s_blpFileLoadBuffer.m_data); +} + +int32_t CBLPFile::Source(void* fileBits) { + this->m_inMemoryImage = nullptr; + + if (this->m_images) { + SMemFree(this->m_images, __FILE__, __LINE__, 0); + } + + this->m_images = nullptr; + this->m_inMemoryNeedsFree = 0; + this->m_inMemoryImage = fileBits; + + memcpy(&this->m_header, fileBits, sizeof(this->m_header)); + + if (this->m_header.magic != 0x32504C42 || this->m_header.formatVersion != 1) { + return 0; + } + + if (this->m_header.hasMips & 0xF) { + this->m_numLevels = CalcLevelCount(this->m_header.width, this->m_header.height); + } else { + this->m_numLevels = 1; + } + + return 1; +} diff --git a/src/gx/texture/CBLPFile.hpp b/src/gx/texture/CBLPFile.hpp new file mode 100644 index 0000000..4ad468b --- /dev/null +++ b/src/gx/texture/CBLPFile.hpp @@ -0,0 +1,70 @@ +#ifndef GX_TEXTURE_C_BLP_FILE_HPP +#define GX_TEXTURE_C_BLP_FILE_HPP + +#include "gx/Types.hpp" +#include "gx/texture/CGxTex.hpp" +#include +#include + +enum MipMapAlgorithm { + MMA_BOX = 0x0, + MMA_CUBIC = 0x1, + MMA_FULLDFT = 0x2, + MMA_KAISER = 0x3, + MMA_LINEARLIGHTKAISER = 0x4, +}; + +struct BlpPalPixel { + char b; + char g; + char r; + char pad; +}; + +class CBLPFile { + struct BLPHeader { + uint32_t magic; + uint32_t formatVersion; + char colorEncoding; + char alphaSize; + char preferredFormat; + char hasMips; + uint32_t width; + uint32_t height; + uint32_t mipOffsets[16]; + uint32_t mipSizes[16]; + + union { + BlpPalPixel palette[256]; + + struct { + uint32_t headerSize; + char headerData[1020]; + } jpeg; + } extended; + }; + + public: + // Static variables + static TSGrowableArray s_blpFileLoadBuffer; + + // Member variables + MipBits* m_images = nullptr; + BLPHeader m_header; + void* m_inMemoryImage = nullptr; + int32_t m_inMemoryNeedsFree; + uint32_t m_numLevels; + uint32_t m_quality = 100; + void* m_colorMapping; + MipMapAlgorithm m_mipMapAlgorithm = MMA_BOX; + char* m_lockDecompMem; + + // Member functions + void Close(void); + int32_t Lock2(const char*, PIXEL_FORMAT, uint32_t, unsigned char*, uint32_t&); + int32_t LockChain2(const char*, PIXEL_FORMAT, MipBits*&, uint32_t, int32_t); + int32_t Open(const char*, int32_t); + int32_t Source(void*); +}; + +#endif diff --git a/src/gx/texture/CGxTex.cpp b/src/gx/texture/CGxTex.cpp new file mode 100644 index 0000000..ef2c0dd --- /dev/null +++ b/src/gx/texture/CGxTex.cpp @@ -0,0 +1,59 @@ +#include "gx/texture/CGxTex.hpp" +#include "gx/Gx.hpp" +#include + +CGxTexFlags::CGxTexFlags(EGxTexFilter filter, uint32_t wrapU, uint32_t wrapV, uint32_t force, uint32_t generateMipMaps, uint32_t renderTarget, uint32_t maxAnisotropy) { + this->m_filter = filter; + this->m_wrapU = wrapU; + this->m_wrapV = wrapV; + this->m_forceMipTracking = force; + this->m_generateMipMaps = generateMipMaps; + this->m_renderTarget = renderTarget; + this->m_maxAnisotropy = std::min(maxAnisotropy, GxCaps()->m_maxTexAnisotropy); + + // TODO + this->m_bit13 = 0; + this->m_bit14 = 0; + this->m_bit15 = 0; +} + +bool CGxTexFlags::operator==(const CGxTexFlags& texFlags) { + return this->m_filter == texFlags.m_filter + && this->m_wrapU == texFlags.m_wrapU + && this->m_wrapV == texFlags.m_wrapV + && this->m_forceMipTracking == texFlags.m_forceMipTracking + && this->m_generateMipMaps == texFlags.m_generateMipMaps + && this->m_renderTarget == texFlags.m_renderTarget + && this->m_maxAnisotropy == texFlags.m_maxAnisotropy + && this->m_bit13 == texFlags.m_bit13 + && this->m_bit14 == texFlags.m_bit14 + && this->m_bit15 == texFlags.m_bit15; +} + +CGxTex::CGxTex(EGxTexTarget target, uint32_t width, uint32_t height, uint32_t depth, EGxTexFormat format, EGxTexFormat dataFormat, CGxTexFlags flags, void* userArg, void (*userFunc)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char* name) { + this->m_updateRect = { 0, 0, static_cast(height), static_cast(width) }; + this->m_target = target; + this->m_width = width; + this->m_height = height; + this->m_dataFormat = dataFormat; + this->m_format = format; + this->m_depth = depth; + this->m_userFunc = userFunc; + this->m_userArg = userArg; + this->m_flags = flags; + this->m_apiSpecificData = nullptr; + this->m_apiSpecificData2 = nullptr; + this->m_needsUpdate = 1; + this->m_needsFlagUpdate = 1; + this->m_needsCreation = 1; + + // TODO remaining constructor logic +} + +float CGxTex::GetHeight() { + return this->m_height; +} + +float CGxTex::GetWidth() { + return this->m_width; +} diff --git a/src/gx/texture/CGxTex.hpp b/src/gx/texture/CGxTex.hpp new file mode 100644 index 0000000..b85aa96 --- /dev/null +++ b/src/gx/texture/CGxTex.hpp @@ -0,0 +1,71 @@ +#ifndef GX_TEXTURE_C_GX_TEX_HPP +#define GX_TEXTURE_C_GX_TEX_HPP + +#include "gx/Types.hpp" +#include +#include + +class CGxTexFlags { + public: + // Member variables + uint32_t m_filter : 3; + uint32_t m_wrapU : 1; + uint32_t m_wrapV : 1; + uint32_t m_forceMipTracking : 1; + uint32_t m_generateMipMaps : 1; + uint32_t m_renderTarget : 1; + uint32_t m_maxAnisotropy : 5; + uint32_t m_bit13 : 1; + uint32_t m_bit14 : 1; + uint32_t m_bit15 : 1; + + // Member functions + CGxTexFlags() + : CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1) + {}; + CGxTexFlags(EGxTexFilter, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); + bool operator==(const CGxTexFlags& texFlags); +}; + +class CGxTexParms { + public: + // Member variables + EGxTexTarget target; + uint32_t width; + uint32_t height; + uint32_t depth; + EGxTexFormat format; + EGxTexFormat dataFormat; + CGxTexFlags flags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1); + void* userArg; + void (*userFunc)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&); +}; + +class CGxTex { + public: + // Member variables + CiRect m_updateRect = { 0, 0, 0, 0 }; + int16_t m_updatePlaneMin = -1; + int16_t m_updatePlaneMax = -1; + uint32_t m_width; + uint32_t m_height; + uint32_t m_depth; + EGxTexTarget m_target; + EGxTexFormat m_format; + EGxTexFormat m_dataFormat; + CGxTexFlags m_flags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1); + void* m_userArg; + void (*m_userFunc)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&); + void* m_apiSpecificData; + void* m_apiSpecificData2; // invented name + uint8_t m_needsUpdate; + uint8_t m_needsCreation; + uint8_t m_needsFlagUpdate; + + // Member functions + CGxTex(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexFormat, EGxTexFormat, CGxTexFlags, void*, void (*)(EGxTexCommand, uint32_t, uint32_t, uint32_t, uint32_t, void*, uint32_t&, const void*&), const char*); + float GetHeight(void); + float GetWidth(void); +}; + +#endif diff --git a/src/gx/texture/CTexture.cpp b/src/gx/texture/CTexture.cpp new file mode 100644 index 0000000..21b8741 --- /dev/null +++ b/src/gx/texture/CTexture.cpp @@ -0,0 +1,29 @@ +#include "gx/texture/CTexture.hpp" +#include "gx/Texture.hpp" + +EGxTexFilter CTexture::s_filterMode = GxTex_LinearMipNearest; +int32_t CTexture::s_maxAnisotropy = 1; + +bool HASHKEY_TEXTUREFILE::operator==(const HASHKEY_TEXTUREFILE& key) { + if (!SStrCmpI(this->m_filename, key.m_filename, STORM_MAX_PATH) && this->m_texFlags == key.m_texFlags) { + return true; + } + + return false; +} + +CTexture::CTexture() { + // TODO +} + +CTexture::~CTexture() { + if (this->gxTex) { + TextureFreeGxTex(this->gxTex); + } + + if (this->atlas) { + // TODO this->atlas->Free(this); + } + + // TODO +} diff --git a/src/gx/texture/CTexture.hpp b/src/gx/texture/CTexture.hpp new file mode 100644 index 0000000..7aca59d --- /dev/null +++ b/src/gx/texture/CTexture.hpp @@ -0,0 +1,52 @@ +#ifndef GX_TEXTURE_C_TEXTURE_HPP +#define GX_TEXTURE_C_TEXTURE_HPP + +#include "async/AsyncFile.hpp" +#include "gx/Types.hpp" +#include "gx/texture/CGxTex.hpp" +#include "util/CStatus.hpp" +#include +#include +#include + +class HASHKEY_TEXTUREFILE { + public: + // Member variables + char* m_filename; + CGxTexFlags m_texFlags; + + // Member functions + bool operator==(const HASHKEY_TEXTUREFILE&); +}; + +class CTexture : public CHandleObject, public TSHashObject { + public: + // Static variables + static EGxTexFilter s_filterMode; + static int32_t s_maxAnisotropy; + + // Member variables + uint32_t unk1; + uint16_t flags = 0; + uint8_t bestMip = 0; + uint8_t alphaBits = 0; + CStatus loadStatus; + CAsyncObject* asyncObject = nullptr; + CGxTex* gxTex = nullptr; + EGxTexTarget gxTexTarget = GxTex_2d; + uint16_t gxWidth = 0; + uint16_t gxHeight = 0; + EGxTexFormat gxTexFormat = GxTex_Unknown; + EGxTexFormat dataFormat = GxTex_Unknown; + CGxTexFlags gxTexFlags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1); + void* atlas = nullptr; // TODO CTextureAtlas + int32_t atlasBlockIndex = 0; + uint32_t unk2[2]; + char filename[260]; + + // Member functions + CTexture(); + ~CTexture(); +}; + +#endif diff --git a/src/math/CMakeLists.txt b/src/math/CMakeLists.txt new file mode 100644 index 0000000..b01efc0 --- /dev/null +++ b/src/math/CMakeLists.txt @@ -0,0 +1,10 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(math STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(math + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) diff --git a/src/math/Types.cpp b/src/math/Types.cpp new file mode 100644 index 0000000..33cc435 --- /dev/null +++ b/src/math/Types.cpp @@ -0,0 +1,7 @@ +#include "math/Types.hpp" +#include "math/Utils.hpp" +#include + +fixed16::operator float() const { + return this->n / 32767.0f; +} diff --git a/src/math/Types.hpp b/src/math/Types.hpp new file mode 100644 index 0000000..19c7893 --- /dev/null +++ b/src/math/Types.hpp @@ -0,0 +1,12 @@ +#ifndef MATH_TYPES_HPP +#define MATH_TYPES_HPP + +#include + +struct fixed16 { + int16_t n; + + explicit operator float() const; +}; + +#endif diff --git a/src/math/Utils.hpp b/src/math/Utils.hpp new file mode 100644 index 0000000..faef664 --- /dev/null +++ b/src/math/Utils.hpp @@ -0,0 +1,22 @@ +#ifndef MATH_UTILS_HPP +#define MATH_UTILS_HPP + +#include +#include + +#define WHOA_EPSILON_1 0.00000023841858 +#define WHOA_EPSILON_2 0.0000099999997 + +inline bool AreEqual(float a, float b, double epsilon) { + return std::abs(a - b) < epsilon; +} + +inline bool NotEqual(float a, float b, double epsilon) { + return std::abs(a - b) >= epsilon; +} + +inline float SignOf(float value) { + return value >= 0.0 ? 1.0f : -1.0f; +} + +#endif diff --git a/src/model/CM2Cache.cpp b/src/model/CM2Cache.cpp new file mode 100644 index 0000000..2e45beb --- /dev/null +++ b/src/model/CM2Cache.cpp @@ -0,0 +1,94 @@ +#include "model/CM2Cache.hpp" +#include "gx/Gx.hpp" +#include "model/CM2Shared.hpp" +#include "model/Model2.hpp" +#include "util/Filesystem.hpp" +#include "util/SFile.hpp" +#include +#include +#include +#include +#include + +CM2Cache CM2Cache::s_cache; + +void CM2Cache::BeginThread(void (*callback)(void*), void* arg) { + // TODO +} + +CM2Shared* CM2Cache::CreateShared(const char* path, uint32_t flags) { + char convertedPath[STORM_MAX_PATH]; + if (!M2ConvertModelFileName(path, convertedPath, STORM_MAX_PATH, flags)) { + return nullptr; + } + + char* ext = OsPathFindExtensionWithDot(convertedPath); + + CAaBox v28; + ModelBlobQuery(convertedPath, v28.b, v28.t); + + if (ext) { + *ext = '.'; + } + + // TODO + + SFile* fileptr; + + if (SFile::OpenEx(nullptr, convertedPath, (flags >> 2) & 1, &fileptr)) { + void* m = SMemAlloc(sizeof(CM2Shared), __FILE__, __LINE__, 0x0); + CM2Shared* shared = new (m) CM2Shared(this); + + if (shared->Load(fileptr, flags & 0x4, &v28)) { + strcpy(shared->m_filePath, convertedPath); + shared->ext = strrchr(shared->m_filePath, '.');; + + if (shared->ext > shared->m_filePath) { + // TODO + } + + // TODO + + return shared; + } + + SFile::Close(fileptr); + delete shared; + } + + return nullptr; +} + +void CM2Cache::GarbageCollect(int32_t a2) { + // TODO +} + +int32_t CM2Cache::Initialize(uint32_t flags) { + if (this->m_initialized) { + // TODO + + return 1; + } + + // TODO + + if (flags & 0x8) { + if (GxCaps()->m_vertexShaderTarget > GxShVS_none && GxCaps()->m_pixelShaderTarget > GxShPS_none) { + this->m_flags |= 0x8; + } + } + + // TODO + + this->m_initialized = 1; + + return 1; +} + +void CM2Cache::UpdateShared() { + // TODO +} + +void CM2Cache::WaitThread() { + // TODO +} diff --git a/src/model/CM2Cache.hpp b/src/model/CM2Cache.hpp new file mode 100644 index 0000000..3c12264 --- /dev/null +++ b/src/model/CM2Cache.hpp @@ -0,0 +1,26 @@ +#ifndef MODEL_C_M2_CACHE_HPP +#define MODEL_C_M2_CACHE_HPP + +#include + +class CM2Shared; + +class CM2Cache { + public: + // Static variables + static CM2Cache s_cache; + + // Member variables + uint32_t m_initialized = 0; + uint32_t m_flags = 0; + + // Member functions + void BeginThread(void (*callback)(void*), void* arg); + CM2Shared* CreateShared(const char*, uint32_t); + void GarbageCollect(int32_t a2); + int32_t Initialize(uint32_t flags); + void UpdateShared(void); + void WaitThread(void); +}; + +#endif diff --git a/src/model/CM2Light.cpp b/src/model/CM2Light.cpp new file mode 100644 index 0000000..6a0b63d --- /dev/null +++ b/src/model/CM2Light.cpp @@ -0,0 +1,78 @@ +#include "model/CM2Light.hpp" +#include "model/CM2Scene.hpp" + +void CM2Light::Initialize(CM2Scene* scene) { + this->m_scene = scene; + + // TODO +} + +void CM2Light::Link() { + if (!this->m_visible || !this->m_scene) { + return; + } + + if (this->m_type == M2LIGHT_1) { + // TODO + } else { + if (!(this->m_scene->m_flags & 0x1)) { + this->m_lightPrev = &this->m_scene->m_lightList; + this->m_lightNext = this->m_scene->m_lightList; + this->m_scene->m_lightList = this; + + if (this->m_lightNext) { + this->m_lightNext->m_lightPrev = &this->m_lightNext; + } + } + } +} + +void CM2Light::SetDirection(const C3Vector& dir) { + this->m_dir = dir; + + if (this->m_dir.SquaredMag() > 0.00000023841858) { + this->m_dir.Normalize(); + } +} + +void CM2Light::SetLightType(M2LIGHTTYPE lightType) { + if (this->m_type == lightType) { + return; + } + + this->m_type = lightType; + + if (this->m_visible && this->m_scene) { + this->Unlink(); + this->Link(); + } +} + +void CM2Light::SetVisible(int32_t visible) { + if (this->m_visible == visible) { + return; + } + + this->m_visible = visible; + + if (this->m_scene) { + if (this->m_visible) { + this->Link(); + } else { + this->Unlink(); + } + } +} + +void CM2Light::Unlink() { + if (this->m_lightPrev) { + *this->m_lightPrev = this->m_lightNext; + } + + if (this->m_lightNext) { + this->m_lightNext->m_lightPrev = this->m_lightPrev; + } + + this->m_lightPrev = nullptr; + this->m_lightNext = nullptr; +} diff --git a/src/model/CM2Light.hpp b/src/model/CM2Light.hpp new file mode 100644 index 0000000..ec30f53 --- /dev/null +++ b/src/model/CM2Light.hpp @@ -0,0 +1,35 @@ +#ifndef MODEL_C_M2_LIGHT_HPP +#define MODEL_C_M2_LIGHT_HPP + +#include "model/M2Types.hpp" +#include + +class CM2Scene; + +class CM2Light { + public: + // Member variables + CM2Scene* m_scene = nullptr; + int32_t m_type = 1; + C3Vector m_pos; + C3Vector m_dir; + C3Vector m_ambColor; + C3Vector m_dirColor; + C3Vector m_specColor; + float m_constantAttenuation = 0.0f; + float m_linearAttenuation = 0.69999999f; + float m_quadraticAttenuation = 0.029999999f; + int32_t m_visible = 0; + CM2Light** m_lightPrev = nullptr; + CM2Light* m_lightNext = nullptr; + + // Member functions + void Initialize(CM2Scene* scene); + void Link(); + void SetDirection(const C3Vector& dir); + void SetLightType(M2LIGHTTYPE lightType); + void SetVisible(int32_t visible); + void Unlink(); +}; + +#endif diff --git a/src/model/CM2Lighting.cpp b/src/model/CM2Lighting.cpp new file mode 100644 index 0000000..33d63d3 --- /dev/null +++ b/src/model/CM2Lighting.cpp @@ -0,0 +1,130 @@ +#include "model/CM2Lighting.hpp" +#include "model/CM2Light.hpp" +#include "model/CM2Scene.hpp" +#include + +void CM2Lighting::AddAmbient(const C3Vector& ambColor) { + this->m_sunAmbient = this->m_sunAmbient + ambColor; +} + +void CM2Lighting::AddDiffuse(const C3Vector& dirColor, const C3Vector& dir) { + C3Vector viewDir = dir; + + if (this->m_scene) { + viewDir = { + this->m_scene->m_view.a0 * dir.x + this->m_scene->m_view.b0 * dir.y + this->m_scene->m_view.c0 * dir.z, + this->m_scene->m_view.a1 * dir.x + this->m_scene->m_view.b1 * dir.y + this->m_scene->m_view.c1 * dir.z, + this->m_scene->m_view.a2 * dir.x + this->m_scene->m_view.b2 * dir.y + this->m_scene->m_view.c2 * dir.z + }; + } + + this->vector18.x = viewDir.x * dirColor.x + this->vector18.x; + this->vector18.y = viewDir.y * dirColor.x + this->vector18.y; + this->vector18.z = viewDir.z * dirColor.x + this->vector18.z; + + this->vector24.x = viewDir.x * dirColor.y + this->vector24.x; + this->vector24.y = viewDir.y * dirColor.y + this->vector24.y; + this->vector24.z = viewDir.z * dirColor.y + this->vector24.z; + + this->vector30.x = viewDir.x * dirColor.z + this->vector30.x; + this->vector30.y = viewDir.y * dirColor.z + this->vector30.y; + this->vector30.z = viewDir.z * dirColor.z + this->vector30.z; + + float v7 = dirColor.y * 0.71516001f + dirColor.x * 0.212671f + dirColor.z * 0.072168998f; + + this->vector3C.x = viewDir.x * v7 + this->vector3C.x; + this->vector3C.y = viewDir.y * v7 + this->vector3C.y; + this->vector3C.z = viewDir.z * v7 + this->vector3C.z; + + this->vector48.x = dirColor.x + this->vector48.x; + this->vector48.y = dirColor.y + this->vector48.y; + this->vector48.z = dirColor.z + this->vector48.z; + + this->m_sunDir = dir; + + this->m_sunDiffuse = dirColor; +} + +void CM2Lighting::AddLight(CM2Light* light) { + if (!light->m_visible) { + return; + } + + if (light->m_type == 1) { + // TODO + } else { + this->AddAmbient(light->m_ambColor); + this->AddDiffuse(light->m_dirColor, light->m_dir); + this->AddSpecular(light->m_specColor); + } +} + +void CM2Lighting::AddSpecular(const C3Vector& specColor) { + this->m_sunSpecular = this->m_sunSpecular + specColor; +} + +void CM2Lighting::CameraSpace() { + // TODO +} + +void CM2Lighting::Initialize(CM2Scene* scene, const CAaSphere& a3) { + memset(this, 0, sizeof(CM2Lighting)); + + this->m_scene = scene; + this->m_flags |= 0x20u; + this->sphere4 = a3; +} + +void CM2Lighting::SetFog(const C3Vector& fogColor, float fogStart, float fogEnd) { + this->m_fogStart = fogStart; + this->m_fogEnd = fogEnd; + this->m_fogScale = 1.0f / (fogEnd - fogStart); + this->m_fogDensity = 1.0f; + this->m_fogColor = fogColor; +} + +void CM2Lighting::SetFog(const C3Vector& fogColor, float fogStart, float fogEnd, float fogDensity) { + this->m_fogStart = fogStart; + this->m_fogEnd = fogEnd; + this->m_fogScale = 1.0f / (fogEnd - fogStart); + this->m_fogDensity = fogDensity; + this->m_fogColor = fogColor; +} + +void CM2Lighting::SetupGxLights(const C3Vector* a2) { + // TODO +} + +void CM2Lighting::SetupSunlight() { + if (this->m_flags & 0x2) { + return; + } + + this->m_sunDir.x = this->vector3C.x; + this->m_sunDir.y = this->vector3C.y; + this->m_sunDir.z = this->vector3C.z; + + if (this->m_sunDir.SquaredMag() <= 0.0000099999997) { + this->m_sunDir = { 0.0f, 0.0f, -1.0f }; + } else { + this->m_sunDir.Normalize(); + } + + float v6 = this->m_sunDir.z * this->vector18.z + this->m_sunDir.y * this->vector18.y + this->m_sunDir.x * this->vector18.x; + float v7 = this->m_sunDir.z * this->vector24.z + this->m_sunDir.y * this->vector24.y + this->m_sunDir.x * this->vector24.x; + float v8 = this->m_sunDir.z * this->vector30.z + this->m_sunDir.y * this->vector30.y + this->m_sunDir.x * this->vector30.x; + + this->m_sunDiffuse = { + v6 * 1.25f - this->vector48.x * 0.25f, + v7 * 1.25f - this->vector48.y * 0.25f, + v8 * 1.25f - this->vector48.z * 0.25f + }; + + this->m_sunAmbient = { + (this->vector48.x - v6) * 0.25f + this->m_sunAmbient.x, + (this->vector48.y - v7) * 0.25f + this->m_sunAmbient.y, + (this->vector48.z - v8) * 0.25f + this->m_sunAmbient.z + }; + + this->m_flags |= 0x2; +} diff --git a/src/model/CM2Lighting.hpp b/src/model/CM2Lighting.hpp new file mode 100644 index 0000000..13825a3 --- /dev/null +++ b/src/model/CM2Lighting.hpp @@ -0,0 +1,48 @@ +#ifndef MODEL_C_M2_LIGHTING_HPP +#define MODEL_C_M2_LIGHTING_HPP + +#include +#include +#include + +class CM2Light; +class CM2Scene; + +class CM2Lighting { + public: + // Member variables + CM2Scene* m_scene; + CAaSphere sphere4; + uint32_t m_flags; + C3Vector vector18; + C3Vector vector24; + C3Vector vector30; + C3Vector vector3C; + C3Vector vector48; + C3Vector m_sunAmbient; + C3Vector m_sunDiffuse; + C3Vector m_sunSpecular; + C3Vector m_sunDir; + CM2Light* m_lights[4]; + uint32_t m_lightCount; + float m_fogStart; + float m_fogEnd; + float m_fogScale; + float m_fogDensity; + C3Vector m_fogColor; + C4Plane m_liquidPlane; + + // Member functions + void AddAmbient(const C3Vector& ambColor); + void AddDiffuse(const C3Vector& dirColor, const C3Vector& dir); + void AddLight(CM2Light* light); + void AddSpecular(const C3Vector& specColor); + void CameraSpace(void); + void Initialize(CM2Scene* scene, const CAaSphere& a3); + void SetFog(const C3Vector& fogColor, float fogStart, float fogEnd); + void SetFog(const C3Vector& fogColor, float fogStart, float fogEnd, float fogDensity); + void SetupGxLights(const C3Vector* a2); + void SetupSunlight(void); +}; + +#endif diff --git a/src/model/CM2Model.cpp b/src/model/CM2Model.cpp new file mode 100644 index 0000000..0caf26f --- /dev/null +++ b/src/model/CM2Model.cpp @@ -0,0 +1,1505 @@ +#include "model/CM2Model.hpp" +#include "math/Types.hpp" +#include "model/CM2Scene.hpp" +#include "model/CM2Shared.hpp" +#include "model/M2Animate.hpp" +#include "model/M2Data.hpp" +#include "model/M2Model.hpp" +#include +#include +#include +#include +#include + +uint32_t CM2Model::s_loadingSequence = 0xFFFFFFFF; +uint8_t* CM2Model::s_sequenceBase; +uint32_t CM2Model::s_sequenceBaseSize; +uint32_t CM2Model::s_skinProfileBoneCountMax[] = { 256, 64, 53, 21 }; + +CM2Model* CM2Model::AllocModel(uint32_t* heapId) { + uint32_t memHandle; + void* object = nullptr; + + if (ObjectAlloc(*heapId, &memHandle, &object, 0)) { + CM2Model* model = new (object) CM2Model(); + + // TODO + // model->uint2E8 = memHandle; + + return model; + } + + return nullptr; +} + +bool CM2Model::Sub825E00(M2Data* data, uint32_t a2) { + if (data->sequenceIdxHashById.Count() == 0) { + for (int32_t i = 0; i < data->sequences.Count(); i++) { + auto& sequence = data->sequences[i]; + + if (sequence.id == a2) { + return i < data->sequences.Count(); + } + } + + return data->sequences.Count() > 0xFFFF; + } + + uint32_t v8 = a2 % data->sequenceIdxHashById.Count(); + uint16_t v5 = data->sequenceIdxHashById[v8]; + if (v5 == 0xFFFF) { + return data->sequences.Count() > 0xFFFF; + } + if (data->sequences[v5].id == a2) { + return v5 < data->sequences.Count(); + } + + int32_t v10 = 1; + while (1) { + v8 = (v8 + v10 * v10) % data->sequenceIdxHashById.Count(); + v5 = data->sequenceIdxHashById[v8]; + if (v5 == 0xFFFF) { + return data->sequences.Count() > 0xFFFF; + } + + ++v10; + + if (data->sequences[v5].id == a2) { + return v5 < data->sequences.Count(); + } + } +} + +uint16_t CM2Model::Sub8260C0(M2Data* data, uint32_t sequenceId, int32_t a3) { + // TODO + return -1; +} + +void CM2Model::Animate() { + // TODO +} + +void CM2Model::AnimateCamerasST() { + for (int32_t i = 0; i < this->m_shared->m_data->cameras.Count(); i++) { + auto& camera = this->m_shared->m_data->cameras[i]; + auto& modelCamera = this->m_cameras[i]; + + C3Vector v56 = modelCamera.positionTrack.currentValue + camera.positionPivot; + C3Vector cameraPos = (v56 * this->matrixF4) * this->m_scene->m_viewInv; + DataMgrSetCoord(modelCamera.m_camera, 7, cameraPos, 0x0); + + C3Vector v57 = modelCamera.targetTrack.currentValue + camera.targetPivot; + C3Vector targetPos = (v57 * this->matrixF4) * this->m_scene->m_viewInv; + DataMgrSetCoord(modelCamera.m_camera, 8, targetPos, 0x0); + + DataMgrSetFloat(modelCamera.m_camera, 5, modelCamera.rollTrack.currentValue); + } +} + +void CM2Model::AnimateMT(const C44Matrix* view, const C3Vector& a3, const C3Vector& a4, float a5, float a6) { + if (!this->m_loaded /* TODO other conditionals */) { + return; + } + + // TODO + + for (int32_t i = 0; i < this->m_shared->m_data->loops.Count(); i++) { + auto loopLength = this->m_shared->m_data->loops[i].length; + this->m_loops[i] = loopLength ? (this->m_scene->m_time - this->uint74) % loopLength : 0; + } + + this->matrixF4 = this->matrixB4 * *view; + + this->float88 = !this->m_attachParent || this->m_attachParent->m_flags & 0x1 + ? this->matrixF4.d2 * this->matrixF4.d2 + this->matrixF4.d1 * this->matrixF4.d1 + this->matrixF4.d0 * this->matrixF4.d0 + : this->m_attachParent->float88; + + C44Matrix v237; + C44Matrix v224; + C3Vector v236; + + // TODO + + uint32_t elapsedTime = 0; + if (this->m_time && this->m_scene->m_time) { + elapsedTime = this->m_scene->m_time - this->m_time; + this->m_time = this->m_scene->m_time; + } + + for (int32_t i = 0; i < this->m_shared->m_data->bones.Count(); i++) { + auto& bone = this->m_shared->m_data->bones[i]; + auto& modelBone = this->m_bones[i]; + + if (modelBone.sequence.uint8 == 0xFFFF) { + if (bone.parentIndex >= this->m_shared->m_data->bones.Count()) { + if (i != 0) { + modelBone.sequence.uint0 = this->m_bones[0].sequence.uint0; + modelBone.sequence.uint4 = this->m_bones[0].sequence.uint4; + modelBone.sequence.uint6 = this->m_bones[0].sequence.uint6; + } + } else { + modelBone.sequence.uint0 = this->m_bones[bone.parentIndex].sequence.uint0; + modelBone.sequence.uint4 = this->m_bones[bone.parentIndex].sequence.uint4; + modelBone.sequence.uint6 = this->m_bones[bone.parentIndex].sequence.uint6; + } + } else { + if (this->m_time) { + modelBone.sequence.uintC += elapsedTime; + modelBone.sequence.uint10 += elapsedTime; + } + + auto v45 = this->m_scene->m_time; + auto& v46 = this->m_shared->m_data->sequences[modelBone.sequence.uint8]; + uint32_t v47 = 0; + + if (v46.flags & 0x1) { + if (modelBone.sequence.uint10 - v45 <= 0) { + auto v234 = modelBone.sequence.uint10 - modelBone.sequence.uintC; + auto v235 = CMath::fuint(v234 * modelBone.sequence.float14); + v47 = modelBone.sequence.uint1C + v235; + v47 = std::min(v47, v46.duration); + } else { + if (modelBone.sequence.uintC - v45 > 0) { + v45 = modelBone.sequence.uintC; + } + + if (v46.duration) { + auto v234 = v45 - modelBone.sequence.uintC; + auto v235 = CMath::fuint(v234 * modelBone.sequence.float14); + v47 = (modelBone.sequence.uint1C + v235) % v46.duration; + } + } + } else { + if (v46.duration) { + auto v234 = v45 - modelBone.sequence.uintC; + auto v235 = CMath::fuint(v234 * modelBone.sequence.float14); + v47 = (modelBone.sequence.uint1C + v235) % v46.duration; + } + } + + modelBone.sequence.uint0 = v47; + modelBone.sequence.uint4 = modelBone.sequence.uint8; + modelBone.sequence.uint6 = i; + } + + // TODO + + uint32_t boneFlags = bone.flags | modelBone.flags; + + C44Matrix* boneParentMatrix; + + if (bone.parentIndex == 0xFFFF) { + boneParentMatrix = &this->matrixF4; + } else { + boneParentMatrix = &this->m_boneMatrices[bone.parentIndex]; + + if (boneFlags & (0x1 | 0x2 | 0x4)) { + // TODO + } + } + + if (boneFlags & (0x80 | 0x200)) { + C44Matrix boneLocalMatrix; + + if (bone.rotationTrack.sequenceTimes.Count()) { + auto& rotationTrack = bone.rotationTrack; + + if ( + rotationTrack.sequenceTimes.Count() > 1 + || (rotationTrack.sequenceTimes.Count() == 1 && rotationTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C4Quaternion defaultValue = { 0.0f, 0.0f, 0.0f, 1.0f }; + M2AnimateTrack(this, &modelBone, rotationTrack, modelBone.rotationTrack, defaultValue); + } + + boneLocalMatrix = C44Matrix(modelBone.rotationTrack.currentValue); + } else { + // TODO + } + + if (bone.scaleTrack.sequenceTimes.Count()) { + auto& scaleTrack = bone.scaleTrack; + + if ( + scaleTrack.sequenceTimes.Count() > 1 + || (scaleTrack.sequenceTimes.Count() == 1 && scaleTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C3Vector defaultValue = { 1.0f, 1.0f, 1.0f }; + M2AnimateTrack(this, &modelBone, scaleTrack, modelBone.scaleTrack, defaultValue); + } + + boneLocalMatrix.Scale(modelBone.scaleTrack.currentValue); + } + + // TODO + // conditional involving bone flags and a matrix member of M2ModelBone + + C3Vector translation; + + if (bone.translationTrack.sequenceTimes.Count()) { + auto& translationTrack = bone.translationTrack; + + if ( + translationTrack.sequenceTimes.Count() > 1 + || (translationTrack.sequenceTimes.Count() == 1 && translationTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C3Vector defaultValue = { 0.0f, 0.0f, 0.0f }; + M2AnimateTrack(this, &modelBone, translationTrack, modelBone.translationTrack, defaultValue); + } + + translation = modelBone.translationTrack.currentValue + bone.pivot; + } else { + translation = bone.pivot; + } + + boneLocalMatrix.d0 += translation.x; + boneLocalMatrix.d1 += translation.y; + boneLocalMatrix.d2 += translation.z; + + C3Vector negPivot = { + -bone.pivot.x, + -bone.pivot.y, + -bone.pivot.z + }; + + boneLocalMatrix.Translate(negPivot); + + this->m_boneMatrices[i] = boneLocalMatrix * *boneParentMatrix; + } else { + this->m_boneMatrices[i] = *boneParentMatrix; + } + + if (boneFlags & (0x8 | 0x10 | 0x20 | 0x40)) { + // TODO + } + + // TODO + } + + for (int32_t i = 0; i < this->m_shared->m_data->colors.Count(); i++) { + auto& color = this->m_shared->m_data->colors[i]; + auto& modelColor = this->m_colors[i]; + + auto& colorTrack = color.colorTrack; + if ( + colorTrack.sequenceTimes.Count() > 1 + || (colorTrack.sequenceTimes.Count() == 1 && colorTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C3Vector defaultValue = { 0.0f, 0.0f, 0.0f }; + M2AnimateTrack( + this, + this->m_bones, + color.colorTrack, + modelColor.colorTrack, + defaultValue + ); + } + + auto& alphaTrack = color.alphaTrack; + if ( + alphaTrack.sequenceTimes.Count() > 1 + || (alphaTrack.sequenceTimes.Count() == 1 && alphaTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + float defaultValue = 1.0f; + M2AnimateTrack( + this, + this->m_bones, + color.alphaTrack, + modelColor.alphaTrack, + defaultValue + ); + } + } + + for (int32_t i = 0; i < this->m_shared->m_data->textureWeights.Count(); i++) { + auto& textureWeight = this->m_shared->m_data->textureWeights[i]; + auto& modelTextureWeight = this->m_textureWeights[i]; + + auto& weightTrack = textureWeight.weightTrack; + if ( + weightTrack.sequenceTimes.Count() > 1 + || (weightTrack.sequenceTimes.Count() == 1 && weightTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + float defaultValue = 1.0f; + M2AnimateTrack( + this, + this->m_bones, + textureWeight.weightTrack, + modelTextureWeight.weightTrack, + defaultValue + ); + } + } + + // TODO + + for (int32_t i = 0; i < this->m_shared->m_data->lights.Count(); i++) { + auto& light = this->m_shared->m_data->lights[i]; + auto& modelLight = this->m_lights[i]; + + if (modelLight.uint64) { + uint8_t defaultValue = 1; + M2AnimateTrack( + this, + &this->m_bones[light.boneIndex], + light.visibilityTrack, + modelLight.visibilityTrack, + defaultValue + ); + } + + if ((modelLight.uint64 == 0 || modelLight.visibilityTrack.currentValue == 0) && this->uint90) { + continue; + } + + auto& ambientIntensityTrack = light.ambientIntensityTrack; + if ( + ambientIntensityTrack.sequenceTimes.Count() > 1 + || (ambientIntensityTrack.sequenceTimes.Count() == 1 && ambientIntensityTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + float defaultValue = 0.0f; + M2AnimateTrack( + this, + &this->m_bones[light.boneIndex], + light.ambientIntensityTrack, + modelLight.ambientIntensityTrack, + defaultValue + ); + } + + auto& ambientColorTrack = light.ambientColorTrack; + if ( + ambientColorTrack.sequenceTimes.Count() > 1 + || (ambientColorTrack.sequenceTimes.Count() == 1 && ambientColorTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C3Vector defaultValue = { 0.0f, 0.0f, 0.0f }; + M2AnimateTrack( + this, + &this->m_bones[light.boneIndex], + light.ambientColorTrack, + modelLight.ambientColorTrack, + defaultValue + ); + + float mul = modelLight.ambientIntensityTrack.currentValue * this->float198; + + modelLight.light.m_ambColor.x = modelLight.ambientColorTrack.currentValue.x * mul; + modelLight.light.m_ambColor.y = modelLight.ambientColorTrack.currentValue.y * mul; + modelLight.light.m_ambColor.z = modelLight.ambientColorTrack.currentValue.z * mul; + } + + auto& diffuseIntensityTrack = light.diffuseIntensityTrack; + if ( + diffuseIntensityTrack.sequenceTimes.Count() > 1 + || (diffuseIntensityTrack.sequenceTimes.Count() == 1 && diffuseIntensityTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + float defaultValue = 0.0f; + M2AnimateTrack( + this, + &this->m_bones[light.boneIndex], + light.diffuseIntensityTrack, + modelLight.diffuseIntensityTrack, + defaultValue + ); + } + + auto& diffuseColorTrack = light.diffuseColorTrack; + if ( + diffuseColorTrack.sequenceTimes.Count() > 1 + || (diffuseColorTrack.sequenceTimes.Count() == 1 && diffuseColorTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C3Vector defaultValue = { 0.0f, 0.0f, 0.0f }; + M2AnimateTrack( + this, + &this->m_bones[light.boneIndex], + light.diffuseColorTrack, + modelLight.diffuseColorTrack, + defaultValue + ); + + float mul = modelLight.diffuseIntensityTrack.currentValue * this->float198; + + modelLight.light.m_dirColor.x = modelLight.ambientColorTrack.currentValue.x * mul; + modelLight.light.m_dirColor.y = modelLight.ambientColorTrack.currentValue.y * mul; + modelLight.light.m_dirColor.z = modelLight.ambientColorTrack.currentValue.z * mul; + } + } + + for (int32_t i = 0; i < this->m_shared->m_data->cameras.Count(); i++) { + auto& camera = this->m_shared->m_data->cameras[i]; + auto& modelCamera = this->m_cameras[i]; + + auto& positionTrack = camera.positionTrack; + if ( + positionTrack.sequenceTimes.Count() > 1 + || (positionTrack.sequenceTimes.Count() == 1 && positionTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C3Vector defaultValue = { 0.0f, 0.0f, 0.0f }; + M2AnimateSplineTrack, C3Vector>( + this, + this->m_bones, + camera.positionTrack, + modelCamera.positionTrack, + defaultValue + ); + } + + auto& targetTrack = camera.targetTrack; + if ( + targetTrack.sequenceTimes.Count() > 1 + || (targetTrack.sequenceTimes.Count() == 1 && targetTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + C3Vector defaultValue = { 0.0f, 0.0f, 0.0f }; + M2AnimateSplineTrack, C3Vector>( + this, + this->m_bones, + camera.targetTrack, + modelCamera.targetTrack, + defaultValue + ); + } + + auto& rollTrack = camera.rollTrack; + if ( + rollTrack.sequenceTimes.Count() > 1 + || (rollTrack.sequenceTimes.Count() == 1 && rollTrack.sequenceTimes[0].times.Count() > this->uint90) + ) { + float defaultValue = 0.0f; + M2AnimateSplineTrack, float>( + this, + this->m_bones, + camera.rollTrack, + modelCamera.rollTrack, + defaultValue + ); + } + } + + // TODO +} + +void CM2Model::AnimateMTSimple(const C44Matrix* view, const C3Vector& a3, const C3Vector& a4, float a5, float a6) { + // TODO +} + +void CM2Model::AnimateST() { + if (!this->m_loaded) { + return; + } + + auto attachParent = this->m_attachParent; + + if (!attachParent) { + this->m_currentLighting = &this->m_lighting; + } else { + this->m_flag8000 = attachParent->m_flag8000; + + if (this->m_flag8000 && attachParent->m_flags & 0x1) { + this->m_currentLighting = &this->m_lighting; + } else { + this->m_currentLighting = attachParent->m_currentLighting; + } + } + + if (!this->m_currentLighting) { + this->m_currentLighting = &this->m_lighting; + } + + for (int32_t i = 0; i < this->m_shared->m_data->lights.Count(); i++) { + auto& light = this->m_shared->m_data->lights[i]; + auto& modelLight = this->m_lights[i]; + + int32_t visible = 0; + if (modelLight.uint64 && modelLight.visibilityTrack.currentValue) { + visible = 1; + + if (light.lightType == M2LIGHT_1) { + // TODO + } else { + float v10 = -this->m_boneMatrices[light.boneIndex].c0; + float v11 = -this->m_boneMatrices[light.boneIndex].c1; + float v12 = -this->m_boneMatrices[light.boneIndex].c2; + + float x = this->m_scene->m_viewInv.a0 * v10 + + this->m_scene->m_viewInv.b0 * v11 + + this->m_scene->m_viewInv.c0 * v12; + float y = this->m_scene->m_viewInv.a1 * v10 + + this->m_scene->m_viewInv.b1 * v11 + + this->m_scene->m_viewInv.c1 * v12; + float z = this->m_scene->m_viewInv.a2 * v10 + + this->m_scene->m_viewInv.b2 * v11 + + this->m_scene->m_viewInv.c2 * v12; + + C3Vector dir = { x, y, z }; + + modelLight.light.SetDirection(dir); + } + } + + modelLight.light.SetVisible(visible); + + // TODO modelLight.light.dword4 = this->m_scene->uint14; + } + + if (this->m_shared->m_data->cameras.Count()) { + this->AnimateCamerasST(); + } + + // TODO + + if (this->m_flag8) { + this->m_drawPrev = &this->m_scene->m_drawList; + this->m_drawNext = this->m_scene->m_drawList; + this->m_scene->m_drawList = this; + + if (this->m_drawNext) { + this->m_drawNext->m_drawPrev = &this->m_drawNext; + } + } + + // TODO +} + +void CM2Model::AttachToScene(CM2Scene* scene) { + this->DetachFromScene(); + + this->m_scene = scene; + + this->m_scenePrev = &this->m_scene->m_modelList; + this->m_sceneNext = this->m_scene->m_modelList; + this->m_scene->m_modelList = this; + if (this->m_sceneNext) { + this->m_sceneNext->m_scenePrev = &this->m_sceneNext; + } + + if (this->m_loaded) { + for (int32_t i = 0; i < this->m_shared->m_data->lights.Count(); i++) { + this->m_lights[i].light.Initialize(this->m_scene); + } + + // TODO + // - sequence / sequence fallback logic + } else { + for (auto modelCall = this->m_modelCallList; modelCall; modelCall = modelCall->modelCallNext) { + modelCall->time += this->m_scene->m_time; + } + } +} + +void CM2Model::CancelDeferredSequences(uint32_t boneIndex, bool a3) { + // TODO +} + +void CM2Model::DetachFromScene() { + // TODO +} + +void CM2Model::FindKey(M2ModelBoneSeq* sequence, const M2TrackBase& track, uint32_t& currentKey, uint32_t& nextKey, float& ratio) { + if (!track.sequenceTimes.Count()) { + nextKey = 0; + currentKey = 0; + ratio = 0.0f; + + return; + } + + uint32_t v6 = sequence->uint0; + uint32_t v7 = sequence->uint4; + + if (track.loopIndex == 0xFFFF) { + if (v7 >= track.sequenceTimes.Count()) { + v7 = 0; + } + } else { + v6 = this->m_loops[track.loopIndex]; + v7 = 0; + } + + uint32_t v12 = track.sequenceTimes[v7].times.Count(); + + if (v12 <= 1) { + nextKey = 0; + currentKey = 0; + ratio = 0.0f; + + return; + } + + if (currentKey >= v12) { + currentKey = 0; + } + + uint32_t v15 = currentKey; + auto& v24 = track.sequenceTimes[v7]; + auto v14 = v24.times.Data(); + auto v16 = v6 - v14[currentKey]; + uint32_t* v17; + uint32_t* v18; + uint32_t* v19; + uint32_t v20; + uint32_t v21; + + if (v16 >= 500) { + if (v16 < 0xFFFFFE0C) { + v15 = 0; + + if (v6 >= 500) { + v20 = v12; + + while (1) { + v21 = (v20 + v15) >> 1; + + if (v6 >= v14[v21]) { + v15 = v21 + 1; + + if (v21 + 1 >= v12 || v6 < v14[v21 + 1]) { + v15 = v21; + goto LABEL_36; + } + } else { + v20 = v21 - 1; + } + + if (v15 >= v20) { + goto LABEL_36; + } + } + } + + v19 = v14 + 1; + + do { + if (*v19 > v6) { + break; + } + + ++v15; + ++v19; + } while (v15 < v12 - 1); + } else if (v15) { + v18 = &v14[v15]; + + do { + if (*v18 <= v6) { + break; + } + + --v15; + --v18; + } while (v15); + } + } else if (v15 < v12 - 1) { + v17 = &v14[v15 + 1]; + + do { + if (*v17 > v6) { + break; + } + + ++v15; + ++v17; + } while (v15 < v12 - 1); + } + +LABEL_36: + + if (v15 + 1 >= v24.times.Count()) { + nextKey = v15; + currentKey = v15; + ratio = 0.0f; + } else { + currentKey = v15; + nextKey = v15 + 1; + + uint32_t* v22 = &v24.times[v15]; + float v23 = static_cast(v6 - v22[0]); + float v25 = static_cast(v22[1] - v22[0]); + ratio = v23 / v25; + } +} + +CAaBox& CM2Model::GetBoundingBox(CAaBox& bounds) { + // TODO + // WaitForLoad + + bounds = this->m_shared->m_data->bounds.extent; + + return bounds; +} + +HCAMERA CM2Model::GetCameraByIndex(uint32_t index) { + if (!this->m_loaded) { + this->WaitForLoad("GetCameraByIndex"); + } + + return this->m_cameras[index].m_camera; +} + +C3Vector CM2Model::GetPosition() { + return reinterpret_cast(this->matrixF4.d0) * this->m_scene->m_viewInv; +} + +int32_t CM2Model::Initialize(CM2Scene* scene, CM2Shared* shared, CM2Model* a4, uint32_t flags) { + this->AttachToScene(scene); + + // TODO + // this->dword30[23] = this->m_scene->dwordC; + + this->m_shared = shared; + this->m_shared->AddRef(); + + if (a4) { + a4->m_refCount++; + } + + this->m_flags = flags; + + this->m_modelCallTail = &this->m_modelCallList; + + this->uint74 = this->m_scene->m_time; + + // TODO + + return this->m_shared->CallbackWhenLoaded(this); +} + +int32_t CM2Model::InitializeLoaded() { + if (!this->m_shared->m_m2DataLoaded || !this->m_shared->m_skinProfileLoaded) { + return 1; + } + + uint32_t dataSize + = (sizeof(M2ModelBone) * this->m_shared->m_data->bones.Count()) + + (sizeof(uint32_t) * this->m_shared->m_data->loops.Count()) + + (sizeof(uint32_t) * this->m_shared->skinProfile->skinSections.Count()) + + (sizeof(M2ModelColor) * this->m_shared->m_data->colors.Count()) + + (sizeof(HTEXTURE) * this->m_shared->m_data->textures.Count()) + + (sizeof(M2ModelTextureWeight) * this->m_shared->m_data->textureWeights.Count()) + + (sizeof(M2ModelTextureTransform) * this->m_shared->m_data->textureTransforms.Count()) + + (sizeof(M2ModelAttachment) * this->m_shared->m_data->attachments.Count()) + + (sizeof(M2ModelLight) * this->m_shared->m_data->lights.Count()) + + (sizeof(M2ModelCamera) * this->m_shared->m_data->cameras.Count()); + + // TODO + // allocate space for particles and ribbons + + char* data = static_cast(SMemAlloc(dataSize, __FILE__, __LINE__, 0)); + + if (this->m_shared->m_data->bones.Count()) { + this->m_bones = reinterpret_cast(&data[0]); + data += (sizeof(M2ModelBone) * this->m_shared->m_data->bones.Count()); + + for (int32_t i = 0; i < this->m_shared->m_data->bones.Count(); i++) { + new (&this->m_bones[i]) M2ModelBone(); + } + + for (int32_t i = 0; i < this->m_shared->m_data->bones.Count(); i++) { + this->m_bones[i].flags = this->m_shared->m_data->bones[i].flags; + } + + // TODO use A16 allocator + this->m_boneMatrices = static_cast(SMemAlloc(sizeof(C44Matrix) * this->m_shared->m_data->bones.Count(), __FILE__, __LINE__, 0)); + + for (int32_t i = 0; i < this->m_shared->m_data->bones.Count(); i++) { + new (&this->m_boneMatrices[i]) C44Matrix(); + } + } + + if (this->m_shared->m_data->loops.Count()) { + this->m_loops = reinterpret_cast(&data[0]); + data += (sizeof(uint32_t) * this->m_shared->m_data->loops.Count()); + + for (int32_t i = 0; i < this->m_shared->m_data->loops.Count(); i++) { + if (this->m_loops[i]) { + this->m_loops[i] = 0; + } + } + } + + // TODO + + if (this->m_shared->m_data->colors.Count()) { + this->m_colors = reinterpret_cast(&data[0]); + data += (sizeof(M2ModelColor) * this->m_shared->m_data->colors.Count()); + + for (int32_t i = 0; i < this->m_shared->m_data->colors.Count(); i++) { + new (&this->m_colors[i]) M2ModelColor(); + } + } + + if (this->m_shared->m_data->textures.Count()) { + this->m_textures = reinterpret_cast(&data[0]); + data += (sizeof(HTEXTURE) * this->m_shared->m_data->textures.Count()); + + for (int32_t i = 0; i < this->m_shared->m_data->textures.Count(); i++) { + HTEXTURE textureHandle = this->model30 + ? this->model30->m_textures[i] + : this->m_shared->textures[i]; + + this->m_textures[i] = textureHandle + ? HandleDuplicate(textureHandle) + : nullptr; + } + } + + if (this->m_shared->m_data->textureWeights.Count()) { + this->m_textureWeights = reinterpret_cast(&data[0]); + data += (sizeof(M2ModelTextureWeight) * this->m_shared->m_data->textureWeights.Count()); + + for (int32_t i = 0; i < this->m_shared->m_data->textureWeights.Count(); i++) { + new (&this->m_textureWeights[i]) M2ModelTextureWeight(); + } + } + + // TODO + + if (this->m_shared->m_data->lights.Count()) { + this->m_lights = reinterpret_cast(&data[0]); + data += (sizeof(M2ModelLight) * this->m_shared->m_data->lights.Count()); + + for (int32_t i = 0; i < this->m_shared->m_data->lights.Count(); i++) { + new (&this->m_lights[i]) M2ModelLight(); + + auto& light = this->m_shared->m_data->lights[i]; + auto& modelLight = this->m_lights[i]; + + modelLight.light.Initialize(this->m_scene); + modelLight.light.SetLightType(static_cast(light.lightType)); + modelLight.ambientIntensityTrack.currentValue = 1.0f; + modelLight.diffuseIntensityTrack.currentValue = 1.0f; + modelLight.visibilityTrack.currentValue = 1; + } + } + + if (this->m_shared->m_data->cameras.Count()) { + this->m_cameras = reinterpret_cast(&data[0]); + data += (sizeof(M2ModelCamera) * this->m_shared->m_data->cameras.Count()); + + for (int32_t i = 0; i < this->m_shared->m_data->cameras.Count(); i++) { + new (&this->m_cameras[i]) M2ModelCamera(); + } + + for (int32_t i = 0; i < this->m_shared->m_data->cameras.Count(); i++) { + auto& camera = this->m_shared->m_data->cameras[i]; + auto cameraHandle = CameraCreate(); + + if (camera.fieldOfView <= 0.0f || camera.fieldOfView >= 3.1415927f || camera.farClip <= camera.nearClip) { + break; + } + + DataMgrSetFloat(cameraHandle, 4, camera.fieldOfView); + DataMgrSetFloat(cameraHandle, 3, camera.nearClip); + DataMgrSetFloat(cameraHandle, 2, camera.farClip); + + this->m_cameras[i].m_camera = cameraHandle; + } + } + + // TODO + + this->m_loaded = 1; + + uint32_t savedTime = this->m_scene->m_time; + + while (this->m_modelCallList) { + auto modelCall = this->m_modelCallList; + + this->m_scene->m_time = modelCall->time; + + switch (modelCall->type) { + case 0: { + // TODO + break; + } + + case 1: { + // TODO + break; + } + + case 2: { + // TODO + break; + } + + case 3: { + // TODO + break; + } + + case 4: { + // TODO + break; + } + + case 5: { + this->SetBoneSequence( + modelCall->args[0], + modelCall->args[1], + modelCall->args[2], + modelCall->args[3], + *reinterpret_cast(&modelCall->args[4]), + modelCall->args[5], + modelCall->args[6] + ); + + break; + } + + case 6: { + // TODO + break; + } + + case 7: { + // TODO + break; + } + + case 8: { + // TODO + break; + } + + case 9: { + // TODO + break; + } + + case 10: { + // TODO + break; + } + + case 11: { + // TODO + break; + } + + case 12: { + // TODO + break; + } + + case 13: { + // TODO + break; + } + + case 14: { + // TODO + break; + } + } + + this->m_modelCallList = modelCall->modelCallNext; + + if (modelCall->type == 0) { + HTEXTURE texture = reinterpret_cast(&modelCall->args[1]); + + if (texture) { + HandleClose(texture); + } + } + + SMemFree(modelCall); + } + + this->m_scene->m_time = savedTime; + + this->UpdateLoaded(); + this->m_flag800 = 0; + + return 1; +} + +int32_t CM2Model::IsBatchDoodadCompatible(M2Batch* batch) { + // TODO + + return 0; +} + +int32_t CM2Model::IsDrawable(int32_t a2, int32_t a3) { + // TODO + + return 1; +} + +int32_t CM2Model::IsLoaded(int32_t a2, int32_t attachments) { + if (this->m_flags & 0x20) { + if (this->m_loaded) { + return 1; + } + + if (a2) { + this->WaitForLoad(nullptr); + } + + return this->m_loaded && this->m_shared->m_m2DataLoaded && this->m_shared->m_skinProfileLoaded; + } + + if (!this->m_loaded && a2) { + this->WaitForLoad(nullptr); + } + + if (!this->m_loaded) { + return 0; + } + + if (!attachments || this->m_flag100) { + return 1; + } + + // TODO + + return 0; +} + +void CM2Model::LinkToCallbackListTail() { + this->m_callbackPrev = this->m_shared->m_callbackListTail; + this->m_callbackNext = nullptr; + *this->m_shared->m_callbackListTail = this; + this->m_shared->m_callbackListTail = &this->m_callbackNext; +} + +int32_t CM2Model::ProcessCallbacks() { + // TODO + return 1; +} + +void CM2Model::ProcessCallbacksRecursive() { + if (!this->m_loaded) { + return; + } + + this->m_refCount++; + + if (this->ProcessCallbacks()) { + // TODO process attachments + } + + this->Release(); +} + +void CM2Model::Release() { + // TODO +} + +void CM2Model::SetAnimating(int32_t animating) { + if (!animating) { + if (this->m_animatePrev) { + *this->m_animatePrev = this->m_animateNext; + + if (this->m_animateNext) { + this->m_animateNext->m_animatePrev = this->m_animatePrev; + } + + this->m_animatePrev = nullptr; + this->m_animateNext = nullptr; + } + + return; + } + + if (this->m_flags & 0x20 && !this->m_loaded) { + this->WaitForLoad(nullptr); + } + + if (!this->m_animatePrev) { + this->m_animatePrev = &this->m_scene->m_animateList; + this->m_animateNext = this->m_scene->m_animateList; + this->m_scene->m_animateList = this; + + if (this->m_animateNext) { + this->m_animateNext->m_animatePrev = &this->m_animateNext; + } + } +} + +void CM2Model::SetBoneSequence(uint32_t boneId, uint32_t sequenceId, uint32_t a4, uint32_t time, float a6, int32_t a7, int32_t a8) { + if (sequenceId == -1) { + this->UnsetBoneSequence(boneId, a7, a8); + return; + } + + if (!this->m_loaded) { + auto m = SMemAlloc(sizeof(CM2ModelCall), __FILE__, __LINE__, 0x0); + auto modelCall = new (m) CM2ModelCall(); + + modelCall->type = 5; + modelCall->modelCallNext = nullptr; + modelCall->time = this->m_scene->m_time; + modelCall->args[0] = boneId; + modelCall->args[1] = sequenceId; + modelCall->args[2] = a4; + modelCall->args[3] = time; + *reinterpret_cast(&modelCall->args[4]) = a6; + modelCall->args[5] = a7; + modelCall->args[6] = a8; + + *this->m_modelCallTail = modelCall; + this->m_modelCallTail = &modelCall->modelCallNext; + + return; + } + + if (this->m_flag800) { + a7 = 0; + } + + uint16_t boneIndex; + if (boneId == -1) { + boneIndex = 0; + } else if (boneId < this->m_shared->m_data->boneIndicesById.Count()) { + boneIndex = this->m_shared->m_data->boneIndicesById[boneId]; + } else { + boneIndex = -1; + } + + if (boneIndex >= this->m_shared->m_data->bones.Count()) { + return; + } + + M2SequenceFallback fallback; + this->Sub826350(fallback, sequenceId); + int32_t v33 = a4 == -1; + + uint16_t v15 = CM2Model::Sub8260C0(this->m_shared->m_data, fallback.uint0, a4 != -1 ? a4 : 0); + uint32_t v16 = v15; + uint32_t v32 = v15; + uint32_t v17; + + if (v15 != 0xFFFF) { + if (!v33) { + goto LABEL_30; + } + + goto LABEL_29; + } + + v17 = this->m_shared->m_data->sequenceIdxHashById.Count(); + v33 = 1; + v32 = v17; + uint16_t v18; + + if (v17) { + uint32_t v20 = fallback.uint0 % v17; + v18 = this->m_shared->m_data->sequenceIdxHashById[v20]; + + if (v18 != 0xFFFF) { + uint32_t v21 = 1; + + if (this->m_shared->m_data->sequences[v18].id != fallback.uint0) { + while (1) { + v20 = (v20 + v21 * v21) % v32; + v18 = this->m_shared->m_data->sequenceIdxHashById[v20]; + + if (v18 == 0xFFFF) { + break; + } + + ++v21; + + if (this->m_shared->m_data->sequences[v18].id == fallback.uint0) { + goto LABEL_26; + } + } + + v32 = 0xFFFF; + + goto LABEL_29; + } + + goto LABEL_26; + } + + v32 = 0xFFFF; + } else { + v18 = 0; + + if (this->m_shared->m_data->sequences.Count()) { + while (this->m_shared->m_data->sequences[v18].id != fallback.uint0) { + ++v18; + + if (v18 >= this->m_shared->m_data->sequences.Count()) { + goto LABEL_20; + } + } + +LABEL_26: + v32 = v18; + goto LABEL_29; + } + +LABEL_20: + v32 = 0xFFFF; + } + +LABEL_29: + this->Sub826E60(&a4, &v32); + v16 = v32; + +LABEL_30: + if (this->m_shared->m_data->sequences[v16].flags & 0x20) { + if (this->Sub8269C0(boneId, boneIndex)) { + this->CancelDeferredSequences(boneIndex, a8 != 0); + + auto& modelBone = this->m_bones[boneIndex]; + + if (a8) { + modelBone.uint90 = sequenceId; + modelBone.uint94 = a4; + + this->SetPrimaryBoneSequence(v16, boneIndex, fallback, time, a6, a7); + modelBone.sequence.uintB = v33; + } else { + this->SetSecondaryBoneSequence(v16, boneIndex, fallback, time, a6); + modelBone.secondarySequence.uintB = v33; + } + } + } else { + this->SetBoneSequenceDeferred(v16, this->m_shared->m_data, boneIndex, time, a6, fallback, a7, a8, v33); + } +} + +void CM2Model::SetBoneSequenceDeferred(uint16_t a2, M2Data* data, uint16_t boneIndex, uint32_t time, float a6, M2SequenceFallback fallback, int32_t a8, int32_t a9, int32_t a10) { + // TODO +} + +void CM2Model::SetIndices() { + // TODO +} + +void CM2Model::SetLightingCallback(void (*lightingCallback)(CM2Model*, CM2Lighting*, void*), void* lightingArg) { + this->m_lightingCallback = lightingCallback; + this->m_lightingArg = lightingArg; +} + +void CM2Model::SetLoadedCallback(void (*loadedCallback)(CM2Model*, void*), void* loadedArg) { + this->m_loadedCallback = loadedCallback; + this->m_loadedArg = loadedArg; + + this->UpdateLoaded(); +} + +void CM2Model::SetPrimaryBoneSequence(uint16_t sequenceIndex, uint16_t boneIndex, M2SequenceFallback fallback, uint32_t time, float a6, int32_t a7) { + auto& modelBone = this->m_bones[boneIndex]; + auto& sequence = this->m_shared->m_data->sequences[sequenceIndex]; + + if (a7) { + if (!modelBone.sequence.uintA || sequenceIndex != modelBone.sequence.uint8) { + double v10; + double v11; + double v12; + + if (modelBone.secondarySequence.uint8 == 0xFFFF + || ((v10 = (double)(modelBone.uint9C - this->m_scene->m_time) * modelBone.floatA0, v10 >= 0.0) ? (v10 <= 1.0 ? (v11 = v10 * ((3.0 - (v10 + v10)) * v10)) : (v11 = 1.0)) : (v11 = 0.0), v11 * modelBone.floatA4 <= 0.5) + ) { + memcpy(&modelBone.secondarySequence, &modelBone.sequence, sizeof(modelBone.secondarySequence)); + + modelBone.uint9C = this->m_scene->m_time + sequence.blendtime; + if (sequence.blendtime) { + v12 = 1.0 / (double)sequence.blendtime; + } else { + v12 = 1.0; + } + modelBone.floatA0 = v12; + modelBone.floatA4 = 1.0f; + } + } + } else { + modelBone.secondarySequence.uint8 = -1; + } + + this->SetupBoneSequence(sequenceIndex, fallback, time, a6, &modelBone.sequence); + + int32_t v13 = modelBone.sequence.uint10; + if (modelBone.sequence.uintC == v13 || ((sequence.flags & 0x1) != 0 && (v13 -= this->m_scene->m_time, v13 <= 0))) { + modelBone.sequence.uintA = 1; + } + + // TODO + // if (!modelBone.dword98) { + // modelBone.dword98 = (DWORD)&this->dword14; + // v14 = this->dword14; + // v15 = &smodelBone.word96; + // *v15 = v14; + // if (v14 != 0xFFFF) { + // this->m_bones[v14].dword98 = v15; + // } + // LOWORD(v13) = a3; + // LOWORD(this->dword14) = a3; + // } +} + +void CM2Model::SetSecondaryBoneSequence(uint16_t a2, uint16_t boneIndex, M2SequenceFallback fallback, uint32_t time, float a6) { + // TODO +} + +void CM2Model::SetupBoneSequence(uint16_t sequenceIndex, M2SequenceFallback fallback, uint32_t a4, float a5, M2ModelBoneSeq* boneSequence) { + auto& sequence = this->m_shared->m_data->sequences[sequenceIndex]; + + int32_t v9 = rand(); + uint32_t v10 = (sequence.replay.l + (sequence.replay.h - sequence.replay.l) * v9 / 0x8000 == 0) + + sequence.replay.l + (sequence.replay.h - sequence.replay.l) * v9 / 0x8000; + int32_t v11 = v10 * sequence.duration; + + double v12; + double v13; + long double v15; + + if (fallback.uint2 == 1 || fallback.uint2 == 3) { + v12 = -a5; + } else { + v12 = a5; + } + + v13 = 0.0; + + uint32_t v18 = 0; + if (v12 < 0.0) { + v18 = v11; + } + + if (fallback.uint2 == 2 || fallback.uint2 == 3) { + v12 = 0.0; + } + + if (abs(v12) > 0.0000099999997) { + v13 = 1.0 / v12; + } + + v15 = abs(v13); + uint32_t v16 = this->m_scene->m_time - floor((double)a4 * v15); + + if ((~(this->m_scene->m_flags >> 2) & 0x1) != 0) { + v16++; + } + + boneSequence->uint8 = sequenceIndex; + boneSequence->uintC = v16; + boneSequence->uint20 = v10; + boneSequence->uintA = 0; + boneSequence->uint10 = v16 + floor(v15 * (double)(unsigned int)v11); + boneSequence->uint1C = v18; + boneSequence->float14 = v12; + boneSequence->float18 = v13; +} + +void CM2Model::SetupLighting() { + if (!this->m_attachParent || this->m_attachParent->m_flags & 0x1) { + this->Animate(); + + CAaSphere sphere; + sphere.c = this->GetPosition(); + sphere.r = 0.0f; + + this->m_lighting.Initialize(this->m_scene, sphere); + this->m_scene->SelectLights(&this->m_lighting); + + if (this->m_lightingCallback) { + this->m_lightingCallback(this, &this->m_lighting, this->m_lightingArg); + } + + this->m_lighting.SetupSunlight(); + this->m_lighting.CameraSpace(); + } + + // TODO + // for (auto model = this->model58; model; model = model->model60) { + // model->SetupLighting(); + // } +} + +void CM2Model::SetVisible(int32_t visible) { + if (this->m_attachParent) { + this->m_flag80 = visible ? 1 : 0; + } else { + this->m_flag8 = visible ? 1 : 0; + } +} + +void CM2Model::SetWorldTransform(const C3Vector& position, float orientation, float scale) { + C44Matrix(this->matrixB4); + + this->matrixB4.RotateAroundZ(orientation); + this->matrixB4.Scale(scale); + this->matrixB4.d0 = position.x; + this->matrixB4.d1 = position.y; + this->matrixB4.d2 = position.z; + + this->m_flag8000 = 1; +} + +void CM2Model::Sub826350(M2SequenceFallback& fallback, uint32_t sequenceId) { + auto data = this->m_shared->m_data; + + int32_t v12; + if (CM2Model::Sub825E00(data, 0)) { + v12 = 0; + } else if (CM2Model::Sub825E00(data, 147)) { + v12 = 147; + } else { + v12 = data->sequences[0].id; + } + + uint32_t v10[506]; + memset(v10, 0, sizeof(v10)); + + if (CM2Model::Sub825E00(data, sequenceId)) { + fallback.uint0 = sequenceId; + fallback.uint2 = 0; + return; + } + + // TODO +} + +int32_t CM2Model::Sub8269C0(uint32_t boneId, uint16_t boneIndex) { + // TODO + return 1; +} + +void CM2Model::Sub826E60(uint32_t* a2, uint32_t* a3) { + // TODO +} + +void CM2Model::UnlinkFromCallbackList() { + if (this->m_callbackPrev) { + *this->m_callbackPrev = this->m_callbackNext; + + if (this->m_callbackNext) { + this->m_callbackNext->m_callbackPrev = this->m_callbackPrev; + } else { + this->m_shared->m_callbackListTail = this->m_callbackPrev; + } + + this->m_callbackPrev = nullptr; + this->m_callbackNext = nullptr; + } +} + +void CM2Model::UnsetBoneSequence(uint32_t boneId, int32_t a3, int32_t a4) { + // TODO +} + +void CM2Model::UpdateLoaded() { + auto model = this; + + while (model) { + if (!model->IsLoaded(0, !(this->m_flags & 0x20))) { + break; + } + + if (model->m_loadedCallback) { + model->m_loadedCallback(this, model->m_loadedArg); + model->m_loadedCallback = nullptr; + } + + model = model->m_attachParent; + } +} + +void CM2Model::WaitForLoad(const char* a2) { + // TODO +} diff --git a/src/model/CM2Model.hpp b/src/model/CM2Model.hpp new file mode 100644 index 0000000..8b3609a --- /dev/null +++ b/src/model/CM2Model.hpp @@ -0,0 +1,180 @@ +#ifndef MODEL_C_M2_MODEL_HPP +#define MODEL_C_M2_MODEL_HPP + +#include "gx/Camera.hpp" +#include "gx/Texture.hpp" +#include "model/CM2Lighting.hpp" +#include +#include +#include + +class CAaBox; +class CM2Scene; +class CM2Shared; +class M2Batch; +class M2Data; +class M2ModelBone; +class M2ModelBoneSeq; +class M2ModelCamera; +class M2ModelColor; +class M2ModelLight; +class M2ModelTextureWeight; +class M2SequenceFallback; +class M2TrackBase; + +struct CM2ModelCall { + uint32_t type = -1; + CM2ModelCall* modelCallNext; + uint32_t time; + uint32_t args[8]; +}; + +class CM2Model { + public: + // Static variables + static uint32_t s_loadingSequence; + static uint8_t* s_sequenceBase; + static uint32_t s_sequenceBaseSize; + static uint32_t s_skinProfileBoneCountMax[]; + + // Static functions + static CM2Model* AllocModel(uint32_t* heapId); + static bool Sub825E00(M2Data* data, uint32_t a2); + static uint16_t Sub8260C0(M2Data* data, uint32_t sequenceId, int32_t a3); + + // Member variables + uint32_t m_refCount = 1; + uint32_t m_flags = 0; + CM2Model** m_scenePrev = nullptr; + CM2Model* m_sceneNext = nullptr; + uint32_t m_loaded : 1; + uint32_t m_flag2 : 1; + uint32_t m_flag4 : 1; + uint32_t m_flag8 : 1; + uint32_t m_flag10 : 1; + uint32_t m_flag20 : 1; + uint32_t m_flag40 : 1; + uint32_t m_flag80 : 1; + uint32_t m_flag100 : 1; + uint32_t m_flag200 : 1; + uint32_t m_flag400 : 1; + uint32_t m_flag800 : 1; + uint32_t m_flag1000 : 1; + uint32_t m_flag2000 : 1; + uint32_t m_flag4000 : 1; + uint32_t m_flag8000 : 1; + uint32_t m_flag10000 : 1; + uint32_t m_flag20000 : 1; + uint32_t m_flag40000 : 1; + uint32_t m_flag80000 : 1; + uint32_t m_flag100000 : 1; + uint32_t m_flag200000 : 1; + uint32_t m_flag400000 : 1; + CM2Model** m_callbackPrev = nullptr; + CM2Model* m_callbackNext = nullptr; + void (*m_loadedCallback)(CM2Model*, void*) = nullptr; + void* m_loadedArg = nullptr; + CM2Scene* m_scene = nullptr; + CM2Shared* m_shared = nullptr; + CM2Model* model30 = nullptr; + CM2ModelCall* m_modelCallList = nullptr; + CM2ModelCall** m_modelCallTail = nullptr; + CM2Model** m_animatePrev = nullptr; + CM2Model* m_animateNext = nullptr; + CM2Model* m_attachParent = nullptr; + uint32_t m_time = 0; + CM2Model** m_drawPrev = nullptr; + CM2Model* m_drawNext = nullptr; + uint32_t* m_loops = nullptr; + uint32_t uint74 = 0; + float float88 = 0.0f; + uint32_t uint90 = 0; + M2ModelBone* m_bones = nullptr; + C44Matrix* m_boneMatrices = nullptr; + M2ModelColor* m_colors = nullptr; + HTEXTURE* m_textures = nullptr; + M2ModelTextureWeight* m_textureWeights = nullptr; + C44Matrix* m_textureMatrices = nullptr; + C44Matrix matrixB4; + C44Matrix matrixF4; + float float198 = 1.0f; + float alpha19C = 1.0f; + C3Vector m_currentDiffuse = { 1.0f, 1.0f, 1.0f }; + C3Vector m_currentEmissive = { 0.0f, 0.0f, 0.0f }; + M2ModelLight* m_lights; + CM2Lighting m_lighting; + CM2Lighting* m_currentLighting = nullptr; + void (*m_lightingCallback)(CM2Model*, CM2Lighting*, void*) = nullptr; + void* m_lightingArg = nullptr; + M2ModelCamera* m_cameras = nullptr; + void* ptr2D0 = nullptr; + + // Member functions + CM2Model() + : m_loaded(0) + , m_flag2(0) + , m_flag4(0) + , m_flag8(0) + , m_flag10(0) + , m_flag20(0) + , m_flag40(1) + , m_flag80(0) + , m_flag100(1) + , m_flag200(1) + , m_flag400(0) + , m_flag800(0) + , m_flag1000(0) + , m_flag2000(0) + , m_flag4000(0) + , m_flag8000(0) + , m_flag10000(0) + , m_flag20000(0) + , m_flag40000(0) + , m_flag80000(0) + , m_flag100000(0) + , m_flag200000(0) + , m_flag400000(0) + {}; + void Animate(void); + void AnimateCamerasST(void); + void AnimateMT(const C44Matrix* view, const C3Vector& a3, const C3Vector& a4, float a5, float a6); + void AnimateMTSimple(const C44Matrix* view, const C3Vector& a3, const C3Vector& a4, float a5, float a6); + void AnimateST(void); + void AttachToScene(CM2Scene* scene); + void CancelDeferredSequences(uint32_t boneIndex, bool a3); + void DetachFromScene(void); + void FindKey(M2ModelBoneSeq* sequence, const M2TrackBase& track, uint32_t& currentKey, uint32_t& nextKey, float& ratio); + CAaBox& GetBoundingBox(CAaBox& bounds); + HCAMERA GetCameraByIndex(uint32_t index); + C3Vector GetPosition(void); + int32_t Initialize(CM2Scene* scene, CM2Shared* shared, CM2Model* a4, uint32_t flags); + int32_t InitializeLoaded(void); + int32_t IsBatchDoodadCompatible(M2Batch* batch); + int32_t IsDrawable(int32_t a2, int32_t a3); + int32_t IsLoaded(int32_t a2, int32_t attachments); + void LinkToCallbackListTail(void); + int32_t ProcessCallbacks(); + void ProcessCallbacksRecursive(void); + void Release(void); + void SetAnimating(int32_t animating); + void SetBoneSequence(uint32_t boneId, uint32_t sequenceId, uint32_t a4, uint32_t time, float a6, int32_t a7, int32_t a8); + void SetBoneSequenceDeferred(uint16_t a2, M2Data* data, uint16_t boneIndex, uint32_t time, float a6, M2SequenceFallback fallback, int32_t a8, int32_t a9, int32_t a10); + void SetIndices(void); + void SetLightingCallback(void (*lightingCallback)(CM2Model*, CM2Lighting*, void*), void* lightingArg); + void SetLoadedCallback(void (*loadedCallback)(CM2Model*, void*), void* loadedArg); + void SetPrimaryBoneSequence(uint16_t sequenceIndex, uint16_t boneIndex, M2SequenceFallback fallback, uint32_t time, float a6, int32_t a7); + void SetSecondaryBoneSequence(uint16_t a2, uint16_t boneIndex, M2SequenceFallback fallback, uint32_t time, float a6); + void SetupBoneSequence(uint16_t sequenceIndex, M2SequenceFallback fallback, uint32_t a4, float a5, M2ModelBoneSeq* boneSequence); + void SetupLighting(void); + void SetVisible(int32_t visible); + void SetWorldTransform(const C3Vector& position, float orientation, float scale); + void Sub826350(M2SequenceFallback& fallback, uint32_t sequenceId); + int32_t Sub8269C0(uint32_t boneId, uint16_t boneIndex); + void Sub826E60(uint32_t* a2, uint32_t* a3); + void UnlinkFromCallbackList(void); + void UnsetBoneSequence(uint32_t boneId, int32_t a3, int32_t a4); + void UpdateLoaded(void); + void WaitForLoad(const char* a2); +}; + +#endif diff --git a/src/model/CM2Scene.cpp b/src/model/CM2Scene.cpp new file mode 100644 index 0000000..e5fdc7b --- /dev/null +++ b/src/model/CM2Scene.cpp @@ -0,0 +1,718 @@ +#include "model/CM2Scene.hpp" +#include "gx/Shader.hpp" +#include "gx/Transform.hpp" +#include "model/CM2Cache.hpp" +#include "model/CM2Light.hpp" +#include "model/CM2Model.hpp" +#include "model/CM2SceneRender.hpp" +#include "model/CM2Shared.hpp" +#include "model/M2Internal.hpp" +#include "model/M2Sort.hpp" +#include +#include +#include + +uint32_t CM2Scene::s_optFlags = 0xFFFFFFFF; + +void CM2Scene::AnimateThread(void* arg) { + // TODO +} + +void CM2Scene::ComputeElementShaders(M2Element* element) { + auto model = element->model; + auto batch = element->batch; + auto material = &model->m_shared->m_data->materials[batch->materialIndex]; + auto lighting = model->m_currentLighting; + + int32_t shaded; + int32_t lightCount; + + if (material->flags & 0x1 || CM2SceneRender::s_shadedList[material->blendMode] == 0) { + shaded = 0; + lightCount = material->flags & 0x1 ? 0 : lighting->m_lightCount; + } else { + shaded = 1; + lightCount = lighting->m_lightCount; + } + + int32_t boneInfluences = element->skinSection->boneInfluences; + + int32_t v18; + if (material->blendMode == M2BLEND_OPAQUE) { + v18 = 0; + } else if (material->blendMode == M2BLEND_ALPHA_KEY) { + v18 = CMath::fuint(element->alpha * 224.0f); + } else { + v18 = 1; + } + + int32_t v8 = 0; + if (!(material->flags & 0x1) && !(material->flags & 0x100) && lighting->m_flags & 0x10) { + // TODO + // v8 = Sub873FF0(); + + if (v8) { + if (lighting->m_flags & 0x8) { + v8 = 1; + } + + if (element->type == 1) { + v8 = 0; + } + } + } + + int32_t v9 = v18 && (/* TODO !GxCaps()->dword130 ||*/ v8); + int32_t v10 = std::min(boneInfluences, 2); + int32_t v11 = std::min(v8, 2); + + element->vertexPermute = shaded + 2 * (v11 + v10 + 2 * v11 + lightCount + 4 * (v11 + v10 + 2 * v11)); + element->pixelPermute = v8 + 4 * (CShaderEffect::s_usePcfFiltering + 2 * v9); + + // TODO + // element->dword3C = v8; +} + +int32_t CM2Scene::SortOpaque(uint32_t a, uint32_t b, const void* userArg) { + auto elements = static_cast(userArg)->m_elements.Ptr(); + auto elementA = const_cast(&elements[a]); + auto elementB = const_cast(&elements[b]); + + if (elementA->type < elementB->type) { + return -1; + } + + if (elementA->type > elementB->type) { + return 1; + } + + switch (elementA->type) { + case 0: + case 1: + return CM2Scene::SortOpaqueGeoBatches(elementA, elementB); + + case 3: + return CM2Scene::SortOpaqueRibbons(elementA, elementB); + + case 4: + return CM2Scene::SortOpaqueParticles(elementA, elementB); + + default: + return 0; + } +} + +int32_t CM2Scene::SortOpaqueGeoBatches(M2Element* elementA, M2Element* elementB) { + auto modelA = elementA->model; + auto dataA = modelA->m_shared->m_data; + auto batchA = elementA->batch; + auto modelB = elementB->model; + auto dataB = modelB->m_shared->m_data; + auto batchB = elementB->batch; + + if (elementA->type == 0) { + if (batchA->materialLayer < batchB->materialLayer) { + return -1; + } + + if (batchA->materialLayer > batchB->materialLayer) { + return 1; + } + + if (elementA->effect && elementB->effect) { + auto effectA = elementA->effect; + auto effectB = elementB->effect; + auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute]; + auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute]; + auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute]; + auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute]; + + if (vertexShaderA < vertexShaderB) { + return -1; + } + + if (vertexShaderA > vertexShaderB) { + return 1; + } + + if (pixelShaderA < pixelShaderB) { + return -1; + } + + if (pixelShaderA > pixelShaderB) { + return 1; + } + } + + if (modelA->m_shared < modelB->m_shared) { + return -1; + } + + if (modelA->m_shared > modelB->m_shared) { + return 1; + } + + if ((elementA->flags & 0x4) < (elementB->flags & 0x4)) { + return -1; + } + + if ((elementA->flags & 0x4) > (elementB->flags & 0x4)) { + return 1; + } + + if (modelA < modelB) { + return -1; + } + + if (modelA > modelB) { + return 1; + } + + if (elementA->skinSection->boneComboIndex < elementB->skinSection->boneComboIndex) { + return -1; + } + + if (elementA->skinSection->boneComboIndex > elementB->skinSection->boneComboIndex) { + return 1; + } + } + + auto materialA = &dataA->materials[batchA->materialIndex]; + auto materialB = &dataB->materials[batchB->materialIndex]; + + if (materialA->blendMode < materialB->blendMode) { + return -1; + } + + if (materialA->blendMode > materialB->blendMode) { + return 1; + } + + if ((materialA->flags & 0x1F) < (materialB->flags & 0x1F)) { + return -1; + } + + if ((materialA->flags & 0x1F) > (materialB->flags & 0x1F)) { + return 1; + } + + if (batchA->textureCount > 0 && batchB->textureCount > 0) { + for (int32_t i = 0; i < std::min(batchA->textureCount, batchB->textureCount); i++) { + auto textureIndexA = dataA->textureCombos[batchA->textureComboIndex]; + auto textureA = textureIndexA >= dataA->textures.Count() ? 0 : reinterpret_cast(modelA->m_textures[textureIndexA]); + auto textureIndexB = dataB->textureCombos[batchB->textureComboIndex]; + auto textureB = textureIndexB >= dataB->textures.Count() ? 0 : reinterpret_cast(modelB->m_textures[textureIndexB]); + + if ((textureA - textureB) / sizeof(void*) < 0) { + return -1; + } + + if ((textureA - textureB) / sizeof(void*) > 0) { + return 1; + } + } + } + + if (batchA->textureCount < batchB->textureCount) { + return -1; + } + + if (batchA->textureCount > batchB->textureCount) { + return 1; + } + + if (batchA < batchB) { + return -1; + } + + return batchA > batchB; +} + +int32_t CM2Scene::SortOpaqueParticles(M2Element* elementA, M2Element* elementB) { + // TODO + return 0; +} + +int32_t CM2Scene::SortOpaqueRibbons(M2Element* elementA, M2Element* elementB) { + // TODO + return 0; +} + +int32_t CM2Scene::SortTransparent(uint32_t a, uint32_t b, const void* userArg) { + auto elements = static_cast(userArg)->m_elements.Ptr(); + auto elementA = const_cast(&elements[a]); + auto elementB = const_cast(&elements[b]); + + if (elementA->float10 > elementB->float10) { + return -1; + } + + if (elementA->float10 < elementB->float10) { + return 1; + } + + if ((elementA->flags & 0x1) > (elementB->flags & 0x1)) { + return -1; + } + + if ((elementA->flags & 0x1) < (elementB->flags & 0x1)) { + return 1; + } + + if (elementA->priorityPlane < elementB->priorityPlane) { + return -1; + } + + if (elementA->priorityPlane > elementB->priorityPlane) { + return 1; + } + + if (elementA->float14 > elementB->float14) { + return -1; + } + + if (elementA->float14 < elementB->float14) { + return 1; + } + + if ((CM2Scene::s_optFlags & 0x4000) + && (elementA->type != elementB->type || elementA->model != elementB->model) + && elementA->effect + && elementB->effect + ) { + auto effectA = elementA->effect; + auto effectB = elementB->effect; + auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute]; + auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute]; + auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute]; + auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute]; + + if (vertexShaderA < vertexShaderB) { + return -1; + } + + if (vertexShaderA > vertexShaderB) { + return 1; + } + + if (pixelShaderA < pixelShaderB) { + return -1; + } + + if (pixelShaderA > pixelShaderB) { + return 1; + } + } + + if (elementA->model < elementB->model) { + return -1; + } + + if (elementA->model > elementB->model) { + return 1; + } + + if (elementA->type < elementB->type) { + return -1; + } + + if (elementA->type > elementB->type) { + return 1; + } + + if (elementA->type <= 2) { + if (elementA->batch->materialLayer < elementB->batch->materialLayer) { + return -1; + } + + if (elementA->batch->materialLayer > elementB->batch->materialLayer) { + return 1; + } + } + + if (!(CM2Scene::s_optFlags & 0x4000) || !elementA->effect || !elementB->effect) { + return CM2Scene::SortOpaque(a, b, userArg); + } + + auto effectA = elementA->effect; + auto effectB = elementB->effect; + auto vertexShaderA = effectA->m_vertexShaders[elementA->vertexPermute]; + auto pixelShaderA = effectA->m_pixelShaders[elementA->pixelPermute]; + auto vertexShaderB = effectB->m_vertexShaders[elementB->vertexPermute]; + auto pixelShaderB = effectB->m_pixelShaders[elementB->pixelPermute]; + + if (vertexShaderA < vertexShaderB) { + return -1; + } + + if (vertexShaderA > vertexShaderB) { + return 1; + } + + if (pixelShaderA < pixelShaderB) { + return -1; + } + + if (pixelShaderA > pixelShaderB) { + return 1; + } + + return CM2Scene::SortOpaque(a, b, userArg); +} + +void CM2Scene::AdvanceTime(uint32_t a2) { + this->m_time += a2; + + this->m_cache->UpdateShared(); + this->m_cache->GarbageCollect(0); + + this->m_flags |= 0x4; + this->uint10 = a2; + + if (a2) { + for (auto model = this->m_animateList; model; model = model->m_animateNext) { + model->ProcessCallbacksRecursive(); + } + } + + this->m_flags &= ~0x4; +} + +void CM2Scene::Animate(const C3Vector& cameraPos) { + this->uint14++; + + uint32_t optFlags = this->m_cache->m_flags & 0xE000; + if (CM2Scene::s_optFlags != optFlags) { + CM2Scene::s_optFlags = optFlags; + } + + GxXformView(this->m_view); + C3Vector invCameraPos = { -cameraPos.x, -cameraPos.y, -cameraPos.z }; + this->m_view.Translate(invCameraPos); + this->m_viewInv = this->m_view.Inverse(this->m_view.Determinant()); + + if (this->m_cache->m_flags & 0x4) { + // In multithreaded mode, iteration over the animate list is interleaved: + // - the current thread animates entries 0, 2, 4, ... + // - the newly created thread animates entries 1, 3, 5, ... + + this->m_cache->BeginThread(CM2Scene::AnimateThread, this); + + CM2Model* nextModel; + for (auto model = this->m_animateList; model; model = nextModel->m_animateNext) { + if (!model->m_attachParent) { + if (model->m_flag1000) { + C3Vector v222 = { 0.0f, 0.0f, 0.0f }; + C3Vector v218 = { 1.0f, 1.0f, 1.0f }; + + model->AnimateMTSimple(&this->m_view, v218, v222, 1.0f, 1.0f); + } else { + C3Vector v220 = { 0.0f, 0.0f, 0.0f }; + C3Vector v221 = { 1.0f, 1.0f, 1.0f }; + + model->AnimateMT(&this->m_view, v221, v220, 1.0f, 1.0f); + } + } + + nextModel = model->m_animateNext; + if (!nextModel) { + break; + } + } + + this->m_cache->WaitThread(); + } else { + for (auto model = this->m_animateList; model; model = model->m_animateNext) { + if (!model->m_attachParent) { + if (model->m_flag1000 != 0) { + C3Vector v222 = { 0.0f, 0.0f, 0.0f }; + C3Vector v218 = { 1.0f, 1.0f, 1.0f }; + + model->AnimateMTSimple(&this->m_view, v218, v222, 1.0f, 1.0f); + } else { + C3Vector v220 = { 0.0f, 0.0f, 0.0f }; + C3Vector v221 = { 1.0f, 1.0f, 1.0f }; + + model->AnimateMT(&this->m_view, v221, v220, 1.0f, 1.0f); + } + } + } + } + + for (auto model = this->m_animateList; model; model = model->m_animateNext) { + if (!model->m_attachParent) { + model->AnimateST(); + } + } + + while (this->m_animateList) { + // TODO + // - this is clearing out the animate list; why? something must reattach things to it... + auto model = this->m_animateList; + this->m_animateList = model->m_animateNext; + model->m_animatePrev = nullptr; + model->m_animateNext = nullptr; + + model->SetupLighting(); + } + + this->array44.SetCount(0); + for (int32_t i = 0; i < M2PASS_COUNT; i++) { + this->array54[i].SetCount(0); + } + + this->m_elements.SetCount(0); + int32_t elementIndex = 0; + + while (this->m_drawList) { + auto model = this->m_drawList; + this->m_drawList = model->m_drawNext; + + model->m_flag8 = 0; + model->m_flag10000 = 0; + model->m_drawPrev = nullptr; + model->m_drawNext = nullptr; + + if (!model->IsDrawable(0, 0) || model->m_flag4000) { + continue; + } + + auto v19 = model->m_currentLighting; + auto data = model->m_shared->m_data; + auto v21 = v19->m_flags & 0x20; + auto v22 = v19->m_flags & 0x40; + + if (v21 && v22) { + // TODO + // - liquid plane stuff + } + + auto skinProfile = model->m_shared->skinProfile; + auto v17 = (this->m_cache->m_flags & 0x1) == 0; + + int32_t v229; + if (v17 || (model->m_flags & 0x1) != 0 || (v17 = (model->m_flag40) == 0, v229 = 1, v17)) { + v229 = 0; + } + + uint32_t batchCount; + if (model->ptr2D0) { + // TODO + // batchCount = (model->ptr2D0 + 4); + + assert(false); + } else { + batchCount = skinProfile->batches.Count(); + } + + for (int32_t batchIndex = 0; batchIndex < batchCount; batchIndex++) { + M2Batch* batch; + M2SkinSection* skinSection; + CShaderEffect* effect; + int32_t v221; + int32_t v222; + + if (model->ptr2D0) { + // TODO + // batch = &model->m_optGeo->batches[batchIndex]; + // skinSection = model->m_optGeo->skinSections[batch->skinSectionIndex]; + + assert(false); + } else { + batch = &skinProfile->batches[batchIndex]; + skinSection = &model->m_shared->m_skinSections[batch->skinSectionIndex]; + + if (!skinSection) { + continue; + } + } + + if (batch->shader == 0x8000) { + continue; + } + + float alpha = model->alpha19C; + + if (batch->colorIndex < data->colors.Count()) { + auto& color = model->m_colors[batch->colorIndex]; + alpha *= color.alphaTrack.currentValue; + } + + if (batch->textureCount) { + auto& textureWeight = model->m_textureWeights[data->textureWeightCombos[batch->textureWeightComboIndex]]; + alpha *= textureWeight.weightTrack.currentValue; + } + + if (alpha < 0.000099999997f) { + continue; + } + + M2Material* material = &data->materials[batch->materialIndex]; + + auto v17 = (batch->flags & 0x4) == 0; + if (v17 || (v17 = this->uint104 == 0, v222 = 1, v17)) { + v222 = 0; + } + + M2Material* layerMaterial = batch->materialLayer + ? &data->materials[batch->materialIndex - batch->materialLayer] + : &data->materials[batch->materialIndex]; + + if (layerMaterial->blendMode > 1 || (v221 = 0, alpha < 0.99998999f)) { + v221 = 1; + } + + if (model->ptr2D0) { + // TODO + // effect = model->m_optGeo->effects[batchIndex]; + + assert(false); + } else { + effect = model->m_shared->m_batchShaders[batchIndex]; + } + + if (!effect) { + continue; + } + + auto element = this->m_elements.New(); + + if (v222) { + element->type = 1; + } else if (!model->IsBatchDoodadCompatible(batch) || v221) { + element->type = 0; + } else { + element->type = 2; + } + + element->model = model; + + element->flags = 0x0; + if (v221 == 1 && v21 && v22 && !v222) { + element->flags |= 0x2; + } + if (model->ptr2D0) { + element->flags |= 0x4; + } + + element->alpha = alpha; + element->index = batchIndex; + element->priorityPlane = batch->priorityPlane; + element->batch = batch; + element->skinSection = skinSection; + element->effect = effect; + + CM2Scene::ComputeElementShaders(element); + + float v58; + + if (v221 < 1) { + element->float14 = model->float88; + v58 = model->float88; + } else if (data->flags & 0x10) { + element->float14 = (skinSection->sortCenterPosition * model->m_boneMatrices[skinSection->centerBoneIndex]).SquaredMag(); + v58 = model->float88; + } else { + // TODO other sort position logic + + v58 = model->float88; + } + + element->float10 = v58; + + if (element->type == 2) { + // TODO + } else if (v221 == 1) { + if (v222) { + if (v22) { + *this->array54[2].New() = elementIndex; + } else { + *this->array54[1].New() = elementIndex; + } + } else { + if (v21) { + *this->array54[1].New() = elementIndex; + } + + if (v22) { + *this->array54[2].New() = elementIndex; + } + } + } else { + *this->array54[v221].New() = elementIndex; + } + + elementIndex++; + + if (v229 && !v222 && v221 >= 1 && !(material->flags & 0x10)) { + // TODO + } + } + + // TODO + // - ribbons + + // TODO + // - draw callbacks + } + + M2HeapSort(CM2Scene::SortOpaque, this->array54[0].Ptr(), this->array54[0].Count(), this); + M2HeapSort(CM2Scene::SortTransparent, this->array54[1].Ptr(), this->array54[1].Count(), this); + M2HeapSort(CM2Scene::SortTransparent, this->array54[2].Ptr(), this->array54[2].Count(), this); + + // TODO sort additive particles +} + +CM2Model* CM2Scene::CreateModel(const char* file, uint32_t a3) { + if (!file) { + return nullptr; + } + + CM2Shared* shared = this->m_cache->CreateShared(file, a3); + if (!shared) { + shared = this->m_cache->CreateShared("Spells\\ErrorCube.mdx", 0); + } + + CM2Model* model = nullptr; + + if (shared) { + model = CM2Model::AllocModel(g_modelPool); + + if (model) { + if (!model->Initialize(this, shared, nullptr, a3)) { + // TODO + } + } + + shared->Release(); + } + + return model; +} + +int32_t CM2Scene::Draw(M2PASS pass) { + // TODO + // - conditional check on this->dword144 + + if (CM2Scene::s_optFlags != (this->m_cache->m_flags & 0xE000)) { + CM2Scene::s_optFlags = this->m_cache->m_flags & 0xE000; + } + + CM2SceneRender render(this); + + render.Draw(pass, this->m_elements.m_data, this->array54[pass].m_data, this->array54[pass].Count()); + + if (pass == M2PASS_0) { + render.Draw(pass, this->m_elements.m_data, this->array44.m_data, this->array44.Count()); + } + + return 1; +} + +void CM2Scene::SelectLights(CM2Lighting* lighting) { + for (auto light = this->m_lightList; light; light = light->m_lightNext) { + lighting->AddLight(light); + } + + // TODO +} diff --git a/src/model/CM2Scene.hpp b/src/model/CM2Scene.hpp new file mode 100644 index 0000000..6a9bb74 --- /dev/null +++ b/src/model/CM2Scene.hpp @@ -0,0 +1,57 @@ +#ifndef MODEL_C_M2_SCENE_HPP +#define MODEL_C_M2_SCENE_HPP + +#include "model/M2Model.hpp" +#include "model/M2Types.hpp" +#include +#include +#include + +class CM2Cache; +class CM2Light; +class CM2Lighting; +class CM2Model; + +class CM2Scene { + public: + // Static variables + static uint32_t s_optFlags; + + // Static functions + static void AnimateThread(void* arg); + static void ComputeElementShaders(M2Element* element); + static int32_t SortOpaque(uint32_t a, uint32_t b, const void* userArg); + static int32_t SortOpaqueGeoBatches(M2Element* elementA, M2Element* elementB); + static int32_t SortOpaqueParticles(M2Element* elementA, M2Element* elementB); + static int32_t SortOpaqueRibbons(M2Element* elementA, M2Element* elementB); + static int32_t SortTransparent(uint32_t a, uint32_t b, const void* userArg); + + // Member variables + CM2Cache* m_cache; + CM2Model* m_modelList = nullptr; + uint32_t m_time = 0; + uint32_t uint10; + uint32_t uint14 = 0; + uint32_t m_flags = 0; + CM2Light* m_lightList = nullptr; + CM2Model* m_animateList = nullptr; + CM2Model* m_drawList = nullptr; + TSGrowableArray m_elements; + TSGrowableArray array44; + TSGrowableArray array54[3]; + C44Matrix m_view; + C44Matrix m_viewInv; + uint32_t uint104 = 0; + + // Member functions + CM2Scene(CM2Cache* cache) + : m_cache(cache) + {}; + void AdvanceTime(uint32_t a2); + void Animate(const C3Vector& cameraPos); + CM2Model* CreateModel(const char*, uint32_t); + int32_t Draw(M2PASS pass); + void SelectLights(CM2Lighting* lighting); +}; + +#endif diff --git a/src/model/CM2SceneRender.cpp b/src/model/CM2SceneRender.cpp new file mode 100644 index 0000000..a94b8f2 --- /dev/null +++ b/src/model/CM2SceneRender.cpp @@ -0,0 +1,541 @@ +#include "model/CM2SceneRender.hpp" +#include "gx/Draw.hpp" +#include "gx/RenderState.hpp" +#include "gx/Shader.hpp" +#include "gx/Texture.hpp" +#include "gx/Transform.hpp" +#include "model/CM2Cache.hpp" +#include "model/CM2Model.hpp" +#include "model/CM2Shared.hpp" +#include "model/M2Types.hpp" +#include + +C44Matrix CM2SceneRender::s_identity; + +int32_t CM2SceneRender::s_fogModeList[M2BLEND_COUNT] = { + 1, // M2BLEND_OPAQUE + 1, // M2BLEND_ALPHA_KEY + 1, // M2BLEND_ALPHA + 2, // M2BLEND_NO_ALPHA_ADD + 2, // M2BLEND_ADD + 3, // M2BLEND_MOD + 4 // M2BLEND_MOD_2X +}; + +EGxBlend CM2SceneRender::s_gxBlend[M2PASS_COUNT][M2BLEND_COUNT] = { + // M2PASS_0 + { + GxBlend_Opaque, // M2BLEND_OPAQUE + GxBlend_AlphaKey, // M2BLEND_ALPHA_KEY + GxBlend_Alpha, // M2BLEND_ALPHA + GxBlend_NoAlphaAdd, // M2BLEND_NO_ALPHA_ADD + GxBlend_Add, // M2BLEND_ADD + GxBlend_Mod, // M2BLEND_MOD + GxBlend_Mod2x // M2BLEND_MOD_2X + }, + + // M2PASS_1 + { + GxBlend_Alpha, // M2BLEND_OPAQUE + GxBlend_Alpha, // M2BLEND_ALPHA_KEY + GxBlend_Alpha, // M2BLEND_ALPHA + GxBlend_NoAlphaAdd, // M2BLEND_NO_ALPHA_ADD + GxBlend_Add, // M2BLEND_ADD + GxBlend_Mod, // M2BLEND_MOD + GxBlend_Mod2x // M2BLEND_MOD_2X + }, + + // M2PASS_2 + { + GxBlend_Alpha, // M2BLEND_OPAQUE + GxBlend_Alpha, // M2BLEND_ALPHA_KEY + GxBlend_Alpha, // M2BLEND_ALPHA + GxBlend_NoAlphaAdd, // M2BLEND_NO_ALPHA_ADD + GxBlend_Add, // M2BLEND_ADD + GxBlend_Mod, // M2BLEND_MOD + GxBlend_Mod2x // M2BLEND_MOD_2X + } +}; + +int32_t CM2SceneRender::s_shadedList[M2BLEND_COUNT] = { + 1, // M2BLEND_OPAQUE + 1, // M2BLEND_ALPHA_KEY + 1, // M2BLEND_ALPHA + 1, // M2BLEND_NO_ALPHA_ADD + 1, // M2BLEND_ADD + 0, // M2BLEND_MOD + 0 // M2BLEND_MOD_2X +}; + +void CM2SceneRender::Draw(M2PASS pass, M2Element* elements, uint32_t* a4, uint32_t a5) { + if (!a5) { + return; + } + + GxRsPush(); + + C44Matrix savedView; + GxXformView(savedView); + + for (int32_t xf = GxXform_Tex0; xf <= GxXform_World; xf++) { + GxXformPush(static_cast(xf)); + } + + GxXformSetView(CM2SceneRender::s_identity); + GxXformSet(GxXform_World, CM2SceneRender::s_identity); + + GxRsSet(GxRs_DepthFunc, 0); + GxRsSet(GxRs_PolygonOffset, 0); + GxRsSet(GxRs_Lighting, 0); + GxRsSet(GxRs_Fog, 0); + GxRsSet(GxRs_MatSpecularExp, 0.0f); + + if (CShaderEffect::s_enableShaders) { + C44Matrix projNative; + GxXformProjNative(projNative); + this->matrix0 = projNative.Inverse(projNative.Determinant()).Transpose(); + + CShaderEffect::UpdateProjMatrix(); + + // TODO + // CShadowCache::SetShadowMapGenericGlobal(); + } + + this->m_curPass = pass; + + for (int32_t i = 0; i < a5; i++) { + uint32_t index = a4[i]; + auto element = &elements[index]; + + if (element->type == 2 || element->type == 4 || !element->model->m_flag2000) { + this->m_curElement = element; + this->m_curType = element->type; + this->m_curModel = element->model; + this->m_curShared = element->model->m_shared; + this->m_curShaded = 1; + this->m_curFogMode = 1; + this->m_curBatch = nullptr; + this->m_curSkinSection = nullptr; + this->m_curLighting = element->model->m_currentLighting; + // TODO + // this->m_curMaterial = this->dwordB8; + this->m_data = element->model->m_shared->m_data; + + // TODO + // this->m_cache->LinkToSharedUpdateList(this->m_curShared); + + switch (this->m_curElement->type) { + case 0: { + this->DrawBatch(); + break; + } + + case 1: { + this->DrawBatchProj(); + break; + } + + case 2: { + this->DrawBatchDoodad(elements, &a4[i]); + // TODO + // i += this->m_curElement->dword1C - 1; + break; + } + + case 3: { + this->DrawRibbon(); + break; + } + + case 4: { + i += this->DrawParticle(i, elements, a4, a5); + break; + } + + case 5: { + this->DrawCallback(); + break; + } + + default: + continue; + } + + this->m_prevElement = this->m_curElement; + this->m_prevType = this->m_curType; + this->m_prevModel = this->m_curModel; + this->m_prevShared = this->m_curShared; + this->m_curLighting = this->m_prevLighting; // TODO investigate (maybe bug?) + this->m_prevShaded = this->m_curShaded; + this->m_prevFogMode = this->m_curFogMode; + this->m_prevBatch = this->m_curBatch; + this->m_prevSkinSection = this->m_curSkinSection; + this->m_prevMaterial = this->m_curMaterial; + } + } + + for (int32_t xf = GxXform_Tex0; xf <= GxXform_World; xf++) { + GxXformPop(static_cast(xf)); + } + + GxXformSetView(savedView); + + GxRsPop(); + GxRsSet(GxRs_Fog, 0); +} + +void CM2SceneRender::DrawBatch() { + auto element = this->m_curElement; + + this->m_curBatch = element->batch; + this->m_curSkinSection = element->skinSection; + this->m_curMaterial = &this->m_data->materials[element->batch->materialIndex]; + + element->effect->SetCurrent(); + this->SetupLighting(); + this->SetupMaterial(); + this->SetupTextures(); + + if ( + CShaderEffect::s_enableShaders + && ( + this->m_curType != this->m_prevType + || this->m_curModel != this->m_prevModel + || this->m_curSkinSection->boneComboIndex != this->m_prevSkinSection->boneComboIndex + ) + ) { + C4Vector* constants = reinterpret_cast(GxShaderConstantsLock(GxSh_Vertex)); + + for (int32_t i = 0; i < this->m_curSkinSection->boneCount; i++) { + auto& boneMatrix = this->m_curModel->m_boneMatrices[this->m_data->boneCombos[this->m_curSkinSection->boneComboIndex + i]]; + + constants[31 + (i * 3) + 0] = { boneMatrix.a0, boneMatrix.b0, boneMatrix.c0, boneMatrix.d0 }; + constants[31 + (i * 3) + 1] = { boneMatrix.a1, boneMatrix.b1, boneMatrix.c1, boneMatrix.d1 }; + constants[31 + (i * 3) + 2] = { boneMatrix.a2, boneMatrix.b2, boneMatrix.c2, boneMatrix.d2 }; + } + + GxShaderConstantsUnlock(GxSh_Vertex, 31, this->m_curSkinSection->boneCount * 3); + } + + if (this->m_curElement->flags & 0x4) { + if ( + this->m_curType != this->m_prevType + || this->m_curModel != this->m_prevModel + ) { + this->m_curModel->SetIndices(); + } + } else if ( + this->m_curType != this->m_prevType + || this->m_curShared != this->m_prevShared + || this->m_prevElement->flags & 0x4 + ) { + this->m_curShared->SetIndices(); + } + + int32_t v9 = this->m_curModel->m_shared->m_data->bones.count == 1 && this->m_cache->m_flags & 0x40; + this->SetBatchVertices(v9); + + CShaderEffect::SetShaders(this->m_curElement->vertexPermute, this->m_curElement->pixelPermute); + + if (CShaderEffect::s_enableShaders) { + auto skinSection = this->m_curSkinSection; + + CGxBatch batch; + + batch.m_primType = GxPrim_Triangles; + batch.m_start = skinSection->indexStart; + batch.m_count = skinSection->indexCount; + batch.m_minIndex = skinSection->vertexStart; + batch.m_maxIndex = skinSection->vertexStart + skinSection->vertexCount - 1; + + GxDraw(&batch, 1); + } else if (v9) { + // TODO + } else { + // TODO + } +} + +void CM2SceneRender::DrawBatchDoodad(M2Element* elements, uint32_t* a3) { + // TODO +} + +void CM2SceneRender::DrawBatchProj() { + // TODO +} + +void CM2SceneRender::DrawCallback() { + // TODO +} + +int32_t CM2SceneRender::DrawParticle(uint32_t a2, M2Element* elements, uint32_t* a4, uint32_t a5) { + // TODO + return 0; +} + +void CM2SceneRender::DrawRibbon() { + // TODO +} + +void CM2SceneRender::SetBatchVertices(int32_t a2) { + if (CShaderEffect::s_enableShaders) { + if (this->m_curType != this->m_prevType || this->m_curShared != this->m_prevShared) { + this->m_curShared->SetVertices(0); + } + } else { + // TODO + // - non-shader code path + } +} + +void CM2SceneRender::SetupLighting() { + if (this->m_curMaterial->flags & 0x1) { + this->m_curShaded = 0; + } else { + this->m_curShaded = CM2SceneRender::s_shadedList[this->m_curMaterial->blendMode]; + } + + CShaderEffect::SetLocalLighting(this->m_curLighting, this->m_curShaded, 0); + + // TODO + // dwordD43010 = this->m_curElement->dword3C; + + if ((this->m_curMaterial->flags & 0x2) || this->m_curLighting->m_fogScale <= 0.0f) { + this->m_curFogMode = 0; + } else { + this->m_curFogMode = CM2SceneRender::s_fogModeList[this->m_curMaterial->blendMode]; + } + + if (this->m_curFogMode == 0) { + CShaderEffect::SetFogEnabled(0); + } else { + CImVector fogColor; + + switch (this->m_curFogMode) { + case 1: { + float x = this->m_curLighting->m_fogColor.x; + float y = this->m_curLighting->m_fogColor.y; + float z = this->m_curLighting->m_fogColor.z; + + fogColor.b = z <= 0.0f ? 0x00 : z >= 1.0f ? 0xFF : CMath::fuint_n(z * 255.0f); + fogColor.g = y <= 0.0f ? 0x00 : y >= 1.0f ? 0xFF : CMath::fuint_n(y * 255.0f); + fogColor.r = x <= 0.0f ? 0x00 : x >= 1.0f ? 0xFF : CMath::fuint_n(x * 255.0f); + fogColor.a = 0xFF; + + break; + } + + case 2: { + fogColor = { 0x00, 0x00, 0x00, 0x00 }; + break; + } + + case 3: { + fogColor = { 0xFF, 0xFF, 0xFF, 0x00 }; + break; + } + + case 4: { + fogColor = { 0x80, 0x80, 0x80, 0x00 }; + break; + } + } + + CShaderEffect::SetFogParams( + this->m_curLighting->m_fogStart, + this->m_curLighting->m_fogEnd, + this->m_curLighting->m_fogDensity, + fogColor + ); + + CShaderEffect::SetFogEnabled(1); + } + + if ( + this->m_curType != this->m_prevType + || this->m_curModel != this->m_prevModel + || this->m_curLighting != this->m_prevLighting + || ((this->m_curElement->flags & 0x2) != (this->m_prevElement->flags & 0x2)) + ) { + if (this->m_curElement->flags & 0x2) { + // TODO + // - enable clip plane mask for liquid plane + } else { + GxRsSet(GxRs_ClipPlaneMask, 0); + } + } +} + +void CM2SceneRender::SetupMaterial() { + if ( + this->m_curType != this->m_prevType + || this->m_curMaterial != this->m_prevMaterial + || (this->m_curElement->flags & 0x1) != (this->m_prevElement->flags & 0x1) + || this->m_curElement->alpha != this->m_prevElement->alpha + ) { + EGxBlend blendingMode = this->m_curElement->flags & 0x1 + ? GxBlend_AlphaKey + : CM2SceneRender::s_gxBlend[this->m_curPass][this->m_curMaterial->blendMode]; + + int32_t colorWrite = (this->m_curElement->flags & 0x1) + ? 0 + : 15; + + GxRsSet(GxRs_ColorWrite, colorWrite); + GxRsSet(GxRs_BlendingMode, blendingMode); + + float alphaRef; + if (this->m_curMaterial->blendMode == 0) { + alphaRef = 0.0f; + } else if (this->m_curMaterial->blendMode == 1) { + alphaRef = this->m_curElement->alpha * 0.87843138f; + } else { + alphaRef = 0.0039215689f; + } + + CShaderEffect::SetAlphaRef(alphaRef); + } + + if ( + this->m_curType != this->m_prevType + || this->m_curMaterial != this->m_prevMaterial + ) { + int32_t culling = (this->m_curMaterial->flags & 0x4) == 0; + GxRsSet(GxRs_Culling, culling); + + int32_t depthTest = (this->m_curMaterial->flags & 0x8) == 0; + GxRsSet(GxRs_DepthTest, depthTest); + + int32_t depthWrite = (this->m_curMaterial->flags & 0x10) == 0; + GxRsSet(GxRs_DepthWrite, depthWrite); + } + + if (!CShaderEffect::s_enableShaders) { + // TODO + } + + if (this->m_curElement->type > 2) { + if ( + this->m_curType != this->m_prevType + || this->m_curElement->alpha != this->m_prevElement->alpha + ) { + C4Vector diffuse = { 1.0f, 1.0f, 1.0f, this->m_curElement->alpha }; + CShaderEffect::SetDiffuse(diffuse); + + C4Vector emissive = { 0.0f, 0.0f, 0.0f, 0.0f }; + CShaderEffect::SetEmissive(emissive); + } + } else if ( + this->m_curType != this->m_prevType + || this->m_curShared != this->m_prevShared + || this->m_curShaded != this->m_prevShaded + || this->m_curMaterial->blendMode != this->m_prevMaterial->blendMode + || this->m_prevBatch == nullptr + || this->m_curBatch->colorIndex != this->m_prevBatch->colorIndex + || this->m_prevElement->alpha != this->m_curElement->alpha + || this->m_curModel->m_currentDiffuse != this->m_prevModel->m_currentDiffuse + || this->m_curModel->m_currentEmissive != this->m_prevModel->m_currentEmissive + ) { + if (this->m_curMaterial->blendMode == M2BLEND_MOD) { + C4Vector diffuse = { 0.0f, 0.0f, 0.0f, this->m_curElement->alpha }; + CShaderEffect::SetDiffuse(diffuse); + + C4Vector emissive = { 1.0f, 1.0f, 1.0f, 0.0f }; + CShaderEffect::SetEmissive(emissive); + } else if (this->m_curMaterial->blendMode == M2BLEND_MOD_2X) { + C4Vector diffuse = { 0.0f, 0.0f, 0.0f, this->m_curElement->alpha }; + CShaderEffect::SetDiffuse(diffuse); + + C4Vector emissive = { 0.5f, 0.5f, 0.5f, 0.0f }; + CShaderEffect::SetEmissive(emissive); + } else { + auto modelDiffuse = this->m_curModel->m_currentDiffuse; + auto modelEmissive = this->m_curModel->m_currentEmissive; + + if (this->m_curBatch->colorIndex < this->m_data->colors.Count()) { + auto& modelColor = this->m_curModel->m_colors[this->m_curBatch->colorIndex]; + + modelDiffuse.x *= modelColor.colorTrack.currentValue.x; + modelDiffuse.y *= modelColor.colorTrack.currentValue.y; + modelDiffuse.z *= modelColor.colorTrack.currentValue.z; + } + + if (!this->m_curShaded) { + modelEmissive.x += modelDiffuse.x; + modelEmissive.y += modelDiffuse.y; + modelEmissive.z += modelDiffuse.z; + + modelDiffuse.x = 0.0f; + modelDiffuse.y = 0.0f; + modelDiffuse.z = 0.0f; + } + + C4Vector diffuse = { modelDiffuse.x, modelDiffuse.y, modelDiffuse.z, this->m_curElement->alpha }; + CShaderEffect::SetDiffuse(diffuse); + + C4Vector emissive = { modelEmissive.x, modelEmissive.y, modelEmissive.z, 0.0f }; + CShaderEffect::SetEmissive(emissive); + } + } +} + +void CM2SceneRender::SetupTextures() { + if (this->m_curType > 2) { + for (int32_t i = 0; i < 2; i++) { + GxRsSet(static_cast(GxRs_Texture0 + i), 0); + CShaderEffect::SetTexMtx_Identity(i); + } + + return; + } + + uint32_t textureCount = this->m_curBatch->textureCount; + int32_t v19 = 1; + + // TODO + // - override texture count in certain cases + + if (!(this->m_curBatch->shader & 0x4000) || textureCount != 1) { + v19 = 0; + } + + for (int32_t i = 0; i < 2; i++) { + if (i >= textureCount) { + GxRsSet(static_cast(GxRs_Texture0 + i), static_cast(nullptr)); + continue; + } + + auto textureIndex = this->m_data->textureCombos[this->m_curBatch->textureComboIndex + i]; + auto textureHandle = textureIndex < this->m_data->textures.Count() + ? this->m_curModel->m_textures[textureIndex] + : nullptr; + auto texture = textureHandle + ? TextureGetGxTex(textureHandle, 1, nullptr) + : nullptr; + + if (texture) { + uint16_t textureFlags = this->m_data->textures[textureIndex].flags; + + EGxTexWrapMode wrapU = static_cast(textureFlags & 0x1); + EGxTexWrapMode wrapV = static_cast(textureFlags & 0x2); + GxTexSetWrap(texture, wrapU, wrapV); + } + + GxRsSet(static_cast(GxRs_Texture0 + i), texture); + + auto textureTransformIndex = this->m_data->textureTransformCombos[this->m_curBatch->textureTransformComboIndex + i]; + auto stageShift = M2COMBINER_STAGE_SHIFT * (2 - (i + 1)); + + uint32_t v21 = v19 == 0 ? i : 1; + + if (this->m_curBatch->shader & 0x8000 || !((this->m_curBatch->shader >> stageShift) & M2COMBINER_ENVMAP)) { + if (textureTransformIndex >= this->m_data->textureTransforms.Count()) { + CShaderEffect::SetTexMtx_Identity(v21); + } else { + CShaderEffect::SetTexMtx(this->m_curModel->m_textureMatrices[textureTransformIndex], v21); + } + } else { + CShaderEffect::SetTexMtx_SphereMap(v21); + } + } +} diff --git a/src/model/CM2SceneRender.hpp b/src/model/CM2SceneRender.hpp new file mode 100644 index 0000000..e382954 --- /dev/null +++ b/src/model/CM2SceneRender.hpp @@ -0,0 +1,73 @@ +#ifndef MODEL_C_M2_SCENE_RENDER_HPP +#define MODEL_C_M2_SCENE_RENDER_HPP + +#include "gx/Types.hpp" +#include "model/CM2Scene.hpp" +#include "model/M2Types.hpp" +#include + +class CM2Cache; +class CM2Lighting; +class CM2Model; +class CM2Shared; +class M2Batch; +class M2Data; +class M2Element; +class M2Material; +class M2SkinSection; + +class CM2SceneRender { + public: + // Static variables + static C44Matrix s_identity; + static int32_t s_fogModeList[M2BLEND_COUNT]; + static EGxBlend s_gxBlend[M2PASS_COUNT][M2BLEND_COUNT]; + static int32_t s_shadedList[M2BLEND_COUNT]; + + // Member variables + C44Matrix matrix0; + CM2Scene* m_scene; + CM2Cache* m_cache; + M2Data* m_data = nullptr; + M2PASS m_curPass; + M2Element* m_curElement = nullptr; + M2Element* m_prevElement = nullptr; + uint32_t m_curType = -1u; + uint32_t m_prevType = -1u; + CM2Model* m_curModel = nullptr; + CM2Model* m_prevModel = nullptr; + CM2Shared* m_curShared = nullptr; + CM2Shared* m_prevShared = nullptr; + CM2Lighting* m_curLighting = nullptr; + CM2Lighting* m_prevLighting = nullptr; + uint32_t m_curShaded = 0; + uint32_t m_prevShaded = 0; + uint32_t m_curFogMode = -1u; + uint32_t m_prevFogMode = -1u; + M2Batch* m_curBatch = nullptr; + M2Batch* m_prevBatch = nullptr; + M2SkinSection* m_curSkinSection = nullptr; + M2SkinSection* m_prevSkinSection = nullptr; + M2Material* m_curMaterial = nullptr; + M2Material* m_prevMaterial = nullptr; + + // Member functions + CM2SceneRender(CM2Scene* scene) + : m_scene(scene) + , m_cache(scene->m_cache) + {}; + void Draw(M2PASS pass, M2Element* elements, uint32_t* a4, uint32_t a5); + void DrawBatch(void); + void DrawBatchDoodad(M2Element* elements, uint32_t* a3); + void DrawBatchProj(void); + void DrawCallback(void); + int32_t DrawParticle(uint32_t a2, M2Element* elements, uint32_t* a4, uint32_t a5); + void DrawRibbon(void); + void SetBatchVertices(int32_t a2); + void SetupBatchVertices(void); + void SetupLighting(void); + void SetupMaterial(void); + void SetupTextures(void); +}; + +#endif diff --git a/src/model/CM2Shared.cpp b/src/model/CM2Shared.cpp new file mode 100644 index 0000000..92a1ba2 --- /dev/null +++ b/src/model/CM2Shared.cpp @@ -0,0 +1,806 @@ +#include "model/CM2Shared.hpp" +#include "async/AsyncFile.hpp" +#include "gx/Buffer.hpp" +#include "gx/Shader.hpp" +#include "gx/Texture.hpp" +#include "model/CM2Cache.hpp" +#include "model/CM2Model.hpp" +#include "model/M2Data.hpp" +#include "model/M2Init.hpp" +#include "model/M2Types.hpp" +#include "util/CStatus.hpp" +#include "util/SFile.hpp" +#include + +void CM2Shared::LoadFailedCallback(void* arg) { + CM2Shared* shared = static_cast(arg); + + AsyncFileReadDestroyObject(shared->asyncObject); + shared->asyncObject = nullptr; +} + +void CM2Shared::LoadSucceededCallback(void* arg) { + CM2Shared* shared = static_cast(arg); + + AsyncFileReadDestroyObject(shared->asyncObject); + shared->asyncObject = nullptr; + + uint8_t* base = reinterpret_cast(shared->m_data); + uint32_t size = shared->m_dataSize; + M2Data& data = *shared->m_data; + + if (!M2Init(base, size, data)) { + return; + } + + if (!shared->Initialize()) { + return; + } + + // TODO + // - allocate space for low priority sequence pointers + + shared->m_m2DataLoaded = 1; +} + +void CM2Shared::SkinProfileLoadedCallback(void* arg) { + CM2Shared* shared = static_cast(arg); + + shared->FinishLoadingSkinProfile(shared->asyncObject->size); + + AsyncFileReadDestroyObject(shared->asyncObject); + shared->asyncObject = nullptr; +} + +void CM2Shared::AddRef() { + // TODO +} + +int32_t CM2Shared::CallbackWhenLoaded(CM2Model* model) { + // TODO + // if (model->dword4 & 0x20) { + // return 1; + // } + + if (this->m_m2DataLoaded && this->m_skinProfileLoaded) { + model->InitializeLoaded(); + + return 1; + } + + model->LinkToCallbackListTail(); + + return 1; +} + +CShaderEffect* CM2Shared::CreateSimpleEffect(uint32_t textureCount, uint16_t shader, uint16_t textureCoordComboIndex) { + uint32_t combiner[2]; + uint32_t envmap[2]; + + combiner[0] = (shader >> M2COMBINER_STAGE_SHIFT) & M2COMBINER_OP_MASK; // T1 Combiner + combiner[1] = (shader >> 0) & M2COMBINER_OP_MASK; // T2 Combiner + envmap[0] = (shader >> M2COMBINER_STAGE_SHIFT) & M2COMBINER_ENVMAP; // T1 Env Mapped + envmap[1] = (shader >> 0) & M2COMBINER_ENVMAP; // T2 Env Mapped + + const char* vsName; + const char* psName; + + // 1 texture + if (textureCount == 1) { + if (envmap[0]) { + vsName = "Diffuse_Env"; + } else if (this->m_data->textureCoordCombos[textureCoordComboIndex] == 0) { + vsName = "Diffuse_T1"; + } else { + vsName = "Diffuse_T2"; + } + + switch (combiner[0]) { + case M2COMBINER_OPAQUE: + psName = "Combiners_Opaque"; + break; + + case M2COMBINER_MOD: + psName = "Combiners_Mod"; + break; + + case M2COMBINER_DECAL: + psName = "Combiners_Decal"; + break; + + case M2COMBINER_ADD: + psName = "Combiners_Add"; + break; + + case M2COMBINER_MOD2X: + psName = "Combiners_Mod2x"; + break; + + case M2COMBINER_FADE: + psName = "Combiners_Fade"; + break; + + default: + psName = "Combiners_Mod"; + break; + } + + // 2 textures + } else { + if (envmap[0] && envmap[1]) { + vsName = "Diffuse_Env_Env"; + } else if (envmap[0]) { + vsName = "Diffuse_Env_T2"; + } else if (envmap[1]) { + vsName = "Diffuse_T1_Env"; + } else { + vsName = "Diffuse_T1_T2"; + } + + switch (combiner[0]) { + case M2COMBINER_OPAQUE: + switch (combiner[1]) { + case M2COMBINER_OPAQUE: + psName = "Combiners_Opaque_Opaque"; + break; + + case M2COMBINER_ADD: + psName = "Combiners_Opaque_Add"; + break; + + case M2COMBINER_MOD2X: + psName = "Combiners_Opaque_Mod2x"; + break; + + case M2COMBINER_MOD2X_NA: + psName = "Combiners_Opaque_Mod2xNA"; + break; + + case M2COMBINER_ADD_NA: + psName = "Combiners_Opaque_AddNA"; + break; + + default: + psName = "Combiners_Opaque_Mod"; + break; + } + + break; + + case M2COMBINER_MOD: + switch (combiner[1]) { + case M2COMBINER_OPAQUE: + psName = "Combiners_Mod_Opaque"; + break; + + case M2COMBINER_MOD: + psName = "Combiners_Mod_Mod"; + break; + + case M2COMBINER_ADD: + psName = "Combiners_Mod_Add"; + break; + + case M2COMBINER_MOD2X: + psName = "Combiners_Mod_Mod2x"; + break; + + case M2COMBINER_MOD2X_NA: + psName = "Combiners_Mod_Mod2xNA"; + break; + + case M2COMBINER_ADD_NA: + psName = "Combiners_Mod_AddNA"; + break; + + default: + psName = "Combiners_Mod_Mod"; + break; + } + + break; + + case M2COMBINER_ADD: + switch (combiner[1]) { + case M2COMBINER_MOD: + psName = "Combiners_Add_Mod"; + break; + + default: + return nullptr; + } + + break; + + case M2COMBINER_MOD2X: + switch (combiner[1]) { + case M2COMBINER_MOD2X: + psName = "Combiners_Mod2x_Mod2x"; + break; + + default: + return nullptr; + } + + break; + } + } + + char effectName[256]; + + // Create effect name for hashing + strcpy(effectName, vsName); + strcat(effectName, psName); + + CShaderEffect* effect = CShaderEffectManager::GetEffect(effectName); + + if (!effect) { + effect = CShaderEffectManager::CreateEffect(effectName); + + effect->InitEffect(vsName, psName); + + // M2GetCombinerOps (inlined) + for (int32_t textureIndex = 0; textureIndex < textureCount; textureIndex++) { + // TODO + // colorOps[textureIndex] = colorOpTable[combiner[textureIndex]]; + // alphaOps[textureIndex] = alphaOpTable[combiner[textureIndex]]; + } + + // TODO + // effect->InitFixedFuncPass(colorOps, alphaOps, textureCount); + } + + return effect; +} + +int32_t CM2Shared::FinishLoadingSkinProfile(uint32_t size) { + if (this->m_skinProfileLoaded) { + return 1; + } + + uint8_t* base = reinterpret_cast(this->skinProfile); + M2Data& data = *this->m_data; + M2SkinProfile& skinProfile = *this->skinProfile; + + if (!M2Init(base, size, data, skinProfile)) { + return 0; + } + + if (!this->InitializeSkinProfile()) { + return 0; + } + + this->m_skinProfileLoaded = 1; + + while (this->m_callbackList) { + CM2Model* model = this->m_callbackList; + model->UnlinkFromCallbackList(); + model->InitializeLoaded(); + } + + return 1; +} + +CShaderEffect* CM2Shared::GetEffect(M2Batch* batch) { + CShaderEffect* effect; + + // Simple effect + if (!(batch->shader & 0x8000)) { + effect = this->CreateSimpleEffect(batch->textureCount, batch->shader, batch->textureCoordComboIndex); + + // Fallback + // 0000000000010001 + // 1 texture: Combiners_Mod + // 2 texture: Combiners_Mod_Mod + if (!effect) { + effect = this->CreateSimpleEffect(batch->textureCount, 0x11, batch->textureCoordComboIndex); + } + + return effect; + } + + // Specialized effect + const char* vsName = nullptr; + const char* psName = nullptr; + uint32_t colorOp = 0; + uint32_t alphaOp = 0; + + switch (batch->shader & 0x7FFF) { + case 0: + return nullptr; + + case 1: + vsName = "Diffuse_T1_Env"; + psName = "Combiners_Opaque_Mod2xNA_Alpha"; + colorOp = 0; + alphaOp = 3; + + break; + + case 2: + vsName = "Diffuse_T1_Env"; + psName = "Combiners_Opaque_AddAlpha"; + colorOp = 0; + alphaOp = 3; + + break; + + case 3: + vsName = "Diffuse_T1_Env"; + psName = "Combiners_Opaque_AddAlpha_Alpha"; + colorOp = 0; + alphaOp = 3; + + break; + + default: + break; + } + + char effectName[256]; + + // Create effect name for hashing + strcpy(effectName, vsName); + strcat(effectName, psName); + + effect = CShaderEffectManager::GetEffect(effectName); + + if (!effect) { + effect = CShaderEffectManager::CreateEffect(effectName); + + effect->InitEffect(vsName, psName); + + // TODO + // effect->InitFixedFuncPass(effect, &colorOp, &alphaOp, 1); + } + + return effect; +} + +int32_t CM2Shared::Initialize() { + this->skinProfile = nullptr; + + // TODO + // implement logic to select skin profile + + uint32_t profile = this->m_data->numSkinProfiles - 1; + + if (!this->LoadSkinProfile(profile)) { + return 0; + } + + void* textures = SMemAlloc(sizeof(HTEXTURE) * this->m_data->textures.count, __FILE__, __LINE__, 0x8); + this->textures = static_cast(textures); + + if (!textures) { + // TODO + // CM2Model::ErrorSetFileLine(__FILE__, __LINE__); + // CM2Model::ErrorSet("Failed to allocate texture array: %s", this + 60); + + return 0; + } + + for (int32_t i = 0; i < this->m_data->materials.count; i++) { + M2Material& material = this->m_data->materials[i]; + if (material.blendMode >= M2BLEND_MOD) { + material.flags |= 0x1; + } + } + + for (int32_t i = 0; i < this->m_data->textures.count; i++) { + M2Texture& texture = this->m_data->textures[i]; + + if (texture.filename.count > 1) { + CGxTexFlags texFlags = CGxTexFlags(GxTex_Linear, 0, 0, 0, 0, 0, 1); + + int32_t createFlags = 0; + + if (this->m_flag4) { + createFlags |= 0x2; + } + + if (this->m_flag40) { + createFlags |= 0x30; + } + + CStatus* status = &GetGlobalStatusObj(); + + this->textures[i] = TextureCreate(&texture.filename[0], texFlags, status, createFlags); + } else { + static CImVector FRIENDLY_WHITE = { 0xFF, 0xFF, 0xFF, 0xFF }; + this->textures[i] = TextureCreateSolid(FRIENDLY_WHITE); + } + } + + for (int32_t i = 0; i < this->m_data->bones.count; i++) { + M2CompBone& bone = this->m_data->bones[i]; + + if (bone.flags & (0x200 | 0x80 | 0x40 | 0x20 | 0x10 | 0x8)) { + // TODO + } + } + + return 1; +} + +int32_t CM2Shared::InitializeSkinProfile() { + this->uint194 = this->skinProfile->indices.Count() + ? 65536 / this->skinProfile->indices.Count() + : 1; + + for (int32_t i = 0; i < this->skinProfile->skinSections.Count(); i++) { + uint32_t v6 = this->skinProfile->boneCountMax / this->skinProfile->skinSections[i].boneCount; + + if (this->uint194 > v6) { + this->uint194 = v6; + } + } + + if (!this->uint194) { + this->uint194 = 1; + } + + this->uint190 = 1; + + uint32_t dataSize = sizeof(M2SkinSection) * this->skinProfile->skinSections.Count(); + if (this->skinProfile->indices.Count()) { + dataSize += sizeof(CShaderEffect*) * this->skinProfile->batches.Count(); + } + + char* data = static_cast(SMemAlloc(dataSize, __FILE__, __LINE__, 0x0)); + if (!data) { + return 0; + } + + this->m_skinSections = reinterpret_cast(data); + if (this->skinProfile->skinSections.Count()) { + data += sizeof(M2SkinSection) * this->skinProfile->skinSections.Count(); + memcpy(this->m_skinSections, this->skinProfile->skinSections.Data(), this->skinProfile->skinSections.Count() * sizeof(M2SkinSection)); + } + + if (this->skinProfile->indices.Count()) { + this->m_batchShaders = reinterpret_cast(data); + memset(this->m_batchShaders, 0, sizeof(CShaderEffect*) * this->skinProfile->batches.Count()); + } + + this->SubstituteSimpleShaders(); + this->SubstituteSpecializedShaders(); + + if (this->skinProfile->indices.Count()) { + for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) { + this->m_batchShaders[i] = this->GetEffect(&this->skinProfile->batches[i]); + } + } + + if (!(this->m_cache->m_flags & 0x8)) { + // TODO + // - non-shader-path vertex logic + } + + if (!(this->m_cache->m_flags & 0x8)) { + for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) { + auto& batch = this->skinProfile->batches[i]; + + if (batch.textureCount > 1) { + this->skinProfile->batches[i - batch.materialLayer].flags |= 0x40; + } + } + + for (int32_t i = 0; i < this->skinProfile->batches.Count(); i++) { + auto& batch = this->skinProfile->batches[i]; + + if (batch.materialLayer) { + if (this->skinProfile->batches[i - batch.materialLayer].flags & 0x40) { + batch.flags |= 0x40; + } + } + } + } + + return 1; +} + +int32_t CM2Shared::Load(SFile* file, int32_t a3, CAaBox* a4) { + // TODO + // this->dword8 ^= (this->dword8 ^ (4 * (a3 != 0))) & 4; + + this->m_dataSize = SFile::GetFileSize(file, 0); + + // TODO use proper allocation function here + this->m_data = static_cast(SMemAlloc(this->m_dataSize, __FILE__, __LINE__, 0)); + + if (!this->m_data) { + return 0; + } + + if (a4) { + this->aaBox154 = *a4; + } + + this->asyncObject = AsyncFileReadAllocObject(); + + if (!this->asyncObject) { + return 0; + } + + this->asyncObject->file = file; + this->asyncObject->buffer = this->m_data; + this->asyncObject->size = this->m_dataSize; + this->asyncObject->userArg = this; + this->asyncObject->userPostloadCallback = &CM2Shared::LoadSucceededCallback; + this->asyncObject->userFailedCallback = &CM2Shared::LoadFailedCallback; + this->asyncObject->isRead = 0; + this->asyncObject->isProcessed = 0; + + AsyncFileReadObject(this->asyncObject, 0); + + return 1; +} + +int32_t CM2Shared::LoadSkinProfile(uint32_t profile) { + // TODO + // the file path logic is in its own function + + char skinFilePath[STORM_MAX_PATH]; + + strcpy(skinFilePath, this->m_filePath); + char* v4 = strrchr(skinFilePath, '.'); + if (v4) { + *v4 = 0; + } + sprintf(&skinFilePath[strlen(skinFilePath)], "%02d.skin", profile); + + SFile* fileptr; + + if (!SFile::OpenEx(nullptr, skinFilePath, this->m_flag4, &fileptr)) { + // TODO + // error handling + + return 0; + } + + uint32_t size = SFile::GetFileSize(fileptr, nullptr); + + // TODO use proper allocation function here + this->skinProfile = static_cast(SMemAlloc(size, __FILE__, __LINE__, 0)); + + if (!this->skinProfile) { + SFile::Close(fileptr); + + return 0; + } + + this->asyncObject = AsyncFileReadAllocObject(); + + if (!this->asyncObject) { + SFile::Close(fileptr); + delete this->skinProfile; + + return 0; + } + + this->asyncObject->file = fileptr; + this->asyncObject->buffer = this->skinProfile; + this->asyncObject->size = size, + this->asyncObject->userArg = this; + this->asyncObject->userPostloadCallback = &CM2Shared::SkinProfileLoadedCallback; + this->asyncObject->userFailedCallback = &CM2Shared::LoadFailedCallback; + this->asyncObject->isRead = 0; + this->asyncObject->isProcessed = 0; + this->asyncObject->priority = 125; + + AsyncFileReadObject(this->asyncObject, 1); + + return 1; +} + +void CM2Shared::Release() { + // TODO +} + +int32_t CM2Shared::SetIndices() { + if (!this->m_indexPool) { + this->m_indexPool = GxPoolCreate( + GxPoolTarget_Index, + GxPoolUsage_Dynamic, + 2 * this->uint190 * this->skinProfile->indices.Count(), + GxPoolHintBit_Unk1, + this->ext + ); + + this->m_indexBuf = GxBufCreate( + this->m_indexPool, + 2, + this->uint190 * this->skinProfile->indices.Count(), + 0 + ); + + if (!this->m_indexPool || !this->m_indexBuf) { + return 0; + } + } + + if (!this->m_indexBuf->unk1C || !this->m_indexBuf->unk1D) { + char* indexData = GxBufLock(this->m_indexBuf); + uint16_t* indexBuf = reinterpret_cast(indexData); + + if (!indexData) { + return 0; + } + + int32_t v10 = (this->m_cache->m_flags & 0x8) != 0 + || (this->m_data->bones.Count() == 1 && (this->m_cache->m_flags & 0x40) != 0); + uint32_t v21 = 0; + + for (int32_t i = 0; i < this->skinProfile->skinSections.Count(); i++) { + auto& skinSection = this->m_skinSections[i]; + auto indexStart = this->skinProfile->skinSections[i].indexStart; + auto v25 = v10 ? 0 : -skinSection.vertexStart; + + for (int32_t j = 0; j < this->uint190; j++) { + for (int32_t k = 0; k < skinSection.indexCount; k++) { + indexBuf[k] = this->skinProfile->indices[indexStart + k + v25]; + } + + indexBuf += skinSection.indexCount; + + v25 += v10 ? this->skinProfile->vertices.Count() : skinSection.vertexCount; + } + + skinSection.indexStart = v21; + v21 += this->uint190 * skinSection.indexCount; + } + + GxBufUnlock(this->m_indexBuf, 0); + } + + GxPrimIndexPtr(this->m_indexBuf); + + return 1; +} + +int32_t CM2Shared::SetVertices(uint32_t a2) { + if (!this->m_vertexPool) { + this->m_vertexPool = GxPoolCreate( + GxPoolTarget_Vertex, + GxPoolUsage_Static, + sizeof(CGxVertexPBNT2) * this->uint190 * this->skinProfile->vertices.Count(), + GxPoolHintBit_Unk1, + this->ext + ); + + this->m_vertexBuf = GxBufCreate( + this->m_vertexPool, + sizeof(CGxVertexPBNT2), + this->uint190 * this->skinProfile->vertices.Count(), + 0 + ); + + if (!this->m_vertexPool || !this->m_vertexBuf) { + return 0; + } + } + + if (CShaderEffect::s_enableShaders) { + if (!this->m_vertexBuf->unk1C || !this->m_vertexBuf->unk1D) { + char* vertexData = GxBufLock(this->m_vertexBuf); + CGxVertexPBNT2* vertexBuf = reinterpret_cast(vertexData); + + if (!vertexData) { + return 0; + } + + auto v27 = 0; + for (int32_t i = 0; i < this->uint190; i++) { + for (int32_t j = 0; j < this->skinProfile->skinSections.Count(); j++) { + auto& skinSection = this->skinProfile->skinSections[j]; + auto vertexStart = skinSection.vertexStart; + auto vertexEnd = vertexStart + skinSection.vertexCount; + + uint32_t v25 = 0x1010101 * i * skinSection.boneCount; + + if (vertexStart < vertexEnd) { + for (int32_t k = vertexStart; k < vertexEnd; k++) { + auto vertex = &this->m_data->vertices[this->skinProfile->vertices[k]]; + memcpy(&vertexBuf[k], vertex, sizeof(CGxVertexPBNT2)); + vertexBuf[k].bi.u = v25 + this->skinProfile->bones[k].u; + } + } + } + + vertexBuf += this->skinProfile->vertices.Count(); + } + + GxBufUnlock(this->m_vertexBuf, 0); + } + + GxPrimVertexPtr(this->m_vertexBuf, GxVBF_PBNT2); + + return 1; + } else { + // TODO + // non-shader code path + + return 1; + } +} + +void CM2Shared::SubstituteSimpleShaders() { + for (int32_t batchIndex = 0; batchIndex < this->skinProfile->batches.Count(); batchIndex++) { + auto& batch = this->skinProfile->batches[batchIndex]; + + if (batch.shader & 0x8000) { + continue; + } + + auto& material = this->m_data->materials[batch.materialIndex]; + uint16_t textureCoordComboIndex = batch.textureCoordComboIndex; + + // M2Data flag 0x8: use combiner combos + if (this->m_data->flags & 0x8) { + uint16_t textureCombinerComboIndex = batch.shader; + + batch.shader = 0; + + uint16_t shader[2] = { 0, 0 }; + + for (int32_t textureIndex = 0; textureIndex < batch.textureCount; textureIndex++) { + bool isFirstTexture = textureIndex == 0; + bool isLastTexture = textureIndex == batch.textureCount - 1; + + // If this is the first texture and the batch material's blending mode is opaque, + // override the combiner mode to opaque; otherwise, use the combiner mode from the + // combiner combos + uint16_t textureCombiner = isFirstTexture && material.blendMode == M2BLEND_OPAQUE + ? M2COMBINER_OPAQUE + : this->m_data->textureCombinerCombos[textureCombinerComboIndex + textureIndex]; + + shader[textureIndex] |= textureCombiner; + + uint16_t textureCoord = this->m_data->textureCoordCombos[textureCoordComboIndex + textureIndex]; + + // If the texture coord is env, set env bit for texture + if (textureCoord > 2) { + shader[textureIndex] |= M2COMBINER_ENVMAP; + } + + // If this is the last texture and the texture coord is T2, enable bit 15 + if (isLastTexture && textureCoord == 1) { + batch.shader |= 0x4000; + } + } + + batch.shader |= (shader[0] << M2COMBINER_STAGE_SHIFT) | shader[1]; + } else { + uint16_t shader = 0; + + // If the material blend mode is opaque, force the combiner to opaque; otherwise, + // default combiner to mod + uint16_t textureCombiner = material.blendMode == M2BLEND_OPAQUE + ? M2COMBINER_OPAQUE + : M2COMBINER_MOD; + + shader |= textureCombiner; + + uint16_t textureCoord = this->m_data->textureCoordCombos[textureCoordComboIndex]; + + // If the texture coord is env, set env bit for texture + if (textureCoord > 2) { + shader |= M2COMBINER_ENVMAP; + } + + shader <<= M2COMBINER_STAGE_SHIFT; + + // If the texture coord is T2, enable bit 15 + if (textureCoord == 1) { + shader |= 0x4000; + } + + batch.shader = shader; + } + } +} + +void CM2Shared::SubstituteSpecializedShaders() { + // TODO +} diff --git a/src/model/CM2Shared.hpp b/src/model/CM2Shared.hpp new file mode 100644 index 0000000..65567fe --- /dev/null +++ b/src/model/CM2Shared.hpp @@ -0,0 +1,83 @@ +#ifndef MODEL_C_M2_SHARED_HPP +#define MODEL_C_M2_SHARED_HPP + +#include "gx/Texture.hpp" +#include +#include +#include + +class CAsyncObject; +class CGxBuf; +class CGxPool; +class CM2Cache; +class CM2Model; +class CShaderEffect; +class M2Batch; +class M2Data; +class M2SkinProfile; +class M2SkinSection; +class SFile; + +class CM2Shared { + public: + // Static functions + static void LoadFailedCallback(void* param); + static void LoadSucceededCallback(void* param); + static void SkinProfileLoadedCallback(void* param); + + // Member variables + CM2Cache* m_cache; + uint32_t m_m2DataLoaded : 1; + uint32_t m_skinProfileLoaded : 1; + uint32_t m_flag4 : 1; + uint32_t m_flag8 : 1; + uint32_t m_flag10 : 1; + uint32_t m_flag20 : 1; + uint32_t m_flag40 : 1; + CAsyncObject* asyncObject = nullptr; + CM2Model* m_callbackList = nullptr; + CM2Model** m_callbackListTail = &this->m_callbackList; + char m_filePath[STORM_MAX_PATH]; + char* ext = nullptr; + M2Data* m_data = nullptr; + CAaBox aaBox154; + uint32_t m_dataSize = 0; + M2SkinProfile* skinProfile = nullptr; + HTEXTURE* textures = nullptr; + CGxPool* m_indexPool = nullptr; + CGxBuf* m_indexBuf = nullptr; + CGxPool* m_vertexPool = nullptr; + CGxBuf* m_vertexBuf = nullptr; + CShaderEffect** m_batchShaders = nullptr; + M2SkinSection* m_skinSections = nullptr; + uint32_t uint190 = 0; + uint32_t uint194 = 0; + + // Member functions + CM2Shared(CM2Cache* cache) + : m_cache(cache) + , m_m2DataLoaded(0) + , m_skinProfileLoaded(0) + , m_flag4(0) + , m_flag8(0) + , m_flag10(0) + , m_flag20(0) + , m_flag40(0) + {}; + void AddRef(void); + int32_t CallbackWhenLoaded(CM2Model* model); + CShaderEffect* CreateSimpleEffect(uint32_t textureCount, uint16_t shader, uint16_t textureCoordComboIndex); + CShaderEffect* GetEffect(M2Batch* batch); + int32_t FinishLoadingSkinProfile(uint32_t size); + int32_t Initialize(void); + int32_t InitializeSkinProfile(void); + int32_t Load(SFile*, int32_t, CAaBox*); + int32_t LoadSkinProfile(uint32_t profile); + void Release(void); + int32_t SetIndices(void); + int32_t SetVertices(uint32_t a2); + void SubstituteSimpleShaders(void); + void SubstituteSpecializedShaders(void); +}; + +#endif diff --git a/src/model/CMakeLists.txt b/src/model/CMakeLists.txt new file mode 100644 index 0000000..86f8a56 --- /dev/null +++ b/src/model/CMakeLists.txt @@ -0,0 +1,22 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(model STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(model + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(model + PRIVATE + async + gx + math + util + PUBLIC + common + storm + tempest +) diff --git a/src/model/M2Animate.hpp b/src/model/M2Animate.hpp new file mode 100644 index 0000000..39cc269 --- /dev/null +++ b/src/model/M2Animate.hpp @@ -0,0 +1,150 @@ +#ifndef MODEL_M2_ANIMATE_HPP +#define MODEL_M2_ANIMATE_HPP + +#include "model/CM2Model.hpp" +#include "model/M2Data.hpp" +#include "model/M2Model.hpp" + +struct M2SequenceFallback { + uint16_t uint0; + uint16_t uint2; +}; + +template +void M2SetValue(const T1& sourceValue, T2& destValue) { + destValue = sourceValue; +} + +template<> +void M2SetValue(const M2CompQuat& sourceValue, C4Quaternion& destValue) { + destValue.x = (sourceValue.auCompQ[0] & 0xFFFF) * 0.000030518044f - 1.0f; + destValue.y = (sourceValue.auCompQ[0] >> 16) * 0.000030518044f - 1.0f; + destValue.z = (sourceValue.auCompQ[1] & 0xFFFF) * 0.000030518044f - 1.0f; + destValue.w = (sourceValue.auCompQ[1] >> 16) * 0.000030518044f - 1.0f; +} + +template<> +void M2SetValue(const fixed16& sourceValue, float& destValue) { + destValue = static_cast(sourceValue); +} + +void M2InterpolateLinear(const C3Vector& startValue, const C3Vector& endValue, float ratio, C3Vector& value) { + value.x = startValue.x + (ratio * (endValue.x - startValue.x)); + value.y = startValue.y + (ratio * (endValue.y - startValue.y)); + value.z = startValue.z + (ratio * (endValue.z - startValue.z)); +} + +void M2InterpolateLinear(float startValue, float endValue, float ratio, float& value) { + value = startValue + (ratio * (endValue - startValue)); +} + +void M2InterpolateLinear(fixed16 startValue, fixed16 endValue, float ratio, float& value) { + value = static_cast(startValue) + (ratio * (static_cast(endValue) - static_cast(startValue))); +} + +void M2InterpolateLinear(uint8_t startValue, uint8_t endValue, float ratio, uint8_t& value) { + value = startValue + (ratio * (endValue - startValue)); +} + +void M2InterpolateLinear(const M2CompQuat& startValue, const M2CompQuat& endValue, float ratio, C4Quaternion& value) { + C4Quaternion quat1; + C4Quaternion quat2; + M2SetValue(startValue, quat1); + M2SetValue(endValue, quat2); + + value = C4Quaternion::Nlerp(ratio, quat1, quat2); +} + +void M2InterpolateCubicBezier(const M2SplineKey& startKey, const M2SplineKey& endKey, float ratio, C3Vector& value) { + // TODO +} + +void M2InterpolateCubicBezier(const M2SplineKey& startKey, const M2SplineKey& endKey, float ratio, float& value) { + // TODO +} + +void M2InterpolateCubicHermite(const M2SplineKey& startKey, const M2SplineKey& endKey, float ratio, C3Vector& value) { + // TODO +} + +void M2InterpolateCubicHermite(const M2SplineKey& startKey, const M2SplineKey& endKey, float ratio, float& value) { + // TODO +} + +template +void M2AnimateSplineTrack(CM2Model* model, M2ModelBone* modelBone, const M2Track& track, M2ModelTrack& modelTrack, const T2& defaultValue) { + auto seqIndex = modelBone->sequence.uint4 < track.sequenceKeys.Count() ? modelBone->sequence.uint4 : 0; + auto& seqKeys = track.sequenceKeys[seqIndex]; + + if (seqKeys.keys.Count()) { + uint32_t nextKey; + float ratio; + + model->FindKey(&modelBone->sequence, track, modelTrack.currentKey, nextKey, ratio); + + if (track.trackType == 0) { + modelTrack.currentValue = seqKeys.keys[modelTrack.currentKey].value; + return; + } + + auto& startKey = seqKeys.keys[modelTrack.currentKey]; + auto& endKey = seqKeys.keys[nextKey]; + + switch (track.trackType) { + case 1: + M2InterpolateLinear(startKey.value, endKey.value, ratio, modelTrack.currentValue); + break; + + case 2: + M2InterpolateCubicBezier(startKey, endKey, ratio, modelTrack.currentValue); + break; + + case 3: + M2InterpolateCubicHermite(startKey, endKey, ratio, modelTrack.currentValue); + break; + } + } else { + modelTrack.currentValue = defaultValue; + + if (track.trackType == 0) { + return; + } + } + + // TODO + // - blend with secondary active sequence +} + +template +void M2AnimateTrack(CM2Model* model, M2ModelBone* modelBone, const M2Track& track, M2ModelTrack& modelTrack, const T2& defaultValue) { + auto seqIndex = modelBone->sequence.uint4 < track.sequenceKeys.Count() ? modelBone->sequence.uint4 : 0; + auto& seqKeys = track.sequenceKeys[seqIndex]; + + if (seqKeys.keys.Count()) { + uint32_t nextKey; + float ratio; + + model->FindKey(&modelBone->sequence, track, modelTrack.currentKey, nextKey, ratio); + + if (track.trackType == 0) { + M2SetValue(seqKeys.keys[modelTrack.currentKey], modelTrack.currentValue); + return; + } + + auto& startValue = seqKeys.keys[modelTrack.currentKey]; + auto& endValue = seqKeys.keys[nextKey]; + + M2InterpolateLinear(startValue, endValue, ratio, modelTrack.currentValue); + } else { + modelTrack.currentValue = defaultValue; + + if (track.trackType == 0) { + return; + } + } + + // TODO + // - blend with secondary active sequence +} + +#endif diff --git a/src/model/M2Data.hpp b/src/model/M2Data.hpp new file mode 100644 index 0000000..0bdf4e5 --- /dev/null +++ b/src/model/M2Data.hpp @@ -0,0 +1,409 @@ +#ifndef MODEL_M2_DATA_HPP +#define MODEL_M2_DATA_HPP + +#include "gx/Buffer.hpp" +#include "math/Types.hpp" +#include +#include +#include + +/* + M2Array has been modified from the implementation present in 12340. The + implementation present in 12340 looks like this: + + template + struct M2Array { + uint32_t count; + union { + T* data; + uint32_t offset; + } + }; + + On a 32-bit system, sizeof(M2Array) == 8 bytes in memory: 4 bytes for the + count, and 4 bytes for the union. This lines up with M2Array in the .m2 + files: each M2Array is 8 bytes. + + In 12340 (and until 64-bit support was introduced), the M2Init functions + simply adjust the M2Array when loading: + + m2Data->someM2Array.offset = + (uint32_t)m2Data + m2Data->someM2Array.offset; + + This ensures T* data points to the appropriate (absolute) location in + memory. + + Unfortunately, this approach fails on 64-bit systems. On a 64-bit system, + M2Array would occupy 12 bytes in memory: 4 bytes for the count, and 8 bytes + for the union. This would make the approach outlined above fail. + + As a result, on 64-bit systems, a different approach is used: M2Arrays are + assumed (reasonably so) to only exist within the same structure that their + on-disk offsets reference. Thus, M2Init adjusts the M2Array when loading: + + uintptr_t absoluteOffset = + (uintptr_t)m2Data + m2Data->someM2Array.offset; + uintptr_t relativeOffset = + absoluteOffset - (uintptr_t)&m2Data->someM2Array; + m2Data->someM2Array.offset = + (uint32_t)relativeOffset; + + By storing the relative offset, access to the data is possible by adding + the relative offset to the address of the M2Array: + + uintptr_t absoluteOffset = + (uintptr_t)m2Data->someM2Array + m2Data->someM2Array.offset; + T* data = (T*)absoluteOffset; +*/ + +template +struct M2Array { + uint32_t count; + uint32_t offset; + + T& operator[](uint32_t i); + T& operator[](uint32_t i) const; + uint32_t Count(void); + uint32_t Count(void) const; + T* Data(void); +}; + +template +T& M2Array::operator[](uint32_t i) { + T* data = reinterpret_cast(reinterpret_cast(this) + this->offset); + return data[i]; +} + +template +T& M2Array::operator[](uint32_t i) const { + T* data = reinterpret_cast(reinterpret_cast(this) + this->offset); + return data[i]; +} + +template +uint32_t M2Array::Count() { + return this->count; +} + +template +uint32_t M2Array::Count() const { + return this->count; +} + +template +T* M2Array::Data() { + T* data = reinterpret_cast(reinterpret_cast(this) + this->offset); + return data; +} + +template +struct M2SequenceKeys { + M2Array keys; +}; + +struct M2SequenceTimes { + M2Array times; +}; + +struct M2TrackBase { + uint16_t trackType; + uint16_t loopIndex; + M2Array sequenceTimes; +}; + +template +class M2Track : public M2TrackBase { + public: + M2Array> sequenceKeys; +}; + +struct M2Attachment { + uint32_t attachmentId; + uint16_t boneIndex; + C3Vector position; + M2Track visibilityTrack; +}; + +struct M2Batch { + uint8_t flags; + int8_t priorityPlane; + uint16_t shader; + uint16_t skinSectionIndex; + uint16_t geosetIndex; + uint16_t colorIndex; + uint16_t materialIndex; + uint16_t materialLayer; + uint16_t textureCount; + uint16_t textureComboIndex; + uint16_t textureCoordComboIndex; + uint16_t textureWeightComboIndex; + uint16_t textureTransformComboIndex; +}; + +struct M2Bounds { + CAaBox extent; + float radius; +}; + +template +struct M2SplineKey { + T value; + T inTan; + T outTan; +}; + +struct M2Camera { + uint32_t cameraId; + float fieldOfView; + float farClip; + float nearClip; + M2Track> positionTrack; + C3Vector positionPivot; + M2Track> targetTrack; + C3Vector targetPivot; + M2Track> rollTrack; +}; + +struct M2Color { + M2Track colorTrack; + M2Track alphaTrack; +}; + +struct M2CompQuat { + uint32_t auCompQ[2]; +}; + +struct M2CompBone { + uint32_t boneId; + uint32_t flags; + uint16_t parentIndex; + uint16_t uDistToParent; + union { + struct { + uint16_t uDistToFurthDesc; + uint16_t uZRatioOfChain; + } CompressData; + uint32_t boneNameCRC; + }; + M2Track translationTrack; + M2Track rotationTrack; + M2Track scaleTrack; + C3Vector pivot; +}; + +struct M2Event { + uint32_t eventId; + uint32_t data; + uint16_t boneIndex; + C3Vector position; + M2TrackBase eventTrack; +}; + +struct M2Light { + uint16_t lightType; + uint16_t boneIndex; + C3Vector position; + M2Track ambientColorTrack; + M2Track ambientIntensityTrack; + M2Track diffuseColorTrack; + M2Track diffuseIntensityTrack; + M2Track attenuationStartTrack; + M2Track attenuationEndTrack; + M2Track visibilityTrack; +}; + +struct M2Loop { + uint32_t length; +}; + +struct M2Material { + uint16_t flags; + uint16_t blendMode; +}; + +template +struct M2PartTrack { + M2Array times; + M2Array values; +}; + +struct M2Particle { + uint32_t particleId; + uint32_t flags; + C3Vector position; + uint16_t boneIndex; + uint16_t textureIndex; + M2Array geometryMdl; + M2Array recursionMdl; + uint8_t blendMode; + uint8_t emitterType; + uint16_t colorIndex; + uint16_t pad; + int16_t priorityPlane; + uint16_t rows; + uint16_t cols; + M2Track speedTrack; + M2Track variationTrack; + M2Track latitudeTrack; + M2Track longitudeTrack; + M2Track gravityTrack; + M2Track lifeTrack; + float lifeVariation; + M2Track emissionRateTrack; + float emissionRateVariation; + M2Track widthTrack; + M2Track lengthTrack; + M2Track zsourceTrack; + M2PartTrack colorTrack; + M2PartTrack alphaTrack; + M2PartTrack scaleTrack; + C2Vector scaleVariation; + M2PartTrack headCellTrack; + M2PartTrack tailCellTrack; + float tailLength; + float twinkleFPS; + float twinkleOnOff; + CRange twinkleScale; + float ivelScale; + float drag; + float initialSpin; + float initialSpinVariation; + float spin; + float spinVariation; + CAaBox tumble; + C3Vector windVector; + float windTime; + float followSpeed1; + float followScale1; + float followSpeed2; + float followScale2; + M2Array spline; + M2Track visibilityTrack; +}; + +struct M2Ribbon { + uint32_t ribbonId; + uint16_t boneIndex; + C3Vector position; + M2Array textureIndices; + M2Array materialIndices; + M2Track colorTrack; + M2Track alphaTrack; + M2Track heightAboveTrack; + M2Track heightBelowTrack; + float edgesPerSecond; + float edgeLifetime; + float gravity; + uint16_t textureRows; + uint16_t textureCols; + M2Track textureSlotTrack; + M2Track visibilityTrack; + int16_t priorityPlane; + uint16_t pad; +}; + +struct M2Sequence { + uint16_t id; + uint16_t variationIndex; + uint32_t duration; + float movespeed; + uint32_t flags; + uint32_t frequency; + CiRange replay; + uint32_t blendtime; + M2Bounds bounds; + uint16_t variationNext; + uint16_t aliasNext; +}; + +struct M2SkinSection { + uint32_t skinSectionId; + uint16_t vertexStart; + uint16_t vertexCount; + uint16_t indexStart; + uint16_t indexCount; + uint16_t boneCount; + uint16_t boneComboIndex; + uint16_t boneInfluences; + uint16_t centerBoneIndex; + C3Vector centerPosition; + C3Vector sortCenterPosition; + float sortRadius; +}; + +struct M2Texture { + uint32_t textureId; + uint16_t flags; + M2Array filename; +}; + +struct M2TextureTransform { + M2Track translationTrack; + M2Track rotationTrack; + M2Track scaleTrack; +}; + +struct M2TextureWeight { + M2Track weightTrack; +}; + +struct M2Vertex { + C3Vector position; + ubyte4 weights; + ubyte4 indices; + C3Vector normal; + C2Vector texcoord[2]; +}; + +// .m2 files +struct M2Data { + uint32_t MD20; + uint32_t version; + M2Array name; + uint32_t flags; + M2Array loops; + M2Array sequences; + M2Array sequenceIdxHashById; + M2Array bones; + M2Array boneIndicesById; + M2Array vertices; + uint32_t numSkinProfiles; + M2Array colors; + M2Array textures; + M2Array textureWeights; + M2Array textureTransforms; + M2Array textureIndicesById; + M2Array materials; + M2Array boneCombos; + M2Array textureCombos; + M2Array textureCoordCombos; + M2Array textureWeightCombos; + M2Array textureTransformCombos; + M2Bounds bounds; + M2Bounds collisionBounds; + M2Array collisionIndices; + M2Array collisionPositions; + M2Array collisionFaceNormals; + M2Array attachments; + M2Array attachmentIndicesById; + M2Array events; + M2Array lights; + M2Array cameras; + M2Array cameraIndicesById; + M2Array ribbons; + M2Array particles; + M2Array textureCombinerCombos; +}; + +// .skin files +struct M2SkinProfile { + uint32_t magic; + M2Array vertices; + M2Array indices; + M2Array bones; + M2Array skinSections; + M2Array batches; + uint32_t boneCountMax; +}; + +#endif diff --git a/src/model/M2Init.cpp b/src/model/M2Init.cpp new file mode 100644 index 0000000..2e5aa80 --- /dev/null +++ b/src/model/M2Init.cpp @@ -0,0 +1,538 @@ +#include "model/M2Init.hpp" + +int32_t M2Init(uint8_t* base, uint32_t size, M2Data& data) { + if (!M2Init(base, size, data, data.name)) { + return 0; + } + + if (!M2Init(base, size, data, data.loops)) { + return 0; + } + + if (!M2Init(base, size, data, data.sequences)) { + return 0; + } + + if (!M2Init(base, size, data, data.sequenceIdxHashById)) { + return 0; + } + + if (!M2Init(base, size, data, data.bones)) { + return 0; + } + + if (!M2Init(base, size, data, data.boneIndicesById)) { + return 0; + } + + if (!M2Init(base, size, data, data.vertices)) { + return 0; + } + + if (!M2Init(base, size, data, data.colors)) { + return 0; + } + + if (!M2Init(base, size, data, data.textures)) { + return 0; + } + + if (!M2Init(base, size, data, data.textureWeights)) { + return 0; + } + + if (!M2Init(base, size, data, data.textureTransforms)) { + return 0; + } + + if (!M2Init(base, size, data, data.textureIndicesById)) { + return 0; + } + + if (!M2Init(base, size, data, data.materials)) { + return 0; + } + + if (!M2Init(base, size, data, data.boneCombos)) { + return 0; + } + + if (!M2Init(base, size, data, data.textureCombos)) { + return 0; + } + + if (!M2Init(base, size, data, data.textureCoordCombos)) { + return 0; + } + + if (!M2Init(base, size, data, data.textureWeightCombos)) { + return 0; + } + + if (!M2Init(base, size, data, data.textureTransformCombos)) { + return 0; + } + + if (!M2Init(base, size, data, data.bounds)) { + return 0; + } + + if (!M2Init(base, size, data, data.collisionBounds)) { + return 0; + } + + if (!M2Init(base, size, data, data.collisionIndices)) { + return 0; + } + + if (!M2Init(base, size, data, data.collisionPositions)) { + return 0; + } + + if (!M2Init(base, size, data, data.collisionFaceNormals)) { + return 0; + } + + if (!M2Init(base, size, data, data.attachments)) { + return 0; + } + + if (!M2Init(base, size, data, data.attachmentIndicesById)) { + return 0; + } + + if (!M2Init(base, size, data, data.events)) { + return 0; + } + + if (!M2Init(base, size, data, data.lights)) { + return 0; + } + + if (!M2Init(base, size, data, data.cameras)) { + return 0; + } + + if (!M2Init(base, size, data, data.cameraIndicesById)) { + return 0; + } + + if (!M2Init(base, size, data, data.ribbons)) { + return 0; + } + + if (!M2Init(base, size, data, data.particles)) { + return 0; + } + + if (data.flags & 0x8) { + if (!M2Init(base, size, data, data.textureCombinerCombos)) { + return 0; + } + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, char& value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, fixed16& value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, float& value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint8_t value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, ubyte4 value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint16_t value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint32_t value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C2Vector& value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C3Vector& value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Attachment& attachment) { + if (!M2Init(base, size, data, attachment.visibilityTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Batch& batch) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Bounds& bounds) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Camera& camera) { + if (!M2Init>(base, size, data, camera.positionTrack)) { + return 0; + } + + if (!M2Init>(base, size, data, camera.targetTrack)) { + return 0; + } + + if (!M2Init>(base, size, data, camera.rollTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Color& color) { + if (!M2Init(base, size, data, color.colorTrack)) { + return 0; + } + + if (!M2Init(base, size, data, color.alphaTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompBone& bone) { + if (!M2Init(base, size, data, bone.translationTrack)) { + return 0; + } + + if (!M2Init(base, size, data, bone.rotationTrack)) { + return 0; + } + + if (!M2Init(base, size, data, bone.scaleTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompQuat& value) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Event& event) { + if (!M2Init(base, size, data, event.eventTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Light& light) { + if (!M2Init(base, size, data, light.ambientColorTrack)) { + return 0; + } + + if (!M2Init(base, size, data, light.ambientIntensityTrack)) { + return 0; + } + + if (!M2Init(base, size, data, light.diffuseColorTrack)) { + return 0; + } + + if (!M2Init(base, size, data, light.diffuseIntensityTrack)) { + return 0; + } + + if (!M2Init(base, size, data, light.attenuationStartTrack)) { + return 0; + } + + if (!M2Init(base, size, data, light.attenuationEndTrack)) { + return 0; + } + + if (!M2Init(base, size, data, light.visibilityTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Loop& loop) { + if (!M2Init(base, size, data, loop.length)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Material& material) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Particle& particle) { + if (!M2Init(base, size, data, particle.geometryMdl)) { + return 0; + } + + if (!M2Init(base, size, data, particle.recursionMdl)) { + return 0; + } + + if (!M2Init(base, size, data, particle.speedTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.variationTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.latitudeTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.longitudeTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.gravityTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.lifeTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.emissionRateTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.widthTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.lengthTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.zsourceTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.colorTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.alphaTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.scaleTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.headCellTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.tailCellTrack)) { + return 0; + } + + if (!M2Init(base, size, data, particle.visibilityTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Ribbon& ribbon) { + if (!M2Init(base, size, data, ribbon.textureIndices)) { + return 0; + } + + if (!M2Init(base, size, data, ribbon.materialIndices)) { + return 0; + } + + if (!M2Init(base, size, data, ribbon.colorTrack)) { + return 0; + } + + if (!M2Init(base, size, data, ribbon.alphaTrack)) { + return 0; + } + + if (!M2Init(base, size, data, ribbon.heightAboveTrack)) { + return 0; + } + + if (!M2Init(base, size, data, ribbon.heightBelowTrack)) { + return 0; + } + + if (!M2Init(base, size, data, ribbon.textureSlotTrack)) { + return 0; + } + + if (!M2Init(base, size, data, ribbon.visibilityTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Sequence& sequence) { + if (sequence.flags & 0x1) { + sequence.flags |= 0x80; + } + + switch (sequence.id) { + case 0: + case 4: + case 5: + case 13: + case 41: + case 42: + case 43: + case 44: + case 45: + case 69: + case 119: + case 120: + case 143: + case 223: + sequence.flags &= ~0x1; + break; + + default: + break; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SequenceTimes& track) { + if (!M2Init(base, size, data, track.times)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinProfile& skinProfile) { + if (!M2Init(base, size, data, skinProfile.vertices)) { + return 0; + } + + if (!M2Init(base, size, data, skinProfile.indices)) { + return 0; + } + + if (!M2Init(base, size, data, skinProfile.bones)) { + return 0; + } + + if (!M2Init(base, size, data, skinProfile.skinSections)) { + return 0; + } + + if (!M2Init(base, size, data, skinProfile.batches)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinSection& skinSection) { + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Texture& texture) { + if (!M2Init(base, size, data, texture.filename)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureTransform& transform) { + if (!M2Init(base, size, data, transform.translationTrack)) { + return 0; + } + + if (!M2Init(base, size, data, transform.rotationTrack)) { + return 0; + } + + if (!M2Init(base, size, data, transform.scaleTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureWeight& weight) { + if (!M2Init(base, size, data, weight.weightTrack)) { + return 0; + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TrackBase& track) { + if (CM2Model::s_loadingSequence == 0xFFFFFFFF) { + if (track.loopIndex == 0xFFFF) { + if (track.sequenceTimes.count) { + uintptr_t absoluteOffset = reinterpret_cast(base) + track.sequenceTimes.offset; + uintptr_t relativeOffset = absoluteOffset - reinterpret_cast(&track.sequenceTimes); + + track.sequenceTimes.offset = relativeOffset; + } else { + track.sequenceTimes.offset = 0; + } + + for (int32_t i = 0; i < track.sequenceTimes.count; i++) { + auto& keys = track.sequenceTimes[i]; + + if (!M2InitSequenceKeyFrames(base, size, data, i, keys.times)) { + return 0; + } + } + } else { + if (!M2Init(base, size, data, track.sequenceTimes)) { + return 0; + } + } + } else { + if (CM2Model::s_loadingSequence < track.sequenceTimes.count) { + auto& keys = track.sequenceTimes[CM2Model::s_loadingSequence]; + + if (!M2InitKeyFrameData(CM2Model::s_sequenceBase, CM2Model::s_sequenceBaseSize, data, CM2Model::s_loadingSequence, keys.times)) { + return 0; + } + } + } + + return 1; +} + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Vertex& vertex) { + return 1; +} diff --git a/src/model/M2Init.hpp b/src/model/M2Init.hpp new file mode 100644 index 0000000..1005ef9 --- /dev/null +++ b/src/model/M2Init.hpp @@ -0,0 +1,219 @@ +#ifndef MODEL_M2_INIT_HPP +#define MODEL_M2_INIT_HPP + +#include "model/M2Data.hpp" +#include "model/CM2Model.hpp" + +int32_t M2Init(uint8_t* base, uint32_t size, M2Data& data); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, char& value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, fixed16& value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, float& value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint8_t value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, ubyte4 value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint16_t value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, uint32_t value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C2Vector& value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, C3Vector& value); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Attachment& attachment); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Batch& batch); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Bounds& bounds); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Camera& camera); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Color& color); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompBone& bone); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2CompQuat& quat); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Event& event); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Light& light); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Loop& loop); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Material& material); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Particle& particle); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Ribbon& ribbon); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Sequence& sequence); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SequenceTimes& track); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinProfile& skinProfile); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SkinSection& skinSection); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Texture& texture); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureTransform& transform); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TextureWeight& weight); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2TrackBase& track); + +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Vertex& vertex); + +template +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Array& array) { + if (CM2Model::s_loadingSequence == 0xFFFFFFFF) { + if (array.offset > size) { + return 0; + } + + if (array.offset + (array.count * sizeof(T)) > size) { + return 0; + } + + if (array.count) { + uintptr_t absoluteOffset = reinterpret_cast(base) + array.offset; + uintptr_t relativeOffset = absoluteOffset - reinterpret_cast(&array); + + array.offset = relativeOffset; + } else { + array.offset = 0; + } + } + + for (int32_t i = 0; i < array.count; i++) { + auto& element = array[i]; + + if (!M2Init(base, size, data, element)) { + return 0; + } + } + + return 1; +} + +template +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2PartTrack& track) { + if (!M2Init(base, size, data, track.times)) { + return 0; + } + + if (!M2Init(base, size, data, track.values)) { + return 0; + } + + return 1; +} + +template +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SequenceKeys& track) { + if (!M2Init(base, size, data, track.keys)) { + return 0; + } + + return 1; +} + +template +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2SplineKey& key) { + return 1; +} + +template +int32_t M2InitKeyFrameData(uint8_t* base, uint32_t size, const M2Data& data, uint32_t sequence, M2Array& keys) { + if (keys.offset > size) { + return 0; + } + + if (keys.offset + (keys.count * sizeof(T)) > size) { + return 0; + } + + if (keys.count) { + uintptr_t absoluteOffset = reinterpret_cast(base) + keys.offset; + uintptr_t relativeOffset = absoluteOffset - reinterpret_cast(&keys); + + keys.offset = relativeOffset; + } else { + keys.offset = 0; + } + + uint32_t sequenceFlags = data.sequences[sequence].flags; + + if (sequenceFlags & 0x40) { + return 1; + } + + for (int32_t i = 0; i < keys.count; i++) { + auto& element = keys[i]; + + if (!M2Init(base, size, data, element)) { + return 0; + } + } + + return 1; +} + +template +int32_t M2InitSequenceKeyFrames(uint8_t* base, uint32_t size, const M2Data& data, uint32_t sequence, M2Array& keys) { + uint32_t sequenceFlags = data.sequences[sequence].flags; + + if (sequenceFlags & 0x20) { + if (!M2InitKeyFrameData(base, size, data, sequence, keys)) { + return 0; + } + } + + return 1; +} + +template +int32_t M2Init(uint8_t* base, uint32_t size, const M2Data& data, M2Track& track) { + if (!M2Init(base, size, data, static_cast(track))) { + return 0; + } + + if (CM2Model::s_loadingSequence == 0xFFFFFFFF) { + if (track.loopIndex == 0xFFFF) { + if (track.sequenceKeys.count) { + uintptr_t absoluteOffset = reinterpret_cast(base) + track.sequenceKeys.offset; + uintptr_t relativeOffset = absoluteOffset - reinterpret_cast(&track.sequenceKeys); + + track.sequenceKeys.offset = relativeOffset; + } else { + track.sequenceKeys.offset = 0; + } + + for (int32_t i = 0; i < track.sequenceTimes.count; i++) { + auto& keys = track.sequenceKeys[i]; + + if (!M2InitSequenceKeyFrames(base, size, data, i, keys.keys)) { + return 0; + } + } + } else { + if (!M2Init>(base, size, data, track.sequenceKeys)) { + return 0; + } + } + } else if (CM2Model::s_loadingSequence < track.sequenceKeys.count) { + auto& keys = track.sequenceKeys[CM2Model::s_loadingSequence]; + + if (!M2InitKeyFrameData(CM2Model::s_sequenceBase, CM2Model::s_sequenceBaseSize, data, CM2Model::s_loadingSequence, keys.keys)) { + return 0; + } + } + + return 1; +} + +#endif diff --git a/src/model/M2Internal.cpp b/src/model/M2Internal.cpp new file mode 100644 index 0000000..babca0d --- /dev/null +++ b/src/model/M2Internal.cpp @@ -0,0 +1,3 @@ +#include "model/M2Internal.hpp" + +uint32_t* g_modelPool; diff --git a/src/model/M2Internal.hpp b/src/model/M2Internal.hpp new file mode 100644 index 0000000..bf20165 --- /dev/null +++ b/src/model/M2Internal.hpp @@ -0,0 +1,8 @@ +#ifndef MODEL_M2_INTERNAL_HPP +#define MODEL_M2_INTERNAL_HPP + +#include + +extern uint32_t* g_modelPool; + +#endif diff --git a/src/model/M2Model.hpp b/src/model/M2Model.hpp new file mode 100644 index 0000000..abca706 --- /dev/null +++ b/src/model/M2Model.hpp @@ -0,0 +1,82 @@ +#ifndef MODEL_M2_MODEL_HPP +#define MODEL_M2_MODEL_HPP + +#include "gx/Camera.hpp" +#include "model/CM2Light.hpp" +#include +#include +#include + +template +class M2Track; + +template +struct M2ModelTrack { + uint32_t currentKey = 0; + M2Track* sourceTrack = nullptr; + T currentValue; +}; + +struct M2ModelAttachment { +}; + +struct M2ModelBoneSeq { + uint32_t uint0 = 0; + uint16_t uint4 = -1; + uint16_t uint6 = -1; + uint16_t uint8 = -1; + uint8_t uintA = 1; + uint8_t uintB = 1; + uint32_t uintC = 0; + uint32_t uint10 = 0; + float float14 = 0.0; + float float18 = 0.0; + uint32_t uint1C = 0; + uint32_t uint20 = 0; +}; + +struct M2ModelBone { + M2ModelTrack translationTrack; + M2ModelTrack rotationTrack; + M2ModelTrack scaleTrack; + M2ModelBoneSeq sequence; + M2ModelBoneSeq secondarySequence; + uint32_t flags = 0; + uint32_t uint90 = -1; + uint16_t uint94 = 0; + uint32_t uint9C = 0; + float floatA0 = 0.0f; + float floatA4 = 1.0f; + float floatA8 = 0.0f; +}; + +struct M2ModelCamera { + M2ModelTrack positionTrack; + M2ModelTrack targetTrack; + M2ModelTrack rollTrack; + HCAMERA m_camera = nullptr; +}; + +struct M2ModelColor { + M2ModelTrack colorTrack; + M2ModelTrack alphaTrack; +}; + +struct M2ModelLight { + M2ModelTrack ambientColorTrack; + M2ModelTrack ambientIntensityTrack; + M2ModelTrack diffuseColorTrack; + M2ModelTrack diffuseIntensityTrack; + M2ModelTrack visibilityTrack; + uint32_t uint64 = 1; + CM2Light light; +}; + +struct M2ModelTextureTransform { +}; + +struct M2ModelTextureWeight { + M2ModelTrack weightTrack; +}; + +#endif diff --git a/src/model/M2Sort.cpp b/src/model/M2Sort.cpp new file mode 100644 index 0000000..1d6625a --- /dev/null +++ b/src/model/M2Sort.cpp @@ -0,0 +1,83 @@ +#include "model/M2Sort.hpp" + +void M2HeapSort(int32_t (*sortFunc)(uint32_t, uint32_t, const void*), uint32_t* indices, uint32_t count, const void* userArg) { + if (count <= 1) { + return; + } + + auto v5 = count / 2; + + if (v5) { + auto v6 = 2 * v5 + 1; + + do { + auto v7 = indices[--v5]; + v6 -= 2; + auto v15 = v5; + auto v14 = v6; + auto v22 = v5; + auto v18 = v7; + + if (v6 < count) { + do { + auto v20 = indices[v6]; + + if (v6 + 1 < count) { + auto v17 = indices[v6 + 1]; + + if (sortFunc(v17, indices[v6], userArg) > 0) { + v6++; + v20 = v17; + } + } + + if (sortFunc(v20, v18, userArg) <= 0) { + break; + } + + auto v8 = v22; + auto v9 = indices[v6]; + v22 = v6; + v6 = 2 * v6 + 1; + indices[v8] = v9; + } while (v6 < count); + + v5 = v15; + v6 = v14; + } + + indices[v22] = v18; + } while (v5); + } + + uint32_t j, v21, v12, v13; + for (auto i = count - 1; i; indices[j] = v21) { + auto v11 = 1; + v21 = indices[i]; + indices[i] = *indices; + + for (j = 0; v11 < i; indices[v12] = v13) { + auto v19 = indices[v11]; + + if ( v11 + 1 < i ) { + auto v16 = indices[v11 + 1]; + + if (sortFunc(v16, indices[v11], userArg) > 0) { + v11++; + v19 = v16; + } + } + + if (sortFunc(v19, v21, userArg) <= 0) { + break; + } + + v12 = j; + v13 = indices[v11]; + j = v11; + v11 = 2 * v11 + 1; + } + + i--; + } +} diff --git a/src/model/M2Sort.hpp b/src/model/M2Sort.hpp new file mode 100644 index 0000000..d20b0a8 --- /dev/null +++ b/src/model/M2Sort.hpp @@ -0,0 +1,8 @@ +#ifndef MODEL_M2_SORT_HPP +#define MODEL_M2_SORT_HPP + +#include + +void M2HeapSort(int32_t (*sortFunc)(uint32_t, uint32_t, const void*), uint32_t* indices, uint32_t count, const void* userArg); + +#endif diff --git a/src/model/M2Types.hpp b/src/model/M2Types.hpp new file mode 100644 index 0000000..4a10831 --- /dev/null +++ b/src/model/M2Types.hpp @@ -0,0 +1,62 @@ +#ifndef MODEL_M2_TYPES_HPP +#define MODEL_M2_TYPES_HPP + +#include "M2Data.hpp" + +class CM2Model; +class CShaderEffect; + +enum M2BLEND { + M2BLEND_OPAQUE = 0x0, + M2BLEND_ALPHA_KEY = 0x1, + M2BLEND_ALPHA = 0x2, + M2BLEND_NO_ALPHA_ADD = 0x3, + M2BLEND_ADD = 0x4, + M2BLEND_MOD = 0x5, + M2BLEND_MOD_2X = 0x6, + M2BLEND_COUNT = 0x7, +}; + +enum M2COMBINER { + M2COMBINER_OPAQUE = 0x0, + M2COMBINER_MOD = 0x1, + M2COMBINER_DECAL = 0x2, + M2COMBINER_ADD = 0x3, + M2COMBINER_MOD2X = 0x4, + M2COMBINER_FADE = 0x5, + M2COMBINER_MOD2X_NA = 0x6, + M2COMBINER_ADD_NA = 0x7, + M2COMBINER_OP_MASK = 0x7, + M2COMBINER_ENVMAP = 0x8, + M2COMBINER_STAGE_SHIFT = 0x4, +}; + +enum M2LIGHTTYPE { + M2LIGHT_0 = 0, + M2LIGHT_1 = 1 +}; + +enum M2PASS { + M2PASS_0 = 0, + M2PASS_1 = 1, + M2PASS_2 = 2, + M2PASS_COUNT = 3 +}; + +struct M2Element { + int32_t type; + CM2Model* model; + uint32_t flags; + float alpha; + float float10; + float float14; + int32_t index; + int32_t priorityPlane; + M2Batch* batch; + M2SkinSection* skinSection; + CShaderEffect* effect; + uint32_t vertexPermute; + uint32_t pixelPermute; +}; + +#endif diff --git a/src/model/Model2.cpp b/src/model/Model2.cpp new file mode 100644 index 0000000..fd4356a --- /dev/null +++ b/src/model/Model2.cpp @@ -0,0 +1,276 @@ +#include "model/Model2.hpp" +#include "model/CM2Cache.hpp" +#include "model/M2Internal.hpp" +#include "util/CVar.hpp" +#include "util/Filesystem.hpp" +#include +#include +#include +#include +#include + +static CVar* s_M2UseZFillVar; +static CVar* s_M2UseClipPlanesVar; +static CVar* s_M2UseThreadsVar; +static CVar* s_M2BatchDoodadsVar; +static CVar* s_M2BatchParticlesVar; +static CVar* s_M2ForceAdditiveParticleSortVar; +static CVar* s_M2FasterVar; +static CVar* s_M2FasterDebugVar; + +uint32_t M2ConvertFasterFlags(int32_t faster, int32_t debugFaster) { + uint32_t flags = 0x0; + + switch (faster) { + case 0: { + if (debugFaster) { + // TODO + } else { + flags = 0x0; + } + + break; + } + + case 1: { + flags = 0x2000 | 0x4000 | 0x8000; + break; + } + + case 2: + case 3: { + flags = 0x2000; + break; + } + + default: { + flags = 0x0; + } + } + + return flags; +} + +bool BatchDoodadsCallback(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) { + // TODO + return true; +} + +bool BatchParticlesCallback(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) { + // TODO + return true; +} + +bool ForceAdditiveParticleSortCallback(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) { + // TODO + return true; +} + +bool M2FasterChanged(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) { + uint16_t flags = s_M2FasterDebugVar + ? M2ConvertFasterFlags(SStrToInt(newValue), s_M2FasterDebugVar->GetInt()) + : M2ConvertFasterFlags(SStrToInt(newValue), 0); + + M2SetGlobalOptFlags(flags); + + return true; +} + +bool M2DebugFasterChanged(CVar* cvar, char const* oldValue, char const* newValue, void* userArg) { + uint16_t flags = s_M2FasterVar + ? M2ConvertFasterFlags(s_M2FasterVar->GetInt(), SStrToInt(newValue)) + : M2ConvertFasterFlags(0, SStrToInt(newValue)); + + M2SetGlobalOptFlags(flags); + + return true; +} + +int32_t M2ConvertModelFileName(const char* source, char* dest, uint32_t a3, uint32_t a4) { + SStrCopy(dest, source, a3); + SStrLower(dest); + + char* ext = OsPathFindExtensionWithDot(dest); + + if ((a4 & 0x1000) != 0) { + return 1; + } + + int32_t invalidExt = + !*ext || (strcmp(ext, ".mdl") && strcmp(ext, ".mdx") && strcmp(ext, ".m2")); + + if (invalidExt) { + // TODO + // OsOutputDebugString("Model2: Invalid file extension: %s\n", dest); + + return 0; + } + + if (!strcmp(ext, ".m2")) { + return 1; + } + + strcpy(ext, ".m2"); + + return 1; +} + +CM2Scene* M2CreateScene() { + void* m = SMemAlloc(sizeof(CM2Scene), __FILE__, __LINE__, 0x0); + return new (m) CM2Scene(&CM2Cache::s_cache); +} + +uint32_t M2GetCacheFlags() { + return CM2Cache::s_cache.m_flags; +} + +void M2Initialize(uint16_t flags, uint32_t a2) { + CM2Cache::s_cache.Initialize(flags); + + if (!a2) { + a2 = 2048; + } + + uint32_t* heapId = static_cast(SMemAlloc(sizeof(uint32_t), __FILE__, __LINE__, 0)); + *heapId = ObjectAllocAddHeap(sizeof(CM2Model), a2, "CM2Model", 1); + + g_modelPool = heapId; +} + +void M2SetGlobalOptFlags(uint16_t flags) { + flags &= (0x2000 | 0x4000 | 0x8000); + CM2Cache::s_cache.m_flags |= flags; +} + +uint32_t M2RegisterCVars() { + s_M2UseZFillVar = CVar::Register( + "M2UseZFill", + "z-fill transparent objects", + 0, + "1", + nullptr, + 1, + false, + nullptr, + false + ); + + s_M2UseClipPlanesVar = CVar::Register( + "M2UseClipPlanes", + "use clip planes for sorting transparent objects", + 0, + "1", + nullptr, + 1, + false, + nullptr, + false + ); + + s_M2UseThreadsVar = CVar::Register( + "M2UseThreads", + "multithread model animations", + 0, + "1", + nullptr, + 1, + false, + nullptr, + false + ); + + s_M2BatchDoodadsVar = CVar::Register( + "M2BatchDoodads", + "combine doodads to reduce batch count", + 0, + "1", + BatchDoodadsCallback, + 1, + false, + nullptr, + false + ); + + s_M2BatchParticlesVar = CVar::Register( + "M2BatchParticles", + "combine particle emitters to reduce batch count", + 0, + "1", + BatchParticlesCallback, + 1, + false, + nullptr, + false + ); + + s_M2ForceAdditiveParticleSortVar = CVar::Register( + "M2ForceAdditiveParticleSort", + "force all particles to sort as though they were additive", + 0, + "0", + ForceAdditiveParticleSortCallback, + 1, + false, + nullptr, + false + ); + + s_M2FasterVar = CVar::Register( + "M2Faster", + "end user control of scene optimization mode - (0-3)", + 0, + "1", + M2FasterChanged, + 1, + false, + nullptr, + false + ); + + s_M2FasterDebugVar = CVar::Register( + "M2FasterDebug", + "programmer control of scene optimization mode", + 0, + "0", + M2DebugFasterChanged, + 1, + false, + nullptr, + false + ); + + uint32_t flags = 0; + + if (s_M2UseZFillVar->GetInt()) { + flags |= 0x1; + } + + if (s_M2UseClipPlanesVar->GetInt()) { + flags |= 0x2; + } + + if (s_M2UseThreadsVar->GetInt()) { + flags |= 0x4; + } + + if (s_M2BatchDoodadsVar->GetInt()) { + flags |= 0x20; + } + + if (s_M2BatchParticlesVar->GetInt()) { + flags |= 0x80; + } + + if (s_M2ForceAdditiveParticleSortVar->GetInt()) { + flags |= 0x100; + } + + flags |= 0x8; + + return flags; +} + +int32_t ModelBlobQuery(const char* a1, C3Vector& a2, C3Vector& a3) { + // TODO + return 0; +} diff --git a/src/model/Model2.hpp b/src/model/Model2.hpp new file mode 100644 index 0000000..04f7740 --- /dev/null +++ b/src/model/Model2.hpp @@ -0,0 +1,24 @@ +#ifndef MODEL_MODEL2_HPP +#define MODEL_MODEL2_HPP + +#include "model/CM2Light.hpp" +#include "model/CM2Model.hpp" +#include "model/CM2Scene.hpp" + +class C3Vector; + +int32_t M2ConvertModelFileName(const char*, char*, uint32_t, uint32_t); + +CM2Scene* M2CreateScene(void); + +uint32_t M2GetCacheFlags(void); + +void M2Initialize(uint16_t flags, uint32_t a2); + +uint32_t M2RegisterCVars(); + +void M2SetGlobalOptFlags(uint16_t flags); + +int32_t ModelBlobQuery(const char*, C3Vector&, C3Vector&); + +#endif diff --git a/src/net/CMakeLists.txt b/src/net/CMakeLists.txt new file mode 100644 index 0000000..7aba042 --- /dev/null +++ b/src/net/CMakeLists.txt @@ -0,0 +1,19 @@ +file(GLOB PRIVATE_SOURCES "*.cpp" "connection/*.cpp" "grunt/*.cpp" "login/*.cpp") + +add_library(net STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(net + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(net + PRIVATE + client + event + PUBLIC + common + storm +) diff --git a/src/net/Connection.hpp b/src/net/Connection.hpp new file mode 100644 index 0000000..782ba30 --- /dev/null +++ b/src/net/Connection.hpp @@ -0,0 +1,7 @@ +#ifndef NET_CONNECTION_HPP +#define NET_CONNECTION_HPP + +#include "net/connection/ClientConnection.hpp" +#include "net/connection/RealmResponse.hpp" + +#endif diff --git a/src/net/Grunt.hpp b/src/net/Grunt.hpp new file mode 100644 index 0000000..6d74636 --- /dev/null +++ b/src/net/Grunt.hpp @@ -0,0 +1,7 @@ +#ifndef NET_GRUNT_HPP +#define NET_GRUNT_HPP + +#include "net/grunt/ClientLink.hpp" +#include "net/grunt/ClientResponse.hpp" + +#endif diff --git a/src/net/Login.hpp b/src/net/Login.hpp new file mode 100644 index 0000000..844b2ce --- /dev/null +++ b/src/net/Login.hpp @@ -0,0 +1,7 @@ +#ifndef NET_LOGIN_HPP +#define NET_LOGIN_HPP + +#include "net/login/BattlenetLogin.hpp" +#include "net/login/GruntLogin.hpp" + +#endif diff --git a/src/net/Types.hpp b/src/net/Types.hpp new file mode 100644 index 0000000..761d3b0 --- /dev/null +++ b/src/net/Types.hpp @@ -0,0 +1,1221 @@ +#ifndef NET_TYPES_HPP +#define NET_TYPES_HPP + +#include + +enum LOGIN_RESULT { + LOGIN_OK = 0, + LOGIN_INVALID_CHALLENGE_MESSAGE = 1, + LOGIN_SRP_ERROR = 2, + LOGIN_INVALID_PROOF_MESSAGE = 3, + LOGIN_BAD_SERVER_PROOF = 4, + LOGIN_INVALID_RECODE_MESSAGE = 5, + LOGIN_BAD_SERVER_RECODE_PROOF = 6, + LOGIN_UNKNOWN_ACCOUNT = 7, + LOGIN_UNKNOWN_ACCOUNT_PIN = 8, + LOGIN_UNKNOWN_ACCOUNT_CALL = 9, + LOGIN_INCORRECT_PASSWORD = 10, + LOGIN_FAILED = 11, + LOGIN_SERVER_DOWN = 12, + LOGIN_BANNED = 13, + LOGIN_BADVERSION = 14, + LOGIN_ALREADYONLINE = 15, + LOGIN_NOTIME = 16, + LOGIN_DBBUSY = 17, + LOGIN_SUSPENDED = 18, + LOGIN_PARENTALCONTROL = 19, + LOGIN_LOCKED_ENFORCED = 20, + LOGIN_RESULT_21 = 21, // DISCONNECTED + LOGIN_ACCOUNT_CONVERTED = 22, + LOGIN_ANTI_INDULGENCE = 23, + LOGIN_EXPIRED = 24, + LOGIN_TRIAL_EXPIRED = 25, + LOGIN_NO_GAME_ACCOUNT = 26, + LOGIN_AUTH_OUTAGE = 27, + LOGIN_GAME_ACCOUNT_LOCKED = 28, + LOGIN_NO_BATTLENET_MANAGER = 29, + LOGIN_NO_BATTLENET_APPLICATION = 30, + LOGIN_MALFORMED_ACCOUNT_NAME = 31, + LOGIN_USE_GRUNT = 32, + LOGIN_TOO_FAST = 33, + LOGIN_CHARGEBACK = 34, + LOGIN_IGR_WITHOUT_BNET = 35, + LOGIN_UNLOCKABLE_LOCK = 36, + LOGIN_CONVERSION_REQUIRED = 37, + LOGIN_UNABLE_TO_DOWNLOAD_MODULE = 38, + LOGIN_NO_GAME_ACCOUNTS_IN_REGION = 39, + LOGIN_ACCOUNT_LOCKED = 40, + LOGIN_RESULT_MAX = 41, +}; + +enum LOGIN_STATE { + LOGIN_STATE_INITIALIZED = 0, + LOGIN_STATE_CONNECTING = 1, + LOGIN_STATE_HANDSHAKING = 2, + LOGIN_STATE_AUTHENTICATING = 3, + LOGIN_STATE_AUTHENTICATED = 4, + LOGIN_STATE_FAILED = 5, + LOGIN_STATE_DOWNLOADFILE = 6, + LOGIN_STATE_FIRST_SECURITY = 7, + LOGIN_STATE_PIN = 8, + LOGIN_STATE_PIN_WAIT = 9, + LOGIN_STATE_MATRIX = 10, + LOGIN_STATE_MATRIX_WAIT = 11, + LOGIN_STATE_TOKEN = 12, + LOGIN_STATE_TOKEN_WAIT = 13, + LOGIN_STATE_CHECKINGVERSIONS = 14, + LOGIN_STATE_15 = 15, // RESPONSE_CONNECTED + LOGIN_STATE_DISCONNECTED = 16, + LOGIN_STATE_SURVEY = 17, + LOGIN_STATE_MAX = 18, +}; + +enum NETMESSAGE { + MSG_NULL_ACTION = 0x0000, + CMSG_BOOTME = 0x0001, + CMSG_DBLOOKUP = 0x0002, + SMSG_DBLOOKUP = 0x0003, + CMSG_QUERY_OBJECT_POSITION = 0x0004, + SMSG_QUERY_OBJ_POSITION = 0x0005, + CMSG_QUERY_OBJECT_ROTATION = 0x0006, + SMSG_QUERY_OBJ_ROTATION = 0x0007, + CMSG_WORLD_TELEPORT = 0x0008, + CMSG_TELEPORT_TO_UNIT = 0x0009, + CMSG_ZONE_MAP = 0x000A, + SMSG_ZONE_MAP = 0x000B, + CMSG_DEBUG_CHANGECELLZONE = 0x000C, + CMSG_MOVE_CHARACTER_CHEAT = 0x000D, + SMSG_MOVE_CHARACTER_CHEAT = 0x000E, + CMSG_RECHARGE = 0x000F, + CMSG_LEARN_SPELL = 0x0010, + CMSG_CREATEMONSTER = 0x0011, + CMSG_DESTROYMONSTER = 0x0012, + CMSG_CREATEITEM = 0x0013, + CMSG_CREATEGAMEOBJECT = 0x0014, + SMSG_CHECK_FOR_BOTS = 0x0015, + CMSG_MAKEMONSTERATTACKGUID = 0x0016, + CMSG_BOT_DETECTED2 = 0x0017, + CMSG_FORCEACTION = 0x0018, + CMSG_FORCEACTIONONOTHER = 0x0019, + CMSG_FORCEACTIONSHOW = 0x001A, + SMSG_FORCEACTIONSHOW = 0x001B, + CMSG_PETGODMODE = 0x001C, + SMSG_PETGODMODE = 0x001D, + SMSG_REFER_A_FRIEND_EXPIRED = 0x001E, + CMSG_WEATHER_SPEED_CHEAT = 0x001F, + CMSG_UNDRESSPLAYER = 0x0020, + CMSG_BEASTMASTER = 0x0021, + CMSG_GODMODE = 0x0022, + SMSG_GOD_MODE = 0x0023, + CMSG_CHEAT_SETMONEY = 0x0024, + CMSG_LEVEL_CHEAT = 0x0025, + CMSG_PET_LEVEL_CHEAT = 0x0026, + CMSG_SET_WORLDSTATE = 0x0027, + CMSG_COOLDOWN_CHEAT = 0x0028, + CMSG_USE_SKILL_CHEAT = 0x0029, + CMSG_FLAG_QUEST = 0x002A, + CMSG_FLAG_QUEST_FINISH = 0x002B, + CMSG_CLEAR_QUEST = 0x002C, + CMSG_SEND_EVENT = 0x002D, + CMSG_DEBUG_AISTATE = 0x002E, + SMSG_DEBUG_AISTATE = 0x002F, + CMSG_DISABLE_PVP_CHEAT = 0x0030, + CMSG_ADVANCE_SPAWN_TIME = 0x0031, + SMSG_DESTRUCTIBLE_BUILDING_DAMAGE = 0x0032, + CMSG_AUTH_SRP6_BEGIN = 0x0033, + CMSG_AUTH_SRP6_PROOF = 0x0034, + CMSG_AUTH_SRP6_RECODE = 0x0035, + CMSG_CREATE_CHARACTER = 0x0036, + CMSG_ENUM_CHARACTERS = 0x0037, + CMSG_CHAR_DELETE = 0x0038, + SMSG_AUTH_SRP6_RESPONSE = 0x0039, + SMSG_CREATE_CHAR = 0x003A, + SMSG_ENUM_CHARACTERS_RESULT = 0x003B, + SMSG_DELETE_CHAR = 0x003C, + CMSG_PLAYER_LOGIN = 0x003D, + SMSG_NEW_WORLD = 0x003E, + SMSG_TRANSFER_PENDING = 0x003F, + SMSG_TRANSFER_ABORTED = 0x0040, + SMSG_CHARACTER_LOGIN_FAILED = 0x0041, + SMSG_LOGIN_SET_TIME_SPEED = 0x0042, + SMSG_GAME_TIME_UPDATE = 0x0043, + CMSG_GAMETIME_SET = 0x0044, + SMSG_GAME_TIME_SET = 0x0045, + CMSG_GAMESPEED_SET = 0x0046, + SMSG_GAME_SPEED_SET = 0x0047, + CMSG_SERVERTIME = 0x0048, + SMSG_SERVERTIME = 0x0049, + CMSG_PLAYER_LOGOUT = 0x004A, + CMSG_LOGOUT_REQUEST = 0x004B, + SMSG_LOGOUT_RESPONSE = 0x004C, + SMSG_LOGOUT_COMPLETE = 0x004D, + CMSG_LOGOUT_CANCEL = 0x004E, + SMSG_LOGOUT_CANCEL_ACK = 0x004F, + CMSG_NAME_QUERY = 0x0050, + SMSG_QUERY_PLAYER_NAME_RESPONSE = 0x0051, + CMSG_QUERY_PET_NAME = 0x0052, + SMSG_QUERY_PET_NAME_RESPONSE = 0x0053, + CMSG_QUERY_GUILD_INFO = 0x0054, + SMSG_QUERY_GUILD_INFO_RESPONSE = 0x0055, + CMSG_ITEM_QUERY_SINGLE = 0x0056, + CMSG_ITEM_QUERY_MULTIPLE = 0x0057, + SMSG_ITEM_QUERY_SINGLE_RESPONSE = 0x0058, + SMSG_ITEM_QUERY_MULTIPLE_RESPONSE = 0x0059, + CMSG_QUERY_PAGE_TEXT = 0x005A, + SMSG_QUERY_PAGE_TEXT_RESPONSE = 0x005B, + CMSG_QUERY_QUEST_INFO = 0x005C, + SMSG_QUERY_QUEST_INFO_RESPONSE = 0x005D, + CMSG_QUERY_GAME_OBJECT = 0x005E, + SMSG_QUERY_GAME_OBJECT_RESPONSE = 0x005F, + CMSG_QUERY_CREATURE = 0x0060, + SMSG_QUERY_CREATURE_RESPONSE = 0x0061, + CMSG_WHO = 0x0062, + SMSG_WHO = 0x0063, + CMSG_WHO_IS = 0x0064, + SMSG_WHO_IS = 0x0065, + CMSG_CONTACT_LIST = 0x0066, + SMSG_CONTACT_LIST = 0x0067, + SMSG_FRIEND_STATUS = 0x0068, + CMSG_ADD_FRIEND = 0x0069, + CMSG_DEL_FRIEND = 0x006A, + CMSG_SET_CONTACT_NOTES = 0x006B, + CMSG_ADD_IGNORE = 0x006C, + CMSG_DEL_IGNORE = 0x006D, + CMSG_GROUP_INVITE = 0x006E, + SMSG_GROUP_INVITE = 0x006F, + CMSG_GROUP_CANCEL = 0x0070, + SMSG_GROUP_CANCEL = 0x0071, + CMSG_GROUP_ACCEPT = 0x0072, + CMSG_GROUP_DECLINE = 0x0073, + SMSG_GROUP_DECLINE = 0x0074, + CMSG_GROUP_UNINVITE = 0x0075, + CMSG_GROUP_UNINVITE_GUID = 0x0076, + SMSG_GROUP_UNINVITE = 0x0077, + CMSG_GROUP_SET_LEADER = 0x0078, + SMSG_GROUP_SET_LEADER = 0x0079, + CMSG_SET_LOOT_METHOD = 0x007A, + CMSG_GROUP_DISBAND = 0x007B, + SMSG_GROUP_DESTROYED = 0x007C, + SMSG_GROUP_LIST = 0x007D, + SMSG_PARTY_MEMBER_STATS = 0x007E, + SMSG_PARTY_COMMAND_RESULT = 0x007F, + UMSG_UPDATE_GROUP_MEMBERS = 0x0080, + CMSG_GUILD_CREATE = 0x0081, + CMSG_GUILD_INVITE = 0x0082, + SMSG_GUILD_INVITE = 0x0083, + CMSG_GUILD_ACCEPT = 0x0084, + CMSG_GUILD_DECLINE_INVITATION = 0x0085, + SMSG_GUILD_DECLINE = 0x0086, + CMSG_GUILD_INFO = 0x0087, + SMSG_GUILD_INFO = 0x0088, + CMSG_GUILD_GET_ROSTER = 0x0089, + SMSG_GUILD_ROSTER = 0x008A, + CMSG_GUILD_PROMOTE_MEMBER = 0x008B, + CMSG_GUILD_DEMOTE_MEMBER = 0x008C, + CMSG_GUILD_LEAVE = 0x008D, + CMSG_GUILD_OFFICER_REMOVE_MEMBER = 0x008E, + CMSG_GUILD_DISBAND = 0x008F, + CMSG_GUILD_LEADER = 0x0090, + CMSG_GUILD_MOTD = 0x0091, + SMSG_GUILD_EVENT = 0x0092, + SMSG_GUILD_COMMAND_RESULT = 0x0093, + UMSG_UPDATE_GUILD = 0x0094, + CMSG_MESSAGECHAT = 0x0095, + SMSG_CHAT = 0x0096, + CMSG_CHAT_JOIN_CHANNEL = 0x0097, + CMSG_CHAT_LEAVE_CHANNEL = 0x0098, + SMSG_CHANNEL_NOTIFY = 0x0099, + CMSG_CHAT_CHANNEL_LIST = 0x009A, + SMSG_CHANNEL_LIST = 0x009B, + CMSG_CHAT_CHANNEL_PASSWORD = 0x009C, + CMSG_CHAT_CHANNEL_SET_OWNER = 0x009D, + CMSG_CHAT_CHANNEL_OWNER = 0x009E, + CMSG_CHAT_CHANNEL_MODERATOR = 0x009F, + CMSG_CHAT_CHANNEL_UNMODERATOR = 0x00A0, + CMSG_CHAT_CHANNEL_MUTE = 0x00A1, + CMSG_CHAT_CHANNEL_UNMUTE = 0x00A2, + CMSG_CHAT_CHANNEL_INVITE = 0x00A3, + CMSG_CHAT_CHANNEL_KICK = 0x00A4, + CMSG_CHAT_CHANNEL_BAN = 0x00A5, + CMSG_CHAT_CHANNEL_UNBAN = 0x00A6, + CMSG_CHAT_CHANNEL_ANNOUNCEMENTS = 0x00A7, + CMSG_CHAT_CHANNEL_MODERATE = 0x00A8, + SMSG_UPDATE_OBJECT = 0x00A9, + SMSG_DESTROY_OBJECT = 0x00AA, + CMSG_USE_ITEM = 0x00AB, + CMSG_OPEN_ITEM = 0x00AC, + CMSG_READ_ITEM = 0x00AD, + SMSG_READ_ITEM_RESULT_OK = 0x00AE, + SMSG_READ_ITEM_RESULT_FAILED = 0x00AF, + SMSG_ITEM_COOLDOWN = 0x00B0, + CMSG_GAME_OBJ_USE = 0x00B1, + CMSG_DESTROY_ITEMS = 0x00B2, + SMSG_GAME_OBJECT_CUSTOM_ANIM = 0x00B3, + CMSG_AREA_TRIGGER = 0x00B4, + MSG_MOVE_START_FORWARD = 0x00B5, + MSG_MOVE_START_BACKWARD = 0x00B6, + MSG_MOVE_STOP = 0x00B7, + MSG_MOVE_START_STRAFE_LEFT = 0x00B8, + MSG_MOVE_START_STRAFE_RIGHT = 0x00B9, + MSG_MOVE_STOP_STRAFE = 0x00BA, + MSG_MOVE_JUMP = 0x00BB, + MSG_MOVE_START_TURN_LEFT = 0x00BC, + MSG_MOVE_START_TURN_RIGHT = 0x00BD, + MSG_MOVE_STOP_TURN = 0x00BE, + MSG_MOVE_START_PITCH_UP = 0x00BF, + MSG_MOVE_START_PITCH_DOWN = 0x00C0, + MSG_MOVE_STOP_PITCH = 0x00C1, + MSG_MOVE_SET_RUN_MODE = 0x00C2, + MSG_MOVE_SET_WALK_MODE = 0x00C3, + MSG_MOVE_TOGGLE_LOGGING = 0x00C4, + MSG_MOVE_TELEPORT = 0x00C5, + MSG_MOVE_TELEPORT_CHEAT = 0x00C6, + MSG_MOVE_TELEPORT_ACK = 0x00C7, + MSG_MOVE_TOGGLE_FALL_LOGGING = 0x00C8, + MSG_MOVE_FALL_LAND = 0x00C9, + MSG_MOVE_START_SWIM = 0x00CA, + MSG_MOVE_STOP_SWIM = 0x00CB, + MSG_MOVE_SET_RUN_SPEED_CHEAT = 0x00CC, + MSG_MOVE_SET_RUN_SPEED = 0x00CD, + MSG_MOVE_SET_RUN_BACK_SPEED_CHEAT = 0x00CE, + MSG_MOVE_SET_RUN_BACK_SPEED = 0x00CF, + MSG_MOVE_SET_WALK_SPEED_CHEAT = 0x00D0, + MSG_MOVE_SET_WALK_SPEED = 0x00D1, + MSG_MOVE_SET_SWIM_SPEED_CHEAT = 0x00D2, + MSG_MOVE_SET_SWIM_SPEED = 0x00D3, + MSG_MOVE_SET_SWIM_BACK_SPEED_CHEAT = 0x00D4, + MSG_MOVE_SET_SWIM_BACK_SPEED = 0x00D5, + MSG_MOVE_SET_ALL_SPEED_CHEAT = 0x00D6, + MSG_MOVE_SET_TURN_RATE_CHEAT = 0x00D7, + MSG_MOVE_SET_TURN_RATE = 0x00D8, + MSG_MOVE_TOGGLE_COLLISION_CHEAT = 0x00D9, + MSG_MOVE_SET_FACING = 0x00DA, + MSG_MOVE_SET_PITCH = 0x00DB, + MSG_MOVE_WORLDPORT_ACK = 0x00DC, + SMSG_ON_MONSTER_MOVE = 0x00DD, + SMSG_MOVE_SET_WATER_WALK = 0x00DE, + SMSG_MOVE_SET_LAND_WALK = 0x00DF, + SMSG_FORCE_RUN_SPEED_CHANGE = 0x00E2, + SMSG_FORCE_RUN_BACK_SPEED_CHANGE = 0x00E4, + SMSG_FORCE_SWIM_SPEED_CHANGE = 0x00E6, + SMSG_FORCE_MOVE_ROOT = 0x00E8, + SMSG_FORCE_MOVE_UNROOT = 0x00EA, + SMSG_MOVE_KNOCK_BACK = 0x00EF, + SMSG_MOVE_SET_FEATHER_FALL = 0x00F2, + SMSG_MOVE_SET_NORMAL_FALL = 0x00F3, + SMSG_MOVE_SET_HOVERING = 0x00F4, + SMSG_MOVE_UNSET_HOVERING = 0x00F5, + SMSG_TRIGGER_CINEMATIC = 0x00FA, + SMSG_TUTORIAL_FLAGS = 0x00FD, + SMSG_EMOTE = 0x0103, + SMSG_TEXT_EMOTE = 0x0105, + SMSG_INVENTORY_CHANGE_FAILURE = 0x0112, + SMSG_OPEN_CONTAINER = 0x0113, + SMSG_INSPECT_RESULTS_UPDATE = 0x0115, + SMSG_TRADE_STATUS = 0x0120, + SMSG_TRADE_STATUS_EXTENDED = 0x0121, + SMSG_INITIALIZE_FACTIONS = 0x0122, + SMSG_SET_FACTION_VISIBLE = 0x0123, + SMSG_SET_FACTION_STANDING = 0x0124, + SMSG_SET_PROFICIENCY = 0x0127, + SMSG_UPDATE_ACTION_BUTTONS = 0x0129, + SMSG_SEND_KNOWN_SPELLS = 0x012A, + SMSG_LEARNED_SPELL = 0x012B, + SMSG_SUPERCEDED_SPELLS = 0x012C, + SMSG_CAST_FAILED = 0x0130, + SMSG_SPELL_START = 0x0131, + SMSG_SPELL_GO = 0x0132, + SMSG_SPELL_FAILURE = 0x0133, + SMSG_SPELL_COOLDOWN = 0x0134, + SMSG_COOLDOWN_EVENT = 0x0135, + SMSG_EQUIPMENT_SET_ID = 0x0137, + SMSG_PET_CAST_FAILED = 0x0138, + SMSG_AI_REACTION = 0x013C, + SMSG_ATTACK_START = 0x0143, + SMSG_ATTACK_STOP = 0x0144, + SMSG_ATTACKSWING_NOTINRANGE = 0x0145, + SMSG_ATTACKSWING_BADFACING = 0x0146, + SMSG_INSTANCE_LOCK_WARNING_QUERY = 0x0147, + SMSG_ATTACKSWING_DEADTARGET = 0x0148, + SMSG_ATTACKSWING_CANT_ATTACK = 0x0149, + SMSG_ATTACKER_STATE_UPDATE = 0x014A, + SMSG_BATTLEFIELD_PORT_DENIED = 0x014B, + CMSG_PERFORM_ACTION_SET = 0x014C, + SMSG_RESUME_CAST_BAR = 0x014D, + SMSG_CANCEL_COMBAT = 0x014E, + SMSG_SPELL_BREAK_LOG = 0x014F, + SMSG_SPELL_HEAL_LOG = 0x0150, + SMSG_SPELL_ENERGIZE_LOG = 0x0151, + SMSG_BREAK_TARGET = 0x0152, + CMSG_SAVE_PLAYER = 0x0153, + CMSG_SETDEATHBINDPOINT = 0x0154, + SMSG_BIND_POINT_UPDATE = 0x0155, + CMSG_GETDEATHBINDZONE = 0x0156, + SMSG_BINDZONEREPLY = 0x0157, + SMSG_PLAYER_BOUND = 0x0158, + SMSG_CONTROL_UPDATE = 0x0159, + CMSG_REPOP_REQUEST = 0x015A, + SMSG_RESURRECT_REQUEST = 0x015B, + CMSG_RESURRECT_RESPONSE = 0x015C, + CMSG_LOOT_UNIT = 0x015D, + CMSG_LOOT_MONEY = 0x015E, + CMSG_LOOT_RELEASE = 0x015F, + SMSG_LOOT_RESPONSE = 0x0160, + SMSG_LOOT_RELEASE = 0x0161, + SMSG_LOOT_REMOVED = 0x0162, + SMSG_LOOT_MONEY_NOTIFY = 0x0163, + SMSG_LOOT_ITEM_NOTIFY = 0x0164, + SMSG_LOOT_CLEAR_MONEY = 0x0165, + SMSG_ITEM_PUSH_RESULT = 0x0166, + SMSG_DUEL_REQUESTED = 0x0167, + SMSG_DUEL_OUT_OF_BOUNDS = 0x0168, + SMSG_DUEL_IN_BOUNDS = 0x0169, + SMSG_DUEL_COMPLETE = 0x016A, + SMSG_DUEL_WINNER = 0x016B, + CMSG_DUEL_ACCEPTED = 0x016C, + CMSG_DUEL_CANCELLED = 0x016D, + SMSG_MOUNT_RESULT = 0x016E, + SMSG_DISMOUNT_RESULT = 0x016F, + SMSG_REMOVED_FROM_PVP_QUEUE = 0x0170, + CMSG_MOUNT_SPECIAL_ANIM = 0x0171, + SMSG_MOUNT_SPECIAL_ANIM = 0x0172, + SMSG_PET_TAME_FAILURE = 0x0173, + CMSG_PET_SET_ACTION = 0x0174, + CMSG_PET_ACTION = 0x0175, + CMSG_PET_ABANDON = 0x0176, + CMSG_PET_RENAME = 0x0177, + SMSG_PET_NAME_INVALID = 0x0178, + SMSG_PET_SPELLS_MESSAGE = 0x0179, + SMSG_PET_MODE = 0x017A, + CMSG_GOSSIP_HELLO = 0x017B, + CMSG_GOSSIP_SELECT_OPTION = 0x017C, + SMSG_GOSSIP_MESSAGE = 0x017D, + SMSG_GOSSIP_COMPLETE = 0x017E, + CMSG_QUERY_NPC_TEXT = 0x017F, + SMSG_QUERY_NPC_TEXT_RESPONSE = 0x0180, + SMSG_NPC_WONT_TALK = 0x0181, + CMSG_QUEST_GIVER_STATUS_QUERY = 0x0182, + SMSG_QUEST_GIVER_STATUS = 0x0183, + CMSG_QUEST_GIVER_HELLO = 0x0184, + SMSG_QUEST_GIVER_QUEST_LIST_MESSAGE = 0x0185, + CMSG_QUEST_GIVER_QUERY_QUEST = 0x0186, + CMSG_QUEST_GIVER_QUEST_AUTOLAUNCH = 0x0187, + SMSG_QUEST_GIVER_QUEST_DETAILS = 0x0188, + CMSG_QUEST_GIVER_ACCEPT_QUEST = 0x0189, + CMSG_QUEST_GIVER_COMPLETE_QUEST = 0x018A, + SMSG_QUEST_GIVER_REQUEST_ITEMS = 0x018B, + CMSG_QUEST_GIVER_REQUEST_REWARD = 0x018C, + SMSG_QUEST_GIVER_OFFER_REWARD_MESSAGE = 0x018D, + CMSG_QUEST_GIVER_CHOOSE_REWARD = 0x018E, + SMSG_QUEST_GIVER_INVALID_QUEST = 0x018F, + CMSG_QUEST_GIVER_CANCEL = 0x0190, + SMSG_QUEST_GIVER_QUEST_COMPLETE = 0x0191, + SMSG_QUEST_GIVER_QUEST_FAILED = 0x0192, + CMSG_QUEST_LOG_SWAP_QUEST = 0x0193, + CMSG_QUEST_LOG_REMOVE_QUEST = 0x0194, + SMSG_QUEST_LOG_FULL = 0x0195, + SMSG_QUEST_UPDATE_FAILED = 0x0196, + SMSG_QUEST_UPDATE_FAILED_TIMER = 0x0197, + SMSG_QUEST_UPDATE_COMPLETE = 0x0198, + SMSG_QUEST_UPDATE_ADD_KILL = 0x0199, + SMSG_QUEST_UPDATE_ADD_ITEM = 0x019A, + CMSG_QUEST_CONFIRM_ACCEPT = 0x019B, + SMSG_QUEST_CONFIRM_ACCEPT = 0x019C, + CMSG_PUSH_QUEST_TO_PARTY = 0x019D, + CMSG_LIST_INVENTORY = 0x019E, + SMSG_VENDOR_INVENTORY = 0x019F, + CMSG_SELL_ITEM = 0x01A0, + SMSG_SELL_ITEM = 0x01A1, + CMSG_BUY_ITEM = 0x01A2, + CMSG_BUY_ITEM_IN_SLOT = 0x01A3, + SMSG_BUY_SUCCEEDED = 0x01A4, + SMSG_BUY_FAILED = 0x01A5, + CMSG_TAXICLEARALLNODES = 0x01A6, + CMSG_TAXIENABLEALLNODES = 0x01A7, + CMSG_TAXISHOWNODES = 0x01A8, + SMSG_SHOW_TAXI_NODES = 0x01A9, + CMSG_TAXI_NODE_STATUS_QUERY = 0x01AA, + SMSG_TAXI_NODE_STATUS = 0x01AB, + CMSG_TAXI_QUERY_AVAILABLE_NODES = 0x01AC, + CMSG_ACTIVATE_TAXI = 0x01AD, + SMSG_ACTIVATE_TAXI_REPLY = 0x01AE, + SMSG_NEW_TAXI_PATH = 0x01AF, + CMSG_TRAINER_LIST = 0x01B0, + SMSG_TRAINER_LIST = 0x01B1, + CMSG_TRAINER_BUY_SPELL = 0x01B2, + SMSG_TRAINER_BUY_SUCCEEDED = 0x01B3, + SMSG_TRAINER_BUY_FAILED = 0x01B4, + CMSG_BINDER_ACTIVATE = 0x01B5, + SMSG_PLAYERBINDERROR = 0x01B6, + CMSG_BANKER_ACTIVATE = 0x01B7, + SMSG_SHOW_BANK = 0x01B8, + CMSG_BUY_BANK_SLOT = 0x01B9, + SMSG_BUY_BANK_SLOT_RESULT = 0x01BA, + CMSG_PETITION_SHOW_LIST = 0x01BB, + SMSG_PETITION_SHOW_LIST = 0x01BC, + CMSG_PETITION_BUY = 0x01BD, + CMSG_PETITION_SHOW_SIGNATURES = 0x01BE, + SMSG_PETITION_SHOW_SIGNATURES = 0x01BF, + CMSG_PETITION_SIGN = 0x01C0, + SMSG_PETITION_SIGN_RESULTS = 0x01C1, + CMSG_OFFER_PETITION = 0x01C3, + CMSG_TURN_IN_PETITION = 0x01C4, + SMSG_TURN_IN_PETITION_RESULT = 0x01C5, + CMSG_PETITION_QUERY = 0x01C6, + SMSG_PETITION_QUERY_RESPONSE = 0x01C7, + SMSG_FISH_NOT_HOOKED = 0x01C8, + SMSG_FISH_ESCAPED = 0x01C9, + CMSG_BUG = 0x01CA, + SMSG_NOTIFICATION = 0x01CB, + CMSG_REQUEST_PLAYED_TIME = 0x01CC, + SMSG_PLAYED_TIME = 0x01CD, + CMSG_QUERY_TIME = 0x01CE, + SMSG_QUERY_TIME_RESPONSE = 0x01CF, + SMSG_LOG_XP_GAIN = 0x01D0, + SMSG_AURACASTLOG = 0x01D1, + CMSG_RECLAIM_CORPSE = 0x01D2, + CMSG_WRAP_ITEM = 0x01D3, + SMSG_LEVEL_UP_INFO = 0x01D4, + SMSG_RESISTLOG = 0x01D6, + SMSG_ENCHANTMENT_LOG = 0x01D7, + CMSG_SET_SKILL_CHEAT = 0x01D8, + SMSG_START_MIRROR_TIMER = 0x01D9, + SMSG_PAUSE_MIRROR_TIMER = 0x01DA, + SMSG_STOP_MIRROR_TIMER = 0x01DB, + CMSG_PING = 0x01DC, + SMSG_PONG = 0x01DD, + SMSG_CLEAR_COOLDOWN = 0x01DE, + SMSG_PAGE_TEXT = 0x01DF, + CMSG_SET_SHEATHED = 0x01E0, + SMSG_COOLDOWN_CHEAT = 0x01E1, + SMSG_SPELL_DELAYED = 0x01E2, + CMSG_QUEST_POI_QUERY = 0x01E3, + SMSG_QUEST_POI_QUERY_RESPONSE = 0x01E4, + CMSG_GHOST = 0x01E5, + CMSG_GM_INVIS = 0x01E6, + SMSG_INVALID_PROMOTION_CODE = 0x01E7, + SMSG_ITEM_TIME_UPDATE = 0x01EA, + SMSG_ITEM_ENCHANT_TIME_UPDATE = 0x01EB, + SMSG_AUTH_CHALLENGE = 0x01EC, + CMSG_AUTH_SESSION = 0x01ED, + SMSG_AUTH_RESPONSE = 0x01EE, + CMSG_PET_CAST_SPELL = 0x01F0, + SMSG_PLAY_SPELL_VISUAL = 0x01F3, + CMSG_ZONEUPDATE = 0x01F4, + SMSG_PARTY_KILL_LOG = 0x01F5, + SMSG_COMPRESSED_UPDATE_OBJECT = 0x01F6, + SMSG_PLAY_SPELL_IMPACT = 0x01F7, + SMSG_EXPLORATION_EXPERIENCE = 0x01F8, + CMSG_GM_SET_SECURITY_GROUP = 0x01F9, + CMSG_GM_NUKE = 0x01FA, + SMSG_ENVIRONMENTAL_DAMAGE_LOG = 0x01FC, + CMSG_CHANGEPLAYER_DIFFICULTY = 0x01FD, + SMSG_RWHOIS = 0x01FE, + SMSG_LFG_PLAYER_REWARD = 0x01FF, + SMSG_LFG_TELEPORT_DENIED = 0x0200, + CMSG_UNLEARN_SPELL = 0x0201, + CMSG_UNLEARN_SKILL = 0x0202, + SMSG_UNLEARNED_SPELLS = 0x0203, + CMSG_DECHARGE = 0x0204, + CMSG_GM_TICKET_CREATE = 0x0205, + SMSG_GM_TICKET_CREATE = 0x0206, + CMSG_GM_TICKET_UPDATE_TEXT = 0x0207, + SMSG_GM_TICKET_UPDATE_TEXT = 0x0208, + SMSG_ACCOUNT_DATA_TIMES = 0x0209, + CMSG_REQUEST_ACCOUNT_DATA = 0x020A, + CMSG_UPDATE_ACCOUNT_DATA = 0x020B, + SMSG_UPDATE_ACCOUNT_DATA = 0x020C, + SMSG_CLEAR_FAR_SIGHT_IMMEDIATE = 0x020D, + SMSG_CHANGE_PLAYER_DIFFICULTY_RESULT = 0x020E, + CMSG_GM_TEACH = 0x020F, + CMSG_GM_CREATE_ITEM_TARGET = 0x0210, + CMSG_GM_TICKET_GET_TICKET = 0x0211, + SMSG_GM_TICKET_GET_TICKET = 0x0212, + CMSG_UNLEARN_TALENTS = 0x0213, + SMSG_UPDATE_INSTANCE_ENCOUNTER_UNIT = 0x0214, + SMSG_GAMEOBJECT_DESPAWN_ANIM = 0x0215, + CMSG_GM_TICKET_DELETE_TICKET = 0x0217, + SMSG_GM_TICKET_DELETE_TICKET = 0x0218, + SMSG_CHAT_WRONG_FACTION = 0x0219, + CMSG_GM_TICKET_GET_SYSTEM_STATUS = 0x021A, + SMSG_GM_TICKET_GET_SYSTEM_STATUS = 0x021B, + CMSG_SPIRIT_HEALER_ACTIVATE = 0x021C, + CMSG_SET_STAT_CHEAT = 0x021D, + SMSG_QUEST_FORCE_REMOVED = 0x021E, + CMSG_SKILL_BUY_STEP = 0x021F, + CMSG_SKILL_BUY_RANK = 0x0220, + CMSG_XP_CHEAT = 0x0221, + SMSG_SPIRIT_HEALER_CONFIRM = 0x0222, + CMSG_CHARACTER_POINT_CHEAT = 0x0223, + SMSG_GOSSIP_POI = 0x0224, + CMSG_CHAT_REPORT_IGNORED = 0x0225, + CMSG_GM_VISION = 0x0226, + CMSG_SERVER_COMMAND = 0x0227, + CMSG_GM_SILENCE = 0x0228, + CMSG_GM_REVEALTO = 0x0229, + CMSG_GM_RESURRECT = 0x022A, + CMSG_GM_SUMMONMOB = 0x022B, + CMSG_GM_MOVECORPSE = 0x022C, + CMSG_GM_FREEZE = 0x022D, + CMSG_GM_UBERINVIS = 0x022E, + CMSG_GM_REQUEST_PLAYER_INFO = 0x022F, + SMSG_GM_PLAYER_INFO = 0x0230, + CMSG_GUILD_SET_RANK_PERMISSIONS = 0x0231, + CMSG_GUILD_ADD_RANK = 0x0232, + CMSG_GUILD_DELETE_RANK = 0x0233, + CMSG_GUILD_SET_PUBLIC_NOTE = 0x0234, + CMSG_GUILD_SET_OFFICER_NOTE = 0x0235, + SMSG_LOGIN_VERIFY_WORLD = 0x0236, + CMSG_CLEAR_EXPLORATION = 0x0237, + CMSG_SEND_MAIL = 0x0238, + SMSG_MAIL_COMMAND_RESULT = 0x0239, + CMSG_MAIL_GET_LIST = 0x023A, + SMSG_MAIL_LIST_RESULT = 0x023B, + CMSG_BATTLEFIELD_LIST = 0x023C, + SMSG_BATTLEFIELD_LIST = 0x023D, + CMSG_BATTLEFIELD_JOIN = 0x023E, + SMSG_FORCE_SET_VEHICLE_REC_ID = 0x023F, + CMSG_SET_VEHICLE_REC_ID_ACK = 0x0240, + CMSG_TAXICLEARNODE = 0x0241, + CMSG_TAXIENABLENODE = 0x0242, + CMSG_ITEM_TEXT_QUERY = 0x0243, + SMSG_QUERY_ITEM_TEXT_RESPONSE = 0x0244, + CMSG_MAIL_TAKE_MONEY = 0x0245, + CMSG_MAIL_TAKE_ITEM = 0x0246, + CMSG_MAIL_MARK_AS_READ = 0x0247, + CMSG_MAIL_RETURN_TO_SENDER = 0x0248, + CMSG_MAIL_DELETE = 0x0249, + CMSG_MAIL_CREATE_TEXT_ITEM = 0x024A, + SMSG_SPELL_MISS_LOG = 0x024B, + SMSG_SPELL_EXECUTE_LOG = 0x024C, + SMSG_DEBUGAURAPROC = 0x024D, + SMSG_SPELL_PERIODIC_AURA_LOG = 0x024E, + SMSG_SPELL_DAMAGE_SHIELD = 0x024F, + SMSG_SPELL_NON_MELEE_DAMAGE_LOG = 0x0250, + CMSG_LEARN_TALENT = 0x0251, + SMSG_RESURRECT_FAILED = 0x0252, + CMSG_TOGGLE_PVP = 0x0253, + SMSG_ZONE_UNDER_ATTACK = 0x0254, + CMSG_AUCTION_SELL_ITEM = 0x0256, + CMSG_AUCTION_REMOVE_ITEM = 0x0257, + CMSG_AUCTION_LIST_ITEMS = 0x0258, + CMSG_AUCTION_LIST_OWNER_ITEMS = 0x0259, + CMSG_AUCTION_PLACE_BID = 0x025A, + SMSG_AUCTION_COMMAND_RESULT = 0x025B, + SMSG_AUCTION_LIST_RESULT = 0x025C, + SMSG_AUCTION_LIST_OWNER_ITEMS_RESULT = 0x025D, + SMSG_AUCTION_BIDDER_NOTIFICATION = 0x025E, + SMSG_AUCTION_OWNER_NOTIFICATION = 0x025F, + SMSG_PROC_RESIST = 0x0260, + SMSG_COMBAT_EVENT_FAILED = 0x0261, + SMSG_DISPEL_FAILED = 0x0262, + SMSG_SPELL_OR_DAMAGE_IMMUNE = 0x0263, + CMSG_AUCTION_LIST_BIDDER_ITEMS = 0x0264, + SMSG_AUCTION_LIST_BIDDER_ITEMS_RESULT = 0x0265, + SMSG_SET_FLAT_SPELL_MODIFIER = 0x0266, + SMSG_SET_PCT_SPELL_MODIFIER = 0x0267, + CMSG_SET_AMMO = 0x0268, + SMSG_CORPSE_RECLAIM_DELAY = 0x0269, + CMSG_SET_ACTIVE_MOVER = 0x026A, + CMSG_PET_CANCEL_AURA = 0x026B, + CMSG_PLAYER_AI_CHEAT = 0x026C, + CMSG_CANCEL_AUTO_REPEAT_SPELL = 0x026D, + CMSG_STABLE_PET = 0x0270, + CMSG_UNSTABLE_PET = 0x0271, + CMSG_BUY_STABLE_SLOT = 0x0272, + SMSG_STABLE_RESULT = 0x0273, + CMSG_STABLE_REVIVE_PET = 0x0274, + CMSG_STABLE_SWAP_PET = 0x0275, + SMSG_PLAY_MUSIC = 0x0277, + SMSG_PLAY_OBJECT_SOUND = 0x0278, + CMSG_REQUEST_PET_INFO = 0x0279, + CMSG_FAR_SIGHT = 0x027A, + SMSG_SPELL_DISPELL_LOG = 0x027B, + SMSG_DAMAGE_CALC_LOG = 0x027C, + CMSG_ENABLE_DAMAGE_LOG = 0x027D, + CMSG_GROUP_CHANGE_SUB_GROUP = 0x027E, + CMSG_REQUEST_PARTY_MEMBER_STATS = 0x027F, + CMSG_GROUP_SWAP_SUB_GROUP = 0x0280, + CMSG_RESET_FACTION_CHEAT = 0x0281, + CMSG_AUTOSTORE_BANK_ITEM = 0x0282, + CMSG_AUTOBANK_ITEM = 0x0283, + SMSG_RECEIVED_MAIL = 0x0285, + SMSG_RAID_GROUP_ONLY = 0x0286, + CMSG_SET_DURABILITY_CHEAT = 0x0287, + CMSG_SET_PVP_RANK_CHEAT = 0x0288, + CMSG_ADD_PVP_MEDAL_CHEAT = 0x0289, + CMSG_DEL_PVP_MEDAL_CHEAT = 0x028A, + CMSG_SET_PVP_TITLE = 0x028B, + SMSG_PVP_CREDIT = 0x028C, + SMSG_AUCTION_REMOVED_NOTIFICATION = 0x028D, + CMSG_GROUP_RAID_CONVERT = 0x028E, + CMSG_SET_ASSISTANT_LEADER = 0x028F, + CMSG_BUY_BACK_ITEM = 0x0290, + SMSG_CHAT_SERVER_MESSAGE = 0x0291, + CMSG_SET_SAVED_INSTANCE_EXTEND = 0x0292, + SMSG_LFG_OFFER_CONTINUE = 0x0293, + CMSG_TEST_DROP_RATE = 0x0294, + SMSG_TEST_DROP_RATE_RESULT = 0x0295, + CMSG_DF_GET_JOIN_STATUS = 0x0296, + SMSG_SHOW_MAILBOX = 0x0297, + SMSG_RESET_RANGED_COMBAT_TIMER = 0x0298, + SMSG_CHAT_NOT_IN_PARTY = 0x0299, + CMSG_GMTICKETSYSTEM_TOGGLE = 0x029A, + CMSG_CANCEL_GROWTH_AURA = 0x029B, + SMSG_CANCEL_AUTO_REPEAT = 0x029C, + SMSG_STAND_STATE_UPDATE = 0x029D, + SMSG_LOOT_ALL_PASSED = 0x029E, + SMSG_LOOT_ROLL_WON = 0x029F, + CMSG_LOOT_ROLL = 0x02A0, + SMSG_LOOT_START_ROLL = 0x02A1, + SMSG_LOOT_ROLL = 0x02A2, + CMSG_LOOT_MASTER_GIVE = 0x02A3, + SMSG_LOOT_MASTER_LIST = 0x02A4, + SMSG_SET_FORCED_REACTIONS = 0x02A5, + SMSG_SPELL_FAILED_OTHER = 0x02A6, + SMSG_GAME_OBJECT_RESET_STATE = 0x02A7, + CMSG_REPAIR_ITEM = 0x02A8, + SMSG_CHAT_PLAYER_NOTFOUND = 0x02A9, + SMSG_SUMMON_REQUEST = 0x02AB, + CMSG_SUMMON_RESPONSE = 0x02AC, + SMSG_MONSTER_MOVE_TRANSPORT = 0x02AE, + SMSG_PET_BROKEN = 0x02AF, + CMSG_SERVER_BROADCAST = 0x02B2, + CMSG_SELF_RES = 0x02B3, + SMSG_FEIGN_DEATH_RESISTED = 0x02B4, + CMSG_RUN_SCRIPT = 0x02B5, + SMSG_SCRIPT_MESSAGE = 0x02B6, + SMSG_DUEL_COUNTDOWN = 0x02B7, + SMSG_AREA_TRIGGER_MESSAGE = 0x02B8, + CMSG_SHOWING_HELM = 0x02B9, + CMSG_SHOWING_CLOAK = 0x02BA, + SMSG_LFG_ROLE_CHOSEN = 0x02BB, + SMSG_PLAYER_SKINNED = 0x02BC, + SMSG_DURABILITY_DAMAGE_DEATH = 0x02BD, + CMSG_SET_EXPLORATION = 0x02BE, + CMSG_SET_ACTION_BAR_TOGGLES = 0x02BF, + SMSG_INIT_WORLD_STATES = 0x02C2, + SMSG_UPDATE_WORLD_STATE = 0x02C3, + CMSG_ITEM_NAME_QUERY = 0x02C4, + SMSG_ITEM_NAME_QUERY_RESPONSE = 0x02C5, + SMSG_PET_ACTION_FEEDBACK = 0x02C6, + CMSG_CHARACTER_RENAME_REQUEST = 0x02C7, + SMSG_CHARACTER_RENAME_RESULT = 0x02C8, + CMSG_MOVE_SPLINE_DONE = 0x02C9, + CMSG_MOVE_FALL_RESET = 0x02CA, + SMSG_INSTANCE_SAVE_CREATED = 0x02CB, + SMSG_RAID_INSTANCE_INFO = 0x02CC, + CMSG_REQUEST_RAID_INFO = 0x02CD, + CMSG_MOVE_TIME_SKIPPED = 0x02CE, + CMSG_MOVE_FEATHER_FALL_ACK = 0x02CF, + CMSG_MOVE_WATER_WALK_ACK = 0x02D0, + CMSG_MOVE_NOT_ACTIVE_MOVER = 0x02D1, + SMSG_PLAY_SOUND = 0x02D2, + CMSG_BATTLEFIELD_STATUS = 0x02D3, + SMSG_BATTLEFIELD_STATUS = 0x02D4, + CMSG_BATTLEGROUND_PORT_AND_LEAVE = 0x02D5, + CMSG_BATTLEMASTER_HELLO = 0x02D7, + CMSG_MOVE_START_SWIM_CHEAT = 0x02D8, + CMSG_MOVE_STOP_SWIM_CHEAT = 0x02D9, + SMSG_FORCE_WALK_SPEED_CHANGE = 0x02DA, + CMSG_FORCE_WALK_SPEED_CHANGE_ACK = 0x02DB, + SMSG_FORCE_SWIM_BACK_SPEED_CHANGE = 0x02DC, + CMSG_FORCE_SWIM_BACK_SPEED_CHANGE_ACK = 0x02DD, + SMSG_FORCE_TURN_RATE_CHANGE = 0x02DE, + CMSG_FORCE_TURN_RATE_CHANGE_ACK = 0x02DF, + CMSG_LEAVE_BATTLEFIELD = 0x02E1, + CMSG_AREA_SPIRIT_HEALER_QUERY = 0x02E2, + CMSG_AREA_SPIRIT_HEALER_QUEUE = 0x02E3, + SMSG_AREA_SPIRIT_HEALER_TIME = 0x02E4, + CMSG_GM_UNTEACH = 0x02E5, + SMSG_WARDEN_DATA = 0x02E6, + CMSG_WARDEN_DATA = 0x02E7, + SMSG_BATTLEFIELD_STATUS_QUEUED = 0x02E8, + CMSG_PET_STOP_ATTACK = 0x02EA, + SMSG_BINDER_CONFIRM = 0x02EB, + SMSG_BATTLEGROUND_PLAYER_JOINED = 0x02EC, + SMSG_BATTLEGROUND_PLAYER_LEFT = 0x02ED, + CMSG_BATTLEMASTER_JOIN = 0x02EE, + SMSG_ADDON_INFO = 0x02EF, + CMSG_PET_UNLEARN = 0x02F0, + SMSG_PET_UNLEARN_CONFIRM = 0x02F1, + SMSG_PARTY_MEMBER_STATS_FULL = 0x02F2, + CMSG_PET_SPELL_AUTOCAST = 0x02F3, + SMSG_WEATHER = 0x02F4, + SMSG_PLAY_TIME_WARNING = 0x02F5, + SMSG_MINIGAME_SETUP = 0x02F6, + SMSG_MINIGAME_STATE = 0x02F7, + CMSG_MINIGAME_MOVE = 0x02F8, + SMSG_MINIGAME_MOVE_FAILED = 0x02F9, + SMSG_RAID_INSTANCE_MESSAGE = 0x02FA, + SMSG_COMPRESSED_MOVES = 0x02FB, + CMSG_GUILD_INFO_TEXT = 0x02FC, + SMSG_CHAT_RESTRICTED = 0x02FD, + SMSG_MOVE_SPLINE_SET_RUN_SPEED = 0x02FE, + SMSG_MOVE_SPLINE_SET_RUN_BACK_SPEED = 0x02FF, + SMSG_MOVE_SPLINE_SET_SWIM_SPEED = 0x0300, + SMSG_MOVE_SPLINE_SET_WALK_BACK_SPEED = 0x0301, + SMSG_MOVE_SPLINE_SET_SWIM_BACK_SPEED = 0x0302, + SMSG_MOVE_SPLINE_SET_TURN_RATE = 0x0303, + SMSG_MOVE_SPLINE_UNROOT = 0x0304, + SMSG_MOVE_SPLINE_SET_FEATHER_FALL = 0x0305, + SMSG_MOVE_SPLINE_SET_NORMAL_FALL = 0x0306, + SMSG_MOVE_SPLINE_SET_HOVER = 0x0307, + SMSG_MOVE_SPLINE_UNSET_HOVER = 0x0308, + SMSG_MOVE_SPLINE_SET_WATER_WALK = 0x0309, + SMSG_MOVE_SPLINE_SET_LAND_WALK = 0x030A, + SMSG_MOVE_SPLINE_START_SWIM = 0x030B, + SMSG_MOVE_SPLINE_STOP_SWIM = 0x030C, + SMSG_MOVE_SPLINE_SET_RUN_MODE = 0x030D, + SMSG_MOVE_SPLINE_SET_WALK_MODE = 0x030E, + CMSG_GM_NUKE_ACCOUNT = 0x030F, + CMSG_GM_DESTROY_ONLINE_CORPSE = 0x0311, + CMSG_ACTIVATE_TAXI_EXPRESS = 0x0312, + SMSG_SET_FACTION_AT_WAR = 0x0313, + SMSG_GAMETIMEBIAS_SET = 0x0314, + CMSG_DEBUG_ACTIONS_START = 0x0315, + CMSG_DEBUG_ACTIONS_STOP = 0x0316, + CMSG_SET_FACTION_INACTIVE = 0x0317, + CMSG_SET_WATCHED_FACTION = 0x0318, + SMSG_MOVE_SPLINE_ROOT = 0x031A, + CMSG_SET_EXPLORATION_ALL = 0x031B, + SMSG_INVALIDATE_PLAYER = 0x031C, + CMSG_RESET_INSTANCES = 0x031D, + SMSG_INSTANCE_RESET = 0x031E, + SMSG_INSTANCE_RESET_FAILED = 0x031F, + SMSG_UPDATE_LAST_INSTANCE = 0x0320, + CMSG_LUA_USAGE = 0x0323, + SMSG_PET_ACTION_SOUND = 0x0324, + SMSG_PET_DISMISS_SOUND = 0x0325, + SMSG_GHOSTEE_GONE = 0x0326, + CMSG_GM_UPDATE_TICKET_STATUS = 0x0327, + SMSG_GM_TICKET_STATUS_UPDATE = 0x0328, + CMSG_GM_SURVEY_SUBMIT = 0x032A, + SMSG_UPDATE_INSTANCE_OWNERSHIP = 0x032B, + CMSG_IGNORE_KNOCKBACK_CHEAT = 0x032C, + SMSG_CHAT_PLAYER_AMBIGUOUS = 0x032D, + SMSG_SPELL_INSTAKILL_LOG = 0x032F, + SMSG_SPELL_UPDATE_CHAIN_TARGETS = 0x0330, + CMSG_CHAT_REPORT_FILTERED = 0x0331, + SMSG_EXPECTED_SPAM_RECORDS = 0x0332, + SMSG_SPELL_STEAL_LOG = 0x0333, + CMSG_LOTTERY_QUERY_OBSOLETE = 0x0334, + SMSG_LOTTERY_QUERY_RESULT_OBSOLETE = 0x0335, + CMSG_BUY_LOTTERY_TICKET_OBSOLETE = 0x0336, + SMSG_LOTTERY_RESULT_OBSOLETE = 0x0337, + SMSG_CHARACTER_PROFILE = 0x0338, + SMSG_CHARACTER_PROFILE_REALM_CONNECTED = 0x0339, + SMSG_DEFENSE_MESSAGE = 0x033A, + SMSG_INSTANCE_DIFFICULTY = 0x033B, + SMSG_MOTD = 0x033D, + SMSG_MOVE_ENABLE_TRANSITION_BETWEEN_SWIM_AND_FLY = 0x033E, + SMSG_MOVE_DISABLE_TRANSITION_BETWEEN_SWIM_AND_FLY = 0x033F, + CMSG_MOVE_SET_CAN_TRANSITION_BETWEEN_SWIM_AND_FLY_ACK = 0x0340, + SMSG_MOVE_SET_CAN_FLY = 0x0343, + SMSG_MOVE_UNSET_CAN_FLY = 0x0344, + CMSG_MOVE_SET_CAN_FLY_ACK = 0x0345, + CMSG_MOVE_SET_FLY = 0x0346, + CMSG_SOCKET_GEMS = 0x0347, + CMSG_ARENA_TEAM_CREATE = 0x0348, + SMSG_ARENA_TEAM_COMMAND_RESULT = 0x0349, + CMSG_ARENA_TEAM_QUERY = 0x034B, + SMSG_ARENA_TEAM_QUERY_RESPONSE = 0x034C, + CMSG_ARENA_TEAM_ROSTER = 0x034D, + SMSG_ARENA_TEAM_ROSTER = 0x034E, + CMSG_ARENA_TEAM_INVITE = 0x034F, + SMSG_ARENA_TEAM_INVITE = 0x0350, + CMSG_ARENA_TEAM_ACCEPT = 0x0351, + CMSG_ARENA_TEAM_DECLINE = 0x0352, + CMSG_ARENA_TEAM_LEAVE = 0x0353, + CMSG_ARENA_TEAM_REMOVE = 0x0354, + CMSG_ARENA_TEAM_DISBAND = 0x0355, + CMSG_ARENA_TEAM_LEADER = 0x0356, + SMSG_ARENA_TEAM_EVENT = 0x0357, + CMSG_BATTLEMASTER_JOIN_ARENA = 0x0358, + SMSG_ARENA_TEAM_STATS = 0x035B, + CMSG_LFG_JOIN = 0x035C, + CMSG_LFG_LEAVE = 0x035D, + CMSG_LFG_LFR_JOIN = 0x035E, + CMSG_LFG_LFR_LEAVE = 0x035F, + SMSG_LFG_LFR_LIST = 0x0360, + SMSG_LFG_PROPOSAL_UPDATE = 0x0361, + CMSG_LFG_PROPOSAL_RESULT = 0x0362, + SMSG_LFG_ROLE_CHECK_UPDATE = 0x0363, + SMSG_LFG_JOIN_RESULT = 0x0364, + SMSG_LFG_QUEUE_STATUS = 0x0365, + CMSG_LFG_SET_COMMENT = 0x0366, + SMSG_LFG_UPDATE_PLAYER = 0x0367, + SMSG_LFG_UPDATE_PARTY = 0x0368, + SMSG_LFG_UPDATE_SEARCH = 0x0369, + CMSG_LFG_SET_ROLES = 0x036A, + CMSG_LFG_SET_NEEDS = 0x036B, + CMSG_LFG_SET_BOOT_VOTE = 0x036C, + SMSG_LFG_BOOT_PROPOSAL_UPDATE = 0x036D, + CMSG_LFG_PLAYER_LOCK_INFO_REQUEST = 0x036E, + SMSG_LFG_PLAYER_INFO = 0x036F, + CMSG_LFG_TELEPORT = 0x0370, + CMSG_LFG_PARTY_LOCK_INFO_REQUEST = 0x0371, + SMSG_LFG_PARTY_INFO = 0x0372, + SMSG_TITLE_EARNED = 0x0373, + CMSG_SET_TITLE = 0x0374, + CMSG_CANCEL_MOUNT_AURA = 0x0375, + SMSG_ARENA_ERROR = 0x0376, + SMSG_DEATH_RELEASE_LOC = 0x0378, + CMSG_CANCEL_TEMP_ENCHANTMENT = 0x0379, + SMSG_FORCED_DEATH_UPDATE = 0x037A, + CMSG_CHEAT_SET_HONOR_CURRENCY = 0x037B, + CMSG_CHEAT_SET_ARENA_CURRENCY = 0x037C, + SMSG_FORCE_FLIGHT_SPEED_CHANGE = 0x0381, + CMSG_FORCE_FLIGHT_SPEED_CHANGE_ACK = 0x0382, + SMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE = 0x0383, + CMSG_FORCE_FLIGHT_BACK_SPEED_CHANGE_ACK = 0x0384, + SMSG_MOVE_SPLINE_SET_FLIGHT_SPEED = 0x0385, + SMSG_MOVE_SPLINE_SET_FLIGHT_BACK_SPEED = 0x0386, + CMSG_MAELSTROM_INVALIDATE_CACHE = 0x0387, + SMSG_FLIGHT_SPLINE_SYNC = 0x0388, + CMSG_SET_TAXI_BENCHMARK_MODE = 0x0389, + SMSG_JOINED_BATTLEGROUND_QUEUE = 0x038A, + SMSG_REALM_SPLIT = 0x038B, + CMSG_REALM_SPLIT = 0x038C, + CMSG_MOVE_CHANGE_TRANSPORT = 0x038D, + SMSG_OFFER_PETITION_ERROR = 0x038F, + SMSG_TIME_SYNC_REQUEST = 0x0390, + CMSG_TIME_SYNC_RESPONSE = 0x0391, + CMSG_SEND_LOCAL_EVENT = 0x0392, + CMSG_SEND_GENERAL_TRIGGER = 0x0393, + CMSG_SEND_COMBAT_TRIGGER = 0x0394, + CMSG_MAELSTROM_GM_SENT_MAIL = 0x0395, + SMSG_RESET_FAILED_NOTIFY = 0x0396, + SMSG_REAL_GROUP_UPDATE = 0x0397, + SMSG_LFG_DISABLED = 0x0398, + CMSG_ACTIVE_PVP_CHEAT = 0x0399, + CMSG_CHEAT_DUMP_ITEMS_DEBUG_ONLY = 0x039A, + SMSG_CHEAT_DUMP_ITEMS_DEBUG_ONLY_RESPONSE = 0x039B, + SMSG_CHEAT_DUMP_ITEMS_DEBUG_ONLY_RESPONSE_WRITE_FILE = 0x039C, + SMSG_UPDATE_COMBO_POINTS = 0x039D, + SMSG_VOICE_SESSION_ROSTER_UPDATE = 0x039E, + SMSG_VOICE_SESSION_LEAVE = 0x039F, + SMSG_VOICE_SESSION_ADJUST_PRIORITY = 0x03A0, + CMSG_VOICE_SET_TALKER_MUTED_REQUEST = 0x03A1, + SMSG_VOICE_SET_TALKER_MUTED = 0x03A2, + SMSG_INIT_EXTRA_AURA_INFO_OBSOLETE = 0x03A3, + SMSG_SET_EXTRA_AURA_INFO_OBSOLETE = 0x03A4, + SMSG_SET_EXTRA_AURA_INFO_NEED_UPDATE_OBSOLETE = 0x03A5, + SMSG_CLEAR_EXTRA_AURA_INFO_OBSOLETE = 0x03A6, + CMSG_IGNORE_REQUIREMENTS_CHEAT = 0x03A8, + SMSG_IGNORE_REQUIREMENTS_CHEAT = 0x03A9, + SMSG_SPELL_CHANCE_PROC_LOG = 0x03AA, + CMSG_MOVE_SET_RUN_SPEED = 0x03AB, + SMSG_DISMOUNT = 0x03AC, + CMSG_VOICE_SESSION_ENABLE = 0x03AF, + SMSG_VOICE_SESSION_ENABLE = 0x03B0, + SMSG_VOICE_PARENTAL_CONTROLS = 0x03B1, + CMSG_GM_WHISPER = 0x03B2, + SMSG_GM_MESSAGECHAT = 0x03B3, + CMSG_COMMENTATOR_ENABLE = 0x03B5, + SMSG_COMMENTATOR_STATE_CHANGED = 0x03B6, + CMSG_COMMENTATOR_GET_MAP_INFO = 0x03B7, + SMSG_COMMENTATOR_MAP_INFO = 0x03B8, + CMSG_COMMENTATOR_GET_PLAYER_INFO = 0x03B9, + SMSG_COMMENTATOR_GET_PLAYER_INFO = 0x03BA, + SMSG_COMMENTATOR_PLAYER_INFO = 0x03BB, + CMSG_COMMENTATOR_ENTER_INSTANCE = 0x03BC, + CMSG_COMMENTATOR_EXIT_INSTANCE = 0x03BD, + CMSG_COMMENTATOR_INSTANCE_COMMAND = 0x03BE, + SMSG_CLEAR_TARGET = 0x03BF, + CMSG_BOT_DETECTED = 0x03C0, + SMSG_CROSSED_INEBRIATION_THRESHOLD = 0x03C1, + CMSG_CHEAT_PLAYER_LOGIN = 0x03C2, + CMSG_CHEAT_PLAYER_LOOKUP = 0x03C3, + SMSG_CHEAT_PLAYER_LOOKUP = 0x03C4, + SMSG_KICK_REASON = 0x03C5, + CMSG_COMPLAINT = 0x03C7, + SMSG_COMPLAINT_RESULT = 0x03C8, + SMSG_FEATURE_SYSTEM_STATUS = 0x03C9, + CMSG_GM_SHOW_COMPLAINTS = 0x03CA, + CMSG_GM_UNSQUELCH = 0x03CB, + CMSG_CHAT_CHANNEL_SILENCE_VOICE = 0x03CC, + CMSG_CHAT_CHANNEL_SILENCE_ALL = 0x03CD, + CMSG_CHAT_CHANNEL_UNSILENCE_VOICE = 0x03CE, + CMSG_CHAT_CHANNEL_UNSILENCE_ALL = 0x03CF, + CMSG_TARGET_CAST = 0x03D0, + CMSG_TARGET_SCRIPT_CAST = 0x03D1, + CMSG_CHAT_CHANNEL_DISPLAY_LIST = 0x03D2, + CMSG_SET_ACTIVE_VOICE_CHANNEL = 0x03D3, + CMSG_GET_CHANNEL_MEMBER_COUNT = 0x03D4, + SMSG_CHANNEL_MEMBER_COUNT = 0x03D5, + CMSG_CHAT_CHANNEL_VOICE_ON = 0x03D6, + CMSG_CHAT_CHANNEL_VOICE_OFF = 0x03D7, + CMSG_DEBUG_LIST_TARGETS = 0x03D8, + SMSG_DEBUG_LIST_TARGETS = 0x03D9, + SMSG_AVAILABLE_VOICE_CHANNEL = 0x03DA, + CMSG_VOICE_ADD_IGNORE = 0x03DB, + CMSG_VOICE_DEL_IGNORE = 0x03DC, + CMSG_PARTY_SILENCE = 0x03DD, + CMSG_PARTY_UNSILENCE = 0x03DE, + SMSG_COMSAT_RECONNECT_TRY = 0x03E0, + SMSG_COMSAT_DISCONNECT = 0x03E1, + SMSG_COMSAT_CONNECT_FAIL = 0x03E2, + SMSG_VOICE_CHAT_STATUS = 0x03E3, + CMSG_REPORT_PVP_PLAYER_AFK = 0x03E4, + SMSG_REPORT_PVP_AFK_RESULT = 0x03E5, + CMSG_GUILD_BANK_ACTIVATE = 0x03E6, + CMSG_GUILD_BANK_QUERY_TAB = 0x03E7, + SMSG_GUILD_BANK_QUERY_RESULTS = 0x03E8, + CMSG_GUILD_BANK_SWAP_ITEMS = 0x03E9, + CMSG_GUILD_BANK_BUY_TAB = 0x03EA, + CMSG_GUILD_BANK_UPDATE_TAB = 0x03EB, + CMSG_GUILD_BANK_DEPOSIT_MONEY = 0x03EC, + CMSG_GUILD_BANK_WITHDRAW_MONEY = 0x03ED, + CMSG_SET_CHANNEL_WATCH = 0x03EF, + SMSG_USERLIST_ADD = 0x03F0, + SMSG_USERLIST_REMOVE = 0x03F1, + SMSG_USERLIST_UPDATE = 0x03F2, + CMSG_CLEAR_CHANNEL_WATCH = 0x03F3, + SMSG_INSPECT_TALENT = 0x03F4, + SMSG_GOGOGO_OBSOLETE = 0x03F5, + SMSG_ECHO_PARTY_SQUELCH = 0x03F6, + CMSG_SET_TITLE_SUFFIX = 0x03F7, + CMSG_SPELL_CLICK = 0x03F8, + SMSG_LOOT_LIST = 0x03F9, + CMSG_GM_CHARACTER_RESTORE = 0x03FA, + CMSG_GM_CHARACTER_SAVE = 0x03FB, + SMSG_VOICESESSION_FULL = 0x03FC, + CMSG_MAELSTROM_RENAME_GUILD = 0x0400, + CMSG_GET_MIRROR_IMAGE_DATA = 0x0401, + SMSG_MIRROR_IMAGE_COMPONENTED_DATA = 0x0402, + SMSG_FORCE_DISPLAY_UPDATE = 0x0403, + SMSG_SPELL_CHANCE_RESIST_PUSHBACK = 0x0404, + CMSG_IGNORE_DIMINISHING_RETURNS_CHEAT = 0x0405, + SMSG_IGNORE_DIMINISHING_RETURNS_CHEAT = 0x0406, + CMSG_KEEP_ALIVE = 0x0407, + SMSG_READY_CHECK_ERROR = 0x0408, + CMSG_OPT_OUT_OF_LOOT = 0x0409, + CMSG_GUILD_BANK_SET_TAB_TEXT = 0x040B, + CMSG_SET_GRANTABLE_LEVELS = 0x040C, + CMSG_GRANT_LEVEL = 0x040D, + CMSG_REFER_A_FRIEND = 0x040E, + CMSG_CHAT_CHANNEL_DECLINE_INVITE = 0x0410, + SMSG_GROUP_ACTION_THROTTLED = 0x0411, + SMSG_OVERRIDE_LIGHT = 0x0412, + SMSG_TOTEM_CREATED = 0x0413, + CMSG_TOTEM_DESTROYED = 0x0414, + CMSG_EXPIRE_RAID_INSTANCE = 0x0415, + CMSG_NO_SPELL_VARIANCE = 0x0416, + CMSG_QUEST_GIVER_STATUS_MULTIPLE_QUERY = 0x0417, + SMSG_QUEST_GIVER_STATUS_MULTIPLE = 0x0418, + CMSG_SET_PLAYER_DECLINED_NAMES = 0x0419, + SMSG_SET_PLAYER_DECLINED_NAMES_RESULT = 0x041A, + CMSG_QUERY_SERVER_BUCK_DATA = 0x041B, + CMSG_CLEAR_SERVER_BUCK_DATA = 0x041C, + SMSG_SERVER_BUCK_DATA = 0x041D, + SMSG_SEND_UNLEARN_SPELLS = 0x041E, + SMSG_PROPOSE_LEVEL_GRANT = 0x041F, + CMSG_ACCEPT_LEVEL_GRANT = 0x0420, + SMSG_REFER_A_FRIEND_FAILURE = 0x0421, + SMSG_MOVE_SPLINE_SET_FLYING = 0x0422, + SMSG_MOVE_SPLINE_UNSET_FLYING = 0x0423, + SMSG_SUMMON_CANCEL = 0x0424, + CMSG_CHANGE_PERSONAL_ARENA_RATING = 0x0425, + CMSG_ALTER_APPEARANCE = 0x0426, + SMSG_ENABLE_BARBER_SHOP = 0x0427, + SMSG_BARBER_SHOP_RESULT = 0x0428, + CMSG_CALENDAR_GET_CALENDAR = 0x0429, + CMSG_CALENDAR_GET_EVENT = 0x042A, + CMSG_CALENDAR_GUILD_FILTER = 0x042B, + CMSG_CALENDAR_ARENA_TEAM = 0x042C, + CMSG_CALENDAR_ADD_EVENT = 0x042D, + CMSG_CALENDAR_UPDATE_EVENT = 0x042E, + CMSG_CALENDAR_REMOVE_EVENT = 0x042F, + CMSG_CALENDAR_COPY_EVENT = 0x0430, + CMSG_CALENDAR_EVENT_INVITE = 0x0431, + CMSG_CALENDAR_EVENT_RSVP = 0x0432, + CMSG_CALENDAR_EVENT_REMOVE_INVITE = 0x0433, + CMSG_CALENDAR_EVENT_STATUS = 0x0434, + CMSG_CALENDAR_EVENT_MODERATOR_STATUS = 0x0435, + SMSG_CALENDAR_SEND_CALENDAR = 0x0436, + SMSG_CALENDAR_SEND_EVENT = 0x0437, + SMSG_CALENDAR_FILTER_GUILD = 0x0438, + SMSG_CALENDAR_ARENA_TEAM = 0x0439, + SMSG_CALENDAR_EVENT_INVITE = 0x043A, + SMSG_CALENDAR_EVENT_INVITE_REMOVED = 0x043B, + SMSG_CALENDAR_EVENT_STATUS = 0x043C, + SMSG_CALENDAR_COMMAND_RESULT = 0x043D, + SMSG_CALENDAR_RAID_LOCKOUT_ADDED = 0x043E, + SMSG_CALENDAR_RAID_LOCKOUT_REMOVED = 0x043F, + SMSG_CALENDAR_EVENT_INVITE_ALERT = 0x0440, + SMSG_CALENDAR_EVENT_INVITE_REMOVED_ALERT = 0x0441, + SMSG_CALENDAR_EVENT_INVITE_STATUS_ALERT = 0x0442, + SMSG_CALENDAR_EVENT_REMOVED_ALERT = 0x0443, + SMSG_CALENDAR_EVENT_UPDATED_ALERT = 0x0444, + SMSG_CALENDAR_EVENT_MODERATOR_STATUS_ALERT = 0x0445, + CMSG_CALENDAR_COMPLAIN = 0x0446, + CMSG_CALENDAR_GET_NUM_PENDING = 0x0447, + SMSG_CALENDAR_SEND_NUM_PENDING = 0x0448, + CMSG_SAVE_DANCE = 0x0449, + SMSG_NOTIFY_DANCE = 0x044A, + CMSG_PLAY_DANCE = 0x044B, + SMSG_PLAY_DANCE = 0x044C, + CMSG_LOAD_DANCES = 0x044D, + CMSG_STOP_DANCE = 0x044E, + SMSG_STOP_DANCE = 0x044F, + CMSG_SYNC_DANCE = 0x0450, + CMSG_DANCE_QUERY = 0x0451, + SMSG_DANCE_QUERY_RESPONSE = 0x0452, + SMSG_INVALIDATE_DANCE = 0x0453, + CMSG_DELETE_DANCE = 0x0454, + SMSG_LEARNED_DANCE_MOVES = 0x0455, + CMSG_LEARN_DANCE_MOVE = 0x0456, + CMSG_UNLEARN_DANCE_MOVE = 0x0457, + CMSG_SET_RUNE_COUNT = 0x0458, + CMSG_SET_RUNE_COOLDOWN = 0x0459, + SMSG_FORCE_PITCH_RATE_CHANGE = 0x045C, + CMSG_FORCE_PITCH_RATE_CHANGE_ACK = 0x045D, + SMSG_MOVE_SPLINE_SET_PITCH_RATE = 0x045E, + CMSG_CALENDAR_EVENT_INVITE_NOTES = 0x045F, + SMSG_CALENDAR_EVENT_INVITE_NOTES = 0x0460, + SMSG_CALENDAR_EVENT_INVITE_NOTES_ALERT = 0x0461, + CMSG_UPDATE_MISSILE_TRAJECTORY = 0x0462, + SMSG_UPDATE_ACCOUNT_DATA_COMPLETE = 0x0463, + SMSG_TRIGGER_MOVIE = 0x0464, + CMSG_COMPLETE_MOVIE = 0x0465, + CMSG_SET_GLYPH_SLOT = 0x0466, + CMSG_SET_GLYPH = 0x0467, + SMSG_ACHIEVEMENT_EARNED = 0x0468, + SMSG_DYNAMIC_DROP_ROLL_RESULT = 0x0469, + SMSG_CRITERIA_UPDATE = 0x046A, + CMSG_QUERY_INSPECT_ACHIEVEMENTS = 0x046B, + SMSG_RESPOND_INSPECT_ACHIEVEMENTS = 0x046C, + CMSG_DISMISS_CONTROLLED_VEHICLE = 0x046D, + CMSG_COMPLETE_ACHIEVEMENT_CHEAT = 0x046E, + SMSG_QUEST_UPDATE_ADD_PVP_CREDIT = 0x046F, + CMSG_SET_CRITERIA_CHEAT = 0x0470, + SMSG_CALENDAR_RAID_LOCKOUT_UPDATED = 0x0471, + CMSG_UNITANIMTIER_CHEAT = 0x0472, + CMSG_CHAR_CUSTOMIZE = 0x0473, + SMSG_CHAR_CUSTOMIZE = 0x0474, + SMSG_PET_RENAMEABLE = 0x0475, + CMSG_REQUEST_VEHICLE_EXIT = 0x0476, + CMSG_REQUEST_VEHICLE_PREV_SEAT = 0x0477, + CMSG_REQUEST_VEHICLE_NEXT_SEAT = 0x0478, + CMSG_REQUEST_VEHICLE_SWITCH_SEAT = 0x0479, + CMSG_PET_LEARN_TALENT = 0x047A, + CMSG_PET_UNLEARN_TALENTS = 0x047B, + SMSG_PHASE_SHIFT_CHANGE = 0x047C, + SMSG_ALL_ACHIEVEMENT_DATA = 0x047D, + CMSG_FORCE_SAY_CHEAT = 0x047E, + SMSG_HEALTH_UPDATE = 0x047F, + SMSG_POWER_UPDATE = 0x0480, + CMSG_GAME_OBJ_REPORT_USE = 0x0481, + SMSG_HIGHEST_THREAT_UPDATE = 0x0482, + SMSG_THREAT_UPDATE = 0x0483, + SMSG_THREAT_REMOVE = 0x0484, + SMSG_THREAT_CLEAR = 0x0485, + SMSG_CONVERT_RUNE = 0x0486, + SMSG_RESYNC_RUNES = 0x0487, + SMSG_ADD_RUNE_POWER = 0x0488, + CMSG_QUERY_QUEST_COMPLETION_NPCS = 0x0489, + CMSG_REMOVE_GLYPH = 0x048A, + CMSG_DUMP_OBJECTS = 0x048B, + SMSG_DUMP_OBJECTS_DATA = 0x048C, + CMSG_DISMISS_CRITTER = 0x048D, + SMSG_NOTIFY_DEST_LOC_SPELL_CAST = 0x048E, + CMSG_AUCTION_LIST_PENDING_SALES = 0x048F, + SMSG_AUCTION_LIST_PENDING_SALES = 0x0490, + SMSG_MODIFY_COOLDOWN = 0x0491, + SMSG_PET_UPDATE_COMBO_POINTS = 0x0492, + CMSG_ENABLE_TAXI_NODE = 0x0493, + SMSG_PRE_RESSURECT = 0x0494, + SMSG_AURA_UPDATE_ALL = 0x0495, + SMSG_AURA_UPDATE = 0x0496, + CMSG_FLOOD_GRACE_CHEAT = 0x0497, + SMSG_SERVER_FIRST_ACHIEVEMENT = 0x0498, + SMSG_PET_LEARNED_SPELLS = 0x0499, + SMSG_PET_UNLEARNED_SPELLS = 0x049A, + CMSG_CHANGE_SEATS_ON_CONTROLLED_VEHICLE = 0x049B, + CMSG_HEARTH_AND_RESURRECT = 0x049C, + SMSG_ON_CANCEL_EXPECTED_RIDE_VEHICLE_AURA = 0x049D, + SMSG_CRITERIA_DELETED = 0x049E, + SMSG_ACHIEVEMENT_DELETED = 0x049F, + CMSG_SERVER_INFO_QUERY = 0x04A0, + SMSG_SERVER_INFO_RESPONSE = 0x04A1, + CMSG_CHECK_LOGIN_CRITERIA = 0x04A2, + SMSG_SERVER_BUCK_DATA_START = 0x04A3, + CMSG_SET_BREATH = 0x04A4, + CMSG_QUERY_VEHICLE_STATUS = 0x04A5, + SMSG_BATTLEGROUND_INFO_THROTTLED = 0x04A6, + SMSG_PLAYER_VEHICLE_DATA = 0x04A7, + CMSG_PLAYER_VEHICLE_ENTER = 0x04A8, + CMSG_EJECT_PASSENGER = 0x04A9, + SMSG_PET_GUIDS = 0x04AA, + SMSG_CACHE_VERSION = 0x04AB, + CMSG_CHANGE_GDF_ARENA_RATING = 0x04AC, + CMSG_SET_ARENA_TEAM_RATING_BY_INDEX = 0x04AD, + CMSG_SET_ARENA_TEAM_WEEKLY_GAMES = 0x04AE, + CMSG_SET_ARENA_TEAM_SEASON_GAMES = 0x04AF, + CMSG_SET_ARENA_MEMBER_WEEKLY_GAMES = 0x04B0, + CMSG_SET_ARENA_MEMBER_SEASON_GAMES = 0x04B1, + SMSG_ITEM_REFUND_INFO_RESPONSE = 0x04B2, + CMSG_GET_ITEM_PURCHASE_DATA = 0x04B3, + CMSG_ITEM_PURCHASE_REFUND = 0x04B4, + SMSG_ITEM_PURCHASE_REFUND_RESULT = 0x04B5, + CMSG_CORPSE_MAP_POSITION_QUERY = 0x04B6, + SMSG_CORPSE_MAP_POSITION_QUERY_RESPONSE = 0x04B7, + SMSG_LOAD_EQUIPMENT_SET = 0x04BC, + NUM_MSG_TYPES = 0x051F, +}; + +enum NETSTATE { + NS_UNINITIALIZED = 0, + NS_INITIALIZING = 1, + NS_INITIALIZED = 2, +}; + +enum WOW_CONN_STATE { + WOWC_UNINITIALIZED = 0, + WOWC_INITIALIZED = 1, + WOWC_CONNECTING = 2, + WOWC_LISTENING = 3, + WOWC_ACCEPTED = 4, + WOWC_CONNECTED = 5, + WOWC_DISCONNECTED = 6, + WOWC_DISCONNECTING = 7, + WOWC_ERROR = 8, +}; + +enum WOWC_TYPE { + WOWC_TYPE_MESSAGES = 0, + WOWC_TYPE_STREAM = 1, +}; + +enum WC_SEND_RESULT { + WC_SEND_SENT = 0, + WC_SEND_QUEUED = 1, + WC_SEND_ERROR = 2, +}; + +enum WOWCS_OPS { + COP_NONE = 0, + COP_INIT = 1, + COP_CONNECT = 2, + COP_AUTHENTICATE = 3, + COP_CREATE_ACCOUNT = 4, + COP_CREATE_CHARACTER = 5, + COP_GET_CHARACTERS = 6, + COP_DELETE_CHARACTER = 7, + COP_LOGIN_CHARACTER = 8, + COP_GET_REALMS = 9, + COP_WAIT_QUEUE = 10, +}; + +struct NETADDR { + uint16_t family; + char data[14]; +}; + +struct NETCONNADDR { + NETADDR peerAddr; + NETADDR selfAddr; +}; + +#endif diff --git a/src/net/connection/ClientConnection.cpp b/src/net/connection/ClientConnection.cpp new file mode 100644 index 0000000..c618e35 --- /dev/null +++ b/src/net/connection/ClientConnection.cpp @@ -0,0 +1,34 @@ +#include "net/connection/ClientConnection.hpp" +#include "net/Login.hpp" +#include "client/ClientServices.hpp" + +void ClientConnection::Cancel(int32_t errorCode) { + this->Cleanup(); + + this->m_statusResult = 0; + this->m_errorCode = errorCode; + this->m_statusComplete = 1; + + // TODO + // LogConnectionStatus(this->m_statusCop, errorCode, 0); +} + +void ClientConnection::Cleanup() { + if (this->m_cleanup) { + this->m_cleanup(); + this->m_cleanup = nullptr; + } +} + +void ClientConnection::Connect() { + // TODO + + this->m_cleanup = nullptr; + this->m_statusCop = COP_CONNECT; + this->m_errorCode = 7; + this->m_statusComplete = 0; + + // TODO + + ClientServices::LoginConnection()->GetRealmList(); +} diff --git a/src/net/connection/ClientConnection.hpp b/src/net/connection/ClientConnection.hpp new file mode 100644 index 0000000..5735614 --- /dev/null +++ b/src/net/connection/ClientConnection.hpp @@ -0,0 +1,27 @@ +#ifndef NET_CONNECTION_CLIENT_CONNECTION_HPP +#define NET_CONNECTION_CLIENT_CONNECTION_HPP + +#include "net/connection/RealmConnection.hpp" +#include "net/Types.hpp" + +class RealmResponse; + +class ClientConnection : public RealmConnection { + public: + // Member variables + int32_t m_statusComplete = 1; + int32_t m_statusResult = 1; + WOWCS_OPS m_statusCop = COP_NONE; + int32_t m_errorCode = 0; + void (*m_cleanup)() = nullptr; + + // Member functions + ClientConnection(RealmResponse* realmResponse) + : RealmConnection(realmResponse) + {}; + void Cancel(int32_t errorCode); + void Cleanup(); + void Connect(); +}; + +#endif diff --git a/src/net/connection/NetClient.cpp b/src/net/connection/NetClient.cpp new file mode 100644 index 0000000..62aa9ac --- /dev/null +++ b/src/net/connection/NetClient.cpp @@ -0,0 +1,66 @@ +#include "net/connection/NetClient.hpp" +#include "net/connection/WowConnection.hpp" +#include +#include +#include +#include + +HPROPCONTEXT s_propContext; + +int32_t NetClient::s_clientCount; + +void InitializePropContext() { + if (PropGetSelectedContext() != s_propContext) { + PropSelectContext(s_propContext); + } +} + +int32_t NetClient::Initialize() { + STORM_ASSERT(this->m_netState == NS_UNINITIALIZED); + + if (NetClient::s_clientCount == 0) { + s_propContext = PropGetSelectedContext(); + + if (!WowConnection::InitOsNet(nullptr, InitializePropContext, 1, false)) { + return 0; + } + } + + NetClient::s_clientCount++; + + auto queueMem = SMemAlloc(sizeof(NETEVENTQUEUE), __FILE__, __LINE__, 0x0); + auto queue = new (queueMem) NETEVENTQUEUE(this); + this->m_netEventQueue = queue; + + memset(this->m_handlers, 0, sizeof(this->m_handlers)); + memset(this->m_handlerParams, 0, sizeof(this->m_handlerParams)); + + auto connectionMem = SMemAlloc(sizeof(WowConnection), __FILE__, __LINE__, 0x0); + auto connection = new (connectionMem) WowConnection(this, nullptr); + this->m_serverConnection = connection; + + this->m_netState = NS_INITIALIZED; + + return 1; +} + +void NetClient::SetMessageHandler(NETMESSAGE msgId, MESSAGE_HANDLER handler, void* param) { + this->m_handlers[msgId] = handler; + this->m_handlerParams[msgId] = param; +} + +void NetClient::WCCantConnect(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr) { + // TODO +} + +void NetClient::WCConnected(WowConnection* conn, WowConnection* inbound, uint32_t timeStamp, const NETCONNADDR* addr) { + // TODO +} + +void NetClient::WCDataReady(WowConnection* conn, uint32_t timeStamp, uint8_t* data, int32_t len) { + // TODO +} + +void NetClient::WCDisconnected(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr) { + // TODO +} diff --git a/src/net/connection/NetClient.hpp b/src/net/connection/NetClient.hpp new file mode 100644 index 0000000..63deaa7 --- /dev/null +++ b/src/net/connection/NetClient.hpp @@ -0,0 +1,56 @@ +#ifndef NET_CONNECTION_NET_CLIENT_HPP +#define NET_CONNECTION_NET_CLIENT_HPP + +#include "net/connection/WowConnectionResponse.hpp" +#include "net/Types.hpp" +#include +#include +#include + +class CDataStore; +class NetClient; +class WowConnection; + +typedef int32_t (*MESSAGE_HANDLER)(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg); + +class NETEVENTQUEUENODE : public TSLinkedNode { + public: +}; + +class NETEVENTQUEUE { + public: + // Member variables + NetClient* m_client; + SCritSect m_critSect; + STORM_LIST(NETEVENTQUEUENODE) m_eventQueue; + + // Member functions + NETEVENTQUEUE(NetClient* client) + : m_client(client) + {}; +}; + +class NetClient : public WowConnectionResponse { + public: + // Static variables + static int32_t s_clientCount; + + // Member variables + NETSTATE m_netState = NS_UNINITIALIZED; + MESSAGE_HANDLER m_handlers[NUM_MSG_TYPES]; + void* m_handlerParams[NUM_MSG_TYPES]; + NETEVENTQUEUE* m_netEventQueue = nullptr; + WowConnection* m_serverConnection = nullptr; + + // Virtual member functions + virtual void WCConnected(WowConnection* conn, WowConnection* inbound, uint32_t timeStamp, const NETCONNADDR* addr); + virtual void WCCantConnect(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr); + virtual void WCDisconnected(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr); + virtual void WCDataReady(WowConnection* conn, uint32_t timeStamp, uint8_t* data, int32_t len); + + // Member functions + int32_t Initialize(); + void SetMessageHandler(NETMESSAGE msgId, MESSAGE_HANDLER handler, void* param); +}; + +#endif diff --git a/src/net/connection/RealmConnection.cpp b/src/net/connection/RealmConnection.cpp new file mode 100644 index 0000000..68e87b7 --- /dev/null +++ b/src/net/connection/RealmConnection.cpp @@ -0,0 +1,29 @@ +#include "net/connection/RealmConnection.hpp" +#include "net/Types.hpp" + +int32_t RealmConnection::MessageHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg) { + // TODO + return 0; +} + +RealmConnection::RealmConnection(RealmResponse* realmResponse) { + this->m_realmResponse = realmResponse; + + // TODO + + this->Initialize(); + + this->SetMessageHandler(SMSG_AUTH_CHALLENGE, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_AUTH_RESPONSE, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_ADDON_INFO, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_ENUM_CHARACTERS_RESULT, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_CREATE_CHAR, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_CHARACTER_LOGIN_FAILED, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_LOGOUT_COMPLETE, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_LOGOUT_CANCEL_ACK, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_LOGOUT_RESPONSE, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_DELETE_CHAR, &RealmConnection::MessageHandler, this); + this->SetMessageHandler(SMSG_CACHE_VERSION, &RealmConnection::MessageHandler, this); + + // TODO +} diff --git a/src/net/connection/RealmConnection.hpp b/src/net/connection/RealmConnection.hpp new file mode 100644 index 0000000..427bf96 --- /dev/null +++ b/src/net/connection/RealmConnection.hpp @@ -0,0 +1,22 @@ +#ifndef NET_CONNECTION_REALM_CONNECTION_HPP +#define NET_CONNECTION_REALM_CONNECTION_HPP + +#include "net/connection/NetClient.hpp" +#include + +class CDataStore; +class RealmResponse; + +class RealmConnection : public NetClient { + public: + // Static functions + int32_t static MessageHandler(void* param, NETMESSAGE msgId, uint32_t time, CDataStore* msg); + + // Member variables + RealmResponse* m_realmResponse; + + // Member functions + RealmConnection(RealmResponse* realmResponse); +}; + +#endif diff --git a/src/net/connection/RealmResponse.hpp b/src/net/connection/RealmResponse.hpp new file mode 100644 index 0000000..e4b6eb6 --- /dev/null +++ b/src/net/connection/RealmResponse.hpp @@ -0,0 +1,8 @@ +#ifndef NET_CONNECTION_REALM_RESPONSE_HPP +#define NET_CONNECTION_REALM_RESPONSE_HPP + +class RealmResponse { + public: +}; + +#endif diff --git a/src/net/connection/WowConnection.cpp b/src/net/connection/WowConnection.cpp new file mode 100644 index 0000000..4a88f62 --- /dev/null +++ b/src/net/connection/WowConnection.cpp @@ -0,0 +1,591 @@ +#include "net/connection/WowConnection.hpp" +#include "net/connection/WowConnectionNet.hpp" +#include "net/connection/WowConnectionResponse.hpp" +#include +#include +#include +#include +#include +#include +#include + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) +#include +#include +#include +#include +#include +#include +#include +#endif + +#if defined(WHOA_SYSTEM_WIN) +#include +#endif + +uint64_t WowConnection::s_countTotalBytes; +int32_t WowConnection::s_destroyed; +WowConnectionNet* WowConnection::s_network; +ATOMIC32 WowConnection::s_numWowConnections; +bool (*WowConnection::s_verifyAddr)(const NETADDR*); + +int32_t WowConnection::CreateSocket() { + int32_t sock = socket(AF_INET, SOCK_STREAM, 0); + + // TODO + + return sock; +} + +int32_t WowConnection::InitOsNet(bool (*fcn)(const NETADDR*), void (*threadinit)(), int32_t numThreads, bool useEngine) { + if (!WowConnection::s_network) { + // TODO s_usedSocketBits logic + // TODO WDataStore::StaticInitialize(); + + WowConnection::s_verifyAddr = fcn; + WowConnection::s_destroyed = 0; + + numThreads = std::min(numThreads, 32); + + auto networkMem = SMemAlloc(sizeof(WowConnectionNet), __FILE__, __LINE__, 0x0); + auto network = new (networkMem) WowConnectionNet(numThreads, threadinit); + + WowConnection::s_network = network; + WowConnection::s_network->PlatformInit(useEngine); + WowConnection::s_network->Start(); + } + + return 1; +} + +WowConnection::WowConnection(WowConnectionResponse* response, void (*func)(void)) { + // TODO + + this->Init(response, func); + + this->m_sock = -1; +} + +WowConnection::WowConnection(int32_t sock, sockaddr_in* addr, WowConnectionResponse* response) { + // TODO + + this->Init(response, nullptr); + + // TODO + + this->m_sock = sock; + this->m_connState = WOWC_CONNECTED; +} + +void WowConnection::AcquireResponseRef() { + this->m_responseLock.Enter(); + + STORM_ASSERT(this->m_responseRef == 0 || this->GetState() == WOWC_LISTENING); + + this->m_responseRef++; + this->m_responseRefThread = SGetCurrentThreadId(); + + this->m_responseLock.Leave(); +} + +void WowConnection::AddRef() { + SInterlockedIncrement(&this->m_refCount); +} + +void WowConnection::CheckAccept() { + for (int32_t i = 0; i < 10000; i++) { + NETADDR addr; + socklen_t addrLen = sizeof(addr); + + int32_t sock = accept(this->m_sock, reinterpret_cast(&addr), &addrLen); + if (sock < 0) { + break; + } + + if (WowConnection::s_verifyAddr) { + NETADDR verifyAddr; + socklen_t verifyAddrLen = sizeof(verifyAddr); + + getpeername(sock, reinterpret_cast(&verifyAddr), &verifyAddrLen); + if (!WowConnection::s_verifyAddr(&verifyAddr)) { + close(sock); + continue; + } + } + + // TODO + // RegisterSocket(sock); + + fcntl(sock, F_SETFL, O_NONBLOCK); + + auto connMem = SMemAlloc(sizeof(WowConnection), __FILE__, __LINE__, 0x0); + auto conn = new (connMem) WowConnection(sock, reinterpret_cast(&addr), this->m_response); + conn->AddRef(); + + this->AddRef(); + this->AcquireResponseRef(); + + this->m_lock.Leave(); + + if (this->m_response) { + this->m_response->WCConnected(this, conn, OsGetAsyncTimeMs(), &conn->m_peer); + } + + WowConnection::s_network->Add(conn); + WowConnection::s_network->PlatformChangeState(conn, conn->GetState()); + + conn->Release(); + + this->m_lock.Enter(); + + this->ReleaseResponseRef(); + + this->Release(); + } +} + +void WowConnection::CheckConnect() { + int32_t err; + socklen_t errLen = sizeof(err); + if (getsockopt(this->m_sock, SOL_SOCKET, SO_ERROR, &err, &errLen)) { + return; + } + + if (err) { + WowConnection::s_network->Remove(this); + + WowConnection::CloseSocket(this->m_sock); + this->m_sock = -1; + + this->SetState(WOWC_DISCONNECTED); + this->AddRef(); + this->AcquireResponseRef(); + + this->m_lock.Leave(); + + if (this->m_response) { + this->m_response->WCCantConnect(this, OsGetAsyncTimeMsPrecise(), &this->m_peer); + } + } else { + this->SetState(WOWC_CONNECTED); + this->AddRef(); + this->AcquireResponseRef(); + + this->m_lock.Leave(); + + socklen_t peerLen = sizeof(this->m_peer.peerAddr); + getpeername(this->m_sock, reinterpret_cast(&this->m_peer.peerAddr), &peerLen); + + socklen_t selfLen = sizeof(this->m_peer.selfAddr); + getsockname(this->m_sock, reinterpret_cast(&this->m_peer.selfAddr), &selfLen); + + if (this->m_response) { + this->m_response->WCConnected(this, nullptr, OsGetAsyncTimeMsPrecise(), &this->m_peer); + } + } + + this->m_lock.Enter(); + + this->ReleaseResponseRef(); + this->Release(); +} + +void WowConnection::CloseSocket(int32_t sock) { +#if defined(WHOA_SYSTEM_WIN) + closesocket(sock); +#endif + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + close(sock); +#endif + + if (sock >= 0) { + // TODO + } +} + +bool WowConnection::Connect(char const* address, int32_t retryMs) { + char host[256]; + auto port = SStrChr(address, ':'); + + if (port) { + this->m_connectPort = SStrToInt(port + 1); + + size_t portIndex = port - address + 1; + portIndex = std::min(portIndex, sizeof(host)); + SStrCopy(host, address, portIndex); + } else { + this->m_connectPort = 0; + + SStrCopy(host, address, sizeof(host)); + } + + this->Connect(host, this->m_connectPort, retryMs); + + return true; +} + +bool WowConnection::Connect(char const* address, uint16_t port, int32_t retryMs) { + auto connectAddress = inet_addr(address); + + if (connectAddress == -1 || connectAddress == 0) { + auto entry = gethostbyname(address); + if (entry) { + auto addrs = reinterpret_cast(entry->h_addr_list); + auto addr0 = addrs[0]; + this->m_connectAddress = addr0[0] + | addr0[1] << 8 + | addr0[2] << 16 + | addr0[3] << 24; + } else { + this->m_connectAddress = 0; + } + } else { + this->m_connectAddress = connectAddress; + } + + this->m_connectPort = port; + + this->StartConnect(); + + return true; +} + +void WowConnection::Disconnect() { + this->m_lock.Enter(); + + if (this->m_sock >= 0 && this->GetState() == WOWC_CONNECTED) { + this->m_connState = WOWC_DISCONNECTING; + + if (WowConnection::s_network) { + WowConnection::s_network->PlatformChangeState(this, WOWC_CONNECTED); + } + } + + this->m_lock.Leave(); +} + +void WowConnection::DoDisconnect() { + this->m_lock.Enter(); + + if (this->m_sock >= 0) { + WowConnection::s_network->Remove(this); + this->CloseSocket(this->m_sock); + } + + this->SetState(WOWC_DISCONNECTED); + + this->AddRef(); + this->AcquireResponseRef(); + + this->m_lock.Leave(); + + if (this->m_response && this->m_sock >= 0) { + // TODO + // this->m_response->Vfunc4(this, OsGetAsyncTimeMsPrecise()); + } + + this->m_lock.Enter(); + + this->m_sock = -1; + this->ReleaseResponseRef(); + + this->m_lock.Leave(); + + this->Release(); +} + +void WowConnection::DoExceptions() { + this->AddRef(); + this->m_lock.Enter(); + + if (this->GetState() == WOWC_CONNECTING) { + this->CheckConnect(); + } + + this->m_lock.Leave(); + this->Release(); +} + +void WowConnection::DoMessageReads() { + // TODO +} + +void WowConnection::DoReads() { + this->AddRef(); + + this->m_lock.Enter(); + + if (this->m_connState == WOWC_LISTENING) { + this->CheckAccept(); + } else if (this->m_connState == WOWC_CONNECTED) { + if (this->m_type == WOWC_TYPE_STREAM) { + this->DoStreamReads(); + } else { + this->DoMessageReads(); + } + } + + this->m_lock.Leave(); + + this->Release(); +} + +void WowConnection::DoStreamReads() { + uint32_t startTime = OsGetAsyncTimeMsPrecise(); + uint8_t buf[4096]; + uint32_t bytesRead; + + while (1) { + while (1) { + bytesRead = recv(this->m_sock, buf, sizeof(buf), 0); + + if (bytesRead >= 0) { + break; + } + +#if defined(WHOA_SYSTEM_WIN) + if (WSAGetLastError() != WSAEINTR) { + break; + } +#elif defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + if (errno != EINTR) { + break; + } +#endif + } + + if (bytesRead == 0) { + break; + } + + this->AcquireResponseRef(); + this->m_lock.Leave(); + + if (this->m_response) { + this->m_response->WCDataReady(this, OsGetAsyncTimeMs(), buf, bytesRead); + } + + this->m_lock.Enter(); + this->ReleaseResponseRef(); + + if (this->GetState() == WOWC_DISCONNECTING || (OsGetAsyncTimeMsPrecise() - startTime) >= 5) { + return; + } + } + + bool shouldDisconnect = false; +#if defined(WHOA_SYSTEM_WIN) + shouldDisconnect = bytesRead >= 0 || WSAGetLastError() != WSAEAGAIN; +#elif defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + shouldDisconnect = bytesRead >= 0 || errno != EAGAIN; +#endif + + if (shouldDisconnect) { + this->AcquireResponseRef(); + + WowConnection::s_network->Remove(this); + this->CloseSocket(this->m_sock); + this->SetState(WOWC_DISCONNECTED); + + this->m_lock.Leave(); + + if (this->m_response && this->m_sock >= 0) { + this->m_response->WCDisconnected(this, OsGetAsyncTimeMs(), &this->m_peer); + } + + this->m_lock.Enter(); + + this->m_sock = -1; + + this->ReleaseResponseRef(); + } +} + +void WowConnection::DoWrites() { + this->AddRef(); + + this->m_lock.Enter(); + + if (this->m_connState == WOWC_CONNECTING) { + this->CheckConnect(); + } else { + // TODO + } + + // TODO + + this->m_lock.Leave(); + + this->Release(); +} + +WOW_CONN_STATE WowConnection::GetState() { + return this->m_connState; +} + +void WowConnection::Init(WowConnectionResponse* response, void (*func)(void)) { + SInterlockedIncrement(&WowConnection::s_numWowConnections); + + this->m_refCount = 1; + this->m_responseRef = 0; + + // TODO + + this->m_connState = WOWC_UNINITIALIZED; + + // TODO + + this->m_response = response; + + // TODO + + this->m_connectAddress = 0; + this->m_connectPort = 0; + + // TODO + + this->m_serviceFlags = 0x0; + this->m_serviceCount = 0; + + // TODO + + this->SetState(WOWC_INITIALIZED); + this->m_type = WOWC_TYPE_MESSAGES; +} + +void WowConnection::Release() { + if (SInterlockedDecrement(&this->m_refCount) <= 0) { + if (WowConnection::s_network) { + WowConnection::s_network->Delete(this); + } else { + // TODO SMemFree + delete this; + } + } +} + +void WowConnection::ReleaseResponseRef() { + this->m_responseLock.Enter(); + + STORM_ASSERT(this->m_responseRef > 0); + + this->m_responseRef--; + + // TODO + // dwordD4 = (void *)this->dwordD4; + // if (dwordD4) { + // this->m_response = dwordD4; + // this->dwordD4 = 0; + // } + + this->m_responseLock.Leave(); +} + +WC_SEND_RESULT WowConnection::SendRaw(uint8_t* data, int32_t len, bool a4) { + WowConnection::s_countTotalBytes += len; + + this->m_lock.Enter(); + + // TODO + + if (len > 0 && this->m_connState == WOWC_CONNECTED) { + STORM_ASSERT(this->m_sock >= 0); + +#if defined (WHOA_SYSTEM_WIN) + // TODO +#elif defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + if (this->m_sendList.Head()) { + // TODO + } else { + auto written = write(this->m_sock, data, len); + + if (written <= 0) { + // TODO + } else if (written == len) { + this->m_lock.Leave(); + return WC_SEND_SENT; + } + } +#endif + } + + this->m_lock.Leave(); + + return WC_SEND_ERROR; +} + +void WowConnection::SetState(WOW_CONN_STATE state) { + WOW_CONN_STATE oldState = this->m_connState; + this->m_connState = state; + + if (WowConnection::s_network) { + WowConnection::s_network->PlatformChangeState(this, oldState); + } +} + +void WowConnection::SetType(WOWC_TYPE type) { + this->m_lock.Enter(); + this->m_type = type; + this->m_lock.Leave(); +} + +void WowConnection::StartConnect() { + if (this->m_sock >= 0) { + if (this->m_netlink.IsLinked()) { + WowConnection::s_network->Remove(this); + } + + this->CloseSocket(this->m_sock); + this->m_sock = -1; + } + + this->m_lock.Enter(); + + this->m_sock = WowConnection::CreateSocket(); + + if (this->m_sock < 0) { + this->SetState(WOWC_ERROR); + this->m_lock.Leave(); + + return; + } + +#if defined(WHOA_SYSTEM_MAC) + fcntl(this->m_sock, F_SETFL, O_NONBLOCK); + + uint32_t opt = 1; + setsockopt(this->m_sock, SOL_SOCKET, 4130, &opt, sizeof(opt)); + + sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(this->m_connectPort); + addr.sin_addr.s_addr = this->m_connectAddress; + + if (!this->m_netlink.IsLinked()) { + WowConnection::s_network->Add(this); + } + + this->SetState(WOWC_CONNECTING); + + if (connect(this->m_sock, reinterpret_cast(&addr), sizeof(addr)) >= 0) { + this->m_lock.Leave(); + + return; + } + + if (errno == EAGAIN || errno == EINTR || errno == EINPROGRESS) { + this->m_lock.Leave(); + + return; + } + + WowConnection::s_network->Remove(this); + this->CloseSocket(this->m_sock); + this->m_sock = -1; + + this->SetState(WOWC_ERROR); + + this->m_lock.Leave(); +#endif +} diff --git a/src/net/connection/WowConnection.hpp b/src/net/connection/WowConnection.hpp new file mode 100644 index 0000000..67d1fa1 --- /dev/null +++ b/src/net/connection/WowConnection.hpp @@ -0,0 +1,87 @@ +#ifndef NET_CONNECTION_WOW_CONNECTION_HPP +#define NET_CONNECTION_WOW_CONNECTION_HPP + +#include "net/Types.hpp" +#include +#include +#include +#include + +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) +#include +#endif + +#if defined(WHOA_SYSTEM_WIN) +#include +#endif + +class WowConnectionNet; +class WowConnectionResponse; + +class WowConnection { + public: + // Types + struct SENDNODE : public TSLinkedNode { + uint8_t* data; + uint32_t size; + uint32_t offset; + uint32_t datasize; + }; + + // Static variables + static uint64_t s_countTotalBytes; + static int32_t s_destroyed; + static WowConnectionNet* s_network; + static ATOMIC32 s_numWowConnections; + static bool (*s_verifyAddr)(const NETADDR*); + + // Static functions + static int32_t CreateSocket(); + static int32_t InitOsNet(bool (*fcn)(const NETADDR*), void (*threadinit)(), int32_t numThreads, bool useEngine); + + // Member variables + ATOMIC32 m_refCount; + int32_t m_sock; + WOW_CONN_STATE m_connState; + WowConnectionResponse* m_response; + uint32_t m_connectAddress; + uint16_t m_connectPort; + NETCONNADDR m_peer; + SCritSect m_responseLock; + int32_t m_responseRef; + uintptr_t m_responseRefThread; + STORM_LIST(SENDNODE) m_sendList; + uint32_t m_serviceFlags; + TSLink m_netlink; + SCritSect m_lock; + ATOMIC32 m_serviceCount; + WOWC_TYPE m_type; + + // Member functions + WowConnection(WowConnectionResponse* response, void (*func)(void)); + WowConnection(int32_t sock, sockaddr_in* addr, WowConnectionResponse* response); + void AcquireResponseRef(); + void AddRef(); + void CheckAccept(); + void CheckConnect(); + void CloseSocket(int32_t sock); + bool Connect(char const* address, int32_t retryMs); + bool Connect(char const* address, uint16_t port, int32_t retryMs); + void Disconnect(); + void DoDisconnect(); + void DoExceptions(); + void DoMessageReads(); + void DoReads(); + void DoStreamReads(); + void DoWrites(); + WOW_CONN_STATE GetState(); + void Init(WowConnectionResponse* response, void (*func)(void)); + void Release(); + void ReleaseResponseRef(); + WC_SEND_RESULT SendRaw(uint8_t* data, int32_t len, bool a4); + void SetState(WOW_CONN_STATE state); + void SetType(WOWC_TYPE type); + void StartConnect(); +}; + +#endif diff --git a/src/net/connection/WowConnectionNet.cpp b/src/net/connection/WowConnectionNet.cpp new file mode 100644 index 0000000..af44643 --- /dev/null +++ b/src/net/connection/WowConnectionNet.cpp @@ -0,0 +1,166 @@ +#include "net/connection/WowConnectionNet.hpp" +#include "net/connection/WowConnection.hpp" +#include +#include +#include +#include + +uint32_t MainProc(void* param) { + auto network = static_cast(param); + network->Run(); + + return 0; +} + +uint32_t WorkerProc(void* param) { + auto worker = static_cast(param); + worker->owner->RunWorker(worker->id); + + return 0; +} + +void WowConnectionNet::Add(WowConnection* connection) { + this->m_connectionsLock.Enter(); + + if (!this->m_connections.IsLinked(connection)) { + this->m_connections.LinkToTail(connection); + this->PlatformAdd(connection); + } + + this->m_connectionsLock.Leave(); +} + +void WowConnectionNet::Delete(WowConnection* connection) { + // TODO +} + +void WowConnectionNet::Remove(WowConnection* connection) { + // TODO +} + +void WowConnectionNet::Run() { + this->PlatformRun(); + this->m_stopEvent.Set(); +} + +void WowConnectionNet::RunWorker(int32_t id) { + if (this->m_threadinit) { + this->m_threadinit(); + } + + this->m_workerSem.Signal(1); + + auto& worker = this->m_workers[id]; + + while (true) { + do { + // TODO worker.time40 = OsGetAsyncTimeMsPrecise(); + } while (worker.event.Wait(1000)); + + if (worker.quit) { + break; + } + + auto serviceConn = worker.serviceConn; + auto serviceFlags = serviceConn->m_serviceFlags; + serviceConn->m_serviceFlags = 0; + + this->Service(worker.serviceConn, serviceFlags); + + worker.lock.Enter(); + + SInterlockedDecrement(&serviceConn->m_serviceCount); + serviceConn->Release(); + worker.serviceConn = nullptr; + + worker.lock.Leave(); + + this->m_workerSem.Signal(1); + this->PlatformWorkerReady(); + } +} + +void WowConnectionNet::Service(WowConnection* connection, uint32_t serviceFlags) { + while (serviceFlags) { + if (serviceFlags & 0x1) { + connection->DoWrites(); + } + + if (serviceFlags & 0x2) { + connection->DoReads(); + } + + if (serviceFlags & 0x4) { + connection->DoExceptions(); + } + + if (serviceFlags & 0x8) { + connection->DoDisconnect(); + } + + this->m_connectionsLock.Enter(); + + serviceFlags = connection->m_serviceFlags; + connection->m_serviceFlags = 0; + + this->m_connectionsLock.Leave(); + } +} + +void WowConnectionNet::SignalWorker(WowConnection* connection, uint32_t flags) { + if (!this->m_workerSem.Wait(500)) { + connection->AddRef(); + connection->m_serviceFlags = flags; + SInterlockedIncrement(&connection->m_serviceCount); + + int32_t i = 0; + while (1) { + this->m_workers[i].lock.Enter(); + + if (!this->m_workers[i].serviceConn) { + break; + } + + STORM_ASSERT(this->m_workers[i].serviceConn != connection); + + this->m_workers[i].lock.Leave(); + + i++; + + STORM_ASSERT(i < this->m_numWorkers); + } + + this->m_workers[i].serviceConn = connection; + this->m_workers[i].lock.Leave(); + this->m_workers[i].event.Set(); + } +} + +void WowConnectionNet::Start() { + for (int32_t i = 0; i < this->m_numWorkers; i++) { + auto worker = &this->m_workers[i]; + + worker->id = i; + worker->serviceConn = nullptr; + worker->quit = 0; + worker->owner = this; + // TODO worker.time40 = OsGetAsyncTimeMsPrecise(); + + char name[32]; + SStrPrintf(name, sizeof(name), "NetThread %d", i); + + SThread::Create(&WorkerProc, worker, worker->thread, name, 0); + } + + char name[32]; + SStrPrintf(name, sizeof(name), "Network"); + + // TODO BYTE1(this->dword8EC) = 0; + + SThread::Create(&MainProc, this, this->m_thread, name, 0); + + // TODO + // while (!BYTE1(this->dword8EC)) { + // OsSleep(100); + // } +} diff --git a/src/net/connection/WowConnectionNet.hpp b/src/net/connection/WowConnectionNet.hpp new file mode 100644 index 0000000..6da04bc --- /dev/null +++ b/src/net/connection/WowConnectionNet.hpp @@ -0,0 +1,55 @@ +#ifndef NET_CONNECTION_WOW_CONNECTION_NET_HPP +#define NET_CONNECTION_WOW_CONNECTION_NET_HPP + +#include "net/connection/WowConnection.hpp" +#include +#include +#include + +class WowConnectionNet { + public: + // Types + struct Worker { + WowConnectionNet* owner; + SThread thread; + int32_t id; + WowConnection* serviceConn; + SEvent event = SEvent(0, 0); + int8_t quit; + SCritSect lock; + }; + + // Member variables + SThread m_thread; + SEvent m_stopEvent = SEvent(1, 0); + uint8_t m_stop; + int32_t m_numWorkers; + Worker m_workers[32]; + SCritSect m_connectionsLock; + STORM_EXPLICIT_LIST(WowConnection, m_netlink) m_connections; + SSemaphore m_workerSem; + void (*m_threadinit)(); + + // Member functions + WowConnectionNet(uint32_t numThreads, void (*threadinit)()) + : m_workerSem(0, numThreads) + , m_numWorkers(numThreads) + , m_threadinit(threadinit) + , m_stop(0) + {}; + void Add(WowConnection* connection); + void Delete(WowConnection* connection); + void PlatformAdd(WowConnection* connection); + void PlatformChangeState(WowConnection* connection, WOW_CONN_STATE state); + void PlatformInit(bool useEngine); + void PlatformRun(); + void PlatformWorkerReady(); + void Remove(WowConnection* connection); + void Run(); + void RunWorker(int32_t id); + void Service(WowConnection* connection, uint32_t serviceFlags); + void SignalWorker(WowConnection* connection, uint32_t flags); + void Start(); +}; + +#endif diff --git a/src/net/connection/WowConnectionNetMac.cpp b/src/net/connection/WowConnectionNetMac.cpp new file mode 100644 index 0000000..13de64d --- /dev/null +++ b/src/net/connection/WowConnectionNetMac.cpp @@ -0,0 +1,175 @@ +#if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) + +#include "net/connection/WowConnectionNet.hpp" +#include "net/connection/WowConnection.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int32_t s_workerPipe[2]; + +void WowConnectionNet::PlatformAdd(WowConnection* connection) { + uint32_t on = 1; + setsockopt(connection->m_sock, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on)); + + char buf = '\1'; + write(s_workerPipe[1], &buf, sizeof(buf)); +} + +void WowConnectionNet::PlatformChangeState(WowConnection* connection, WOW_CONN_STATE state) { + char buf = '\1'; + write(s_workerPipe[1], &buf, sizeof(buf)); +} + +void WowConnectionNet::PlatformInit(bool useEngine) { + // TODO +} + +void WowConnectionNet::PlatformRun() { + pipe(s_workerPipe); + + if (fcntl(s_workerPipe[0], F_SETFL, O_NONBLOCK) < 0) { + perror("fcntl(worker pipe)"); + } + + TSGrowableArray connections; + char buf[25]; + + while (!this->m_stop) { + timeval timeout = { 30, 0 }; + + fd_set readFds = {}; + fd_set writeFds = {}; + fd_set errorFds = {}; + + readFds.fds_bits[s_workerPipe[0] >> 5] |= 1 << (s_workerPipe[0] & 0x1F); + + auto fdCount = s_workerPipe[0]; + + int32_t v39 = 0; + int32_t v41 = 0; + + this->m_connectionsLock.Enter(); + + for (auto connection = this->m_connections.Head(); connection; connection = this->m_connections.Link(connection)->Next()) { + if (connection->m_serviceCount) { + continue; + } + + switch (connection->m_connState) { + case WOWC_CONNECTING: { + errorFds.fds_bits[connection->m_sock >> 5] |= 1 << (connection->m_sock & 0x1F); + writeFds.fds_bits[connection->m_sock >> 5] |= 1 << (connection->m_sock & 0x1F); + + connections.Add(1, &connection); + connection->AddRef(); + fdCount = std::max(fdCount, connection->m_sock); + + break; + } + + case WOWC_LISTENING: { + readFds.fds_bits[connection->m_sock >> 5] |= 1 << (connection->m_sock & 0x1F); + + connections.Add(1, &connection); + connection->AddRef(); + fdCount = std::max(fdCount, connection->m_sock); + + break; + } + + case WOWC_CONNECTED: { + readFds.fds_bits[connection->m_sock >> 5] |= 1 << (connection->m_sock & 0x1F); + errorFds.fds_bits[connection->m_sock >> 5] |= 1 << (connection->m_sock & 0x1F); + + // TODO + + connections.Add(1, &connection); + connection->AddRef(); + fdCount = std::max(fdCount, connection->m_sock); + } + + case WOWC_DISCONNECTING: { + // TODO + + v41++; + connections.Add(1, &connection); + connection->AddRef(); + + break; + } + + default: { + break; + } + } + } + + this->m_connectionsLock.Leave(); + + if (v41 > 0) { + timeout = { 0, 0 }; + } + + if (connections.Count() > 0) { + // TODO + } + + select(fdCount + 1, &readFds, &writeFds, &errorFds, &timeout); + + auto v1 = s_workerPipe[0]; + if (((1 << (s_workerPipe[0] & 0x1F)) & readFds.fds_bits[s_workerPipe[0] >> 5]) != 0) { + while (read(v1, buf, 1u) > 0) { + v1 = s_workerPipe[0]; + } + } + + for (int32_t i = 0; i < connections.Count(); i++) { + auto connection = connections[i]; + uint32_t signalFlags = 0x0; + + if (!(connection->m_sock & 0x80000000)) { + if (FD_ISSET(connection->m_sock, &writeFds)) { + signalFlags |= 0x1; + } + + if (FD_ISSET(connection->m_sock, &readFds)) { + signalFlags |= 0x2; + } + + if (FD_ISSET(connection->m_sock, &errorFds)) { + signalFlags |= 0x4; + } + } + + if (connection->m_connState == WOWC_DISCONNECTING) { + signalFlags |= 0x8; + } + + // TODO + // auto v15 = connection->dword10C; + // if (!(v15 & 1) && v15) { + // signalFlags |= 0x2; + // } + + if (signalFlags) { + this->SignalWorker(connection, signalFlags); + } + + connection->Release(); + } + } +} + +void WowConnectionNet::PlatformWorkerReady() { + char buf = '\1'; + write(s_workerPipe[1], &buf, sizeof(buf)); +} + +#endif diff --git a/src/net/connection/WowConnectionNetWin.cpp b/src/net/connection/WowConnectionNetWin.cpp new file mode 100644 index 0000000..f5e2d55 --- /dev/null +++ b/src/net/connection/WowConnectionNetWin.cpp @@ -0,0 +1,25 @@ +#if defined(WHOA_SYSTEM_WIN) + +#include "net/connection/WowConnectionNet.hpp" + +void WowConnectionNet::PlatformAdd(WowConnection* connection) { + // TODO +} + +void WowConnectionNet::PlatformChangeState(WowConnection* connection, WOW_CONN_STATE state) { + // TODO +} + +void WowConnectionNet::PlatformInit(bool useEngine) { + // TODO +} + +void WowConnectionNet::PlatformRun() { + // TODO +} + +void WowConnectionNet::PlatformWorkerReady() { + // TODO +} + +#endif diff --git a/src/net/connection/WowConnectionResponse.hpp b/src/net/connection/WowConnectionResponse.hpp new file mode 100644 index 0000000..322886f --- /dev/null +++ b/src/net/connection/WowConnectionResponse.hpp @@ -0,0 +1,17 @@ +#ifndef NET_CONNECTION_WOW_CONNECTION_RESPONSE_HPP +#define NET_CONNECTION_WOW_CONNECTION_RESPONSE_HPP + +#include "net/Types.hpp" + +class WowConnection; + +class WowConnectionResponse { + public: + // Virtual member functions + virtual void WCConnected(WowConnection* conn, WowConnection* inbound, uint32_t timeStamp, const NETCONNADDR* addr) = 0; + virtual void WCCantConnect(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr) = 0; + virtual void WCDisconnected(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr) {}; + virtual void WCDataReady(WowConnection* conn, uint32_t timeStamp, uint8_t* data, int32_t len) {}; +}; + +#endif diff --git a/src/net/grunt/ClientLink.cpp b/src/net/grunt/ClientLink.cpp new file mode 100644 index 0000000..c4417c1 --- /dev/null +++ b/src/net/grunt/ClientLink.cpp @@ -0,0 +1,165 @@ +#include "net/grunt/ClientLink.hpp" +#include "net/connection/WowConnection.hpp" +#include "net/grunt/ClientResponse.hpp" +#include +#include +#include +#include + +Grunt::ClientLink::ClientLink(Grunt::ClientResponse& clientResponse) { + // TODO + + this->m_clientResponse = &clientResponse; + + this->SetState(0); + + if (this->m_timer.m_thread.Valid()) { + this->m_interval = 100; + this->m_timer.Insert(*this); + } +} + +void Grunt::ClientLink::Call() { + // TODO + // this->CheckExpired(false); + + this->m_critSect.Enter(); + + if (this->m_state == 2) { + this->m_clientResponse->GetLogonMethod(); + } else if (this->m_state == 6 && !this->m_clientResponse->OnlineIdle()) { + this->Disconnect(); + } + + this->m_critSect.Leave(); +} + +void Grunt::ClientLink::Connect(const char* a2) { + if (this->m_state) { + return; + } + + this->SetState(1); + + auto connectionMem = SMemAlloc(sizeof(WowConnection), __FILE__, __LINE__, 0x0); + auto connection = new (connectionMem) WowConnection(this, nullptr); + + this->m_connection = connection; + this->m_connection->SetType(WOWC_TYPE_STREAM); + + auto port = SStrChr(a2, ':'); + if (port) { + this->m_connection->Connect(a2, 5000); + } else { + this->m_connection->Connect(a2, 3724, 5000); + } +} + +void Grunt::ClientLink::Disconnect() { + this->m_critSect.Enter(); + + if (this->m_connection) { + this->m_connection->Disconnect(); + } + + this->m_critSect.Leave(); +} + +void Grunt::ClientLink::LogonNewSession(const Grunt::ClientLink::Logon& logon) { + this->SetState(3); + + SStrCopy(this->m_accountName, logon.accountName, sizeof(this->m_accountName)); + SStrUpper(this->m_accountName); + + char* password = static_cast(alloca(SStrLen(logon.password) + 1)); + SStrCopy(password, logon.password, STORM_MAX_STR); + SStrUpper(password); + + static char accountNameUnDecorated[1280]; + SStrCopy(accountNameUnDecorated, this->m_accountName, STORM_MAX_STR); + auto decoration = const_cast(SStrChr(accountNameUnDecorated, '#')); + if (decoration) { + *decoration = '\0'; + } + + // TODO SRP6_Client::BeginAuthentication + + CDataStoreCache<1024> clientChallenge; + + uint8_t opcode = 0; + clientChallenge.Put(opcode); + + uint8_t protocol = 8; + clientChallenge.Put(protocol); + + this->PackLogon(clientChallenge, logon); + clientChallenge.Finalize(); + + this->Send(clientChallenge); +} + +void Grunt::ClientLink::PackLogon(CDataStore& msg, const Logon& logon) { + uint32_t startPos = msg.m_size; + uint16_t tmpSize = 0; + msg.Put(tmpSize); + + msg.Put(logon.programID); + msg.Put(logon.version[0]); + msg.Put(logon.version[1]); + msg.Put(logon.version[2]); + msg.Put(logon.build); + msg.Put(logon.processorID); + msg.Put(logon.osID); + msg.Put(logon.locale); + msg.Put(logon.tz); + + msg.Put(this->m_clientIP); + + uint32_t accountNameLen = SStrLen(this->m_accountName); + msg.Put(accountNameLen); + msg.PutData(this->m_accountName, accountNameLen); + + msg.Set(startPos, msg.m_size - startPos - 2); +} + +void Grunt::ClientLink::Send(CDataStore& msg) { + this->m_critSect.Enter(); + + if (this->m_connection) { + void* data; + msg.GetDataInSitu(data, msg.m_size); + + this->m_connection->SendRaw(static_cast(data), msg.m_size, false); + } + + this->m_critSect.Leave(); +} + +void Grunt::ClientLink::SetState(int32_t state) { + this->m_critSect.Enter(); + + this->m_state = state; + + this->m_critSect.Leave(); +} + +void Grunt::ClientLink::WCCantConnect(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr) { + // TODO +} + +void Grunt::ClientLink::WCConnected(WowConnection* conn, WowConnection* inbound, uint32_t timeStamp, const NETCONNADDR* addr) { + this->m_critSect.Enter(); + + this->SetState(2); + + int32_t connected = this->m_clientResponse->Connected(addr->peerAddr); + + // TODO + // this->m_clientIP = OsNetAddrGetAddress(&addr->selfAddr, 0); + + this->m_critSect.Leave(); + + if (!connected) { + this->Disconnect(); + } +} diff --git a/src/net/grunt/ClientLink.hpp b/src/net/grunt/ClientLink.hpp new file mode 100644 index 0000000..206eada --- /dev/null +++ b/src/net/grunt/ClientLink.hpp @@ -0,0 +1,54 @@ +#ifndef NET_GRUNT_CLIENT_LINK_HPP +#define NET_GRUNT_CLIENT_LINK_HPP + +#include "net/grunt/Grunt.hpp" +#include "net/connection/WowConnectionResponse.hpp" +#include "net/grunt/Pending.hpp" +#include "net/grunt/Timer.hpp" +#include "net/Types.hpp" +#include + +class CDataStore; +class WowConnection; + +class Grunt::ClientLink : public WowConnectionResponse, Grunt::Pending, Grunt::Timer::Event { + public: + // Types + struct Logon { + const char* accountName; + const char* password; + uint32_t programID; + uint32_t processorID; + uint32_t osID; + uint8_t version[4]; + uint16_t build; + uint16_t uint1A; + uint32_t locale; + uint32_t tz; + }; + + // Member variables + Grunt::Timer m_timer; + uint32_t m_clientIP = 0; + int32_t m_state; + SCritSect m_critSect; + WowConnection* m_connection = nullptr; + ClientResponse* m_clientResponse; + char m_accountName[1280]; + + // Virtual member functions + virtual void WCConnected(WowConnection* conn, WowConnection* inbound, uint32_t timeStamp, const NETCONNADDR* addr); + virtual void WCCantConnect(WowConnection* conn, uint32_t timeStamp, NETCONNADDR* addr); + virtual void Call(); + + // Member functions + ClientLink(Grunt::ClientResponse& clientResponse); + void Connect(const char* a2); + void Disconnect(); + void LogonNewSession(const Logon& logon); + void PackLogon(CDataStore& msg, const Logon& logon); + void Send(CDataStore& msg); + void SetState(int32_t state); +}; + +#endif diff --git a/src/net/grunt/ClientResponse.hpp b/src/net/grunt/ClientResponse.hpp new file mode 100644 index 0000000..82956c6 --- /dev/null +++ b/src/net/grunt/ClientResponse.hpp @@ -0,0 +1,20 @@ +#ifndef NET_GRUNT_CLIENT_RESPONSE_HPP +#define NET_GRUNT_CLIENT_RESPONSE_HPP + +#include "net/grunt/Grunt.hpp" +#include "net/Types.hpp" + +class LoginResponse; + +class Grunt::ClientResponse { + public: + virtual bool Connected(const NETADDR& addr) = 0; + virtual bool OnlineIdle() = 0; + virtual void GetLogonMethod() = 0; + virtual void GetRealmList() = 0; + virtual void Logon(const char* a2, const char* a3) = 0; + virtual void Logoff() = 0; + virtual void Init(LoginResponse* loginResponse) = 0; +}; + +#endif diff --git a/src/net/grunt/Grunt.hpp b/src/net/grunt/Grunt.hpp new file mode 100644 index 0000000..f78b013 --- /dev/null +++ b/src/net/grunt/Grunt.hpp @@ -0,0 +1,11 @@ +#ifndef NET_GRUNT_GRUNT_HPP +#define NET_GRUNT_GRUNT_HPP + +namespace Grunt { + class ClientLink; + class ClientResponse; + class Pending; + class Timer; +} + +#endif diff --git a/src/net/grunt/Pending.hpp b/src/net/grunt/Pending.hpp new file mode 100644 index 0000000..96882e8 --- /dev/null +++ b/src/net/grunt/Pending.hpp @@ -0,0 +1,10 @@ +#ifndef NET_GRUNT_PENDING_HPP +#define NET_GRUNT_PENDING_HPP + +#include "net/Grunt.hpp" + +class Grunt::Pending { + public: +}; + +#endif diff --git a/src/net/grunt/Timer.cpp b/src/net/grunt/Timer.cpp new file mode 100644 index 0000000..7b1ffcf --- /dev/null +++ b/src/net/grunt/Timer.cpp @@ -0,0 +1,70 @@ +#include "net/grunt/Timer.hpp" +#include + +uint32_t Grunt::Timer::ThreadProc(void* param) { + auto timer = static_cast(param); + + while (true) { + auto timeout = timer->Pump(); + + if (timer->m_event.Wait(timeout) == 0) { + break; + } + } + + return 1; +} + +Grunt::Timer::Timer() { + SThread::Create(Grunt::Timer::ThreadProc, this, this->m_thread, "GruntTimerEvt", 0); +} + +void Grunt::Timer::Insert(Grunt::Timer::Event& newEvent) { + this->m_critSect.Enter(); + + newEvent.m_schedTime = OsGetAsyncTimeMsPrecise() + newEvent.m_interval; + + for (auto event = this->m_eventList.Head(); event; event = this->m_eventList.Link(event)->Next()) { + // Keep event listed sorted by scheduled time + if (newEvent.m_schedTime - event->m_schedTime < 0) { + this->m_eventList.LinkNode(&newEvent, 2, event); + + this->m_critSect.Leave(); + + return; + } + } + + this->m_eventList.LinkToTail(&newEvent); + + this->m_critSect.Leave(); +} + +uint32_t Grunt::Timer::Pump() { + this->m_critSect.Enter(); + + auto* event = this->m_eventList.Head(); + + if (!event) { + this->m_critSect.Leave(); + + return 100; + } + + int32_t timeUntilSched = event->m_schedTime - OsGetAsyncTimeMsPrecise(); + + if (timeUntilSched <= 0) { + this->m_eventList.UnlinkNode(event); + + this->m_critSect.Leave(); + + event->Call(); + this->Insert(*event); + + return 100; + } + + this->m_critSect.Leave(); + + return timeUntilSched; +} diff --git a/src/net/grunt/Timer.hpp b/src/net/grunt/Timer.hpp new file mode 100644 index 0000000..9a44e8d --- /dev/null +++ b/src/net/grunt/Timer.hpp @@ -0,0 +1,37 @@ +#ifndef NET_GRUNT_TIMER_HPP +#define NET_GRUNT_TIMER_HPP + +#include "net/grunt/Grunt.hpp" +#include "storm/List.hpp" +#include "storm/Thread.hpp" + +class Grunt::Timer { + public: + // Types + class Event { + public: + // Member variables + TSLink m_link; + uint32_t m_schedTime; + uint32_t m_interval; + + // Virtual methods + virtual void Call() = 0; + }; + + // Static functions + static uint32_t ThreadProc(void* param); + + // Member variables + SEvent m_event = SEvent(0, 0); + SThread m_thread; + SCritSect m_critSect; + STORM_EXPLICIT_LIST(Event, m_link) m_eventList; + + // Member functions + Timer(); + void Insert(Event&); + uint32_t Pump(); +}; + +#endif diff --git a/src/net/login/BattlenetLogin.hpp b/src/net/login/BattlenetLogin.hpp new file mode 100644 index 0000000..edaf744 --- /dev/null +++ b/src/net/login/BattlenetLogin.hpp @@ -0,0 +1,10 @@ +#ifndef NET_LOGIN_BATTLENET_LOGIN_HPP +#define NET_LOGIN_BATTLENET_LOGIN_HPP + +#include "net/login/Login.hpp" + +class BattlenetLogin : public Login { + public: +}; + +#endif diff --git a/src/net/login/GruntLogin.cpp b/src/net/login/GruntLogin.cpp new file mode 100644 index 0000000..7446131 --- /dev/null +++ b/src/net/login/GruntLogin.cpp @@ -0,0 +1,126 @@ +#include "net/login/GruntLogin.hpp" +#include "net/grunt/ClientLink.hpp" +#include "net/login/LoginResponse.hpp" +#include +#include +#include +#include + +GruntLogin::~GruntLogin() { + // TODO +} + +bool GruntLogin::Connected(const NETADDR& addr) { + this->m_loggedOn = true; + + this->m_loginResponse->m_loginState = LOGIN_STATE_15; + this->m_loginResponse->m_loginResult = LOGIN_OK; + + char addrStr[32]; + // TODO + // OsNetAddrToStr(addr, addrStr, sizeof(addrStr)); + + char stateStr[64]; + // TODO + // SStrCopy(stateStr, g_LoginStateStringNames[LOGIN_STATE_15], sizeof(stateStr)); + + char resultStr[64]; + // TODO + // SStrCopy(resultStr, g_LoginResultStringNames[LOGIN_OK], sizeof(resultStr)); + + // TODO + this->m_loginResponse->LoginServerStatus(LOGIN_STATE_15, LOGIN_OK, addrStr, stateStr, resultStr, 0); + + return true; +} + +void GruntLogin::GetLogonMethod() { + Grunt::ClientLink::Logon logon; + + logon.accountName = nullptr; + logon.password = nullptr; + + // TODO + + // TODO Pull build info into something common + logon.version[0] = 3; + logon.version[1] = 3; + logon.version[2] = 5; + logon.build = 12340; + + // TODO + + if (this->IsReconnect()) { + // TODO + } else if (this->m_password) { + this->m_loginResponse->m_loginState = LOGIN_STATE_AUTHENTICATING; + this->m_loginResponse->m_loginResult = LOGIN_OK; + + char stateStr[64]; + // TODO + // SStrCopy(stateStr, g_LoginStateStringNames[LOGIN_STATE_AUTHENTICATING], sizeof(stateStr)); + + char resultStr[64]; + // TODO + // SStrCopy(resultStr, g_LoginResultStringNames[LOGIN_OK], sizeof(resultStr)); + + this->m_loginResponse->LoginServerStatus( + LOGIN_STATE_AUTHENTICATING, + LOGIN_OK, + nullptr, + stateStr, + resultStr, + 0 + ); + + logon.password = this->m_password; + logon.accountName = this->m_accountName; + this->m_clientLink->LogonNewSession(logon); + + auto passwordLen = SStrLen(this->m_password); + memset(this->m_password, 0, passwordLen); + SMemFree(this->m_password, __FILE__, __LINE__, 0); + this->m_password = nullptr; + } +} + +void GruntLogin::GetRealmList() { + // TODO +} + +void GruntLogin::Init(LoginResponse* loginResponse) { + this->m_loginResponse = loginResponse; + + auto clientLinkMem = SMemAlloc(sizeof(Grunt::ClientLink), __FILE__, __LINE__, 0x0); + auto clientLink = new (clientLinkMem) Grunt::ClientLink(*this); + this->m_clientLink = clientLink; +} + +void GruntLogin::Logoff() { + // TODO +} + +void GruntLogin::Logon(const char* a2, const char* a3) { + if (this->m_loggedOn) { + return; + } + + this->m_reconnect = false; + + // TODO + + this->m_loginResponse->m_loginState = LOGIN_STATE_CONNECTING; + this->m_loginResponse->m_loginResult = LOGIN_OK; + + // TODO + // char v6[64], v7[64]; + // SStrCopy(v6, g_LoginStateStringNames[1], sizeof(v6)); + // SStrCopy(v7, g_LoginResultStringNames[0], sizeof(v7)); + // this->m_loginResponse->Vfunc6(1, 0, 0, v6, v7, 0); + + if (!a2) { + a2 = "us.logon.worldofwarcraft.com:3724"; + } + + this->m_clientLink->Connect(a2); +} diff --git a/src/net/login/GruntLogin.hpp b/src/net/login/GruntLogin.hpp new file mode 100644 index 0000000..1d80904 --- /dev/null +++ b/src/net/login/GruntLogin.hpp @@ -0,0 +1,23 @@ +#ifndef NET_LOGIN_GRUNT_LOGIN_HPP +#define NET_LOGIN_GRUNT_LOGIN_HPP + +#include "net/grunt/Grunt.hpp" +#include "net/grunt/ClientResponse.hpp" +#include "net/login/Login.hpp" + +class GruntLogin : public Login { + public: + // Member variables + Grunt::ClientLink* m_clientLink = nullptr; + + // Virtual member functions + virtual ~GruntLogin(); + virtual bool Connected(const NETADDR& addr); + virtual void GetLogonMethod(); + virtual void GetRealmList(); + virtual void Logon(const char* a2, const char* a3); + virtual void Logoff(); + virtual void Init(LoginResponse* loginResponse); +}; + +#endif diff --git a/src/net/login/Login.cpp b/src/net/login/Login.cpp new file mode 100644 index 0000000..49b5197 --- /dev/null +++ b/src/net/login/Login.cpp @@ -0,0 +1,29 @@ +#include "net/login/Login.hpp" +#include +#include +#include + +Login::~Login() { + // TODO +} + +bool Login::IsReconnect() { + return this->m_reconnect; +} + +bool Login::OnlineIdle() { + // TODO + + return true; +} + +void Login::SetLogonCreds(const char* accountName, const char* password) { + SStrCopy(this->m_accountName, accountName, 1280); + + if (this->m_password) { + memset(this->m_password, 0, SStrLen(this->m_password)); + SMemFree(this->m_password, __FILE__, __LINE__, 0x0); + } + + this->m_password = SStrDupA(password, __FILE__, __LINE__); +} diff --git a/src/net/login/Login.hpp b/src/net/login/Login.hpp new file mode 100644 index 0000000..6ba2265 --- /dev/null +++ b/src/net/login/Login.hpp @@ -0,0 +1,27 @@ +#ifndef NET_LOGIN_LOGIN_HPP +#define NET_LOGIN_LOGIN_HPP + +#include "net/grunt/ClientResponse.hpp" + +class LoginResponse; + +class Login : public Grunt::ClientResponse { + public: + // Member variables + bool m_loggedOn = false; + bool m_reconnect = false; + char m_accountName[1280] = {}; + char m_rawAccountName[1280] = {}; + char* m_password = nullptr; + LoginResponse* m_loginResponse = nullptr; + + // Virtual member functions + virtual ~Login(); + virtual bool OnlineIdle(); + virtual bool IsReconnect(); + + // Member functions + void SetLogonCreds(const char* accountName, const char* password); +}; + +#endif diff --git a/src/net/login/LoginResponse.hpp b/src/net/login/LoginResponse.hpp new file mode 100644 index 0000000..3edbcb6 --- /dev/null +++ b/src/net/login/LoginResponse.hpp @@ -0,0 +1,16 @@ +#ifndef NET_LOGIN_LOGIN_RESPONSE_HPP +#define NET_LOGIN_LOGIN_RESPONSE_HPP + +#include "net/Types.hpp" + +class LoginResponse { + public: + // Member variables + LOGIN_STATE m_loginState; + LOGIN_RESULT m_loginResult; + + // Virtual member functions + virtual void LoginServerStatus(LOGIN_STATE state, LOGIN_RESULT result, const char* addrStr, const char* stateStr, const char* resultStr, uint16_t a7) = 0; +}; + +#endif diff --git a/src/os/Compat.hpp b/src/os/Compat.hpp new file mode 100644 index 0000000..a5aeb05 --- /dev/null +++ b/src/os/Compat.hpp @@ -0,0 +1,8 @@ +#ifndef OS_COMPAT_HPP +#define OS_COMPAT_HPP + +#if defined(WHOA_SYSTEM_MAC) + #include "os/compat/Mac.hpp" +#endif + +#endif diff --git a/src/os/compat/Mac.hpp b/src/os/compat/Mac.hpp new file mode 100644 index 0000000..22d89e6 --- /dev/null +++ b/src/os/compat/Mac.hpp @@ -0,0 +1,12 @@ +#ifndef OS_COMPAT_MAC_HPP +#define OS_COMPAT_MAC_HPP + +#ifdef __OBJC__ + #include + + #if WHOA_SYSTEM_VERSION < WHOA_MACOS_10_12 + #define NSEventTypeApplicationDefined NSApplicationDefined + #endif +#endif + +#endif diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt new file mode 100644 index 0000000..6ac8bbd --- /dev/null +++ b/src/sound/CMakeLists.txt @@ -0,0 +1,16 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(sound STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(sound + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(sound + PRIVATE + ui + util +) diff --git a/src/sound/SI2.cpp b/src/sound/SI2.cpp new file mode 100644 index 0000000..7622510 --- /dev/null +++ b/src/sound/SI2.cpp @@ -0,0 +1,11 @@ +#include "sound/SI2.hpp" +#include "ui/FrameScript.hpp" + +void SI2::RegisterScriptFunctions() { + for (int32_t i = 0; i < NUM_SCRIPT_FUNCTIONS_SI2; ++i) { + FrameScript_RegisterFunction( + SI2::s_ScriptFunctions[i].name, + SI2::s_ScriptFunctions[i].method + ); + } +} diff --git a/src/sound/SI2.hpp b/src/sound/SI2.hpp new file mode 100644 index 0000000..6d1b624 --- /dev/null +++ b/src/sound/SI2.hpp @@ -0,0 +1,17 @@ +#ifndef SOUND_SI2_HPP +#define SOUND_SI2_HPP + +#include "sound/SI2Script.hpp" +#include "ui/Types.hpp" +#include + +class SI2 { + public: + // Static variables + static FrameScript_Method s_ScriptFunctions[NUM_SCRIPT_FUNCTIONS_SI2]; + + // Static functions + static void RegisterScriptFunctions(void); +}; + +#endif diff --git a/src/sound/SI2Script.cpp b/src/sound/SI2Script.cpp new file mode 100644 index 0000000..cf024aa --- /dev/null +++ b/src/sound/SI2Script.cpp @@ -0,0 +1,123 @@ +#include "sound/SI2Script.hpp" +#include "sound/SI2.hpp" +#include "ui/Types.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" + +int32_t Script_PlaySound(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_PlayMusic(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_PlaySoundFile(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_StopMusic(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_GameSystem_GetNumInputDrivers(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_GameSystem_GetInputDriverNameByIndex(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_GameSystem_GetNumOutputDrivers(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_GameSystem_GetOutputDriverNameByIndex(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_GameSystem_RestartSoundSystem(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_ChatSystem_GetNumInputDrivers(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_ChatSystem_GetInputDriverNameByIndex(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_ChatSystem_GetNumOutputDrivers(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_Sound_ChatSystem_GetOutputDriverNameByIndex(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_StartCapture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_StopCapture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_RecordLoopbackSound(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_StopRecordingLoopbackSound(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_PlayLoopbackSound(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_StopPlayingLoopbackSound(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_IsRecordingLoopbackSound(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_IsPlayingLoopbackSound(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_GetCurrentMicrophoneSignalLevel(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t Script_VoiceChat_ActivatePrimaryCaptureCallback(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SI2::s_ScriptFunctions[NUM_SCRIPT_FUNCTIONS_SI2] = { + { "PlaySound", &Script_PlaySound }, + { "PlayMusic", &Script_PlayMusic }, + { "PlaySoundFile", &Script_PlaySoundFile }, + { "StopMusic", &Script_StopMusic }, + { "Sound_GameSystem_GetNumInputDrivers", &Script_Sound_GameSystem_GetNumInputDrivers }, + { "Sound_GameSystem_GetInputDriverNameByIndex", &Script_Sound_GameSystem_GetInputDriverNameByIndex }, + { "Sound_GameSystem_GetNumOutputDrivers", &Script_Sound_GameSystem_GetNumOutputDrivers }, + { "Sound_GameSystem_GetOutputDriverNameByIndex", &Script_Sound_GameSystem_GetOutputDriverNameByIndex }, + { "Sound_GameSystem_RestartSoundSystem", &Script_Sound_GameSystem_RestartSoundSystem }, + { "Sound_ChatSystem_GetNumInputDrivers", &Script_Sound_ChatSystem_GetNumInputDrivers }, + { "Sound_ChatSystem_GetInputDriverNameByIndex", &Script_Sound_ChatSystem_GetInputDriverNameByIndex }, + { "Sound_ChatSystem_GetNumOutputDrivers", &Script_Sound_ChatSystem_GetNumOutputDrivers }, + { "Sound_ChatSystem_GetOutputDriverNameByIndex", &Script_Sound_ChatSystem_GetOutputDriverNameByIndex }, + { "VoiceChat_StartCapture", &Script_VoiceChat_StartCapture }, + { "VoiceChat_StopCapture", &Script_VoiceChat_StopCapture }, + { "VoiceChat_RecordLoopbackSound", &Script_VoiceChat_RecordLoopbackSound }, + { "VoiceChat_StopRecordingLoopbackSound", &Script_VoiceChat_StopRecordingLoopbackSound }, + { "VoiceChat_PlayLoopbackSound", &Script_VoiceChat_PlayLoopbackSound }, + { "VoiceChat_StopPlayingLoopbackSound", &Script_VoiceChat_StopPlayingLoopbackSound }, + { "VoiceChat_IsRecordingLoopbackSound", &Script_VoiceChat_IsRecordingLoopbackSound }, + { "VoiceChat_IsPlayingLoopbackSound", &Script_VoiceChat_IsPlayingLoopbackSound }, + { "VoiceChat_GetCurrentMicrophoneSignalLevel", &Script_VoiceChat_GetCurrentMicrophoneSignalLevel }, + { "VoiceChat_ActivatePrimaryCaptureCallback", &Script_VoiceChat_ActivatePrimaryCaptureCallback } +}; diff --git a/src/sound/SI2Script.hpp b/src/sound/SI2Script.hpp new file mode 100644 index 0000000..9c3b882 --- /dev/null +++ b/src/sound/SI2Script.hpp @@ -0,0 +1,6 @@ +#ifndef SOUND_SI2_SCRIPT_HPP +#define SOUND_SI2_SCRIPT_HPP + +#define NUM_SCRIPT_FUNCTIONS_SI2 23 + +#endif diff --git a/src/ui/CBackdropGenerator.cpp b/src/ui/CBackdropGenerator.cpp new file mode 100644 index 0000000..a176c9e --- /dev/null +++ b/src/ui/CBackdropGenerator.cpp @@ -0,0 +1,351 @@ +#include "ui/CBackdropGenerator.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/CSimpleTexture.hpp" +#include "ui/LoadXML.hpp" +#include "ui/Util.hpp" +#include "util/CStatus.hpp" +#include "util/StringTo.hpp" +#include +#include + +CBackdropGenerator::CBackdropGenerator() { + this->m_color.Set(1.0f, 1.0f, 1.0f, 1.0f); + this->m_borderColor.Set(1.0f, 1.0f, 1.0f, 1.0f); +} + +void CBackdropGenerator::Generate(const CRect* rect) { + STORM_ASSERT(rect); + + C2Vector texCoords[4]; + + float v3 = rect->maxX - rect->minX; + float v7 = rect->maxY - rect->minY; + + float v4 = 1.0f / this->m_cornerSize; + float v5 = v4 * v3 - 2.0f; + float v9 = v4 * v7 - 2.0f; + + float v34 = v5 < 0.0f ? 0.0f : v5; + float v17 = v9 < 0.0f ? 0.0f : v9; + + const char* background = this->m_background.GetString(); + + if (background && *background && this->m_tileBackground) { + float v13 = this->m_backgroundSize == 0.0f + ? this->m_cornerSize + : this->m_backgroundSize; + + texCoords[0] = { 0.0f, 0.0f }; + texCoords[1] = { 0.0f, v7 / v13 }; + texCoords[2] = { v3 / v13, 0.0f }; + texCoords[3] = { v3 / v13, v7 / v13 }; + + this->m_backgroundTexture->SetTexCoord(texCoords); + } + + if (this->m_pieces & 0x1) { + texCoords[0] = { 0.0078125f, 0.0625f }; + texCoords[1] = { 0.0078125f, v17 - 0.0625f }; + texCoords[2] = { 0.1171875f, 0.0625f }; + texCoords[3] = { 0.1171875f, v17 - 0.0625f }; + + this->m_leftTexture->SetTexCoord(texCoords); + } + + if (this->m_pieces & 0x2) { + texCoords[0] = { 0.1328125f, 0.0625f }; + texCoords[1] = { 0.1328125f, v17 - 0.0625f }; + texCoords[2] = { 0.2421875f, 0.0625f }; + texCoords[3] = { 0.2421875f, v17 - 0.0625f }; + + this->m_rightTexture->SetTexCoord(texCoords); + } + + if (this->m_pieces & 0x4) { + texCoords[0] = { 0.2578125f, v34 - 0.0625f }; + texCoords[1] = { 0.3671875f, v34 - 0.0625f }; + texCoords[2] = { 0.2578125f, 0.0625f }; + texCoords[3] = { 0.3671875f, 0.0625f }; + + this->m_topTexture->SetTexCoord(texCoords); + } + + if (this->m_pieces & 0x8) { + texCoords[0] = { 0.3828125f, v34 - 0.0625f }; + texCoords[1] = { 0.4921875f, v34 - 0.0625f }; + texCoords[2] = { 0.3828125f, 0.0625f }; + texCoords[3] = { 0.4921875f, 0.0625f }; + + this->m_bottomTexture->SetTexCoord(texCoords); + } + + if (this->m_backgroundTexture) { + this->m_backgroundTexture->SetVertexColor(this->m_color); + } + + this->SetBorderVertexColor(this->m_borderColor); +} + +void CBackdropGenerator::LoadXML(XMLNode* node, CStatus* status) { + const char* bgFileAttr = node->GetAttributeByName("bgFile"); + const char* edgeFileAttr = node->GetAttributeByName("edgeFile"); + const char* tileAttr = node->GetAttributeByName("tile"); + const char* alphaModeAttr = node->GetAttributeByName("alphaMode"); + + this->m_background.Copy(bgFileAttr); + this->m_border.Copy(edgeFileAttr); + + this->m_tileBackground = tileAttr && *tileAttr + ? StringToBOOL(tileAttr) + : 0; + + this->m_pieces = edgeFileAttr && *edgeFileAttr + ? 0xFF + : 0; + + this->m_blend = GxBlend_Alpha; + if (alphaModeAttr && *alphaModeAttr) { + StringToBlendMode(alphaModeAttr, this->m_blend); + } + + for (auto child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "TileSize", STORM_MAX_STR)) { + float backgroundSize; + + if (LoadXML_Value(child, backgroundSize, status)) { + this->m_backgroundSize = backgroundSize; + } + + } else if (!SStrCmpI(child->GetName(), "EdgeSize", STORM_MAX_STR)) { + float cornerSize; + + if (LoadXML_Value(child, cornerSize, status)) { + this->m_cornerSize = cornerSize; + } + + } else if (!SStrCmpI(child->GetName(), "BackgroundInsets", STORM_MAX_STR)) { + float leftInset, rightInset, topInset, bottomInset; + + if (LoadXML_Insets(child, leftInset, rightInset, topInset, bottomInset, status)) { + this->m_leftInset = leftInset; + this->m_rightInset = rightInset; + this->m_topInset = topInset; + this->m_bottomInset = bottomInset; + } + + } else if (!SStrCmpI(child->GetName(), "Color", STORM_MAX_STR)) { + // TODO + + } else if (!SStrCmpI(child->GetName(), "BorderColor", STORM_MAX_STR)) { + // TODO + + } else { + status->Add( + STATUS_WARNING, + "Unknown child node in %s element: %s", + node->GetName(), + child->GetName() + ); + } + } +} + +void CBackdropGenerator::SetBorderVertexColor(const CImVector& borderColor) { + // TODO +} + +void CBackdropGenerator::SetOutput(CSimpleFrame* frame) { + STORM_ASSERT(frame); + + C2Vector texCoords[4]; + + const char* background = this->m_background.GetString(); + const char* border = this->m_border.GetString(); + + if (background && *background) { + // TODO + // CSimpleTexture* backgroundTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* backgroundTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND, 1); + + this->m_backgroundTexture = backgroundTexture; + + backgroundTexture->SetPoint(FRAMEPOINT_TOPLEFT, frame, FRAMEPOINT_TOPLEFT, this->m_leftInset, -this->m_topInset, 0); + backgroundTexture->SetPoint(FRAMEPOINT_TOPRIGHT, frame, FRAMEPOINT_TOPRIGHT, -this->m_rightInset, -this->m_topInset, 0); + backgroundTexture->SetPoint(FRAMEPOINT_BOTTOMLEFT, frame, FRAMEPOINT_BOTTOMLEFT, this->m_leftInset, this->m_bottomInset, 0); + backgroundTexture->SetPoint(FRAMEPOINT_BOTTOMRIGHT, frame, FRAMEPOINT_BOTTOMRIGHT, -this->m_rightInset, this->m_topInset, 0); + + backgroundTexture->Resize(0); + + backgroundTexture->SetTexture(background, this->m_tileBackground, 0, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + } + + if (this->m_pieces & 0x1) { + // TODO + // CSimpleTexture* leftTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* leftTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_leftTexture = leftTexture; + + leftTexture->SetWidth(this->m_cornerSize); + leftTexture->SetPoint(FRAMEPOINT_TOPLEFT, frame, FRAMEPOINT_TOPLEFT, 0.0f, -this->m_cornerSize, 0); + leftTexture->SetPoint(FRAMEPOINT_BOTTOMLEFT, frame, FRAMEPOINT_BOTTOMLEFT, 0.0f, this->m_cornerSize, 0); + + leftTexture->Resize(0); + + leftTexture->SetTexture(border, 1, 1, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + leftTexture->SetBlendMode(this->m_blend); + } + + if (this->m_pieces & 0x2) { + // TODO + // CSimpleTexture* rightTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* rightTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_rightTexture = rightTexture; + + rightTexture->SetWidth(this->m_cornerSize); + rightTexture->SetPoint(FRAMEPOINT_TOPRIGHT, frame, FRAMEPOINT_TOPRIGHT, 0.0f, -this->m_cornerSize, 0); + rightTexture->SetPoint(FRAMEPOINT_BOTTOMRIGHT, frame, FRAMEPOINT_BOTTOMRIGHT, 0.0f, this->m_cornerSize, 0); + + rightTexture->Resize(0); + + rightTexture->SetTexture(border, 1, 1, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + rightTexture->SetBlendMode(this->m_blend); + } + + if (this->m_pieces & 0x4) { + // TODO + // CSimpleTexture* topTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* topTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_topTexture = topTexture; + + topTexture->SetHeight(this->m_cornerSize); + topTexture->SetPoint(FRAMEPOINT_TOPLEFT, frame, FRAMEPOINT_TOPLEFT, this->m_cornerSize, 0.0f, 0); + topTexture->SetPoint(FRAMEPOINT_TOPRIGHT, frame, FRAMEPOINT_TOPRIGHT, -this->m_cornerSize, 0.0f, 0); + + topTexture->Resize(0); + + topTexture->SetTexture(border, 1, 1, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + topTexture->SetBlendMode(this->m_blend); + } + + if (this->m_pieces & 0x8) { + // TODO + // CSimpleTexture* bottomTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* bottomTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_bottomTexture = bottomTexture; + + bottomTexture->SetHeight(this->m_cornerSize); + bottomTexture->SetPoint(FRAMEPOINT_BOTTOMLEFT, frame, FRAMEPOINT_BOTTOMLEFT, this->m_cornerSize, 0.0f, 0); + bottomTexture->SetPoint(FRAMEPOINT_BOTTOMRIGHT, frame, FRAMEPOINT_BOTTOMRIGHT, -this->m_cornerSize, 0.0f, 0); + + bottomTexture->Resize(0); + + bottomTexture->SetTexture(border, 1, 1, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + bottomTexture->SetBlendMode(this->m_blend); + } + + if (this->m_pieces & 0x10) { + // TODO + // CSimpleTexture* topLeftTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* topLeftTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_topLeftTexture = topLeftTexture; + + topLeftTexture->SetWidth(this->m_cornerSize); + topLeftTexture->SetHeight(this->m_cornerSize); + topLeftTexture->SetPoint(FRAMEPOINT_TOPLEFT, frame, FRAMEPOINT_TOPLEFT, 0.0f, 0.0f, 1); + + texCoords[0] = { 0.5078125f, 0.0625f }; + texCoords[1] = { 0.5078125f, 0.9375f }; + texCoords[2] = { 0.6171875f, 0.0625f }; + texCoords[3] = { 0.6171875f, 0.9375f }; + + topLeftTexture->SetTexture(border, 0, 0, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + topLeftTexture->SetTexCoord(texCoords); + topLeftTexture->SetBlendMode(this->m_blend); + } + + if (this->m_pieces & 0x20) { + // TODO + // CSimpleTexture* topRightTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* topRightTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_topRightTexture = topRightTexture; + + topRightTexture->SetWidth(this->m_cornerSize); + topRightTexture->SetHeight(this->m_cornerSize); + topRightTexture->SetPoint(FRAMEPOINT_TOPRIGHT, frame, FRAMEPOINT_TOPRIGHT, 0.0f, 0.0f, 1); + + texCoords[0] = { 0.6328125f, 0.0625f }; + texCoords[1] = { 0.6328125f, 0.9375f }; + texCoords[2] = { 0.7421875f, 0.0625f }; + texCoords[3] = { 0.7421875f, 0.9375f }; + + topRightTexture->SetTexture(border, 0, 0, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + topRightTexture->SetTexCoord(texCoords); + topRightTexture->SetBlendMode(this->m_blend); + } + + if (this->m_pieces & 0x40) { + // TODO + // CSimpleTexture* bottomLeftTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* bottomLeftTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_bottomLeftTexture = bottomLeftTexture; + + bottomLeftTexture->SetWidth(this->m_cornerSize); + bottomLeftTexture->SetHeight(this->m_cornerSize); + bottomLeftTexture->SetPoint(FRAMEPOINT_BOTTOMLEFT, frame, FRAMEPOINT_BOTTOMLEFT, 0.0f, 0.0f, 1); + + texCoords[0] = { 0.7578125f, 0.0625f }; + texCoords[1] = { 0.7578125f, 0.9375f }; + texCoords[2] = { 0.8671875f, 0.0625f }; + texCoords[3] = { 0.8671875f, 0.9375f }; + + bottomLeftTexture->SetTexture(border, 0, 0, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + bottomLeftTexture->SetTexCoord(texCoords); + bottomLeftTexture->SetBlendMode(this->m_blend); + } + + if (this->m_pieces & 0x80) { + // TODO + // CSimpleTexture* bottomRightTexture = CSimpleTexture::s_allocator.GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + CSimpleTexture* bottomRightTexture = new (m) CSimpleTexture(frame, DRAWLAYER_BACKGROUND_BORDER, 1); + + this->m_bottomRightTexture = bottomRightTexture; + + bottomRightTexture->SetWidth(this->m_cornerSize); + bottomRightTexture->SetHeight(this->m_cornerSize); + bottomRightTexture->SetPoint(FRAMEPOINT_BOTTOMRIGHT, frame, FRAMEPOINT_BOTTOMRIGHT, 0.0f, 0.0f, 1); + + texCoords[0] = { 0.8828125f, 0.0625f }; + texCoords[1] = { 0.8828125f, 0.9375f }; + texCoords[2] = { 0.9921875f, 0.0625f }; + texCoords[3] = { 0.9921875f, 0.9375f }; + + bottomRightTexture->SetTexture(border, 0, 0, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + bottomRightTexture->SetTexCoord(texCoords); + bottomRightTexture->SetBlendMode(this->m_blend); + } +} diff --git a/src/ui/CBackdropGenerator.hpp b/src/ui/CBackdropGenerator.hpp new file mode 100644 index 0000000..b120929 --- /dev/null +++ b/src/ui/CBackdropGenerator.hpp @@ -0,0 +1,49 @@ +#ifndef UI_C_BACKDROP_GENERATOR_HPP +#define UI_C_BACKDROP_GENERATOR_HPP + +#include "gx/Types.hpp" +#include +#include + +class CRect; +class CSimpleFrame; +class CSimpleTexture; +class CStatus; +class XMLNode; + +class CBackdropGenerator { + public: + // Member variables + CSimpleTexture* m_backgroundTexture = nullptr; + CSimpleTexture* m_leftTexture = nullptr; + CSimpleTexture* m_rightTexture = nullptr; + CSimpleTexture* m_topTexture = nullptr; + CSimpleTexture* m_bottomTexture = nullptr; + CSimpleTexture* m_topLeftTexture = nullptr; + CSimpleTexture* m_topRightTexture = nullptr; + CSimpleTexture* m_bottomLeftTexture = nullptr; + CSimpleTexture* m_bottomRightTexture = nullptr; + RCString m_background; + RCString m_border; + uint32_t m_pieces = 0; + int32_t m_tileBackground = 0; + int32_t int44; + float m_cornerSize = 0.025f; + float m_backgroundSize = 0.0f; + float m_topInset = 0.0f; + float m_bottomInset = 0.0f; + float m_leftInset = 0.0f; + float m_rightInset = 0.0f; + CImVector m_color = { 0x00 }; + CImVector m_borderColor = { 0x00 }; + EGxBlend m_blend; + + // Member functions + CBackdropGenerator(); + void Generate(const CRect*); + void LoadXML(XMLNode*, CStatus*); + void SetBorderVertexColor(const CImVector&); + void SetOutput(CSimpleFrame*); +}; + +#endif diff --git a/src/ui/CFramePoint.cpp b/src/ui/CFramePoint.cpp new file mode 100644 index 0000000..4582037 --- /dev/null +++ b/src/ui/CFramePoint.cpp @@ -0,0 +1,146 @@ +#include "ui/CFramePoint.hpp" +#include "ui/CLayoutFrame.hpp" + +const float CFramePoint::UNDEFINED = 3.4028237e38; + +CFramePoint::CFramePoint(CLayoutFrame* relative, FRAMEPOINT framePoint, float offsetX, float offsetY) { + this->m_offset = { offsetX, offsetY }; + this->m_framePoint = framePoint; + this->m_relative = relative; + + // TODO + // - this doesn't appear to be zeroed out + // - what is it actually checking? + this->m_flags = (this->m_flags & 0x2) >= 1 ? 0x6 : 0; +} + +CLayoutFrame* CFramePoint::GetRelative() { + return this->m_relative; +} + +int32_t CFramePoint::GetRelativeRect(CRect& rect) { + bool flag2initial = this->m_flags & 0x2; + + if (!flag2initial) { + this->m_flags |= 0x2; + } + + this->m_flags &= ~0x4; + + CLayoutFrame* relative = this->GetRelative(); + + if (relative->IsResizePending()) { + relative->Resize(1); + + if (this->m_flags & 0x4) { + if (!flag2initial) { + this->m_flags &= ~0x2; + } + + return 0; + } + } + + if (relative->m_flags & 0x2) { + relative->SetDeferredResize(0); + + if (this->m_flags & 0x4) { + if (!flag2initial) { + this->m_flags &= ~0x2; + } + + return 0; + } + } + + if (!relative->GetRect(&rect)) { + if (!flag2initial) { + this->m_flags &= ~0x2; + } + + return 0; + } + + if (relative->IsAttachmentOrigin()) { + rect.minY -= rect.minY; + rect.minX -= rect.minX; + rect.maxY -= rect.minY; + rect.maxX -= rect.minX; + } + + if (!flag2initial) { + this->m_flags &= ~0x2; + } + + return 1; +} + +void CFramePoint::MarkUnused() { + this->m_framePoint = FRAMEPOINT_NUMPOINTS; + this->m_offset = { 0.0f, 0.0f }; + this->m_relative = nullptr; + this->m_flags = this->m_flags & 0x2 ? 0x2 | 0x4 | 0x8 : 0x8; +} + +void CFramePoint::SetRelative(CLayoutFrame* relative, FRAMEPOINT relativePoint, float offsetX, float offsetY) { + this->m_offset = { offsetX, offsetY }; + this->m_framePoint = relativePoint; + this->m_relative = relative; + this->m_flags = this->m_flags & 0x2 ? 0x2 | 0x4 : 0x0; +} + +float CFramePoint::X(float scale) { + CRect relative = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (!this->GetRelativeRect(relative)) { + return UNDEFINED; + } + + switch (this->m_framePoint) { + case FRAMEPOINT_TOPLEFT: + case FRAMEPOINT_LEFT: + case FRAMEPOINT_BOTTOMLEFT: + return relative.minX + (this->m_offset.x * scale); + + case FRAMEPOINT_TOP: + case FRAMEPOINT_CENTER: + case FRAMEPOINT_BOTTOM: + return ((relative.minX + relative.maxX) * 0.5f) + (this->m_offset.x * scale); + + case FRAMEPOINT_TOPRIGHT: + case FRAMEPOINT_RIGHT: + case FRAMEPOINT_BOTTOMRIGHT: + return relative.maxX + (this->m_offset.x * scale); + + default: + return UNDEFINED; + } +} + +float CFramePoint::Y(float scale) { + CRect relative = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (!this->GetRelativeRect(relative)) { + return UNDEFINED; + } + + switch (this->m_framePoint) { + case FRAMEPOINT_TOPLEFT: + case FRAMEPOINT_TOP: + case FRAMEPOINT_TOPRIGHT: + return relative.maxY + (this->m_offset.y * scale); + + case FRAMEPOINT_LEFT: + case FRAMEPOINT_CENTER: + case FRAMEPOINT_RIGHT: + return ((relative.minY + relative.maxY) * 0.5f) + (this->m_offset.y * scale); + + case FRAMEPOINT_BOTTOMLEFT: + case FRAMEPOINT_BOTTOM: + case FRAMEPOINT_BOTTOMRIGHT: + return relative.minY + (this->m_offset.y * scale); + + default: + return UNDEFINED; + } +} diff --git a/src/ui/CFramePoint.hpp b/src/ui/CFramePoint.hpp new file mode 100644 index 0000000..cccb3b4 --- /dev/null +++ b/src/ui/CFramePoint.hpp @@ -0,0 +1,32 @@ +#ifndef UI_C_FRAME_POINT_HPP +#define UI_C_FRAME_POINT_HPP + +#include "ui/Types.hpp" +#include +#include + +class CLayoutFrame; +class CRect; + +class CFramePoint { + public: + // Static variables + static float const UNDEFINED; + + // Member variables + C2Vector m_offset; + CLayoutFrame* m_relative; + uint32_t m_framePoint : 8; + uint32_t m_flags : 24; + + // Member functions + CFramePoint(CLayoutFrame*, FRAMEPOINT, float, float); + CLayoutFrame* GetRelative(); + int32_t GetRelativeRect(CRect&); + void MarkUnused(void); + void SetRelative(CLayoutFrame*, FRAMEPOINT, float, float); + float X(float); + float Y(float); +}; + +#endif diff --git a/src/ui/CFrameStrata.cpp b/src/ui/CFrameStrata.cpp new file mode 100644 index 0000000..432f04e --- /dev/null +++ b/src/ui/CFrameStrata.cpp @@ -0,0 +1,168 @@ +#include "ui/CFrameStrata.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/CSimpleRender.hpp" + +int32_t CFrameStrataNode::BuildBatches() { + if (!this->batchDirty) { + return 0; + } + + int32_t batchDirty = this->batchDirty; + this->batchDirty = 0; + + for (int32_t layer = 0; layer < NUM_SIMPLEFRAME_DRAWLAYERS; layer++) { + CRenderBatch* batch = &this->batches[layer]; + + this->renderList.UnlinkNode(batch); + + if ((1 << layer) & batchDirty) { + batch->Clear(); + + for (auto frame = this->frames.Head(); frame; frame = this->frames.Link(frame)->Next()) { + if (!(frame->m_flags & 0x2000)) { + frame->OnFrameRender(batch, layer); + } + } + + batch->Finish(); + } + + if (batch->m_count) { + this->renderList.LinkToTail(batch); + } + } + + if (this->batchDirty & 0x20) { + this->batchDirty |= 0x1F; + } + + return 0; +} + +void CFrameStrataNode::OnLayerUpdate(float elapsedSec) { + auto frame = this->frames.Head(); + + while (frame) { + auto next = this->frames.Link(frame)->Next(); + this->pendingFrame = next; + frame->OnLayerUpdate(elapsedSec); + frame = this->pendingFrame; + } + + // TODO + // this->frames.Combine(this->pendingFrames, 2, nullptr); +} + +bool CFrameStrataNode::RemoveFrame(CSimpleFrame* frame) { + if (!this->frames.Link(frame)->IsLinked()) { + return 0; + } + + if (frame == this->pendingFrame) { + this->pendingFrame = this->frames.Link(frame)->Next(); + } + + this->frames.UnlinkNode(frame); + + if (!(frame->m_flags & 0x2000)) { + this->batchDirty = -1; + } + + return this->batchDirty != 0; +} + +void CFrameStrata::AddFrame(CSimpleFrame* frame) { + // TODO + // - potentially an inlined TSFixedArray function? + if (frame->m_level >= this->levels.Count()) { + uint32_t count = this->levels.Count(); + + this->levels.SetCount(frame->m_level + 1); + + for (int32_t i = count; i < frame->m_level + 1; i++) { + void* m = SMemAlloc(sizeof(CFrameStrataNode), __FILE__, __LINE__, 0); + CFrameStrataNode* node = m ? new (m) CFrameStrataNode() : nullptr; + this->levels[i] = node; + } + } + + if (frame->m_level >= this->topLevel) { + this->topLevel = frame->m_level + 1; + } + + auto level = this->levels[frame->m_level]; + + if (!frame->m_strataLink.IsLinked()) { + auto frames = level->pendingFrame ? &level->pendingFrames : &level->frames; + + frames->LinkToTail(frame); + + if (!(frame->m_flags & 0x2000)) { + level->batchDirty = -1; + } + } + + this->levelsDirty = 1; + this->batchDirty |= (level->batchDirty != 0); +} + +void CFrameStrata::CheckOcclusion() { + // TODO +} + +int32_t CFrameStrata::BuildBatches(int32_t a2) { + if (this->levelsDirty) { + this->CheckOcclusion(); + this->levelsDirty = 0; + } + + if (!this->batchDirty) { + return this->batchDirty; + } + + this->batchDirty = 0; + + if (this->topLevel == 0) { + return 0; + } + + for (int32_t i = 0; i < this->topLevel; i++) { + if (this->levels[i]->BuildBatches()) { + this->batchDirty = 1; + } + } + + return this->batchDirty; +} + +int32_t CFrameStrata::FrameOccluded(CSimpleFrame* frame) { + // TODO + return 0; +} + +void CFrameStrata::OnLayerUpdate(float elapsedSec) { + if (this->topLevel) { + for (int32_t l = 0; l < this->topLevel; l++) { + this->levels[l]->OnLayerUpdate(elapsedSec); + } + } +} + +void CFrameStrata::RemoveFrame(CSimpleFrame* frame) { + if (frame->m_level < this->levels.Count()) { + CFrameStrataNode* level = this->levels[frame->m_level]; + int32_t batchDirty = level->RemoveFrame(frame); + this->batchDirty |= batchDirty; + this->levelsDirty = 1; + } +} + +void CFrameStrata::RenderBatches() { + for (int32_t i = 0; i < this->topLevel; i++) { + auto renderList = &this->levels[i]->renderList; + + for (auto batch = renderList->Head(); batch; batch = renderList->Link(batch)->Next()) { + CSimpleRender::DrawBatch(batch); + } + } +} diff --git a/src/ui/CFrameStrata.hpp b/src/ui/CFrameStrata.hpp new file mode 100644 index 0000000..e2a0caa --- /dev/null +++ b/src/ui/CFrameStrata.hpp @@ -0,0 +1,44 @@ +#ifndef UI_C_FRAME_STRATA_HPP +#define UI_C_FRAME_STRATA_HPP + +#include "ui/CRenderBatch.hpp" +#include "ui/CSimpleFrame.hpp" +#include +#include +#include + +class CFrameStrataNode { + public: + // Member variables + STORM_EXPLICIT_LIST(CSimpleFrame, m_strataLink) pendingFrames; + STORM_EXPLICIT_LIST(CSimpleFrame, m_strataLink) frames; + CSimpleFrame* pendingFrame = nullptr; + CRenderBatch batches[5]; + int32_t batchDirty = 0; + STORM_EXPLICIT_LIST(CRenderBatch, renderLink) renderList; + + // Member functions + int32_t BuildBatches(void); + void OnLayerUpdate(float); + bool RemoveFrame(CSimpleFrame*); +}; + +class CFrameStrata { + public: + // Member variables + int32_t batchDirty = 0; + int32_t levelsDirty = 0; + uint32_t topLevel = 0; + TSFixedArray levels; + + // Member functions + void AddFrame(CSimpleFrame*); + int32_t BuildBatches(int32_t); + void CheckOcclusion(void); + int32_t FrameOccluded(CSimpleFrame*); + void OnLayerUpdate(float); + void RemoveFrame(CSimpleFrame*); + void RenderBatches(void); +}; + +#endif diff --git a/src/ui/CLayoutFrame.cpp b/src/ui/CLayoutFrame.cpp new file mode 100644 index 0000000..cfe6792 --- /dev/null +++ b/src/ui/CLayoutFrame.cpp @@ -0,0 +1,920 @@ +#include "ui/CLayoutFrame.hpp" +#include "gx/Coordinate.hpp" +#include "math/Utils.hpp" +#include "ui/CFramePoint.hpp" +#include "ui/FrameXML.hpp" +#include "ui/LoadXML.hpp" +#include "util/CStatus.hpp" +#include +#include + +STORM_EXPLICIT_LIST(CLayoutFrame, resizeLink) LayoutFrame::s_resizePendingList; + +float SynthesizeSide(float center, float opposite, float size) { + if (center != CFramePoint::UNDEFINED && opposite != CFramePoint::UNDEFINED) { + return center + center - opposite; + } else if (opposite != CFramePoint::UNDEFINED && size != 0.0f) { + return opposite + size; + } else if (center != CFramePoint::UNDEFINED && size != 0.0f) { + return center + (size * 0.5f); + } else { + return CFramePoint::UNDEFINED; + } +} + +float SynthesizeCenter(float side1, float side2, float size) { + if (side1 != CFramePoint::UNDEFINED && side2 != CFramePoint::UNDEFINED) { + return (side1 + side2) * 0.5f; + } else if (side1 != CFramePoint::UNDEFINED && size != 0.0f) { + return side1 + (size * 0.5f); + } else if (side2 != CFramePoint::UNDEFINED && size != 0.0f) { + return side2 - (size * 0.5f); + } else { + return CFramePoint::UNDEFINED; + } +} + +void CLayoutFrame::ResizePending() { + int32_t loading = 0; + + for (CLayoutFrame* frame = LayoutFrame::s_resizePendingList.Head(); frame && loading < 32; frame = LayoutFrame::s_resizePendingList.Link(frame)->Next()) { + if (!frame->IsObjectLoaded()) { + loading++; + continue; + } + + if (frame->OnFrameResize() || (frame->m_resizeCounter--, frame->m_resizeCounter == 0)) { + frame->m_flags &= ~0x4; + LayoutFrame::s_resizePendingList.UnlinkNode(frame); + } + } +} + +CLayoutFrame::CLayoutFrame() { + this->m_flags = 0; + + this->m_rect = { 0.0, 0.0, 0.0, 0.0 }; + this->m_width = 0.0; + this->m_height = 0.0; + + this->m_layoutScale = 1.0; + this->m_layoutDepth = 1.0; + + this->m_guard = { 0 }; +} + +CLayoutFrame::~CLayoutFrame() { + this->DestroyLayout(); + + // TODO +} + +void CLayoutFrame::AddToResizeList() { + if (LayoutFrame::s_resizePendingList.IsLinked(this)) { + return; + } + + CLayoutFrame* dependent = nullptr; + + for (auto frame = LayoutFrame::s_resizePendingList.Head(); frame; frame = LayoutFrame::s_resizePendingList.Link(frame)->Next()) { + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + auto point = frame->m_points[i]; + + if (point && !(point->m_flags & 0x8) && point->GetRelative() == this) { + dependent = frame; + } + } + } + + if (dependent) { + LayoutFrame::s_resizePendingList.LinkNode(this, 2, dependent); + } else { + LayoutFrame::s_resizePendingList.LinkToTail(this); + } + + this->m_resizeCounter = 6; +} + +float CLayoutFrame::Bottom() { + if (this->m_guard.bottom) { + return CFramePoint::UNDEFINED; + } + + this->m_guard.bottom = 1; + + static FRAMEPOINT sidepoints[] = { + FRAMEPOINT_BOTTOMLEFT, + FRAMEPOINT_BOTTOM, + FRAMEPOINT_BOTTOMRIGHT + }; + + float bottom = CFramePoint::UNDEFINED; + this->GetFirstPointY(sidepoints, 3, bottom); + + if (bottom == CFramePoint::UNDEFINED) { + float size = -(this->GetHeight() * this->m_layoutScale); + float opposite = this->Top(); + float center = this->CenterY(); + + bottom = SynthesizeSide(center, opposite, size); + } + + this->m_guard.bottom = 0; + + return bottom; +} + +int32_t CLayoutFrame::CalculateRect(CRect* rect) { + rect->minX = this->Left(); + + if (rect->minX == CFramePoint::UNDEFINED) { + return 0; + } + + rect->minY = this->Bottom(); + + if (rect->minY == CFramePoint::UNDEFINED) { + return 0; + } + + rect->maxX = this->Right(); + + if (rect->maxX == CFramePoint::UNDEFINED) { + return 0; + } + + rect->maxY = this->Top(); + + if (rect->maxY == CFramePoint::UNDEFINED) { + return 0; + } + + if (!(this->m_flags & 0x10)) { + return 1; + } + + float v14 = 0.0f; + float v15 = 0.0f; + float v16 = 0.0f; + float v17 = 0.0f; + + this->GetClampRectInsets(v15, v17, v16, v14); + + float v12 = -(this->m_layoutScale * v15); + float v7 = NDCToDDCWidth(1.0f); + float v13 = v7 - this->m_layoutScale * v17; + float v11 = -(this->m_layoutScale * v14); + float v8 = NDCToDDCHeight(1.0f); + float v9 = v8 - this->m_layoutScale * v16; + + if (v12 > rect->minX) { + rect->maxX = rect->maxX - (rect->minX - v12); + rect->minX = v12; + } + + if (v11 > rect->minY) { + rect->maxY = rect->maxY - (rect->minY - v11); + rect->minY = v11; + } + + if (v13 < rect->maxX) { + rect->minX = rect->minX - (rect->maxX - v13); + rect->maxX = v13; + } + + if (v9 < rect->maxY ) { + rect->minY = rect->minY - (rect->maxY - v9); + rect->maxY = v9; + } + + return 1; +} + +bool CLayoutFrame::CanBeAnchorFor(CLayoutFrame*) { + // TODO + + return true; +} + +float CLayoutFrame::CenterX() { + if (this->m_guard.centerX) { + return CFramePoint::UNDEFINED; + } + + this->m_guard.centerX = 1; + + static FRAMEPOINT sidepoints[] = { + FRAMEPOINT_TOP, + FRAMEPOINT_CENTER, + FRAMEPOINT_BOTTOM + }; + + float centerX = CFramePoint::UNDEFINED; + this->GetFirstPointX(sidepoints, 3, centerX); + + if (centerX == CFramePoint::UNDEFINED) { + float size = this->GetWidth() * this->m_layoutScale; + float side2 = this->Right(); + float side1 = this->Left(); + + centerX = SynthesizeCenter(side1, side2, size); + } + + this->m_guard.centerX = 0; + + return centerX; +} + +float CLayoutFrame::CenterY() { + if (this->m_guard.centerY) { + return CFramePoint::UNDEFINED; + } + + this->m_guard.centerY = 1; + + static FRAMEPOINT sidepoints[] = { + FRAMEPOINT_LEFT, + FRAMEPOINT_CENTER, + FRAMEPOINT_RIGHT + }; + + float centerY = CFramePoint::UNDEFINED; + this->GetFirstPointY(sidepoints, 3, centerY); + + if (centerY == CFramePoint::UNDEFINED) { + float size = this->GetHeight() * this->m_layoutScale; + float side2 = this->Top(); + float side1 = this->Bottom(); + + centerY = SynthesizeCenter(side1, side2, size); + } + + this->m_guard.centerY = 0; + + return centerY; +} + +void CLayoutFrame::ClearAllPoints() { + this->FreePoints(); +} + +void CLayoutFrame::DestroyLayout() { + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + auto point = this->m_points[i]; + + if (point) { + if (!(point->m_flags & 0x8) && point->m_relative) { + point->m_relative->UnregisterResize(this, 1 << i); + } + + delete point; + this->m_points[i] = nullptr; + } + } + + for (auto node = this->m_resizeList.Head(); node; node = this->m_resizeList.Link(node)->Next()) { + auto frame = node->frame; + + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + auto point = this->m_points[i]; + + if (point && point->m_relative == this) { + point->MarkUnused(); + } + } + + frame->m_flags &= ~0x8; + } + + this->m_resizeList.DeleteAll(); +} + +void CLayoutFrame::FreePoints() { + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + auto point = this->m_points[i]; + + if (point && !(point->m_flags & 0x8)) { + if (point->m_relative) { + point->m_relative->UnregisterResize(this, 1 << i); + } + + point->MarkUnused(); + } + } +} + +void CLayoutFrame::GetClampRectInsets(float& a1, float& a2, float& a3, float& a4) { + a4 = 0.0f; + a3 = 0.0f; + a2 = 0.0f; + a1 = 0.0f; +} + +void CLayoutFrame::GetFirstPointX(const FRAMEPOINT* const pointarray, int32_t elements, float& x) { + // TODO + // - what's up with this loop? + + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + if (this->Sub488DB0(pointarray, elements, x)) { + break; + } + } +} + +void CLayoutFrame::GetFirstPointY(const FRAMEPOINT* const pointarray, int32_t elements, float& y) { + // TODO + // - what's up with this loop? + + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + if (this->Sub488E40(pointarray, elements, y)) { + break; + } + } +} + +CLayoutFrame* CLayoutFrame::GetLayoutFrameByName(const char* name) { + return nullptr; +} + +CLayoutFrame* CLayoutFrame::GetLayoutParent() { + return nullptr; +} + +int32_t CLayoutFrame::GetRect(CRect* rect) { + if (!(this->m_flags & 0x1)) { + return 0; + } + + rect->minY = this->m_rect.minY; + rect->minX = this->m_rect.minX; + rect->maxY = this->m_rect.maxY; + rect->maxX = this->m_rect.maxX; + + return 1; +} + +float CLayoutFrame::GetHeight() { + return this->m_height; +} + +float CLayoutFrame::GetWidth() { + return this->m_width; +} + +int32_t CLayoutFrame::IsAttachmentOrigin() { + return 0; +} + +int32_t CLayoutFrame::IsObjectLoaded() { + return 1; +} + +int32_t CLayoutFrame::IsResizeDependency(CLayoutFrame* dependentFrame) { + CLayoutFrame* v2 = nullptr; + + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + auto framePoint = this->m_points[i]; + + if (!framePoint || framePoint->m_flags & 0x8) { + continue; + } + + if (!framePoint->GetRelative() || framePoint->GetRelative() == v2) { + continue; + } + + if (framePoint->GetRelative() == dependentFrame || framePoint->GetRelative()->IsResizeDependency(dependentFrame)) { + return 1; + } + + v2 = framePoint->GetRelative(); + } + + return 0; +} + +uint32_t CLayoutFrame::IsResizePending() { + return this->m_flags & 0x4; +} + +float CLayoutFrame::Left() { + if (this->m_guard.left) { + return CFramePoint::UNDEFINED; + } + + this->m_guard.left = 1; + + static FRAMEPOINT sidepoints[] = { + FRAMEPOINT_TOPLEFT, + FRAMEPOINT_LEFT, + FRAMEPOINT_BOTTOMLEFT + }; + + float left = CFramePoint::UNDEFINED; + this->GetFirstPointX(sidepoints, 3, left); + + if (left == CFramePoint::UNDEFINED) { + float size = -(this->GetWidth() * this->m_layoutScale); + float opposite = this->Right(); + float center = this->CenterX(); + + left = SynthesizeSide(center, opposite, size); + } + + this->m_guard.left = 0; + + return left; +} + +void CLayoutFrame::LoadXML(XMLNode* node, CStatus* status) { + XMLNode* size = node->GetChildByName("Size"); + float width; + float height; + + if (size && LoadXML_Dimensions(size, width, height, status)) { + this->SetWidth(width); + this->SetHeight(height); + } + + CLayoutFrame* layoutParent = this->GetLayoutParent(); + + int32_t setAllPoints; + + const char* setAllPointsAttr = node->GetAttributeByName("setAllPoints"); + + if (setAllPointsAttr && !SStrCmpI(setAllPointsAttr, "true", 0x7FFFFFFF)) { + setAllPoints = 1; + } else { + setAllPoints = 0; + } + + const XMLNode* anchorsNode = node->GetChildByName("Anchors"); + + if (anchorsNode) { + if (setAllPoints) { + status->Add( + STATUS_WARNING, + "SETALLPOINTS set to true in frame with anchors (ignored)" + ); + } + + for (XMLNode* anchorNode = anchorsNode->m_child; anchorNode; anchorNode = anchorNode->m_next) { + const char* pointAttr = anchorNode->GetAttributeByName("point"); + + FRAMEPOINT point; + + if (!pointAttr || !StringToFramePoint(pointAttr, point)) { + status->Add( + STATUS_WARNING, + "Invalid anchor point in frame: %s", + pointAttr + ); + + continue; + } + + const char* relativePointAttr = anchorNode->GetAttributeByName("relativePoint"); + + FRAMEPOINT relativePoint; + + if (relativePointAttr && *relativePointAttr) { + if (!StringToFramePoint(relativePointAttr, relativePoint)) { + status->Add( + STATUS_WARNING, + "Invalid anchor point in frame: %s", + relativePointAttr + ); + + continue; + } + } else { + relativePoint = point; + } + + const char* relativeToAttr = anchorNode->GetAttributeByName("relativeTo"); + + CLayoutFrame* relative; + + if (!relativeToAttr || !*relativeToAttr) { + relative = layoutParent; + } else { + relative = this->GetLayoutFrameByName(relativeToAttr); + + if (!relative) { + status->Add( + STATUS_WARNING, + "Couldn't find relative frame: %s", + relativeToAttr + ); + + continue; + } + + if (relative && relative == this) { + status->Add( + STATUS_WARNING, + "Frame anchored to itself: %s", + relativeToAttr + ); + + continue; + } + } + + XMLNode* offsetNode = anchorNode->GetChildByName("Offset"); + + float offsetX = 0.0f; + float offsetY = 0.0f; + + if (offsetNode) { + LoadXML_Dimensions(offsetNode, offsetX, offsetY, status); + } else { + LoadXML_Dimensions(anchorNode, offsetX, offsetY, status); + } + + this->SetPoint(point, relative, relativePoint, offsetX, offsetY, 0); + } + + this->Resize(0); + } else if (setAllPoints) { + this->SetAllPoints(layoutParent, 1); + } +} + +int32_t CLayoutFrame::OnFrameResize() { + CRect rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (!this->CalculateRect(&rect)) { + this->m_flags = (this->m_flags & ~0x1) | 0x8; + return 0; + } + + this->m_flags &= ~(0x4 | 0x8); + + if ( + this->m_flags & 0x1 + && AreEqual(rect.minX, this->m_rect.minX, WHOA_EPSILON_2) + && AreEqual(rect.maxX, this->m_rect.maxX, WHOA_EPSILON_2) + && AreEqual(rect.minY, this->m_rect.minY, WHOA_EPSILON_2) + && AreEqual(rect.maxY, this->m_rect.maxY, WHOA_EPSILON_2) + ) { + return 1; + } + + CRect prevRect = { + this->m_rect.minY, + this->m_rect.minX, + this->m_rect.maxY, + this->m_rect.maxX + }; + + this->m_rect.minY = rect.minY; + this->m_rect.minX = rect.minX; + this->m_rect.maxY = rect.maxY; + this->m_rect.maxX = rect.maxX; + + this->m_flags |= 0x1; + + this->OnFrameSizeChanged(prevRect); + + return this->m_flags & 0x1; +} + +void CLayoutFrame::OnFrameSizeChanged(const CRect& rect) { + for (auto node = this->m_resizeList.Head(); node; node = this->m_resizeList.Link(node)->Next()) { + node->frame->Resize(0); + } +} + +void CLayoutFrame::OnProtectedAttach(CLayoutFrame* frame) { + if (this->m_flags & 0x400) { + frame->SetProtectFlag(0x400); + } + + if (this->m_flags & 0x300) { + frame->SetProtectFlag(0x200); + } +} + +int32_t CLayoutFrame::PtInFrameRect(const C2Vector& pt) { + // TODO + return 0; +} + +void CLayoutFrame::RegisterResize(CLayoutFrame* frame, uint32_t dep) { + for (auto node = this->m_resizeList.Head(); node; node = this->m_resizeList.Link(node)->Next()) { + if (node->frame == frame) { + node->dep |= dep; + return; + } + } + + auto node = this->m_resizeList.NewNode(2, 0, 0x8); + + node->frame = frame; + node->dep = dep; + + if (static_cast(frame)->m_flags & 0x400) { + this->SetProtectFlag(0x400); + } + + if (static_cast(frame)->m_flags & (0x100 | 0x200)) { + this->SetProtectFlag(0x200); + } +} + +void CLayoutFrame::Resize(int32_t force) { + if (force && !(this->m_flags & 0x8) && this->OnFrameResize()) { + LayoutFrame::s_resizePendingList.UnlinkNode(this); + return; + } + + if (this->m_flags & 0x4 && (this->m_flags & 0x2 || LayoutFrame::s_resizePendingList.IsLinked(this))) { + this->m_resizeCounter = 6; + return; + } + + this->m_flags |= 0x4; + + if (this->m_flags & 0x2) { + for (auto node = this->m_resizeList.Head(); node; node = this->m_resizeList.Link(node)->Next()) { + node->frame->Resize(0); + } + } else { + this->AddToResizeList(); + } +} + +float CLayoutFrame::Right() { + if (this->m_guard.right) { + return CFramePoint::UNDEFINED; + } + + this->m_guard.right = 1; + + static FRAMEPOINT sidepoints[] = { + FRAMEPOINT_TOPRIGHT, + FRAMEPOINT_RIGHT, + FRAMEPOINT_BOTTOMRIGHT + }; + + float right = CFramePoint::UNDEFINED; + this->GetFirstPointX(sidepoints, 3, right); + + if (right == CFramePoint::UNDEFINED) { + float size = this->GetWidth() * this->m_layoutScale; + float opposite = this->Left(); + float center = this->CenterX(); + + right = SynthesizeSide(center, opposite, size); + } + + this->m_guard.right = 0; + + return right; +} + +void CLayoutFrame::SetAllPoints(CLayoutFrame* relative, int32_t doResize) { + STORM_ASSERT(relative); + STORM_ASSERT(relative != this); + + if (!relative->CanBeAnchorFor(this)) { + return; + } + + this->FreePoints(); + + auto topLeft = this->m_points[FRAMEPOINT_TOPLEFT]; + + if (topLeft) { + topLeft->SetRelative(relative, FRAMEPOINT_TOPLEFT, 0.0f, 0.0f); + } else { + // TODO + // CFramePoint::s_framePointHeap->GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CFramePoint), __FILE__, __LINE__, 0); + topLeft = m ? new (m) CFramePoint(relative, FRAMEPOINT_TOPLEFT, 0.0f, 0.0f) : nullptr; + + this->m_points[FRAMEPOINT_TOPLEFT] = topLeft; + } + + auto bottomRight = this->m_points[FRAMEPOINT_BOTTOMRIGHT]; + + if (bottomRight) { + bottomRight->SetRelative(relative, FRAMEPOINT_BOTTOMRIGHT, 0.0f, 0.0f); + } else { + // TODO + // CFramePoint::s_framePointHeap->GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CFramePoint), __FILE__, __LINE__, 0); + bottomRight = m ? new (m) CFramePoint(relative, FRAMEPOINT_BOTTOMRIGHT, 0.0f, 0.0f) : nullptr; + + this->m_points[FRAMEPOINT_BOTTOMRIGHT] = bottomRight; + } + + this->m_flags &= ~0x8; + relative->RegisterResize(this, (1 << FRAMEPOINT_TOPLEFT) | (1 << FRAMEPOINT_BOTTOMRIGHT)); + + if (doResize) { + this->Resize(0); + } +} + +void CLayoutFrame::SetDeferredResize(int32_t enable) { + if (enable) { + this->m_flags |= 0x2; + + if (LayoutFrame::s_resizePendingList.Link(this)->IsLinked()) { + LayoutFrame::s_resizePendingList.UnlinkNode(this); + } + + return; + } + + this->m_flags &= ~0x2; + + if (this->m_flags & 0x4) { + this->Resize(1); + } +} + +void CLayoutFrame::SetHeight(float height) { + this->m_flags &= ~0x8; + this->m_height = height; + this->Resize(0); +} + +bool CLayoutFrame::SetLayoutScale(float scale, bool force) { + STORM_ASSERT(scale); + + if (force || (!AreEqual(this->m_layoutScale, scale, WHOA_EPSILON_1) && scale > 0.00000011920929)) { + this->m_layoutScale = scale; + this->m_rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + this->m_flags &= ~0x1; + + this->Resize(0); + + return true; + } + + return false; +} + +void CLayoutFrame::SetPoint(FRAMEPOINT point, CLayoutFrame* relative, FRAMEPOINT relativePoint, float offsetX, float offsetY, int32_t doResize) { + STORM_ASSERT(relative); + STORM_ASSERT(relative != this); + + if (!relative->CanBeAnchorFor(this)) { + return; + } + + auto framePoint = this->m_points[point]; + + if (framePoint) { + if (framePoint->GetRelative()) { + if (relative == framePoint->GetRelative()) { + if ( + framePoint->m_framePoint != relativePoint + || !AreEqual(framePoint->m_offset.x, offsetX, WHOA_EPSILON_1) + || !AreEqual(framePoint->m_offset.y, offsetY, WHOA_EPSILON_1) + ) { + framePoint->SetRelative(relative, relativePoint, offsetX, offsetY); + this->m_flags &= ~0x8; + + if (doResize) { + this->Resize(0); + } + } + + return; + } else { + framePoint->GetRelative()->UnregisterResize(this, 1 << point); + } + } + + framePoint->SetRelative(relative, relativePoint, offsetX, offsetY); + } else { + // TODO + // CFramePoint::s_framePointHeap->GetData(0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CFramePoint), __FILE__, __LINE__, 0); + framePoint = m ? new (m) CFramePoint(relative, relativePoint, offsetX, offsetY) : nullptr; + this->m_points[point] = framePoint; + } + + this->m_flags &= ~0x8; + relative->RegisterResize(this, 1 << point); + + if (doResize) { + this->Resize(0); + } +} + +void CLayoutFrame::SetProtectFlag(uint32_t flag) { + // TODO +} + +void CLayoutFrame::SetWidth(float width) { + this->m_flags &= ~0x8; + this->m_width = width; + this->Resize(0); +} + +int32_t CLayoutFrame::Sub488DB0(const FRAMEPOINT* const pointarray, int32_t elements, float& x) { + for (int32_t i = 0; i < elements; i++) { + FRAMEPOINT f = pointarray[i]; + CFramePoint* point = this->m_points[f]; + + if (point && !(point->m_flags & 0x8)) { + x = point->X(this->m_layoutScale); + + if (point->m_flags & 0x4) { + x = CFramePoint::UNDEFINED; + return 0; + } + + if (x != CFramePoint::UNDEFINED) { + return 1; + } + } + } + + x = CFramePoint::UNDEFINED; + return 1; +} + +int32_t CLayoutFrame::Sub488E40(const FRAMEPOINT* const pointarray, int32_t elements, float& y) { + for (int32_t i = 0; i < elements; i++) { + FRAMEPOINT f = pointarray[i]; + CFramePoint* point = this->m_points[f]; + + if (point && !(point->m_flags & 0x8)) { + y = point->Y(this->m_layoutScale); + + if (point->m_flags & 0x4) { + y = CFramePoint::UNDEFINED; + return 0; + } + + if (y != CFramePoint::UNDEFINED) { + return 1; + } + } + } + + y = CFramePoint::UNDEFINED; + return 1; +} + +float CLayoutFrame::Top() { + if (this->m_guard.top) { + return CFramePoint::UNDEFINED; + } + + this->m_guard.top = 1; + + static FRAMEPOINT sidepoints[] = { + FRAMEPOINT_TOPLEFT, + FRAMEPOINT_TOP, + FRAMEPOINT_TOPRIGHT + }; + + float top = CFramePoint::UNDEFINED; + this->GetFirstPointY(sidepoints, 3, top); + + if (top == CFramePoint::UNDEFINED) { + float size = this->GetHeight() * this->m_layoutScale; + float opposite = this->Bottom(); + float center = this->CenterY(); + + top = SynthesizeSide(center, opposite, size); + } + + this->m_guard.top = 0; + + return top; +} + +void CLayoutFrame::UnflattenFrame(CLayoutFrame* frame) { + // TODO +} + +void CLayoutFrame::UnregisterResize(CLayoutFrame* frame, uint32_t dep) { + for (auto node = this->m_resizeList.Head(); node; node = this->m_resizeList.Link(node)->Next()) { + if (node->frame == frame) { + node->dep &= ~dep; + + if (node->dep) { + return; + } + + if (static_cast(frame)->m_flags & (0x100 | 0x200 | 0x400)) { + this->SetProtectFlag(0x400); + } + + this->m_resizeList.DeleteNode(node); + + return; + } + } +} diff --git a/src/ui/CLayoutFrame.hpp b/src/ui/CLayoutFrame.hpp new file mode 100644 index 0000000..36f8f7c --- /dev/null +++ b/src/ui/CLayoutFrame.hpp @@ -0,0 +1,99 @@ +#ifndef UI_C_LAYOUT_FRAME_HPP +#define UI_C_LAYOUT_FRAME_HPP + +#include "ui/Types.hpp" +#include "ui/Util.hpp" +#include +#include +#include + +class CFramePoint; +class CStatus; +class XMLNode; + +class CLayoutFrame { + public: + // Types + struct FRAMENODE : public TSLinkedNode { + CLayoutFrame* frame; + uint32_t dep; + }; + + // Static functions + static void ResizePending(void); + + // Member variables + TSLink resizeLink; + CFramePoint* m_points[FRAMEPOINT_NUMPOINTS] = {}; + TSList> m_resizeList; + struct { + int32_t left : 1; + int32_t top : 1; + int32_t right : 1; + int32_t bottom : 1; + int32_t centerX : 1; + int32_t centerY : 1; + } m_guard; + uint32_t m_resizeCounter : 8; + uint32_t m_flags : 16; + CRect m_rect; + float m_width; + float m_height; + float m_layoutScale; + float m_layoutDepth; + + // Virtual member functions + virtual ~CLayoutFrame(); + virtual CLayoutFrame* GetLayoutParent(void); + virtual bool SetLayoutScale(float, bool); + virtual void SetWidth(float); + virtual void SetHeight(float); + virtual float GetWidth(void); + virtual float GetHeight(void); + virtual void GetClampRectInsets(float&, float&, float&, float&); + virtual int32_t IsAttachmentOrigin(void); + virtual CLayoutFrame* GetLayoutFrameByName(const char*); + virtual int32_t IsObjectLoaded(void); + virtual void OnFrameSizeChanged(const CRect&); + + // Member functions + CLayoutFrame(); + void AddToResizeList(void); + float Bottom(void); + int32_t CalculateRect(CRect*); + bool CanBeAnchorFor(CLayoutFrame*); + float CenterX(void); + float CenterY(void); + void ClearAllPoints(void); + void DestroyLayout(); + void FreePoints(void); + void GetFirstPointX(const FRAMEPOINT* const, int32_t, float&); + void GetFirstPointY(const FRAMEPOINT* const, int32_t, float&); + int32_t GetRect(CRect*); + int32_t IsResizeDependency(CLayoutFrame*); + uint32_t IsResizePending(void); + float Left(void); + void LoadXML(XMLNode*, CStatus*); + int32_t OnFrameResize(void); + void OnProtectedAttach(CLayoutFrame*); + int32_t PtInFrameRect(const C2Vector&); + void RegisterResize(CLayoutFrame*, uint32_t); + void Resize(int32_t); + float Right(void); + void SetAllPoints(CLayoutFrame*, int32_t); + void SetDeferredResize(int32_t); + void SetPoint(FRAMEPOINT, CLayoutFrame*, FRAMEPOINT, float, float, int32_t); + void SetProtectFlag(uint32_t); + int32_t Sub488DB0(const FRAMEPOINT* const, int32_t, float&); + int32_t Sub488E40(const FRAMEPOINT* const, int32_t, float&); + float Top(void); + void UnflattenFrame(CLayoutFrame*); + void UnregisterResize(CLayoutFrame*, uint32_t); +}; + +namespace LayoutFrame { + // TODO put in better location + extern STORM_EXPLICIT_LIST(CLayoutFrame, resizeLink) s_resizePendingList; +} + +#endif diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt new file mode 100644 index 0000000..9c50757 --- /dev/null +++ b/src/ui/CMakeLists.txt @@ -0,0 +1,24 @@ +file(GLOB PRIVATE_SOURCES "*.cpp") + +add_library(ui STATIC + ${PRIVATE_SOURCES} +) + +target_include_directories(ui + PRIVATE + ${CMAKE_SOURCE_DIR}/src +) + +target_link_libraries(ui + PRIVATE + event + glue + gx + math + model + util + PUBLIC + common + storm + tempest +) diff --git a/src/ui/CRenderBatch.cpp b/src/ui/CRenderBatch.cpp new file mode 100644 index 0000000..9bb4460 --- /dev/null +++ b/src/ui/CRenderBatch.cpp @@ -0,0 +1,220 @@ +#include "ui/CRenderBatch.hpp" +#include "ui/CSimpleFontString.hpp" +#include "ui/CSimpleTexture.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Font.hpp" +#include "gx/Shader.hpp" +#include "gx/font/CGxStringBatch.hpp" +#include +#include + +int32_t SortByTexture(const void* a, const void* b) { + auto meshA = reinterpret_cast(a); + auto meshB = reinterpret_cast(b); + + if (meshA->textureID != meshB->textureID) { + return meshA->textureID - meshB->textureID; + } + + if (meshA->alphaMode != meshB->alphaMode) { + if ((meshA->alphaMode >= 2 || !meshA->color) && (meshB->alphaMode >= 2 || !meshB->color)) { + return meshA->alphaMode - meshB->alphaMode; + } else if (meshA->alphaMode >= 2 || !meshA->color) { + return meshA->alphaMode - 2; + } else if (meshB->alphaMode >= 2 || !meshB->color) { + return 2 - meshB->alphaMode; + } + } + + if (meshA->shader != meshB->shader) { + return meshA->shader - meshB->shader; + } + + return 0; +} + +EGxBlend CSimpleBatchedMesh::GetAlphaMode() { + if (this->color && this->alphaMode < GxBlend_Alpha) { + return GxBlend_Alpha; + } else { + return this->alphaMode; + } +} + +void CRenderBatch::Clear() { + this->m_texturelist.SetCount(0); + + if (this->m_stringbatch) { + GxuFontDestroyBatch(this->m_stringbatch); + this->m_stringbatch = nullptr; + } + + this->m_callbacks.DeleteAll(); + + this->m_count = 0; +} + +void CRenderBatch::Finish() { + if (this->m_texturelist.Count() > 1) { + qsort(this->m_texturelist.m_data, this->m_texturelist.Count(), sizeof(CSimpleBatchedMesh), SortByTexture); + } +} + +void CRenderBatch::Queue(CTexture* texture, EGxBlend alphaMode, int32_t posCount, const C3Vector* position, const C2Vector* texCoord, int32_t colorCount, const CImVector* color, int32_t idxCount, const uint16_t* indices, CGxShader* shader) { + CGxTex* textureID = TextureGetGxTex(texture, 1, nullptr); + + if (!textureID) { + return; + } + + CSimpleBatchedMesh* mesh = this->m_texturelist.New(); + + mesh->texture = texture; + mesh->textureID = textureID; + mesh->alphaMode = alphaMode; + mesh->shader = shader; + mesh->posCount = posCount; + mesh->position = const_cast(position); + mesh->texCoord = const_cast(texCoord); + mesh->color = const_cast(color); + mesh->colorCount = colorCount; + mesh->indices = const_cast(indices); + mesh->idxCount = idxCount; + + // TODO + // - implement atlas stuff + mesh->onAtlas = 0; + // mesh->onAtlas = TextureOnAtlas(texture); + // if (onAtlas) { + // TextureGetAtlasOffsetAndScale(mesh->texture, mesh->offset, mesh->scale); + // } + + this->m_count++; +} + +void CRenderBatch::QueueCallback(void (*callback)(void*), void* param) { + auto node = this->m_callbacks.NewNode(2, 0, 0); + node->callback = callback; + node->param = param; + + this->m_count++; +} + +void CRenderBatch::QueueFontString(CSimpleFontString* string) { + CRect rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (!string->GetRect(&rect)) { + return; + } + + if (rect.minX > 1.0f || rect.maxX < 0.0f || rect.minY > 1.0f || rect.maxY < 0.0f) { + return; + } + + if (!string->m_string) { + return; + } + + auto gxString = TextBlockGetStringPtr(string->m_string); + + if (gxString) { + if (!this->m_stringbatch) { + this->m_stringbatch = GxuFontCreateBatch(false, false); + STORM_ASSERT(this->m_stringbatch); + } + + GxuFontAddToBatch(this->m_stringbatch, gxString); + this->m_count++; + } +} + +void CRenderBatch::QueueTexture(CSimpleTexture* texture) { + CGxTex* gxTex = TextureGetGxTex(texture->m_texture, texture->m_nonBlocking ? 0 : 2, 0); + + if (!gxTex) { + // TODO + // this->OnDelayedLoad(); + + return; + } + + if (texture->IsResizePending()) { + texture->Resize(1); + } + + if (texture->m_updateTexCoord) { + C2Vector texCoord[4]; + texture->GetTexCoord(texCoord); + + CRect rect = { + texture->m_position[1].y, + texture->m_position[0].x, + texture->m_position[0].y, + texture->m_position[2].x + }; + + if (texture->m_horizTile) { + float width = gxTex->GetWidth(); + float ddcWidth = CoordinateGetAspectCompensation() * 1024.0f * (rect.maxX - rect.minX); + float ndcWidth = DDCToNDCWidth(ddcWidth); + + if (width && ndcWidth > 0.0f) { + if (texCoord[0].x != 0.0f) { + texCoord[0].x = ndcWidth / width; + } + + if (texCoord[1].x != 0.0f) { + texCoord[1].x = ndcWidth / width; + } + + if (texCoord[2].x != 0.0f) { + texCoord[2].x = ndcWidth / width; + } + + if (texCoord[3].x != 0.0f) { + texCoord[3].x = ndcWidth / width; + } + } + } + + if (texture->m_vertTile) { + float height = gxTex->GetHeight(); + float ddcHeight = CoordinateGetAspectCompensation() * 1024.0f * (rect.maxY - rect.minY); + float ndcHeight = DDCToNDCWidth(ddcHeight); + + if (height && ndcHeight > 0.0f) { + if (texCoord[0].y != 0.0f) { + texCoord[0].y = ndcHeight / height; + } + + if (texCoord[1].y != 0.0f) { + texCoord[1].y = ndcHeight / height; + } + + if (texCoord[2].y != 0.0f) { + texCoord[2].y = ndcHeight / height; + } + + if (texCoord[3].y != 0.0f) { + texCoord[3].y = ndcHeight / height; + } + } + } + + texture->m_updateTexCoord = 0; + texture->SetTexCoord(texCoord); + } + + this->Queue( + TextureGetTexturePtr(texture->m_texture), + texture->m_alphaMode, + 4, + texture->m_position, + texture->m_texCoord, + texture->m_colorCount > 1 ? 4 : 0, + texture->m_colorCount ? texture->m_color : nullptr, + 6, + CSimpleTexture::s_indices, + texture->m_shader + ); +} diff --git a/src/ui/CRenderBatch.hpp b/src/ui/CRenderBatch.hpp new file mode 100644 index 0000000..ee6ebea --- /dev/null +++ b/src/ui/CRenderBatch.hpp @@ -0,0 +1,63 @@ +#ifndef UI_C_RENDER_BATCH_HPP +#define UI_C_RENDER_BATCH_HPP + +#include "gx/Types.hpp" +#include +#include +#include +#include + +class CGxShader; +class CGxStringBatch; +class CGxTex; +class CSimpleFontString; +class CSimpleTexture; +class CTexture; + +class RENDERCALLBACKNODE : public TSLinkedNode { + public: + void (*callback)(void*); + void* param; +}; + +class CSimpleBatchedMesh { + public: + // Member variables + CTexture* texture; + CGxTex* textureID; + EGxBlend alphaMode; + CGxShader* shader; + int32_t posCount; + C3Vector* position; + C2Vector* texCoord; + CImVector* color; + int32_t colorCount; + uint16_t* indices; + int32_t idxCount; + int32_t onAtlas; + float atlasScale; + C2Vector atlasOffset; + + // Member functions + EGxBlend GetAlphaMode(void); +}; + +class CRenderBatch { + public: + // Member variables + uint32_t m_count = 0; + TSGrowableArray m_texturelist; + CGxStringBatch* m_stringbatch = nullptr; + TSList> m_callbacks; + TSLink renderLink; + + // Member functions + void Clear(void); + void Finish(void); + void Queue(CTexture*, EGxBlend, int32_t, const C3Vector*, const C2Vector*, int32_t, const CImVector*, int32_t, const uint16_t*, CGxShader*); + void QueueCallback(void (*)(void*), void*); + void QueueFontString(CSimpleFontString*); + void QueueTexture(CSimpleTexture*); +}; + +#endif diff --git a/src/ui/CScriptObject.cpp b/src/ui/CScriptObject.cpp new file mode 100644 index 0000000..7c5b699 --- /dev/null +++ b/src/ui/CScriptObject.cpp @@ -0,0 +1,131 @@ +#include "ui/CScriptObject.hpp" +#include "ui/CScriptObjectScript.hpp" +#include "util/CStatus.hpp" +#include "util/Lua.hpp" +#include +#include +#include + +int32_t CScriptObject::s_objectType; +const char* CScriptObject::s_objectTypeName = "Object"; + +void CScriptObject::RegisterScriptMethods(lua_State* L) { + FrameScript_Object::FillScriptMethodTable(L, ScriptObjectMethods, NUM_SCRIPT_OBJECT_SCRIPT_METHODS); +} + +CScriptObject* CScriptObject::GetScriptObjectByName(const char* name, int32_t type) { + lua_State* L = FrameScript_GetContext(); + + lua_pushstring(L, name); + lua_rawget(L, LUA_GLOBALSINDEX); + + if (lua_type(L, -1) == 5) { + lua_rawgeti(L, -1, 0); + auto v4 = static_cast(lua_touserdata(L, -1)); + lua_settop(L, -3); + + if (v4 && v4->IsA(type)) { + return v4; + } else { + return NULL; + } + } else { + lua_settop(L, -2); + return NULL; + } +} + +CScriptObject::~CScriptObject() { + if (this->m_name.GetString()) { + this->UnregisterScriptObject(this->m_name.GetString()); + this->m_name.Copy(nullptr); + } +} + +/** + * @brief Converts the value of the name="" attribute into a fully resolved name. + * @param source eg. $parentCategory from + */ +void CScriptObject::CreateName(const char* source, char* dest, uint32_t destsize) { + // If source is not $parent, use it as the name + uint32_t v5 = SStrLen("$parent"); + if (SStrCmpI(source, "$parent", v5)) { + SStrCopy(dest, source, destsize); + return; + } + + SStrCopy(dest, "", 0x7FFFFFFF); + + CScriptObject* parent = this->GetScriptObjectParent(); + + if (parent) { + const char* parentName; + + while (1) { + parentName = parent->GetName(); + + // Found a name + if (parentName && *parentName) { + break; + } + + // Didn't find a name, go up a level + parent = parent->GetScriptObjectParent(); + + // Out of levels to go up + if (!parent) { + goto LABEL_8; + } + } + + SStrCopy(dest, parentName, destsize); + } + + LABEL_8: + // Copy the part of the string after $parent into dest + int v8 = SStrLen("$parent"); + SStrPack(dest, &source[v8], destsize); +} + +char* CScriptObject::GetName() { + return this->m_name.m_str; +} + +void CScriptObject::PreLoadXML(XMLNode* node, CStatus* status) { + const char* name = node->GetAttributeByName("name"); + + if (name && *name) { + this->SetName(name); + } +} + +void CScriptObject::SetName(const char* name) { + // If name is already set, clear it out + if (this->m_name.m_str) { + this->UnregisterScriptObject(this->m_name.m_str); + this->m_name.Copy(0); + } + + // Set the name + if (name && *name) { + // Deal with $parent in name + char fullName[1024]; + this->CreateName(name, fullName, 1024); + + this->m_name.Copy(fullName); + + this->RegisterScriptObject(fullName); + } +} + +const char* CScriptObject::GetObjectTypeName() { + return CScriptObject::s_objectTypeName; +} + +bool CScriptObject::IsA(int32_t type) { + return type == CScriptObject::s_objectType; +} + +bool CScriptObject::IsA(const char* typeName) { + return SStrCmpI(typeName, CScriptObject::s_objectTypeName, 0x7FFFFFFFu) == 0; +} diff --git a/src/ui/CScriptObject.hpp b/src/ui/CScriptObject.hpp new file mode 100644 index 0000000..018a9e9 --- /dev/null +++ b/src/ui/CScriptObject.hpp @@ -0,0 +1,38 @@ +#ifndef UI_C_SCRIPT_OBJECT_HPP +#define UI_C_SCRIPT_OBJECT_HPP + +#include "ui/FrameScript_Object.hpp" +#include +#include + +class CStatus; +class XMLNode; + +class CScriptObject : public FrameScript_Object { + public: + // Static variables + static int32_t s_objectType; + static const char* s_objectTypeName; + + // Static functions + static void RegisterScriptMethods(lua_State*); + static CScriptObject* GetScriptObjectByName(const char*, int32_t); + + // Member variables + RCString m_name; + + // Virtual member functions + virtual ~CScriptObject(void); + virtual char* GetName(void); + virtual bool IsA(int32_t); + virtual CScriptObject* GetScriptObjectParent(void) = 0; + virtual bool IsA(const char*); + virtual const char* GetObjectTypeName(void); + + // Member functions + void CreateName(const char*, char*, uint32_t); + void PreLoadXML(XMLNode*, CStatus*); + void SetName(const char*); +}; + +#endif diff --git a/src/ui/CScriptObjectScript.cpp b/src/ui/CScriptObjectScript.cpp new file mode 100644 index 0000000..4974bdf --- /dev/null +++ b/src/ui/CScriptObjectScript.cpp @@ -0,0 +1,94 @@ +#include "ui/CScriptObjectScript.hpp" +#include "ui/CScriptObject.hpp" +#include "util/Lua.hpp" +#include + +int32_t CScriptObject_GetObjectType(lua_State* L) { + if (!CScriptObject::s_objectType) { + CScriptObject::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + CScriptObject* object = (CScriptObject*)FrameScript_GetObjectThis(L, CScriptObject::s_objectType); + + const char* type = object->GetObjectTypeName(); + + lua_pushstring(L, type); + + return 1; +} + +int32_t CScriptObject_IsObjectType(lua_State* L) { + if (!CScriptObject::s_objectType) { + CScriptObject::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + CScriptObject* object = (CScriptObject*)FrameScript_GetObjectThis(L, CScriptObject::s_objectType); + + if (!lua_isstring(L, 2)) { + const char* name = object->GetName(); + + if (!name) { + name = ""; + } + + luaL_error(L, "Usage: %s:IsObjectType(\"type\")", name); + return 0; + } + + const char* type = lua_tolstring(L, 2, 0); + + if (object->IsA(type)) { + lua_pushnumber(L, 1.0); + } else { + lua_pushnil(L); + } + + return 1; +} + +int32_t CScriptObject_GetName(lua_State* L) { + if (!CScriptObject::s_objectType) { + CScriptObject::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + CScriptObject* object = (CScriptObject*)FrameScript_GetObjectThis(L, CScriptObject::s_objectType); + + char* name = object->GetName(); + + if (name && *name) { + lua_pushstring(L, name); + } else { + lua_pushnil(L); + } + + return 1; +} + +int32_t CScriptObject_GetParent(lua_State* L) { + if (!CScriptObject::s_objectType) { + CScriptObject::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + CScriptObject* object = (CScriptObject*)FrameScript_GetObjectThis(L, CScriptObject::s_objectType); + + CScriptObject* parent = object->GetScriptObjectParent(); + + if (parent) { + if (!parent->lua_registered) { + parent->RegisterScriptObject(0); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, parent->lua_objectRef); + } else { + lua_pushnil(L); + } + + return 1; +} + +FrameScript_Method ScriptObjectMethods[NUM_SCRIPT_OBJECT_SCRIPT_METHODS] = { + { "GetObjectType", &CScriptObject_GetObjectType }, + { "IsObjectType", &CScriptObject_IsObjectType }, + { "GetName", &CScriptObject_GetName }, + { "GetParent", &CScriptObject_GetParent } +}; diff --git a/src/ui/CScriptObjectScript.hpp b/src/ui/CScriptObjectScript.hpp new file mode 100644 index 0000000..de26e42 --- /dev/null +++ b/src/ui/CScriptObjectScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SCRIPT_OBJECT_SCRIPT_HPP +#define UI_C_SCRIPT_OBJECT_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SCRIPT_OBJECT_SCRIPT_METHODS 4 + +extern FrameScript_Method ScriptObjectMethods[NUM_SCRIPT_OBJECT_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CScriptRegion.cpp b/src/ui/CScriptRegion.cpp new file mode 100644 index 0000000..6fe36be --- /dev/null +++ b/src/ui/CScriptRegion.cpp @@ -0,0 +1,122 @@ +#include "ui/CScriptRegion.hpp" +#include "ui/CScriptObject.hpp" +#include "ui/CScriptRegionScript.hpp" +#include "ui/CSimpleTop.hpp" +#include "util/Lua.hpp" +#include + +int32_t CScriptRegion::s_objectType; +const char* CScriptRegion::s_objectTypeName = "Region"; + +int32_t CScriptRegion::GetObjectType() { + if (!CScriptRegion::s_objectType) { + CScriptRegion::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CScriptRegion::s_objectType; +} + +void CScriptRegion::RegisterScriptMethods(lua_State* L) { + CScriptObject::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, ScriptRegionMethods, NUM_SCRIPT_REGION_SCRIPT_METHODS); +} + +// TODO verify return type +CLayoutFrame* CScriptRegion::GetLayoutParent() { + if (!this->m_parent || this->m_parent->m_layoutScale == 0.0f) { + return CSimpleTop::s_instance; + } else { + return (CLayoutFrame*)this->m_parent; + } +} + +CLayoutFrame* CScriptRegion::GetLayoutFrameByName(const char* name) { + char fullName[1024]; + this->CreateName(name, fullName, 1024); + + int32_t type = CScriptRegion::GetObjectType(); + CScriptRegion* object = static_cast(this->GetScriptObjectByName(fullName, type)); + + return static_cast(object); +} + +const char* CScriptRegion::GetObjectTypeName() { + return CScriptRegion::s_objectTypeName; +} + +// TODO verify return type +CScriptObject* CScriptRegion::GetScriptObjectParent() { + return (CScriptObject*)(this->m_parent); +} + +bool CScriptRegion::IsA(int32_t type) { + return type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +bool CScriptRegion::IsA(const char* typeName) { + return !SStrCmpI(typeName, CScriptRegion::s_objectTypeName, 0x7FFFFFFF) + || !SStrCmpI(typeName, CScriptObject::s_objectTypeName, 0x7FFFFFFF); +} + +bool CScriptRegion::IsDragging() { + // TODO + return false; +} + +void CScriptRegion::LoadXML(XMLNode* node, CStatus* status) { + CLayoutFrame::LoadXML(node, status); + + const char* parentKey = node->GetAttributeByName("parentKey"); + + if (parentKey && *parentKey) { + lua_State* L = FrameScript_GetContext(); + + CScriptObject* parent = this->GetScriptObjectParent(); + + if (parent) { + if (!parent->lua_registered) { + parent->RegisterScriptObject(0); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, parent->lua_objectRef); + lua_pushstring(L, parentKey); + + if (!this->lua_registered) { + this->RegisterScriptObject(0); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, this->lua_objectRef); + lua_settable(L, -3); + lua_settop(L, -2); + } + } + + this->LoadXML_Animations(node, status); +} + +void CScriptRegion::LoadXML_Animations(XMLNode* node, CStatus* status) { + // TODO +} + +void CScriptRegion::NotifyAnimBegin(CSimpleAnimGroup* animGroup) { + // TODO +} + +void CScriptRegion::NotifyAnimEnd(CSimpleAnimGroup* animGroup) { + // TODO +} + +void CScriptRegion::OnLayerUpdate(float elapsedSec) { + // TODO +} + +bool CScriptRegion::ProtectedFunctionsAllowed() { + // TODO + + return true; +} + +void CScriptRegion::StopAnimating() { + // TODO +} diff --git a/src/ui/CScriptRegion.hpp b/src/ui/CScriptRegion.hpp new file mode 100644 index 0000000..ff75ee9 --- /dev/null +++ b/src/ui/CScriptRegion.hpp @@ -0,0 +1,52 @@ +#ifndef UI_C_SCRIPT_REGION_HPP +#define UI_C_SCRIPT_REGION_HPP + +#include "ui/CLayoutFrame.hpp" +#include "ui/CScriptObject.hpp" + +class C2Vector; +class CSimpleAnimGroup; +class CSimpleFrame; +class XMLNode; +struct lua_State; + +class CScriptRegion : public CScriptObject, public CLayoutFrame { + public: + // Static members + static int32_t s_objectType; + static const char* s_objectTypeName; + + // Static functions + static void RegisterScriptMethods(lua_State*); + static int32_t GetObjectType(void); + + // Member variables + CSimpleFrame* m_parent = NULL; // TODO verify type + + // Virtual member functions + virtual bool IsA(int32_t); + virtual CScriptObject* GetScriptObjectParent(void); + virtual bool IsA(const char*); + virtual const char* GetObjectTypeName(void); + virtual bool IsDragging(void); + virtual void PreOnAnimUpdate(void) {}; + virtual void OnLayerUpdate(float); + virtual void NotifyAnimBegin(CSimpleAnimGroup*); + virtual void NotifyAnimEnd(CSimpleAnimGroup*); + virtual void StopAnimating(void); + virtual void AnimActivated(CSimpleAnimGroup*, int32_t, int32_t) {}; + virtual void AnimDeactivated(CSimpleAnimGroup*, int32_t, int32_t) {}; + virtual void AddAnimTranslation(CScriptRegion*, const C2Vector&) {}; + virtual void AddAnimRotation(CScriptRegion*, FRAMEPOINT, const C2Vector&, float) {}; + virtual void AddAnimScale(CScriptRegion*, FRAMEPOINT, const C2Vector&, const C2Vector&) {}; + virtual void AddAnimAlpha(CScriptRegion*, int16_t) {}; + virtual void LoadXML(XMLNode*, CStatus*); + virtual CLayoutFrame* GetLayoutParent(void); + virtual CLayoutFrame* GetLayoutFrameByName(const char*); + + // Member functions + void LoadXML_Animations(XMLNode*, CStatus*); + bool ProtectedFunctionsAllowed(void); +}; + +#endif diff --git a/src/ui/CScriptRegionScript.cpp b/src/ui/CScriptRegionScript.cpp new file mode 100644 index 0000000..4d74f7a --- /dev/null +++ b/src/ui/CScriptRegionScript.cpp @@ -0,0 +1,387 @@ +#include "ui/CScriptRegionScript.hpp" +#include "gx/Coordinate.hpp" +#include "ui/FrameScript_Object.hpp" +#include "ui/CScriptRegion.hpp" +#include "ui/CSimpleTop.hpp" +#include "util/Lua.hpp" +#include "util/StringTo.hpp" +#include "util/Unimplemented.hpp" +#include +#include + +int32_t CScriptRegion_IsProtected(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_CanChangeProtectedState(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_SetParent(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetRect(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetCenter(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + if (region->IsResizePending()) { + region->Resize(1); + } + + CRect rect; + + if (region->GetRect(&rect)) { + float width = rect.maxX - rect.minX; + float v5 = CoordinateGetAspectCompensation() * 1024.0f * ((width * 0.5f + rect.minX) / region->m_layoutScale); + float v6 = DDCToNDCWidth(v5); + lua_pushnumber(L, v6); + + float height = rect.maxY - rect.minY; + float v7 = CoordinateGetAspectCompensation() * 1024.0f * ((height * 0.5f + rect.minY) / region->m_layoutScale); + float v8 = DDCToNDCWidth(v7); + lua_pushnumber(L, v8); + } else { + lua_pushnil(L); + lua_pushnil(L); + } + + return 2; +} + +int32_t CScriptRegion_GetLeft(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetRight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetTop(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetBottom(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetWidth(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + float width = region->GetWidth(); + + if (width == 0.0 && !StringToBOOL(L, 2, 0)) { + if (region->IsResizePending()) { + region->Resize(1); + } + + CRect rect = { 0.0, 0.0, 0.0, 0.0 }; + + if (region->GetRect(&rect)) { + width = (rect.maxX - rect.minX) / region->m_layoutScale; + } + } + + float ddcWidth = CoordinateGetAspectCompensation() * 1024.0 * width; + float ndcWidth = DDCToNDCWidth(ddcWidth); + lua_pushnumber(L, ndcWidth); + + return 1; +} + +int32_t CScriptRegion_SetWidth(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!region->ProtectedFunctionsAllowed()) { + // TODO + // - disallowed logic + + return 0; + } + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "Usage: %s:SetWidth(width)", region->GetDisplayName()); + } + + float width = lua_tonumber(L, 2); + float ndcWidth = width / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcWidth = NDCToDDCWidth(ndcWidth); + + region->SetWidth(ddcWidth); + + return 0; +} + +int32_t CScriptRegion_GetHeight(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + float height = region->GetHeight(); + + if (height == 0.0f && !StringToBOOL(L, 2, 0)) { + if (region->IsResizePending()) { + region->Resize(1); + } + + CRect rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (region->GetRect(&rect)) { + height = (rect.maxY - rect.minY) / region->m_layoutScale; + } + } + + float ddcHeight = CoordinateGetAspectCompensation() * 1024.0f * height; + float ndcHeight = DDCToNDCWidth(ddcHeight); + lua_pushnumber(L, ndcHeight); + + return 1; +} + +int32_t CScriptRegion_SetHeight(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!region->ProtectedFunctionsAllowed()) { + // TODO + // - disallowed logic + + return 0; + } + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "Usage: %s:SetHeight(height)", region->GetDisplayName()); + } + + float height = lua_tonumber(L, 2); + float ndcHeight = height / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcHeight = NDCToDDCWidth(ndcHeight); + + region->SetHeight(ddcHeight); + + return 0; +} + +int32_t CScriptRegion_SetSize(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetSize(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetNumPoints(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetPoint(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_SetPoint(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!region->ProtectedFunctionsAllowed()) { + // TODO + // - disallowed logic + + return 0; + } + + if (!lua_isstring(L, 2)) { + return luaL_error(L, "Usage: %s:SetPoint(\"point\" [, region or nil] [, \"relativePoint\"] [, offsetX, offsetY])", region->GetDisplayName()); + } + + auto relative = region->GetLayoutParent(); + + const char* pointStr = lua_tolstring(L, 2, 0); + FRAMEPOINT point; + + if (!StringToFramePoint(pointStr, point)) { + return luaL_error(L, "%s:SetPoint(): Unknown region point", region->GetDisplayName()); + } + + int32_t argsIndex = 3; + + if (lua_type(L, 3) == LUA_TSTRING) { + const char* name = lua_tolstring(L, 3, 0); + relative = region->GetLayoutFrameByName(name); + + argsIndex++; + } else if (lua_type(L, 3) == LUA_TTABLE) { + lua_rawgeti(L, 3, 0); + + auto r = reinterpret_cast(lua_touserdata(L, -1)); + relative = r ? static_cast(r) : nullptr; + + lua_settop(L, -2); + + argsIndex++; + } else if (lua_type(L, 3) == LUA_TNIL) { + relative = CSimpleTop::s_instance; + + argsIndex++; + } + + if (!relative) { + const char* name = lua_tolstring(L, 3, 0); + return luaL_error(L, "%s:SetPoint(): Couldn't find region named '%s'", region->GetDisplayName(), name); + } + + if (relative == region) { + return luaL_error(L, "%s:SetPoint(): trying to anchor to itself", region->GetDisplayName()); + } + + if (relative->IsResizeDependency(region)) { + return luaL_error(L, "%s:SetPoint(): %s is dependent on this", region->GetDisplayName(), static_cast(relative)->GetDisplayName()); + } + + FRAMEPOINT relativePoint = point; + + if (lua_type(L, argsIndex) == LUA_TSTRING) { + const char* relativePointStr = lua_tolstring(L, argsIndex, 0); + + if (!StringToFramePoint(relativePointStr, relativePoint)) { + return luaL_error(L, "%s:SetPoint(): Unknown region point", region->GetDisplayName()); + } + + argsIndex++; + } + + float offsetX = 0.0f; + float offsetY = 0.0f; + + if (lua_isnumber(L, argsIndex) && lua_isnumber(L, argsIndex + 1)) { + float x = lua_tonumber(L, argsIndex); + float ndcX = x / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcX = NDCToDDCWidth(ndcX); + + float y = lua_tonumber(L, argsIndex + 1); + float ndcY = y / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcY = NDCToDDCWidth(ndcY); + + offsetX = ddcX; + offsetY = ddcY; + } + + region->SetPoint(point, relative, relativePoint, offsetX, offsetY, 1); + + return 0; +} + +int32_t CScriptRegion_SetAllPoints(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!region->ProtectedFunctionsAllowed()) { + // TODO + // - disallowed logic + + return 0; + } + + auto relative = region->GetLayoutParent(); + + if (lua_isstring(L, 2)) { + const char* name = lua_tolstring(L, 2, 0); + relative = region->GetLayoutFrameByName(name); + } else if (lua_type(L, 2) == LUA_TTABLE) { + lua_rawgeti(L, 2, 0); + + auto r = reinterpret_cast(lua_touserdata(L, -1)); + relative = r ? static_cast(r) : nullptr; + + lua_settop(L, -2); + } else if (lua_type(L, 2) == LUA_TNIL) { + relative = CSimpleTop::s_instance; + } + + if (!relative) { + const char* name = lua_tolstring(L, 2, 0); + return luaL_error(L, "%s:SetAllPoints(): Couldn't find region named '%s'", region->GetDisplayName(), name); + } + + if (relative == region) { + return luaL_error(L, "%s:SetAllPoints(): trying to anchor to itself", region->GetDisplayName()); + } + + if (relative->IsResizeDependency(region)) { + return luaL_error(L, "%s:SetAllPoints(): %s is dependent on this", region->GetDisplayName(), static_cast(relative)->GetDisplayName()); + } + + region->SetAllPoints(relative, 1); + + return 0; +} + +int32_t CScriptRegion_ClearAllPoints(lua_State* L) { + int32_t type = CScriptRegion::GetObjectType(); + auto region = static_cast(FrameScript_GetObjectThis(L, type)); + + if (region->ProtectedFunctionsAllowed()) { + region->ClearAllPoints(); + } else { + // TODO + // void* v3 = CSimpleTop::s_instance->Function4692; + + // if (v3) { + // v3(object); + // } + } + + return 0; +} + +int32_t CScriptRegion_CreateAnimationGroup(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_GetAnimationGroups(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_StopAnimating(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_IsDragging(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CScriptRegion_IsMouseOver(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method ScriptRegionMethods[NUM_SCRIPT_REGION_SCRIPT_METHODS] = { + { "IsProtected", &CScriptRegion_IsProtected }, + { "CanChangeProtectedState", &CScriptRegion_CanChangeProtectedState }, + { "SetParent", &CScriptRegion_SetParent }, + { "GetRect", &CScriptRegion_GetRect }, + { "GetCenter", &CScriptRegion_GetCenter }, + { "GetLeft", &CScriptRegion_GetLeft }, + { "GetRight", &CScriptRegion_GetRight }, + { "GetTop", &CScriptRegion_GetTop }, + { "GetBottom", &CScriptRegion_GetBottom }, + { "GetWidth", &CScriptRegion_GetWidth }, + { "SetWidth", &CScriptRegion_SetWidth }, + { "GetHeight", &CScriptRegion_GetHeight }, + { "SetHeight", &CScriptRegion_SetHeight }, + { "SetSize", &CScriptRegion_SetSize }, + { "GetSize", &CScriptRegion_GetSize }, + { "GetNumPoints", &CScriptRegion_GetNumPoints }, + { "GetPoint", &CScriptRegion_GetPoint }, + { "SetPoint", &CScriptRegion_SetPoint }, + { "SetAllPoints", &CScriptRegion_SetAllPoints }, + { "ClearAllPoints", &CScriptRegion_ClearAllPoints }, + { "CreateAnimationGroup", &CScriptRegion_CreateAnimationGroup }, + { "GetAnimationGroups", &CScriptRegion_GetAnimationGroups }, + { "StopAnimating", &CScriptRegion_StopAnimating }, + { "IsDragging", &CScriptRegion_IsDragging }, + { "IsMouseOver", &CScriptRegion_IsMouseOver } +}; diff --git a/src/ui/CScriptRegionScript.hpp b/src/ui/CScriptRegionScript.hpp new file mode 100644 index 0000000..005a83f --- /dev/null +++ b/src/ui/CScriptRegionScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SCRIPT_REGION_SCRIPT_HPP +#define UI_C_SCRIPT_REGION_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SCRIPT_REGION_SCRIPT_METHODS 25 + +extern FrameScript_Method ScriptRegionMethods[NUM_SCRIPT_REGION_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleButton.cpp b/src/ui/CSimpleButton.cpp new file mode 100644 index 0000000..527e1a1 --- /dev/null +++ b/src/ui/CSimpleButton.cpp @@ -0,0 +1,617 @@ +#include "ui/CSimpleButton.hpp" +#include "event/Input.hpp" +#include "ui/CSimpleButtonScript.hpp" +#include "ui/CSimpleFont.hpp" +#include "ui/CSimpleFontString.hpp" +#include "ui/CSimpleTexture.hpp" +#include "ui/CSimpleTop.hpp" +#include "ui/LoadXML.hpp" +#include "util/CStatus.hpp" +#include "util/Lua.hpp" +#include +#include +#include + +int32_t CSimpleButton::s_metatable; +int32_t CSimpleButton::s_objectType; + +void CSimpleButton::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleButton::RegisterScriptMethods); + CSimpleButton::s_metatable = ref; +} + +int32_t CSimpleButton::GetObjectType() { + if (!CSimpleButton::s_objectType) { + CSimpleButton::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleButton::s_objectType; +} + +void CSimpleButton::RegisterScriptMethods(lua_State* L) { + CSimpleFrame::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleButtonMethods, NUM_SIMPLE_BUTTON_SCRIPT_METHODS); +} + +CSimpleButton::CSimpleButton(CSimpleFrame* parent) : CSimpleFrame(parent) { + this->m_pressedOffset = { 0.001, -0.001 }; + + this->m_textures[BUTTONSTATE_DISABLED] = nullptr; + this->m_textures[BUTTONSTATE_NORMAL] = nullptr; + this->m_textures[BUTTONSTATE_PUSHED] = nullptr; + + this->Enable(1); + this->EnableEvent(SIMPLE_EVENT_MOUSE, -1); + this->SetFrameFlag(0x10000, 1); +} + +void CSimpleButton::Enable(int32_t enabled) { + if (enabled) { + if (this->m_state != BUTTONSTATE_DISABLED) { + return; + } + + this->SetButtonState(BUTTONSTATE_NORMAL, 0); + + if (this == this->m_top->m_mouseFocus) { + this->OnLayerCursorEnter(0); + } + + if (this->m_highlightLocked) { + this->EnableDrawLayer(DRAWLAYER_HIGHLIGHT); + } + } else { + if (this->m_state == BUTTONSTATE_DISABLED) { + return; + } + + if (this == this->m_top->m_mouseFocus) { + this->OnLayerCursorExit(0, 1); + } + + this->DisableDrawLayer(DRAWLAYER_HIGHLIGHT); + this->SetButtonState(BUTTONSTATE_DISABLED, 0); + } + + this->SetFrameFlag(0x400, enabled == 0); + + auto& script = enabled ? this->m_onEnable : this->m_onDisable; + + if (script.luaRef && !this->m_loading) { + this->RunScript(script, 0, nullptr); + } +} + +FrameScript_Object::ScriptIx* CSimpleButton::GetScriptByName(const char* name, ScriptData& data) { + auto parentScript = CSimpleFrame::GetScriptByName(name, data); + + if (parentScript) { + return parentScript; + } + + if (!SStrCmpI(name, "PreClick", STORM_MAX_STR)) { + data.wrapper = "return function(self,button,down) %s end"; + return &this->m_preClick; + } + + if (!SStrCmpI(name, "OnClick", STORM_MAX_STR)) { + data.wrapper = "return function(self,button,down) %s end"; + return &this->m_onClick; + } + + if (!SStrCmpI(name, "PostClick", STORM_MAX_STR)) { + data.wrapper = "return function(self,button,down) %s end"; + return &this->m_postClick; + } + + if (!SStrCmpI(name, "OnDoubleClick", STORM_MAX_STR)) { + data.wrapper = "return function(self,button) %s end"; + return &this->m_onDoubleClick; + } + + return nullptr; +} + +int32_t CSimpleButton::GetScriptMetaTable() { + return CSimpleButton::s_metatable; +} + +bool CSimpleButton::IsA(int32_t type) { + return type == CSimpleButton::s_objectType + || type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleButton::LoadXML(XMLNode* node, CStatus* status) { + CSimpleFrame::LoadXML(node, status); + + CSimpleFontString* buttonText = nullptr; + + for (XMLNode* child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "NormalTexture", STORM_MAX_STR)) { + CSimpleTexture* texture = LoadXML_Texture(child, this, status); + this->SetStateTexture(BUTTONSTATE_NORMAL, texture); + + } else if (!SStrCmpI(child->GetName(), "PushedTexture", STORM_MAX_STR)) { + CSimpleTexture* texture = LoadXML_Texture(child, this, status); + this->SetStateTexture(BUTTONSTATE_PUSHED, texture); + + } else if (!SStrCmpI(child->GetName(), "DisabledTexture", STORM_MAX_STR)) { + CSimpleTexture* texture = LoadXML_Texture(child, this, status); + this->SetStateTexture(BUTTONSTATE_DISABLED, texture); + + } else if (!SStrCmpI(child->GetName(), "HighlightTexture", STORM_MAX_STR)) { + CSimpleTexture* texture = LoadXML_Texture(child, this, status); + this->SetHighlight(texture, texture->m_alphaMode); + + } else if (!SStrCmpI(child->GetName(), "ButtonText", STORM_MAX_STR)) { + buttonText = LoadXML_String(child, this, status); + + } else if (!SStrCmpI(child->GetName(), "NormalFont", STORM_MAX_STR)) { + const char* fontName = child->GetAttributeByName("style"); + + if (fontName && *fontName) { + CSimpleFont* font = CSimpleFont::GetFont(fontName, 0); + + this->m_normalFont = font; + + if (!font) { + status->Add(STATUS_WARNING, "Couldn't find NORMAL font: %s", fontName); + } + } + + } else if (!SStrCmpI(child->GetName(), "DisabledFont", STORM_MAX_STR)) { + const char* fontName = child->GetAttributeByName("style"); + + if (fontName && *fontName) { + CSimpleFont* font = CSimpleFont::GetFont(fontName, 0); + + this->m_disabledFont = font; + + if (!font) { + status->Add(STATUS_WARNING, "Couldn't find DISABLED font: %s", fontName); + } + } + + } else if (!SStrCmpI(child->GetName(), "HighlightFont", STORM_MAX_STR)) { + const char* fontName = child->GetAttributeByName("style"); + + if (fontName && *fontName) { + CSimpleFont* font = CSimpleFont::GetFont(fontName, 0); + + this->m_highlightFont = font; + + if (!font) { + status->Add(STATUS_WARNING, "Couldn't find HIGHLIGHT font: %s", fontName); + } + } + + } else if (!SStrCmpI(child->GetName(), "NormalColor", STORM_MAX_STR)) { + // TODO + + } else if (!SStrCmpI(child->GetName(), "DisabledColor", STORM_MAX_STR)) { + // TODO + + } else if (!SStrCmpI(child->GetName(), "HighlightColor", STORM_MAX_STR)) { + // TODO + + } else if (!SStrCmpI(child->GetName(), "PushedTextOffset", STORM_MAX_STR)) { + float x, y; + + if (LoadXML_Dimensions(child, x, y, status)) { + C2Vector offset = { x, y }; + this->SetPressedOffset(offset); + } + } + } + + if (buttonText) { + this->SetFontString(buttonText); + } else { + // TODO + // int32_t v24 = (this + 162) << 28 >> 28; + // this->UpdateTextState(v24); + } + + const char* textRef = node->GetAttributeByName("text"); + + if (textRef && *textRef) { + const char* text = FrameScript_GetText(textRef, -1, GENDER_NOT_APPLICABLE); + + if (!text || !*text) { + text = textRef; + } + + this->SetText(text); + } + + // TODO + // - registerForClicks + + // TODO + // - motionScriptsWhileDisabled +} + +void CSimpleButton::LockHighlight(int32_t lock) { + CSimpleFrame::LockHighlight(lock); + this->UpdateTextState(this->m_state); +} + +void CSimpleButton::OnClick(const char* btn, int32_t a3) { + // TODO + // - also check this->m_unk != 1 + if (this->m_state != BUTTONSTATE_DISABLED) { + // TODO + // const char* old1234 = this->m_top->m_char1234; + // this->m_top->m_char1234 = btn; + + // TODO + // this->m_unk = 1; + + this->RunOnClickScript(btn, a3); + + // TODO + // this->m_unk = 0; + + // TODO + // this->m_top->m_char1234 = old1234; + } +} + +void CSimpleButton::OnDoubleClick(const char* btn) { + // TODO +} + +void CSimpleButton::OnLayerCursorEnter(int32_t a2) { + if (this->m_state == BUTTONSTATE_DISABLED) { + // TODO + // if (this->GetMotionScriptsWhileDisabled()) { + // if (this->m_tooltip) { + // this->m_tooltip->Show(); + // } + // + // this->RunOnEnterScript(a2); + // } + } else { + CSimpleFrame::OnLayerCursorEnter(a2); + this->UpdateTextState(this->m_state); + } +} + +void CSimpleButton::OnLayerCursorExit(int32_t a2, int32_t a3) { + if (this->m_state == BUTTONSTATE_DISABLED) { + // TODO + // if (this->GetMotionScriptsWhileDisabled()) { + // if (this->m_tooltip) { + // this->m_tooltip->Hide(); + // } + // + // this->RunOnLeaveScript(a2); + // } + } else { + CSimpleFrame::OnLayerCursorExit(a2, a3); + this->UpdateTextState(this->m_state); + } +} + +void CSimpleButton::OnLayerHide() { + if (this->m_state && !this->m_stateLocked) { + this->SetButtonState(BUTTONSTATE_NORMAL, 0); + } + + CSimpleFrame::OnLayerHide(); +} + +int32_t CSimpleButton::OnLayerMouseDown(const CMouseEvent& evt, const char* btn) { + if (CSimpleFrame::OnLayerMouseDown(evt, btn)) { + return 1; + } + + C2Vector pt = { evt.x, evt.y }; + + if ((btn || this->TestHitRect(pt)) && this->m_state != BUTTONSTATE_DISABLED) { + int32_t v7 = btn ? 0x7FFFFFFF : evt.button; + + if (this->m_clickAction & v7) { + if (!btn) { + btn = GetButtonName(evt.button); + } + + // TODO + // this->m_top->m_int1241 = 1; + + this->OnClick(btn, 1); + + // TODO + // this->m_top->m_int1241 = 0; + } + + if (!this->m_stateLocked && this->m_state != BUTTONSTATE_DISABLED) { + this->SetButtonState(BUTTONSTATE_PUSHED, 0); + } + + return 1; + } + + return 0; +} + +int32_t CSimpleButton::OnLayerMouseUp(const CMouseEvent& evt, const char* btn) { + if (CSimpleFrame::OnLayerMouseUp(evt, btn)) { + return 1; + } + + if (this->m_state != BUTTONSTATE_PUSHED) { + return 1; + } + + C2Vector pt = { evt.x, evt.y }; + + if (btn || this->TestHitRect(pt)) { + uint64_t v6 = btn ? 0xFFFFFFFF80000000 : static_cast(evt.button) << 31; + + if (this->m_clickAction & v6) { + if (!btn) { + btn = GetButtonName(evt.button); + } + + // TODO + // this->m_top->m_int1241 = 1; + + uint32_t currentTime = OsGetAsyncTimeMs(); + + if (this->m_onDoubleClick.luaRef && this->m_doubleClickTime && currentTime - this->m_doubleClickTime <= 300) { + this->OnDoubleClick(btn); + this->m_doubleClickTime = 0; + } else { + this->OnClick(btn, 0); + this->m_doubleClickTime = currentTime; + } + + // TODO + // this->m_top->m_int1241 = 0; + } + } + + if (!this->m_stateLocked && this->m_state != BUTTONSTATE_DISABLED) { + this->SetButtonState(BUTTONSTATE_NORMAL, 0); + } + + return 1; +} + +void CSimpleButton::RunOnClickScript(const char* btn, int32_t down) { + if (this->m_preClick.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, btn); + lua_pushboolean(L, down); + + this->RunScript(this->m_preClick, 2, 0); + } + + if (this->m_onClick.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, btn); + lua_pushboolean(L, down); + + this->RunScript(this->m_onClick, 2, 0); + } + + if (this->m_postClick.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, btn); + lua_pushboolean(L, down); + + this->RunScript(this->m_postClick, 2, 0); + } +} + +void CSimpleButton::SetButtonState(CSimpleButtonState state, int32_t stateLocked) { + this->m_stateLocked = stateLocked; + + if (state == this->m_state) { + return; + } + + if (this->m_activeTexture && (this->m_textures[state] || state == BUTTONSTATE_NORMAL)) { + this->m_activeTexture->Hide(); + this->m_activeTexture = nullptr; + } + + if (this->m_textures[state]) { + this->m_activeTexture = this->m_textures[state]; + this->m_activeTexture->Show(); + } + + this->UpdateTextState(state); + + this->m_state = state; +} + +void CSimpleButton::SetClickAction(uint64_t action) { + this->m_clickAction = action; +} + +void CSimpleButton::SetHighlight(CSimpleTexture* texture, EGxBlend blend) { + if (texture == this->m_highlightTexture) { + return; + } + + if (this->m_highlightTexture) { + delete this->m_highlightTexture; + } + + if (texture) { + texture->SetFrame(this, DRAWLAYER_HIGHLIGHT, 1); + texture->SetBlendMode(blend); + } + + this->m_highlightTexture = texture; +} + +int32_t CSimpleButton::SetHighlight(const char* texFile, EGxBlend blendMode) { + if (this->m_highlightTexture) { + this->m_highlightTexture->SetTexture(texFile, false, false, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + this->m_highlightTexture->SetBlendMode(blendMode); + + return 1; + } + + // TODO void* m = CDataAllocator::GetData(CSimpleTexture::s_allocator, 0, __FILE__, __LINE__); + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + auto texture = new (m) CSimpleTexture(this, DRAWLAYER_HIGHLIGHT, 1); + + if (!texture->SetTexture(texFile, false, false, CSimpleTexture::s_textureFilterMode, ImageMode_UI)) { + delete texture; + + return 0; + } + + texture->SetAllPoints(this, 1); + texture->SetBlendMode(blendMode); + this->m_highlightTexture = texture; + + return 1; +} + +void CSimpleButton::SetFontString(CSimpleFontString* text) { + if (this->m_text == text) { + return; + } + + if (this->m_text) { + delete this->m_text; + } + + if (text) { + text->SetFrame(this, 2, 1); + } + + if (!text->Sub482AC0()) { + auto font = this->m_normalFont; + + int32_t styleFlags; + + if (font) { + styleFlags = font->m_attributes.m_styleFlags; + } else { + styleFlags = 0; + } + + float offsetX = 0.0f; + float offsetY = 0.0f; + + if (styleFlags & 0x1) { + text->SetPoint(FRAMEPOINT_LEFT, this, FRAMEPOINT_LEFT, offsetX, offsetY, 1); + } else if (styleFlags & 0x4) { + text->SetPoint(FRAMEPOINT_RIGHT, this, FRAMEPOINT_RIGHT, offsetX, offsetY, 1); + } else { + text->SetPoint(FRAMEPOINT_CENTER, this, FRAMEPOINT_CENTER, offsetX, offsetY, 1); + } + } + + this->m_text = text; + + this->UpdateTextState(this->m_state); +} + +void CSimpleButton::SetPressedOffset(C2Vector& offset) { + this->m_pressedOffset = offset; +} + +void CSimpleButton::SetStateTexture(CSimpleButtonState state, CSimpleTexture* texture) { + if (this->m_textures[state] == texture) { + return; + } + + if (this->m_textures[state] == this->m_activeTexture) { + this->m_activeTexture = nullptr; + } + + if (this->m_textures[state]) { + delete this->m_textures[state]; + } + + if (texture) { + texture->SetFrame(this, DRAWLAYER_ARTWORK, 0); + } + + this->m_textures[state] = texture; + + if (texture) { + if (state == this->m_state) { + this->m_activeTexture = texture; + texture->Show(); + } + } +} + +int32_t CSimpleButton::SetStateTexture(CSimpleButtonState state, const char* texFile) { + if (this->m_textures[state]) { + this->m_textures[state]->SetTexture(texFile, false, false, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + + return 1; + } + + // TODO void* m = CDataAllocator::GetData(CSimpleTexture::s_allocator, 0, __FILE__, __LINE__); + void* m = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0x0); + auto texture = new (m) CSimpleTexture(nullptr, 2, 1); + + if (texture->SetTexture(texFile, false, false, CSimpleTexture::s_textureFilterMode, ImageMode_UI)) { + texture->SetAllPoints(this, 1); + this->SetStateTexture(state, texture); + + return 1; + } + + if (texture) { + delete texture; + } + + return 0; +} + +void CSimpleButton::SetText(const char* string) { + if ((string && *string) || this->m_text) { + if (!this->m_text) { + // TODO + // void* m = CDataAllocator::GetData(CSimpleFontString::s_allocator, 0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleFontString), __FILE__, __LINE__, 0x0); + + CSimpleFontString* text; + + if (m) { + text = new (m) CSimpleFontString(this, 2, 1); + } else { + text = nullptr; + } + + this->SetFontString(text); + } + + this->m_text->SetText(string, 1); + } +} + +void CSimpleButton::UpdateTextState(CSimpleButtonState state) { + if (!this->m_text) { + return; + } + + if (state == BUTTONSTATE_PUSHED) { + this->m_text->SetJustificationOffset(this->m_pressedOffset.x, this->m_pressedOffset.y); + } else { + this->m_text->SetJustificationOffset(0.0f, 0.0f); + } + + if (state == BUTTONSTATE_DISABLED) { + this->m_text->SetFontObject(this->m_disabledFont); + } else if (this->m_drawenabled[DRAWLAYER_HIGHLIGHT]) { + this->m_text->SetFontObject(this->m_highlightFont); + } else { + this->m_text->SetFontObject(this->m_normalFont); + } +} diff --git a/src/ui/CSimpleButton.hpp b/src/ui/CSimpleButton.hpp new file mode 100644 index 0000000..083c80e --- /dev/null +++ b/src/ui/CSimpleButton.hpp @@ -0,0 +1,77 @@ +#ifndef UI_C_SIMPLE_BUTTON_HPP +#define UI_C_SIMPLE_BUTTON_HPP + +#include "ui/CSimpleFrame.hpp" +#include + +class CSimpleFont; +class CSimpleFontString; +class CSimpleTexture; + +enum CSimpleButtonState { + BUTTONSTATE_DISABLED = 0x0, + BUTTONSTATE_NORMAL = 0x1, + BUTTONSTATE_PUSHED = 0x2, + NUM_BUTTONSTATES = 0x3, +}; + +class CSimpleButton : public CSimpleFrame { + public: + // Static variables + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + + // Member variables + uint32_t m_doubleClickTime = 0; + uint64_t m_clickAction = 0x0000000080000000; + CSimpleButtonState m_state = BUTTONSTATE_DISABLED; + int32_t m_stateLocked = 0; + CSimpleFontString* m_text = nullptr; + CSimpleFont* m_normalFont = nullptr; + CSimpleFont* m_highlightFont = nullptr; + CSimpleFont* m_disabledFont = nullptr; + C2Vector m_pressedOffset; + CSimpleTexture* m_textures[NUM_BUTTONSTATES]; + CSimpleTexture* m_activeTexture = nullptr; + CSimpleTexture* m_highlightTexture = nullptr; + ScriptIx m_preClick; + ScriptIx m_onClick; + ScriptIx m_postClick; + ScriptIx m_onDoubleClick; + + // Virtual member functions + virtual ScriptIx* GetScriptByName(const char*, ScriptData&); + virtual bool IsA(int32_t); + virtual int32_t GetScriptMetaTable(void); + virtual void LoadXML(XMLNode*, CStatus*); + virtual void OnLayerHide(void); + virtual void OnLayerCursorEnter(int32_t); + virtual void OnLayerCursorExit(int32_t, int32_t); + virtual int32_t OnLayerMouseDown(const CMouseEvent&, const char*); + virtual int32_t OnLayerMouseUp(const CMouseEvent&, const char*); + virtual void LockHighlight(int32_t); + virtual void Enable(int32_t); + virtual void OnClick(const char*, int32_t); + virtual void OnDoubleClick(const char*); + virtual void SetButtonState(CSimpleButtonState, int32_t); + + // Member functions + CSimpleButton(CSimpleFrame*); + void RunOnClickScript(const char*, int32_t); + void SetClickAction(uint64_t action); + void SetFontString(CSimpleFontString*); + void SetHighlight(CSimpleTexture*, EGxBlend); + int32_t SetHighlight(const char* texFile, EGxBlend blendMode); + void SetPressedOffset(C2Vector&); + void SetStateTexture(CSimpleButtonState, CSimpleTexture*); + int32_t SetStateTexture(CSimpleButtonState state, const char* texFile); + void SetText(const char*); + void UpdateTextState(CSimpleButtonState); +}; + +#endif diff --git a/src/ui/CSimpleButtonScript.cpp b/src/ui/CSimpleButtonScript.cpp new file mode 100644 index 0000000..099f4ff --- /dev/null +++ b/src/ui/CSimpleButtonScript.cpp @@ -0,0 +1,349 @@ +#include "ui/CSimpleButtonScript.hpp" +#include "gx/Coordinate.hpp" +#include "ui/CSimpleButton.hpp" +#include "ui/CSimpleFontString.hpp" +#include "ui/CSimpleTexture.hpp" +#include "util/Lua.hpp" +#include "util/StringTo.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleButton_SetStateTexture(lua_State* L, CSimpleButtonState state, const char* method) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + if (lua_type(L, 2) == LUA_TTABLE) { + lua_rawgeti(L, 2, 0); + auto texture = static_cast(lua_touserdata(L, -1)); + lua_settop(L, -2); + + if (!texture) { + luaL_error(L, "%s:%s(): Couldn't find 'this' in texture", button->GetDisplayName(), method); + } + + if (!texture->IsA(CSimpleTexture::GetObjectType())) { + luaL_error(L, "%s:%s(): Wrong object type, expected texture", button->GetDisplayName(), method); + } + + button->SetStateTexture(state, texture); + + } else if (lua_isstring(L, 2)) { + auto texFile = lua_tolstring(L, 2, nullptr); + button->SetStateTexture(state, texFile); + + } else if (lua_type(L, 2) == LUA_TNIL) { + CSimpleTexture* texture = nullptr; + button->SetStateTexture(state, texture); + + } else { + luaL_error(L, "Usage: %s:%s(texture or \"texture\" or nil)", button->GetDisplayName(), method); + } + + return 0; +} + +int32_t CSimpleButton_Enable(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + if (button->ProtectedFunctionsAllowed()) { + button->Enable(1); + } else { + // TODO + // - disallowed logic + } + + return 0; +} + +int32_t CSimpleButton_Disable(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + if (button->ProtectedFunctionsAllowed()) { + button->Enable(0); + } else { + // TODO + // - disallowed logic + } + + return 0; +} + +int32_t CSimpleButton_IsEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetButtonState(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetButtonState(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetNormalFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetNormalFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetDisabledFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetDisabledFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetHighlightFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetHighlightFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetFontString(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetFontString(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetText(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + const char* text = lua_tolstring(L, 2, 0); + button->SetText(text); + + return 0; +} + +int32_t CSimpleButton_SetFormattedText(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetText(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + const char* text = nullptr; + + if (button->m_text && button->m_text->GetText() && *button->m_text->GetText()) { + text = button->m_text->GetText(); + } + + lua_pushstring(L, text); + + return 1; +} + +int32_t CSimpleButton_SetNormalTexture(lua_State* L) { + return CSimpleButton_SetStateTexture(L, BUTTONSTATE_NORMAL, "SetNormalTexture"); +} + +int32_t CSimpleButton_GetNormalTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetPushedTexture(lua_State* L) { + return CSimpleButton_SetStateTexture(L, BUTTONSTATE_PUSHED, "SetPushedTexture"); +} + +int32_t CSimpleButton_GetPushedTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetDisabledTexture(lua_State* L) { + return CSimpleButton_SetStateTexture(L, BUTTONSTATE_DISABLED, "SetDisabledTexture"); +} + +int32_t CSimpleButton_GetDisabledTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetHighlightTexture(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + EGxBlend blendMode = GxBlend_Add; + if (lua_isstring(L, 3)) { + auto blendString = lua_tolstring(L, 3, nullptr); + StringToBlendMode(blendString, blendMode); + } + + if (lua_type(L, 2) == LUA_TTABLE) { + lua_rawgeti(L, 2, 0); + auto texture = static_cast(lua_touserdata(L, -1)); + lua_settop(L, -2); + + if (!texture) { + luaL_error(L, "%s:SetHighlightTexture(): Couldn't find 'this' in texture", button->GetDisplayName()); + } + + if (!texture->IsA(CSimpleTexture::GetObjectType())) { + luaL_error(L, "%s:SetHighlightTexture(): Wrong object type, expected texture", button->GetDisplayName()); + } + + button->SetHighlight(texture, blendMode); + + } else if (lua_isstring(L, 2)) { + auto texFile = lua_tolstring(L, 2, nullptr); + button->SetHighlight(texFile, blendMode); + + } else if (lua_type(L, 2) == LUA_TNIL) { + CSimpleTexture* texture = nullptr; + button->SetHighlight(texture, GxBlend_Add); + + } else { + luaL_error(L, "Usage: %s:SetHighlightTexture(texture or \"texture\" or nil [, \"blendmode\")", button->GetDisplayName()); + } + + return 0; +} + +int32_t CSimpleButton_GetHighlightTexture(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + auto texture = button->m_highlightTexture; + + if (texture) { + if (!texture->lua_registered) { + texture->RegisterScriptObject(nullptr); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, texture->lua_objectRef); + } else { + lua_pushnil(L); + } + + return 1; +} + +int32_t CSimpleButton_SetPushedTextOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetPushedTextOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_GetTextWidth(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + auto text = button->m_text; + + float width = text ? text->GetWidth() : 0.0f; + float ddcWidth = CoordinateGetAspectCompensation() * 1024.0f * width; + float ndcWidth = DDCToNDCWidth(ddcWidth); + + lua_pushnumber(L, ndcWidth); + + return 1; +} + +int32_t CSimpleButton_GetTextHeight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_RegisterForClicks(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + uint64_t action = 0; + + for (int32_t i = 2; lua_isstring(L, i); i++) { + auto actionStr = lua_tolstring(L, i, nullptr); + action |= StringToClickAction(actionStr); + } + + button->SetClickAction(action); + + return 0; +} + +int32_t CSimpleButton_Click(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + auto inputButton = "LeftButton"; + if (lua_isstring(L, 2)) { + inputButton = lua_tolstring(L, 2, nullptr); + } + + auto v6 = StringToBOOL(L, 3, 0); + + button->OnClick(inputButton, v6); + + return 0; +} + +int32_t CSimpleButton_LockHighlight(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + button->LockHighlight(1); + + return 0; +} + +int32_t CSimpleButton_UnlockHighlight(lua_State* L) { + auto type = CSimpleButton::GetObjectType(); + auto button = static_cast(FrameScript_GetObjectThis(L, type)); + + button->LockHighlight(0); + + return 0; +} + +int32_t CSimpleButton_GetMotionScriptsWhileDisabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleButton_SetMotionScriptsWhileDisabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleButtonMethods[NUM_SIMPLE_BUTTON_SCRIPT_METHODS] = { + { "Enable", &CSimpleButton_Enable }, + { "Disable", &CSimpleButton_Disable }, + { "IsEnabled", &CSimpleButton_IsEnabled }, + { "GetButtonState", &CSimpleButton_GetButtonState }, + { "SetButtonState", &CSimpleButton_SetButtonState }, + { "SetNormalFontObject", &CSimpleButton_SetNormalFontObject }, + { "GetNormalFontObject", &CSimpleButton_GetNormalFontObject }, + { "SetDisabledFontObject", &CSimpleButton_SetDisabledFontObject }, + { "GetDisabledFontObject", &CSimpleButton_GetDisabledFontObject }, + { "SetHighlightFontObject", &CSimpleButton_SetHighlightFontObject }, + { "GetHighlightFontObject", &CSimpleButton_GetHighlightFontObject }, + { "SetFontString", &CSimpleButton_SetFontString }, + { "GetFontString", &CSimpleButton_GetFontString }, + { "SetText", &CSimpleButton_SetText }, + { "SetFormattedText", &CSimpleButton_SetFormattedText }, + { "GetText", &CSimpleButton_GetText }, + { "SetNormalTexture", &CSimpleButton_SetNormalTexture }, + { "GetNormalTexture", &CSimpleButton_GetNormalTexture }, + { "SetPushedTexture", &CSimpleButton_SetPushedTexture }, + { "GetPushedTexture", &CSimpleButton_GetPushedTexture }, + { "SetDisabledTexture", &CSimpleButton_SetDisabledTexture }, + { "GetDisabledTexture", &CSimpleButton_GetDisabledTexture }, + { "SetHighlightTexture", &CSimpleButton_SetHighlightTexture }, + { "GetHighlightTexture", &CSimpleButton_GetHighlightTexture }, + { "SetPushedTextOffset", &CSimpleButton_SetPushedTextOffset }, + { "GetPushedTextOffset", &CSimpleButton_GetPushedTextOffset }, + { "GetTextWidth", &CSimpleButton_GetTextWidth }, + { "GetTextHeight", &CSimpleButton_GetTextHeight }, + { "RegisterForClicks", &CSimpleButton_RegisterForClicks }, + { "Click", &CSimpleButton_Click }, + { "LockHighlight", &CSimpleButton_LockHighlight }, + { "UnlockHighlight", &CSimpleButton_UnlockHighlight }, + { "GetMotionScriptsWhileDisabled", &CSimpleButton_GetMotionScriptsWhileDisabled }, + { "SetMotionScriptsWhileDisabled", &CSimpleButton_SetMotionScriptsWhileDisabled } +}; diff --git a/src/ui/CSimpleButtonScript.hpp b/src/ui/CSimpleButtonScript.hpp new file mode 100644 index 0000000..72ce17d --- /dev/null +++ b/src/ui/CSimpleButtonScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_BUTTON_SCRIPT_HPP +#define UI_C_SIMPLE_BUTTON_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_BUTTON_SCRIPT_METHODS 34 + +extern FrameScript_Method SimpleButtonMethods[NUM_SIMPLE_BUTTON_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleCheckbox.cpp b/src/ui/CSimpleCheckbox.cpp new file mode 100644 index 0000000..64b455d --- /dev/null +++ b/src/ui/CSimpleCheckbox.cpp @@ -0,0 +1,40 @@ +#include "ui/CSimpleCheckbox.hpp" +#include "ui/CSimpleCheckboxScript.hpp" + +int32_t CSimpleCheckbox::s_metatable; +int32_t CSimpleCheckbox::s_objectType; + +void CSimpleCheckbox::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleCheckbox::RegisterScriptMethods); + CSimpleCheckbox::s_metatable = ref; +} + +int32_t CSimpleCheckbox::GetObjectType() { + if (!CSimpleCheckbox::s_objectType) { + CSimpleCheckbox::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleCheckbox::s_objectType; +} + +void CSimpleCheckbox::RegisterScriptMethods(lua_State* L) { + CSimpleButton::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleCheckboxMethods, NUM_SIMPLE_CHECKBOX_SCRIPT_METHODS); +} + +CSimpleCheckbox::CSimpleCheckbox(CSimpleFrame* parent) : CSimpleButton(parent) { + // TODO +} + +int32_t CSimpleCheckbox::GetScriptMetaTable() { + return CSimpleCheckbox::s_metatable; +} + +bool CSimpleCheckbox::IsA(int32_t type) { + return type == CSimpleCheckbox::s_objectType + || type == CSimpleButton::s_objectType + || type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} diff --git a/src/ui/CSimpleCheckbox.hpp b/src/ui/CSimpleCheckbox.hpp new file mode 100644 index 0000000..866a21e --- /dev/null +++ b/src/ui/CSimpleCheckbox.hpp @@ -0,0 +1,27 @@ +#ifndef UI_C_SIMPLE_CHECKBOX_HPP +#define UI_C_SIMPLE_CHECKBOX_HPP + +#include "ui/CSimpleButton.hpp" + +class CSimpleCheckbox : public CSimpleButton { + public: + // Static variables + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + + // Member variables + + // Virtual member functions + virtual bool IsA(int32_t); + virtual int32_t GetScriptMetaTable(void); + + // Member functions + CSimpleCheckbox(CSimpleFrame*); +}; + +#endif diff --git a/src/ui/CSimpleCheckboxScript.cpp b/src/ui/CSimpleCheckboxScript.cpp new file mode 100644 index 0000000..bee7189 --- /dev/null +++ b/src/ui/CSimpleCheckboxScript.cpp @@ -0,0 +1,36 @@ +#include "ui/CSimpleCheckboxScript.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleCheckbox_SetChecked(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleCheckbox_GetChecked(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleCheckbox_GetCheckedTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleCheckbox_SetCheckedTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleCheckbox_GetDisabledCheckedTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleCheckbox_SetDisabledCheckedTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleCheckboxMethods[NUM_SIMPLE_CHECKBOX_SCRIPT_METHODS] = { + { "SetChecked", &CSimpleCheckbox_SetChecked }, + { "GetChecked", &CSimpleCheckbox_GetChecked }, + { "GetCheckedTexture", &CSimpleCheckbox_GetCheckedTexture }, + { "SetCheckedTexture", &CSimpleCheckbox_SetCheckedTexture }, + { "GetDisabledCheckedTexture", &CSimpleCheckbox_GetDisabledCheckedTexture }, + { "SetDisabledCheckedTexture", &CSimpleCheckbox_SetDisabledCheckedTexture } +}; diff --git a/src/ui/CSimpleCheckboxScript.hpp b/src/ui/CSimpleCheckboxScript.hpp new file mode 100644 index 0000000..4ef108c --- /dev/null +++ b/src/ui/CSimpleCheckboxScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_CHECKBOX_SCRIPT_HPP +#define UI_C_SIMPLE_CHECKBOX_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_CHECKBOX_SCRIPT_METHODS 6 + +extern FrameScript_Method SimpleCheckboxMethods[NUM_SIMPLE_CHECKBOX_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleEditBox.cpp b/src/ui/CSimpleEditBox.cpp new file mode 100644 index 0000000..fe5bb6b --- /dev/null +++ b/src/ui/CSimpleEditBox.cpp @@ -0,0 +1,1349 @@ +#include "ui/CSimpleEditBox.hpp" +#include "gx/Gx.hpp" +#include "gx/Coordinate.hpp" +#include "ui/CSimpleEditBoxScript.hpp" +#include "ui/CSimpleFontedFrameFont.hpp" +#include "ui/CSimpleFontString.hpp" +#include "ui/CSimpleTexture.hpp" +#include "ui/CSimpleTop.hpp" +#include "ui/LoadXML.hpp" +#include "util/Byte.hpp" +#include "util/CStatus.hpp" +#include "util/Lua.hpp" +#include "util/StringTo.hpp" +#include +#include +#include +#include + +CSimpleEditBox* CSimpleEditBox::s_currentFocus; +int32_t CSimpleEditBox::s_metatable; +int32_t CSimpleEditBox::s_objectType; + +void CSimpleEditBox::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleEditBox::RegisterScriptMethods); + CSimpleEditBox::s_metatable = ref; +} + +int32_t CSimpleEditBox::GetObjectType() { + if (!CSimpleEditBox::s_objectType) { + CSimpleEditBox::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleEditBox::s_objectType; +} + +void CSimpleEditBox::RegisterScriptMethods(lua_State* L) { + CSimpleFrame::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleEditBoxMethods, NUM_SIMPLE_EDIT_BOX_SCRIPT_METHODS); +} + +void CSimpleEditBox::SetKeyboardFocus(CSimpleEditBox* editBox) { + if (!editBox->m_visible) { + return; + } + + if (CSimpleEditBox::s_currentFocus == editBox) { + return; + } + + if (CSimpleEditBox::s_currentFocus) { + // TODO + // if (CSimpleEditBox::s_currentFocus->m_password) { + // nullsub_3(); + // } + + // TODO + // if (!(CSimpleEditBox::s_currentFocus->simpleeditbox_dword4 & 0xC)) { + // CSimpleEditBox::s_currentFocus->Sub963390(); + // } + + CSimpleEditBox::s_currentFocus->RunOnEditFocusLostScript(); + CSimpleEditBox::s_currentFocus->m_dirtyFlags |= 0x4; + } + + CSimpleEditBox::s_currentFocus = editBox; + + // TODO + // if (editBox->m_password) { + // nullsub_3(); + // } + + // TODO + // if (!(editBox->simpleeditbox_dword4 & 0xC)) { + // OsIMEEnable(1); + // } + + editBox->RunOnEditFocusGainedScript(); + editBox->m_dirtyFlags |= 0x4; +} + +CSimpleEditBox::CSimpleEditBox(CSimpleFrame* parent) : CSimpleFrame(parent) { + this->m_autoFocus = 1; + this->m_multiline = 0; + this->m_numeric = 0; + this->m_password = 0; + this->m_ignoreArrows = 0; + this->m_countInvisibleLetters = 0; + + char* text = static_cast(SMemAlloc(this->m_textSize, __FILE__, __LINE__, 0)); + text[0] = 0; + this->m_text = text; + + uint32_t* textInfo = static_cast(SMemAlloc(sizeof(uint32_t) * this->m_textSize, __FILE__, __LINE__, 0)); + memset(textInfo, 0, sizeof(uint32_t) * this->m_textSize); + this->m_textInfo = textInfo; + + void* fontMem = SMemAlloc(sizeof(CSimpleFontedFrameFont), __FILE__, __LINE__, 0); + CSimpleFontedFrameFont* font = new (fontMem) CSimpleFontedFrameFont(this); + this->m_font = font; + + this->m_font->m_attributes.SetJustifyH(0x1); + + // TODO + // void* stringMem = CDataAllocator::GetData(CSimpleFontString::s_allocator, 0, __FILE__, __LINE__); + void* stringMem = SMemAlloc(sizeof(CSimpleFontString), __FILE__, __LINE__, 0); + CSimpleFontString* string = new (stringMem) CSimpleFontString(this, DRAWLAYER_ARTWORK, 1); + this->m_string = string; + + this->m_string->SetFontObject(this->m_font); + + this->SetMultiLine(0); + + for (int32_t i = 0; i < 3; i++) { + // TODO + // void* highlightMem = CDataAllocator::GetData(CSimpleTexture::s_allocator, 0, __FILE__, __LINE__); + void* highlightMem = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0); + CSimpleTexture* highlight = new (highlightMem) CSimpleTexture(this, DRAWLAYER_ARTWORK, 0); + this->m_highlight[i] = highlight; + } + + CImVector highlightColor = { 0x60, 0x60, 0x60, 0xFF }; + + for (int32_t i = 0; i < 3; i++) { + this->m_highlight[i]->SetTexture(highlightColor); + } + + // TODO + // void* cursorMem = CDataAllocator::GetData(CSimpleTexture::s_allocator, 0, __FILE__, __LINE__); + void* cursorMem = SMemAlloc(sizeof(CSimpleTexture), __FILE__, __LINE__, 0); + CSimpleTexture* cursor = new (cursorMem) CSimpleTexture(this, DRAWLAYER_ARTWORK_OVERLAY, 1); + cursor->Hide(); + this->m_cursor = cursor; + + CImVector cursorColor = { 0xFF, 0xFF, 0xFF, 0xFF }; + this->m_cursor->SetTexture(cursorColor); + + // TODO + // - initialize actions + + this->EnableEvent(SIMPLE_EVENT_CHAR, -1); + this->EnableEvent(SIMPLE_EVENT_KEY, -1); + this->EnableEvent(SIMPLE_EVENT_MOUSE, -1); +} + +int32_t CSimpleEditBox::ConvertCoordinateToIndex(float a2, float a3, int32_t& a4) { + // TODO + return 0; +} + +void CSimpleEditBox::DeleteBackward(int32_t a2) { + if (this->m_highlightLeft != this->m_highlightRight) { + this->DeleteHighlight(a2); + return; + } + + if (this->m_cursorPos > 0) { + int32_t len = this->GetNumToLen(this->m_cursorPos, -1, true); + this->DeleteSubstring(this->m_cursorPos - len, this->m_cursorPos, a2); + } +} + +void CSimpleEditBox::DeleteForward(int32_t a2) { + if (this->m_highlightLeft != this->m_highlightRight) { + this->DeleteHighlight(a2); + return; + } + + if (this->m_cursorPos < this->m_textLength) { + int32_t len = this->GetNumToLen(this->m_cursorPos, 1, true); + this->DeleteSubstring(this->m_cursorPos, this->m_cursorPos + len, a2); + } +} + +void CSimpleEditBox::DeleteHighlight(int32_t a2) { + // TODO +} + +void CSimpleEditBox::DeleteSubstring(int32_t left, int32_t right, int32_t a4) { + auto v5 = this->m_textInfo; + auto v6 = left; + + if ((v5[left] & 0x80000000) != 0) { + auto v7 = left; + + if (left >= 0) { +LABEL_3: + auto v8 = BYTE2(v5[v6]); + auto v9 = &v5[v6]; + + if (v8 == 1 || v8 == 5 || v8 == 7) { + if (v6 >= 0) { +LABEL_12: + auto v10 = BYTE2(v5[v6]); + auto v11 = &v5[v6]; + + if (v10 == 1 || v10 == 5 || v10 == 7) { + v7 = v6; + + while (1) { + v6--; + v11--; + + if (v6 < 0) { + break; + } + + if (*v11) { + if (v6 >= 0) { + goto LABEL_12; + } + + break; + } + } + } + } + } else { + v7 = v6; + + while (1) { + v6--; + v9--; + + if (v6 < 0) { + break; + } + + if (*v9) { + if (v6 >= 0) { + goto LABEL_3; + } + + break; + } + } + } + } + + v6 = v7; + } + + auto v12 = right; + if ((v5[right] & 0x80000000) != 0) { + auto v13 = this->m_textLength; + + if (right < v13) { + do { + auto v14 = BYTE2(v5[v12]); + + if (v14 == 2 || v14 == 6 || v14 == 8) { + break; + } + + v12 += v5[v12] & 0xFFFF; + } while (v12 < v13); + + for (; v12 < v13; v12 += v5[v12] & 0xFFFF) { + auto v15 = BYTE2(v5[v12]); + + if (v15 != 2 && v15 != 6 && v15 != 8) { + break; + } + } + } + } + + auto v16 = v12 - v6; + auto v17 = &this->m_text[v6]; + auto newLength = this->m_textLength - (v12 - v6); + + this->m_cursorPos = v6; + + auto v19 = &v5[v6]; + + if (newLength >= 0) { + this->m_textLength = newLength; + + memcpy(v17, &v17[v16], newLength - v6); + memcpy(v19, &v19[v16], 4 * (this->m_textLength - this->m_cursorPos)); + + this->m_text[this->m_textLength] = '\0'; + this->m_textInfo[this->m_textLength] = '\0'; + + if (this->m_highlightLeft != this->m_highlightRight) { + this->m_dirtyFlags |= 0x2; + + this->m_highlightRight = 0; + this->m_highlightLeft = 0; + } + + this->m_dirtyFlags |= 0x1 | 0x4; + + if (!a4) { + this->m_dirtyFlags |= 0x8; + } + + if (!this->m_textLength) { + // TODO this->simpleeditbox_dwordC = 0; + } + } +} + +void CSimpleEditBox::FontUpdated(CSimpleFontedFrameFont* font, int32_t a3) { + // TODO +} + +int32_t CSimpleEditBox::GetNumToLen(int32_t offset, int32_t amount, bool a4) { + uint32_t len = 0; + uint32_t* textInfo = this->m_textInfo + offset; + + if (amount > 0) { + for (int32_t i = 0; i < amount; i++) { + if (!*textInfo) { + break; + } + + uint32_t v8; + uint32_t v9; + uint32_t v10; + + do { + do { + v8 = *((uint8_t*)textInfo + 2); + v9 = (uint16_t)*textInfo; + v10 = *textInfo & 0xFF000000; + + len += v9; + textInfo += v9; + } while (v8 == 1); + } while (v8 == 5 || v8 == 7 || (a4 && v10 < 0 && v8 != 6)); + + uint32_t v11; + uint32_t v12; + + while (1) { + v11 = *((uint8_t*)textInfo + 2); + v12 = *textInfo & 0xFFFF; + + if (v11 != 2 && v11 != 6 && v11 != 8) { + break; + } + + len += v12; + textInfo += v12; + } + } + } else { + for (int32_t i = 0; i < -amount; i++) { + if (textInfo <= this->m_textInfo) { + break; + } + + uint32_t v14; + + do { + for (--textInfo; textInfo > this->m_textInfo; --textInfo) { + if (*textInfo) { + break; + } + } + + v14 = *((uint8_t*)textInfo + 2); + len += (uint16_t)*textInfo; + } while (v14 == 2 || v14 == 6 || v14 == 8 || (a4 && (*textInfo & 0x80000000) != 0 && v14 != 5)); + + if (textInfo > this->m_textInfo) { + uint32_t v15; + uint32_t v16; + + while (1) { + for (--textInfo; textInfo > this->m_textInfo; --textInfo) { + if (*textInfo) { + break; + } + } + + v15 = *((uint8_t*)textInfo + 2); + v16 = *textInfo & 0xFFFF; + + if (v15 != 1 && v15 != 5 && v15 != 7) { + break; + } + + len += v16; + + if (textInfo <= this->m_textInfo) { + break; + } + } + + if (textInfo > this->m_textInfo) { + textInfo += v16; + } + } + } + } + + return len; +} + +FrameScript_Object::ScriptIx* CSimpleEditBox::GetScriptByName(const char* name, ScriptData& data) { + auto parentScript = CSimpleFrame::GetScriptByName(name, data); + + if (parentScript) { + return parentScript; + } + + if (!SStrCmpI(name, "OnEnterPressed", STORM_MAX_STR)) { + return &this->m_onEnterPressed; + } + + if (!SStrCmpI(name, "OnEscapePressed", STORM_MAX_STR)) { + return &this->m_onEscapePressed; + } + + if (!SStrCmpI(name, "OnSpacePressed", STORM_MAX_STR)) { + return &this->m_onSpacePressed; + } + + if (!SStrCmpI(name, "OnTabPressed", STORM_MAX_STR)) { + return &this->m_onTabPressed; + } + + if (!SStrCmpI(name, "OnTextChanged", STORM_MAX_STR)) { + data.wrapper = "return function(self, userInput) %s end"; + return &this->m_onTextChanged; + } + + if (!SStrCmpI(name, "OnTextSet", STORM_MAX_STR)) { + return &this->m_onTextSet; + } + + if (!SStrCmpI(name, "OnCursorChanged", STORM_MAX_STR)) { + data.wrapper = "return function(self,x,y,w,h) %s end"; + return &this->m_onCursorChanged; + } + + if (!SStrCmpI(name, "OnInputLanguageChanged", STORM_MAX_STR)) { + data.wrapper = "return function(self,language) %s end"; + return &this->m_onInputLanguageChanged; + } + + if (!SStrCmpI(name, "OnEditFocusGained", STORM_MAX_STR)) { + return &this->m_onEditFocusGained; + } + + if (!SStrCmpI(name, "OnEditFocusLost", STORM_MAX_STR)) { + return &this->m_onEditFocusLost; + } + + if (!SStrCmpI(name, "OnCharComposition", STORM_MAX_STR)) { + data.wrapper = "return function(self,text) %s end"; + return &this->m_onCharComposition; + } + + return nullptr; +} + +int32_t CSimpleEditBox::GetScriptMetaTable() { + return CSimpleEditBox::s_metatable; +} + +void CSimpleEditBox::GrowText(int32_t size) { + if (size + 1 <= this->m_textSize) { + return; + } + + this->m_textSize = (size + 32) & 0xFFFFFFE0; + + this->m_text = static_cast( + SMemReAlloc(this->m_text, this->m_textSize, __FILE__, __LINE__, 0) + ); + + this->m_textInfo = static_cast( + SMemReAlloc(this->m_textInfo, this->m_textSize * sizeof(uint32_t), __FILE__, __LINE__, 0) + ); +} + +void CSimpleEditBox::Insert(uint32_t chr) { + if (chr == '\t') { + this->Insert(" ", nullptr, 1, 0, 0); + return; + } + + if (chr == '|') { + this->Insert("||", nullptr, 1, 0, 0); + return; + } + + if ((this->m_multiline && chr == '\n') || (chr >= ' ' && chr != 127)) { + char charBuf[8]; + SUniSPutUTF8(chr, charBuf); + + this->Insert(charBuf, nullptr, 1, 0, 0); + return; + } +} + +void CSimpleEditBox::Insert(const char* a2, const char* a3, int32_t a4, int32_t a5, int32_t a6) { + if ( + (this->m_textInfo[this->m_cursorPos] & 0x80000000) + && this->m_cursorPos > 0 + && (this->m_textInfo[this->PrevCharOffset(this->m_cursorPos)] & 0x80000000) + ) { + return; + } + + if (this->m_highlightLeft != this->m_highlightRight) { + this->DeleteHighlight(a6); + } + + if (!a2) { + a2 = ""; + } + + const char* v10 = a2; + + if (this->m_numeric && !a5 && *v10) { + while (*v10) { + int32_t v33; + + if (SUniSGetUTF8(reinterpret_cast(a2), &v33) - 48 > 9) { + return; + } + + v10 += v33; + } + } + + int32_t v11 = SStrLen(a2); + + this->GrowText(this->m_textLength + v11); + + char* v14 = this->m_text + this->m_cursorPos; + + if (this->m_cursorPos < this->m_textLength) { + memcpy(v14 + v11, v14, this->m_textLength - this->m_cursorPos); + } + + memcpy(v14, a2, v11); + + this->m_textLength += v11; + *(this->m_text + this->m_textLength) = 0; + + int32_t v31 = this->m_cursorPos; + this->m_cursorPos += v11; + + this->UpdateTextInfo(); + + this->m_dirtyFlags |= (0x1 | 0x4); + a6 ? this->m_dirtyFlags &= ~0x8 : this->m_dirtyFlags |= 0x8; + + if (this->m_textLengthMax >= 0 && this->m_textLength > this->m_textLengthMax) { + // TODO + } + + if (this->m_textLettersMax) { + // TODO + } + + if (a5) { + this->m_highlightLeft = v31; + this->m_highlightRight = std::min(this->m_textLength, this->m_cursorPos); + this->m_dirtyFlags |= 2u; + } + + // TODO + // if (!this->m_intC && this->m_textLength > 0) { + // this->m_intC = a3; + // } + + if (a4) { + this->RunOnCharScript(a2); + + // TODO + // - check for spaces and run onSpacePressed script + } +} + +bool CSimpleEditBox::IsA(int32_t type) { + return type == CSimpleEditBox::s_objectType + || type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +int32_t CSimpleEditBox::IsCurrentFocus() { + return this == CSimpleEditBox::s_currentFocus; +} + +void CSimpleEditBox::LoadXML(XMLNode* node, CStatus* status) { + CSimpleFrame::LoadXML(node, status); + + const char* fontAttr = node->GetAttributeByName("font"); + if (fontAttr && *fontAttr) { + auto font = CSimpleFont::GetFont(fontAttr, 0); + + if (font) { + this->m_font->SetFontObject(font); + } else { + status->Add( + STATUS_WARNING, + "%s %s: Couldn't find font object named %s", + this->GetObjectTypeName(), + this->GetDisplayName(), + fontAttr + ); + } + } + + // TODO + // - letters + + const char* countInvisibleLettersAttr = node->GetAttributeByName("countInvisibleLetters"); + if (countInvisibleLettersAttr && *countInvisibleLettersAttr) { + this->m_countInvisibleLetters = StringToBOOL(countInvisibleLettersAttr); + } + + const char* blinkSpeedAttr = node->GetAttributeByName("blinkSpeed"); + if (blinkSpeedAttr && *blinkSpeedAttr) { + float blinkSpeed = std::max(SStrToFloat(blinkSpeedAttr), 0.0f); + this->m_cursorBlinkSpeed = blinkSpeed; + } + + const char* autoFocusAttr = node->GetAttributeByName("autoFocus"); + if (autoFocusAttr && *autoFocusAttr) { + this->m_autoFocus = StringToBOOL(autoFocusAttr); + } + + const char* multiLineAttr = node->GetAttributeByName("multiLine"); + if (multiLineAttr && *multiLineAttr) { + this->m_multiline = StringToBOOL(multiLineAttr); + } + + const char* numericAttr = node->GetAttributeByName("numeric"); + if (numericAttr && *numericAttr) { + this->m_numeric = StringToBOOL(numericAttr); + } + + const char* passwordAttr = node->GetAttributeByName("password"); + if (passwordAttr && *passwordAttr) { + this->m_password = StringToBOOL(passwordAttr); + } + + const char* ignoreArrowsAttr = node->GetAttributeByName("ignoreArrows"); + if (ignoreArrowsAttr && *ignoreArrowsAttr) { + this->m_ignoreArrows = StringToBOOL(ignoreArrowsAttr); + } + + const char* historyLinesAttr = node->GetAttributeByName("historyLines"); + if (historyLinesAttr && *historyLinesAttr) { + int32_t historyLines = std::max(SStrToInt(historyLinesAttr), 0); + this->SetHistoryLines(historyLines); + } + + for (XMLNode* child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "FontString", STORM_MAX_STR)) { + CSimpleFont* font = this->m_font; + + if (font->GetFontObject()) { + status->Add( + STATUS_WARNING, + "%s: FontString element overriding font %s", + this->GetDisplayName(), + font->GetFontObject()->GetName() + ); + } + + this->m_font->LoadXML(child, status); + + const char* bytesAttr = child->GetAttributeByName("bytes"); + if (bytesAttr && *bytesAttr) { + int32_t bytes = SStrToInt(bytesAttr); + + // TODO + // - set some member of CSimpleEditBox + } + + } else if (!SStrCmpI(child->GetName(), "HighlightColor", STORM_MAX_STR)) { + // TODO + + } else if (!SStrCmpI(child->GetName(), "TextInsets", STORM_MAX_STR)) { + float left, right, top, bottom; + + if (LoadXML_Insets(child, left, right, top, bottom, status)) { + this->m_editTextInset.minX = left; + this->m_editTextInset.maxX = right; + this->m_editTextInset.maxY = top; + this->m_editTextInset.minY = bottom; + + this->UpdateSizes(); + } + } + } +} + +void CSimpleEditBox::Move(int32_t distance, int32_t highlight) { + STORM_ASSERT(distance); + + auto offset = this->GetNumToLen(this->m_cursorPos, distance, true); + if (distance < 0) { + offset = -offset; + } + + if (highlight) { + if (this->m_highlightLeft == this->m_highlightRight) { + this->m_highlightLeft = this->m_cursorPos; + this->m_highlightRight = this->m_cursorPos; + } + + // TODO this->Sub9631A0(offset); + + this->m_cursorPos += offset; + this->m_dirtyFlags |= 0x4; + } else { + if (this->m_highlightLeft != this->m_highlightRight) { + this->m_dirtyFlags |= 0x2; + this->m_highlightLeft = 0; + this->m_highlightRight = 0; + } + + this->m_cursorPos += offset; + this->m_dirtyFlags |= 0x4; + } +} + +void CSimpleEditBox::MoveBackward(int32_t highlight) { + if (this->m_cursorPos > 0) { + this->Move(-1, highlight); + } + + if (!highlight && this->m_highlightLeft != this->m_highlightRight) { + this->m_dirtyFlags |= 0x2; + this->m_highlightLeft = 0; + this->m_highlightRight = 0; + } +} + +void CSimpleEditBox::MoveForward(int32_t highlight) { + if (this->m_cursorPos < this->m_textLength) { + this->Move(1, highlight); + } + + if (!highlight && this->m_highlightLeft != this->m_highlightRight) { + this->m_dirtyFlags |= 0x2; + this->m_highlightLeft = 0; + this->m_highlightRight = 0; + } +} + +void CSimpleEditBox::OnEnterPressed(void) { + this->RunOnEnterPressedScript(); + + // TODO + // - invoke action +} + +void CSimpleEditBox::OnEscapePressed(void) { + // TODO +} + +void CSimpleEditBox::OnFrameSizeChanged(float width, float height) { + CSimpleFrame::OnFrameSizeChanged(width, height); + this->UpdateSizes(); +} + +int32_t CSimpleEditBox::OnLayerChar(const CCharEvent& evt) { + if (!this->m_visible) { + return 0; + } + + if (!CSimpleEditBox::s_currentFocus && this->m_autoFocus) { + CSimpleEditBox::SetKeyboardFocus(this); + } else if (!this->IsCurrentFocus()) { + return 0; + } + + this->m_imeInputMode = 0; + + this->Insert(evt.ch); + + return 1; +} + +int32_t CSimpleEditBox::OnLayerKeyDown(const CKeyEvent& evt) { + if (!this->m_visible) { + return 0; + } + + if (evt.key == KEY_PRINTSCREEN + || ( + this->m_ignoreArrows + && (evt.key == KEY_LEFT || evt.key == KEY_RIGHT || evt.key == KEY_UP || evt.key == KEY_DOWN) + && !EventIsKeyDown(KEY_LALT) + && !EventIsKeyDown(KEY_RALT) + ) + ) { + return 0; + } + + if (!CSimpleEditBox::s_currentFocus && this->m_autoFocus) { + CSimpleEditBox::SetKeyboardFocus(this); + } else if (!this->IsCurrentFocus()) { + return 0; + } + + this->UpdateDirtyBits(); + + switch (evt.key) { + case KEY_ESCAPE: { + this->OnEscapePressed(); + return 1; + } + + case KEY_ENTER: { + if (!this->m_multiline || this->m_onEnterPressed.luaRef) { + // TODO + // this->m_top->m_int1250 = 1; + + this->OnEnterPressed(); + + // TODO + // this->m_top->m_int1250 = 0; + } else { + this->Insert("\n", 0, 1, 0, 0); + } + + return 1; + } + + case KEY_BACKSPACE: { + if (EventIsControlKeyDown()) { + // TODO this->DeleteBackwardWord(0); + } else { + this->DeleteBackward(0); + } + + return 1; + } + + case KEY_LEFT: { + if (EventIsControlKeyDown()) { + // TODO this->MoveBackwardWord(EventIsShiftKeyDown()); + } else { + this->MoveBackward(EventIsShiftKeyDown()); + } + + return 1; + } + + case KEY_RIGHT: { + if (EventIsControlKeyDown()) { + // TODO this->MoveForwardWord(EventIsShiftKeyDown()); + } else { + this->MoveForward(EventIsShiftKeyDown()); + } + + return 1; + } + + case KEY_DELETE: { + if (EventIsShiftKeyDown()) { + // TODO cut to clipboard + } else if (EventIsControlKeyDown()) { + // TODO this->DeleteForwardWord(0); + } else { + this->DeleteForward(0); + } + + return 1; + } + + // TODO + // - remaining keys + + default: + return 1; + } +} + +int32_t CSimpleEditBox::OnLayerMouseDown(const CMouseEvent& evt, const char* btn) { + int32_t eaten = CSimpleFrame::OnLayerMouseDown(evt, btn); + + if (!eaten) { + this->UpdateDirtyBits(); + + int32_t position; + + if (!this->m_imeInputMode && this->ConvertCoordinateToIndex(evt.x, evt.y, position)) { + if (this->m_highlightLeft != this->m_highlightRight) { + this->m_dirtyFlags |= 0x2; + this->m_highlightRight = 0; + this->m_highlightLeft = 0; + } + + this->SetCursorPosition(position); + + this->StartHighlight(); + this->m_highlightDrag = 1; + + eaten = 1; + } + + CSimpleEditBox::SetKeyboardFocus(this); + } + + return eaten; +} + +int32_t CSimpleEditBox::OnLayerMouseUp(const CMouseEvent& evt, const char* btn) { + int32_t eaten = CSimpleFrame::OnLayerMouseUp(evt, btn); + + if (!eaten) { + if (this->m_highlightDrag) { + this->m_highlightDrag = 0; + eaten = 1; + } + } + + return eaten; +} + +void CSimpleEditBox::OnLayerUpdate(float elapsedSec) { + CSimpleFrame::OnLayerUpdate(elapsedSec); + + this->UpdateDirtyBits(); + + if (!GxCapsWindowHasFocus(-1)) { + this->m_cursor->Hide(); + return; + } + + if (this->m_cursorBlinkSpeed == 0.0f) { + return; + } + + bool cursorVisible = this->m_cursorPos >= this->m_visiblePos + && this->m_cursorPos <= this->m_visiblePos + this->m_visibleLen; + + if (!cursorVisible) { + return; + } + + this->m_blinkElapsedTime += elapsedSec; + + if (this->IsCurrentFocus() && this->m_blinkElapsedTime > this->m_cursorBlinkSpeed) { + if (this->m_cursor->IsShown()) { + this->m_cursor->Hide(); + } else { + this->m_cursor->Show(); + } + + this->m_blinkElapsedTime = 0.0f; + } +} + +void CSimpleEditBox::OnTextChanged() { + this->RunOnTextChangedScript((this->m_dirtyFlags & 0x8) != 0); + + // TODO + // - invoke action +} + +int32_t CSimpleEditBox::PrevCharOffset(int32_t offset) { + int32_t prevCharOffset = offset; + + while (prevCharOffset > 0 && !this->m_textInfo[prevCharOffset - 1]) { + prevCharOffset--; + } + + return prevCharOffset; +} + +void CSimpleEditBox::RunOnCursorChangedScript(float x, float y, float w, float h) { + // TODO +} + +void CSimpleEditBox::RunOnEditFocusGainedScript() { + if (this->m_onEditFocusGained.luaRef) { + this->RunScript(this->m_onEditFocusGained, 0, 0); + } +} + +void CSimpleEditBox::RunOnEditFocusLostScript() { + if (this->m_onEditFocusLost.luaRef) { + this->RunScript(this->m_onEditFocusLost, 0, 0); + } +} + +void CSimpleEditBox::RunOnEnterPressedScript() { + if (this->m_onEnterPressed.luaRef) { + this->RunScript(this->m_onEnterPressed, 0, 0); + } +} + +void CSimpleEditBox::RunOnTextChangedScript(int32_t changed) { + if (this->m_onTextChanged.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushboolean(L, changed); + + this->RunScript(this->m_onTextChanged, 1, 0); + } +} + +void CSimpleEditBox::SetCursorPosition(int32_t position) { + if (position >= 0) { + this->m_cursorPos = std::min(this->m_textLength, position); + } else { + this->m_cursorPos = 0; + } + + this->m_dirtyFlags |= 0x4; +} + +void CSimpleEditBox::SetHistoryLines(int32_t a2) { + // TODO +} + +void CSimpleEditBox::SetMultiLine(int32_t enabled) { + if (enabled) { + this->m_multiline = 1; + } else { + this->m_multiline = 0; + } + + this->m_visibleLines.SetCount(2); + + if (this->m_multiline) { + this->m_string->SetJustifyV(0x8); + + // TODO + // - unknown style change (remove flag 0x4000) + + this->m_string->SetNonWordWrap(0); + } else { + this->m_string->SetJustifyV(0x10); + + // TODO + // - unknown style change (add flag 0x4000) + + this->m_string->SetNonWordWrap(1); + } + + this->m_string->SetNonSpaceWrap(1); + + this->UpdateSizes(); +} + +void CSimpleEditBox::StartHighlight() { + // TODO +} + +void CSimpleEditBox::UpdateDirtyBits() { + int32_t textChanged = this->m_dirtyFlags & 0x1; + + if (this->m_dirtyFlags & 0x4) { + // TODO + } + + if (this->m_dirtyFlags & 0x1) { + this->m_dirtyFlags &= ~0x1; + this->UpdateVisibleText(); + } + + if (this->m_dirtyFlags & 0x2) { + this->m_dirtyFlags &= ~0x2; + this->UpdateVisibleHighlight(); + } + + if (this->m_dirtyFlags & 0x4) { + this->m_dirtyFlags &= ~0x4; + this->UpdateVisibleCursor(); + } + + if (textChanged) { + this->OnTextChanged(); + this->m_dirtyFlags &= ~0x8; + } +} + +void CSimpleEditBox::UpdateSizes() { + if (!(CLayoutFrame::m_flags & 0x1)) { + return; + } + + this->m_string->ClearAllPoints(); + + float stringWidth = ((this->m_rect.maxX - this->m_rect.minX) / this->m_layoutScale) + - (this->m_editTextInset.maxX + this->m_editTextInset.minX); + + this->m_string->SetWidth(stringWidth); + + float stringHeight = 0.0f; + + if (!this->m_multiline) { + stringHeight = ((this->m_rect.maxY - this->m_rect.minY) / this->m_layoutScale) + - (this->m_editTextInset.maxY + this->m_editTextInset.minY); + } + + this->m_string->SetHeight(stringHeight); + + this->m_string->SetPoint( + FRAMEPOINT_TOPLEFT, + this, + FRAMEPOINT_TOPLEFT, + this->m_editTextInset.minX, + -this->m_editTextInset.maxY, + 1 + ); + + float fontHeight = this->m_string->GetFontHeight(1); + + for (int32_t i = 0; i < 3; i++) { + // TODO + // - highlight sizing + } + + // TODO + // - clause highlight? sizing + + float v13 = 4.0f / (CoordinateGetAspectCompensation() * 1024.0f); + float cursorWidth = NDCToDDCWidth(v13); + + this->m_cursor->ClearAllPoints(); + this->m_cursor->SetWidth(cursorWidth); + this->m_cursor->SetHeight(fontHeight); + + this->m_dirtyFlags |= (0x1 | 0x2 | 0x4); +} + +void CSimpleEditBox::UpdateTextInfo() { + memset(this->m_textInfo, 0, this->m_textSize * sizeof(uint32_t)); + + uint32_t i = 0; + uint32_t v9 = 0; + int32_t advance; + uint32_t code; + + const char* text = this->m_text; + + while (text && *text) { + QUOTEDCODE quotedCode = GxuDetermineQuotedCode(text, advance, nullptr, 0, code); + + if (quotedCode == CODE_HYPERLINKSTART) { + v9 |= 0x80000000; + } + + this->m_textInfo[i] = v9 | (quotedCode << 16) | static_cast(advance); + + if (quotedCode == CODE_HYPERLINKSTOP) { + v9 &= 0x7FFFFFFF; + } + + i += advance; + text += advance; + } +} + +void CSimpleEditBox::UpdateVisibleCursor() { + bool cursorVisible = this->m_cursorPos >= this->m_visiblePos + && this->m_cursorPos <= this->m_visiblePos + this->m_visibleLen; + + if (!cursorVisible) { + this->m_cursor->Hide(); + return; + } + + const char* text = this->m_password + ? this->m_textHidden + : this->m_text; + + float cursorX = 0.0f; + float cursorY = 0.0f; + + float v8 = CSimpleTop::RoundToPixelHeight(this->m_string->GetFontHeight(1) * this->m_layoutScale); + float v10 = CSimpleTop::RoundToPixelHeight(this->m_string->m_spacing * this->m_layoutScale); + float v13 = (v10 + v8) / this->m_layoutScale; + + int32_t cursorPos = this->m_cursorPos; + int32_t line = 0; + + while (cursorPos >= this->m_visibleLines[line + 1] && line < this->m_visibleLines.Count() - 2) { + line++; + cursorY -= v13; + } + + this->m_cursor->ClearAllPoints(); + + // Left aligned + if (this->m_string->m_styleFlags & 0x1) { + float v36; + + if (cursorPos == this->m_visibleLines[line]) { + v36 = 0.0f; + } else { + v36 = this->m_string->GetTextWidth( + text + this->m_visibleLines[line], + cursorPos - this->m_visibleLines[line] + ); + } + + cursorX = v36; + + if (this->m_multiline) { + this->m_cursor->SetPoint( + FRAMEPOINT_TOPLEFT, + this->m_string, + FRAMEPOINT_TOPLEFT, + cursorX, + cursorY, + 1 + ); + } else { + this->m_cursor->SetPoint( + FRAMEPOINT_LEFT, + this->m_string, + FRAMEPOINT_LEFT, + cursorX, + cursorY, + 1 + ); + } + + // Center aligned + } else if (this->m_string->m_styleFlags & 0x2) { + float v28; + + if (cursorPos == this->m_visibleLines[line]) { + v28 = 0.0f; + } else { + v28 = this->m_string->GetTextWidth( + text + this->m_visibleLines[line], + cursorPos - this->m_visibleLines[line] + ); + } + + float v29 = this->m_string->GetTextWidth( + text + this->m_visibleLines[line], + this->m_visibleLines[line + 1] - this->m_visibleLines[line] + ); + + cursorX = (v28 - v29) / 2.0f; + + if (this->m_multiline) { + this->m_cursor->SetPoint( + FRAMEPOINT_TOPLEFT, + this->m_string, + FRAMEPOINT_TOP, + cursorX, + cursorY, + 1 + ); + } else { + this->m_cursor->SetPoint( + FRAMEPOINT_LEFT, + this->m_string, + FRAMEPOINT_CENTER, + cursorX, + cursorY, + 1 + ); + } + + // Right aligned + } else if (this->m_string->m_styleFlags & 0x4) { + // TODO + } + + this->m_blinkElapsedTime = 0.0f; + + float cursorWidth = this->m_cursor->GetWidth(); + float cursorHeight = this->m_cursor->GetHeight(); + this->RunOnCursorChangedScript(cursorX, cursorY, cursorWidth, cursorHeight); + + if (this->IsCurrentFocus()) { + this->m_cursor->Show(); + } else { + this->m_cursor->Hide(); + } +} + +void CSimpleEditBox::UpdateVisibleHighlight() { + // TODO +} + +void CSimpleEditBox::UpdateVisibleText() { + float stringWidth = this->m_string->GetWidth(); + + // Update password mask + if (this->m_password) { + int32_t textLen = SStrLen(this->m_text); + this->m_textHidden = static_cast( + SMemReAlloc(this->m_textHidden, textLen + 1, __FILE__, __LINE__, 0) + ); + memset(this->m_textHidden, '*', textLen); + this->m_textHidden[textLen] = '\0'; + } + + char* text = this->m_password + ? this->m_textHidden + : this->m_text; + + if (this->m_multiline) { + // TODO + } else { + float v16 = 4.0f / (CoordinateGetAspectCompensation() * 1024.0f); + float v17 = stringWidth - NDCToDDCWidth(v16); + if (v17 <= 0.0) { + v17 = stringWidth; + } + + this->m_visibleLen = this->m_string->GetNumCharsWithinWidth( + text + this->m_visiblePos, + 0, + v17 + ); + + if (this->m_text == text) { + this->m_visibleLen = this->GetNumToLen( + this->m_visiblePos, + this->m_visibleLen, + 0 + ); + } + + this->m_visibleLines[0] = this->m_visiblePos; + this->m_visibleLines[1] = this->m_visiblePos + this->m_visibleLen; + } + + if (!this->m_password) { + // TODO + } + + int32_t visibleEnd = this->m_visiblePos + this->m_visibleLen; + auto v42 = text[visibleEnd]; + text[visibleEnd] = '\0'; + this->m_string->SetText(text + this->m_visiblePos, 0); + text[visibleEnd] = v42; + + if (this->m_multiline) { + auto height = this->m_string->GetStringHeight(); + if (height == 0.0f) { + height = this->m_string->GetFontHeight(1); + } + + this->SetHeight(height + this->m_editTextInset.minY + this->m_editTextInset.maxY); + } +} diff --git a/src/ui/CSimpleEditBox.hpp b/src/ui/CSimpleEditBox.hpp new file mode 100644 index 0000000..bd890c0 --- /dev/null +++ b/src/ui/CSimpleEditBox.hpp @@ -0,0 +1,114 @@ +#ifndef UI_C_SIMPLE_EDIT_BOX_HPP +#define UI_C_SIMPLE_EDIT_BOX_HPP + +#include "ui/CSimpleFrame.hpp" +#include "ui/CSimpleFontedFrame.hpp" + +class CSimpleFontString; +class CSimpleTexture; + +class CSimpleEditBox : public CSimpleFrame, CSimpleFontedFrame { + public: + // Static variables + static CSimpleEditBox* s_currentFocus; + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + static void SetKeyboardFocus(CSimpleEditBox*); + + // Member variables + int32_t m_autoFocus : 1; + int32_t m_multiline : 1; + int32_t m_numeric : 1; + int32_t m_password : 1; + int32_t m_ignoreArrows : 1; + int32_t m_countInvisibleLetters : 1; + int32_t m_dirtyFlags = 0x1; + CSimpleFontedFrameFont* m_font; + CSimpleFontString* m_string; + char* m_text; + uint32_t* m_textInfo; + char* m_textHidden = nullptr; + int32_t m_textLength = 0; + int32_t m_textLengthMax = -1; + int32_t m_textLettersMax = 0; + int32_t m_textSize = 32; + int32_t m_visiblePos = 0; + int32_t m_visibleLen = 0; + CSimpleTexture* m_highlight[3]; + int32_t m_highlightLeft = 0; + int32_t m_highlightRight = 0; + int32_t m_highlightDrag = 0; + CSimpleTexture* m_cursor = nullptr; + int32_t m_cursorPos = 0; + float m_cursorBlinkSpeed = 0.5f; + float m_blinkElapsedTime = 0.0f; + TSGrowableArray m_visibleLines; + int32_t m_imeInputMode = 0; + CRect m_editTextInset = {}; + ScriptIx m_onEnterPressed; + ScriptIx m_onEscapePressed; + ScriptIx m_onSpacePressed; + ScriptIx m_onTabPressed; + ScriptIx m_onTextChanged; + ScriptIx m_onTextSet; + ScriptIx m_onCursorChanged; + ScriptIx m_onInputLanguageChanged; + ScriptIx m_onEditFocusGained; + ScriptIx m_onEditFocusLost; + ScriptIx m_onCharComposition; + + // Virtual member functions + virtual ScriptIx* GetScriptByName(const char*, ScriptData&); + virtual bool IsA(int32_t); + virtual int32_t GetScriptMetaTable(void); + virtual void LoadXML(XMLNode*, CStatus*); + virtual void OnLayerUpdate(float); + virtual void OnFrameSizeChanged(float, float); + virtual int32_t OnLayerChar(const CCharEvent&); + virtual int32_t OnLayerKeyDown(const CKeyEvent&); + virtual int32_t OnLayerMouseDown(const CMouseEvent&, const char*); + virtual int32_t OnLayerMouseUp(const CMouseEvent&, const char*); + virtual void FontUpdated(CSimpleFontedFrameFont*, int32_t); + + // Member functions + CSimpleEditBox(CSimpleFrame*); + int32_t ConvertCoordinateToIndex(float, float, int32_t&); + void DeleteBackward(int32_t a2); + void DeleteForward(int32_t a2); + void DeleteHighlight(int32_t); + void DeleteSubstring(int32_t left, int32_t right, int32_t a4); + int32_t GetNumToLen(int32_t, int32_t, bool); + void GrowText(int32_t); + void Insert(uint32_t); + void Insert(const char*, const char*, int32_t, int32_t, int32_t); + int32_t IsCurrentFocus(void); + void Move(int32_t distance, int32_t highlight); + void MoveBackward(int32_t highlight); + void MoveForward(int32_t highlight); + void OnEnterPressed(void); + void OnEscapePressed(void); + void OnTextChanged(void); + int32_t PrevCharOffset(int32_t); + void RunOnCursorChangedScript(float, float, float, float); + void RunOnEditFocusGainedScript(void); + void RunOnEditFocusLostScript(void); + void RunOnEnterPressedScript(void); + void RunOnTextChangedScript(int32_t); + void SetCursorPosition(int32_t); + void SetHistoryLines(int32_t); + void SetMultiLine(int32_t); + void StartHighlight(void); + void UpdateDirtyBits(void); + void UpdateSizes(void); + void UpdateTextInfo(void); + void UpdateVisibleCursor(void); + void UpdateVisibleHighlight(void); + void UpdateVisibleText(void); +}; + +#endif diff --git a/src/ui/CSimpleEditBoxScript.cpp b/src/ui/CSimpleEditBoxScript.cpp new file mode 100644 index 0000000..e4dfee7 --- /dev/null +++ b/src/ui/CSimpleEditBoxScript.cpp @@ -0,0 +1,309 @@ +#include "ui/CSimpleEditBoxScript.hpp" +#include "ui/CSimpleEditBox.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleEditBox_SetFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetAutoFocus(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_IsAutoFocus(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetCountInvisibleLetters(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_IsCountInvisibleLetters(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetMultiLine(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_IsMultiLine(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetNumeric(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_IsNumeric(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetPassword(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_IsPassword(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetBlinkSpeed(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetBlinkSpeed(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_Insert(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetText(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetText(lua_State* L) { + int32_t type = CSimpleEditBox::GetObjectType(); + CSimpleEditBox* editBox = static_cast(FrameScript_GetObjectThis(L, type)); + + // TODO + // - taint management + // if (editBox->m_dwordC && lua_taintexpected && !lua_taintedclosure) { + // lua_tainted = editBox->simpleeditbox_dwordC; + // } + + lua_pushstring(L, editBox->m_text); + + return 1; +} + +int32_t CSimpleEditBox_SetNumber(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetNumber(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_HighlightText(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_AddHistoryLine(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_ClearHistory(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetTextInsets(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetTextInsets(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetFocus(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_ClearFocus(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_HasFocus(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetMaxBytes(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetMaxBytes(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetMaxLetters(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetMaxLetters(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetNumLetters(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetHistoryLines(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetHistoryLines(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetInputLanguage(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_ToggleInputLanguage(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetAltArrowKeyMode(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetAltArrowKeyMode(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_IsInIMECompositionMode(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_SetCursorPosition(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetCursorPosition(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleEditBox_GetUTF8CursorPosition(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleEditBoxMethods[NUM_SIMPLE_EDIT_BOX_SCRIPT_METHODS] = { + { "SetFontObject", &CSimpleEditBox_SetFontObject }, + { "GetFontObject", &CSimpleEditBox_GetFontObject }, + { "SetFont", &CSimpleEditBox_SetFont }, + { "GetFont", &CSimpleEditBox_GetFont }, + { "SetTextColor", &CSimpleEditBox_SetTextColor }, + { "GetTextColor", &CSimpleEditBox_GetTextColor }, + { "SetShadowColor", &CSimpleEditBox_SetShadowColor }, + { "GetShadowColor", &CSimpleEditBox_GetShadowColor }, + { "SetShadowOffset", &CSimpleEditBox_SetShadowOffset }, + { "GetShadowOffset", &CSimpleEditBox_GetShadowOffset }, + { "SetSpacing", &CSimpleEditBox_SetSpacing }, + { "GetSpacing", &CSimpleEditBox_GetSpacing }, + { "SetJustifyH", &CSimpleEditBox_SetJustifyH }, + { "GetJustifyH", &CSimpleEditBox_GetJustifyH }, + { "SetJustifyV", &CSimpleEditBox_SetJustifyV }, + { "GetJustifyV", &CSimpleEditBox_GetJustifyV }, + { "SetIndentedWordWrap", &CSimpleEditBox_SetIndentedWordWrap }, + { "GetIndentedWordWrap", &CSimpleEditBox_GetIndentedWordWrap }, + { "SetAutoFocus", &CSimpleEditBox_SetAutoFocus }, + { "IsAutoFocus", &CSimpleEditBox_IsAutoFocus }, + { "SetCountInvisibleLetters", &CSimpleEditBox_SetCountInvisibleLetters }, + { "IsCountInvisibleLetters", &CSimpleEditBox_IsCountInvisibleLetters }, + { "SetMultiLine", &CSimpleEditBox_SetMultiLine }, + { "IsMultiLine", &CSimpleEditBox_IsMultiLine }, + { "SetNumeric", &CSimpleEditBox_SetNumeric }, + { "IsNumeric", &CSimpleEditBox_IsNumeric }, + { "SetPassword", &CSimpleEditBox_SetPassword }, + { "IsPassword", &CSimpleEditBox_IsPassword }, + { "SetBlinkSpeed", &CSimpleEditBox_SetBlinkSpeed }, + { "GetBlinkSpeed", &CSimpleEditBox_GetBlinkSpeed }, + { "Insert", &CSimpleEditBox_Insert }, + { "SetText", &CSimpleEditBox_SetText }, + { "GetText", &CSimpleEditBox_GetText }, + { "SetNumber", &CSimpleEditBox_SetNumber }, + { "GetNumber", &CSimpleEditBox_GetNumber }, + { "HighlightText", &CSimpleEditBox_HighlightText }, + { "AddHistoryLine", &CSimpleEditBox_AddHistoryLine }, + { "ClearHistory", &CSimpleEditBox_ClearHistory }, + { "SetTextInsets", &CSimpleEditBox_SetTextInsets }, + { "GetTextInsets", &CSimpleEditBox_GetTextInsets }, + { "SetFocus", &CSimpleEditBox_SetFocus }, + { "ClearFocus", &CSimpleEditBox_ClearFocus }, + { "HasFocus", &CSimpleEditBox_HasFocus }, + { "SetMaxBytes", &CSimpleEditBox_SetMaxBytes }, + { "GetMaxBytes", &CSimpleEditBox_GetMaxBytes }, + { "SetMaxLetters", &CSimpleEditBox_SetMaxLetters }, + { "GetMaxLetters", &CSimpleEditBox_GetMaxLetters }, + { "GetNumLetters", &CSimpleEditBox_GetNumLetters }, + { "GetHistoryLines", &CSimpleEditBox_GetHistoryLines }, + { "SetHistoryLines", &CSimpleEditBox_SetHistoryLines }, + { "GetInputLanguage", &CSimpleEditBox_GetInputLanguage }, + { "ToggleInputLanguage", &CSimpleEditBox_ToggleInputLanguage }, + { "SetAltArrowKeyMode", &CSimpleEditBox_SetAltArrowKeyMode }, + { "GetAltArrowKeyMode", &CSimpleEditBox_GetAltArrowKeyMode }, + { "IsInIMECompositionMode", &CSimpleEditBox_IsInIMECompositionMode }, + { "SetCursorPosition", &CSimpleEditBox_SetCursorPosition }, + { "GetCursorPosition", &CSimpleEditBox_GetCursorPosition }, + { "GetUTF8CursorPosition", &CSimpleEditBox_GetUTF8CursorPosition } +}; diff --git a/src/ui/CSimpleEditBoxScript.hpp b/src/ui/CSimpleEditBoxScript.hpp new file mode 100644 index 0000000..34ee28d --- /dev/null +++ b/src/ui/CSimpleEditBoxScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_EDIT_BOX_SCRIPT_HPP +#define UI_C_SIMPLE_EDIT_BOX_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_EDIT_BOX_SCRIPT_METHODS 58 + +extern FrameScript_Method SimpleEditBoxMethods[NUM_SIMPLE_EDIT_BOX_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleFont.cpp b/src/ui/CSimpleFont.cpp new file mode 100644 index 0000000..80db984 --- /dev/null +++ b/src/ui/CSimpleFont.cpp @@ -0,0 +1,241 @@ +#include "ui/CSimpleFont.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Font.hpp" +#include "ui/CSimpleFontScript.hpp" +#include "ui/FrameXML.hpp" +#include "ui/LoadXML.hpp" +#include "ui/Types.hpp" +#include "util/CStatus.hpp" +#include "util/StringTo.hpp" +#include +#include + +TSHashTable CSimpleFont::s_fontList; +int32_t CSimpleFont::s_metatable; +int32_t CSimpleFont::s_objectType; + +SIMPLEFONT::SIMPLEFONT() : TSHashObject() { + void* m = SMemAlloc(sizeof(CSimpleFont), __FILE__, __LINE__, 0); + + if (m) { + this->font = new (m) CSimpleFont(); + } else { + this->font = nullptr; + } +} + +void CSimpleFont::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleFont::RegisterScriptMethods); + CSimpleFont::s_metatable = ref; +} + +CSimpleFont* CSimpleFont::GetFont(const char* name, int32_t a2) { + SIMPLEFONT* hashed = CSimpleFont::s_fontList.Ptr(name); + + if (hashed) { + return hashed->font; + } + + if (!a2) { + return nullptr; + } + + hashed = CSimpleFont::s_fontList.New(name, 0, 0); + + CSimpleFont* font = hashed->font; + + if (font->m_name) { + font->UnregisterScriptObject(font->m_name); + } + + font->m_name = hashed->m_key.m_str; + + if (font->m_name) { + font->RegisterScriptObject(font->m_name); + } + + return hashed->font; +} + +int32_t CSimpleFont::GetObjectType() { + if (!CSimpleFont::s_objectType) { + CSimpleFont::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleFont::s_objectType; +} + +void CSimpleFont::RegisterScriptMethods(lua_State* L) { + FrameScript_Object::FillScriptMethodTable(L, SimpleFontMethods, NUM_SIMPLE_FONT_SCRIPT_METHODS); +} + +CSimpleFont::CSimpleFont() : FrameScript_Object() { + // TODO +} + +void CSimpleFont::FontObjectUpdated(CSimpleFontStringAttributes& attributes) { + attributes.Update(this->m_attributes, this->m_fontableFlags); + this->UpdateObjects(); +} + +char* CSimpleFont::GetName() { + return const_cast(this->m_name); +} + +int32_t CSimpleFont::GetScriptMetaTable() { + return CSimpleFont::s_metatable; +} + +bool CSimpleFont::IsA(int32_t type) { + return type == CSimpleFont::s_objectType; +} + +void CSimpleFont::LoadXML(XMLNode* node, CStatus* status) { + const char* inheritsAttr = node->GetAttributeByName("inherits"); + if (inheritsAttr && *inheritsAttr) { + auto font = CSimpleFont::GetFont(inheritsAttr, 0); + + if (font) { + this->SetFontObject(font); + } else { + status->Add( + STATUS_WARNING, + "Couldn't find inherited font: %s", + inheritsAttr + ); + } + } + + const char* fontAttr = node->GetAttributeByName("font"); + if (fontAttr && *fontAttr) { + auto font = CSimpleFont::GetFont(fontAttr, 0); + + if (font) { + this->SetFontObject(font); + } else { + float fontHeight = 0.0f; + + XMLNode* fontHeightNode = node->GetChildByName("FontHeight"); + if (fontHeightNode) { + LoadXML_Value(fontHeightNode, fontHeight, status); + } + + if (fontHeight == 0.0f) { + status->Add( + STATUS_WARNING, + "Font %s: Missing font height in %s element", + this->GetDisplayName(), + node->GetName() + ); + + return; + } + + uint32_t fontFlags = 0; + + const char* outlineAttr = node->GetAttributeByName("outline"); + if (outlineAttr && *outlineAttr) { + if (!SStrCmpI(outlineAttr, "NORMAL", STORM_MAX_STR)) { + fontFlags |= FONT_OUTLINE; + } else if (!SStrCmpI(outlineAttr, "THICK", STORM_MAX_STR)) { + fontFlags |= (FONT_OUTLINE | FONT_THICKOUTLINE); + } + } + + const char* monochromeAttr = node->GetAttributeByName("monochrome"); + if (monochromeAttr && *monochromeAttr && StringToBOOL(monochromeAttr)) { + fontFlags |= FONT_MONOCHROME; + } + + if (this->m_attributes.SetFont(fontAttr, fontHeight, fontFlags)) { + this->m_fontableFlags &= ~FLAG_FONT_UPDATE; + } else { + status->Add( + STATUS_WARNING, + "Font %s: Unable to load font file %s", + this->GetDisplayName(), + fontAttr + ); + } + } + } + + const char* spacingAttr = node->GetAttributeByName("spacing"); + if (spacingAttr && *spacingAttr) { + float spacing = SStrToFloat(spacingAttr); + float ndcSpacing = spacing / (CoordinateGetAspectCompensation() * 1024.0); + float ddcSpacing = NDCToDDCWidth(ndcSpacing); + + this->m_attributes.SetSpacing(ddcSpacing); + this->m_fontableFlags &= ~FLAG_SPACING_UPDATE; + } + + const char* justifyVAttr = node->GetAttributeByName("justifyV"); + if (justifyVAttr && *justifyVAttr) { + uint32_t justify; + + if (StringToJustify(justifyVAttr, justify)) { + this->m_attributes.SetJustifyV(justify); + this->m_fontableFlags &= ~FLAG_STYLE_UPDATE; + } + } + + const char* justifyHAttr = node->GetAttributeByName("justifyH"); + if (justifyHAttr && *justifyHAttr) { + uint32_t justify; + + if (StringToJustify(justifyHAttr, justify)) { + this->m_attributes.SetJustifyH(justify); + this->m_fontableFlags &= ~FLAG_STYLE_UPDATE; + } + } + + const char* nonspacewrapAttr = node->GetAttributeByName("nonspacewrap"); + if (nonspacewrapAttr && *nonspacewrapAttr) { + bool nonspacewrap = StringToBOOL(nonspacewrapAttr); + this->m_attributes.SetNonSpaceWrap(nonspacewrap); + } + + const char* indentedAttr = node->GetAttributeByName("indented"); + if (indentedAttr && *indentedAttr) { + bool indented = StringToBOOL(indentedAttr); + this->m_attributes.SetIndented(indented); + } + + for (XMLNode* child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "Color", STORM_MAX_STR)) { + CImVector color; + LoadXML_Color(child, color); + this->m_attributes.SetColor(color); + this->m_fontableFlags &= ~FLAG_COLOR_UPDATE; + + } else if(!SStrCmpI(child->GetName(), "Shadow", STORM_MAX_STR)) { + C2Vector shadowOffset = { 0.001f, -0.001f }; + CImVector shadowColor = { 0x00, 0x00, 0x00, 0xFF }; + + auto colorChild = child->GetChildByName("Color"); + if (colorChild) { + LoadXML_Color(colorChild, shadowColor); + } + + auto offsetChild = child->GetChildByName("Offset"); + if (offsetChild) { + LoadXML_Dimensions(offsetChild, shadowOffset.x, shadowOffset.y, status); + } + + this->m_attributes.SetShadow(shadowColor, shadowOffset); + this->m_fontableFlags &= ~FLAG_SHADOW_UPDATE; + } + } +} + +void CSimpleFont::UpdateObjects() { + if (this->m_attributes.m_flags) { + for (auto node = this->m_fontableList.Head(); node; node = this->m_fontableList.Link(node)->Next()) { + node->FontObjectUpdated(this->m_attributes); + } + + this->m_attributes.m_flags &= ~FLAG_COMPLETE_UPDATE; + } +} diff --git a/src/ui/CSimpleFont.hpp b/src/ui/CSimpleFont.hpp new file mode 100644 index 0000000..e2bcbc1 --- /dev/null +++ b/src/ui/CSimpleFont.hpp @@ -0,0 +1,54 @@ +#ifndef UI_C_SIMPLE_FONT_HPP +#define UI_C_SIMPLE_FONT_HPP + +#include "ui/CSimpleFontable.hpp" +#include "ui/CSimpleFontStringAttributes.hpp" +#include "ui/FrameScript_Object.hpp" +#include +#include +#include + +class CSimpleFont; +class CStatus; +class XMLNode; + +class SIMPLEFONT : public TSHashObject { + public: + // Member variables + CSimpleFont* font = nullptr; + + // Member functions + SIMPLEFONT(); +}; + +class CSimpleFont : public FrameScript_Object, public CSimpleFontable { + public: + // Static variables + static TSHashTable s_fontList; + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static CSimpleFont* GetFont(const char*, int32_t); + static int32_t GetObjectType(); + static void RegisterScriptMethods(lua_State*); + + // Member variables + CSimpleFontStringAttributes m_attributes; + const char* m_name = nullptr; + STORM_EXPLICIT_LIST(CSimpleFontable, m_fontableLink) m_fontableList; + + // Virtual member functions + virtual char* GetName(void); + virtual int32_t GetScriptMetaTable(void); + virtual bool IsA(int32_t); + virtual void FontObjectUpdated(CSimpleFontStringAttributes&); + + // Member functions + CSimpleFont(); + void LoadXML(XMLNode*, CStatus*); + void UpdateObjects(void); +}; + +#endif diff --git a/src/ui/CSimpleFontScript.cpp b/src/ui/CSimpleFontScript.cpp new file mode 100644 index 0000000..0abbf50 --- /dev/null +++ b/src/ui/CSimpleFontScript.cpp @@ -0,0 +1,126 @@ +#include "ui/CSimpleFontScript.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleFont_GetObjectType(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_IsObjectType(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetName(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_CopyFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_SetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFont_GetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleFontMethods[NUM_SIMPLE_FONT_SCRIPT_METHODS] = { + { "GetObjectType", &CSimpleFont_GetObjectType }, + { "IsObjectType", &CSimpleFont_IsObjectType }, + { "GetName", &CSimpleFont_GetName }, + { "SetFontObject", &CSimpleFont_SetFontObject }, + { "GetFontObject", &CSimpleFont_GetFontObject }, + { "CopyFontObject", &CSimpleFont_CopyFontObject }, + { "SetFont", &CSimpleFont_SetFont }, + { "GetFont", &CSimpleFont_GetFont }, + { "SetAlpha", &CSimpleFont_SetAlpha }, + { "GetAlpha", &CSimpleFont_GetAlpha }, + { "SetTextColor", &CSimpleFont_SetTextColor }, + { "GetTextColor", &CSimpleFont_GetTextColor }, + { "SetShadowColor", &CSimpleFont_SetShadowColor }, + { "GetShadowColor", &CSimpleFont_GetShadowColor }, + { "SetShadowOffset", &CSimpleFont_SetShadowOffset }, + { "GetShadowOffset", &CSimpleFont_GetShadowOffset }, + { "SetSpacing", &CSimpleFont_SetSpacing }, + { "GetSpacing", &CSimpleFont_GetSpacing }, + { "SetJustifyH", &CSimpleFont_SetJustifyH }, + { "GetJustifyH", &CSimpleFont_GetJustifyH }, + { "SetJustifyV", &CSimpleFont_SetJustifyV }, + { "GetJustifyV", &CSimpleFont_GetJustifyV }, + { "SetIndentedWordWrap", &CSimpleFont_SetIndentedWordWrap }, + { "GetIndentedWordWrap", &CSimpleFont_GetIndentedWordWrap } +}; diff --git a/src/ui/CSimpleFontScript.hpp b/src/ui/CSimpleFontScript.hpp new file mode 100644 index 0000000..31fd693 --- /dev/null +++ b/src/ui/CSimpleFontScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_FONT_SCRIPT_HPP +#define UI_C_SIMPLE_FONT_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_FONT_SCRIPT_METHODS 24 + +extern FrameScript_Method SimpleFontMethods[NUM_SIMPLE_FONT_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleFontString.cpp b/src/ui/CSimpleFontString.cpp new file mode 100644 index 0000000..cfc5889 --- /dev/null +++ b/src/ui/CSimpleFontString.cpp @@ -0,0 +1,919 @@ +#include "ui/CSimpleFontString.hpp" +#include "gx/Coordinate.hpp" +#include "math/Utils.hpp" +#include "ui/CFramePoint.hpp" +#include "ui/CRenderBatch.hpp" +#include "ui/CSimpleFont.hpp" +#include "ui/CSimpleFontStringScript.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/CSimpleTop.hpp" +#include "ui/FrameXML.hpp" +#include "ui/LoadXML.hpp" +#include "ui/Types.hpp" +#include "ui/Util.hpp" +#include "util/CStatus.hpp" +#include "util/StringTo.hpp" +#include +#include + +int32_t CSimpleFontString::s_count; +int32_t CSimpleFontString::s_metatable; +int32_t CSimpleFontString::s_objectType; + +void CSimpleFontString::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleFontString::RegisterScriptMethods); + CSimpleFontString::s_metatable = ref; +} + +int32_t CSimpleFontString::GetObjectType() { + if (!CSimpleFontString::s_objectType) { + CSimpleFontString::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleFontString::s_objectType; +} + +void CSimpleFontString::RegisterScriptMethods(lua_State* L) { + CScriptRegion::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleFontStringMethods, NUM_SIMPLE_FONT_STRING_SCRIPT_METHODS); +} + +CSimpleFontString::CSimpleFontString(CSimpleFrame* frame, uint32_t drawlayer, int32_t show) : CSimpleRegion(frame, drawlayer, show) { + this->m_maxLines = 0; + this->m_unk12b = 1; + + // TODO + + CSimpleFontString::s_count++; +} + +CSimpleFontString::~CSimpleFontString() { + this->FreeEmbeddedTextures(); + + if (this->m_text) { + SMemFree(this->m_text, __FILE__, __LINE__, 0x0); + this->m_text = nullptr; + } + + this->m_fontHeight = 0.0f; + + if (this->m_string) { + HandleClose(this->m_string); + this->m_string = nullptr; + } + + if (this->m_font) { + HandleClose(this->m_font); + this->m_font = nullptr; + } + + CSimpleFontString::s_count--; +} + +void CSimpleFontString::AddShadow(const CImVector& shadowColor, const C2Vector& shadowOffset) { + auto styleFlags = this->m_styleFlags; + + if ( + !(styleFlags & 0x100) + || shadowColor != this->m_shadowColor + || this->m_shadowOffset.x != shadowOffset.x + || this->m_shadowOffset.y != shadowOffset.y + ) { + this->m_styleFlags = styleFlags | 0x100; + this->m_shadowColor = shadowColor; + this->m_shadowOffset = shadowOffset; + + if (this->m_string) { + auto shadowColor = this->m_shadowColor; + // TODO alpha manipulation + + auto shadowOffset = this->m_shadowOffset; + shadowOffset.x *= this->m_layoutScale; + shadowOffset.y *= this->m_layoutScale; + + TextBlockAddShadow(this->m_string, shadowColor, shadowOffset); + } + } +} + +void CSimpleFontString::ClearString() { + this->m_cachedWidth = 0.0f; + this->m_cachedHeight = 0.0f; + + if (this->m_string) { + HandleClose(this->m_string); + this->m_string = nullptr; + } + + this->m_flags &= ~0x1; + this->Resize(0); +} + +void CSimpleFontString::Draw(CRenderBatch* batch) { + if (this->m_font && this->m_text && *this->m_text) { + batch->QueueFontString(this); + this->DrawEmbeddedTextures(batch); + } +} + +void CSimpleFontString::DrawEmbeddedTextures(CRenderBatch* batch) { + // TODO +} + +bool CSimpleFontString::IsA(int32_t type) { + return type == CSimpleFontString::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleFontString::FontObjectUpdated(CSimpleFontStringAttributes& attributes) { + attributes.Update(this, this->m_fontableFlags); +} + +void CSimpleFontString::FreeEmbeddedTextures() { + // TODO +} + +const char* CSimpleFontString::GetDisplayText(float width, float height) { + static char buffer[8192]; + + if (!this->m_text || !*this->m_text) { + return nullptr; + } + + if (!this->m_font || width <= 0.0f || /* TODO */ height <= 0.0f) { + return this->m_text; + } + + auto fontHeight = this->GetFontHeight(true); + auto v5 = (CSimpleTop::RoundToPixelHeight(this->m_spacing) + fontHeight) * this->m_layoutScale; + + // TODO + + auto text = this->m_text; + auto textLen = SStrLen(text); + auto scaledSpacing = this->m_spacing * this->m_layoutScale; + auto scaledShadowOffset = this->m_shadowOffset.x * this->m_layoutScale; + auto scaledFontHeight = fontHeight * this->m_layoutScale; + + auto maxChars = TextBlockGetMaxCharsWithinWidthAndHeight( + this->m_font, + text, + scaledFontHeight, + width, + height, + textLen, + scaledShadowOffset, + this->m_layoutScale, + scaledSpacing, + this->m_styleFlags + ); + + if (maxChars >= textLen) { + return text; + } + + SStrCopy(buffer, text, sizeof(buffer)); + auto end = std::min(textLen, sizeof(buffer) - 4); + + do { + if (end) { + do { + end--; + } while (end && (buffer[end] & 0xC0) == 0x80); + } + + SStrCopy(&buffer[end], "...", STORM_MAX_STR); + + maxChars = TextBlockGetMaxCharsWithinWidthAndHeight( + this->m_font, + buffer, + scaledFontHeight, + width, + height, + end + 3, + scaledShadowOffset, + this->m_layoutScale, + scaledSpacing, + this->m_styleFlags + ); + } while (end && maxChars < end + 3); + + return buffer; +} + +uint32_t CSimpleFontString::GetFontFlags() { + if (this->m_font) { + return TextBlockGetFontFlags(this->m_font); + } + + return 0; +} + +float CSimpleFontString::GetFontHeight(bool a2) { + float fontHeight = this->m_fontHeight; + + if (a2 && this->m_font && this->m_styleFlags & 0x200) { + auto font = TextBlockGetFontPtr(this->m_font); + float ndcHeight = GxuFontGetOneToOneHeight(font) / this->m_layoutScale; + NDCToDDC(0.0f, ndcHeight, nullptr, &fontHeight); + } + + return fontHeight; +} + +const char* CSimpleFontString::GetFontName() { + return this->m_font + ? TextBlockGetFontName(this->m_font) + : nullptr; +} + +float CSimpleFontString::GetHeight() { + float v2 = CLayoutFrame::GetHeight(); + float v9 = v2 == 0.0f ? this->GetStringHeight() : v2; + + float v3 = 1.0f / (CoordinateGetAspectCompensation() * 1024.0f); + float v4 = NDCToDDCWidth(v3); + + return v9 <= v4 ? v4 : v9; +} + +uint32_t CSimpleFontString::GetNumCharsWithinWidth(const char* text, uint32_t textBytes, float maxWidth) { + if (!this->m_font) { + return 0; + } + + if (textBytes == 0) { + textBytes = SStrLen(text); + } + + float fontHeight = this->GetFontHeight(true); + float shadowWidth = this->m_shadowOffset.x; + float extent[4]; + + return TextBlockGetMaxCharsWithinWidth( + this->m_font, + text, + fontHeight * this->m_layoutScale, + maxWidth * this->m_layoutScale, + textBytes, + extent, + shadowWidth * this->m_layoutScale, + this->m_layoutScale, + 0.0f, + this->m_styleFlags + ); +} + +int32_t CSimpleFontString::GetScriptMetaTable() { + return CSimpleFontString::s_metatable; +} + +float CSimpleFontString::GetStringHeight() { + if (this->m_cachedHeight == 0.0f && this->m_font) { + float width = CLayoutFrame::GetWidth() * this->m_layoutScale; + float height = CLayoutFrame::GetHeight() * this->m_layoutScale; + + const char* displayText = this->GetDisplayText(width, height); + if (displayText && *displayText) { + float fontHeight = this->GetFontHeight(1) * this->m_layoutScale; + C2Vector shadowSize = { + this->m_shadowOffset.x * this->m_layoutScale, + this->m_shadowOffset.y * this->m_layoutScale + }; + + this->m_cachedHeight = TextBlockGetWrappedTextHeight( + this->m_font, + displayText, + fontHeight, + this->GetWidth() * this->m_layoutScale, + shadowSize, + this->m_layoutScale, + this->m_spacing * this->m_layoutScale, + this->m_styleFlags + ); + } + } + + return this->m_cachedHeight / this->m_layoutScale; +} + +float CSimpleFontString::GetStringWidth() { + if (this->m_cachedWidth == 0.0f && this->m_font) { + float width = CLayoutFrame::GetWidth() * this->m_layoutScale; + float height = CLayoutFrame::GetHeight() * this->m_layoutScale; + + const char* displayText = this->GetDisplayText(width, height); + if (displayText && *displayText) { + float fontHeight = this->GetFontHeight(1) * this->m_layoutScale; + float shadowWidth = this->m_shadowOffset.x * this->m_layoutScale; + size_t displayTextLength = SStrLen(displayText); + + TextBlockGetTextExtent( + this->m_font, + displayText, + displayTextLength, + fontHeight, + &this->m_cachedWidth, + shadowWidth, + this->m_layoutScale, + 0.0f, + this->m_styleFlags + ); + } + } + + return this->m_cachedWidth / this->m_layoutScale; +} + +const char* CSimpleFontString::GetText() { + return this->m_text; +} + +float CSimpleFontString::GetTextWidth(const char* text, uint32_t textBytes) { + if (!this->m_font) { + return 0.0f; + } + + if (textBytes == 0) { + textBytes = SStrLen(text); + } + + float fontHeight = this->GetFontHeight(true); + float extent = 0.0f; + + TextBlockGetTextExtent( + this->m_font, + text, + textBytes, + fontHeight * this->m_layoutScale, + &extent, + this->m_shadowOffset.x * this->m_layoutScale, + this->m_layoutScale, + 0.0f, + this->m_styleFlags + ); + + float width = extent / this->m_layoutScale; + + return width; +} + +float CSimpleFontString::GetWidth() { + float v2 = CLayoutFrame::GetWidth(); + float v9 = v2 == 0.0f ? this->GetStringWidth() : v2; + + float v3 = 1.0f / (CoordinateGetAspectCompensation() * 1024.0f); + float v4 = NDCToDDCWidth(v3); + + return v9 <= v4 ? v4 : v9; +} + +void CSimpleFontString::LoadXML(XMLNode* node, CStatus* status) { + const char* inheritsAttr = node->GetAttributeByName("inherits"); + if (inheritsAttr && *inheritsAttr) { + auto font = CSimpleFont::GetFont(inheritsAttr, 0); + + if (font) { + if (this->m_unk12b) { + this->SetFontObject(font); + } + } else { + const char* tainted; + bool locked; + + XMLNode* inheritsNode = FrameXML_AcquireHashNode(inheritsAttr, tainted, locked); + + if (inheritsNode) { + if (locked) { + status->Add(STATUS_WARNING, "Recursively inherited node: %s", inheritsAttr); + } else { + this->LoadXML(inheritsNode, status); + FrameXML_ReleaseHashNode(inheritsAttr); + } + } else { + status->Add(STATUS_WARNING, "Couldn't find inherited node: %s", inheritsAttr); + } + } + } + + CSimpleRegion::LoadXML(node, status); + + const char* hiddenAttr = node->GetAttributeByName("hidden"); + if (hiddenAttr && *hiddenAttr) { + bool hide = StringToBOOL(hiddenAttr); + + if (hide) { + this->Hide(); + } else { + this->Show(); + } + } + + const char* textAttr = node->GetAttributeByName("text"); + if (textAttr && *textAttr) { + const char* text = FrameScript_GetText(textAttr, -1, GENDER_NOT_APPLICABLE); + this->SetText(text && *text ? text : textAttr, 1); + } + + const char* nonSpaceWrapAttr = node->GetAttributeByName("nonspacewrap"); + if (nonSpaceWrapAttr && *nonSpaceWrapAttr) { + this->SetNonSpaceWrap(StringToBOOL(nonSpaceWrapAttr)); + } + + const char* wordWrapAttr = node->GetAttributeByName("wordwrap"); + if (wordWrapAttr && *wordWrapAttr) { + this->SetNonWordWrap(!StringToBOOL(wordWrapAttr)); + } + + const char* bytesAttr = node->GetAttributeByName("bytes"); + if (bytesAttr && *bytesAttr) { + uint32_t length = SStrToInt(bytesAttr); + this->SetTextLength(length); + } + + if (!this->m_unk12b) { + return; + } + + const char* fontAttr = node->GetAttributeByName("font"); + if (fontAttr && *fontAttr) { + auto font = CSimpleFont::GetFont(fontAttr, 0); + + if (font) { + this->SetFontObject(font); + } else { + float fontHeight = 0.0f; + + XMLNode* fontHeightNode = node->GetChildByName("FontHeight"); + if (fontHeightNode) { + LoadXML_Value(fontHeightNode, fontHeight, status); + } + + if (fontHeight == 0.0f) { + status->Add( + STATUS_WARNING, + "FontString %s: invalid font height %f in %s element", + this->GetDisplayName(), + fontHeight, + node->GetName() + ); + + return; + } + + uint32_t fontFlags = 0; + + const char* outlineAttr = node->GetAttributeByName("outline"); + if (outlineAttr && *outlineAttr) { + if (!SStrCmpI(outlineAttr, "NORMAL", STORM_MAX_STR)) { + fontFlags |= FONT_OUTLINE; + } else if (!SStrCmpI(outlineAttr, "THICK", STORM_MAX_STR)) { + fontFlags |= (FONT_OUTLINE | FONT_THICKOUTLINE); + } + } + + const char* monochromeAttr = node->GetAttributeByName("monochrome"); + if (monochromeAttr && *monochromeAttr && StringToBOOL(monochromeAttr)) { + fontFlags |= FONT_MONOCHROME; + } + + if (this->SetFont(fontAttr, fontHeight, fontFlags, false)) { + this->m_fontableFlags &= ~FLAG_FONT_UPDATE; + } else { + status->Add( + STATUS_WARNING, + "FontString %s: Unable to load font file %s", + this->GetDisplayName(), + fontAttr + ); + } + } + } + + const char* spacingAttr = node->GetAttributeByName("spacing"); + if (spacingAttr && *spacingAttr) { + float spacing = SStrToFloat(spacingAttr); + float ndcSpacing = spacing / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcSpacing = NDCToDDCWidth(ndcSpacing); + + this->SetSpacing(ddcSpacing); + this->m_fontableFlags &= ~FLAG_SPACING_UPDATE; + } + + const char* justifyVAttr = node->GetAttributeByName("justifyV"); + if (justifyVAttr && *justifyVAttr) { + uint32_t justify; + + if (StringToJustify(justifyVAttr, justify)) { + this->SetJustifyV(justify); + this->m_fontableFlags &= ~FLAG_STYLE_UPDATE; + } + } + + const char* justifyHAttr = node->GetAttributeByName("justifyH"); + if (justifyHAttr && *justifyHAttr) { + uint32_t justify; + + if (StringToJustify(justifyHAttr, justify)) { + this->SetJustifyH(justify); + this->m_fontableFlags &= ~FLAG_STYLE_UPDATE; + } + } + + const char* indentedAttr = node->GetAttributeByName("indented"); + if (indentedAttr && *indentedAttr) { + bool indented = StringToBOOL(indentedAttr); + this->SetIndentedWordWrap(indented); + } + + for (XMLNode* child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "Color", STORM_MAX_STR)) { + CImVector color; + LoadXML_Color(child, color); + this->SetVertexColor(color); + this->m_fontableFlags &= ~FLAG_COLOR_UPDATE; + + } else if (!SStrCmpI(child->GetName(), "Shadow", STORM_MAX_STR)) { + // TODO + } + } + + // TODO + // - alpha + + // TODO + // - children +} + +void CSimpleFontString::OnColorChanged(bool a2) { + CSimpleRegion::OnColorChanged(a2); + + if (this->m_string) { + CImVector color = { 0xFF, 0xFF, 0xFF, 0xFF }; + if (this->m_colorCount == 1) { + color = this->m_color[0]; + } + + TextBlockUpdateColor(this->m_string, color); + + if (this->m_styleFlags & 0x100) { + CImVector shadowColor = this->m_shadowColor; + + C2Vector shadowOffset = { + this->m_shadowOffset.x * this->m_layoutScale, + this->m_shadowOffset.y * this->m_layoutScale + }; + + // TODO + // - shadow color alpha calculation + + TextBlockAddShadow(this->m_string, shadowColor, shadowOffset); + } + } + + // TODO + // - embedded texture color updates +} + +void CSimpleFontString::OnScreenSizeChanged() { + this->m_cachedWidth = 0.0f; + this->m_cachedHeight = 0.0f; + this->Resize(0); +} + +void CSimpleFontString::OnFrameSizeChanged(const CRect& rect) { + CLayoutFrame::OnFrameSizeChanged(rect); + + if ( + this->m_string + && AreEqual(rect.maxX - rect.minX, this->m_rect.maxX - this->m_rect.minX, WHOA_EPSILON_2) + && AreEqual(rect.maxY - rect.minY, this->m_rect.maxY - this->m_rect.minY, WHOA_EPSILON_2) + ) { + C3Vector pos = { + this->m_justificationOffset.x * this->m_layoutScale + this->m_rect.minX, + this->m_justificationOffset.y * this->m_layoutScale + this->m_rect.minY, + this->m_layoutDepth + }; + + TextBlockSetStringPos(this->m_string, pos); + + if (rect.Sub4826D0() != this->m_rect.Sub4826D0()) { + this->OnRegionChanged(); + } + } else { + this->UpdateString(); + } + + // TODO logic if region flags & 0x40 + + if (this->m_parent) { + this->m_parent->NotifyScrollParent(); + } +} + +void CSimpleFontString::PostLoadXML(XMLNode* node, CStatus* status) { + if (this->m_parent) { + int32_t hasPoint = 0; + + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + auto point = this->m_points[i]; + + if (point && !(point->m_flags & 0x08)) { + hasPoint = 1; + break; + } + } + + if (!hasPoint) { + float offsetX = 0.0f; + float offsetY = 0.0f; + + if (this->m_styleFlags & 0x01) { + this->SetPoint(FRAMEPOINT_LEFT, this->m_parent, FRAMEPOINT_LEFT, offsetX, offsetY, 1); + } else if (this->m_styleFlags & 0x04) { + this->SetPoint(FRAMEPOINT_RIGHT, this->m_parent, FRAMEPOINT_RIGHT, offsetX, offsetY, 1); + } else { + this->SetPoint(FRAMEPOINT_CENTER, this->m_parent, FRAMEPOINT_CENTER, offsetX, offsetY, 1); + } + } + } +} + +void CSimpleFontString::RefreshEmbeddedTextures() { + // TODO +} + +void CSimpleFontString::RemoveShadow() { + // TODO +} + +int32_t CSimpleFontString::SetFont(const char* fontName, float fontHeight, uint32_t fontFlags, bool force) { + if (!force) { + const char* curFontName = this->GetFontName(); + + if ( + fontName + && curFontName + && !SStrCmpI(fontName, curFontName, STORM_MAX_STR) + && AreEqual(fontHeight, this->m_fontHeight, WHOA_EPSILON_1) + && fontFlags == this->GetFontFlags() + ) { + return 1; + } + } + + HTEXTFONT font = nullptr; + + if (fontName && fontHeight != 0.0f) { + font = TextBlockGenerateFont(fontName, fontFlags, fontHeight * this->m_layoutScale); + + if (!font) { + return 0; + } + } + + this->m_fontHeight = fontHeight; + + if (this->m_string) { + HandleClose(this->m_string); + this->m_string = nullptr; + } + + if (this->m_font) { + HandleClose(this->m_font); + } + + this->m_font = font; + + if (font) { + this->m_cachedWidth = 0.0f; + this->m_cachedHeight = 0.0f; + } + + this->m_flags &= ~0x1; + this->Resize(0); + + return 1; +} + +void CSimpleFontString::SetIndentedWordWrap(bool a2) { + // TODO +} + +void CSimpleFontString::SetJustificationOffset(float x, float y) { + if (this->m_justificationOffset.x == x && this->m_justificationOffset.y == y) { + return; + } + + this->m_justificationOffset = { x, y }; + + if (this->m_string) { + C3Vector pos = { + this->m_rect.minX + (this->m_justificationOffset.x * this->m_layoutScale), + this->m_rect.minY + (this->m_justificationOffset.y * this->m_layoutScale), + this->m_layoutDepth + }; + + TextBlockSetStringPos(this->m_string, pos); + } +} + +void CSimpleFontString::SetJustifyV(uint8_t justify) { + this->m_settableStyleFlags &= ~(0x8 | 0x10 | 0x20); + uint32_t newStyleFlags = this->m_styleFlags ^ ((justify ^ this->m_styleFlags) & (0x8 | 0x10 | 0x20)); + + if (this->m_styleFlags != newStyleFlags) { + this->m_styleFlags = newStyleFlags; + + if (this->m_string) { + this->UpdateString(); + } + } +} + +void CSimpleFontString::SetJustifyH(uint8_t justify) { + this->m_settableStyleFlags &= ~(0x1 | 0x2 | 0x4); + uint32_t newStyleFlags = this->m_styleFlags ^ ((justify ^ this->m_styleFlags) & (0x1 | 0x2 | 0x4)); + + if (this->m_styleFlags != newStyleFlags) { + this->m_styleFlags = newStyleFlags; + + if (this->m_string) { + this->UpdateString(); + } + } +} + +void CSimpleFontString::SetNonSpaceWrap(int32_t a2) { + // TODO +} + +void CSimpleFontString::SetSpacing(float spacing) { + if (spacing < 0.0f) { + spacing = 0.0f; + } + + if (fabs(spacing - this->m_spacing) >= WHOA_EPSILON_1) { + this->m_spacing = spacing; + this->m_cachedHeight = 0.0f; + + if (this->m_string) { + this->UpdateString(); + } + } +} + +void CSimpleFontString::SetStyleFlags(uint32_t styleFlags) { + uint32_t newStyleFlags = (styleFlags & this->m_settableStyleFlags) | (this->m_styleFlags & ~this->m_settableStyleFlags); + + if (this->m_styleFlags != newStyleFlags) { + this->m_styleFlags = newStyleFlags; + + if (this->m_string) { + this->UpdateString(); + } + } +} + +void CSimpleFontString::SetText(const char* text, int32_t a3) { + if (text && a3) { + text = LanguageProcess(text); + } + + if (text && *text) { + if (this->m_text && !SStrCmp(text, this->m_text, STORM_MAX_STR)) { + return; + } + + if (this->m_textMaxSize & 0xFFFF) { + SStrCopy(this->m_text, text, this->m_textMaxSize & 0xFFFF); + } else { + size_t textSize = SStrLen(text); + + if (textSize <= this->m_textCurSize) { + SStrCopy(this->m_text, text, STORM_MAX_STR); + } else { + if (this->m_text) { + SMemFree(this->m_text, __FILE__, __LINE__, 0x0); + } + + this->m_text = SStrDupA(text, __FILE__, __LINE__); + this->m_textCurSize = textSize; + } + } + } else { + if (this->m_text) { + *this->m_text = '\0'; + } + } + + this->ClearString(); +} + +int32_t CSimpleFontString::Sub482AC0() { + // TODO + return 0; +} + +void CSimpleFontString::SetTextLength(uint32_t a2) { + // TODO +} + +void CSimpleFontString::SetNonWordWrap(int32_t a2) { + // TODO +} + +void CSimpleFontString::UpdateString() { + if (!(this->m_flags & 0x1)) { + return; + } + + if (this->m_string) { + HandleClose(this->m_string); + this->m_string = nullptr; + } + + this->FreeEmbeddedTextures(); + + if (this->m_rect.maxY <= this->m_rect.minY || this->m_rect.maxX <= this->m_rect.minX) { + return; + } + + if (this->m_font && this->m_text && *this->m_text) { + C3Vector pos = { + this->m_rect.minX + this->m_justificationOffset.x * this->m_layoutScale, + this->m_rect.minY + this->m_justificationOffset.y * this->m_layoutScale, + this->m_layoutDepth + }; + + float width = this->GetWidth(); + float blockWidth = width == 0.0f ? this->m_cachedWidth : width * this->m_layoutScale; + if (NotEqual(blockWidth, this->m_rect.maxX - this->m_rect.minX, WHOA_EPSILON_2)) { + blockWidth = this->m_rect.maxX - this->m_rect.minX; + } + + float height = this->GetHeight(); + float blockHeight = height == 0.0f ? this->m_cachedHeight : height * this->m_layoutScale; + if (NotEqual(blockHeight, this->m_rect.maxY - this->m_rect.minY, WHOA_EPSILON_2)) { + blockHeight = this->m_rect.maxY - this->m_rect.minY; + } + + const char* displayText = this->GetDisplayText(blockWidth, blockHeight); + + uint32_t styleFlags = this->m_styleFlags; + + if (!(this->m_styleFlags & 0x400)) { + // TODO + + styleFlags |= 0x400; + } + + CImVector color = { 0xFF, 0xFF, 0xFF, 0xFF }; + + if (this->m_colorCount) { + color = this->m_color[0]; + } + + float scale = this->m_layoutScale; + float lineSpacing = this->m_spacing * this->m_layoutScale; + float fontHeight = this->GetFontHeight(true) * this->m_layoutScale; + + this->m_string = TextBlockCreate( + this->m_font, + displayText, + color, + pos, + fontHeight, + blockWidth, + blockHeight, + styleFlags, + 0.0f, + lineSpacing, + scale + ); + + if (this->m_styleFlags & 0x100) { + auto shadowColor = this->m_shadowColor; + // TODO alpha manipulation + + auto shadowOffset = this->m_shadowOffset; + shadowOffset.x *= this->m_layoutScale; + shadowOffset.y *= this->m_layoutScale; + + TextBlockAddShadow(this->m_string, shadowColor, shadowOffset); + } + + if (this->m_alphaGradientStart > 0) { + // TODO + } + + this->RefreshEmbeddedTextures(); + + // TODO + // - logic if region flags & 0x40, CLayoutFrame::AnimData_HasActivePosition? + } + + this->OnRegionChanged(); +} diff --git a/src/ui/CSimpleFontString.hpp b/src/ui/CSimpleFontString.hpp new file mode 100644 index 0000000..8847a7a --- /dev/null +++ b/src/ui/CSimpleFontString.hpp @@ -0,0 +1,89 @@ +#ifndef UI_C_SIMPLE_FONT_STRING_HPP +#define UI_C_SIMPLE_FONT_STRING_HPP + +#include "gx/Font.hpp" +#include "ui/CSimpleFontable.hpp" +#include "ui/CSimpleRegion.hpp" + +class CRenderBatch; +class CSimpleFrame; + +class CSimpleFontString : public CSimpleRegion, public CSimpleFontable { + public: + // Static variables + static int32_t s_count; + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + + // Member variables + HTEXTFONT m_font = nullptr; + float m_fontHeight = 0.0f; + uint16_t m_textMaxSize = 0; + uint16_t m_textCurSize = 0; + char* m_text = nullptr; + float m_spacing = 0.0f; + HTEXTBLOCK m_string = nullptr; + float m_cachedWidth = 0.0f; + float m_cachedHeight = 0.0f; + CImVector m_shadowColor; + C2Vector m_shadowOffset; + C2Vector m_justificationOffset; + int16_t m_alphaGradientStart = -1; + int16_t m_alphaGradientLength; + uint32_t m_styleFlags = 0x2 | 0x10 | 0x200; + uint32_t m_settableStyleFlags = 0x1 | 0x2 | 0x4 | 0x8 | 0x10 | 0x20 | 0x40 | 0x1000 | 0x20000; + uint32_t m_maxLines : 24; + uint32_t m_unk12b : 1; + + // Virtual member functions + virtual ~CSimpleFontString(); + virtual bool IsA(int32_t); + virtual int32_t GetScriptMetaTable(void); + virtual void LoadXML(XMLNode*, CStatus*); + virtual void OnColorChanged(bool); + virtual void OnScreenSizeChanged(); + virtual void Draw(CRenderBatch*); + virtual float GetWidth(void); + virtual float GetHeight(void); + virtual void OnFrameSizeChanged(const CRect&); + virtual void FontObjectUpdated(CSimpleFontStringAttributes&); + + // Member functions + CSimpleFontString(CSimpleFrame*, uint32_t, int32_t); + void AddShadow(const CImVector&, const C2Vector&); + void ClearString(void); + void DrawEmbeddedTextures(CRenderBatch*); + void FreeEmbeddedTextures(void); + const char* GetDisplayText(float, float); + uint32_t GetFontFlags(void); + float GetFontHeight(bool); + const char* GetFontName(void); + uint32_t GetNumCharsWithinWidth(const char*, uint32_t, float); + float GetStringHeight(void); + float GetStringWidth(void); + const char* GetText(void); + float GetTextWidth(const char*, uint32_t); + void PostLoadXML(XMLNode*, CStatus*); + void RefreshEmbeddedTextures(void); + void RemoveShadow(void); + int32_t SetFont(const char*, float, uint32_t, bool); + void SetIndentedWordWrap(bool); + void SetJustificationOffset(float, float); + void SetJustifyV(uint8_t); + void SetJustifyH(uint8_t); + void SetNonSpaceWrap(int32_t); + void SetNonWordWrap(int32_t); + void SetSpacing(float); + void SetStyleFlags(uint32_t); + void SetText(const char*, int32_t); + void SetTextLength(uint32_t); + int32_t Sub482AC0(); + void UpdateString(void); +}; + +#endif diff --git a/src/ui/CSimpleFontStringAttributes.cpp b/src/ui/CSimpleFontStringAttributes.cpp new file mode 100644 index 0000000..042179b --- /dev/null +++ b/src/ui/CSimpleFontStringAttributes.cpp @@ -0,0 +1,179 @@ +#include "ui/CSimpleFontStringAttributes.hpp" +#include "gx/Font.hpp" +#include "math/Utils.hpp" +#include "ui/CSimpleFontString.hpp" +#include + +void CSimpleFontStringAttributes::SetColor(const CImVector& color) { + if (this->m_color == color && this->m_flags & 0x400) { + return; + } + + this->m_color = color; + this->m_flags |= (0x4 | 0x400); +} + +int32_t CSimpleFontStringAttributes::SetFont(const char* fontName, float fontHeight, uint32_t fontFlags) { + if (!fontName) { + return 0; + } + + // Return early if existing attributes match arguments + if ( + this->m_font.GetString() + && !SStrCmpI(fontName, this->m_font.m_str, STORM_MAX_STR) + && AreEqual(fontHeight, this->m_fontHeight, WHOA_EPSILON_1) + && fontFlags == this->m_fontFlags + ) { + return 1; + } + + if (fontHeight <= 0.0f) { + return 0; + } + + auto v6 = TextBlockGenerateFont(fontName, fontFlags, fontHeight); + + if (!v6) { + return 0; + } + + HandleClose(v6); + + this->m_font.Copy(fontName); + this->m_fontHeight = fontHeight; + this->m_fontFlags = fontFlags; + this->m_flags |= (0x1 | 0x100); + + return 1; +} + +void CSimpleFontStringAttributes::SetIndented(bool indented) { + uint32_t styleFlags = indented + ? this->m_styleFlags | 0x20000 + : this->m_styleFlags & ~0x20000; + + if (this->m_styleFlags == styleFlags && this->m_flags & 0x200) { + return; + } + + this->m_styleFlags = styleFlags; + this->m_flags |= (0x2 | 0x200); +} + +void CSimpleFontStringAttributes::SetJustifyH(uint8_t justify) { + uint32_t styleFlags = this->m_styleFlags ^ ((justify ^ this->m_styleFlags) & 0x7); + + if (this->m_styleFlags == styleFlags && this->m_flags & 0x200) { + return; + } + + this->m_styleFlags = styleFlags; + this->m_flags |= (0x2 | 0x200); +} + +void CSimpleFontStringAttributes::SetJustifyV(uint8_t justify) { + uint32_t styleFlags = this->m_styleFlags ^ ((justify ^ this->m_styleFlags) & 0x38); + + if (this->m_styleFlags == styleFlags && this->m_flags & 0x200) { + return; + } + + this->m_styleFlags = styleFlags; + this->m_flags |= (0x2 | 0x200); +} + +void CSimpleFontStringAttributes::SetNonSpaceWrap(bool wrap) { + uint32_t styleFlags = wrap + ? this->m_styleFlags | 0x1000 + : this->m_styleFlags & ~0x1000; + + if (this->m_styleFlags == styleFlags && this->m_flags & 0x200) { + return; + } + + this->m_styleFlags = styleFlags; + this->m_flags |= (0x2 | 0x200); +} + +void CSimpleFontStringAttributes::SetShadow(const CImVector& shadowColor, const C2Vector& shadowOffset) { + if (this->m_shadowColor == shadowColor && this->m_shadowOffset == shadowOffset && this->m_flags & 0x800) { + return; + } + + this->m_shadowColor = shadowColor; + this->m_shadowOffset = shadowOffset; + this->m_flags |= (0x8 | 0x800); +} + +void CSimpleFontStringAttributes::SetSpacing(float spacing) { + if (AreEqual(spacing, this->m_spacing, WHOA_EPSILON_1) && this->m_flags & 0x1000) { + return; + } + + this->m_spacing = spacing; + this->m_flags |= (0x10 | 0x1000); +} + +void CSimpleFontStringAttributes::SetStyleFlags(uint32_t styleFlags) { + if (this->m_styleFlags == styleFlags && this->m_flags & 0x200) { + return; + } + + this->m_styleFlags = styleFlags; + this->m_flags |= (0x2 | 0x200); +} + +void CSimpleFontStringAttributes::Update(CSimpleFontString* fontString, uint32_t flags) { + STORM_ASSERT(fontString); + + uint32_t v5 = flags & this->m_flags; + + if (this->m_flags & 0x100 && v5 & 0x1) { + fontString->SetFont(this->m_font.GetString(), this->m_fontHeight, this->m_fontFlags, 0); + } + + if (this->m_flags & 0x200 && v5 & 0x2) { + fontString->SetStyleFlags(this->m_styleFlags); + } + + if (this->m_flags & 0x400 && v5 & 0x4) { + fontString->SetVertexColor(this->m_color); + } + + if (this->m_flags & 0x800 && v5 & 0x8) { + if (this->m_shadowOffset.x == 0.0f && this->m_shadowOffset.y == 0.0f) { + fontString->RemoveShadow(); + } else { + fontString->AddShadow(this->m_shadowColor, this->m_shadowOffset); + } + } + + if (this->m_flags & 0x1000 && v5 & 0x10) { + fontString->SetSpacing(this->m_spacing); + } +} + +void CSimpleFontStringAttributes::Update(CSimpleFontStringAttributes& attributes, uint32_t flags) { + uint32_t v4 = flags & this->m_flags; + + if (this->m_flags & 0x100 && v4 & 0x1) { + attributes.SetFont(this->m_font.GetString(), this->m_fontHeight, this->m_fontFlags); + } + + if (this->m_flags & 0x200 && v4 & 0x2) { + attributes.SetStyleFlags(this->m_styleFlags); + } + + if (this->m_flags & 0x400 && v4 & 0x4) { + attributes.SetColor(this->m_color); + } + + if (this->m_flags & 0x800 && v4 & 0x8) { + attributes.SetShadow(this->m_shadowColor, this->m_shadowOffset); + } + + if (this->m_flags & 0x1000 && v4 & 0x10) { + attributes.SetSpacing(this->m_spacing); + } +} diff --git a/src/ui/CSimpleFontStringAttributes.hpp b/src/ui/CSimpleFontStringAttributes.hpp new file mode 100644 index 0000000..1e8365a --- /dev/null +++ b/src/ui/CSimpleFontStringAttributes.hpp @@ -0,0 +1,37 @@ +#ifndef UI_C_SIMPLE_FONT_STRING_ATTRIBUTES_HPP +#define UI_C_SIMPLE_FONT_STRING_ATTRIBUTES_HPP + +#include +#include +#include + +class CSimpleFontString; + +class CSimpleFontStringAttributes { + public: + // Member variables + uint32_t m_flags = 0; + RCString m_font; + float m_fontHeight = 0.0f; + uint32_t m_fontFlags = 0; + float m_spacing = 0.0f; + uint32_t m_styleFlags = 0x2 | 0x10 | 0x200; + CImVector m_color = { 0, 0, 0, 0 }; + CImVector m_shadowColor = { 0, 0, 0, 0 }; + C2Vector m_shadowOffset; + + // Member functions + void SetColor(const CImVector&); + int32_t SetFont(const char*, float, uint32_t); + void SetIndented(bool); + void SetJustifyV(uint8_t); + void SetJustifyH(uint8_t); + void SetNonSpaceWrap(bool); + void SetShadow(const CImVector&, const C2Vector&); + void SetSpacing(float); + void SetStyleFlags(uint32_t); + void Update(CSimpleFontString*, uint32_t); + void Update(CSimpleFontStringAttributes&, uint32_t); +}; + +#endif diff --git a/src/ui/CSimpleFontStringScript.cpp b/src/ui/CSimpleFontStringScript.cpp new file mode 100644 index 0000000..74997b6 --- /dev/null +++ b/src/ui/CSimpleFontStringScript.cpp @@ -0,0 +1,264 @@ +#include "ui/CSimpleFontStringScript.hpp" +#include "ui/CSimpleFont.hpp" +#include "ui/CSimpleFontString.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleFontString_IsObjectType(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetObjectType(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetDrawLayer(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetDrawLayer(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetVertexColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetAlphaGradient(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_Show(lua_State* L) { + auto type = CSimpleFontString::GetObjectType(); + auto string = static_cast(FrameScript_GetObjectThis(L, type)); + + string->Show(); + + return 0; +} + +int32_t CSimpleFontString_Hide(lua_State* L) { + auto type = CSimpleFontString::GetObjectType(); + auto string = static_cast(FrameScript_GetObjectThis(L, type)); + + string->Hide(); + + return 0; +} + +int32_t CSimpleFontString_IsVisible(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_IsShown(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetFontObject(lua_State* L) { + auto type = CSimpleFontString::GetObjectType(); + auto string = static_cast(FrameScript_GetObjectThis(L, type)); + + CSimpleFont* font = nullptr; + + if (lua_type(L, 2) == LUA_TTABLE) { + lua_rawgeti(L, 2, 0); + font = static_cast(lua_touserdata(L, -1)); + lua_settop(L, -2); + + if (!font) { + luaL_error(L, "%s:SetFontObject(): Couldn't find 'this' in font object", string->GetDisplayName()); + } + + if (!font->IsA(CSimpleFont::GetObjectType())) { + luaL_error(L, "%s:SetFontObject(): Wrong object type, expected font", string->GetDisplayName()); + } + } else if (lua_type(L, 2) == LUA_TSTRING) { + auto fontName = lua_tolstring(L, 2, nullptr); + font = CSimpleFont::GetFont(fontName, 0); + + if (!font) { + luaL_error(L, "%s:SetFontObject(): Couldn't find font named %s", string->GetDisplayName(), fontName); + } + } else if (lua_type(L, 2) != LUA_TNIL) { + luaL_error(L, "Usage: %s:SetFontObject(font or \"font\" or nil)", string->GetDisplayName()); + } + + string->SetFontObject(font); + + return 0; +} + +int32_t CSimpleFontString_GetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetText(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetFieldSize(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetText(lua_State* L) { + auto type = CSimpleFontString::GetObjectType(); + auto string = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!string->m_font) { + luaL_error(L, "%s:SetText(): Font not set", string->GetDisplayName()); + } + + const char* text = lua_tolstring(L, 2, 0); + string->SetText(text, 1); + + return 0; +} + +int32_t CSimpleFontString_SetFormattedText(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetTextHeight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetStringWidth(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetStringHeight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_CanNonSpaceWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetNonSpaceWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_CanWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_GetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFontString_SetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleFontStringMethods[NUM_SIMPLE_FONT_STRING_SCRIPT_METHODS] = { + { "IsObjectType", &CSimpleFontString_IsObjectType }, + { "GetObjectType", &CSimpleFontString_GetObjectType }, + { "GetDrawLayer", &CSimpleFontString_GetDrawLayer }, + { "SetDrawLayer", &CSimpleFontString_SetDrawLayer }, + { "SetVertexColor", &CSimpleFontString_SetVertexColor }, + { "GetAlpha", &CSimpleFontString_GetAlpha }, + { "SetAlpha", &CSimpleFontString_SetAlpha }, + { "SetAlphaGradient", &CSimpleFontString_SetAlphaGradient }, + { "Show", &CSimpleFontString_Show }, + { "Hide", &CSimpleFontString_Hide }, + { "IsVisible", &CSimpleFontString_IsVisible }, + { "IsShown", &CSimpleFontString_IsShown }, + { "GetFontObject", &CSimpleFontString_GetFontObject }, + { "SetFontObject", &CSimpleFontString_SetFontObject }, + { "GetFont", &CSimpleFontString_GetFont }, + { "SetFont", &CSimpleFontString_SetFont }, + { "GetText", &CSimpleFontString_GetText }, + { "GetFieldSize", &CSimpleFontString_GetFieldSize }, + { "SetText", &CSimpleFontString_SetText }, + { "SetFormattedText", &CSimpleFontString_SetFormattedText }, + { "GetTextColor", &CSimpleFontString_GetTextColor }, + { "SetTextColor", &CSimpleFontString_SetTextColor }, + { "GetShadowColor", &CSimpleFontString_GetShadowColor }, + { "SetShadowColor", &CSimpleFontString_SetShadowColor }, + { "GetShadowOffset", &CSimpleFontString_GetShadowOffset }, + { "SetShadowOffset", &CSimpleFontString_SetShadowOffset }, + { "GetSpacing", &CSimpleFontString_GetSpacing }, + { "SetSpacing", &CSimpleFontString_SetSpacing }, + { "SetTextHeight", &CSimpleFontString_SetTextHeight }, + { "GetStringWidth", &CSimpleFontString_GetStringWidth }, + { "GetStringHeight", &CSimpleFontString_GetStringHeight }, + { "GetJustifyH", &CSimpleFontString_GetJustifyH }, + { "SetJustifyH", &CSimpleFontString_SetJustifyH }, + { "GetJustifyV", &CSimpleFontString_GetJustifyV }, + { "SetJustifyV", &CSimpleFontString_SetJustifyV }, + { "CanNonSpaceWrap", &CSimpleFontString_CanNonSpaceWrap }, + { "SetNonSpaceWrap", &CSimpleFontString_SetNonSpaceWrap }, + { "CanWordWrap", &CSimpleFontString_CanWordWrap }, + { "SetWordWrap", &CSimpleFontString_SetWordWrap }, + { "GetIndentedWordWrap", &CSimpleFontString_GetIndentedWordWrap }, + { "SetIndentedWordWrap", &CSimpleFontString_SetIndentedWordWrap } +}; diff --git a/src/ui/CSimpleFontStringScript.hpp b/src/ui/CSimpleFontStringScript.hpp new file mode 100644 index 0000000..914aca3 --- /dev/null +++ b/src/ui/CSimpleFontStringScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_FONT_STRING_SCRIPT_HPP +#define UI_C_SIMPLE_FONT_STRING_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_FONT_STRING_SCRIPT_METHODS 41 + +extern FrameScript_Method SimpleFontStringMethods[NUM_SIMPLE_FONT_STRING_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleFontable.cpp b/src/ui/CSimpleFontable.cpp new file mode 100644 index 0000000..8d16113 --- /dev/null +++ b/src/ui/CSimpleFontable.cpp @@ -0,0 +1,30 @@ +#include "ui/CSimpleFontable.hpp" +#include "ui/CSimpleFont.hpp" + +CSimpleFontable::~CSimpleFontable() { + this->SetFontObject(nullptr); +} + +CSimpleFont* CSimpleFontable::GetFontObject() { + return this->m_fontObject; +} + +void CSimpleFontable::SetFontObject(CSimpleFont* fontObject) { + if (this->m_fontObject == fontObject) { + return; + } + + if (this->m_fontObject) { + this->m_fontObject->m_fontableList.UnlinkNode(this); + } + + this->m_fontObject = fontObject; + + if (fontObject) { + fontObject->m_fontableList.LinkToTail(this); + + fontObject->m_attributes.m_flags |= FLAG_COMPLETE_UPDATE; + this->FontObjectUpdated(fontObject->m_attributes); + fontObject->m_attributes.m_flags &= ~(FLAG_COMPLETE_UPDATE); + } +} diff --git a/src/ui/CSimpleFontable.hpp b/src/ui/CSimpleFontable.hpp new file mode 100644 index 0000000..b081fe8 --- /dev/null +++ b/src/ui/CSimpleFontable.hpp @@ -0,0 +1,26 @@ +#ifndef UI_C_SIMPLE_FONTABLE_HPP +#define UI_C_SIMPLE_FONTABLE_HPP + +#include "ui/Types.hpp" +#include + +class CSimpleFont; +class CSimpleFontStringAttributes; + +class CSimpleFontable { + public: + // Member variables + CSimpleFont* m_fontObject = nullptr; + uint32_t m_fontableFlags = FLAG_COMPLETE_UPDATE; + TSLink m_fontableLink; + + // Virtual member functions + virtual ~CSimpleFontable(); + virtual void FontObjectUpdated(CSimpleFontStringAttributes&) = 0; + + // Member functions + CSimpleFont* GetFontObject(void); + void SetFontObject(CSimpleFont*); +}; + +#endif diff --git a/src/ui/CSimpleFontedFrame.hpp b/src/ui/CSimpleFontedFrame.hpp new file mode 100644 index 0000000..fcffd82 --- /dev/null +++ b/src/ui/CSimpleFontedFrame.hpp @@ -0,0 +1,14 @@ +#ifndef UI_C_SIMPLE_FONTED_FRAME_HPP +#define UI_C_SIMPLE_FONTED_FRAME_HPP + +#include + +class CSimpleFontedFrameFont; + +class CSimpleFontedFrame { + public: + // Virtual member functions + virtual void FontUpdated(CSimpleFontedFrameFont*, int32_t) = 0; +}; + +#endif diff --git a/src/ui/CSimpleFontedFrameFont.hpp b/src/ui/CSimpleFontedFrameFont.hpp new file mode 100644 index 0000000..dff18d1 --- /dev/null +++ b/src/ui/CSimpleFontedFrameFont.hpp @@ -0,0 +1,19 @@ +#ifndef UI_C_SIMPLE_FONTED_FRAME_FONT_HPP +#define UI_C_SIMPLE_FONTED_FRAME_FONT_HPP + +#include "ui/CSimpleFont.hpp" + +class CSimpleFontedFrame; + +class CSimpleFontedFrameFont : public CSimpleFont { + public: + // Member variables + CSimpleFontedFrame* m_frame; + + // Member functions + CSimpleFontedFrameFont(CSimpleFontedFrame* frame) + : m_frame(frame) + {}; +}; + +#endif diff --git a/src/ui/CSimpleFrame.cpp b/src/ui/CSimpleFrame.cpp new file mode 100644 index 0000000..2173950 --- /dev/null +++ b/src/ui/CSimpleFrame.cpp @@ -0,0 +1,1584 @@ +#include "ui/CSimpleFrame.hpp" +#include "event/Event.hpp" +#include "event/Input.hpp" +#include "gx/Coordinate.hpp" +#include "math/Utils.hpp" +#include "ui/CBackdropGenerator.hpp" +#include "ui/CScriptRegion.hpp" +#include "ui/CSimpleFontString.hpp" +#include "ui/CSimpleFrameScript.hpp" +#include "ui/CSimpleRender.hpp" +#include "ui/CSimpleScrollFrame.hpp" +#include "ui/CSimpleTitleRegion.hpp" +#include "ui/CSimpleTexture.hpp" +#include "ui/CSimpleTop.hpp" +#include "ui/FrameScript_Object.hpp" +#include "ui/FrameXML.hpp" +#include "ui/LoadXML.hpp" +#include "util/Lua.hpp" +#include "util/StringTo.hpp" +#include +#include +#include +#include +#include + +int32_t CSimpleFrame::s_metatable; +int32_t CSimpleFrame::s_objectType; + +int32_t CSimpleFrame::GetObjectType() { + if (!CSimpleFrame::s_objectType) { + CSimpleFrame::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleFrame::s_objectType; +} + +void CSimpleFrame::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleFrame::RegisterScriptMethods); + CSimpleFrame::s_metatable = ref; +} + +void CSimpleFrame::RegisterScriptMethods(lua_State* L) { + CScriptRegion::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleFrameMethods, NUM_SIMPLE_FRAME_SCRIPT_METHODS); +} + +CSimpleFrame::CSimpleFrame(CSimpleFrame* parent) : CScriptRegion() { + // TODO + // other constructor logic + + this->m_top = CSimpleTop::s_instance; + this->m_top->RegisterFrame(this); + + this->SetParent(parent); + + this->m_drawenabled[DRAWLAYER_BACKGROUND] = 1; + this->m_drawenabled[DRAWLAYER_BACKGROUND_BORDER] = 1; + this->m_drawenabled[DRAWLAYER_ARTWORK] = 1; + this->m_drawenabled[DRAWLAYER_ARTWORK_OVERLAY] = 1; + this->m_drawenabled[DRAWLAYER_HIGHLIGHT] = 0; + + this->Show(); +} + +CSimpleFrame::~CSimpleFrame() { + // TODO + + this->m_intAC = 3; + + // TODO +} + +void CSimpleFrame::AddFrameRegion(CSimpleRegion* region, uint32_t drawlayer) { + region->SetLayoutScale(this->m_layoutScale, 0); + this->m_drawlayers[drawlayer].LinkToTail(region); + this->NotifyDrawLayerChanged(drawlayer); +} + +void CSimpleFrame::DisableDrawLayer(uint32_t drawlayer) { + this->m_drawenabled[drawlayer] = 0; + this->NotifyDrawLayerChanged(drawlayer); +} + +void CSimpleFrame::EnableDrawLayer(uint32_t drawlayer) { + this->m_drawenabled[drawlayer] = 1; + this->NotifyDrawLayerChanged(drawlayer); +} + +void CSimpleFrame::LoadXML(XMLNode* node, CStatus* status) { + const char* inherits = node->GetAttributeByName("inherits"); + + const char** v68 = &inherits; + char inheritName[1024]; + + if (inherits && *inherits) { + do { + SStrTokenize(v68, inheritName, 0x400u, " ,", NULL); + + if (!*inheritName) { + break; + } + + const char* tainted; + bool locked; + + XMLNode* inheritNode = FrameXML_AcquireHashNode(inheritName, tainted, locked); + + if (inheritNode) { + if (locked) { + status->Add(STATUS_WARNING, "Recursively inherited node: %s", inheritName); + } else { + // TODO lua taint management + + this->LoadXML(inheritNode, status); + + // TODO lua taint management + + FrameXML_ReleaseHashNode(inheritName); + } + } else { + status->Add(STATUS_WARNING, "Couldn't find inherited node: %s", inheritName); + } + + } while (*inheritName); + } + + CScriptRegion::LoadXML(node, status); + + // hidden + + const char* hidden = node->GetAttributeByName("hidden"); + + if (hidden && *hidden) { + if (StringToBOOL(hidden)) { + this->Hide(); + } else { + this->Show(); + } + } + + // toplevel + + const char* toplevel = node->GetAttributeByName("toplevel"); + + if (toplevel && *toplevel) { + this->SetFrameFlag(0x1, StringToBOOL(toplevel)); + } + + // movable + + const char* movable = node->GetAttributeByName("movable"); + + if (movable && *movable) { + this->SetFrameFlag(0x100, StringToBOOL(movable)); + } + + // dontSavePosition + + const char* dontSavePosition = node->GetAttributeByName("dontSavePosition"); + + if (dontSavePosition && *dontSavePosition) { + this->SetFrameFlag(0x80000, StringToBOOL(dontSavePosition)); + } + + // resizable + + const char* resizable = node->GetAttributeByName("resizable"); + + if (resizable && *resizable) { + this->SetFrameFlag(0x200, StringToBOOL(resizable)); + } + + // frameStrata + + const char* frameStrata = node->GetAttributeByName("frameStrata"); + + if (frameStrata && *frameStrata) { + FRAME_STRATA strata; + + if (StringToFrameStrata(frameStrata, strata)) { + this->SetFrameStrata(strata); + } else { + status->Add( + STATUS_WARNING, + "Frame %s: Unknown frame strata: %s", + this->GetName() || "", + frameStrata + ); + } + } + + // frameLevel + + const char* frameLevel = node->GetAttributeByName("frameLevel"); + + if (frameLevel && *frameLevel) { + int32_t level = SStrToInt(frameLevel); + + if (level > 0) { + this->SetFrameLevel(level, 1); + } else { + status->Add( + STATUS_WARNING, + "Frame %s: Unknown frame level: %s", + this->GetName() || "", + frameLevel + ); + } + } + + // TODO alpha + + // enableMouse + + const char* enableMouse = node->GetAttributeByName("enableMouse"); + + if (enableMouse && *enableMouse && StringToBOOL(enableMouse)) { + this->EnableEvent(SIMPLE_EVENT_MOUSE, -1); + } + + // enableKeyboard + + const char* enableKeyboard = node->GetAttributeByName("enableKeyboard"); + + if (enableKeyboard && *enableKeyboard && StringToBOOL(enableKeyboard)) { + this->EnableEvent(SIMPLE_EVENT_CHAR, -1); + this->EnableEvent(SIMPLE_EVENT_KEY, -1); + } + + // TODO clampedToScreen + + // TODO protected + + // depth + + const char* depth = node->GetAttributeByName("depth"); + + if (depth && *depth) { + float d = SStrToFloat(depth); + + if (fabs(d - this->m_depth) >= 0.00000023841858) { + this->m_depth = d; + this->UpdateDepth(false); + } + } + + // Children + + XMLNode* child = node->m_child; + + while (child) { + if (!SStrCmpI(child->GetName(), "TitleRegion", 0x7FFFFFFFu)) { + // TODO + } + + if (!SStrCmpI(child->GetName(), "ResizeBounds", 0x7FFFFFFFu)) { + // TODO + } + + if (!SStrCmpI(child->GetName(), "Backdrop", 0x7FFFFFFFu)) { + this->LoadXML_Backdrop(child, status); + } + + if (!SStrCmpI(child->GetName(), "HitRectInsets", 0x7FFFFFFFu)) { + // TODO + } + + if (!SStrCmpI(child->GetName(), "Layers", 0x7FFFFFFFu)) { + this->LoadXML_Layers(child, status); + } + + if (!SStrCmpI(child->GetName(), "Attributes", 0x7FFFFFFFu)) { + this->LoadXML_Attributes(child, status); + } + + if (!SStrCmpI(child->GetName(), "Scripts", 0x7FFFFFFFu)) { + this->LoadXML_Scripts(child, status); + } + + child = child->m_next; + } +} + +void CSimpleFrame::PostLoadXML(XMLNode* node, CStatus* status) { + this->m_loading = 0; + + if (this->m_visible) { + this->SetDeferredResize(0); + + CSimpleTitleRegion* titleRegion = this->m_titleRegion; + + if (titleRegion) { + titleRegion->SetDeferredResize(0); + } + + CSimpleRegion* region = this->m_regions.Head(); + + while (region) { + region->SetDeferredResize(0); + region = region->m_regionLink.Next(); + } + } + + this->PostLoadXML_Frames(node, status); + + // TODO + // Something to do with alpha animations + // + // int32_t v9 = CSimpleAlphaAnim::GetObjectType(); + // this->sub4887F0(v9, &v13, &v14); + // + // if (v13) { + // this->Vfptr26(); + // + // v10 = LOBYTE(v4->simpleframe_unk3); + // HIBYTE(v4->simpleframe_unk3) ^= (HIBYTE(v4->simpleframe_unk3) ^ 2 * v14) & 2; + // BYTE2(v4->simpleframe_unk3) = v10 * BYTE1(v4->simpleframe_unk3) / 255; + // + // auto region = this->m_regions.Head(); + // while (region) { + // region->Vfptr15(0, 1, v14); + // region = region->m_regionLink.Next(); + // } + // } + + this->RunOnLoadScript(); + + if (this->m_visible) { + this->RunOnShowScript(); + } +} + +void CSimpleFrame::Raise() { + this->m_top->RaiseFrame(this, 1); +} + +void CSimpleFrame::RegisterForEvents(int32_t a2) { + for (int32_t event = SIMPLE_EVENT_CHAR; event < NUM_SIMPLE_EVENTS; event++) { + if (this->m_eventmask & (1 << event)) { + this->m_top->RegisterForEvent(this, static_cast(event), a2, -1); + } + } +} + +void CSimpleFrame::RunOnCharScript(const char* chr) { + if (this->m_onChar.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, chr); + + this->RunScript(this->m_onChar, 1, 0); + } +} + +void CSimpleFrame::RunOnEnterScript(int32_t a2) { + if (this->m_onEnter.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushboolean(L, a2); + + this->RunScript(this->m_onEnter, 1, 0); + } +} + +void CSimpleFrame::RunOnHideScript() { + if (this->m_onHide.luaRef && !this->m_loading) { + this->RunScript(this->m_onHide, 0, 0); + } +} + +void CSimpleFrame::RunOnKeyDownScript(const char* key) { + if (this->m_onKeyDown.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, key); + + this->RunScript(this->m_onKeyDown, 1, 0); + } +} + +void CSimpleFrame::RunOnKeyUpScript(const char* key) { + if (this->m_onKeyUp.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, key); + + this->RunScript(this->m_onKeyUp, 1, 0); + } +} + +void CSimpleFrame::RunOnLeaveScript(int32_t a2) { + if (this->m_onLeave.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushboolean(L, a2); + + this->RunScript(this->m_onLeave, 1, 0); + } +} + +void CSimpleFrame::RunOnLoadScript() { + if (this->m_onLoad.luaRef) { + this->RunScript(this->m_onLoad, 0, 0); + } +} + +void CSimpleFrame::RunOnMouseDownScript(const char* btn) { + if (this->m_onMouseDown.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, btn); + + this->RunScript(this->m_onMouseDown, 1, 0); + } +} + +void CSimpleFrame::RunOnMouseUpScript(const char* btn) { + if (this->m_onMouseUp.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushstring(L, btn); + + this->RunScript(this->m_onMouseUp, 1, 0); + } +} + +void CSimpleFrame::RunOnShowScript() { + if (this->m_onShow.luaRef && !this->m_loading) { + this->RunScript(this->m_onShow, 0, 0); + } +} + +void CSimpleFrame::RunOnSizeChangedScript(float width, float height) { + if (this->m_onSizeChanged.luaRef) { + auto L = FrameScript_GetContext(); + + float ddcWidth = CoordinateGetAspectCompensation() * 1024.0f * width; + float ndcWidth = DDCToNDCWidth(ddcWidth); + lua_pushnumber(L, ndcWidth); + + float ddcHeight = CoordinateGetAspectCompensation() * 1024.0f * height; + float ndcHeight = DDCToNDCWidth(ddcHeight); + lua_pushnumber(L, ndcHeight); + + this->RunScript(this->m_onSizeChanged, 2, 0); + } +} + +void CSimpleFrame::RunOnUpdateScript(float elapsedSec) { + if (this->m_onUpdate.luaRef) { + auto L = FrameScript_GetContext(); + lua_pushnumber(L, elapsedSec); + this->RunScript(this->m_onUpdate, 1, nullptr); + } +} + +void CSimpleFrame::PreLoadXML(XMLNode* node, CStatus* status) { + const char* name = node->GetAttributeByName("name"); + + if (name && *name) { + this->SetName(name); + } + + const char* id = node->GetAttributeByName("id"); + + if (id && *id) { + int32_t idNum = SStrToInt(id); + + if (idNum >= 0) { + this->m_id = idNum; + } + } + + this->m_loading = 1; + + this->SetDeferredResize(1); + + CSimpleTitleRegion* titleRegion = this->m_titleRegion; + + if (titleRegion) { + titleRegion->SetDeferredResize(1); + } + + CSimpleRegion* region = this->m_regions.Head(); + + while (region) { + region->SetDeferredResize(1); + region = region->m_regionLink.Next(); + } +} + +int32_t CSimpleFrame::GetBoundsRect(CRect& bounds) { + if (this->IsResizePending()) { + this->Resize(1); + } + + CRect rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (this->GetRect(&rect)) { + bounds.minX = rect.minX >= bounds.minX ? bounds.minX : rect.minX; + bounds.maxX = rect.maxX <= bounds.maxX ? bounds.maxX : rect.maxX; + bounds.minY = rect.minY >= bounds.minY ? bounds.minY : rect.minY; + bounds.maxY = rect.maxY <= bounds.maxY ? bounds.maxY : rect.maxY; + } + + for (auto region = this->m_regions.Head(); region; region = this->m_regions.Next(region)) { + if (region->IsShown()) { + if (region->IsResizePending()) { + region->Resize(1); + } + + if (region->GetRect(&rect)) { + bounds.minX = rect.minX >= bounds.minX ? bounds.minX : rect.minX; + bounds.maxX = rect.maxX <= bounds.maxX ? bounds.maxX : rect.maxX; + bounds.minY = rect.minY >= bounds.minY ? bounds.minY : rect.minY; + bounds.maxY = rect.maxY <= bounds.maxY ? bounds.maxY : rect.maxY; + } + } + } + + for (auto child = this->m_children.Head(); child; child = this->m_children.Next(child)) { + if (child->frame->m_shown) { + child->frame->GetBoundsRect(bounds); + } + } + + return bounds.maxY > bounds.minY && bounds.maxX > bounds.minX; +} + +int32_t CSimpleFrame::GetHitRect(CRect& rect) { + if (!(this->CLayoutFrame::m_flags & 0x1)) { + return 0; + } + + rect = this->m_hitRect; + + return 1; +} + +FrameScript_Object::ScriptIx* CSimpleFrame::GetScriptByName(const char* name, FrameScript_Object::ScriptData& data) { + auto parentScript = FrameScript_Object::GetScriptByName(name, data); + + if (parentScript) { + return parentScript; + } + + if (!SStrCmpI(name, "OnLoad", 0x7FFFFFFFu)) { + return &this->m_onLoad; + } + + if (!SStrCmpI(name, "OnSizeChanged", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,w,h) %s end"; + return &this->m_onSizeChanged; + } + + if (!SStrCmpI(name, "OnUpdate", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,elapsed) %s end"; + return &this->m_onUpdate; + } + + if (!SStrCmpI(name, "OnShow", 0x7FFFFFFFu)) { + return &this->m_onShow; + } + + if (!SStrCmpI(name, "OnHide", 0x7FFFFFFFu)) { + return &this->m_onHide; + } + + if (!SStrCmpI(name, "OnEnter", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,motion) %s end"; + return &this->m_onEnter; + } + + if (!SStrCmpI(name, "OnLeave", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,motion) %s end"; + return &this->m_onLeave; + } + + if (!SStrCmpI(name, "OnMouseDown", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,button) %s end"; + return &this->m_onMouseDown; + } + + if (!SStrCmpI(name, "OnMouseUp", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,button) %s end"; + return &this->m_onMouseUp; + } + + if (!SStrCmpI(name, "OnMouseWheel", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,delta) %s end"; + return &this->m_onMouseWheel; + } + + if (!SStrCmpI(name, "OnDragStart", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,button) %s end"; + return &this->m_onDragStart; + } + + if (!SStrCmpI(name, "OnDragStop", 0x7FFFFFFFu)) { + return &this->m_onDragStop; + } + + if (!SStrCmpI(name, "OnReceiveDrag", 0x7FFFFFFFu)) { + return &this->m_onReceiveDrag; + } + + if (!SStrCmpI(name, "OnChar", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,text) %s end"; + return &this->m_onChar; + } + + if (!SStrCmpI(name, "OnKeyDown", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,key) %s end"; + return &this->m_onKeyDown; + } + + if (!SStrCmpI(name, "OnKeyUp", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,key) %s end"; + return &this->m_onKeyUp; + } + + if (!SStrCmpI(name, "OnAttributeChanged", 0x7FFFFFFFu)) { + data.wrapper = "return function(self,name,value) %s end"; + return &this->m_onAttributeChange; + } + + if (!SStrCmpI(name, "OnEnable", 0x7FFFFFFFu)) { + return &this->m_onEnable; + } + + if (!SStrCmpI(name, "OnDisable", 0x7FFFFFFFu)) { + return &this->m_onDisable; + } + + return NULL; +} + +int32_t CSimpleFrame::GetScriptMetaTable() { + return CSimpleFrame::s_metatable; +} + +bool CSimpleFrame::IsA(int32_t type) { + return type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleFrame::EnableEvent(CSimpleEventType eventType, int32_t priority) { + if ((1 << eventType) & this->m_eventmask) { + return; + } + + if (this->m_visible) { + this->m_top->RegisterForEvent(this, eventType, 0, priority); + } + + this->m_eventmask |= 1 << eventType; +} + +void CSimpleFrame::Hide() { + if (this->ProtectedFunctionsAllowed()) { + this->m_shown = 0; + this->HideThis(); + } else { + // TODO + // auto v2 = this->m_top->unk5[8]; + + // if (v2) { + // v2(0); + // } + } +} + +int32_t CSimpleFrame::HideThis() { + if (!this->m_visible) { + return 1; + } + + if (!this->m_loading) { + this->SetDeferredResize(1); + + if (this->m_titleRegion) { + this->m_titleRegion->SetDeferredResize(1); + } + } + + this->m_visible = 0; + this->m_top->HideFrame(this, 0); + + for (auto region = this->m_regions.Head(); region; region = this->m_regions.Link(region)->Next()) { + region->HideThis(); + } + + auto child = this->m_children.Head(); + + while (child) { + child->frame->HideThis(); + child = this->m_children.Link(child)->Next(); + } + + this->OnLayerHide(); + + return 1; +} + +void CSimpleFrame::LoadXML_Attributes(XMLNode* node, CStatus* status) { + // TODO +} + +void CSimpleFrame::LoadXML_Backdrop(XMLNode* node, CStatus* status) { + void* m = SMemAlloc(sizeof(CBackdropGenerator), __FILE__, __LINE__, 0x0); + auto backdrop = new (m) CBackdropGenerator(); + + backdrop->LoadXML(node, status); + this->SetBackdrop(backdrop); +} + +void CSimpleFrame::LoadXML_Layers(XMLNode* node, CStatus* status) { + if (!node->m_child) { + return; + } + + XMLNode* layerNode = node->m_child; + + while (layerNode) { + if (SStrCmpI(layerNode->GetName(), "Layer", 0x7FFFFFFFu)) { + const char* layerNodeName = layerNode->GetName(); + const char* nodeName = node->GetName(); + const char* frameName = this->GetName(); + + if (!frameName) { + frameName = ""; + } + + status->Add(STATUS_WARNING, "Frame %s: Unknown child node in %s element: %s", frameName, nodeName, layerNodeName); + } else { + int32_t drawlayer = 2; + + const char* levelStr = layerNode->GetAttributeByName("level"); + + if (levelStr && *levelStr) { + StringToDrawLayer(levelStr, drawlayer); + } + + XMLNode* layerChild; + + for (layerChild = layerNode->m_child; layerChild; layerChild = layerChild->m_next) { + if (SStrCmpI(layerChild->GetName(), "Texture", 0x7FFFFFFFu)) { + if (SStrCmpI(layerChild->GetName(), "FontString", 0x7FFFFFFFu)) { + const char* layerChildName = layerChild->GetName(); + const char* layerNodeName = layerNode->GetName(); + const char* frameName = this->GetName(); + + if (!frameName) { + frameName = ""; + } + + status->Add(STATUS_WARNING, "Frame %s: Unknown child node in %s element: %s", frameName, layerNodeName, layerChildName); + } else { + CSimpleFontString* layerString = LoadXML_String(layerChild, this, status); + layerString->SetFrame(this, drawlayer, layerString->m_shown); + } + } else { + CSimpleTexture* layerTexture = LoadXML_Texture(layerChild, this, status); + layerTexture->SetFrame(this, drawlayer, layerTexture->m_shown); + } + } + } + + layerNode = layerNode->m_next; + } +} + +void CSimpleFrame::LoadXML_Scripts(XMLNode* root, CStatus* status) { + lua_State* L = FrameScript_GetContext(); + + const char* scriptName; + const char* scriptBody; + ScriptData scriptData; + ScriptIx* script; + + XMLNode* node = root->m_child; + + while (node) { + scriptName = node->GetName(); + scriptBody = node->m_body; + scriptData.wrapper = "return function(self) %s end"; + script = this->GetScriptByName(scriptName, scriptData); + + if (script) { + if (script->luaRef) { + luaL_unref(L, LUA_REGISTRYINDEX, script->luaRef); + } + + if (!node->m_userData && scriptBody && *scriptBody) { + char compileName[1024]; + + SStrPrintf(compileName, 1024, "*:%s", scriptName); + + void* scriptFunction = SMemAlloc(sizeof(ScriptFunction), __FILE__, __LINE__, 0); + + if (scriptFunction) { + int32_t luaRef = FrameScript_CompileFunction(compileName, scriptData.wrapper, scriptBody, status); + + scriptFunction = new (scriptFunction) ScriptFunction(); + static_cast(scriptFunction)->luaRef = luaRef; + } else { + scriptFunction = NULL; + } + + node->m_userData = scriptFunction; + } + + const char* functionLookup = node->GetAttributeByName("function"); + + // Set script to already defined function with matching name + if (functionLookup && *functionLookup) { + int32_t luaRef; + const char* frameName; + + lua_pushstring(L, functionLookup); + lua_rawget(L, LUA_GLOBALSINDEX); + + luaRef = luaL_ref(L, LUA_REGISTRYINDEX); + + if (luaRef == -1) { + frameName = this->GetName(); + + if (!frameName) { + frameName = ""; + } + + status->Add(STATUS_WARNING, "Frame %s: Unknown function %s in element %s", frameName, functionLookup, scriptName); + + script->luaRef = 0; + } else { + script->luaRef = luaRef; + } + // Set script to function compiled from body of script element + // eg. foo(); + } else if (node->m_userData) { + lua_rawgeti(L, LUA_REGISTRYINDEX, static_cast(node->m_userData)->luaRef); + script->luaRef = luaL_ref(L, LUA_REGISTRYINDEX); + // Disable script + } else { + script->luaRef = 0; + } + + script->unk = NULL; + + if (!SStrCmpI(node->GetName(), "OnChar", 0x7FFFFFFFu) && !(this->m_eventmask & 1)) { + if (this->m_visible) { + this->m_top->RegisterForEvent(this, SIMPLE_EVENT_CHAR, 0, -1); + } + + this->m_eventmask |= 1u; + } + + if ((!SStrCmpI(node->GetName(), "OnKeyDown", 0x7FFFFFFFu) || !SStrCmpI(node->GetName(), "OnKeyUp", 0x7FFFFFFFu)) && !(this->m_eventmask & 2)) { + if (this->m_visible) { + this->m_top->RegisterForEvent(this, SIMPLE_EVENT_KEY, 0, -1); + } + + this->m_eventmask |= 2u; + } + + if ((!SStrCmpI(node->GetName(), "OnEnter", 0x7FFFFFFFu) || !SStrCmpI(node->GetName(), "OnLeave", 0x7FFFFFFFu) || !SStrCmpI(node->GetName(), "OnMouseDown", 0x7FFFFFFFu) || !SStrCmpI(node->GetName(), "OnMouseUp", 0x7FFFFFFFu) || !SStrCmpI(node->GetName(), "OnDragStart", 0x7FFFFFFFu)) && !(this->m_eventmask & 4)) { + if (this->m_visible) { + this->m_top->RegisterForEvent(this, SIMPLE_EVENT_MOUSE, 0, -1); + } + + this->m_eventmask |= 4u; + } + + if (!SStrCmpI(node->GetName(), "OnMouseWheel", 0x7FFFFFFFu) && !(this->m_eventmask & 8)) { + if (this->m_visible) { + this->m_top->RegisterForEvent(this, SIMPLE_EVENT_MOUSEWHEEL, 0, -1); + } + + this->m_eventmask |= 8u; + } + } else { + const char* frameName = this->GetName(); + + if (!frameName) { + frameName = ""; + } + + status->Add(STATUS_WARNING, "Frame %s: Unknown script element %s", frameName, scriptName); + } + + node = node->m_next; + } +} + +void CSimpleFrame::LockHighlight(int32_t lock) { + if (this->m_highlightLocked == lock) { + return; + } + + this->m_highlightLocked = lock; + + if (lock) { + this->EnableDrawLayer(DRAWLAYER_HIGHLIGHT); + } else { + if (this->m_top->m_mouseFocus != this) { + this->DisableDrawLayer(DRAWLAYER_HIGHLIGHT); + } + } +} + +void CSimpleFrame::NotifyDrawLayerChanged(uint32_t drawlayer) { + if (this->m_flags & 0x2000) { + this->m_batchDirty |= 1 << drawlayer; + } else if (this->m_top && this->m_visible) { + this->m_top->NotifyFrameLayerChanged(this, drawlayer); + } + + this->NotifyScrollParent(); +} + +void CSimpleFrame::NotifyScrollParent() { + if (!(this->m_flags & 0x2000) || !this->m_visible) { + return; + } + + auto parent = this; + while (!(parent->m_flags & 0x4000)) { + parent = parent->m_parent; + } + parent = parent->m_parent; + + if (parent && parent->m_intAC != 3) { + static_cast(parent)->m_updateScrollChild = 1; + } +} + +void CSimpleFrame::OnFrameRender() { + auto batchDirty = this->m_batchDirty; + this->m_batchDirty = 0; + + if (batchDirty) { + for (int32_t layer = 0; layer < NUM_SIMPLEFRAME_DRAWLAYERS; layer++) { + auto batch = this->m_batch[layer]; + + if (!batch) { + auto mem = SMemAlloc(sizeof(CRenderBatch), __FILE__, __LINE__, 0x0); + batch = new (mem) CRenderBatch(); + this->m_batch[layer] = batch; + } + + this->m_renderList.Link(batch)->Unlink(); + + if ((1 << layer) & batchDirty) { + batch->Clear(); + this->OnFrameRender(batch, layer); + batch->Finish(); + } + + if (batch->m_count) { + this->m_renderList.LinkToTail(batch); + } + } + } + + for (int32_t layer = 0; layer < NUM_SIMPLEFRAME_DRAWLAYERS; layer++) { + if (this->m_batch[layer]) { + CSimpleRender::DrawBatch(this->m_batch[layer]); + } + } + + for (auto child = this->m_children.Head(); child; child = this->m_children.Link(child)->Next()) { + if (child->frame->m_visible && !(child->frame->m_flags & 0x4000)) { + child->frame->OnFrameRender(); + } + } +} + +void CSimpleFrame::OnFrameRender(CRenderBatch* batch, uint32_t layer) { + if (!this->m_drawenabled[layer]) { + return; + } + + for (auto region = this->m_drawlayers[layer].Head(); region; region = this->m_drawlayers[layer].Link(region)->Next()) { + region->Draw(batch); + } +} + +void CSimpleFrame::OnFrameSizeChanged(const CRect& rect) { + CLayoutFrame::OnFrameSizeChanged(rect); + + this->SetHitRect(); + + if ( + fabs(rect.maxX - rect.minX - (this->m_rect.maxX - this->m_rect.minX)) >= WHOA_EPSILON_1 + || fabs(rect.maxY - rect.minY - (this->m_rect.maxY - this->m_rect.minY)) >= WHOA_EPSILON_1 + ) { + if (this->m_backdrop) { + this->m_backdrop->Generate(&this->m_rect); + } + + float v4 = 1.0f / this->m_layoutScale; + float v6 = v4 * (this->m_rect.maxX - this->m_rect.minX); + float v7 = v4 * (this->m_rect.maxY - this->m_rect.minY); + + this->OnFrameSizeChanged(v6, v7); + } + + this->m_top->NotifyFrameMovedOrResized(this); +} + +void CSimpleFrame::OnFrameSizeChanged(float width, float height) { + this->RunOnSizeChangedScript(width, height); + this->NotifyScrollParent(); +} + +int32_t CSimpleFrame::OnLayerChar(const CCharEvent& evt) { + if (!this->m_visible) { + return 0; + } + + if (!this->m_onChar.luaRef) { + return 0; + } + + char charBuf[8]; + SUniSPutUTF8(evt.ch, charBuf); + + this->RunOnCharScript(charBuf); + + return 1; +} + +void CSimpleFrame::OnLayerCursorEnter(int32_t a2) { + // TODO + // if (this->m_tooltip) { + // TODO + // } + + if (!this->m_highlightLocked) { + this->m_drawenabled[DRAWLAYER_HIGHLIGHT] = 1; + this->NotifyDrawLayerChanged(DRAWLAYER_HIGHLIGHT); + } + + this->RunOnEnterScript(a2); +} + +void CSimpleFrame::OnLayerCursorExit(int32_t a2, int32_t a3) { + // TODO + // if (this->m_tooltip) { + // TODO + // } + + if (!this->m_highlightLocked) { + this->m_drawenabled[DRAWLAYER_HIGHLIGHT] = 0; + this->NotifyDrawLayerChanged(DRAWLAYER_HIGHLIGHT); + } + + if (a3) { + this->m_mouseDown = 0; + this->m_dragging = 0; + } + + this->RunOnLeaveScript(a2); +} + +void CSimpleFrame::OnLayerHide() { + this->RunOnHideScript(); + this->NotifyScrollParent(); +} + +int32_t CSimpleFrame::OnLayerKeyDown(const CKeyEvent& evt) { + if (!this->m_visible) { + return 0; + } + + if (!this->m_onKeyDown.luaRef && !this->m_onKeyUp.luaRef) { + return 0; + } + + const char* keyName = KeyCodeToString(evt.key); + this->RunOnKeyDownScript(keyName); + + return 1; +} + +int32_t CSimpleFrame::OnLayerKeyUp(const CKeyEvent& evt) { + if (!this->m_visible || !this->m_onKeyUp.luaRef) { + return 0; + } + + const char* keyName = KeyCodeToString(evt.key); + this->RunOnKeyUpScript(keyName); + + return 1; +} + +int32_t CSimpleFrame::OnLayerMouseDown(const CMouseEvent& evt, const char* btn) { + if (!btn) { + if (this->m_lookForDrag & evt.button) { + this->m_mouseDown = 1; + this->m_dragging = 0; + this->m_dragButton = evt.button; + this->m_clickPoint.x = evt.x; + this->m_clickPoint.y = evt.y; + } + + btn = GetButtonName(evt.button); + } + + // TODO + // const char* old1234 = this->m_top->m_char1234; + // this->m_top->m_char1234 = btn; + + this->RunOnMouseDownScript(btn); + + // TODO + // this->m_top->m_char1234 = old1234; + + return 0; +} + +int32_t CSimpleFrame::OnLayerMouseUp(const CMouseEvent& evt, const char* btn) { + if (!btn) { + if (this->m_lookForDrag & evt.button) { + this->m_mouseDown = 0; + + if (this->m_dragging) { + // TODO + + this->m_dragging = 0; + + return 1; + } + } + + btn = GetButtonName(evt.button); + } + + // TODO + // const char* old1234 = this->m_top->m_char1234; + // this->m_top->m_char1234 = btn; + + this->RunOnMouseUpScript(btn); + + // TODO + // this->m_top->m_char1234 = old1234; + + return 0; +} + +void CSimpleFrame::OnLayerShow() { + this->RunOnShowScript(); + + // TODO + // this->sub48FEA0(); +} + +int32_t CSimpleFrame::OnLayerTrackUpdate(const CMouseEvent& evt) { + C2Vector pt = { evt.x, evt.y }; + + if (this->m_mouseDown && !this->m_dragging) { + // TODO + // - check if dragging logic + } + + int32_t hit = this->TestHitRect(pt); + + if (hit) { + // TODO + // - stereo logic + } + + return hit; +} + +void CSimpleFrame::OnLayerUpdate(float elapsedSec) { + this->RunOnUpdateScript(elapsedSec); + + this->PreOnAnimUpdate(); + + for (auto region = this->m_regions.Head(); region; region = this->m_regions.Link(region)->Next()) { + region->PreOnAnimUpdate(); + } + + CScriptRegion::OnLayerUpdate(elapsedSec); + + for (auto region = this->m_regions.Head(); region; region = this->m_regions.Link(region)->Next()) { + region->OnLayerUpdate(elapsedSec); + } +} + +void CSimpleFrame::OnScreenSizeChanged() { + for (auto region = this->m_regions.Head(); region; region = this->m_regions.Link(region)->Next()) { + region->OnScreenSizeChanged(); + } +} + +void CSimpleFrame::ParentFrame(CSimpleFrame* frame) { + SIMPLEFRAMENODE* node = this->m_children.NewNode(2, 0, 0x8); + node->frame = frame; +} + +void CSimpleFrame::PostLoadXML_Frames(XMLNode *node, CStatus* status) { + // Call this function with all inherited nodes + + const char* inheritNames = node->GetAttributeByName("inherits"); + + if (inheritNames && *inheritNames) { + char inheritName[1024]; + + do { + SStrTokenize(&inheritNames, inheritName, 1024, " ,", 0); + + if (!*inheritName) { + break; + } + + const char* tainted; + bool locked; + + XMLNode* inheritNode = FrameXML_AcquireHashNode(inheritName, tainted, locked); + + if (inheritNode && !locked) { + // TODO + // FrameScript_GetContext(); + + // v6 = lua_taintexpected++ == -1; + // v12 = lua_tainted; + + // if (!v6 && !lua_taintedclosure) { + // lua_tainted = tainted; + // } + + // const char* v7 = lua_gctag; + + // lua_gctag = tainted; + + this->PostLoadXML_Frames(inheritNode, status); + + // TODO + // lua_gctag = v7; + + // if (lua_taintexpected && !lua_taintedclosure) { + // lua_tainted = v12; + // } + + // if (--lua_taintexpected <= 0) { + // lua_taintexpected = 0; + // } + + FrameXML_ReleaseHashNode(inheritName); + } + } while (*inheritName); + } + + // Create child frames + + XMLNode* framesNode = node->GetChildByName("Frames"); + + if (framesNode) { + XMLNode* frameNode; + + for (frameNode = framesNode->m_child; frameNode; frameNode = frameNode->m_next) { + FrameXML_CreateFrame(frameNode, this, status); + } + } +} + +void CSimpleFrame::PreOnAnimUpdate() { + // TODO +} + +void CSimpleFrame::RegisterRegion(CSimpleRegion* region) { + STORM_ASSERT(region); + + this->m_regions.LinkToTail(region); +} + +void CSimpleFrame::RemoveFrameRegion(CSimpleRegion* region, uint32_t drawlayer) { + this->m_drawlayers[drawlayer].UnlinkNode(region); + this->NotifyDrawLayerChanged(drawlayer); +} + +void CSimpleFrame::SetBackdrop(CBackdropGenerator* backdrop) { + if (this->m_backdrop) { + delete this->m_backdrop; + } + + if (backdrop) { + backdrop->SetOutput(this); + } + + this->m_backdrop = backdrop; +} + +void CSimpleFrame::SetBeingScrolled(int32_t a2, int32_t a3) { + auto visible = this->m_visible; + + if (visible) { + this->HideThis(); + } + + if (a2) { + this->m_flags |= 0x2000; + } else { + this->m_flags &= ~0x2000; + } + + if (a3 >= 0) { + if (a3) { + this->m_flags |= 0x4000; + } else { + this->m_flags &= ~0x4000; + } + } + + if (a2) { + this->m_batchDirty |= 0x1F; + } + + for (auto child = this->m_children.Head(); child; this->m_children.Link(child)->Next()) { + if (!(child->frame->m_flags & 0x4000)) { + child->frame->SetBeingScrolled(a2, -1); + } + } + + if (visible) { + this->ShowThis(); + } +} + +void CSimpleFrame::SetFrameAlpha(uint8_t alpha) { + // TODO +} + +void CSimpleFrame::SetFrameFlag(int32_t flag, int32_t on) { + if (on) { + this->m_flags |= flag; + } else { + this->m_flags &= ~flag; + } +} + +void CSimpleFrame::SetFrameLevel(int32_t level, int32_t shiftChildren) { + level = std::max(level, 0); + + if (this->m_level == level) { + return; + } + + int32_t delta = std::min(level - this->m_level, 128); + + if (this->m_visible) { + this->m_top->HideFrame(this, 1); + } + + this->m_level += delta; + + if (this->m_visible) { + this->m_top->ShowFrame(this, 1); + } + + if (shiftChildren) { + for (auto child = this->m_children.Head(); child; child = this->m_children.Link(child)->Next()) { + child->frame->SetFrameLevel(child->frame->m_level + delta, 1); + } + } +} + +bool CSimpleFrame::SetFrameScale(float scale, bool a3) { + if (scale == 0.0f || (!a3 && AreEqual(scale, this->m_frameScale, WHOA_EPSILON_1))) { + return false; + } + + this->m_frameScale = scale; + + return this->UpdateScale(a3); +} + +void CSimpleFrame::SetFrameStrata(FRAME_STRATA strata) { + if (this->m_strata == strata) { + return; + } + + if (this->m_visible) { + this->m_top->HideFrame(this, 1); + } + + this->m_strata = strata; + + if (this->m_visible) { + this->m_top->ShowFrame(this, 1); + } + + auto child = this->m_children.Head(); + + while (child) { + child->frame->SetFrameStrata(strata); + child = this->m_children.Link(child)->Next(); + } +} + +void CSimpleFrame::SetHitRect() { + this->m_hitRect.minX = this->m_rect.minX + this->m_hitOffset.minX * this->m_layoutScale; + this->m_hitRect.maxX = this->m_rect.maxX - this->m_hitOffset.maxX * this->m_layoutScale; + this->m_hitRect.minY = this->m_rect.minY + this->m_hitOffset.minY * this->m_layoutScale; + this->m_hitRect.maxY = this->m_rect.maxY - this->m_hitOffset.maxY * this->m_layoutScale; +} + +void CSimpleFrame::SetParent(CSimpleFrame* parent) { + if (this->m_parent == parent) { + return; + } + + if (this->m_parent) { + // TODO + // this->OnProtectedDetach(this->m_parent); + // (this->m_parent->vfptr + 56)(this); + } + + if (this->m_visible) { + this->HideThis(); + } + + this->m_parent = parent; + + if (parent) { + this->SetFrameStrata(parent->m_strata); + this->SetFrameLevel(this->m_parent->m_level + 1, 1); + this->UpdateScale(false); + + // TODO + // alpha stuff? + // v7 = LOBYTE(this->m_parent->simpleframe_unk3) * BYTE1(this->m_parent->simpleframe_unk3) / 255; + // if ((_BYTE)v7 != BYTE1(this->simpleframe_unk3)) { + // BYTE1(this->simpleframe_unk3) = v7; + // (this->vfptr + 53)(this); + // } + + this->SetBeingScrolled((this->m_parent->m_flags >> 13) & 1, -1); + } else { + this->SetFrameStrata(FRAME_STRATA_MEDIUM); + this->SetFrameLevel(0, 1); + this->UpdateScale(false); + + // TODO + // if (BYTE1(this->simpleframe_unk3) != -1) { + // BYTE1(this->simpleframe_unk3) = -1; + // (this->vfptr + 53)(v3); + // } + + this->SetBeingScrolled(0, -1); + } + + if (this->m_parent) { + this->OnProtectedAttach(this->m_parent); + this->m_parent->ParentFrame(this); + } + + if (this->m_shown) { + if (!this->m_parent || this->m_parent->m_visible) { + this->ShowThis(); + } + } +} + +void CSimpleFrame::Show() { + if (this->ProtectedFunctionsAllowed()) { + this->m_shown = 1; + this->ShowThis(); + } else { + // TODO + // auto v2 = this->m_top->unk5[8]; + + // if (v2) { + // v2(0); + // } + } +} + +int32_t CSimpleFrame::ShowThis() { + if (!this->m_shown) { + return 0; + } + + if (this->m_parent && !this->m_parent->m_visible) { + return 0; + } + + if (this->m_visible) { + return 1; + } + + if (!this->m_loading) { + this->SetDeferredResize(0); + + if (this->m_titleRegion) { + this->m_titleRegion->SetDeferredResize(0); + } + } + + this->m_visible = 1; + this->m_top->ShowFrame(this, 0); + + auto region = this->m_regions.Head(); + + while (region) { + region->ShowThis(); + region = this->m_regions.Link(region)->Next(); + } + + auto child = this->m_children.Head(); + + while (child) { + child->frame->ShowThis(); + child = this->m_children.Link(child)->Next(); + } + + if (this->m_flags & 0x1) { + this->m_top->RaiseFrame(this, 1); + } + + this->OnLayerShow(); + + return 1; +} + +int32_t CSimpleFrame::TestHitRect(const C2Vector& pt) { + if (!(static_cast(this)->m_flags & 0x1)) { + return 0; + } + + if (!(this->m_flags & 0x2000)) { + return this->m_hitRect.IsPointInside(pt); + } + + CSimpleFrame* parent = this->m_parent; + while (parent && parent->m_flags & 0x2000) { + parent = parent->m_parent; + } + + if (!parent) { + return 0; + } + + CRect rect = CRect::Intersection(parent->m_hitRect, this->m_hitRect); + return rect.IsPointInside(pt); +} + +void CSimpleFrame::UpdateDepth(bool a1) { + // TODO +} + +bool CSimpleFrame::UpdateScale(bool a2) { + auto scale = this->m_frameScale; + if (this->m_parent) { + scale *= this->m_parent->m_layoutScale; + } + + if ((!a2 && AreEqual(scale, this->m_layoutScale, WHOA_EPSILON_1)) || scale == 0.0f) { + return false; + } + + this->SetLayoutScale(scale, a2); + + for (auto region = this->m_regions.Head(); region; region = this->m_regions.Link(region)->Next()) { + region->SetLayoutScale(scale, a2); + } + + for (auto child = this->m_children.Head(); child; child = this->m_children.Link(child)->Next()) { + child->frame->UpdateScale(false); + } + + return true; +} + +void CSimpleFrame::UnregisterForEvents(int32_t a2) { + for (int32_t event = SIMPLE_EVENT_CHAR; event < NUM_SIMPLE_EVENTS; event++) { + if ((1 << event) & this->m_eventmask) { + this->m_top->UnregisterForEvent(this, static_cast(event), a2); + } + } +} + +void CSimpleFrame::UnregisterRegion(CSimpleRegion* region) { + STORM_ASSERT(region); + + this->m_regions.UnlinkNode(region); +} diff --git a/src/ui/CSimpleFrame.hpp b/src/ui/CSimpleFrame.hpp new file mode 100644 index 0000000..d7df261 --- /dev/null +++ b/src/ui/CSimpleFrame.hpp @@ -0,0 +1,162 @@ +#ifndef UI_C_SIMPLE_FRAME_HPP +#define UI_C_SIMPLE_FRAME_HPP + +#include "ui/CRenderBatch.hpp" +#include "ui/CScriptRegion.hpp" +#include "ui/CSimpleRegion.hpp" +#include "ui/Types.hpp" +#include +#include + +class CBackdropGenerator; +class CCharEvent; +class CKeyEvent; +class CMouseEvent; +class CSimpleTitleRegion; +class CSimpleTop; +struct lua_State; + +class CSimpleFrame : public CScriptRegion { + public: + // Static members + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + + // Member variables + CSimpleTop* m_top = NULL; + CSimpleTitleRegion* m_titleRegion = NULL; + int32_t m_intAC = 1; + int32_t m_id = 0; + uint32_t m_flags = 0; + float m_frameScale = 1.0f; + float m_depth = 0.0; + FRAME_STRATA m_strata = FRAME_STRATA_MEDIUM; + int32_t m_level = 0; + uint32_t m_eventmask = 0; + int32_t m_shown = 0; + int32_t m_visible = 0; + CRect m_hitRect = {}; + CRect m_hitOffset = {}; + int32_t m_highlightLocked = 0; + uint32_t m_lookForDrag = 0; + int32_t m_mouseDown = 0; + int32_t m_dragging = 0; + int32_t m_dragButton; + C2Vector m_clickPoint; + int32_t m_loading = 0; + ScriptIx m_onLoad; + ScriptIx m_onSizeChanged; + ScriptIx m_onUpdate; + ScriptIx m_onShow; + ScriptIx m_onHide; + ScriptIx m_onEnter; + ScriptIx m_onLeave; + ScriptIx m_onMouseDown; + ScriptIx m_onMouseUp; + ScriptIx m_onMouseWheel; + ScriptIx m_onDragStart; + ScriptIx m_onDragStop; + ScriptIx m_onReceiveDrag; + ScriptIx m_onChar; + ScriptIx m_onKeyDown; + ScriptIx m_onKeyUp; + ScriptIx m_onAttributeChange; + ScriptIx m_onEnable; + ScriptIx m_onDisable; + int32_t m_drawenabled[NUM_SIMPLEFRAME_DRAWLAYERS]; + CBackdropGenerator* m_backdrop = nullptr; + STORM_EXPLICIT_LIST(CSimpleRegion, m_regionLink) m_regions; + STORM_EXPLICIT_LIST(CSimpleRegion, m_layerLink) m_drawlayers[NUM_SIMPLEFRAME_DRAWLAYERS]; + uint32_t m_batchDirty = 0; + CRenderBatch* m_batch[NUM_SIMPLEFRAME_DRAWLAYERS] = {}; + STORM_EXPLICIT_LIST(CRenderBatch, renderLink) m_renderList; + TSList> m_children; + TSLink m_framesLink; + TSLink m_destroyedLink; + TSLink m_strataLink; + + // Virtual member functions + virtual ~CSimpleFrame(); + virtual ScriptIx* GetScriptByName(const char*, ScriptData&); + virtual bool IsA(int32_t); + virtual int32_t GetScriptMetaTable(void); + virtual void LoadXML(XMLNode*, CStatus*); + virtual void PreOnAnimUpdate(void); + virtual void OnLayerShow(void); + virtual void OnLayerHide(void); + virtual void OnLayerUpdate(float); + virtual int32_t OnLayerTrackUpdate(const CMouseEvent&); + virtual void OnFrameRender(void); + virtual void OnFrameRender(CRenderBatch*, uint32_t); + virtual void OnScreenSizeChanged(); + virtual void OnFrameSizeChanged(float, float); + virtual void OnLayerCursorEnter(int32_t); + virtual void OnLayerCursorExit(int32_t, int32_t); + virtual int32_t OnLayerChar(const CCharEvent&); + virtual int32_t OnLayerKeyDown(const CKeyEvent&); + virtual int32_t OnLayerKeyUp(const CKeyEvent&); + virtual int32_t OnLayerMouseDown(const CMouseEvent&, const char*); + virtual int32_t OnLayerMouseUp(const CMouseEvent&, const char*); + virtual void PostLoadXML(XMLNode*, CStatus*); + virtual void UnregisterRegion(CSimpleRegion*); + virtual int32_t GetBoundsRect(CRect& bounds); + virtual void PreLoadXML(XMLNode*, CStatus*); + virtual void LockHighlight(int32_t); + virtual int32_t HideThis(void); + virtual int32_t ShowThis(void); + virtual bool UpdateScale(bool); + virtual void UpdateDepth(bool); + virtual void ParentFrame(CSimpleFrame*); + virtual void OnFrameSizeChanged(const CRect&); + + // Member functions + CSimpleFrame(CSimpleFrame*); + void AddFrameRegion(CSimpleRegion*, uint32_t); + void DisableDrawLayer(uint32_t); + void EnableDrawLayer(uint32_t); + void EnableEvent(CSimpleEventType, int32_t); + int32_t GetHitRect(CRect& rect); + void Hide(void); + void LoadXML_Attributes(XMLNode*, CStatus*); + void LoadXML_Backdrop(XMLNode*, CStatus*); + void LoadXML_Layers(XMLNode*, CStatus*); + void LoadXML_Scripts(XMLNode*, CStatus*); + void NotifyDrawLayerChanged(uint32_t); + void NotifyScrollParent(void); + void PostLoadXML_Frames(XMLNode*, CStatus*); + void Raise(void); + void RegisterForEvents(int32_t); + void RegisterRegion(CSimpleRegion*); + void RemoveFrameRegion(CSimpleRegion*, uint32_t); + void RunOnCharScript(const char*); + void RunOnEnterScript(int32_t); + void RunOnHideScript(void); + void RunOnKeyDownScript(const char*); + void RunOnKeyUpScript(const char*); + void RunOnLeaveScript(int32_t); + void RunOnLoadScript(void); + void RunOnMouseDownScript(const char*); + void RunOnMouseUpScript(const char*); + void RunOnShowScript(void); + void RunOnSizeChangedScript(float, float); + void RunOnUpdateScript(float); + void SetBackdrop(CBackdropGenerator*); + void SetBeingScrolled(int32_t, int32_t); + void SetFrameAlpha(uint8_t alpha); + void SetFrameFlag(int32_t, int32_t); + void SetFrameLevel(int32_t, int32_t); + bool SetFrameScale(float scale, bool a3); + void SetFrameStrata(FRAME_STRATA); + void SetHitRect(void); + void SetParent(CSimpleFrame*); + void Show(void); + int32_t TestHitRect(const C2Vector&); + void UnregisterForEvents(int32_t); +}; + +#endif diff --git a/src/ui/CSimpleFrameScript.cpp b/src/ui/CSimpleFrameScript.cpp new file mode 100644 index 0000000..5357f73 --- /dev/null +++ b/src/ui/CSimpleFrameScript.cpp @@ -0,0 +1,580 @@ +#include "ui/CSimpleFrameScript.hpp" +#include "gx/Coordinate.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/FrameScript.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include +#include +#include + +int32_t CSimpleFrame_GetTitleRegion(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_CreateTitleRegion(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_CreateTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_CreateFontString(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetBoundsRect(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + CRect bounds = { + std::numeric_limits::max(), + std::numeric_limits::max(), + 0.0f, + 0.0f + }; + + if (!frame->GetBoundsRect(bounds)) { + return 0; + } + + float ooScale = 1.0f / frame->m_layoutScale; + + float ddcTop = CoordinateGetAspectCompensation() * 1024.0f * ooScale * bounds.minX; + float ndcTop = DDCToNDCWidth(ddcTop); + lua_pushnumber(L, ndcTop); + + float ddcLeft = CoordinateGetAspectCompensation() * 1024.0f * ooScale * bounds.minY; + float ndcLeft = DDCToNDCWidth(ddcLeft); + lua_pushnumber(L, ndcLeft); + + float ddcWidth = CoordinateGetAspectCompensation() * 1024.0f * ooScale * (bounds.maxX - bounds.minX); + float ndcWidth = DDCToNDCWidth(ddcWidth); + lua_pushnumber(L, ndcWidth); + + float ddcHeight = CoordinateGetAspectCompensation() * 1024.0f * ooScale * (bounds.maxY - bounds.minY); + float ndcHeight = DDCToNDCWidth(ddcHeight); + lua_pushnumber(L, ndcHeight); + + return 4; +} + +int32_t CSimpleFrame_GetNumRegions(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetRegions(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetNumChildren(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetChildren(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetFrameStrata(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetFrameStrata(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetFrameLevel(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + lua_pushnumber(L, frame->m_level); + + return 1; +} + +int32_t CSimpleFrame_SetFrameLevel(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!frame->ProtectedFunctionsAllowed()) { + // TODO + // - disallowed logic + + return 0; + } + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "Usage: %s:SetFrameLevel(level)", frame->GetDisplayName()); + } + + int32_t level = lua_tonumber(L, 2); + + if (level < 0) { + return luaL_error(L, "%s:SetFrameLevel(): Passed negative frame level: %d", frame->GetDisplayName(), level); + } + + frame->SetFrameLevel(level, 1); + + return 0; +} + +int32_t CSimpleFrame_HasScript(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetScript(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetScript(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_HookScript(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_RegisterEvent(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isstring(L, 2)) { + return luaL_error(L, "Usage: %s:RegisterEvent(\"event\")", frame->GetDisplayName()); + } + + const char* event = lua_tolstring(L, 2, 0); + + frame->RegisterScriptEvent(event); + + return 0; +} + +int32_t CSimpleFrame_UnregisterEvent(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_RegisterAllEvents(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_UnregisterAllEvents(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsEventRegistered(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_AllowAttributeChanges(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_CanChangeAttributes(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetAttribute(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetAttribute(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetEffectiveScale(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetScale(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetScale(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetEffectiveAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetAlpha(lua_State* L) { + auto type = CSimpleFrame::GetObjectType(); + auto frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2)) { + luaL_error(L, "Usage: %s:SetAlpha(alpha 0 to 1)", frame->GetDisplayName()); + } + + float alpha = lua_tonumber(L, 2); + alpha = std::max(std::min(alpha, 1.0f), 0.0f); + frame->SetFrameAlpha(static_cast(alpha * 255.0f)); + + return 0; +} + +int32_t CSimpleFrame_GetID(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + lua_pushnumber(L, frame->m_id); + + return 1; +} + +int32_t CSimpleFrame_SetID(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!frame->ProtectedFunctionsAllowed()) { + // TODO + // - disallowed logic + + return 0; + } + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "Usage: %s:SetID(ID)", frame->GetDisplayName()); + } + + frame->m_id = lua_tonumber(L, 2); + + return 0; +} + +int32_t CSimpleFrame_SetToplevel(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsToplevel(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_EnableDrawLayer(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_DisableDrawLayer(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_Show(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (frame->ProtectedFunctionsAllowed()) { + frame->Show(); + } else { + // TODO + // - disallowed logic + } + + return 0; +} + +int32_t CSimpleFrame_Hide(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (frame->ProtectedFunctionsAllowed()) { + frame->Hide(); + } else { + // TODO + // - disallowed logic + } + + return 0; +} + +int32_t CSimpleFrame_IsVisible(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsShown(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (frame->m_shown) { + lua_pushnumber(L, 1.0); + } else { + lua_pushnil(L); + } + + return 1; +} + +int32_t CSimpleFrame_Raise(lua_State* L) { + int32_t type = CSimpleFrame::GetObjectType(); + CSimpleFrame* frame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!frame->ProtectedFunctionsAllowed()) { + // TODO + // - disallowed logic + + return 0; + } + + frame->Raise(); + + return 0; +} + +int32_t CSimpleFrame_Lower(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetHitRectInsets(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetHitRectInsets(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetClampRectInsets(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetClampRectInsets(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetMinResize(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetMinResize(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetMaxResize(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetMaxResize(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetMovable(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsMovable(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetDontSavePosition(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetDontSavePosition(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetResizable(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsResizable(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_StartMoving(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_StartSizing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_StopMovingOrSizing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetUserPlaced(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsUserPlaced(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetClampedToScreen(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsClampedToScreen(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_RegisterForDrag(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_EnableKeyboard(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsKeyboardEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_EnableMouse(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsMouseEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_EnableMouseWheel(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsMouseWheelEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_EnableJoystick(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsJoystickEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetBackdrop(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetBackdrop(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetBackdropColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetBackdropColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetBackdropBorderColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetBackdropBorderColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_SetDepth(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetDepth(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_GetEffectiveDepth(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IgnoreDepth(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleFrame_IsIgnoringDepth(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleFrameMethods[NUM_SIMPLE_FRAME_SCRIPT_METHODS] = { + { "GetTitleRegion", &CSimpleFrame_GetTitleRegion }, + { "CreateTitleRegion", &CSimpleFrame_CreateTitleRegion }, + { "CreateTexture", &CSimpleFrame_CreateTexture }, + { "CreateFontString", &CSimpleFrame_CreateFontString }, + { "GetBoundsRect", &CSimpleFrame_GetBoundsRect }, + { "GetNumRegions", &CSimpleFrame_GetNumRegions }, + { "GetRegions", &CSimpleFrame_GetRegions }, + { "GetNumChildren", &CSimpleFrame_GetNumChildren }, + { "GetChildren", &CSimpleFrame_GetChildren }, + { "GetFrameStrata", &CSimpleFrame_GetFrameStrata }, + { "SetFrameStrata", &CSimpleFrame_SetFrameStrata }, + { "GetFrameLevel", &CSimpleFrame_GetFrameLevel }, + { "SetFrameLevel", &CSimpleFrame_SetFrameLevel }, + { "HasScript", &CSimpleFrame_HasScript }, + { "GetScript", &CSimpleFrame_GetScript }, + { "SetScript", &CSimpleFrame_SetScript }, + { "HookScript", &CSimpleFrame_HookScript }, + { "RegisterEvent", &CSimpleFrame_RegisterEvent }, + { "UnregisterEvent", &CSimpleFrame_UnregisterEvent }, + { "RegisterAllEvents", &CSimpleFrame_RegisterAllEvents }, + { "UnregisterAllEvents", &CSimpleFrame_UnregisterAllEvents }, + { "IsEventRegistered", &CSimpleFrame_IsEventRegistered }, + { "AllowAttributeChanges", &CSimpleFrame_AllowAttributeChanges }, + { "CanChangeAttribute", &CSimpleFrame_CanChangeAttributes }, + { "GetAttribute", &CSimpleFrame_GetAttribute }, + { "SetAttribute", &CSimpleFrame_SetAttribute }, + { "GetEffectiveScale", &CSimpleFrame_GetEffectiveScale }, + { "GetScale", &CSimpleFrame_GetScale }, + { "SetScale", &CSimpleFrame_SetScale }, + { "GetEffectiveAlpha", &CSimpleFrame_GetEffectiveAlpha }, + { "GetAlpha", &CSimpleFrame_GetAlpha }, + { "SetAlpha", &CSimpleFrame_SetAlpha }, + { "GetID", &CSimpleFrame_GetID }, + { "SetID", &CSimpleFrame_SetID }, + { "SetToplevel", &CSimpleFrame_SetToplevel }, + { "IsToplevel", &CSimpleFrame_IsToplevel }, + { "EnableDrawLayer", &CSimpleFrame_EnableDrawLayer }, + { "DisableDrawLayer", &CSimpleFrame_DisableDrawLayer }, + { "Show", &CSimpleFrame_Show }, + { "Hide", &CSimpleFrame_Hide }, + { "IsVisible", &CSimpleFrame_IsVisible }, + { "IsShown", &CSimpleFrame_IsShown }, + { "Raise", &CSimpleFrame_Raise }, + { "Lower", &CSimpleFrame_Lower }, + { "GetHitRectInsets", &CSimpleFrame_GetHitRectInsets }, + { "SetHitRectInsets", &CSimpleFrame_SetHitRectInsets }, + { "GetClampRectInsets", &CSimpleFrame_GetClampRectInsets }, + { "SetClampRectInsets", &CSimpleFrame_SetClampRectInsets }, + { "GetMinResize", &CSimpleFrame_GetMinResize }, + { "SetMinResize", &CSimpleFrame_SetMinResize }, + { "GetMaxResize", &CSimpleFrame_GetMaxResize }, + { "SetMaxResize", &CSimpleFrame_SetMaxResize }, + { "SetMovable", &CSimpleFrame_SetMovable }, + { "IsMovable", &CSimpleFrame_IsMovable }, + { "SetDontSavePosition", &CSimpleFrame_SetDontSavePosition }, + { "GetDontSavePosition", &CSimpleFrame_GetDontSavePosition }, + { "SetResizable", &CSimpleFrame_SetResizable }, + { "IsResizable", &CSimpleFrame_IsResizable }, + { "StartMoving", &CSimpleFrame_StartMoving }, + { "StartSizing", &CSimpleFrame_StartSizing }, + { "StopMovingOrSizing", &CSimpleFrame_StopMovingOrSizing }, + { "SetUserPlaced", &CSimpleFrame_SetUserPlaced }, + { "IsUserPlaced", &CSimpleFrame_IsUserPlaced }, + { "SetClampedToScreen", &CSimpleFrame_SetClampedToScreen }, + { "IsClampedToScreen", &CSimpleFrame_IsClampedToScreen }, + { "RegisterForDrag", &CSimpleFrame_RegisterForDrag }, + { "EnableKeyboard", &CSimpleFrame_EnableKeyboard }, + { "IsKeyboardEnabled", &CSimpleFrame_IsKeyboardEnabled }, + { "EnableMouse", &CSimpleFrame_EnableMouse }, + { "IsMouseEnabled", &CSimpleFrame_IsMouseEnabled }, + { "EnableMouseWheel", &CSimpleFrame_EnableMouseWheel }, + { "IsMouseWheelEnabled", &CSimpleFrame_IsMouseWheelEnabled }, + { "EnableJoystick", &CSimpleFrame_EnableJoystick }, + { "IsJoystickEnabled", &CSimpleFrame_IsJoystickEnabled }, + { "GetBackdrop", &CSimpleFrame_GetBackdrop }, + { "SetBackdrop", &CSimpleFrame_SetBackdrop }, + { "GetBackdropColor", &CSimpleFrame_GetBackdropColor }, + { "SetBackdropColor", &CSimpleFrame_SetBackdropColor }, + { "GetBackdropBorderColor", &CSimpleFrame_GetBackdropBorderColor }, + { "SetBackdropBorderColor", &CSimpleFrame_SetBackdropBorderColor }, + { "SetDepth", &CSimpleFrame_SetDepth }, + { "GetDepth", &CSimpleFrame_GetDepth }, + { "GetEffectiveDepth", &CSimpleFrame_GetEffectiveDepth }, + { "IgnoreDepth", &CSimpleFrame_IgnoreDepth }, + { "IsIgnoringDepth", &CSimpleFrame_IsIgnoringDepth } +}; diff --git a/src/ui/CSimpleFrameScript.hpp b/src/ui/CSimpleFrameScript.hpp new file mode 100644 index 0000000..6a4d38f --- /dev/null +++ b/src/ui/CSimpleFrameScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_FRAME_SCRIPT_HPP +#define UI_C_SIMPLE_FRAME_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_FRAME_SCRIPT_METHODS 85 + +extern FrameScript_Method SimpleFrameMethods[NUM_SIMPLE_FRAME_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleHTML.cpp b/src/ui/CSimpleHTML.cpp new file mode 100644 index 0000000..f12eb7b --- /dev/null +++ b/src/ui/CSimpleHTML.cpp @@ -0,0 +1,371 @@ +#include "ui/CSimpleHTML.hpp" +#include "ui/CSimpleFontedFrameFont.hpp" +#include "ui/CSimpleFontString.hpp" +#include "ui/CSimpleHTMLScript.hpp" +#include "ui/CSimpleTop.hpp" +#include "util/CStatus.hpp" +#include "util/SFile.hpp" +#include "util/StringTo.hpp" +#include + +int32_t CSimpleHTML::s_metatable; +int32_t CSimpleHTML::s_objectType; + +CONTENTNODE::~CONTENTNODE() { + if (this->string) { + delete string; + } +} + +void CSimpleHTML::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleHTML::RegisterScriptMethods); + CSimpleHTML::s_metatable = ref; +} + +int32_t CSimpleHTML::GetObjectType() { + if (!CSimpleHTML::s_objectType) { + CSimpleHTML::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleHTML::s_objectType; +} + +void CSimpleHTML::RegisterScriptMethods(lua_State* L) { + CSimpleFrame::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleHTMLMethods, NUM_SIMPLE_HTML_SCRIPT_METHODS); +} + +CSimpleHTML::CSimpleHTML(CSimpleFrame* parent) : CSimpleHyperlinkedFrame(parent) { + // TODO + + for (int32_t i = 0; i < NUM_HTML_TEXT_TYPES; i++) { + auto m = SMemAlloc(sizeof(CSimpleFontedFrameFont), __FILE__, __LINE__, 0x0); + auto font = new (m) CSimpleFontedFrameFont(this); + this->m_fonts[i] = font; + } + + // TODO +} + +void CSimpleHTML::AddText(const char* text, HTML_TEXT_TYPE type, uint32_t justify) { + // TODO void* stringMem = CDataAllocator::GetData(CSimpleFontString::s_allocator, 0x0, __FILE__, __LINE__); + void* stringMem = SMemAlloc(sizeof(CSimpleFontString), __FILE__, __LINE__, 0x0); + CSimpleFontString* string = new (stringMem) CSimpleFontString(this, DRAWLAYER_ARTWORK, 1); + + if (this->m_layoutAnchor) { + string->SetPoint( + FRAMEPOINT_TOPLEFT, + this->m_layoutAnchor, + FRAMEPOINT_BOTTOMLEFT, + 0.0f, + this->m_layoutOffset, + 1 + ); + } else { + string->SetPoint( + FRAMEPOINT_TOPLEFT, + this, + FRAMEPOINT_TOPLEFT, + 0.0f, + 0.0f, + 1 + ); + } + + string->SetWidth(this->GetWidth()); + + auto font = this->m_fonts[type]; + if (!font->m_attributes.m_font.GetString()) { + font = this->m_fonts[0]; + } + string->SetFontObject(font); + + string->SetJustifyH(justify); + // TODO something with style flag 0x1000? + + string->SetText(text, 1); + + this->m_layoutAnchor = string; + this->m_layoutOffset = -CSimpleTop::RoundToPixelHeight(string->m_spacing); + + auto contentNode = this->m_content.NewNode(2, 0, 0x0); + contentNode->string = string; + // TODO contentNode->dword8 = SStrStr(text, "|H") != 0; +} + +void CSimpleHTML::ClearContent() { + this->m_content.DeleteAll(); + + this->m_layoutAnchor = nullptr; + this->m_layoutOffset = 0.0f; + + // TODO clear hyperlinks +} + +void CSimpleHTML::FontUpdated(CSimpleFontedFrameFont* font, int32_t a3) { + // TODO +} + +int32_t CSimpleHTML::GetScriptMetaTable() { + return CSimpleHTML::s_metatable; +} + +bool CSimpleHTML::IsA(int32_t type) { + return type == CSimpleHTML::s_objectType + || type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleHTML::LoadXML(XMLNode* node, CStatus* status) { + CSimpleFrame::LoadXML(node, status); + + const char* fontAttr = node->GetAttributeByName("font"); + if (fontAttr && *fontAttr) { + auto font = CSimpleFont::GetFont(fontAttr, 0); + + if (font) { + for (int32_t i = 0; i < NUM_HTML_TEXT_TYPES; i++) { + this->m_fonts[i]->SetFontObject(font); + } + } else { + status->Add( + STATUS_WARNING, + "%s %s: Couldn't find font object named %s", + this->GetObjectTypeName(), + this->GetDisplayName(), + fontAttr + ); + } + } + + for (XMLNode* child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "FontString", STORM_MAX_STR)) { + this->m_fonts[HTML_TEXT_NORMAL]->LoadXML(child, status); + } else if (!SStrCmpI(child->GetName(), "FontStringHeader1", STORM_MAX_STR)) { + this->m_fonts[HTML_TEXT_HEADER1]->LoadXML(child, status); + } else if (!SStrCmpI(child->GetName(), "FontStringHeader2", STORM_MAX_STR)) { + this->m_fonts[HTML_TEXT_HEADER2]->LoadXML(child, status); + } else if (!SStrCmpI(child->GetName(), "FontStringHeader3", STORM_MAX_STR)) { + this->m_fonts[HTML_TEXT_HEADER3]->LoadXML(child, status); + } + } + + const char* fileAttr = node->GetAttributeByName("file"); + void* fileData; + if (fileAttr && SFile::Load(nullptr, fileAttr, &fileData, nullptr, 1, 0x1, nullptr)) { + this->SetText(static_cast(fileData), nullptr); + SFile::Unload(fileData); + } + + // TODO hyperlinkFormat +} + +void CSimpleHTML::ParseBODY(XMLNode* node, CStatus* status) { + for (auto child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "H1", STORM_MAX_STR)) { + this->ParseP(child, HTML_TEXT_HEADER1, status); + + } else if (!SStrCmpI(child->GetName(), "H2", STORM_MAX_STR)) { + this->ParseP(child, HTML_TEXT_HEADER2, status); + + } else if (!SStrCmpI(child->GetName(), "H3", STORM_MAX_STR)) { + this->ParseP(child, HTML_TEXT_HEADER3, status); + + } else if (!SStrCmpI(child->GetName(), "P", STORM_MAX_STR)) { + this->ParseP(child, HTML_TEXT_NORMAL, status); + + } else if (!SStrCmpI(child->GetName(), "BR", STORM_MAX_STR)) { + this->AddText("\n", HTML_TEXT_NORMAL, 1); + + } else if (!SStrCmpI(child->GetName(), "IMG", STORM_MAX_STR)) { + this->ParseIMG(child, status); + + } else { + status->Add( + STATUS_WARNING, + "Frame %s: Unknown element type: %s", + this->GetDisplayName(), + child->GetName() + ); + } + } +} + +void CSimpleHTML::ParseIMG(XMLNode* node, CStatus* status) { + // TODO +} + +void CSimpleHTML::ParseP(XMLNode* node, HTML_TEXT_TYPE type, CStatus* status) { + uint32_t justify = 1; + auto justifyAttr = node->GetAttributeByName("align"); + if (justifyAttr && *justifyAttr) { + StringToJustify(justifyAttr, justify); + } + + auto body = node->GetBody(); + if (!body) { + body = ""; + } + + auto text = SStrDupA(body, __FILE__, __LINE__); + + uint32_t offset = 0; + for (auto child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "BR", STORM_MAX_STR)) { + char* newText = static_cast(SMemAlloc(SStrLen(text) + 3, __FILE__, __LINE__, 0x0)); + SStrCopy(newText, text, child->m_offset + offset + 1); + SStrPack(newText, "|n", STORM_MAX_STR); + SStrPack(newText, &text[child->m_offset + offset], STORM_MAX_STR); + SMemFree(text, __FILE__, __LINE__, 0x0); + text = newText; + + offset += 2; + } else if (!SStrCmpI(child->GetName(), "A", STORM_MAX_STR)) { + // TODO handle anchor child + } else { + status->Add( + STATUS_WARNING, + "Frame %s: Unknown element type: %s", + this->GetDisplayName(), + child->GetName() + ); + } + } + + int32_t v22 = 0; + int32_t v36 = 1; + auto v23 = text; + auto v24 = text; + + if (*text) { + do { + switch (*v23) { + case '\t': + case '\n': + case '\r': + case ' ': { + if (!v36 && !v22) { + v22 = 1; + *v24 = ' '; + v24++; + } + + break; + } + + case '|': { + if (v23[1] != 'n') { + v22 = 0; + v36 = 0; + + *v24 = *v23; + v24++; + + break; + } + + if (v24 > text && v22) { + v24--; + } + + *v24 = '|'; + v24++; + *v24 = 'n'; + v24++; + + v22 = 0; + v36 = 1; + + v23++; + + break; + } + + default: { + v22 = 0; + v36 = 0; + + *v24 = *v23; + v24++; + + break; + } + } + + v23++; + } while (*v23); + + if (v24 > text && v22) { + v24--; + } + } + + *v24 = '\0'; + + this->AddText(text, type, justify); + + SMemFree(text, __FILE__, __LINE__, 0x0); +} + +bool CSimpleHTML::SetText(const char* text, CStatus* status) { + static auto s_nullStatus = CStatus(); + + if (!status) { + status = &s_nullStatus; + } + + if (!text) { + text = ""; + } + + // TODO some sort of guard condition + + this->ClearContent(); + + auto textLength = SStrLen(text); + auto tree = XMLTree_Load(text, textLength); + bool success = false; + + if (tree) { + auto node = XMLTree_GetRoot(tree); + + if (!SStrCmpI(node->GetName(), "HTML", STORM_MAX_STR)) { + for (auto child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "BODY", STORM_MAX_STR)) { + success = true; + this->ParseBODY(child, status); + + break; + } + + status->Add( + STATUS_WARNING, + "Frame %s: Unknown element type: %s (expected BODY)", + this->GetDisplayName(), + node->GetName() + ); + } + } else { + status->Add( + STATUS_WARNING, + "Frame %s: Unknown element type: %s (expected HTML)", + this->GetDisplayName(), + node->GetName() + ); + } + + XMLTree_Free(tree); + } + + if (success) { + // TODO set flag + + return true; + } + + this->AddText(text, HTML_TEXT_NORMAL, 1); + + return false; +} diff --git a/src/ui/CSimpleHTML.hpp b/src/ui/CSimpleHTML.hpp new file mode 100644 index 0000000..44cda1a --- /dev/null +++ b/src/ui/CSimpleHTML.hpp @@ -0,0 +1,51 @@ +#ifndef UI_C_SIMPLE_HTML_HPP +#define UI_C_SIMPLE_HTML_HPP + +#include "ui/CSimpleFontedFrame.hpp" +#include "ui/CSimpleHyperlinkedFrame.hpp" +#include "ui/Types.hpp" +#include + +class CStatus; +class XMLNode; + +struct CONTENTNODE : TSLinkedNode { + CSimpleFontString* string; + + ~CONTENTNODE(); +}; + +class CSimpleHTML : public CSimpleHyperlinkedFrame, CSimpleFontedFrame { + public: + // Static variables + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + + // Member variables + STORM_LIST(CONTENTNODE) m_content; + CLayoutFrame* m_layoutAnchor; + float m_layoutOffset; + CSimpleFontedFrameFont* m_fonts[NUM_HTML_TEXT_TYPES]; + + // Virtual member functions + virtual bool IsA(int32_t type); + virtual int32_t GetScriptMetaTable(void); + virtual void LoadXML(XMLNode* node, CStatus* status); + virtual void FontUpdated(CSimpleFontedFrameFont* font, int32_t a3); + + // Member functions + CSimpleHTML(CSimpleFrame*); + void AddText(const char* text, HTML_TEXT_TYPE type, uint32_t justify); + void ClearContent(); + void ParseBODY(XMLNode* node, CStatus* status); + void ParseIMG(XMLNode* node, CStatus* status); + void ParseP(XMLNode* node, HTML_TEXT_TYPE type, CStatus* status); + bool SetText(const char* text, CStatus* status); +}; + +#endif diff --git a/src/ui/CSimpleHTMLScript.cpp b/src/ui/CSimpleHTMLScript.cpp new file mode 100644 index 0000000..cffd746 --- /dev/null +++ b/src/ui/CSimpleHTMLScript.cpp @@ -0,0 +1,129 @@ +#include "ui/CSimpleHTMLScript.hpp" +#include "ui/CSimpleHTML.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleHTML_SetFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetFontObject(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetFont(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetTextColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetShadowColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetShadowOffset(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetSpacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetJustifyH(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetJustifyV(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetIndentedWordWrap(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetText(lua_State* L) { + auto type = CSimpleHTML::GetObjectType(); + auto html = static_cast(FrameScript_GetObjectThis(L, type)); + + auto text = lua_tolstring(L, 2, nullptr); + html->SetText(text, nullptr); + + return 0; +} + +int32_t CSimpleHTML_SetHyperlinkFormat(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetHyperlinkFormat(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_SetHyperlinksEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleHTML_GetHyperlinksEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleHTMLMethods[NUM_SIMPLE_HTML_SCRIPT_METHODS] = { + { "SetFontObject", &CSimpleHTML_SetFontObject }, + { "GetFontObject", &CSimpleHTML_GetFontObject }, + { "SetFont", &CSimpleHTML_SetFont }, + { "GetFont", &CSimpleHTML_GetFont }, + { "SetTextColor", &CSimpleHTML_SetTextColor }, + { "GetTextColor", &CSimpleHTML_GetTextColor }, + { "SetShadowColor", &CSimpleHTML_SetShadowColor }, + { "GetShadowColor", &CSimpleHTML_GetShadowColor }, + { "SetShadowOffset", &CSimpleHTML_SetShadowOffset }, + { "GetShadowOffset", &CSimpleHTML_GetShadowOffset }, + { "SetSpacing", &CSimpleHTML_SetSpacing }, + { "GetSpacing", &CSimpleHTML_GetSpacing }, + { "SetJustifyH", &CSimpleHTML_SetJustifyH }, + { "GetJustifyH", &CSimpleHTML_GetJustifyH }, + { "SetJustifyV", &CSimpleHTML_SetJustifyV }, + { "GetJustifyV", &CSimpleHTML_GetJustifyV }, + { "SetIndentedWordWrap", &CSimpleHTML_SetIndentedWordWrap }, + { "GetIndentedWordWrap", &CSimpleHTML_GetIndentedWordWrap }, + { "SetText", &CSimpleHTML_SetText }, + { "SetHyperlinkFormat", &CSimpleHTML_SetHyperlinkFormat }, + { "GetHyperlinkFormat", &CSimpleHTML_GetHyperlinkFormat }, + { "SetHyperlinksEnabled", &CSimpleHTML_SetHyperlinksEnabled }, + { "GetHyperlinksEnabled", &CSimpleHTML_GetHyperlinksEnabled } +}; diff --git a/src/ui/CSimpleHTMLScript.hpp b/src/ui/CSimpleHTMLScript.hpp new file mode 100644 index 0000000..1abc13b --- /dev/null +++ b/src/ui/CSimpleHTMLScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_HTML_SCRIPT_HPP +#define UI_C_SIMPLE_HTML_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_HTML_SCRIPT_METHODS 23 + +extern FrameScript_Method SimpleHTMLMethods[NUM_SIMPLE_HTML_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleHyperlinkedFrame.cpp b/src/ui/CSimpleHyperlinkedFrame.cpp new file mode 100644 index 0000000..ffc398f --- /dev/null +++ b/src/ui/CSimpleHyperlinkedFrame.cpp @@ -0,0 +1,5 @@ +#include "ui/CSimpleHyperlinkedFrame.hpp" + +CSimpleHyperlinkedFrame::CSimpleHyperlinkedFrame(CSimpleFrame* parent) : CSimpleFrame(parent) { + // TODO +} diff --git a/src/ui/CSimpleHyperlinkedFrame.hpp b/src/ui/CSimpleHyperlinkedFrame.hpp new file mode 100644 index 0000000..6467b27 --- /dev/null +++ b/src/ui/CSimpleHyperlinkedFrame.hpp @@ -0,0 +1,12 @@ +#ifndef UI_C_SIMPLE_HYPERLINKED_FRAME_HPP +#define UI_C_SIMPLE_HYPERLINKED_FRAME_HPP + +#include "ui/CSimpleFrame.hpp" + +class CSimpleHyperlinkedFrame : public CSimpleFrame { + public: + // Member functions + CSimpleHyperlinkedFrame(CSimpleFrame*); +}; + +#endif diff --git a/src/ui/CSimpleModel.cpp b/src/ui/CSimpleModel.cpp new file mode 100644 index 0000000..6e31aca --- /dev/null +++ b/src/ui/CSimpleModel.cpp @@ -0,0 +1,407 @@ +#include "ui/CSimpleModel.hpp" +#include "gx/Camera.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Draw.hpp" +#include "gx/Shader.hpp" +#include "gx/Transform.hpp" +#include "math/Utils.hpp" +#include "model/CM2Shared.hpp" +#include "model/M2Data.hpp" +#include "ui/CSimpleModelScript.hpp" +#include "ui/LoadXML.hpp" +#include +#include + +int32_t CSimpleModel::s_metatable; +int32_t CSimpleModel::s_objectType; + +void CSimpleModel::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleModel::RegisterScriptMethods); + CSimpleModel::s_metatable = ref; +} + +int32_t CSimpleModel::GetObjectType() { + if (!CSimpleModel::s_objectType) { + CSimpleModel::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleModel::s_objectType; +} + +void CSimpleModel::LightingCallback(CM2Model* model, CM2Lighting* lighting, void* userArg) { + CSimpleModel* simpleModel = static_cast(userArg); + + if (simpleModel->m_flags & 0x1) { + lighting->SetFog(simpleModel->m_fogColor, simpleModel->m_fogNear, simpleModel->m_fogFar); + } + + if (simpleModel->m_light.m_visible) { + lighting->AddLight(&simpleModel->m_light); + } +} + +void CSimpleModel::ModelLoaded(CM2Model* model, void* arg) { + auto simpleModel = static_cast(arg); + simpleModel->OnModelLoaded(model); +} + +void CSimpleModel::RegisterScriptMethods(lua_State* L) { + CSimpleFrame::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleModelMethods, NUM_SIMPLE_MODEL_SCRIPT_METHODS); +} + +void CSimpleModel::RenderModel(void* arg) { + auto simpleModel = static_cast(arg); + + if (!simpleModel->m_model) { + return; + } + + CRect viewRect; + + if (!simpleModel->GetRect(&viewRect)) { + return; + } + + C44Matrix proj; + GxXformProjection(proj); + + C44Matrix view; + GxXformView(view); + + float minX, maxX, minY, maxY, minZ, maxZ; + GxXformViewport(minX, maxX, minY, maxY, minZ, maxZ); + + C3Vector cameraPos; + C3Vector cameraTarg; + + if (simpleModel->m_camera) { + DataMgrGetCoord(simpleModel->m_camera, 7, &cameraPos); + DataMgrGetCoord(simpleModel->m_camera, 8, &cameraTarg); + CameraSetupWorldProjection(simpleModel->m_camera, viewRect, 0x0); + } else { + C2Vector v19 = { viewRect.minX, viewRect.minY }; + CameraSetupScreenProjection(viewRect, v19, 0.0f, 1); + } + + DDCToNDC(viewRect.minX, viewRect.minY, &viewRect.minX, &viewRect.minY); + DDCToNDC(viewRect.maxX, viewRect.maxY, &viewRect.maxX, &viewRect.maxY); + + viewRect.minX = std::max(0.0f, std::min(viewRect.minX, 1.0f)); + viewRect.maxX = std::max(0.0f, std::min(viewRect.maxX, 1.0f)); + viewRect.minY = std::max(0.0f, std::min(viewRect.minY, 1.0f)); + viewRect.maxY = std::max(0.0f, std::min(viewRect.maxY, 1.0f)); + + // Handle zero width or zero height + if (AreEqual(viewRect.minX, viewRect.maxX, WHOA_EPSILON_1) || AreEqual(viewRect.minY, viewRect.maxY, WHOA_EPSILON_1)) { + GxXformSetProjection(proj); + GxXformSetView(view); + GxXformSetViewport(minX, maxX, minY, maxY, minZ, maxZ); + + return; + } + + float v11 = viewRect.minX * maxX; + float v12 = viewRect.maxX * maxX; + float v13 = viewRect.minY * maxY; + float v14 = viewRect.maxY * maxY; + + GxXformSetViewport(v11, v12, v13, v14, 0.0f, 1.0f); + CImVector clearColor = { 0x00, 0x00, 0x00, 0xFF }; + GxSceneClear(0x2, clearColor); + CShaderEffect::UpdateProjMatrix(); + + simpleModel->UpdateModel(); + + if (simpleModel->m_pendingCameraIndex == -1u) { + simpleModel->GetScene()->Animate(cameraPos); + simpleModel->GetScene()->Draw(M2PASS_0); + simpleModel->GetScene()->Draw(M2PASS_1); + } + + GxXformSetProjection(proj); + GxXformSetView(view); + GxXformSetViewport(minX, maxX, minY, maxY, minZ, maxZ); + CShaderEffect::UpdateProjMatrix(); +} + +CSimpleModel::CSimpleModel(CSimpleFrame* parent) : CSimpleFrame(parent) { + this->m_light.SetVisible(0); + this->m_light.SetLightType(M2LIGHT_1); + this->m_light.m_ambColor = { 1.0f, 1.0f, 1.0f }; + this->m_light.m_dirColor = { 1.0f, 1.0f, 1.0f }; + + this->m_fogColor = { 0xFF, 0xFF, 0xFF, 0xFF }; +} + +CM2Scene* CSimpleModel::GetScene() { + if (!this->m_scene) { + this->m_scene = M2CreateScene(); + } + + return this->m_scene; +} + +FrameScript_Object::ScriptIx* CSimpleModel::GetScriptByName(const char* name, ScriptData& data) { + auto parentScript = CSimpleFrame::GetScriptByName(name, data); + + if (parentScript) { + return parentScript; + } + + if (!SStrCmpI(name, "OnUpdateModel", STORM_MAX_STR)) { + return &this->m_onUpdateModel; + } + + if (!SStrCmpI(name, "OnAnimFinished", STORM_MAX_STR)) { + return &this->m_onAnimFinished; + } + + return nullptr; +} + +int32_t CSimpleModel::GetScriptMetaTable() { + return CSimpleModel::s_metatable; +} + +bool CSimpleModel::IsA(int32_t type) { + return type == CSimpleModel::s_objectType + || type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleModel::LoadXML(XMLNode* node, CStatus* status) { + CSimpleFrame::LoadXML(node, status); + + const char* fileAttr = node->GetAttributeByName("file"); + if (fileAttr && *fileAttr) { + this->SetModel(fileAttr); + + if (!this->m_model) { + status->Add( + STATUS_WARNING, + "Frame %s: Bad model file: %s", + this->GetDisplayName(), + fileAttr + ); + } + } + + const char* scaleAttr = node->GetAttributeByName("scale"); + if (scaleAttr && *scaleAttr) { + float scale = SStrToFloat(scaleAttr); + + if (scale <= 0.0f) { + status->Add( + STATUS_WARNING, + "Frame %s: Invalid model scale: %s", + this->GetDisplayName(), + scaleAttr + ); + } else { + this->SetScale(scale); + } + } + + const char* fogNearAttr = node->GetAttributeByName("fogNear"); + if (fogNearAttr && *fogNearAttr) { + float fogNear = SStrToFloat(fogNearAttr); + + if (fogNear < 0.0f) { + fogNear = 0.0f; + } + + this->SetFogNear(fogNear); + } + + const char* fogFarAttr = node->GetAttributeByName("fogFar"); + if (fogFarAttr && *fogFarAttr) { + float fogFar = SStrToFloat(fogFarAttr); + + if (fogFar < 0.0f) { + fogFar = 0.0f; + } + + this->SetFogFar(fogFar); + } + + for (XMLNode* child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "FogColor", STORM_MAX_STR)) { + CImVector color; + LoadXML_Color(child, color); + this->SetFogColor(color); + } + } +} + +void CSimpleModel::OnFrameRender(CRenderBatch* batch, uint32_t layer) { + // TODO +} + +void CSimpleModel::OnLayerUpdate(float elapsedSec) { + CSimpleFrame::OnLayerUpdate(elapsedSec); + + if (this->m_model && this->m_visible && this->m_pendingCameraIndex == -1u) { + this->m_model->SetAnimating(1); + this->m_model->SetVisible(1); + + if (this->m_model->m_attachParent) { + this->m_model->m_flag20000 = 1; + } else { + this->m_model->m_flag10000 = 1; + } + } + + uint32_t advance = CMath::fuint(elapsedSec * 1000.f); + this->GetScene()->AdvanceTime(advance); +} + +void CSimpleModel::OnModelLoaded(CM2Model* model) { + model->SetAnimating(1); + + C3Vector cameraPos; + model->m_scene->Animate(cameraPos); + + if (this->m_pendingCameraIndex != -1u) { + this->SetCameraByIndex(this->m_pendingCameraIndex); + } else if (this->m_pendingCameraId != -1u) { + this->SetCameraByID(this->m_pendingCameraId); + } + + CAaBox bounds; + this->m_bounds = model->GetBoundingBox(bounds); + + this->Resize(0); +} + +void CSimpleModel::SetCamera(HCAMERA camera) { + if (camera) { + camera = HandleDuplicate(camera); + } + + if (this->m_camera) { + HandleClose(this->m_camera); + } + + this->m_camera = camera; + this->m_pendingCameraIndex = -1u; + this->m_pendingCameraId = -1u; +} + +void CSimpleModel::SetCameraByID(uint32_t id) { + // TODO +} + +void CSimpleModel::SetCameraByIndex(uint32_t index) { + if (!this->m_model || !this->m_model->IsLoaded(0, 0)) { + this->m_pendingCameraIndex = index; + + return; + } + + if (!this->m_model->m_loaded) { + this->m_model->WaitForLoad(nullptr); + } + + if (index >= this->m_model->m_shared->m_data->cameras.Count()) { + if (this->m_camera) { + HandleClose(this->m_camera); + } + + this->m_camera = nullptr; + this->m_pendingCameraIndex = -1u; + this->m_pendingCameraId = -1u; + + return; + } + + HCAMERA camera = this->m_model->GetCameraByIndex(index); + this->SetCamera(camera); +} + +void CSimpleModel::SetFogColor(CImVector& fogColor) { + this->m_fogColor = fogColor; + this->m_flags |= 0x1; +} + +void CSimpleModel::SetFogFar(float fogFar) { + this->m_fogFar = fogFar; +} + +void CSimpleModel::SetFogNear(float fogNear) { + this->m_fogNear = fogNear; +} + +void CSimpleModel::SetModel(const char* sourcefile) { + CM2Model* model = nullptr; + + if (sourcefile && *sourcefile) { + if (!this->m_scene) { + this->m_scene = M2CreateScene(); + } + + model = this->m_scene->CreateModel(sourcefile, 1); + } + + this->SetModel(model); + + if (model) { + model->Release(); + } +} + +void CSimpleModel::SetModel(CM2Model* model) { + if (model) { + model->m_refCount++; + } + + if (this->m_model) { + this->m_model->Release(); + } + + this->m_model = model; + + if (model) { + // TODO + this->m_model->SetLightingCallback(&CSimpleModel::LightingCallback, this); + this->m_model->SetLoadedCallback(&CSimpleModel::ModelLoaded, this); + } + + this->NotifyDrawLayerChanged(DRAWLAYER_ARTWORK); +} + +void CSimpleModel::SetScale(float scale) { + this->m_scale = scale; +} + +void CSimpleModel::SetSequence(uint32_t sequenceId) { + if (this->m_model) { + this->m_model->SetBoneSequence(-1, sequenceId, -1, 0, 1.0f, 0, 1); + } +} + +int32_t CSimpleModel::SetSequenceTime(uint32_t sequence, int32_t time) { + if (this->m_model) { + this->m_model->SetBoneSequence(-1, sequence, -1, time, 1.0f, 0, 1); + } + + return 1; +} + +void CSimpleModel::UpdateModel() { + if (this->m_onUpdateModel.luaRef) { + this->RunScript(this->m_onUpdateModel, 0, nullptr); + } + + C3Vector position = { + this->m_position.x * this->m_layoutScale, + this->m_position.y * this->m_layoutScale, + this->m_position.z * this->m_layoutScale + }; + + float scale = NDCToDDCHeight(1.0f) * 1.6666666 * (this->m_scale * this->m_layoutScale); + + this->m_model->SetWorldTransform(position, this->m_facing, scale); +} diff --git a/src/ui/CSimpleModel.hpp b/src/ui/CSimpleModel.hpp new file mode 100644 index 0000000..5876e2e --- /dev/null +++ b/src/ui/CSimpleModel.hpp @@ -0,0 +1,71 @@ +#ifndef C_SIMPLE_MODEL_HPP +#define C_SIMPLE_MODEL_HPP + +#include "gx/Camera.hpp" +#include "ui/CSimpleFrame.hpp" +#include "model/Model2.hpp" +#include +#include + +class CStatus; +class XMLNode; + +class CSimpleModel : public CSimpleFrame { + public: + // Static variables + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void LightingCallback(CM2Model* model, CM2Lighting* lighting, void* userArg); + static void ModelLoaded(CM2Model* model, void* arg); + static void RegisterScriptMethods(lua_State*); + static void RenderModel(void* arg); + + // Member variables + CM2Scene* m_scene = nullptr; + CM2Model* m_model = nullptr; + HCAMERA m_camera = nullptr; + uint32_t m_pendingCameraIndex = 0; + uint32_t m_pendingCameraId = 0; + CM2Light m_light; + C3Vector m_position; + float m_facing = 0.0f; + float m_scale = 1.0f; + uint32_t m_flags = 0; + CImVector m_fogColor = { 0 }; + float m_fogNear = 0.0f; + float m_fogFar = 1.0f; + CAaBox m_bounds; + ScriptIx m_onUpdateModel; + ScriptIx m_onAnimFinished; + + // Virtual member functions + virtual ScriptIx* GetScriptByName(const char* name, ScriptData& data); + virtual bool IsA(int32_t type); + virtual int32_t GetScriptMetaTable(void); + virtual void LoadXML(XMLNode*, CStatus*); + virtual void OnLayerUpdate(float elapsedSec); + virtual void OnFrameRender(CRenderBatch*, uint32_t); + virtual void UpdateModel(void); + virtual void OnModelLoaded(CM2Model* model); + + // Member functions + CSimpleModel(CSimpleFrame*); + CM2Scene* GetScene(void); + void SetCamera(HCAMERA camera); + void SetCameraByID(uint32_t id); + void SetCameraByIndex(uint32_t index); + void SetFogColor(CImVector&); + void SetFogFar(float); + void SetFogNear(float); + void SetModel(const char*); + void SetModel(CM2Model*); + void SetScale(float); + void SetSequence(uint32_t sequence); + int32_t SetSequenceTime(uint32_t sequence, int32_t time); +}; + +#endif diff --git a/src/ui/CSimpleModelFFX.cpp b/src/ui/CSimpleModelFFX.cpp new file mode 100644 index 0000000..a8140fa --- /dev/null +++ b/src/ui/CSimpleModelFFX.cpp @@ -0,0 +1,64 @@ +#include "ui/CSimpleModelFFX.hpp" +#include "gx/Draw.hpp" +#include "ui/CRenderBatch.hpp" +#include "ui/CSimpleModelFFXScript.hpp" +#include + +int32_t CSimpleModelFFX::s_metatable; + +CSimpleFrame* CSimpleModelFFX::Create(CSimpleFrame* parent) { + // TODO + // void* m = CDataAllocator::GetData(CSimpleModelFFX::s_simpleModelFFXHeap, 0, __FILE__, __LINE__); + + void* m = SMemAlloc(sizeof(CSimpleModelFFX), __FILE__, __LINE__, 0); + + if (m) { + return new (m) CSimpleModelFFX(parent); + } else { + return nullptr; + } +} + +void CSimpleModelFFX::Render(void* arg) { + CSimpleModelFFX* simpleModel = static_cast(arg); + + CImVector clearColor = { 0x00, 0x00, 0x00, 0xFF }; + GxSceneClear(0x1 | 0x2, clearColor); + + // TODO + + GxSceneClear(0x1 | 0x2, clearColor); + + if (simpleModel->m_model) { + CSimpleModel::RenderModel(simpleModel); + } + + // TODO +} + +void CSimpleModelFFX::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleModelFFX::RegisterScriptMethods); + CSimpleModelFFX::s_metatable = ref; +} + +void CSimpleModelFFX::RegisterScriptMethods(lua_State* L) { + CSimpleModel::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleModelFFXMethods, NUM_SIMPLE_MODEL_FFX_SCRIPT_METHODS); +} + +CSimpleModelFFX::CSimpleModelFFX(CSimpleFrame* parent) : CSimpleModel(parent) { + // TODO +} + +int32_t CSimpleModelFFX::GetScriptMetaTable() { + return CSimpleModelFFX::s_metatable; +} + +void CSimpleModelFFX::OnFrameRender(CRenderBatch* batch, uint32_t layer) { + CSimpleFrame::OnFrameRender(batch, layer); + + if (layer == DRAWLAYER_ARTWORK) { + batch->QueueCallback(CSimpleModelFFX::Render, this); + } +} diff --git a/src/ui/CSimpleModelFFX.hpp b/src/ui/CSimpleModelFFX.hpp new file mode 100644 index 0000000..5503cf7 --- /dev/null +++ b/src/ui/CSimpleModelFFX.hpp @@ -0,0 +1,27 @@ +#ifndef UI_C_SIMPLE_MODEL_FFX_HPP +#define UI_C_SIMPLE_MODEL_FFX_HPP + +#include "ui/CSimpleModel.hpp" + +class CSimpleFrame; + +class CSimpleModelFFX : public CSimpleModel { + public: + // Static variables + static int32_t s_metatable; + + // Static functions + static CSimpleFrame* Create(CSimpleFrame*); + static void CreateScriptMetaTable(void); + static void RegisterScriptMethods(lua_State*); + static void Render(void*); + + // Virtual member functions + virtual int32_t GetScriptMetaTable(void); + virtual void OnFrameRender(CRenderBatch*, uint32_t); + + // Member functions + CSimpleModelFFX(CSimpleFrame*); +}; + +#endif diff --git a/src/ui/CSimpleModelFFXScript.cpp b/src/ui/CSimpleModelFFXScript.cpp new file mode 100644 index 0000000..8bebe8e --- /dev/null +++ b/src/ui/CSimpleModelFFXScript.cpp @@ -0,0 +1,26 @@ +#include "ui/CSimpleModelFFXScript.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleModelFFX_ResetLights(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModelFFX_AddLight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModelFFX_AddCharacterLight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModelFFX_AddPetLight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleModelFFXMethods[NUM_SIMPLE_MODEL_FFX_SCRIPT_METHODS] = { + { "ResetLights", &CSimpleModelFFX_ResetLights }, + { "AddLight", &CSimpleModelFFX_AddLight }, + { "AddCharacterLight", &CSimpleModelFFX_AddCharacterLight }, + { "AddPetLight", &CSimpleModelFFX_AddPetLight } +}; diff --git a/src/ui/CSimpleModelFFXScript.hpp b/src/ui/CSimpleModelFFXScript.hpp new file mode 100644 index 0000000..4e9a0df --- /dev/null +++ b/src/ui/CSimpleModelFFXScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_MODEL_FFX_SCRIPT_HPP +#define UI_C_SIMPLE_MODEL_FFX_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_MODEL_FFX_SCRIPT_METHODS 4 + +extern FrameScript_Method SimpleModelFFXMethods[NUM_SIMPLE_MODEL_FFX_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleModelScript.cpp b/src/ui/CSimpleModelScript.cpp new file mode 100644 index 0000000..984cca8 --- /dev/null +++ b/src/ui/CSimpleModelScript.cpp @@ -0,0 +1,189 @@ +#include "ui/CSimpleModelScript.hpp" +#include "ui/CSimpleModel.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleModel_SetModel(lua_State* L) { + auto type = CSimpleModel::GetObjectType(); + auto model = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isstring(L, 2)) { + return luaL_error(L, "Usage: %s:SetModel(\"file\")", model->GetDisplayName()); + } + + const char* file = lua_tolstring(L, 2, 0); + + model->SetModel(file); + + if (!model->m_model) { + return luaL_error(L, "Invalid model file: %s", file); + } + + return 0; +} + +int32_t CSimpleModel_GetModel(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_ClearModel(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_SetPosition(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_SetFacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_SetScale(lua_State* L) { + auto type = CSimpleModel::GetObjectType(); + auto model = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "Usage: %s:SetScale(scale)", model->GetDisplayName()); + } + + float scale = lua_tonumber(L, 2); + model->SetScale(scale); + + return 0; +} + +int32_t CSimpleModel_SetSequence(lua_State* L) { + auto type = CSimpleModel::GetObjectType(); + auto model = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "Usage: %s:SetSequence(sequence)", model->GetDisplayName()); + } + + uint32_t sequence = lua_tonumber(L, 2); + + if (sequence >= 506) { + return luaL_error(L, "Error: %s:SetSequence(sequence) exceeds valid range of 0 - %d", model->GetDisplayName(), 506); + } + + model->SetSequence(sequence); + + return 0; +} + +int32_t CSimpleModel_SetSequenceTime(lua_State* L) { + auto type = CSimpleModel::GetObjectType(); + auto model = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2) || !lua_isnumber(L, 3)) { + return luaL_error(L, "Usage: %s:SetSequenceTime(sequence, time)", model->GetDisplayName()); + } + + uint32_t sequence = lua_tonumber(L, 2); + int32_t time = lua_tonumber(L, 3); + model->SetSequenceTime(sequence, time); + + return 0; +} + +int32_t CSimpleModel_SetCamera(lua_State* L) { + auto type = CSimpleModel::GetObjectType(); + auto model = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2)) { + return luaL_error(L, "Usage: %s:SetCamera(index)", model->GetDisplayName()); + } + + int32_t index = lua_tonumber(L, 2); + model->SetCameraByIndex(index); + + return 0; +} + +int32_t CSimpleModel_SetLight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_GetLight(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_GetPosition(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_GetFacing(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_GetScale(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_AdvanceTime(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_ReplaceIconTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_SetFogColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_GetFogColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_SetFogNear(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_GetFogNear(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_SetFogFar(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_GetFogFar(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_ClearFog(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleModel_SetGlow(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleModelMethods[NUM_SIMPLE_MODEL_SCRIPT_METHODS] = { + { "SetModel", &CSimpleModel_SetModel }, + { "GetModel", &CSimpleModel_GetModel }, + { "ClearModel", &CSimpleModel_ClearModel }, + { "SetPosition", &CSimpleModel_SetPosition }, + { "SetFacing", &CSimpleModel_SetFacing }, + { "SetModelScale", &CSimpleModel_SetScale }, + { "SetSequence", &CSimpleModel_SetSequence }, + { "SetSequenceTime", &CSimpleModel_SetSequenceTime }, + { "SetCamera", &CSimpleModel_SetCamera }, + { "SetLight", &CSimpleModel_SetLight }, + { "GetLight", &CSimpleModel_GetLight }, + { "GetPosition", &CSimpleModel_GetPosition }, + { "GetFacing", &CSimpleModel_GetFacing }, + { "GetModelScale", &CSimpleModel_GetScale }, + { "AdvanceTime", &CSimpleModel_AdvanceTime }, + { "ReplaceIconTexture", &CSimpleModel_ReplaceIconTexture }, + { "SetFogColor", &CSimpleModel_SetFogColor }, + { "GetFogColor", &CSimpleModel_GetFogColor }, + { "SetFogNear", &CSimpleModel_SetFogNear }, + { "GetFogNear", &CSimpleModel_GetFogNear }, + { "SetFogFar", &CSimpleModel_SetFogFar }, + { "GetFogFar", &CSimpleModel_GetFogFar }, + { "ClearFog", &CSimpleModel_ClearFog }, + { "SetGlow", &CSimpleModel_SetGlow } +}; diff --git a/src/ui/CSimpleModelScript.hpp b/src/ui/CSimpleModelScript.hpp new file mode 100644 index 0000000..53ba4fc --- /dev/null +++ b/src/ui/CSimpleModelScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_MODEL_SCRIPT_HPP +#define UI_C_SIMPLE_MODEL_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_MODEL_SCRIPT_METHODS 24 + +extern FrameScript_Method SimpleModelMethods[NUM_SIMPLE_MODEL_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleRegion.cpp b/src/ui/CSimpleRegion.cpp new file mode 100644 index 0000000..7373efc --- /dev/null +++ b/src/ui/CSimpleRegion.cpp @@ -0,0 +1,162 @@ +#include "ui/CSimpleRegion.hpp" +#include "ui/CSimpleFrame.hpp" +#include + +CSimpleRegion::~CSimpleRegion() { + this->SetFrame(nullptr, DRAWLAYER_BACKGROUND_BORDER, 0); +} + +CSimpleRegion::CSimpleRegion(CSimpleFrame* frame, uint32_t drawlayer, int32_t show) : CScriptRegion() { + memset(this->m_color, 0, sizeof(CImVector) * this->m_colorCount); + this->m_colorCount = 0; + + memset(this->m_alpha, 0, sizeof(uint8_t) * this->m_alphaCount); + this->m_alphaCount = 0; + + if (frame) { + this->SetFrame(frame, drawlayer, show); + } +} + +void CSimpleRegion::GetVertexColor(CImVector& color) const { + if (this->m_colorCount == 1) { + color.a = this->m_alpha[0]; + color.r = this->m_color[0].r; + color.g = this->m_color[0].g; + color.b = this->m_color[0].b; + } else { + color = { 0xFF, 0xFF, 0xFF, 0xFF }; + } +} + +void CSimpleRegion::Hide() { + this->m_shown = 0; + this->HideThis(); +} + +void CSimpleRegion::HideThis() { + if (this->m_visible && this->m_parent) { + if (!this->m_parent->m_loading) { + this->SetDeferredResize(1); + } + + this->m_parent->RemoveFrameRegion(this, this->m_drawlayer); + this->m_visible = 0; + } +} + +bool CSimpleRegion::IsShown() { + return this->m_shown == 1; +} + +void CSimpleRegion::OnColorChanged(bool a2) { + if (this->m_parent) { + // TODO adjust color based on parent + } + + if (a2) { + this->OnRegionChanged(); + } +} + +void CSimpleRegion::OnRegionChanged() { + if (this->m_visible && this->m_parent) { + this->m_parent->NotifyDrawLayerChanged(this->m_drawlayer); + } +} + +void CSimpleRegion::SetVertexColor(const CImVector& color) { + if ( + this->m_colorCount == 0 + && color.a >= 0xFE + && color.r == 0xFF + && color.g == 0xFF + && color.b == 0xFF + ) { + return; + } + + if ( + this->m_colorCount == 1 + && this->m_alpha[0] == color.a + && this->m_color[0].r == color.r + && this->m_color[0].g == color.g + && this->m_color[0].b == color.b + ) { + return; + } + + bool b1 = false; + + if (this->m_colorCount != 1) { + this->m_colorCount = 1; + this->m_alphaCount = 1; + + b1 = true; + } + + this->m_color[0] = color; + this->m_alpha[0] = color.a; + + this->OnColorChanged(b1); +} + +void CSimpleRegion::SetFrame(CSimpleFrame* frame, uint32_t drawlayer, int32_t show) { + if (this->m_parent == frame) { + if (this->m_drawlayer == drawlayer) { + if (show != this->m_shown) { + if (show) { + this->Show(); + } else { + this->Hide(); + } + } + } else { + if (this->m_shown) { + this->Hide(); + } + + this->m_drawlayer = drawlayer; + + if (show) { + this->Show(); + } + } + } else { + if (this->m_parent) { + this->HideThis(); + this->m_parent->UnregisterRegion(this); + } + + this->m_parent = frame; + this->m_drawlayer = drawlayer; + + if (frame) { + frame->RegisterRegion(this); + this->SetDeferredResize(static_cast(this->m_parent)->m_flags & 0x2); + this->OnColorChanged(0); + + if (show) { + this->Show(); + } else { + this->Hide(); + } + } + } +} + +void CSimpleRegion::Show() { + this->m_shown = 1; + this->ShowThis(); +} + +void CSimpleRegion::ShowThis() { + if (this->m_shown && this->m_parent && this->m_parent->m_visible && !this->m_visible) { + if (!this->m_parent->m_loading) { + this->SetDeferredResize(0); + } + + this->m_parent->AddFrameRegion(this, this->m_drawlayer); + this->m_visible = 1; + } +} diff --git a/src/ui/CSimpleRegion.hpp b/src/ui/CSimpleRegion.hpp new file mode 100644 index 0000000..2ed96b2 --- /dev/null +++ b/src/ui/CSimpleRegion.hpp @@ -0,0 +1,42 @@ +#ifndef UI_C_SIMPLE_REGION_HPP +#define UI_C_SIMPLE_REGION_HPP + +#include "ui/CScriptRegion.hpp" +#include +#include + +class CRenderBatch; + +class CSimpleRegion : public CScriptRegion { + public: + // Member variables + uint32_t m_alphaCount = 4; + uint8_t m_alpha[4]; + uint32_t m_colorCount = 4; + CImVector m_color[4]; + TSLink m_regionLink; + TSLink m_layerLink; + int32_t m_drawlayer = 0; + int32_t m_shown = 0; + int32_t m_visible = 0; + + // Virtual member functions + virtual ~CSimpleRegion(); + virtual void OnColorChanged(bool); + virtual void OnScreenSizeChanged() {}; + virtual void Draw(CRenderBatch*) = 0; + + // Member functions + CSimpleRegion(CSimpleFrame*, uint32_t, int32_t); + void GetVertexColor(CImVector& color) const; + void Hide(void); + void HideThis(void); + bool IsShown(); + void OnRegionChanged(void); + void SetVertexColor(const CImVector&); + void SetFrame(CSimpleFrame*, uint32_t, int32_t); + void Show(void); + void ShowThis(void); +}; + +#endif diff --git a/src/ui/CSimpleRender.cpp b/src/ui/CSimpleRender.cpp new file mode 100644 index 0000000..53c0beb --- /dev/null +++ b/src/ui/CSimpleRender.cpp @@ -0,0 +1,168 @@ +#include "ui/CSimpleRender.hpp" +#include "gx/Buffer.hpp" +#include "gx/Device.hpp" +#include "gx/Draw.hpp" +#include "gx/Font.hpp" +#include "gx/Gx.hpp" +#include "gx/RenderState.hpp" +#include "gx/Shader.hpp" +#include "gx/Texture.hpp" +#include "gx/Transform.hpp" +#include "gx/Types.hpp" +#include "ui/CRenderBatch.hpp" + +CGxShader* CSimpleRender::s_vertexShader[2]; + +void CSimpleRender::DrawBatch(CRenderBatch* batch) { + if (batch->m_texturelist.Count()) { + GxRsPush(); + + CGxShader* vs = g_theGxDevicePtr->StereoEnabled() + ? CSimpleRender::s_vertexShader[1] + : CSimpleRender::s_vertexShader[0]; + + if (vs && vs->Valid()) { + GxRsSet(GxRs_VertexShader, vs); + + C44Matrix viewProjMat; + GxXformViewProjNativeTranspose(viewProjMat); + GxShaderConstantsSet(GxSh_Vertex, 0, reinterpret_cast(&viewProjMat), 4); + } else { + GxRsSet(GxRs_Lighting, 0); + } + + GxRsSet(GxRs_Fog, 0); + GxRsSet(GxRs_DepthTest, 0); + GxRsSet(GxRs_DepthWrite, 0); + + CGxTex* textureID = nullptr; + EGxBlend alphaMode = GxBlends_Last; + CGxShader* ps = nullptr; + + uint32_t idxCount = 0; + uint32_t posCount = 0; + + for (int32_t i = 0; i < batch->m_texturelist.Count(); i++) { + auto mesh = &batch->m_texturelist[i]; + + auto dirty = 0; + + if (mesh->textureID != textureID) { + textureID = mesh->textureID; + dirty = 1; + } + + if (mesh->GetAlphaMode() != alphaMode) { + alphaMode = mesh->GetAlphaMode(); + dirty = 1; + } + + if (mesh->shader != ps) { + ps = mesh->shader; + dirty = 1; + } + + CTexture* texture = mesh->texture; + CGxTex* gxTex = TextureGetGxTex(texture, 1, nullptr); + GxRsSet(GxRs_Texture0, gxTex); + + GxRsSet(GxRs_BlendingMode, alphaMode); + + GxRsSet(GxRs_PixelShader, ps); + + // TODO + // - alphaRef + + // TODO + // - geometry merging (CSimpleRender::s_mergeEnable) + + posCount = mesh->posCount; + idxCount = mesh->idxCount; + + CGxBuf* vertexStream = g_theGxDevicePtr->BufStream(GxPoolTarget_Vertex, 24, posCount); + char* vertexData = g_theGxDevicePtr->BufLock(vertexStream); + CGxVertexPCT* vertexBuf = reinterpret_cast(vertexData); + + CGxBuf* indexStream = g_theGxDevicePtr->BufStream(GxPoolTarget_Index, 2, idxCount); + char* indexData = g_theGxDevicePtr->BufLock(indexStream); + uint16_t* indexBuf = reinterpret_cast(indexData); + + if (mesh->indices) { + for (int32_t i = 0; i < mesh->posCount; i++) { + C3Vector* p = &mesh->position[i]; + vertexBuf->p.x = p->x; + vertexBuf->p.y = p->y; + vertexBuf->p.z = p->z; + + if (mesh->color) { + auto& color = mesh->color[i * mesh->colorCount >> 2]; + + if (GxCaps()->m_colorFormat == GxCF_rgba) { + vertexBuf->c.r = color.b; + vertexBuf->c.g = color.g; + vertexBuf->c.b = color.r; + vertexBuf->c.a = color.a; + } else { + vertexBuf->c = color; + } + } else { + vertexBuf->c.r = 0xFF; + vertexBuf->c.g = 0xFF; + vertexBuf->c.b = 0xFF; + vertexBuf->c.a = 0xFF; + } + + if (mesh->onAtlas) { + // TODO + } else { + C2Vector* tc = &mesh->texCoord[i]; + vertexBuf->tc[0].x = tc->x; + vertexBuf->tc[0].y = tc->y; + } + + vertexBuf++; + } + + for (int32_t i = 0; i < mesh->idxCount; i++) { + *indexBuf = mesh->indices[i]; + indexBuf++; + } + } + + GxBufUnlock(vertexStream, 24 * posCount); + GxBufUnlock(indexStream, 2 * idxCount); + + GxPrimVertexPtr(vertexStream, GxVBF_PCT); + GxPrimIndexPtr(indexStream); + + CGxBatch batch; + batch.m_primType = GxPrim_Triangles; + batch.m_start = 0; + batch.m_count = 6; + batch.m_minIndex = 0; + batch.m_maxIndex = 3; + + GxDraw(&batch, 1); + } + + GxRsPop(); + } + + if (batch->m_stringbatch) { + GxuFontRenderBatch(batch->m_stringbatch); + } + + for (auto callback = batch->m_callbacks.Head(); callback; callback = batch->m_callbacks.Link(callback)->Next()) { + callback->callback(callback->param); + } +} + +void CSimpleRender::Init() { + g_theGxDevicePtr->ShaderCreate( + CSimpleRender::s_vertexShader, + GxSh_Vertex, + "Shaders\\Vertex", + "UI", + 2 + ); +} diff --git a/src/ui/CSimpleRender.hpp b/src/ui/CSimpleRender.hpp new file mode 100644 index 0000000..63c9899 --- /dev/null +++ b/src/ui/CSimpleRender.hpp @@ -0,0 +1,17 @@ +#ifndef UI_C_SIMPLE_RENDER_HPP +#define UI_C_SIMPLE_RENDER_HPP + +class CGxShader; +class CRenderBatch; + +class CSimpleRender { + public: + // Static variables + static CGxShader* s_vertexShader[]; + + // Static functions + static void DrawBatch(CRenderBatch*); + static void Init(void); +}; + +#endif diff --git a/src/ui/CSimpleScrollFrame.cpp b/src/ui/CSimpleScrollFrame.cpp new file mode 100644 index 0000000..93dbc9d --- /dev/null +++ b/src/ui/CSimpleScrollFrame.cpp @@ -0,0 +1,410 @@ +#include "ui/CSimpleScrollFrame.hpp" +#include "gx/Camera.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Transform.hpp" +#include "ui/CRenderBatch.hpp" +#include "ui/CSimpleScrollFrameScript.hpp" +#include "ui/FrameXML.hpp" +#include "util/CStatus.hpp" +#include "util/Lua.hpp" +#include +#include +#include +#include + +int32_t CSimpleScrollFrame::s_metatable; +int32_t CSimpleScrollFrame::s_objectType; + +void CSimpleScrollFrame::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleScrollFrame::RegisterScriptMethods); + CSimpleScrollFrame::s_metatable = ref; +} + +int32_t CSimpleScrollFrame::GetObjectType() { + if (!CSimpleScrollFrame::s_objectType) { + CSimpleScrollFrame::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleScrollFrame::s_objectType; +} + +void CSimpleScrollFrame::RegisterScriptMethods(lua_State* L) { + CSimpleFrame::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleScrollFrameMethods, NUM_SIMPLE_SCROLL_FRAME_SCRIPT_METHODS); +} + +void CSimpleScrollFrame::RenderScrollChild(void* param) { + auto frame = static_cast(param); + + CRect viewRect; + if (!frame->GetHitRect(viewRect)) { + return; + } + + auto scrollChild = frame->m_scrollChild; + if (!scrollChild) { + return; + } + + if (!scrollChild->m_visible) { + return; + } + + C44Matrix savedProj; + GxXformProjection(savedProj); + + C44Matrix savedView; + GxXformView(savedView); + + float minX, maxX, minY, maxY, minZ, maxZ; + GxXformViewport(minX, maxX, minY, maxY, minZ, maxZ); + + // TODO clean up + float v34 = 0.0f; + float v35 = 0.0f; + float v36 = 0.0f; + float v37 = 0.0f; + NDCToDDC(minX, minY, &v35, &v34); + NDCToDDC(maxX, maxY, &v37, &v36); + + auto v4 = viewRect.minX; + if (v35 > viewRect.minX) { + v4 = v35; + viewRect.minX = v35; + } + auto v5 = viewRect.maxX; + if (v37 < viewRect.maxX) { + v5 = v37; + viewRect.maxX = v37; + } + auto v6 = viewRect.minY; + if (v34 > viewRect.minY) { + v6 = v34; + viewRect.minY = v34; + } + auto v7 = viewRect.maxY; + if (v36 < viewRect.maxY) { + v7 = v36; + viewRect.maxY = v36; + } + + if (v4 != v5 && v6 != v7) { + C2Vector screenPoint = { 0.0f, 0.0f }; + CameraSetupScreenProjection(viewRect, screenPoint, 0.0f, 0); + + DDCToNDC(viewRect.minX, viewRect.minY, &viewRect.minX, &viewRect.minY); + DDCToNDC(viewRect.maxX, viewRect.maxY, &viewRect.maxX, &viewRect.maxY); + + float v11, v12, v13, v14; + + if (viewRect.minX >= 0.0f) { + v12 = viewRect.minX; + v11 = viewRect.maxX; + } else { + v11 = viewRect.maxX - viewRect.minX; + viewRect.maxX = v11; + v12 = 0.0f; + viewRect.minX = 0.0f; + } + + if (v11 > 1.0f) { + v12 = v12 - (v11 - 1.0f); + viewRect.minX = v12; + viewRect.maxX = 1.0f; + v11 = 1.0f; + } + + if (viewRect.minY >= 0.0f) { + v14 = viewRect.minY; + v13 = viewRect.maxY; + } else { + v13 = viewRect.maxY - viewRect.minY; + viewRect.maxY = v13; + v14 = 0.0f; + viewRect.minY = 0.0f; + } + + float v15 = v14; + float v16 = v13; + float v17 = v15; + + if (v16 > 1.0f) { + v17 = v17 - (v16 - 1.0f); + viewRect.minY = v17; + v16 = 1.0f; + viewRect.maxY = 1.0f; + } + + float v18 = v16; + float v19 = v12; + float v20 = v18; + + if (v19 < v11) { + float v21 = v19; + float v22 = v17; + float v23 = v21; + + if (v22 < v20) { + float v27 = v20; + float v26 = v22; + float v25 = v11; + float v24 = v23; + + GxXformSetViewport(v24, v25, v26, v27, 0.0f, 1.0f); + scrollChild->OnFrameRender(); + } + } + + GxXformSetProjection(savedProj); + GxXformSetView(savedView); + GxXformSetViewport(minX, maxX, minY, maxY, minZ, maxZ); + } +} + +CSimpleScrollFrame::CSimpleScrollFrame(CSimpleFrame* parent) : CSimpleFrame(parent) { + // TODO +} + +int32_t CSimpleScrollFrame::GetBoundsRect(CRect& bounds) { + if (this->IsResizePending()) { + this->Resize(1); + } + + CRect rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (this->GetRect(&rect)) { + bounds.minX = rect.minX >= bounds.minX ? bounds.minX : rect.minX; + bounds.maxX = rect.maxX <= bounds.maxX ? bounds.maxX : rect.maxX; + bounds.minY = rect.minY >= bounds.minY ? bounds.minY : rect.minY; + bounds.maxY = rect.maxY <= bounds.maxY ? bounds.maxY : rect.maxY; + } + + for (auto region = this->m_regions.Head(); region; region = this->m_regions.Next(region)) { + if (region->IsShown()) { + if (region->IsResizePending()) { + region->Resize(1); + } + + if (region->GetRect(&rect)) { + bounds.minX = rect.minX >= bounds.minX ? bounds.minX : rect.minX; + bounds.maxX = rect.maxX <= bounds.maxX ? bounds.maxX : rect.maxX; + bounds.minY = rect.minY >= bounds.minY ? bounds.minY : rect.minY; + bounds.maxY = rect.maxY <= bounds.maxY ? bounds.maxY : rect.maxY; + } + } + } + + for (auto child = this->m_children.Head(); child; child = this->m_children.Next(child)) { + if (child->frame != this->m_scrollChild && child->frame->m_shown) { + child->frame->GetBoundsRect(bounds); + } + } + + return bounds.maxY > bounds.minY && bounds.maxX > bounds.minX; +} + +FrameScript_Object::ScriptIx* CSimpleScrollFrame::GetScriptByName(const char* name, ScriptData& data) { + auto parentScript = CSimpleFrame::GetScriptByName(name, data); + + if (parentScript) { + return parentScript; + } + + if (!SStrCmpI(name, "OnHorizontalScroll", STORM_MAX_STR)) { + data.wrapper = "return function(self,offset) %s end"; + return &this->m_onHorizontalScroll; + } + + if (!SStrCmpI(name, "OnVerticalScroll", STORM_MAX_STR)) { + data.wrapper = "return function(self,offset) %s end"; + return &this->m_onVerticalScroll; + } + + if (!SStrCmpI(name, "OnScrollRangeChanged", STORM_MAX_STR)) { + data.wrapper = "return function(self,xrange,yrange) %s end"; + return &this->m_onScrollRangeChanged; + } + + return nullptr; +} + +int32_t CSimpleScrollFrame::GetScriptMetaTable() { + return CSimpleScrollFrame::s_metatable; +} + +bool CSimpleScrollFrame::IsA(int32_t type) { + return type == CSimpleScrollFrame::s_objectType + || type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleScrollFrame::LoadXML(XMLNode* node, CStatus* status) { + CSimpleFrame::LoadXML(node, status); + + XMLNode* scrollChildNode = node->GetChildByName("ScrollChild"); + + if (scrollChildNode) { + XMLNode* childNode = scrollChildNode->m_child; + + if (childNode) { + CSimpleFrame* childFrame = FrameXML_CreateFrame(childNode, this, status); + + if (childFrame) { + this->SetScrollChild(childFrame); + } + } else { + const char* frameName = this->GetName(); + + if (!frameName) { + frameName = ""; + } + + status->Add( + STATUS_WARNING, + "Frame %s: Scroll frame created without scroll child", + frameName + ); + } + } +} + +void CSimpleScrollFrame::OnFrameRender(CRenderBatch* batch, uint32_t layer) { + CSimpleFrame::OnFrameRender(batch, layer); + + if (layer == DRAWLAYER_HIGHLIGHT) { + batch->QueueCallback(CSimpleScrollFrame::RenderScrollChild, this); + } +} + +void CSimpleScrollFrame::OnFrameSizeChanged(float w, float h) { + CSimpleFrame::OnFrameSizeChanged(w, h); + this->m_updateScrollChild = 1; +} + +void CSimpleScrollFrame::OnLayerUpdate(float elapsedSec) { + CSimpleFrame::OnLayerUpdate(elapsedSec); + + if (this->m_updateScrollChild) { + CRect rect = { 0.0f, 0.0f, 0.0f, 0.0f }; + + if (this->GetRect(&rect)) { + float w = rect.maxX - rect.minX; + float h = rect.maxY - rect.minY; + this->UpdateScrollChildRect(w, h); + } + + this->m_updateScrollChild = 0; + } +} + +void CSimpleScrollFrame::RunOnScrollRangeChangedScript() { + if (!this->m_onScrollRangeChanged.luaRef) { + return; + } + + auto L = FrameScript_GetContext(); + + auto rangeX = this->m_scrollRange.x; + auto ddcRangeX = CoordinateGetAspectCompensation() * 1024.0f * rangeX; + auto ndcRangeX = DDCToNDCWidth(ddcRangeX); + lua_pushnumber(L, ndcRangeX); + + auto rangeY = this->m_scrollRange.y; + auto ddcRangeY = CoordinateGetAspectCompensation() * 1024.0f * rangeY; + auto ndcRangeY = DDCToNDCWidth(ddcRangeY); + lua_pushnumber(L, ndcRangeY); + + this->RunScript(this->m_onScrollRangeChanged, 2, nullptr); +} + +void CSimpleScrollFrame::RunOnVerticalScrollScript() { + if (!this->m_onVerticalScroll.luaRef) { + return; + } + + auto L = FrameScript_GetContext(); + + auto offsetY = this->m_scrollOffset.y; + auto ddcOffsetY = CoordinateGetAspectCompensation() * 1024.0f * offsetY; + auto ndcOffsetY = DDCToNDCWidth(ddcOffsetY); + lua_pushnumber(L, ndcOffsetY); + + this->RunScript(this->m_onVerticalScroll, 1, nullptr); +} + +void CSimpleScrollFrame::SetScrollChild(CSimpleFrame* frame) { + if (this->m_scrollChild) { + this->m_scrollChild->SetBeingScrolled(0, 0); + this->m_scrollChild->SetParent(nullptr); + } + + this->m_scrollChild = frame; + + if (this->m_scrollChild) { + this->m_scrollChild->SetParent(this); + this->m_scrollChild->SetBeingScrolled(1, 1); + } + + this->UpdateScrollChild(); + + this->m_updateScrollChild = 1; +} + +void CSimpleScrollFrame::SetVerticalScroll(float offset) { + if (fabs(offset - this->m_scrollOffset.y) >= 0.00000095367432) { + this->m_scrollOffset.y = offset; + this->UpdateScrollChild(); + this->RunOnVerticalScrollScript(); + } +} + +void CSimpleScrollFrame::UpdateScrollChild() { + if (!this->m_scrollChild) { + return; + } + + this->m_scrollChild->ClearAllPoints(); + this->m_scrollChild->SetPoint( + FRAMEPOINT_TOPLEFT, + this, + FRAMEPOINT_TOPLEFT, + -this->m_scrollOffset.x, + this->m_scrollOffset.y, + 1 + ); +} + +void CSimpleScrollFrame::UpdateScrollChildRect(float w, float h) { + if (!this->m_scrollChild) { + return; + } + + float rangeX = this->m_scrollRange.x; + float rangeY = this->m_scrollRange.y; + + CRect childBounds = { + std::numeric_limits::max(), + std::numeric_limits::max(), + 0.0f, + 0.0f + }; + + this->m_scrollChild->GetBoundsRect(childBounds); + + auto ooScale = 1.0f / this->m_layoutScale; + + auto v7 = std::max(childBounds.maxX - childBounds.minX - w, 0.0f); + this->m_scrollRange.x = v7 * ooScale; + + auto v12 = std::max(childBounds.maxY - childBounds.minY - h, 0.0f); + this->m_scrollRange.y = v12 * ooScale; + + if ( + fabs(this->m_scrollRange.x - rangeX) >= 0.00000095367432 + || fabs(this->m_scrollRange.y - rangeY) >= 0.00000095367432 + ) { + this->RunOnScrollRangeChangedScript(); + } +} diff --git a/src/ui/CSimpleScrollFrame.hpp b/src/ui/CSimpleScrollFrame.hpp new file mode 100644 index 0000000..a4da539 --- /dev/null +++ b/src/ui/CSimpleScrollFrame.hpp @@ -0,0 +1,51 @@ +#ifndef UI_C_SIMPLE_SCROLL_FRAME_HPP +#define UI_C_SIMPLE_SCROLL_FRAME_HPP + +#include "ui/CSimpleFrame.hpp" +#include + +class CRect; +class CRenderBatch; + +class CSimpleScrollFrame : public CSimpleFrame { + public: + // Static variables + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + static void RenderScrollChild(void* param); + + // Member variables + int32_t m_updateScrollChild = 0; + CSimpleFrame* m_scrollChild = nullptr; + C2Vector m_scrollRange; + C2Vector m_scrollOffset; + ScriptIx m_onHorizontalScroll; + ScriptIx m_onVerticalScroll; + ScriptIx m_onScrollRangeChanged; + + // Virtual member functions + virtual ScriptIx* GetScriptByName(const char*, ScriptData&); + virtual bool IsA(int32_t); + virtual int32_t GetScriptMetaTable(void); + virtual void LoadXML(XMLNode*, CStatus*); + virtual int32_t GetBoundsRect(CRect& bounds); + virtual void OnLayerUpdate(float elapsedSec); + virtual void OnFrameRender(CRenderBatch* batch, uint32_t layer); + virtual void OnFrameSizeChanged(float w, float h); + + // Member functions + CSimpleScrollFrame(CSimpleFrame*); + void RunOnScrollRangeChangedScript(); + void RunOnVerticalScrollScript(); + void SetScrollChild(CSimpleFrame*); + void SetVerticalScroll(float offset); + void UpdateScrollChild(); + void UpdateScrollChildRect(float w, float h); +}; + +#endif diff --git a/src/ui/CSimpleScrollFrameScript.cpp b/src/ui/CSimpleScrollFrameScript.cpp new file mode 100644 index 0000000..c440826 --- /dev/null +++ b/src/ui/CSimpleScrollFrameScript.cpp @@ -0,0 +1,105 @@ +#include "ui/CSimpleScrollFrameScript.hpp" +#include "gx/Coordinate.hpp" +#include "ui/CSimpleScrollFrame.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleScrollFrame_SetScrollChild(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleScrollFrame_GetScrollChild(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleScrollFrame_SetHorizontalScroll(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleScrollFrame_SetVerticalScroll(lua_State* L) { + auto type = CSimpleScrollFrame::GetObjectType(); + auto scrollFrame = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!scrollFrame->ProtectedFunctionsAllowed()) { + // TODO handle check + + return 0; + } + + if (!lua_isnumber(L, 2)) { + luaL_error(L, "Usage: %s:SetVerticalScroll(offset)", scrollFrame->GetDisplayName()); + } + + float offset = lua_tonumber(L, 2); + float ndcOffset = offset / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcOffset = NDCToDDCWidth(ndcOffset); + + scrollFrame->SetVerticalScroll(ddcOffset); + + return 0; +} + +int32_t CSimpleScrollFrame_GetHorizontalScroll(lua_State* L) { + auto type = CSimpleScrollFrame::GetObjectType(); + auto scrollFrame = static_cast(FrameScript_GetObjectThis(L, type)); + + float ddcOffset = CoordinateGetAspectCompensation() * 1024.0f * scrollFrame->m_scrollOffset.x; + float ndcOffset = DDCToNDCWidth(ddcOffset); + + lua_pushnumber(L, ndcOffset); + + return 1; +} + +int32_t CSimpleScrollFrame_GetVerticalScroll(lua_State* L) { + auto type = CSimpleScrollFrame::GetObjectType(); + auto scrollFrame = static_cast(FrameScript_GetObjectThis(L, type)); + + float ddcOffset = CoordinateGetAspectCompensation() * 1024.0f * scrollFrame->m_scrollOffset.y; + float ndcOffset = DDCToNDCWidth(ddcOffset); + + lua_pushnumber(L, ndcOffset); + + return 1; +} + +int32_t CSimpleScrollFrame_GetHorizontalScrollRange(lua_State* L) { + auto type = CSimpleScrollFrame::GetObjectType(); + auto scrollFrame = static_cast(FrameScript_GetObjectThis(L, type)); + + float ddcRange = CoordinateGetAspectCompensation() * 1024.0f * scrollFrame->m_scrollRange.x; + float ndcRange = DDCToNDCWidth(ddcRange); + + lua_pushnumber(L, ndcRange); + + return 1; +} + +int32_t CSimpleScrollFrame_GetVerticalScrollRange(lua_State* L) { + auto type = CSimpleScrollFrame::GetObjectType(); + auto scrollFrame = static_cast(FrameScript_GetObjectThis(L, type)); + + float ddcRange = CoordinateGetAspectCompensation() * 1024.0f * scrollFrame->m_scrollRange.y; + float ndcRange = DDCToNDCWidth(ddcRange); + + lua_pushnumber(L, ndcRange); + + return 1; +} + +int32_t CSimpleScrollFrame_UpdateScrollChildRect(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleScrollFrameMethods[NUM_SIMPLE_SCROLL_FRAME_SCRIPT_METHODS] = { + { "SetScrollChild", &CSimpleScrollFrame_SetScrollChild }, + { "GetScrollChild", &CSimpleScrollFrame_GetScrollChild }, + { "SetHorizontalScroll", &CSimpleScrollFrame_SetHorizontalScroll }, + { "SetVerticalScroll", &CSimpleScrollFrame_SetVerticalScroll }, + { "GetHorizontalScroll", &CSimpleScrollFrame_GetHorizontalScroll }, + { "GetVerticalScroll", &CSimpleScrollFrame_GetVerticalScroll }, + { "GetHorizontalScrollRange", &CSimpleScrollFrame_GetHorizontalScrollRange }, + { "GetVerticalScrollRange", &CSimpleScrollFrame_GetVerticalScrollRange }, + { "UpdateScrollChildRect", &CSimpleScrollFrame_UpdateScrollChildRect } +}; diff --git a/src/ui/CSimpleScrollFrameScript.hpp b/src/ui/CSimpleScrollFrameScript.hpp new file mode 100644 index 0000000..907fc12 --- /dev/null +++ b/src/ui/CSimpleScrollFrameScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_SCROLL_FRAME_SCRIPT_HPP +#define UI_C_SIMPLE_SCROLL_FRAME_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_SCROLL_FRAME_SCRIPT_METHODS 9 + +extern FrameScript_Method SimpleScrollFrameMethods[NUM_SIMPLE_SCROLL_FRAME_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleSlider.cpp b/src/ui/CSimpleSlider.cpp new file mode 100644 index 0000000..d0afc10 --- /dev/null +++ b/src/ui/CSimpleSlider.cpp @@ -0,0 +1,145 @@ +#include "ui/CSimpleSlider.hpp" +#include "math/Utils.hpp" +#include "ui/CSimpleSliderScript.hpp" +#include "util/Lua.hpp" + +int32_t CSimpleSlider::s_metatable; +int32_t CSimpleSlider::s_objectType; + +void CSimpleSlider::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleSlider::RegisterScriptMethods); + CSimpleSlider::s_metatable = ref; +} + +int32_t CSimpleSlider::GetObjectType() { + if (!CSimpleSlider::s_objectType) { + CSimpleSlider::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleSlider::s_objectType; +} + +void CSimpleSlider::RegisterScriptMethods(lua_State* L) { + CSimpleFrame::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleSliderMethods, NUM_SIMPLE_SLIDER_SCRIPT_METHODS); +} + +FrameScript_Object::ScriptIx* CSimpleSlider::GetScriptByName(const char* name, ScriptData& data) { + auto parentScript = CSimpleFrame::GetScriptByName(name, data); + + if (parentScript) { + return parentScript; + } + + if (!SStrCmpI(name, "OnValueChanged", STORM_MAX_STR)) { + data.wrapper = "return function(self,value) %s end"; + return &this->m_onValueChanged; + } + + if (!SStrCmpI(name, "OnMinMaxChanged", STORM_MAX_STR)) { + data.wrapper = "return function(self,min,max) %s end"; + return &this->m_onMinMaxChanged; + } + + return nullptr; +} + +int32_t CSimpleSlider::GetScriptMetaTable() { + return CSimpleSlider::s_metatable; +} + +bool CSimpleSlider::IsA(int32_t type) { + return type == CSimpleSlider::s_objectType + || type == CSimpleFrame::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleSlider::RunOnMinMaxChangedScript() { + if (!this->m_onMinMaxChanged.luaRef) { + return; + } + + auto L = FrameScript_GetContext(); + + lua_pushnumber(L, this->m_baseValue); + lua_pushnumber(L, this->m_range + this->m_baseValue); + + this->RunScript(this->m_onMinMaxChanged, 2, nullptr); +} + +void CSimpleSlider::RunOnValueChangedScript() { + if (!this->m_onValueChanged.luaRef) { + return; + } + + auto L = FrameScript_GetContext(); + + lua_pushnumber(L, this->m_value); + + this->RunScript(this->m_onValueChanged, 1, nullptr); +} + +void CSimpleSlider::SetMinMaxValues(float min, float max) { + float range = max - min; + + if (this->m_rangeSet && AreEqual(this->m_range, range, WHOA_EPSILON_1) && AreEqual(this->m_baseValue, min, WHOA_EPSILON_1)) { + return; + } + + this->m_baseValue = min; + this->m_range = range; + this->m_changed = 1; + this->m_rangeSet = 1; + + this->RunOnMinMaxChangedScript(); + + if (this->m_valueSet) { + // Fit current value within range + this->SetValue(this->m_value); + } +} + +void CSimpleSlider::SetValue(float value) { + if (!this->m_rangeSet) { + return; + } + + auto v4 = value; + auto v5 = std::max(value, this->m_baseValue); + auto maxValue = this->m_range + this->m_baseValue; + + if (maxValue >= v5) { + if (v4 < this->m_baseValue) { + v4 = this->m_baseValue; + } + } else { + v4 = maxValue; + } + + auto newValue = this->Sub96BC10(v4); + + // Clamp to max value + if (newValue + this->m_valueStep >= maxValue) { + newValue = maxValue; + } + + // Clamp to base value + if (newValue - this->m_valueStep < this->m_baseValue) { + newValue = this->m_baseValue; + } + + if (!this->m_valueSet || fabs(newValue - this->m_value) >= 0.00000023841858) { + this->m_value = newValue; + this->m_changed = 1; + this->m_valueSet = 1; + + this->RunOnValueChangedScript(); + } +} + +float CSimpleSlider::Sub96BC10(float value) { + // TODO + return value; +} diff --git a/src/ui/CSimpleSlider.hpp b/src/ui/CSimpleSlider.hpp new file mode 100644 index 0000000..1c2b0b4 --- /dev/null +++ b/src/ui/CSimpleSlider.hpp @@ -0,0 +1,51 @@ +#ifndef UI_C_SIMPLE_SLIDER_HPP +#define UI_C_SIMPLE_SLIDER_HPP + +#include "ui/CSimpleFrame.hpp" + +class CSimpleSlider : public CSimpleFrame { + public: + // Static variables + static int32_t s_metatable; + static int32_t s_objectType; + + // Static functions + static void CreateScriptMetaTable(void); + static int32_t GetObjectType(void); + static void RegisterScriptMethods(lua_State*); + + // Member variables + int32_t m_changed : 1; + int32_t m_rangeSet : 1; + int32_t m_valueSet : 1; + int32_t m_buttonDown : 1; + float m_baseValue = 0.0f; + float m_range = 0.0f; + float m_value = 0.0f; + float m_valueStep = 0.0f; + ScriptIx m_onValueChanged; + ScriptIx m_onMinMaxChanged; + + // Virtual member functions + virtual ScriptIx* GetScriptByName(const char*, ScriptData&); + virtual bool IsA(int32_t); + virtual int32_t GetScriptMetaTable(void); + + // Member functions + CSimpleSlider(CSimpleFrame* parent) + : CSimpleFrame(parent) + , m_changed(0) + , m_rangeSet(0) + , m_valueSet(0) + , m_buttonDown(0) + { + this->EnableEvent(SIMPLE_EVENT_MOUSE, -1); + }; + void RunOnMinMaxChangedScript(); + void RunOnValueChangedScript(); + void SetMinMaxValues(float min, float max); + void SetValue(float value); + float Sub96BC10(float value); +}; + +#endif diff --git a/src/ui/CSimpleSliderScript.cpp b/src/ui/CSimpleSliderScript.cpp new file mode 100644 index 0000000..351729e --- /dev/null +++ b/src/ui/CSimpleSliderScript.cpp @@ -0,0 +1,108 @@ +#include "ui/CSimpleSliderScript.hpp" +#include "ui/CSimpleSlider.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleSlider_GetThumbTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_SetThumbTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_GetOrientation(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_SetOrientation(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_GetMinMaxValues(lua_State* L) { + auto type = CSimpleSlider::GetObjectType(); + auto slider = static_cast(FrameScript_GetObjectThis(L, type)); + + lua_pushnumber(L, slider->m_baseValue); + lua_pushnumber(L, slider->m_range + slider->m_baseValue); + + return 2; +} + +int32_t CSimpleSlider_SetMinMaxValues(lua_State* L) { + auto type = CSimpleSlider::GetObjectType(); + auto slider = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2) || !lua_isnumber(L, 3) || lua_tonumber(L, 2) > lua_tonumber(L, 3)) { + luaL_error(L, "Usage: %s:SetMinMaxValues(min, max)", slider->GetDisplayName()); + } + + float min = lua_tonumber(L, 2); + float max = lua_tonumber(L, 3); + + slider->SetMinMaxValues(min, max); + + return 0; +} + +int32_t CSimpleSlider_GetValue(lua_State* L) { + auto type = CSimpleSlider::GetObjectType(); + auto slider = static_cast(FrameScript_GetObjectThis(L, type)); + + float value = slider->m_value; + + lua_pushnumber(L, value); + + return 1; +} + +int32_t CSimpleSlider_SetValue(lua_State* L) { + auto type = CSimpleSlider::GetObjectType(); + auto slider = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2)) { + luaL_error(L, "Usage: %s:SetValue(value)", slider->GetDisplayName()); + } + + float value = lua_tonumber(L, 2); + slider->SetValue(value); + + return 0; +} + +int32_t CSimpleSlider_GetValueStep(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_SetValueStep(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_Enable(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_Disable(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleSlider_IsEnabled(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleSliderMethods[NUM_SIMPLE_SLIDER_SCRIPT_METHODS] = { + { "GetThumbTexture", &CSimpleSlider_GetThumbTexture }, + { "SetThumbTexture", &CSimpleSlider_SetThumbTexture }, + { "GetOrientation", &CSimpleSlider_GetOrientation }, + { "SetOrientation", &CSimpleSlider_SetOrientation }, + { "GetMinMaxValues", &CSimpleSlider_GetMinMaxValues }, + { "SetMinMaxValues", &CSimpleSlider_SetMinMaxValues }, + { "GetValue", &CSimpleSlider_GetValue }, + { "SetValue", &CSimpleSlider_SetValue }, + { "GetValueStep", &CSimpleSlider_GetValueStep }, + { "SetValueStep", &CSimpleSlider_SetValueStep }, + { "Enable", &CSimpleSlider_Enable }, + { "Disable", &CSimpleSlider_Disable }, + { "IsEnabled", &CSimpleSlider_IsEnabled } +}; diff --git a/src/ui/CSimpleSliderScript.hpp b/src/ui/CSimpleSliderScript.hpp new file mode 100644 index 0000000..c1c4dbc --- /dev/null +++ b/src/ui/CSimpleSliderScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_SLIDER_SCRIPT_HPP +#define UI_C_SIMPLE_SLIDER_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_SLIDER_SCRIPT_METHODS 13 + +extern FrameScript_Method SimpleSliderMethods[NUM_SIMPLE_SLIDER_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleTexture.cpp b/src/ui/CSimpleTexture.cpp new file mode 100644 index 0000000..274f880 --- /dev/null +++ b/src/ui/CSimpleTexture.cpp @@ -0,0 +1,537 @@ +#include "ui/CSimpleTexture.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Device.hpp" +#include "gx/Shader.hpp" +#include "ui/CFramePoint.hpp" +#include "ui/CRenderBatch.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/CSimpleTextureScript.hpp" +#include "ui/FrameXML.hpp" +#include "ui/LoadXML.hpp" +#include "ui/Util.hpp" +#include "util/StringTo.hpp" +#include +#include +#include +#include + +CGxShader* CSimpleTexture::s_imageModePixelShaders[2] = {}; + +uint16_t CSimpleTexture::s_indices[] = { + 0, 1, 2, + 2, 1, 3 +}; + +int32_t CSimpleTexture::s_metatable; +int32_t CSimpleTexture::s_objectType; +EGxTexFilter CSimpleTexture::s_textureFilterMode = GxTex_Linear; + +void CSimpleTexture::CreateScriptMetaTable() { + lua_State* L = FrameScript_GetContext(); + int32_t ref = FrameScript_Object::CreateScriptMetaTable(L, &CSimpleTexture::RegisterScriptMethods); + CSimpleTexture::s_metatable = ref; +} + +CGxShader* CSimpleTexture::GetImageModePixelShader(TextureImageMode mode) { + CGxShader* shader = CSimpleTexture::s_imageModePixelShaders[mode]; + + if (shader && shader->Valid()) { + return shader; + } else { + return nullptr; + } +} + +int32_t CSimpleTexture::GetObjectType() { + if (!CSimpleTexture::s_objectType) { + CSimpleTexture::s_objectType = ++FrameScript_Object::s_objectTypes; + } + + return CSimpleTexture::s_objectType; +} + +void CSimpleTexture::Init() { + static const char* paths[] = { "UI", "Desaturate" }; + + for (int32_t i = 0; i < 2; i++) { + g_theGxDevicePtr->ShaderCreate( + &s_imageModePixelShaders[i], + GxSh_Pixel, + "Shaders\\Pixel", + paths[i], + 1 + ); + } +} + +void CSimpleTexture::RegisterScriptMethods(lua_State* L) { + CScriptRegion::RegisterScriptMethods(L); + FrameScript_Object::FillScriptMethodTable(L, SimpleTextureMethods, NUM_SIMPLE_TEXTURE_SCRIPT_METHODS); +} + +CSimpleTexture::CSimpleTexture(CSimpleFrame* frame, uint32_t drawlayer, int32_t show) : CSimpleRegion(frame, drawlayer, show) { + this->m_nonBlocking = 0; + this->m_updateTexCoord = 0; + this->m_horizTile = 0; + this->m_vertTile = 0; + + this->m_shader = CSimpleTexture::GetImageModePixelShader(ImageMode_UI); + + // TODO + // CSimpleTexture::s_count++; + + this->m_texCoord[0] = { 0.0f, 0.0f }; + this->m_texCoord[1] = { 0.0f, 1.0f }; + this->m_texCoord[2] = { 1.0f, 0.0f }; + this->m_texCoord[3] = { 1.0f, 1.0f }; +} + +CSimpleTexture::~CSimpleTexture() { + if (this->m_texture) { + HandleClose(this->m_texture); + } + + // TODO CSimpleTexture::s_count++; +} + +void CSimpleTexture::Draw(CRenderBatch* batch) { + if (this->m_texture) { + batch->QueueTexture(this); + } +} + +float CSimpleTexture::GetHeight() { + float layoutHeight = CLayoutFrame::GetHeight(); + + if (layoutHeight != 0.0f) { + return layoutHeight; + } + + uint32_t textureHeight; + + if (TextureGetDimensions(this->m_texture, nullptr, &textureHeight, 0)) { + float ndcHeight = static_cast(textureHeight) / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcHeight = NDCToDDCWidth(ndcHeight); + return ddcHeight; + } + + return 0.0f; +} + +int32_t CSimpleTexture::GetScriptMetaTable() { + return CSimpleTexture::s_metatable; +} + +void CSimpleTexture::GetTexCoord(C2Vector* texCoord) { + texCoord[0] = { this->m_texCoord[0].x, this->m_texCoord[0].y }; + texCoord[1] = { this->m_texCoord[1].x, this->m_texCoord[1].y }; + texCoord[2] = { this->m_texCoord[2].x, this->m_texCoord[2].y }; + texCoord[3] = { this->m_texCoord[3].x, this->m_texCoord[3].y }; +} + +float CSimpleTexture::GetWidth() { + float layoutWidth = CLayoutFrame::GetWidth(); + + if (layoutWidth != 0.0f) { + return layoutWidth; + } + + uint32_t textureWidth; + + if (TextureGetDimensions(this->m_texture, &textureWidth, nullptr, 0)) { + float ndcWidth = static_cast(textureWidth) / (CoordinateGetAspectCompensation() * 1024.0f); + float ddcWidth = NDCToDDCWidth(ndcWidth); + return ddcWidth; + } + + return 0.0f; +} + +bool CSimpleTexture::IsA(int32_t type) { + return type == CSimpleTexture::s_objectType + || type == CScriptRegion::s_objectType + || type == CScriptObject::s_objectType; +} + +void CSimpleTexture::LoadXML(XMLNode* node, CStatus* status) { + const char* inheritsAttr = node->GetAttributeByName("inherits"); + + if (inheritsAttr && *inheritsAttr) { + const char* tainted; + bool locked; + + XMLNode* inheritsNode = FrameXML_AcquireHashNode(inheritsAttr, tainted, locked); + + if (inheritsNode) { + if (locked) { + status->Add(STATUS_WARNING, "Recursively inherited node: %s", inheritsAttr); + } else { + this->LoadXML(inheritsNode, status); + FrameXML_ReleaseHashNode(inheritsAttr); + } + } else { + status->Add(STATUS_WARNING, "Couldn't find inherited node: %s", inheritsAttr); + } + } + + CScriptRegion::LoadXML(node, status); + + const char* hiddenAttr = node->GetAttributeByName("hidden"); + + if (hiddenAttr && *hiddenAttr) { + bool hide = StringToBOOL(hiddenAttr); + + if (hide) { + this->Hide(); + } else { + this->Show(); + } + } + + int32_t wrapU = 0; + int32_t wrapV = 0; + CImVector color = { 0 }; + + const char* horizTileAttr = node->GetAttributeByName("horizTile"); + if (horizTileAttr && *horizTileAttr) { + this->m_horizTile = StringToBOOL(horizTileAttr); + wrapU = 1; + } + + const char* vertTileAttr = node->GetAttributeByName("vertTile"); + if (vertTileAttr && *vertTileAttr) { + this->m_vertTile = StringToBOOL(vertTileAttr); + wrapV = 1; + } + + for (XMLNode* child = node->m_child; child; child = child->m_next) { + if (!SStrCmpI(child->GetName(), "TexCoords", INT_MAX)) { + int32_t valid = 1; + + float left = 0.0f; + float right = 1.0f; + float top = 0.0f; + float bottom = 1.0f; + + const char* name = this->GetName(); + if (!name) { + name = ""; + } + + const XMLNode* rectNode = child->GetChildByName("Rect"); + + if (rectNode) { + // TODO + } else { + const char* leftAttr = child->GetAttributeByName("left"); + if (leftAttr && *leftAttr) { + if (this->m_horizTile) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (horizTile is on)", + name + ); + + valid = 0; + } + + left = SStrToFloat(leftAttr); + + if (left < -10000.0f || left > 10000.0f) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (out of range)", + name + ); + + valid = 0; + } + } + + const char* rightAttr = child->GetAttributeByName("right"); + if (rightAttr && *rightAttr) { + if (this->m_horizTile) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (horizTile is on)", + name + ); + + valid = 0; + } + + right = SStrToFloat(rightAttr); + + if (right < -10000.0f || right > 10000.0f) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (out of range)", + name + ); + + valid = 0; + } + } + + const char* topAttr = child->GetAttributeByName("top"); + if (topAttr && *topAttr) { + if (this->m_vertTile) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (vertTile is on)", + name + ); + + valid = 0; + } + + top = SStrToFloat(topAttr); + + if (top < -10000.0f || top > 10000.0f) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (out of range)", + name + ); + + valid = 0; + } + } + + const char* bottomAttr = child->GetAttributeByName("bottom"); + if (bottomAttr && *bottomAttr) { + if (this->m_vertTile) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (vertTile is on)", + name + ); + + valid = 0; + } + + bottom = SStrToFloat(bottomAttr); + + if (bottom < -10000.0f || bottom > 10000.0f) { + status->Add( + STATUS_ERROR, + "Texture %s: Invalid TexCoords value (out of range)", + name + ); + + valid = 0; + } + } + } + + if (valid) { + C2Vector coords[4]; + + coords[0].x = left; + coords[0].y = top; + + coords[1].x = left; + coords[1].y = bottom; + + coords[2].x = right; + coords[2].y = top; + + coords[3].y = bottom; + coords[3].x = right; + + this->SetTexCoord(coords); + + if (left < 0.0f || left > 1.0f || right < 0.0f || right > 1.0f) { + wrapU = 1; + } + + if (top < 0.0f || top > 1.0f || bottom < 0.0f || bottom > 1.0f) { + wrapV = 1; + } + } + } else if (!SStrCmpI(child->GetName(), "Color", INT_MAX)) { + LoadXML_Color(child, color); + this->SetTexture(color); + } else if (!SStrCmpI(child->GetName(), "Gradient", INT_MAX)) { + // TODO + } + } + + const char* v91 = node->GetAttributeByName("file"); + + if (v91 && *v91) { + if (this->SetTexture(v91, wrapU, wrapV, CSimpleTexture::s_textureFilterMode, ImageMode_UI)) { + + if (color.value) { + this->SetVertexColor(color); + } + } else { + const char* v94 = this->GetName(); + + if (!v94) { + v94 = ""; + } + + status->Add( + STATUS_WARNING, + "Texture %s: Unable to load texture file %s", + v94, + v91 + ); + + // TODO + // CTexture* texture = TextureCreateSolid(&CRAPPY_GREEN); // 0xFF00FF00 + + // if (this->m_texture) { + // HandleClose(this->m_texture); + // } + + // this->m_texture = texture; + + // this->OnRegionChanged(); + } + } + + const char* alphaModeAttr = node->GetAttributeByName("alphaMode"); + if (alphaModeAttr && *alphaModeAttr) { + EGxBlend alphaMode; + + if (StringToBlendMode(alphaModeAttr, alphaMode)) { + this->SetBlendMode(alphaMode); + } + } + + const char* alphaAttr = node->GetAttributeByName("alpha"); + if (alphaAttr && *alphaAttr) { + float alpha = SStrToFloat(alphaAttr); + this->SetAlpha(alpha); + } + + // TODO + // - nonBlocking +} + +void CSimpleTexture::OnFrameSizeChanged(const CRect& rect) { + CLayoutFrame::OnFrameSizeChanged(rect); + + if (false) { + // TODO + // - something about animations? + } else { + this->SetPosition(this->m_rect, this->m_position); + } + + if (this->m_horizTile || this->m_vertTile) { + this->m_updateTexCoord = 1; + this->OnRegionChanged(); + } + + if (this->m_parent) { + this->m_parent->NotifyScrollParent(); + } +} + +void CSimpleTexture::PostLoadXML(XMLNode* node, CStatus* status) { + if (this->m_parent) { + for (int32_t i = 0; i < FRAMEPOINT_NUMPOINTS; i++) { + if (this->m_points[i] && !(this->m_points[i]->m_flags & 0x8)) { + break; + } + + if (i + 1 == FRAMEPOINT_NUMPOINTS) { + this->SetAllPoints(this->m_parent, 1); + break; + } + } + } +} + +void CSimpleTexture::SetAlpha(float alpha) { + // Clamp + alpha = std::max(0.0f, std::min(alpha, 1.0f)); + + CImVector color = { 0 }; + this->GetVertexColor(color); + color.a = alpha * 255.0f; + this->SetVertexColor(color); +} + +void CSimpleTexture::SetBlendMode(EGxBlend blend) { + if (blend == this->m_alphaMode) { + return; + } + + this->m_alphaMode = blend; + this->OnRegionChanged(); +} + +void CSimpleTexture::SetPosition(const CRect& rect, C3Vector* position) { + position[0] = { rect.minX, rect.maxY, this->m_layoutDepth }; + position[1] = { rect.minX, rect.minY, this->m_layoutDepth }; + position[2] = { rect.maxX, rect.maxY, this->m_layoutDepth }; + position[3] = { rect.maxX, rect.minY, this->m_layoutDepth }; +} + +void CSimpleTexture::SetTexCoord(const CRect& texRect) { + this->m_texCoord[0] = { texRect.minX, texRect.minY }; + this->m_texCoord[1] = { texRect.minX, texRect.maxY }; + this->m_texCoord[2] = { texRect.maxX, texRect.minY }; + this->m_texCoord[3] = { texRect.maxX, texRect.maxY }; +} + +void CSimpleTexture::SetTexCoord(const C2Vector* texCoord) { + this->m_texCoord[0] = { texCoord[0].x, texCoord[0].y }; + this->m_texCoord[1] = { texCoord[1].x, texCoord[1].y }; + this->m_texCoord[2] = { texCoord[2].x, texCoord[2].y }; + this->m_texCoord[3] = { texCoord[3].x, texCoord[3].y }; +} + +int32_t CSimpleTexture::SetTexture(const char* fileName, bool wrapU, bool wrapV, EGxTexFilter filter, TextureImageMode mode) { + if (this->m_texture && fileName && TextureIsSame(this->m_texture, fileName)) { + return 1; + } + + HTEXTURE texture = nullptr; + + if (fileName && *fileName) { + CStatus status; + + CGxTexFlags texFlags = CGxTexFlags(filter, wrapU, wrapV, 0, 0, 0, 1); + + texture = TextureCreate(fileName, texFlags, &status, 23); + + // TODO + // if (status.unk4 >= 2) { + // HandleClose(texture); + // SysMsgAdd(&status); + + // return 0; + // } + + this->m_shader = CSimpleTexture::GetImageModePixelShader(mode); + } + + if (this->m_texture) { + HandleClose(this->m_texture); + } + + this->m_texture = texture; + + this->OnRegionChanged(); + + return 1; +} + +int32_t CSimpleTexture::SetTexture(const CImVector& color) { + HTEXTURE texture = TextureCreateSolid(color); + + if (this->m_texture) { + HandleClose(this->m_texture); + } + + this->m_texture = texture; + + this->OnRegionChanged(); + + return 1; +} diff --git a/src/ui/CSimpleTexture.hpp b/src/ui/CSimpleTexture.hpp new file mode 100644 index 0000000..e21fcc1 --- /dev/null +++ b/src/ui/CSimpleTexture.hpp @@ -0,0 +1,64 @@ +#ifndef UI_C_SIMPLE_TEXTURE_HPP +#define UI_C_SIMPLE_TEXTURE_HPP + +#include "gx/Texture.hpp" +#include "ui/CSimpleRegion.hpp" +#include "ui/Types.hpp" +#include + +class CGxShader; +class CRect; +class CRenderBatch; +class CSimpleFrame; + +class CSimpleTexture : public CSimpleRegion { + public: + // Static variables + static CGxShader* s_imageModePixelShaders[]; + static uint16_t s_indices[]; + static int32_t s_metatable; + static int32_t s_objectType; + static EGxTexFilter s_textureFilterMode; + + // Static functions + static void CreateScriptMetaTable(void); + static CGxShader* GetImageModePixelShader(TextureImageMode); + static int32_t GetObjectType(void); + static void Init(void); + static void RegisterScriptMethods(lua_State*); + + // Member variables + HTEXTURE m_texture = nullptr; + EGxBlend m_alphaMode = GxBlend_Alpha; + CGxShader* m_shader = s_imageModePixelShaders[0]; + C3Vector m_position[4]; + C2Vector m_texCoord[4]; + uint32_t m_nonBlocking : 1; + uint32_t m_updateTexCoord : 1; + uint32_t m_horizTile : 1; + uint32_t m_vertTile : 1; + + // Virtual member functions + virtual ~CSimpleTexture(); + virtual int32_t GetScriptMetaTable(void); + virtual bool IsA(int32_t); + virtual void LoadXML(XMLNode*, CStatus*); + virtual float GetWidth(void); + virtual float GetHeight(void); + virtual void Draw(CRenderBatch*); + virtual void OnFrameSizeChanged(const CRect&); + + // Member functions + CSimpleTexture(CSimpleFrame*, uint32_t, int32_t); + void GetTexCoord(C2Vector*); + void PostLoadXML(XMLNode*, CStatus*); + void SetAlpha(float alpha); + void SetBlendMode(EGxBlend); + void SetPosition(const CRect&, C3Vector*); + void SetTexCoord(const CRect& texRect); + void SetTexCoord(const C2Vector*); + int32_t SetTexture(const char*, bool, bool, EGxTexFilter, TextureImageMode); + int32_t SetTexture(const CImVector&); +}; + +#endif diff --git a/src/ui/CSimpleTextureScript.cpp b/src/ui/CSimpleTextureScript.cpp new file mode 100644 index 0000000..be22071 --- /dev/null +++ b/src/ui/CSimpleTextureScript.cpp @@ -0,0 +1,275 @@ +#include "ui/CSimpleTextureScript.hpp" +#include "ui/CSimpleTexture.hpp" +#include "ui/Types.hpp" +#include "util/Lua.hpp" +#include "util/Unimplemented.hpp" +#include + +int32_t CSimpleTexture_IsObjectType(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetObjectType(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetDrawLayer(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetDrawLayer(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetBlendMode(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetBlendMode(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetVertexColor(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetVertexColor(lua_State* L) { + int32_t type = CSimpleTexture::GetObjectType(); + CSimpleTexture* texture = static_cast(FrameScript_GetObjectThis(L, type)); + + CImVector curColor = { 0 }; + texture->GetVertexColor(curColor); + + CImVector newColor = { 0 }; + FrameScript_GetColor(L, 2, newColor); + if (!lua_isnumber(L, 5)) { + newColor.a = curColor.a; + } + + texture->SetVertexColor(newColor); + + return 0; +} + +int32_t CSimpleTexture_SetGradient(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetGradientAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetAlpha(lua_State* L) { + int32_t type = CSimpleTexture::GetObjectType(); + CSimpleTexture* texture = static_cast(FrameScript_GetObjectThis(L, type)); + + if (!lua_isnumber(L, 2)) { + luaL_error(L, "Usage: %s:SetAlpha(alpha)", texture->GetDisplayName()); + } + + float alpha = lua_tonumber(L, 2); + texture->SetAlpha(alpha); + + return 0; +} + +int32_t CSimpleTexture_GetAlpha(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_Show(lua_State* L) { + int32_t type = CSimpleTexture::GetObjectType(); + CSimpleTexture* texture = static_cast(FrameScript_GetObjectThis(L, type)); + + texture->Show(); + + return 0; +} + +int32_t CSimpleTexture_Hide(lua_State* L) { + int32_t type = CSimpleTexture::GetObjectType(); + CSimpleTexture* texture = static_cast(FrameScript_GetObjectThis(L, type)); + + texture->Hide(); + + return 0; +} + +int32_t CSimpleTexture_IsVisible(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_IsShown(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetTexture(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetTexture(lua_State* L) { + int32_t type = CSimpleTexture::GetObjectType(); + CSimpleTexture* texture = static_cast(FrameScript_GetObjectThis(L, type)); + + if (lua_isnumber(L, 2)) { + // TODO + // CArgb v9 = 0; + // FrameScript_GetRGBA(2, L, &v9); + // texture->SetTexture(&v9); + + lua_pushnumber(L, 1.0); + + return 1; + } + + if (!lua_isstring(L, 2)) { + texture->SetTexture(0, 0, 0, CSimpleTexture::s_textureFilterMode, ImageMode_UI); + + lua_pushnumber(L, 1.0); + + return 1; + } + + bool v5 = lua_toboolean(L, 3); + bool v6 = lua_toboolean(L, 3); + const char* v7 = lua_tolstring(L, 2, 0); + + if (texture->SetTexture(v7, v6, v5, CSimpleTexture::s_textureFilterMode, ImageMode_UI)) { + lua_pushnumber(L, 1.0); + + return 1; + } + + lua_pushnil(L); + + return 1; +} + +int32_t CSimpleTexture_GetTexCoord(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetTexCoord(lua_State* L) { + auto type = CSimpleTexture::GetObjectType(); + auto texture = static_cast(FrameScript_GetObjectThis(L, type)); + + auto argCount = lua_gettop(L) - 1; + + if (argCount == 4) { + CRect texRect = { + static_cast(lua_tonumber(L, 4)), + static_cast(lua_tonumber(L, 2)), + static_cast(lua_tonumber(L, 5)), + static_cast(lua_tonumber(L, 3)) + }; + + if ( + texRect.minX < -10000.0f + || texRect.minX > 10000.0f + || texRect.maxX < -10000.0f + || texRect.maxX > 10000.0f + || texRect.minY < -10000.0f + || texRect.minY > 10000.0f + || texRect.maxY < -10000.0f + || texRect.maxY > 10000.0f + ) { + luaL_error(L, "TexCoord out of range"); + } + + texture->SetTexCoord(texRect); + + } else if (argCount == 8) { + C2Vector texCoord[4]; + + for (int32_t i = 0; i < 4; i++) { + texCoord[i] = { + static_cast(lua_tonumber(L, 2 + (i * 2 + 0))), + static_cast(lua_tonumber(L, 2 + (i * 2 + 1))) + }; + + if ( + texCoord[i].x < -10000.0f + || texCoord[i].x > 10000.0f + || texCoord[i].y < -10000.0f + || texCoord[i].y > 10000.0f + ) { + luaL_error(L, "TexCoord out of range"); + } + } + + texture->SetTexCoord(texCoord); + + } else { + luaL_error(L, "Usage: %s:SetTexCoord(minX, maxX, minY, maxY) or SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy)", texture->GetDisplayName()); + } + + return 0; +} + +int32_t CSimpleTexture_SetRotation(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetDesaturated(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_IsDesaturated(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetNonBlocking(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetNonBlocking(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetHorizTile(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetHorizTile(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_SetVertTile(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +int32_t CSimpleTexture_GetVertTile(lua_State* L) { + WHOA_UNIMPLEMENTED(); +} + +FrameScript_Method SimpleTextureMethods[NUM_SIMPLE_TEXTURE_SCRIPT_METHODS] = { + { "IsObjectType", &CSimpleTexture_IsObjectType }, + { "GetObjectType", &CSimpleTexture_GetObjectType }, + { "GetDrawLayer", &CSimpleTexture_GetDrawLayer }, + { "SetDrawLayer", &CSimpleTexture_SetDrawLayer }, + { "GetBlendMode", &CSimpleTexture_GetBlendMode }, + { "SetBlendMode", &CSimpleTexture_SetBlendMode }, + { "GetVertexColor", &CSimpleTexture_GetVertexColor }, + { "SetVertexColor", &CSimpleTexture_SetVertexColor }, + { "SetGradient", &CSimpleTexture_SetGradient }, + { "SetGradientAlpha", &CSimpleTexture_SetGradientAlpha }, + { "SetAlpha", &CSimpleTexture_SetAlpha }, + { "GetAlpha", &CSimpleTexture_GetAlpha }, + { "Show", &CSimpleTexture_Show }, + { "Hide", &CSimpleTexture_Hide }, + { "IsVisible", &CSimpleTexture_IsVisible }, + { "IsShown", &CSimpleTexture_IsShown }, + { "GetTexture", &CSimpleTexture_GetTexture }, + { "SetTexture", &CSimpleTexture_SetTexture }, + { "GetTexCoord", &CSimpleTexture_GetTexCoord }, + { "SetTexCoord", &CSimpleTexture_SetTexCoord }, + { "SetRotation", &CSimpleTexture_SetRotation }, + { "SetDesaturated", &CSimpleTexture_SetDesaturated }, + { "IsDesaturated", &CSimpleTexture_IsDesaturated }, + { "SetNonBlocking", &CSimpleTexture_SetNonBlocking }, + { "GetNonBlocking", &CSimpleTexture_GetNonBlocking }, + { "SetHorizTile", &CSimpleTexture_SetHorizTile }, + { "GetHorizTile", &CSimpleTexture_GetHorizTile }, + { "SetVertTile", &CSimpleTexture_SetVertTile }, + { "GetVertTile", &CSimpleTexture_GetVertTile } +}; diff --git a/src/ui/CSimpleTextureScript.hpp b/src/ui/CSimpleTextureScript.hpp new file mode 100644 index 0000000..9b16941 --- /dev/null +++ b/src/ui/CSimpleTextureScript.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_TEXTURE_SCRIPT_HPP +#define UI_C_SIMPLE_TEXTURE_SCRIPT_HPP + +#include "ui/FrameScript.hpp" + +#define NUM_SIMPLE_TEXTURE_SCRIPT_METHODS 29 + +extern FrameScript_Method SimpleTextureMethods[NUM_SIMPLE_TEXTURE_SCRIPT_METHODS]; + +#endif diff --git a/src/ui/CSimpleTitleRegion.hpp b/src/ui/CSimpleTitleRegion.hpp new file mode 100644 index 0000000..554f66d --- /dev/null +++ b/src/ui/CSimpleTitleRegion.hpp @@ -0,0 +1,10 @@ +#ifndef UI_C_SIMPLE_TITLE_REGION_HPP +#define UI_C_SIMPLE_TITLE_REGION_HPP + +#include "ui/CScriptRegion.hpp" + +class CSimpleTitleRegion : public CScriptRegion { + public: +}; + +#endif diff --git a/src/ui/CSimpleTop.cpp b/src/ui/CSimpleTop.cpp new file mode 100644 index 0000000..dd6c2c6 --- /dev/null +++ b/src/ui/CSimpleTop.cpp @@ -0,0 +1,577 @@ +#include "ui/CSimpleTop.hpp" +#include "gx/Camera.hpp" +#include "gx/Coordinate.hpp" +#include "gx/Device.hpp" +#include "gx/Font.hpp" +#include "gx/Screen.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/CSimpleRender.hpp" +#include "ui/CSimpleTexture.hpp" +#include "ui/CSimpleTitleRegion.hpp" +#include +#include + +CSimpleTop* CSimpleTop::s_instance; +uint32_t CSimpleTop::m_eventTime; + +void PaintScreen(void* param, const RECTF* rect, const RECTF* visible, float elapsedSec) { + CSimpleTop::s_instance->OnLayerUpdate(elapsedSec); + CSimpleTop::s_instance->OnLayerRender(); +} + +int32_t CSimpleTop::OnChar(const EVENT_DATA_CHAR* pCharData, void* param) { + CSimpleTop* top = static_cast(param); + + int32_t eaten = 0; + + CCharEvent charEvent; + charEvent = *pCharData; + charEvent.id = 0x40060067; + + for (int32_t strata = FRAME_STRATA_TOOLTIP; strata >= FRAME_STRATA_WORLD; strata--) { + if (eaten) { + break; + } + + auto priorities = &top->m_eventqueue[strata][SIMPLE_EVENT_CHAR]; + + for (int32_t i = priorities->Count() - 1; i >= 0; i--) { + auto priority = priorities->operator[](i); + + if (!priority || eaten) { + break; + } + + auto frame = priority->frame; + + eaten = frame->OnLayerChar(charEvent); + } + } + + return eaten == 0; +} + +int32_t CSimpleTop::OnDisplaySizeChanged(const EVENT_DATA_SIZE* pSizeData, void* param) { + CSimpleTop* top = static_cast(param); + + GxuFontWindowSizeChanged(); + + for (auto frame = top->m_frames.Head(); frame; frame = top->m_frames.Link(frame)->Next()) { + frame->OnScreenSizeChanged(); + } + + if (top->m_displaySizeCallback) { + CSizeEvent sizeEvent; + sizeEvent = *pSizeData; + sizeEvent.id = 0x40040064; + + top->m_displaySizeCallback(sizeEvent); + } + + return 1; +} + +int32_t CSimpleTop::OnFocusChanged(const void*, void*) { + // TODO + return 0; +} + +int32_t CSimpleTop::OnIme(const void*, void*) { + // TODO + return 0; +} + +int32_t CSimpleTop::OnKeyDown(const EVENT_DATA_KEY* pKeyData, void* param) { + CSimpleTop* top = static_cast(param); + CSimpleTop::m_eventTime = pKeyData->time; + + CKeyEvent keyEvent; + keyEvent = *pKeyData; + keyEvent.id = 0x40060064; + + // TODO + // if (top->unk1239) { + // top->unk1239(keyEvent); + // } + + int32_t eaten = 0; + + for (int32_t strata = FRAME_STRATA_TOOLTIP; strata >= FRAME_STRATA_WORLD; strata--) { + if (eaten) { + break; + } + + auto priorities = &top->m_eventqueue[strata][SIMPLE_EVENT_KEY]; + + for (int32_t i = priorities->Count() - 1; i >= 0; i--) { + auto priority = priorities->operator[](i); + + if (!priority || eaten) { + break; + } + + auto frame = priority->frame; + + eaten = frame->OnLayerKeyDown(keyEvent); + + if (eaten) { + top->m_keydownCapture[keyEvent.key] = frame; + } + } + } + + return eaten == 0; +} + +int32_t CSimpleTop::OnKeyDownRepeat(const void*, void*) { + // TODO + return 0; +} + +int32_t CSimpleTop::OnKeyUp(const EVENT_DATA_KEY* pKeyData, void* param) { + CSimpleTop* top = static_cast(param); + CSimpleTop::m_eventTime = pKeyData->time; + + int32_t eaten = 0; + + CSimpleFrame* frame = top->m_keydownCapture[pKeyData->key]; + + if (frame) { + CKeyEvent keyEvent; + keyEvent = *pKeyData; + keyEvent.id = 0x40060066; + + frame->OnLayerKeyUp(keyEvent); + + eaten = 1; + } else if (pKeyData->key == KEY_PRINTSCREEN) { + eaten = CSimpleTop::OnKeyDown(pKeyData, param); + } + + top->m_keydownCapture[pKeyData->key] = nullptr; + + return eaten == 0; +} + +int32_t CSimpleTop::OnMouseDown(const EVENT_DATA_MOUSE* pMouseData, void* param) { + CSimpleTop* top = static_cast(param); + + CMouseEvent mouseEvent; + mouseEvent = *pMouseData; + mouseEvent.id = 0x400500C8; + + CSimpleTop::m_eventTime = pMouseData->time; + + // TODO + // bool v4 = top->m_layout.enabled; + bool enabled = false; + + memcpy(&top->m_mousePosition, pMouseData, sizeof(top->m_mousePosition)); + + if (enabled && (EventIsKeyDown(KEY_LCONTROL) || EventIsKeyDown(KEY_LALT))) { + int32_t v5 = EventIsKeyDown(KEY_LCONTROL); + top->StartMoveOrResizeFrame(mouseEvent, MOVERESIZE_REASON1, v5); + + return 0; + } + + auto mouseButtonCallback = top->m_mouseButtonCallback; + + if (!top->m_mouseCapture && mouseButtonCallback && mouseButtonCallback(&mouseEvent)) { + return 0; + } + + CSimpleFrame* target = top->m_mouseCapture; + + if (!target) { + target = top->m_mouseFocus; + } + + if (!target) { + return 1; + } + + target->Raise(); + + CSimpleTitleRegion* titleRegion = target->m_titleRegion; + + C2Vector pt = { mouseEvent.x, mouseEvent.y }; + + if (titleRegion && titleRegion->PtInFrameRect(pt)) { + top->StartMoveOrResizeFrame(target, MOVERESIZE_REASON2, mouseEvent.x, mouseEvent.y, 4); + } else { + top->m_mouseCapture = target; + target->OnLayerMouseDown(mouseEvent, nullptr); + } + + return 0; +} + +int32_t CSimpleTop::OnMouseMove(const EVENT_DATA_MOUSE* pMouseData, void* param) { + CSimpleTop* top = static_cast(param); + + CMouseEvent mouseEvent; + mouseEvent = *pMouseData; + mouseEvent.id = 0x400500CA; + + int32_t v18 = pMouseData != &top->m_mousePosition; + + if (v18) { + if (pMouseData->x == top->m_mousePosition.x && pMouseData->y == top->m_mousePosition.y) { + return 0; + } + + CSimpleTop::m_eventTime = pMouseData->time; + } + + memcpy(&top->m_mousePosition, pMouseData, sizeof(top->m_mousePosition)); + + CSimpleFrame* lastFocus = top->m_mouseFocus; + CSimpleFrame* nextFocus = nullptr; + + if (top->m_layout.frame) { + CMouseEvent evt; + evt = *pMouseData; + + top->MoveOrResizeFrame(evt); + + return 0; + } + + auto mousePositionCallback = top->m_mousePositionCallback; + + if (mousePositionCallback && mousePositionCallback(&mouseEvent)) { + return 0; + } + + CSimpleFrame* mouseCapture = top->m_mouseCapture; + + if (mouseCapture) { + mouseCapture->OnLayerTrackUpdate(mouseEvent); + } + + for (int32_t strata = FRAME_STRATA_DIALOG; strata >= FRAME_STRATA_WORLD; strata--) { + auto priorities = &top->m_eventqueue[strata][SIMPLE_EVENT_MOUSE]; + + for (int32_t i = priorities->Count() - 1; i >= 0; i--) { + auto frame = priorities->operator[](i)->frame; + + if (frame->OnLayerTrackUpdate(mouseEvent)) { + nextFocus = frame; + break; + } + } + + if (nextFocus) { + break; + } + } + + if (nextFocus != lastFocus) { + top->m_mouseFocus = nextFocus; + + if (lastFocus) { + lastFocus->OnLayerCursorExit(v18, 0); + } + + if (nextFocus) { + nextFocus->OnLayerCursorEnter(v18); + } + } + + return nextFocus == nullptr; +} + +int32_t CSimpleTop::OnMouseMoveRelative(const EVENT_DATA_MOUSE*, void*) { + // TODO + return 0; +} + +int32_t CSimpleTop::OnMouseUp(const EVENT_DATA_MOUSE* pMouseData, void* param) { + CSimpleTop* top = static_cast(param); + + CMouseEvent mouseEvent; + mouseEvent = *pMouseData; + mouseEvent.id = 0x400500C9; + + CSimpleTop::m_eventTime = pMouseData->time; + + if (top->m_layout.anchor == 1 || top->m_layout.anchor == 2) { + if (top->m_layout.frame) { + top->m_layout.frame->UnflattenFrame(top); + } + + top->m_layout.frame = nullptr; + top->m_layout.anchor = FRAMEPOINT_TOPLEFT; + + return 0; + } + + // TODO + // if (v11 && !top->m_mouseCapture && v11(mouseEvent)) { + // return 0; + // } + + if (!top->m_mouseCapture) { + return 1; + } + + top->m_mouseCapture->OnLayerMouseUp(mouseEvent, nullptr); + + if (!mouseEvent.buttonState) { + top->m_mouseCapture = nullptr; + } + + return 0; +} + +int32_t CSimpleTop::OnMouseWheel(const EVENT_DATA_MOUSE*, void*) { + // TODO + return 0; +} + +float CSimpleTop::RoundToPixelHeight(float ddcHeight) { + if (abs(ddcHeight) < 0.00000023841858) { + return ddcHeight; + } + + CRect windowRect = { 0.0f, 0.0f, 0.0f, 0.0f }; + g_theGxDevicePtr->CapsWindowSizeInScreenCoords(windowRect); + float windowHeight = windowRect.maxY - windowRect.minY; + + if (abs(windowHeight) < 0.00000023841858) { + return ddcHeight; + } + + auto ndcHeight = DDCToNDCHeight(ddcHeight); + auto pixelHeight = ndcHeight * windowHeight; + auto roundedPixelHeight = CMath::fuint_pi(pixelHeight); + auto roundedNdcHeight = static_cast(roundedPixelHeight) / windowHeight; + auto roundedDdcHeight = NDCToDDCHeight(roundedNdcHeight); + + return roundedDdcHeight; +} + +CSimpleTop::CSimpleTop() : CLayoutFrame() { + // TODO + + CSimpleTop::s_instance = this; + + ScrnLayerCreate(nullptr, 1.0, 0x4, nullptr, &PaintScreen, &this->m_screenLayer); + + this->EnableEvents(); + + // TODO + // memset(this->m_keydownCapture, 0, sizeof(this->m_keydownCapture)); + + NDCToDDC(1.0f, 1.0f, &this->m_rect.maxX, &this->m_rect.maxY); + + this->m_flags |= 0x1; + + // TODO + + for (int32_t s = 0; s < NUM_FRAME_STRATA; s++) { + void* m = SMemAlloc(sizeof(CFrameStrata), __FILE__, __LINE__, 0); + this->m_strata[s] = m ? new (m) CFrameStrata() : nullptr; + } + + // TODO + + CSimpleRender::Init(); + CSimpleTexture::Init(); +} + +void CSimpleTop::CompressStrata(int32_t strata) { + // TODO +} + +void CSimpleTop::EnableEvents() { + EventRegisterEx(EVENT_ID_CHAR, reinterpret_cast(CSimpleTop::OnChar), this, 1.0); + EventRegisterEx(EVENT_ID_IME, reinterpret_cast(CSimpleTop::OnIme), this, 1.0); + EventRegisterEx(EVENT_ID_KEYDOWN, reinterpret_cast(CSimpleTop::OnKeyDown), this, 1.0); + EventRegisterEx(EVENT_ID_KEYUP, reinterpret_cast(CSimpleTop::OnKeyUp), this, 1.0); + EventRegisterEx(EVENT_ID_KEYDOWN_REPEATING, reinterpret_cast(CSimpleTop::OnKeyDownRepeat), this, 1.0); + EventRegisterEx(EVENT_ID_MOUSEMOVE, reinterpret_cast(CSimpleTop::OnMouseMove), this, 1.0); + EventRegisterEx(EVENT_ID_MOUSEMOVE_RELATIVE, reinterpret_cast(CSimpleTop::OnMouseMoveRelative), this, 1.0); + EventRegisterEx(EVENT_ID_MOUSEDOWN, reinterpret_cast(CSimpleTop::OnMouseDown), this, 1.0); + EventRegisterEx(EVENT_ID_MOUSEUP, reinterpret_cast(CSimpleTop::OnMouseUp), this, 1.0); + EventRegisterEx(EVENT_ID_MOUSEWHEEL, reinterpret_cast(CSimpleTop::OnMouseWheel), this, 1.0); + EventRegisterEx(EVENT_ID_SIZE, reinterpret_cast(CSimpleTop::OnDisplaySizeChanged), this, 1.0); + EventRegisterEx(EVENT_ID_FOCUS, reinterpret_cast(CSimpleTop::OnFocusChanged), this, 1.0); +} + +void CSimpleTop::HideFrame(CSimpleFrame* frame, int32_t a4) { + if (this->m_layout.frame == frame && !a4) { + if (this->m_layout.frame) { + this->m_layout.frame->UnflattenFrame(this); + } + + this->m_layout.frame = nullptr; + this->m_layout.anchor = FRAMEPOINT_TOPLEFT; + } + + frame->UnregisterForEvents(a4); + this->m_strata[frame->m_strata]->RemoveFrame(frame); +} + +void CSimpleTop::OnLayerUpdate(float elapsedSec) { + // TODO + // - walk m_destroyed and perform some cleanup + + CLayoutFrame::ResizePending(); + + for (int32_t s = 0; s < NUM_FRAME_STRATA; s++) { + auto strata = this->m_strata[s]; + strata->OnLayerUpdate(elapsedSec); + } + + CLayoutFrame::ResizePending(); + + if (this->m_checkFocus) { + this->m_checkFocus = 0; + CSimpleTop::OnMouseMove(&this->m_mousePosition, this); + } +} + +void CSimpleTop::OnLayerRender() { + C2Vector v13 = { 0.0f, 0.0f }; + CameraSetupScreenProjection(this->m_rect, v13, 0.0f, 0); + + for (int32_t s = 0; s < NUM_FRAME_STRATA; s++) { + auto strata = this->m_strata[s]; + strata->BuildBatches(this->m_mouseCapture == nullptr); + strata->RenderBatches(); + } +} + +void CSimpleTop::MoveOrResizeFrame(const CMouseEvent& evt) { + // TODO +} + +void CSimpleTop::NotifyFrameLayerChanged(CSimpleFrame* frame, uint32_t layer) { + auto strata = this->m_strata[frame->m_strata]; + auto level = strata->levels[frame->m_level]; + level->batchDirty |= 1 << layer; + strata->batchDirty = 1; +} + +void CSimpleTop::NotifyFrameMovedOrResized(CSimpleFrame* frame) { + auto strata = this->m_strata[frame->m_strata]; + strata->levelsDirty = 1; + + if (this->m_layout.frame) { + this->RaiseFrame(this->m_layout.frame, 0); + } + + this->m_checkFocus = 1; +} + +int32_t CSimpleTop::RaiseFrame(CSimpleFrame* frame, int32_t checkOcclusion) { + while (frame && frame->m_flags & 0x01) { + frame = frame->m_parent; + } + + if (!frame) { + return 0; + } + + if (checkOcclusion) { + if (!(static_cast(frame)->m_flags & 0x01) || this->IsResizePending()) { + frame->Resize(1); + } + + int32_t occluded = this->m_strata[frame->m_strata]->FrameOccluded(frame); + frame->SetFrameFlag(0x10, occluded); + } + + if (frame->m_flags & 0x10) { + this->CompressStrata(frame->m_strata); + auto strata = this->m_strata[frame->m_strata]; + frame->SetFrameLevel(strata->topLevel, 1); + this->m_checkFocus = 1; + } + + return 1; +} + +void CSimpleTop::RegisterForEvent(CSimpleFrame* frame, CSimpleEventType event, int32_t a4, uint32_t priority) { + auto& queue = this->m_eventqueue[frame->m_strata][event]; + + auto m = SMemAlloc(sizeof(FRAMEPRIORITY), __FILE__, __LINE__, 0x0); + auto framePriority = new (m) FRAMEPRIORITY(); + + framePriority->frame = frame; + framePriority->priority = priority == -1 ? frame->m_level : priority; + + queue.Insert(framePriority); + + if (!a4 || event == SIMPLE_EVENT_MOUSE) { + this->m_checkFocus = 1; + } +} + +void CSimpleTop::RegisterFrame(CSimpleFrame* frame) { + this->m_frames.LinkToTail(frame); +} + +void CSimpleTop::ShowFrame(CSimpleFrame* frame, int32_t a3) { + this->m_strata[frame->m_strata]->AddFrame(frame); + frame->RegisterForEvents(a3); +} + +int32_t CSimpleTop::StartMoveOrResizeFrame(const CMouseEvent& start, MOVERESIZE_REASON reason, int32_t resize) { + // TODO + return 0; +} + +int32_t CSimpleTop::StartMoveOrResizeFrame(CSimpleFrame* frame, MOVERESIZE_REASON reason, float startx, float starty, int32_t a6) { + // TODO + return 0; +} + +int32_t CSimpleTop::StartMoveOrResizeFrame(CSimpleFrame* frame, MOVERESIZE_REASON reason, float startx, float starty, FRAMEPOINT a6) { + // TODO + return 0; +} + +void CSimpleTop::UnregisterForEvent(CSimpleFrame* frame, CSimpleEventType event, int32_t a4) { + auto& queue = this->m_eventqueue[frame->m_strata][event]; + + int32_t found = 0; + + for (int32_t i = 0; i < queue.Count(); i++) { + if (queue[i]->frame == frame) { + queue.Remove(i); + found = 1; + + break; + } + } + + if (!found) { + return; + } + + if (!a4) { + if (event == SIMPLE_EVENT_KEY) { + for (int32_t i = 0; i < 787; i++) { + if (frame == this->m_keydownCapture[i]) { + this->m_keydownCapture[i] = nullptr; + } + } + } else if (event == SIMPLE_EVENT_MOUSE) { + if (frame == this->m_mouseCapture) { + this->m_mouseCapture = nullptr; + } + + if (frame == this->m_mouseFocus) { + this->m_mouseFocus = nullptr; + this->m_checkFocus = 1; + + if (frame->m_intAC != 3) { + frame->OnLayerCursorExit(0, 1); + } + } + } + } +} diff --git a/src/ui/CSimpleTop.hpp b/src/ui/CSimpleTop.hpp new file mode 100644 index 0000000..feb07fa --- /dev/null +++ b/src/ui/CSimpleTop.hpp @@ -0,0 +1,84 @@ +#ifndef UI_C_SIMPLE_TOP_HPP +#define UI_C_SIMPLE_TOP_HPP + +#include "event/Event.hpp" +#include "gx/Screen.hpp" +#include "ui/CFrameStrata.hpp" +#include "ui/CLayoutFrame.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/FrameScript.hpp" +#include + +enum MOVERESIZE_REASON { + MOVERESIZE_REASON0 = 0, + MOVERESIZE_REASON1 = 1, + MOVERESIZE_REASON2 = 2 +}; + +class CSimpleTop : public CLayoutFrame { + public: + // Types + struct frame_layout { + int32_t enabled = 0; + CSimpleFrame* frame = nullptr; + FRAMEPOINT anchor = FRAMEPOINT_TOPLEFT; + C2Vector last; + float float14 = 0.0f; + }; + + // Static variables + static CSimpleTop* s_instance; + static uint32_t m_eventTime; + + // Static functions + static int32_t OnChar(const EVENT_DATA_CHAR*, void*); + static int32_t OnDisplaySizeChanged(const EVENT_DATA_SIZE*, void*); + static int32_t OnFocusChanged(const void*, void*); + static int32_t OnIme(const void*, void*); + static int32_t OnKeyDown(const EVENT_DATA_KEY*, void*); + static int32_t OnKeyDownRepeat(const void*, void*); + static int32_t OnKeyUp(const EVENT_DATA_KEY*, void*); + static int32_t OnMouseDown(const EVENT_DATA_MOUSE*, void*); + static int32_t OnMouseMove(const EVENT_DATA_MOUSE*, void*); + static int32_t OnMouseMoveRelative(const EVENT_DATA_MOUSE*, void*); + static int32_t OnMouseUp(const EVENT_DATA_MOUSE*, void*); + static int32_t OnMouseWheel(const EVENT_DATA_MOUSE*, void*); + static float RoundToPixelHeight(float); + + // Member variables + HLAYER m_screenLayer; + CSimpleFrame* m_mouseFocus = nullptr; + CSimpleFrame* m_mouseCapture = nullptr; + CSimpleFrame* m_keydownCapture[787] = {}; + STORM_EXPLICIT_LIST(CSimpleFrame, m_framesLink) m_frames; + STORM_EXPLICIT_LIST(CSimpleFrame, m_destroyedLink) m_destroyed; + CFrameStrata* m_strata[NUM_FRAME_STRATA]; + frame_layout m_layout; + CSimpleSortedArray m_eventqueue[NUM_FRAME_STRATA][NUM_SIMPLE_EVENTS]; + int32_t m_checkFocus = 1; + EVENT_DATA_MOUSE m_mousePosition; + int32_t (*m_mouseButtonCallback)(CMouseEvent*) = nullptr; + int32_t (*m_mousePositionCallback)(CMouseEvent*) = nullptr; + int32_t (*m_displaySizeCallback)(const CSizeEvent&) = nullptr; + + // Member functions + CSimpleTop(); + void CompressStrata(int32_t); + void EnableEvents(void); + void HideFrame(CSimpleFrame*, int32_t); + void MoveOrResizeFrame(const CMouseEvent&); + void NotifyFrameLayerChanged(CSimpleFrame*, uint32_t); + void NotifyFrameMovedOrResized(CSimpleFrame*); + void OnLayerUpdate(float); + void OnLayerRender(void); + int32_t RaiseFrame(CSimpleFrame*, int32_t); + void RegisterForEvent(CSimpleFrame*, CSimpleEventType, int32_t, uint32_t); + void RegisterFrame(CSimpleFrame*); + void ShowFrame(CSimpleFrame*, int32_t); + int32_t StartMoveOrResizeFrame(const CMouseEvent&, MOVERESIZE_REASON, int32_t); + int32_t StartMoveOrResizeFrame(CSimpleFrame*, MOVERESIZE_REASON, float, float, int32_t); + int32_t StartMoveOrResizeFrame(CSimpleFrame*, MOVERESIZE_REASON, float, float, FRAMEPOINT); + void UnregisterForEvent(CSimpleFrame*, CSimpleEventType, int32_t); +}; + +#endif diff --git a/src/ui/FrameScript.cpp b/src/ui/FrameScript.cpp new file mode 100644 index 0000000..e5cbb09 --- /dev/null +++ b/src/ui/FrameScript.cpp @@ -0,0 +1,1566 @@ +#include "ui/FrameScript.hpp" +#include "ui/FrameScriptInternal.hpp" +#include "ui/FrameScript_Object.hpp" +#include "ui/LuaMemory.hpp" +#include "util/CStatus.hpp" +#include "util/Lua.hpp" +#include "util/SFile.hpp" +#include +#include +#include +#include +#include +#include + +const char* g_glueScriptEvents[41]; +const char* g_scriptEvents[722]; + +void* FrameScript::s_mempool; +lua_State* FrameScript::s_context; +int64_t FrameScript::s_scriptTimeUsed; +int32_t FrameScript::s_scriptProfileEnabled; +int32_t FrameScript::s_errorHandlerFun; +double_t FrameScript::s_scriptTimeDivisor; +int32_t FrameScript::s_errorHandlerRef; +int32_t FrameScript::s_recursiveTableHash; +int32_t FrameScript::s_pluralRule; +int32_t FrameScript::s_handlingError; +TSFixedArray FrameScript::s_scriptEvents; +TSHashTable FrameScript::s_scriptEventsHash; + +int32_t FrameScript::s_blocksizes[9] = { 0x10, 0x18, 0x20, 0x28, 0x40, 0x50, 0x80, 0xa0, 0x100 }; + +const char* FrameScript::s_compat_lua = R"( +------------------------------------------------------------------- +-- Table library +local tab = table +foreach = tab.foreach +foreachi = tab.foreachi +getn = tab.getn +tinsert = tab.insert +tremove = tab.remove +sort = tab.sort +wipe = tab.wipe + +------------------------------------------------------------------- +-- math library +local math = math +abs = math.abs +acos = function (x) return math.deg(math.acos(x)) end +asin = function (x) return math.deg(math.asin(x)) end +atan = function (x) return math.deg(math.atan(x)) end +atan2 = function (x,y) return math.deg(math.atan2(x,y)) end +ceil = math.ceil +cos = function (x) return math.cos(math.rad(x)) end +deg = math.deg +exp = math.exp +floor = math.floor +frexp = math.frexp +ldexp = math.ldexp +log = math.log +log10 = math.log10 +max = math.max +min = math.min +mod = math.fmod +PI = math.pi +--??? pow = math.pow +rad = math.rad +random = math.random +--randomseed = math.randomseed +sin = function (x) return math.sin(math.rad(x)) end +sqrt = math.sqrt +tan = function (x) return math.tan(math.rad(x)) end + +------------------------------------------------------------------- +-- string library +local str = string +strbyte = str.byte +strchar = str.char +strfind = str.find +format = str.format +gmatch = str.gmatch +gsub = str.gsub +strlen = str.len +strlower = str.lower +strmatch = str.match +strrep = str.rep +strrev = str.reverse +strsub = str.sub +strupper = str.upper +------------------------------------------------------------------- +-- Add custom string functions to the string table +str.trim = strtrim +str.split = strsplit +str.join = strjoin +str.replace = strreplace)"; + +const char* FrameScript_EventObject::GetName() { + return this->m_key.m_str; +} + +int64_t OsGetAsyncClocksPerSecond() { + // TODO + + return 1000.0; +} + +int32_t FrameScript_CompileFunction(const char* name, const char* wrapper, const char* body, CStatus* status) { + lua_State* L = FrameScript::s_context; + + size_t functionLen = SStrLen(wrapper) + SStrLen(body) + 1; + + char* function = static_cast(alloca(functionLen)); + + // Insert the function body into the wrapper + SStrPrintf(function, functionLen, wrapper, body); + + lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); + + if (luaL_loadbuffer(L, function, SStrLen(function), name)) { + if (status) { + const char* v10 = lua_tolstring(L, -1, 0); + status->Add(STATUS_ERROR, "%s", v10); + } + + if (lua_pcall(L, 1, 0, 0)) { + lua_settop(L, -2); + } + + return -1; + } else if (lua_pcall(L, 0, 1, -2)) { + if (status) { + const char* v13 = lua_tolstring(L, -1, 0); + status->Add(STATUS_ERROR, "%s", v13); + } + + lua_settop(L, -3); + + return -1; + } else { + int32_t luaRef = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_settop(L, -2); + + return luaRef; + } +} + +void FrameScript_CreateEvents(const char* names[], uint32_t count) { + FrameScript::s_scriptEvents.Clear(); + FrameScript::s_scriptEvents.SetCount(count); + + for (int32_t i = 0; i < count; i++) { + auto event = FrameScript::s_scriptEventsHash.New(names[i], 0, 0); + FrameScript::s_scriptEvents[i] = event; + } +} + +void FrameScript_Destroy() { + // TODO +} + +void FrameScript_Execute(const char* source, const char* filename, const char* a3) { + /* TODO taint tracking + v3 = lua_taintexpected++ == -1; + v8 = *(_DWORD *)lua_tainted; + + if (!v3 && !lua_taintedclosure) { + *(_DWORD *)lua_tainted = a3; + } + */ + + lua_State* L = FrameScript::s_context; + size_t len = SStrLen(source); + + lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); + + if (!luaL_loadbuffer(L, source, len, filename)) { + if (lua_pcall(L, 0, 0, -2)) { + lua_settop(L, -3); + } else { + lua_settop(L, -2); + } + } else if (lua_pcall(L, 1, 0, 0)) { + lua_settop(L, -2); + } + + /* TODO taint tracking + if (lua_taintexpected && !lua_taintedclosure) { + *(_DWORD *)lua_tainted = v8; + } + + v7 = lua_taintexpected - 1; + lua_taintexpected = v7; + + if (v7 <= 0) { + lua_taintexpected = 0; + } + */ +} + +void FrameScript_Execute(int32_t function, FrameScript_Object* objectThis, int32_t argCount, const char* a4, FrameScript_EventObject* event) { + lua_State* L = FrameScript::s_context; + + int32_t v20 = 1 - argCount + lua_gettop(L); + int32_t v19 = argCount; + + lua_checkstack(L, argCount + 2); + + if (objectThis) { + const char* name = objectThis->GetName(); + + if (!name) { + name = ""; + } + + // TODO + // v6 = alloca(SStrLen(name) + 5); + // v17 = (char *)&v14; + // SStrCopy((char *)&v14, "DBG:", 0x7FFFFFFF); + // SStrCopy(v17 + 4, name, 0x7FFFFFFF); + + lua_pushstring(L, "this"); + lua_rawget(L, LUA_GLOBALSINDEX); + + if (!objectThis->lua_registered) { + objectThis->RegisterScriptObject(0); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, objectThis->lua_objectRef); + lua_pushstring(L, "this"); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } + + if (event) { + lua_pushstring(L, "event"); + lua_rawget(L, LUA_GLOBALSINDEX); + lua_pushvalue(L, v20); + lua_pushstring(L, "event"); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } + + char argName[7] = { 'a', 'r', 'g', 0, 0, 0 }; + + int32_t firstArg = event != 0; + int32_t argId = 0; + + if (firstArg < argCount) { + for (int32_t i = firstArg; i < argCount; i++) { + argId = i + 1; + + if (argId >= 10) { + SStrPrintf(&argName[3], 3, "%d", argId); + } else { + argName[3] = '0' + argId; + argName[4] = 0; + } + + lua_pushstring(L, argName); + lua_rawget(L, LUA_GLOBALSINDEX); + lua_pushvalue(L, v20 + firstArg); + lua_pushstring(L, argName); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } + } + + lua_checkstack(L, argCount + 3); + + lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); + lua_rawgeti(L, LUA_REGISTRYINDEX, function); + + if (objectThis) { + if (!objectThis->lua_registered) { + objectThis->RegisterScriptObject(0); + } + + lua_rawgeti(L, LUA_REGISTRYINDEX, objectThis->lua_objectRef); + v19 = argCount + 1; + } + + for (int32_t i = 0; i < argCount; ++i) { + lua_pushvalue(L, v20 + i); + } + + if (lua_pcall(L, v19, 0, -2 - v19)) { + lua_settop(L, -2); + } + + lua_settop(L, -2); + + for (int32_t i = argId; i > 0; i--) { + if (i >= 10) { + SStrPrintf(&argName[3], 3, "%d", i); + } else { + argName[3] = '0' + i; + argName[4] = 0; + } + + lua_pushstring(L, argName); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } + + if (event) { + lua_pushstring(L, "event"); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } + + if (objectThis) { + lua_pushstring(L, "this"); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } + + lua_settop(L, -1 - argCount); +} + +int32_t FrameScript_ExecuteBuffer(const char* buffer, size_t bufferBytes, const char* bufferName, CStatus* status, const char* a5) { + lua_State* L = FrameScript::s_context; + + lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerRef); + + if (luaL_loadbuffer(L, buffer, bufferBytes, bufferName)) { + if (status) { + const char* v7 = lua_tolstring(L, -1, 0); + status->Add(STATUS_ERROR, "%s", v7); + } + + if (lua_pcall(L, 1, 0, 0)) { + lua_settop(L, -2); + } + + return 0; + } else { + int32_t v9 = 0; + + if (a5) { + lua_pushstring(L, a5); + lua_pushvalue(L, -4); + v9 = 2; + } + + if (lua_pcall(L, v9, 0, -2 - v9)) { + if (status) { + const char* v11 = lua_tolstring(L, -1, 0); + status->Add(STATUS_ERROR, "%s", v11); + } + + lua_settop(L, -3); + + return 0; + } else { + lua_settop(L, -2); + + return 1; + } + } +} + +int32_t FrameScript_ExecuteFile(const char* filePath, const char* a2, MD5_CTX* md5, CStatus* status) { + const char* v4 = filePath; + + char v12[260]; + + if (SStrStr(filePath, "..")) { + SStrCopy(v12, filePath, 260); + + char* i; + char* j; + + for (i = (char*)SStrStr(v12, ".."); i; i = (char*)SStrStr(v12, "..")) { + char v6 = *(i - 1); + + if (v6 != 92 && v6 != 47) { + break; + } + + char v7 = i[2]; + + if (v7 != 92 && v7 != 47) { + break; + } + + for (j = i - 2; j >= v12; --j) { + if (*j == 92) { + break; + } + + if (*j == 47) { + break; + } + } + + SStrCopy(j + 1, i + 3, 0x7FFFFFFF); + } + + v4 = v12; + } + + char v11[264]; + + SStrPrintf(v11, 261, "@%s", v4); + + void* fileBuffer; + size_t fileBytes; + + if (SFile::Load(0, v4, &fileBuffer, &fileBytes, 0, 1, NULL)) { + if (md5) { + MD5Update(md5, static_cast(fileBuffer), fileBytes); + } + + int32_t v10 = FrameScript_ExecuteBuffer(static_cast(fileBuffer), fileBytes, v11, status, a2); + + SFile::Unload(fileBuffer); + + return v10; + } else { + if (status) { + status->Add(STATUS_ERROR, "Error loading %s", v4); + } + + return 0; + } +} + +void FrameScript_Flush() { + if (FrameScript::s_context) { + FrameScript_Destroy(); + FrameScript_Initialize(FrameScript::s_scriptProfileEnabled); + } +} + +void FrameScript_GetColor(lua_State* L, int32_t idx, CImVector& color) { + float r = lua_tonumber(L, idx + 0); + r = std::max(0.0f, std::min(r, 1.0f)); + + float g = lua_tonumber(L, idx + 1); + g = std::max(0.0f, std::min(g, 1.0f)); + + float b = lua_tonumber(L, idx + 2); + b = std::max(0.0f, std::min(b, 1.0f)); + + float a = 1.0f; + if (lua_isnumber(L, idx + 3)) { + a = lua_tonumber(L, idx + 2); + a = std::max(0.0f, std::min(a, 1.0f)); + } + + color.Set(a, r, g, b); +} + +lua_State* FrameScript_GetContext(void) { + return FrameScript::s_context; +} + +const char* FrameScript_GetCurrentObject(lua_State* L, int32_t a2) { + lua_Debug info; + + if (!lua_getstack(L, a2, &info)) { + return nullptr; + } + + lua_getinfo(L, "Sln", &info); + + if (info.source[0] != '*' && SStrCmp(info.namewhat, "method", 0x7FFFFFFFu)) { + return nullptr; + } + + if (!lua_getlocal(L, &info, 1)) { + return nullptr; + } + + const char* objName = nullptr; + + if (lua_type(L, -1) == LUA_TTABLE) { + lua_rawgeti(L, -1, 0); + FrameScript_Object* obj = static_cast(lua_touserdata(L, -1)); + lua_settop(L, -2); + + if (obj) { + objName = obj->GetName(); + + if (!objName) { + objName = ""; + } + } + } + + lua_settop(L, -2); + + return objName; +} + +char FrameScript_GetPluralIndex(int32_t a1) { + if (FrameScript::s_pluralRule == PLURAL_RULE_0) { + return a1 != 1; + } + + if (FrameScript::s_pluralRule == PLURAL_RULE_1) { + return a1 > 1; + } + + if (FrameScript::s_pluralRule != PLURAL_RULE_2) { + return 0; + } + + if ((unsigned int)(a1 % 100 - 11) <= 3) { + return 2; + } + + if (a1 % 10 == 1) { + return 0; + } else { + return ((unsigned int)(a1 % 10 - 2) > 2) + 1; + } +} + +const char* FrameScript_GetText(const char* a1, int32_t count, FRAMESCRIPT_GENDER gender) { + const char* text = ""; + + char pluralIndex = FrameScript_GetPluralIndex(count); + + // Suffix based on pluralization rules + char pluralSuffix[4]; + + if (pluralIndex - 1 <= 8) { + pluralSuffix[0] = 95; // _ + pluralSuffix[1] = 80; // P + pluralSuffix[2] = 48 + pluralIndex; // 0-8 + pluralSuffix[3] = 0; // NULL + } + + // Gender suffix + const char* genderSuffix = ""; + + if (gender == GENDER_FEMALE) { + genderSuffix = "_FEMALE"; + } + + uint32_t len = SStrLen((const char*)pluralSuffix) + SStrLen(a1) + SStrLen(genderSuffix) + 1; + char* v9 = (char*)alloca(len); + + SStrPrintf(v9, len, "%s%s%s", a1, &pluralSuffix, genderSuffix); + if (FrameScript_GetVariable(v9, &text)) { + return text; + } + + SStrPrintf(v9, len, "%s%s", a1, &pluralSuffix); + if (FrameScript_GetVariable(v9, &text)) { + return text; + } + + SStrPrintf(v9, len, "%s%s", a1, genderSuffix); + if (FrameScript_GetVariable(v9, &text)) { + return text; + } + + // Fallback + FrameScript_GetVariable(a1, &text); + return text; +} + +int32_t FrameScript_GetVariable(const char* a1, const char** a2) { + lua_State* L = FrameScript::s_context; + + int32_t v3 = 0; + + lua_pushstring(L, a1); + lua_rawget(L, LUA_GLOBALSINDEX); + + if (lua_isstring(L, -1)) { + v3 = 1; + *a2 = lua_tolstring(L, -1, 0); + } + + lua_settop(L, -2); + + return v3; +} + +int32_t FrameScript_HandleError(lua_State* L) { + if (!lua_isstring(L, -1)) { + lua_pushstring(L, "UNKNOWN ERROR"); + lua_insert(L, -1); + } + + const char* v1 = lua_tolstring(L, -1, 0); + const char* v2 = SStrStr(v1, "*:"); + const char* objName = FrameScript_GetCurrentObject(L, 1); + + // TODO + // Remove temporary console debug logging + if (v2 && objName) { + printf("Error: %s%s\n", objName, v2 + 1); + } else { + printf("Error: %s\n", v1); + } + + if (v2 && objName) { + lua_pushlstring(L, v1, v2 - v1); + lua_pushstring(L, objName); + lua_pushstring(L, v2 + 1); + lua_concat(L, 3); + lua_replace(L, -2); + } + + // If seterrorhandler() has been called in Lua, invoke that Lua-side function now + if (FrameScript::s_errorHandlerFun != -1) { + FrameScript::s_handlingError = 1; + + lua_rawgeti(L, LUA_REGISTRYINDEX, FrameScript::s_errorHandlerFun); + lua_insert(L, -2); + lua_call(L, 1, 1); + + FrameScript::s_handlingError = 0; + } + + return 1; +} + +int32_t FrameScript_Initialize(int32_t a1) { + FrameScript::s_mempool = luaM_initPool(); + FrameScript::s_context = lua_newstate(luaM_reallocPool, FrameScript::s_mempool); // TODO a1 + FrameScript::s_scriptTimeUsed = 0ll; + + int64_t v1 = OsGetAsyncClocksPerSecond(); + FrameScript::s_scriptProfileEnabled = a1; + FrameScript::s_errorHandlerFun = -1; + FrameScript::s_scriptTimeDivisor = 1000.0 / (double)v1; + + lua_pushcclosure(FrameScript::s_context, FrameScript_HandleError, 0); + FrameScript::s_errorHandlerRef = luaL_ref(FrameScript::s_context, LUA_REGISTRYINDEX); + lua_createtable(FrameScript::s_context, 0, 0); + FrameScript::s_recursiveTableHash = luaL_ref(FrameScript::s_context, LUA_REGISTRYINDEX); + lua_gc(FrameScript::s_context, LUA_GCSETPAUSE, 110); + luaopen_base(FrameScript::s_context); + lua_settop(FrameScript::s_context, -3); + luaopen_string(FrameScript::s_context); + lua_settop(FrameScript::s_context, -2); + luaopen_table(FrameScript::s_context); + lua_settop(FrameScript::s_context, -2); + luaopen_math(FrameScript::s_context); + lua_settop(FrameScript::s_context, -2); + luaopen_bit(FrameScript::s_context); + lua_settop(FrameScript::s_context, -2); + luaL_register(FrameScript::s_context, "_G", FrameScriptInternal::extra_funcs); + lua_settop(FrameScript::s_context, -2); + + FrameScript_Execute(FrameScript::s_compat_lua, "compat.lua", 0); + + return 1; +} + +void FrameScript_RegisterFunction(const char* name, int32_t (*function)(struct lua_State *)) { + lua_State* L = FrameScript::s_context; + + lua_pushcclosure(L, function, 0); + lua_pushstring(L, name); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); +} + +void FrameScript_RegisterScriptEvent(FrameScript_Object* object, FrameScript_EventObject* event) { + if (event->pendingSignalCount) { + auto node = event->registerListeners.Head(); + + while (node) { + if (node->listener == object) { + return; + } + + node = node->Next(); + } + + node = event->registerListeners.NewNode(2, 0, 0x8); + node->listener = object; + } else { + auto node = event->listeners.NewNode(2, 0, 0x8); + node->listener = object; + } +} + +void FrameScript_SetPluralRule(PLURAL_RULE rule) { + FrameScript::s_pluralRule = rule; +} + +int32_t FrameScript_ShouldSignalEvent(uint32_t index) { + // TODO + return 1; +} + +void FrameScript_PushEventName(uint32_t index) { + // TODO + // bounds checks + + FrameScript_EventObject* event = FrameScript::s_scriptEvents[index]; + lua_pushstring(FrameScript::s_context, event->GetName()); +} + +int32_t FrameScript_PushEventArgs(const char* format, va_list args) { + int32_t argCount = 0; + + if (!format || !*format) { + return argCount; + } + + const char* cur = format; + + while (*cur) { + if (*cur == '%') { + cur++; + + switch (*cur) { + case 'b': + lua_pushboolean(FrameScript::s_context, va_arg(args, int32_t)); + argCount++; + break; + + case 'd': + lua_pushnumber(FrameScript::s_context, va_arg(args, int32_t)); + argCount++; + break; + + case 'f': + lua_pushnumber(FrameScript::s_context, va_arg(args, double)); + argCount++; + break; + + case 's': + lua_pushstring(FrameScript::s_context, va_arg(args, char*)); + argCount++; + break; + + case 'u': + lua_pushnumber(FrameScript::s_context, va_arg(args, uint32_t)); + argCount++; + break; + } + } + + cur++; + } + + return argCount; +} + +void FrameScript_SignalEvent(uint32_t index, lua_State* L, int32_t argCount) { + auto event = FrameScript::s_scriptEvents[index]; + + if (!event) { + return; + } + + // TODO + event->signalCount++; + event->pendingSignalCount++; + + lua_checkstack(L, argCount); + + auto node = event->listeners.Head(); + + while (node) { + auto unregisterNode = event->unregisterListeners.Head(); + + while (unregisterNode) { + if (node->listener == unregisterNode->listener) { + break; + } + + unregisterNode = unregisterNode->Next(); + } + + if (unregisterNode) { + break; + } + + auto script = &node->listener->m_onEvent; + + if (script->luaRef) { + for (int32_t i = 0; i < argCount; i++) { + lua_pushvalue(L, -argCount); + } + + FrameScript_Execute(script->luaRef, node->listener, argCount, script->unk, event); + } + + node = node->Next(); + } + + event->pendingSignalCount--; + + auto unregisterNode = event->unregisterListeners.Head(); + + while (unregisterNode) { + FrameScript_UnregisterScriptEvent(unregisterNode->listener, event); + unregisterNode = event->unregisterListeners.DeleteNode(unregisterNode); + } + + auto registerNode = event->registerListeners.Head(); + + while (registerNode) { + FrameScript_RegisterScriptEvent(registerNode->listener, event); + registerNode = event->registerListeners.DeleteNode(registerNode); + } +} + +void FrameScript_SignalEvent(uint32_t index, const char* format, ...) { + if (!FrameScript_ShouldSignalEvent(index)) { + return; + } + + va_list args; + va_start(args, format); + + // TODO + // v6 = lua_taintexpected++ == -1; + // v18 = lua_tainted; + // if (!v6 && !lua_taintedclosure) { + // lua_tainted = 0; + // } + + FrameScript_PushEventName(index); + + int32_t argCount = 1; + argCount += FrameScript_PushEventArgs(format, args); + + FrameScript_SignalEvent(index, FrameScript::s_context, argCount); + + // TODO + // if (lua_taintexpected && !lua_taintedclosure) { + // lua_tainted = v18; + // } + // if (--lua_taintexpected <= 0) { + // lua_taintexpected = 0; + // } + + va_end(args); +} + +void FrameScript_UnregisterScriptEvent(FrameScript_Object* object, FrameScript_EventObject* event) { + if (event->pendingSignalCount) { + auto node = event->unregisterListeners.Head(); + + while (node) { + if (node->listener == object) { + return; + } + + node = node->Next(); + } + + node = event->unregisterListeners.NewNode(2, 0, 0x8); + node->listener = object; + } else { + auto node = event->listeners.Head(); + + while (node) { + if (node->listener == object) { + event->listeners.DeleteNode(node); + break; + } + + node = node->Next(); + } + } +} + +void GlueScriptEventsInitialize() { + g_glueScriptEvents[0] = "SET_GLUE_SCREEN"; + g_glueScriptEvents[1] = "START_GLUE_MUSIC"; + g_glueScriptEvents[2] = "DISCONNECTED_FROM_SERVER"; + g_glueScriptEvents[3] = "OPEN_STATUS_DIALOG"; + g_glueScriptEvents[4] = "UPDATE_STATUS_DIALOG"; + g_glueScriptEvents[5] = "CLOSE_STATUS_DIALOG"; + g_glueScriptEvents[6] = "ADDON_LIST_UPDATE"; + g_glueScriptEvents[7] = "CHARACTER_LIST_UPDATE"; + g_glueScriptEvents[8] = "UPDATE_SELECTED_CHARACTER"; + g_glueScriptEvents[9] = "OPEN_REALM_LIST"; + g_glueScriptEvents[10] = "GET_PREFERRED_REALM_INFO"; + g_glueScriptEvents[11] = "UPDATE_SELECTED_RACE"; + g_glueScriptEvents[12] = "SELECT_LAST_CHARACTER"; + g_glueScriptEvents[13] = "SELECT_FIRST_CHARACTER"; + g_glueScriptEvents[14] = "GLUE_SCREENSHOT_SUCCEEDED"; + g_glueScriptEvents[15] = "GLUE_SCREENSHOT_FAILED"; + g_glueScriptEvents[16] = "PATCH_UPDATE_PROGRESS"; + g_glueScriptEvents[17] = "PATCH_DOWNLOADED"; + g_glueScriptEvents[18] = "SUGGEST_REALM"; + g_glueScriptEvents[19] = "SUGGEST_REALM_WRONG_PVP"; + g_glueScriptEvents[20] = "SUGGEST_REALM_WRONG_CATEGORY"; + g_glueScriptEvents[21] = "SHOW_SERVER_ALERT"; + g_glueScriptEvents[22] = "FRAMES_LOADED"; + g_glueScriptEvents[23] = "FORCE_RENAME_CHARACTER"; + g_glueScriptEvents[24] = "FORCE_DECLINE_CHARACTER"; + g_glueScriptEvents[25] = "SHOW_SURVEY_NOTIFICATION"; + g_glueScriptEvents[26] = "PLAYER_ENTER_PIN"; + g_glueScriptEvents[27] = "CLIENT_ACCOUNT_MISMATCH"; + g_glueScriptEvents[28] = "PLAYER_ENTER_MATRIX"; + g_glueScriptEvents[29] = "SCANDLL_ERROR"; + g_glueScriptEvents[30] = "SCANDLL_DOWNLOADING"; + g_glueScriptEvents[31] = "SCANDLL_FINISHED"; + g_glueScriptEvents[32] = "SERVER_SPLIT_NOTICE"; + g_glueScriptEvents[33] = "TIMER_ALERT"; + g_glueScriptEvents[34] = "ACCOUNT_MESSAGES_AVAILABLE"; + g_glueScriptEvents[35] = "ACCOUNT_MESSAGES_HEADERS_LOADED"; + g_glueScriptEvents[36] = "ACCOUNT_MESSAGES_BODY_LOADED"; + g_glueScriptEvents[37] = "CLIENT_TRIAL"; + g_glueScriptEvents[38] = "PLAYER_ENTER_TOKEN"; + g_glueScriptEvents[39] = "GAME_ACCOUNTS_UPDATED"; + g_glueScriptEvents[40] = "CLIENT_CONVERTED"; +} + +void ScriptEventsInitialize() { + g_scriptEvents[0] = "UNIT_PET"; + g_scriptEvents[2] = "UNIT_PET"; + g_scriptEvents[12] = "UNIT_TARGET"; + g_scriptEvents[17] = "UNIT_DISPLAYPOWER"; + g_scriptEvents[18] = "UNIT_HEALTH"; + g_scriptEvents[19] = "UNIT_MANA"; + g_scriptEvents[20] = "UNIT_RAGE"; + g_scriptEvents[21] = "UNIT_FOCUS"; + g_scriptEvents[22] = "UNIT_ENERGY"; + g_scriptEvents[23] = "UNIT_HAPPINESS"; + g_scriptEvents[25] = "UNIT_RUNIC_POWER"; + g_scriptEvents[26] = "UNIT_MAXHEALTH"; + g_scriptEvents[27] = "UNIT_MAXMANA"; + g_scriptEvents[28] = "UNIT_MAXRAGE"; + g_scriptEvents[29] = "UNIT_MAXFOCUS"; + g_scriptEvents[30] = "UNIT_MAXENERGY"; + g_scriptEvents[31] = "UNIT_MAXHAPPINESS"; + g_scriptEvents[33] = "UNIT_MAXRUNIC_POWER"; + g_scriptEvents[48] = "UNIT_LEVEL"; + g_scriptEvents[49] = "UNIT_FACTION"; + g_scriptEvents[53] = "UNIT_FLAGS"; + g_scriptEvents[54] = "UNIT_FLAGS"; + g_scriptEvents[56] = "UNIT_ATTACK_SPEED"; + g_scriptEvents[57] = "UNIT_ATTACK_SPEED"; + g_scriptEvents[58] = "UNIT_RANGEDDAMAGE"; + g_scriptEvents[64] = "UNIT_DAMAGE"; + g_scriptEvents[65] = "UNIT_DAMAGE"; + g_scriptEvents[66] = "UNIT_DAMAGE"; + g_scriptEvents[67] = "UNIT_DAMAGE"; + g_scriptEvents[71] = "UNIT_PET_EXPERIENCE"; + g_scriptEvents[72] = "UNIT_PET_EXPERIENCE"; + g_scriptEvents[73] = "UNIT_DYNAMIC_FLAGS"; + g_scriptEvents[78] = "UNIT_STATS"; + g_scriptEvents[79] = "UNIT_STATS"; + g_scriptEvents[80] = "UNIT_STATS"; + g_scriptEvents[81] = "UNIT_STATS"; + g_scriptEvents[82] = "UNIT_STATS"; + g_scriptEvents[93] = "UNIT_RESISTANCES"; + g_scriptEvents[94] = "UNIT_RESISTANCES"; + g_scriptEvents[95] = "UNIT_RESISTANCES"; + g_scriptEvents[96] = "UNIT_RESISTANCES"; + g_scriptEvents[97] = "UNIT_RESISTANCES"; + g_scriptEvents[98] = "UNIT_RESISTANCES"; + g_scriptEvents[99] = "UNIT_RESISTANCES"; + g_scriptEvents[100] = "UNIT_RESISTANCES"; + g_scriptEvents[101] = "UNIT_RESISTANCES"; + g_scriptEvents[102] = "UNIT_RESISTANCES"; + g_scriptEvents[103] = "UNIT_RESISTANCES"; + g_scriptEvents[104] = "UNIT_RESISTANCES"; + g_scriptEvents[105] = "UNIT_RESISTANCES"; + g_scriptEvents[106] = "UNIT_RESISTANCES"; + g_scriptEvents[107] = "UNIT_RESISTANCES"; + g_scriptEvents[108] = "UNIT_RESISTANCES"; + g_scriptEvents[109] = "UNIT_RESISTANCES"; + g_scriptEvents[110] = "UNIT_RESISTANCES"; + g_scriptEvents[111] = "UNIT_RESISTANCES"; + g_scriptEvents[112] = "UNIT_RESISTANCES"; + g_scriptEvents[113] = "UNIT_RESISTANCES"; + g_scriptEvents[117] = "UNIT_ATTACK_POWER"; + g_scriptEvents[118] = "UNIT_ATTACK_POWER"; + g_scriptEvents[119] = "UNIT_ATTACK_POWER"; + g_scriptEvents[120] = "UNIT_RANGED_ATTACK_POWER"; + g_scriptEvents[121] = "UNIT_RANGED_ATTACK_POWER"; + g_scriptEvents[122] = "UNIT_RANGED_ATTACK_POWER"; + g_scriptEvents[123] = "UNIT_RANGEDDAMAGE"; + g_scriptEvents[124] = "UNIT_RANGEDDAMAGE"; + g_scriptEvents[125] = "UNIT_MANA"; + g_scriptEvents[132] = "UNIT_MANA"; + g_scriptEvents[139] = "UNIT_STATS"; + g_scriptEvents[142] = "UNIT_AURA"; + g_scriptEvents[143] = "UNIT_COMBAT"; + g_scriptEvents[144] = "UNIT_NAME_UPDATE"; + g_scriptEvents[145] = "UNIT_PORTRAIT_UPDATE"; + g_scriptEvents[146] = "UNIT_MODEL_CHANGED"; + g_scriptEvents[147] = "UNIT_INVENTORY_CHANGED"; + g_scriptEvents[148] = "UNIT_CLASSIFICATION_CHANGED"; + g_scriptEvents[149] = "UNIT_COMBO_POINTS"; + g_scriptEvents[150] = "ITEM_LOCK_CHANGED"; + g_scriptEvents[151] = "PLAYER_XP_UPDATE"; + g_scriptEvents[152] = "PLAYER_REGEN_DISABLED"; + g_scriptEvents[153] = "PLAYER_REGEN_ENABLED"; + g_scriptEvents[154] = "PLAYER_AURAS_CHANGED"; + g_scriptEvents[155] = "PLAYER_ENTER_COMBAT"; + g_scriptEvents[156] = "PLAYER_LEAVE_COMBAT"; + g_scriptEvents[157] = "PLAYER_TARGET_CHANGED"; + g_scriptEvents[158] = "PLAYER_FOCUS_CHANGED"; + g_scriptEvents[159] = "PLAYER_CONTROL_LOST"; + g_scriptEvents[160] = "PLAYER_CONTROL_GAINED"; + g_scriptEvents[161] = "PLAYER_FARSIGHT_FOCUS_CHANGED"; + g_scriptEvents[162] = "PLAYER_LEVEL_UP"; + g_scriptEvents[163] = "PLAYER_MONEY"; + g_scriptEvents[164] = "PLAYER_DAMAGE_DONE_MODS"; + g_scriptEvents[165] = "PLAYER_TOTEM_UPDATE"; + g_scriptEvents[166] = "ZONE_CHANGED"; + g_scriptEvents[167] = "ZONE_CHANGED_INDOORS"; + g_scriptEvents[168] = "ZONE_CHANGED_NEW_AREA"; + g_scriptEvents[169] = "MINIMAP_UPDATE_ZOOM"; + g_scriptEvents[170] = "MINIMAP_UPDATE_TRACKING"; + g_scriptEvents[171] = "SCREENSHOT_SUCCEEDED"; + g_scriptEvents[172] = "SCREENSHOT_FAILED"; + g_scriptEvents[173] = "ACTIONBAR_SHOWGRID"; + g_scriptEvents[174] = "ACTIONBAR_HIDEGRID"; + g_scriptEvents[175] = "ACTIONBAR_PAGE_CHANGED"; + g_scriptEvents[176] = "ACTIONBAR_SLOT_CHANGED"; + g_scriptEvents[177] = "ACTIONBAR_UPDATE_STATE"; + g_scriptEvents[178] = "ACTIONBAR_UPDATE_USABLE"; + g_scriptEvents[179] = "ACTIONBAR_UPDATE_COOLDOWN"; + g_scriptEvents[180] = "UPDATE_BONUS_ACTIONBAR"; + g_scriptEvents[181] = "PARTY_MEMBERS_CHANGED"; + g_scriptEvents[182] = "PARTY_LEADER_CHANGED"; + g_scriptEvents[183] = "PARTY_MEMBER_ENABLE"; + g_scriptEvents[184] = "PARTY_MEMBER_DISABLE"; + g_scriptEvents[185] = "PARTY_LOOT_METHOD_CHANGED"; + g_scriptEvents[186] = "SYSMSG"; + g_scriptEvents[187] = "UI_ERROR_MESSAGE"; + g_scriptEvents[188] = "UI_INFO_MESSAGE"; + g_scriptEvents[189] = "UPDATE_CHAT_COLOR"; + g_scriptEvents[190] = "CHAT_MSG_ADDON"; + g_scriptEvents[191] = "CHAT_MSG_SYSTEM"; + g_scriptEvents[192] = "CHAT_MSG_SAY"; + g_scriptEvents[193] = "CHAT_MSG_PARTY"; + g_scriptEvents[194] = "CHAT_MSG_RAID"; + g_scriptEvents[195] = "CHAT_MSG_GUILD"; + g_scriptEvents[196] = "CHAT_MSG_OFFICER"; + g_scriptEvents[197] = "CHAT_MSG_YELL"; + g_scriptEvents[198] = "CHAT_MSG_WHISPER"; + g_scriptEvents[199] = "CHAT_MSG_WHISPER_INFORM"; + g_scriptEvents[200] = "CHAT_MSG_EMOTE"; + g_scriptEvents[201] = "CHAT_MSG_TEXT_EMOTE"; + g_scriptEvents[202] = "CHAT_MSG_MONSTER_SAY"; + g_scriptEvents[203] = "CHAT_MSG_MONSTER_PARTY"; + g_scriptEvents[204] = "CHAT_MSG_MONSTER_YELL"; + g_scriptEvents[205] = "CHAT_MSG_MONSTER_WHISPER"; + g_scriptEvents[206] = "CHAT_MSG_MONSTER_EMOTE"; + g_scriptEvents[207] = "CHAT_MSG_CHANNEL"; + g_scriptEvents[208] = "CHAT_MSG_CHANNEL_JOIN"; + g_scriptEvents[209] = "CHAT_MSG_CHANNEL_LEAVE"; + g_scriptEvents[210] = "CHAT_MSG_CHANNEL_LIST"; + g_scriptEvents[211] = "CHAT_MSG_CHANNEL_NOTICE"; + g_scriptEvents[212] = "CHAT_MSG_CHANNEL_NOTICE_USER"; + g_scriptEvents[213] = "CHAT_MSG_AFK"; + g_scriptEvents[214] = "CHAT_MSG_DND"; + g_scriptEvents[215] = "CHAT_MSG_IGNORED"; + g_scriptEvents[216] = "CHAT_MSG_SKILL"; + g_scriptEvents[217] = "CHAT_MSG_LOOT"; + g_scriptEvents[218] = "CHAT_MSG_MONEY"; + g_scriptEvents[219] = "CHAT_MSG_OPENING"; + g_scriptEvents[220] = "CHAT_MSG_TRADESKILLS"; + g_scriptEvents[221] = "CHAT_MSG_PET_INFO"; + g_scriptEvents[222] = "CHAT_MSG_COMBAT_MISC_INFO"; + g_scriptEvents[223] = "CHAT_MSG_COMBAT_XP_GAIN"; + g_scriptEvents[224] = "CHAT_MSG_COMBAT_HONOR_GAIN"; + g_scriptEvents[225] = "CHAT_MSG_COMBAT_FACTION_CHANGE"; + g_scriptEvents[226] = "CHAT_MSG_BG_SYSTEM_NEUTRAL"; + g_scriptEvents[227] = "CHAT_MSG_BG_SYSTEM_ALLIANCE"; + g_scriptEvents[228] = "CHAT_MSG_BG_SYSTEM_HORDE"; + g_scriptEvents[229] = "CHAT_MSG_RAID_LEADER"; + g_scriptEvents[230] = "CHAT_MSG_RAID_WARNING"; + g_scriptEvents[231] = "CHAT_MSG_RAID_BOSS_WHISPER"; + g_scriptEvents[232] = "CHAT_MSG_RAID_BOSS_EMOTE"; + g_scriptEvents[233] = "CHAT_MSG_FILTERED"; + g_scriptEvents[234] = "CHAT_MSG_BATTLEGROUND"; + g_scriptEvents[235] = "CHAT_MSG_BATTLEGROUND_LEADER"; + g_scriptEvents[236] = "CHAT_MSG_RESTRICTED"; + + // TODO + // g_scriptEvents[237] = &byte_9E14FF; + + g_scriptEvents[238] = "CHAT_MSG_ACHIEVEMENT"; + g_scriptEvents[239] = "CHAT_MSG_GUILD_ACHIEVEMENT"; + g_scriptEvents[240] = "LANGUAGE_LIST_CHANGED"; + g_scriptEvents[241] = "TIME_PLAYED_MSG"; + g_scriptEvents[242] = "SPELLS_CHANGED"; + g_scriptEvents[243] = "CURRENT_SPELL_CAST_CHANGED"; + g_scriptEvents[244] = "SPELL_UPDATE_COOLDOWN"; + g_scriptEvents[245] = "SPELL_UPDATE_USABLE"; + g_scriptEvents[246] = "CHARACTER_POINTS_CHANGED"; + g_scriptEvents[247] = "SKILL_LINES_CHANGED"; + g_scriptEvents[248] = "ITEM_PUSH"; + g_scriptEvents[249] = "LOOT_OPENED"; + g_scriptEvents[250] = "LOOT_SLOT_CLEARED"; + g_scriptEvents[251] = "LOOT_SLOT_CHANGED"; + g_scriptEvents[252] = "LOOT_CLOSED"; + g_scriptEvents[253] = "PLAYER_LOGIN"; + g_scriptEvents[254] = "PLAYER_LOGOUT"; + g_scriptEvents[255] = "PLAYER_ENTERING_WORLD"; + g_scriptEvents[256] = "PLAYER_LEAVING_WORLD"; + g_scriptEvents[257] = "PLAYER_ALIVE"; + g_scriptEvents[258] = "PLAYER_DEAD"; + g_scriptEvents[259] = "PLAYER_CAMPING"; + g_scriptEvents[260] = "PLAYER_QUITING"; + g_scriptEvents[261] = "LOGOUT_CANCEL"; + g_scriptEvents[262] = "RESURRECT_REQUEST"; + g_scriptEvents[263] = "PARTY_INVITE_REQUEST"; + g_scriptEvents[264] = "PARTY_INVITE_CANCEL"; + g_scriptEvents[265] = "GUILD_INVITE_REQUEST"; + g_scriptEvents[266] = "GUILD_INVITE_CANCEL"; + g_scriptEvents[267] = "GUILD_MOTD"; + g_scriptEvents[268] = "TRADE_REQUEST"; + g_scriptEvents[269] = "TRADE_REQUEST_CANCEL"; + g_scriptEvents[270] = "LOOT_BIND_CONFIRM"; + g_scriptEvents[271] = "EQUIP_BIND_CONFIRM"; + g_scriptEvents[272] = "AUTOEQUIP_BIND_CONFIRM"; + g_scriptEvents[273] = "USE_BIND_CONFIRM"; + g_scriptEvents[274] = "DELETE_ITEM_CONFIRM"; + g_scriptEvents[275] = "CURSOR_UPDATE"; + g_scriptEvents[276] = "ITEM_TEXT_BEGIN"; + g_scriptEvents[277] = "ITEM_TEXT_TRANSLATION"; + g_scriptEvents[278] = "ITEM_TEXT_READY"; + g_scriptEvents[279] = "ITEM_TEXT_CLOSED"; + g_scriptEvents[280] = "GOSSIP_SHOW"; + g_scriptEvents[281] = "GOSSIP_CONFIRM"; + g_scriptEvents[282] = "GOSSIP_CONFIRM_CANCEL"; + g_scriptEvents[283] = "GOSSIP_ENTER_CODE"; + g_scriptEvents[284] = "GOSSIP_CLOSED"; + g_scriptEvents[285] = "QUEST_GREETING"; + g_scriptEvents[286] = "QUEST_DETAIL"; + g_scriptEvents[287] = "QUEST_PROGRESS"; + g_scriptEvents[288] = "QUEST_COMPLETE"; + g_scriptEvents[289] = "QUEST_FINISHED"; + g_scriptEvents[290] = "QUEST_ITEM_UPDATE"; + g_scriptEvents[291] = "TAXIMAP_OPENED"; + g_scriptEvents[292] = "TAXIMAP_CLOSED"; + g_scriptEvents[293] = "QUEST_LOG_UPDATE"; + g_scriptEvents[294] = "TRAINER_SHOW"; + g_scriptEvents[295] = "TRAINER_UPDATE"; + g_scriptEvents[296] = "TRAINER_DESCRIPTION_UPDATE"; + g_scriptEvents[297] = "TRAINER_CLOSED"; + g_scriptEvents[298] = "CVAR_UPDATE"; + g_scriptEvents[299] = "TRADE_SKILL_SHOW"; + g_scriptEvents[300] = "TRADE_SKILL_UPDATE"; + g_scriptEvents[301] = "TRADE_SKILL_CLOSE"; + g_scriptEvents[302] = "MERCHANT_SHOW"; + g_scriptEvents[303] = "MERCHANT_UPDATE"; + g_scriptEvents[304] = "MERCHANT_CLOSED"; + g_scriptEvents[305] = "TRADE_SHOW"; + g_scriptEvents[306] = "TRADE_CLOSED"; + g_scriptEvents[307] = "TRADE_UPDATE"; + g_scriptEvents[308] = "TRADE_ACCEPT_UPDATE"; + g_scriptEvents[309] = "TRADE_TARGET_ITEM_CHANGED"; + g_scriptEvents[310] = "TRADE_PLAYER_ITEM_CHANGED"; + g_scriptEvents[311] = "TRADE_MONEY_CHANGED"; + g_scriptEvents[312] = "PLAYER_TRADE_MONEY"; + g_scriptEvents[313] = "BAG_OPEN"; + g_scriptEvents[314] = "BAG_UPDATE"; + g_scriptEvents[315] = "BAG_CLOSED"; + g_scriptEvents[316] = "BAG_UPDATE_COOLDOWN"; + g_scriptEvents[317] = "LOCALPLAYER_PET_RENAMED"; + g_scriptEvents[318] = "UNIT_ATTACK"; + g_scriptEvents[319] = "UNIT_DEFENSE"; + g_scriptEvents[320] = "PET_ATTACK_START"; + g_scriptEvents[321] = "PET_ATTACK_STOP"; + g_scriptEvents[322] = "UPDATE_MOUSEOVER_UNIT"; + g_scriptEvents[323] = "UNIT_SPELLCAST_SENT"; + g_scriptEvents[324] = "UNIT_SPELLCAST_START"; + g_scriptEvents[325] = "UNIT_SPELLCAST_STOP"; + g_scriptEvents[326] = "UNIT_SPELLCAST_FAILED"; + g_scriptEvents[327] = "UNIT_SPELLCAST_FAILED_QUIET"; + g_scriptEvents[328] = "UNIT_SPELLCAST_INTERRUPTED"; + g_scriptEvents[329] = "UNIT_SPELLCAST_DELAYED"; + g_scriptEvents[330] = "UNIT_SPELLCAST_SUCCEEDED"; + g_scriptEvents[331] = "UNIT_SPELLCAST_CHANNEL_START"; + g_scriptEvents[332] = "UNIT_SPELLCAST_CHANNEL_UPDATE"; + g_scriptEvents[333] = "UNIT_SPELLCAST_CHANNEL_STOP"; + g_scriptEvents[334] = "UNIT_SPELLCAST_INTERRUPTIBLE"; + g_scriptEvents[335] = "UNIT_SPELLCAST_NOT_INTERRUPTIBLE"; + g_scriptEvents[336] = "PLAYER_GUILD_UPDATE"; + g_scriptEvents[337] = "QUEST_ACCEPT_CONFIRM"; + g_scriptEvents[338] = "PLAYERBANKSLOTS_CHANGED"; + g_scriptEvents[339] = "BANKFRAME_OPENED"; + g_scriptEvents[340] = "BANKFRAME_CLOSED"; + g_scriptEvents[341] = "PLAYERBANKBAGSLOTS_CHANGED"; + g_scriptEvents[342] = "FRIENDLIST_UPDATE"; + g_scriptEvents[343] = "IGNORELIST_UPDATE"; + g_scriptEvents[344] = "MUTELIST_UPDATE"; + g_scriptEvents[345] = "PET_BAR_UPDATE"; + g_scriptEvents[346] = "PET_BAR_UPDATE_COOLDOWN"; + g_scriptEvents[347] = "PET_BAR_SHOWGRID"; + g_scriptEvents[348] = "PET_BAR_HIDEGRID"; + g_scriptEvents[349] = "PET_BAR_HIDE"; + g_scriptEvents[350] = "PET_BAR_UPDATE_USABLE"; + g_scriptEvents[351] = "MINIMAP_PING"; + g_scriptEvents[352] = "MIRROR_TIMER_START"; + g_scriptEvents[353] = "MIRROR_TIMER_PAUSE"; + g_scriptEvents[354] = "MIRROR_TIMER_STOP"; + g_scriptEvents[355] = "WORLD_MAP_UPDATE"; + g_scriptEvents[356] = "WORLD_MAP_NAME_UPDATE"; + g_scriptEvents[357] = "AUTOFOLLOW_BEGIN"; + g_scriptEvents[358] = "AUTOFOLLOW_END"; + g_scriptEvents[360] = "CINEMATIC_START"; + g_scriptEvents[361] = "CINEMATIC_STOP"; + g_scriptEvents[362] = "UPDATE_FACTION"; + g_scriptEvents[363] = "CLOSE_WORLD_MAP"; + g_scriptEvents[364] = "OPEN_TABARD_FRAME"; + g_scriptEvents[365] = "CLOSE_TABARD_FRAME"; + g_scriptEvents[366] = "TABARD_CANSAVE_CHANGED"; + g_scriptEvents[367] = "GUILD_REGISTRAR_SHOW"; + g_scriptEvents[368] = "GUILD_REGISTRAR_CLOSED"; + g_scriptEvents[369] = "DUEL_REQUESTED"; + g_scriptEvents[370] = "DUEL_OUTOFBOUNDS"; + g_scriptEvents[371] = "DUEL_INBOUNDS"; + g_scriptEvents[372] = "DUEL_FINISHED"; + g_scriptEvents[373] = "TUTORIAL_TRIGGER"; + g_scriptEvents[374] = "PET_DISMISS_START"; + g_scriptEvents[375] = "UPDATE_BINDINGS"; + g_scriptEvents[376] = "UPDATE_SHAPESHIFT_FORMS"; + g_scriptEvents[377] = "UPDATE_SHAPESHIFT_FORM"; + g_scriptEvents[378] = "UPDATE_SHAPESHIFT_USABLE"; + g_scriptEvents[379] = "UPDATE_SHAPESHIFT_COOLDOWN"; + g_scriptEvents[380] = "WHO_LIST_UPDATE"; + g_scriptEvents[381] = "PETITION_SHOW"; + g_scriptEvents[382] = "PETITION_CLOSED"; + g_scriptEvents[383] = "EXECUTE_CHAT_LINE"; + g_scriptEvents[384] = "UPDATE_MACROS"; + g_scriptEvents[385] = "UPDATE_TICKET"; + g_scriptEvents[386] = "UPDATE_CHAT_WINDOWS"; + g_scriptEvents[387] = "CONFIRM_XP_LOSS"; + g_scriptEvents[388] = "CORPSE_IN_RANGE"; + g_scriptEvents[389] = "CORPSE_IN_INSTANCE"; + g_scriptEvents[390] = "CORPSE_OUT_OF_RANGE"; + g_scriptEvents[391] = "UPDATE_GM_STATUS"; + g_scriptEvents[392] = "PLAYER_UNGHOST"; + g_scriptEvents[393] = "BIND_ENCHANT"; + g_scriptEvents[394] = "REPLACE_ENCHANT"; + g_scriptEvents[395] = "TRADE_REPLACE_ENCHANT"; + g_scriptEvents[396] = "TRADE_POTENTIAL_BIND_ENCHANT"; + g_scriptEvents[397] = "PLAYER_UPDATE_RESTING"; + g_scriptEvents[398] = "UPDATE_EXHAUSTION"; + g_scriptEvents[399] = "PLAYER_FLAGS_CHANGED"; + g_scriptEvents[400] = "GUILD_ROSTER_UPDATE"; + g_scriptEvents[401] = "GM_PLAYER_INFO"; + g_scriptEvents[402] = "MAIL_SHOW"; + g_scriptEvents[403] = "MAIL_CLOSED"; + g_scriptEvents[404] = "SEND_MAIL_MONEY_CHANGED"; + g_scriptEvents[405] = "SEND_MAIL_COD_CHANGED"; + g_scriptEvents[406] = "MAIL_SEND_INFO_UPDATE"; + g_scriptEvents[407] = "MAIL_SEND_SUCCESS"; + g_scriptEvents[408] = "MAIL_INBOX_UPDATE"; + g_scriptEvents[409] = "MAIL_LOCK_SEND_ITEMS"; + g_scriptEvents[410] = "MAIL_UNLOCK_SEND_ITEMS"; + g_scriptEvents[411] = "BATTLEFIELDS_SHOW"; + g_scriptEvents[412] = "BATTLEFIELDS_CLOSED"; + g_scriptEvents[413] = "UPDATE_BATTLEFIELD_STATUS"; + g_scriptEvents[414] = "UPDATE_BATTLEFIELD_SCORE"; + g_scriptEvents[415] = "AUCTION_HOUSE_SHOW"; + g_scriptEvents[416] = "AUCTION_HOUSE_CLOSED"; + g_scriptEvents[417] = "NEW_AUCTION_UPDATE"; + g_scriptEvents[418] = "AUCTION_ITEM_LIST_UPDATE"; + g_scriptEvents[419] = "AUCTION_OWNED_LIST_UPDATE"; + g_scriptEvents[420] = "AUCTION_BIDDER_LIST_UPDATE"; + g_scriptEvents[421] = "PET_UI_UPDATE"; + g_scriptEvents[422] = "PET_UI_CLOSE"; + g_scriptEvents[423] = "ADDON_LOADED"; + g_scriptEvents[424] = "VARIABLES_LOADED"; + g_scriptEvents[425] = "MACRO_ACTION_FORBIDDEN"; + g_scriptEvents[426] = "ADDON_ACTION_FORBIDDEN"; + g_scriptEvents[427] = "MACRO_ACTION_BLOCKED"; + g_scriptEvents[428] = "ADDON_ACTION_BLOCKED"; + g_scriptEvents[429] = "START_AUTOREPEAT_SPELL"; + g_scriptEvents[430] = "STOP_AUTOREPEAT_SPELL"; + g_scriptEvents[431] = "PET_STABLE_SHOW"; + g_scriptEvents[432] = "PET_STABLE_UPDATE"; + g_scriptEvents[433] = "PET_STABLE_UPDATE_PAPERDOLL"; + g_scriptEvents[434] = "PET_STABLE_CLOSED"; + g_scriptEvents[435] = "RAID_ROSTER_UPDATE"; + g_scriptEvents[436] = "UPDATE_PENDING_MAIL"; + g_scriptEvents[437] = "UPDATE_INVENTORY_ALERTS"; + g_scriptEvents[438] = "UPDATE_INVENTORY_DURABILITY"; + g_scriptEvents[439] = "UPDATE_TRADESKILL_RECAST"; + g_scriptEvents[440] = "OPEN_MASTER_LOOT_LIST"; + g_scriptEvents[441] = "UPDATE_MASTER_LOOT_LIST"; + g_scriptEvents[442] = "START_LOOT_ROLL"; + g_scriptEvents[443] = "CANCEL_LOOT_ROLL"; + g_scriptEvents[444] = "CONFIRM_LOOT_ROLL"; + g_scriptEvents[445] = "CONFIRM_DISENCHANT_ROLL"; + g_scriptEvents[446] = "INSTANCE_BOOT_START"; + g_scriptEvents[447] = "INSTANCE_BOOT_STOP"; + g_scriptEvents[448] = "LEARNED_SPELL_IN_TAB"; + g_scriptEvents[449] = "DISPLAY_SIZE_CHANGED"; + g_scriptEvents[450] = "CONFIRM_TALENT_WIPE"; + g_scriptEvents[451] = "CONFIRM_BINDER"; + g_scriptEvents[452] = "MAIL_FAILED"; + g_scriptEvents[453] = "CLOSE_INBOX_ITEM"; + g_scriptEvents[454] = "CONFIRM_SUMMON"; + g_scriptEvents[455] = "CANCEL_SUMMON"; + g_scriptEvents[456] = "BILLING_NAG_DIALOG"; + g_scriptEvents[457] = "IGR_BILLING_NAG_DIALOG"; + g_scriptEvents[458] = "PLAYER_SKINNED"; + g_scriptEvents[459] = "TABARD_SAVE_PENDING"; + g_scriptEvents[460] = "UNIT_QUEST_LOG_CHANGED"; + g_scriptEvents[461] = "PLAYER_PVP_KILLS_CHANGED"; + g_scriptEvents[462] = "PLAYER_PVP_RANK_CHANGED"; + g_scriptEvents[463] = "INSPECT_HONOR_UPDATE"; + g_scriptEvents[464] = "UPDATE_WORLD_STATES"; + g_scriptEvents[465] = "AREA_SPIRIT_HEALER_IN_RANGE"; + g_scriptEvents[466] = "AREA_SPIRIT_HEALER_OUT_OF_RANGE"; + g_scriptEvents[467] = "PLAYTIME_CHANGED"; + g_scriptEvents[468] = "UPDATE_LFG_TYPES"; + g_scriptEvents[469] = "UPDATE_LFG_LIST"; + g_scriptEvents[470] = "UPDATE_LFG_LIST_INCREMENTAL"; + g_scriptEvents[471] = "START_MINIGAME"; + g_scriptEvents[472] = "MINIGAME_UPDATE"; + g_scriptEvents[473] = "READY_CHECK"; + g_scriptEvents[474] = "READY_CHECK_CONFIRM"; + g_scriptEvents[475] = "READY_CHECK_FINISHED"; + g_scriptEvents[476] = "RAID_TARGET_UPDATE"; + g_scriptEvents[477] = "GMSURVEY_DISPLAY"; + g_scriptEvents[478] = "UPDATE_INSTANCE_INFO"; + g_scriptEvents[479] = "SOCKET_INFO_UPDATE"; + g_scriptEvents[480] = "SOCKET_INFO_CLOSE"; + g_scriptEvents[481] = "PETITION_VENDOR_SHOW"; + g_scriptEvents[482] = "PETITION_VENDOR_CLOSED"; + g_scriptEvents[483] = "PETITION_VENDOR_UPDATE"; + g_scriptEvents[484] = "COMBAT_TEXT_UPDATE"; + g_scriptEvents[485] = "QUEST_WATCH_UPDATE"; + g_scriptEvents[486] = "KNOWLEDGE_BASE_SETUP_LOAD_SUCCESS"; + g_scriptEvents[487] = "KNOWLEDGE_BASE_SETUP_LOAD_FAILURE"; + g_scriptEvents[488] = "KNOWLEDGE_BASE_QUERY_LOAD_SUCCESS"; + g_scriptEvents[489] = "KNOWLEDGE_BASE_QUERY_LOAD_FAILURE"; + g_scriptEvents[490] = "KNOWLEDGE_BASE_ARTICLE_LOAD_SUCCESS"; + g_scriptEvents[491] = "KNOWLEDGE_BASE_ARTICLE_LOAD_FAILURE"; + g_scriptEvents[492] = "KNOWLEDGE_BASE_SYSTEM_MOTD_UPDATED"; + g_scriptEvents[493] = "KNOWLEDGE_BASE_SERVER_MESSAGE"; + g_scriptEvents[494] = "ARENA_TEAM_UPDATE"; + g_scriptEvents[495] = "ARENA_TEAM_ROSTER_UPDATE"; + g_scriptEvents[496] = "ARENA_TEAM_INVITE_REQUEST"; + g_scriptEvents[497] = "HONOR_CURRENCY_UPDATE"; + g_scriptEvents[498] = "KNOWN_TITLES_UPDATE"; + g_scriptEvents[499] = "NEW_TITLE_EARNED"; + g_scriptEvents[500] = "OLD_TITLE_LOST"; + g_scriptEvents[501] = "LFG_UPDATE"; + g_scriptEvents[502] = "LFG_PROPOSAL_UPDATE"; + g_scriptEvents[503] = "LFG_PROPOSAL_SHOW"; + g_scriptEvents[504] = "LFG_PROPOSAL_FAILED"; + g_scriptEvents[505] = "LFG_PROPOSAL_SUCCEEDED"; + g_scriptEvents[506] = "LFG_ROLE_UPDATE"; + g_scriptEvents[507] = "LFG_ROLE_CHECK_UPDATE"; + g_scriptEvents[508] = "LFG_ROLE_CHECK_SHOW"; + g_scriptEvents[509] = "LFG_ROLE_CHECK_HIDE"; + g_scriptEvents[510] = "LFG_ROLE_CHECK_ROLE_CHOSEN"; + g_scriptEvents[511] = "LFG_QUEUE_STATUS_UPDATE"; + g_scriptEvents[512] = "LFG_BOOT_PROPOSAL_UPDATE"; + g_scriptEvents[513] = "LFG_LOCK_INFO_RECEIVED"; + g_scriptEvents[514] = "LFG_UPDATE_RANDOM_INFO"; + g_scriptEvents[515] = "LFG_OFFER_CONTINUE"; + g_scriptEvents[516] = "LFG_OPEN_FROM_GOSSIP"; + g_scriptEvents[517] = "LFG_COMPLETION_REWARD"; + g_scriptEvents[518] = "PARTY_LFG_RESTRICTED"; + g_scriptEvents[519] = "PLAYER_ROLES_ASSIGNED"; + g_scriptEvents[520] = "COMBAT_RATING_UPDATE"; + g_scriptEvents[521] = "MODIFIER_STATE_CHANGED"; + g_scriptEvents[522] = "UPDATE_STEALTH"; + g_scriptEvents[523] = "ENABLE_TAXI_BENCHMARK"; + g_scriptEvents[524] = "DISABLE_TAXI_BENCHMARK"; + g_scriptEvents[525] = "VOICE_START"; + g_scriptEvents[526] = "VOICE_STOP"; + g_scriptEvents[527] = "VOICE_STATUS_UPDATE"; + g_scriptEvents[528] = "VOICE_CHANNEL_STATUS_UPDATE"; + g_scriptEvents[529] = "UPDATE_FLOATING_CHAT_WINDOWS"; + g_scriptEvents[530] = "RAID_INSTANCE_WELCOME"; + g_scriptEvents[531] = "MOVIE_RECORDING_PROGRESS"; + g_scriptEvents[532] = "MOVIE_COMPRESSING_PROGRESS"; + g_scriptEvents[533] = "MOVIE_UNCOMPRESSED_MOVIE"; + g_scriptEvents[534] = "VOICE_PUSH_TO_TALK_START"; + g_scriptEvents[535] = "VOICE_PUSH_TO_TALK_STOP"; + g_scriptEvents[536] = "GUILDBANKFRAME_OPENED"; + g_scriptEvents[537] = "GUILDBANKFRAME_CLOSED"; + g_scriptEvents[538] = "GUILDBANKBAGSLOTS_CHANGED"; + g_scriptEvents[539] = "GUILDBANK_ITEM_LOCK_CHANGED"; + g_scriptEvents[540] = "GUILDBANK_UPDATE_TABS"; + g_scriptEvents[541] = "GUILDBANK_UPDATE_MONEY"; + g_scriptEvents[542] = "GUILDBANKLOG_UPDATE"; + g_scriptEvents[543] = "GUILDBANK_UPDATE_WITHDRAWMONEY"; + g_scriptEvents[544] = "GUILDBANK_UPDATE_TEXT"; + g_scriptEvents[545] = "GUILDBANK_TEXT_CHANGED"; + g_scriptEvents[546] = "CHANNEL_UI_UPDATE"; + g_scriptEvents[547] = "CHANNEL_COUNT_UPDATE"; + g_scriptEvents[548] = "CHANNEL_ROSTER_UPDATE"; + g_scriptEvents[549] = "CHANNEL_VOICE_UPDATE"; + g_scriptEvents[550] = "CHANNEL_INVITE_REQUEST"; + g_scriptEvents[551] = "CHANNEL_PASSWORD_REQUEST"; + g_scriptEvents[552] = "CHANNEL_FLAGS_UPDATED"; + g_scriptEvents[553] = "VOICE_SESSIONS_UPDATE"; + g_scriptEvents[554] = "VOICE_CHAT_ENABLED_UPDATE"; + g_scriptEvents[555] = "VOICE_LEFT_SESSION"; + g_scriptEvents[556] = "INSPECT_TALENT_READY"; + g_scriptEvents[557] = "VOICE_SELF_MUTE"; + g_scriptEvents[558] = "VOICE_PLATE_START"; + g_scriptEvents[559] = "VOICE_PLATE_STOP"; + g_scriptEvents[560] = "ARENA_SEASON_WORLD_STATE"; + g_scriptEvents[561] = "GUILD_EVENT_LOG_UPDATE"; + g_scriptEvents[562] = "GUILDTABARD_UPDATE"; + g_scriptEvents[563] = "SOUND_DEVICE_UPDATE"; + g_scriptEvents[564] = "COMMENTATOR_MAP_UPDATE"; + g_scriptEvents[565] = "COMMENTATOR_ENTER_WORLD"; + g_scriptEvents[566] = "COMBAT_LOG_EVENT"; + g_scriptEvents[567] = "COMBAT_LOG_EVENT_UNFILTERED"; + g_scriptEvents[568] = "COMMENTATOR_PLAYER_UPDATE"; + g_scriptEvents[569] = "PLAYER_ENTERING_BATTLEGROUND"; + g_scriptEvents[570] = "BARBER_SHOP_OPEN"; + g_scriptEvents[571] = "BARBER_SHOP_CLOSE"; + g_scriptEvents[572] = "BARBER_SHOP_SUCCESS"; + g_scriptEvents[573] = "BARBER_SHOP_APPEARANCE_APPLIED"; + g_scriptEvents[574] = "CALENDAR_UPDATE_INVITE_LIST"; + g_scriptEvents[575] = "CALENDAR_UPDATE_EVENT_LIST"; + g_scriptEvents[576] = "CALENDAR_NEW_EVENT"; + g_scriptEvents[577] = "CALENDAR_OPEN_EVENT"; + g_scriptEvents[578] = "CALENDAR_CLOSE_EVENT"; + g_scriptEvents[579] = "CALENDAR_UPDATE_EVENT"; + g_scriptEvents[580] = "CALENDAR_UPDATE_PENDING_INVITES"; + g_scriptEvents[581] = "CALENDAR_EVENT_ALARM"; + g_scriptEvents[582] = "CALENDAR_UPDATE_ERROR"; + g_scriptEvents[583] = "CALENDAR_ACTION_PENDING"; + g_scriptEvents[584] = "VEHICLE_ANGLE_SHOW"; + g_scriptEvents[585] = "VEHICLE_ANGLE_UPDATE"; + g_scriptEvents[586] = "VEHICLE_POWER_SHOW"; + g_scriptEvents[587] = "UNIT_ENTERING_VEHICLE"; + g_scriptEvents[588] = "UNIT_ENTERED_VEHICLE"; + g_scriptEvents[589] = "UNIT_EXITING_VEHICLE"; + g_scriptEvents[590] = "UNIT_EXITED_VEHICLE"; + g_scriptEvents[591] = "VEHICLE_PASSENGERS_CHANGED"; + g_scriptEvents[592] = "PLAYER_GAINS_VEHICLE_DATA"; + g_scriptEvents[593] = "PLAYER_LOSES_VEHICLE_DATA"; + g_scriptEvents[594] = "PET_FORCE_NAME_DECLENSION"; + g_scriptEvents[595] = "LEVEL_GRANT_PROPOSED"; + g_scriptEvents[596] = "SYNCHRONIZE_SETTINGS"; + g_scriptEvents[597] = "PLAY_MOVIE"; + g_scriptEvents[598] = "RUNE_POWER_UPDATE"; + g_scriptEvents[599] = "RUNE_TYPE_UPDATE"; + g_scriptEvents[600] = "ACHIEVEMENT_EARNED"; + g_scriptEvents[601] = "CRITERIA_UPDATE"; + g_scriptEvents[602] = "RECEIVED_ACHIEVEMENT_LIST"; + g_scriptEvents[603] = "PET_RENAMEABLE"; + g_scriptEvents[604] = "KNOWN_CURRENCY_TYPES_UPDATE"; + g_scriptEvents[605] = "CURRENCY_DISPLAY_UPDATE"; + g_scriptEvents[606] = "COMPANION_LEARNED"; + g_scriptEvents[607] = "COMPANION_UNLEARNED"; + g_scriptEvents[608] = "COMPANION_UPDATE"; + g_scriptEvents[609] = "UNIT_THREAT_LIST_UPDATE"; + g_scriptEvents[610] = "UNIT_THREAT_SITUATION_UPDATE"; + g_scriptEvents[611] = "GLYPH_ADDED"; + g_scriptEvents[612] = "GLYPH_REMOVED"; + g_scriptEvents[613] = "GLYPH_UPDATED"; + g_scriptEvents[614] = "GLYPH_ENABLED"; + g_scriptEvents[615] = "GLYPH_DISABLED"; + g_scriptEvents[616] = "USE_GLYPH"; + g_scriptEvents[617] = "TRACKED_ACHIEVEMENT_UPDATE"; + g_scriptEvents[618] = "ARENA_OPPONENT_UPDATE"; + g_scriptEvents[619] = "INSPECT_ACHIEVEMENT_READY"; + g_scriptEvents[620] = "RAISED_AS_GHOUL"; + g_scriptEvents[621] = "PARTY_CONVERTED_TO_RAID"; + g_scriptEvents[622] = "PVPQUEUE_ANYWHERE_SHOW"; + g_scriptEvents[623] = "PVPQUEUE_ANYWHERE_UPDATE_AVAILABLE"; + g_scriptEvents[624] = "QUEST_ACCEPTED"; + g_scriptEvents[625] = "PLAYER_TALENT_UPDATE"; + g_scriptEvents[626] = "ACTIVE_TALENT_GROUP_CHANGED"; + g_scriptEvents[627] = "PET_TALENT_UPDATE"; + g_scriptEvents[628] = "PREVIEW_TALENT_POINTS_CHANGED"; + g_scriptEvents[629] = "PREVIEW_PET_TALENT_POINTS_CHANGED"; + g_scriptEvents[630] = "WEAR_EQUIPMENT_SET"; + g_scriptEvents[631] = "EQUIPMENT_SETS_CHANGED"; + g_scriptEvents[632] = "INSTANCE_LOCK_START"; + g_scriptEvents[633] = "INSTANCE_LOCK_STOP"; + g_scriptEvents[634] = "PLAYER_EQUIPMENT_CHANGED"; + g_scriptEvents[635] = "ITEM_LOCKED"; + g_scriptEvents[636] = "ITEM_UNLOCKED"; + g_scriptEvents[637] = "TRADE_SKILL_FILTER_UPDATE"; + g_scriptEvents[638] = "EQUIPMENT_SWAP_PENDING"; + g_scriptEvents[639] = "EQUIPMENT_SWAP_FINISHED"; + g_scriptEvents[640] = "NPC_PVPQUEUE_ANYWHERE"; + g_scriptEvents[641] = "UPDATE_MULTI_CAST_ACTIONBAR"; + g_scriptEvents[642] = "ENABLE_XP_GAIN"; + g_scriptEvents[643] = "DISABLE_XP_GAIN"; + g_scriptEvents[644] = "BATTLEFIELD_MGR_ENTRY_INVITE"; + g_scriptEvents[645] = "BATTLEFIELD_MGR_ENTERED"; + g_scriptEvents[646] = "BATTLEFIELD_MGR_QUEUE_REQUEST_RESPONSE"; + g_scriptEvents[647] = "BATTLEFIELD_MGR_EJECT_PENDING"; + g_scriptEvents[648] = "BATTLEFIELD_MGR_EJECTED"; + g_scriptEvents[649] = "BATTLEFIELD_MGR_QUEUE_INVITE"; + g_scriptEvents[650] = "BATTLEFIELD_MGR_STATE_CHANGE"; + g_scriptEvents[651] = "WORLD_STATE_UI_TIMER_UPDATE"; + g_scriptEvents[652] = "END_REFUND"; + g_scriptEvents[653] = "END_BOUND_TRADEABLE"; + g_scriptEvents[654] = "UPDATE_CHAT_COLOR_NAME_BY_CLASS"; + g_scriptEvents[655] = "GMRESPONSE_RECEIVED"; + g_scriptEvents[656] = "VEHICLE_UPDATE"; + g_scriptEvents[657] = "WOW_MOUSE_NOT_FOUND"; + g_scriptEvents[659] = "MAIL_SUCCESS"; + g_scriptEvents[660] = "TALENTS_INVOLUNTARILY_RESET"; + g_scriptEvents[661] = "INSTANCE_ENCOUNTER_ENGAGE_UNIT"; + g_scriptEvents[662] = "QUEST_QUERY_COMPLETE"; + g_scriptEvents[663] = "QUEST_POI_UPDATE"; + g_scriptEvents[664] = "PLAYER_DIFFICULTY_CHANGED"; + g_scriptEvents[665] = "CHAT_MSG_PARTY_LEADER"; + g_scriptEvents[666] = "VOTE_KICK_REASON_NEEDED"; + g_scriptEvents[667] = "ENABLE_LOW_LEVEL_RAID"; + g_scriptEvents[668] = "DISABLE_LOW_LEVEL_RAID"; + g_scriptEvents[669] = "CHAT_MSG_TARGETICONS"; + g_scriptEvents[670] = "AUCTION_HOUSE_DISABLED"; + g_scriptEvents[671] = "AUCTION_MULTISELL_START"; + g_scriptEvents[672] = "AUCTION_MULTISELL_UPDATE"; + g_scriptEvents[673] = "AUCTION_MULTISELL_FAILURE"; + g_scriptEvents[674] = "PET_SPELL_POWER_UPDATE"; + g_scriptEvents[675] = "BN_CONNECTED"; + g_scriptEvents[676] = "BN_DISCONNECTED"; + g_scriptEvents[677] = "BN_SELF_ONLINE"; + g_scriptEvents[678] = "BN_SELF_OFFLINE"; + g_scriptEvents[679] = "BN_FRIEND_LIST_SIZE_CHANGED"; + g_scriptEvents[680] = "BN_FRIEND_INVITE_LIST_INITIALIZED"; + g_scriptEvents[681] = "BN_FRIEND_INVITE_SEND_RESULT"; + g_scriptEvents[682] = "BN_FRIEND_INVITE_ADDED"; + g_scriptEvents[683] = "BN_FRIEND_INVITE_REMOVED"; + g_scriptEvents[684] = "BN_FRIEND_INFO_CHANGED"; + g_scriptEvents[685] = "BN_CUSTOM_MESSAGE_CHANGED"; + g_scriptEvents[686] = "BN_CUSTOM_MESSAGE_LOADED"; + g_scriptEvents[687] = "CHAT_MSG_BN_WHISPER"; + g_scriptEvents[688] = "CHAT_MSG_BN_WHISPER_INFORM"; + g_scriptEvents[689] = "BN_CHAT_WHISPER_UNDELIVERABLE"; + g_scriptEvents[690] = "BN_CHAT_CHANNEL_JOINED"; + g_scriptEvents[691] = "BN_CHAT_CHANNEL_LEFT"; + g_scriptEvents[692] = "BN_CHAT_CHANNEL_CLOSED"; + g_scriptEvents[693] = "CHAT_MSG_BN_CONVERSATION"; + g_scriptEvents[694] = "CHAT_MSG_BN_CONVERSATION_NOTICE"; + g_scriptEvents[695] = "CHAT_MSG_BN_CONVERSATION_LIST"; + g_scriptEvents[696] = "BN_CHAT_CHANNEL_MESSAGE_UNDELIVERABLE"; + g_scriptEvents[697] = "BN_CHAT_CHANNEL_MESSAGE_BLOCKED"; + g_scriptEvents[698] = "BN_CHAT_CHANNEL_MEMBER_JOINED"; + g_scriptEvents[699] = "BN_CHAT_CHANNEL_MEMBER_LEFT"; + g_scriptEvents[700] = "BN_CHAT_CHANNEL_MEMBER_UPDATED"; + g_scriptEvents[701] = "BN_CHAT_CHANNEL_CREATE_SUCCEEDED"; + g_scriptEvents[702] = "BN_CHAT_CHANNEL_CREATE_FAILED"; + g_scriptEvents[703] = "BN_CHAT_CHANNEL_INVITE_SUCCEEDED"; + g_scriptEvents[704] = "BN_CHAT_CHANNEL_INVITE_FAILED"; + g_scriptEvents[705] = "BN_BLOCK_LIST_UPDATED"; + g_scriptEvents[706] = "BN_SYSTEM_MESSAGE"; + g_scriptEvents[707] = "BN_REQUEST_FOF_SUCCEEDED"; + g_scriptEvents[708] = "BN_REQUEST_FOF_FAILED"; + g_scriptEvents[709] = "BN_NEW_PRESENCE"; + g_scriptEvents[710] = "BN_TOON_NAME_UPDATED"; + g_scriptEvents[711] = "BN_FRIEND_ACCOUNT_ONLINE"; + g_scriptEvents[712] = "BN_FRIEND_ACCOUNT_OFFLINE"; + g_scriptEvents[713] = "BN_FRIEND_TOON_ONLINE"; + g_scriptEvents[714] = "BN_FRIEND_TOON_OFFLINE"; + g_scriptEvents[715] = "BN_MATURE_LANGUAGE_FILTER"; + g_scriptEvents[716] = "COMMENTATOR_SKIRMISH_QUEUE_REQUEST"; + g_scriptEvents[717] = "COMMENTATOR_SKIRMISH_MODE_REQUEST"; + g_scriptEvents[718] = "CHAT_MSG_BN_INLINE_TOAST_ALERT"; + g_scriptEvents[719] = "CHAT_MSG_BN_INLINE_TOAST_BROADCAST"; + g_scriptEvents[720] = "CHAT_MSG_BN_INLINE_TOAST_BROADCAST_INFORM"; + g_scriptEvents[721] = "CHAT_MSG_BN_INLINE_TOAST_CONVERSATION"; +} diff --git a/src/ui/FrameScript.hpp b/src/ui/FrameScript.hpp new file mode 100644 index 0000000..3a9896f --- /dev/null +++ b/src/ui/FrameScript.hpp @@ -0,0 +1,107 @@ +#ifndef UI_FRAME_SCRIPT_HPP +#define UI_FRAME_SCRIPT_HPP + +#include "ui/Types.hpp" +#include +#include +#include +#include +#include +#include + +#define NUM_GLUE_SCRIPT_EVENTS 41 + +class CImVector; +class CSimpleFrame; +class CStatus; +class FrameScript_Object; +struct lua_State; + +struct SIMPLEFRAMENODE : TSLinkedNode { + CSimpleFrame *frame; +}; + +struct EVENTLISTENERNODE : TSLinkedNode { + FrameScript_Object *listener; +}; + +class FrameScript_EventObject : public TSHashObject { + public: + // Member variables + TSList> listeners; + TSList> unregisterListeners; + TSList> registerListeners; + uint32_t signalCount = 0; + uint32_t pendingSignalCount = 0; + // TODO performance counters + + // Member functions + FrameScript_EventObject() : TSHashObject() {}; + const char* GetName(void); +}; + +extern const char* g_glueScriptEvents[41]; +extern const char* g_scriptEvents[722]; + +namespace FrameScript { + extern void* s_mempool; + extern int32_t s_blocksizes[9]; + extern lua_State* s_context; + extern int64_t s_scriptTimeUsed; + extern int32_t s_scriptProfileEnabled; + extern int32_t s_errorHandlerFun; + extern double_t s_scriptTimeDivisor; + extern int32_t s_errorHandlerRef; + extern int32_t s_recursiveTableHash; + extern char const* s_compat_lua; + extern int32_t s_pluralRule; + extern int32_t s_handlingError; + extern TSFixedArray s_scriptEvents; + extern TSHashTable s_scriptEventsHash; +} + +int32_t FrameScript_CompileFunction(const char*, const char*, const char*, CStatus*); + +void FrameScript_CreateEvents(const char*[], uint32_t); + +void FrameScript_Destroy(void); + +void FrameScript_Execute(const char*, const char*, const char*); + +void FrameScript_Execute(int32_t, FrameScript_Object*, int32_t, const char*, FrameScript_EventObject*); + +int32_t FrameScript_ExecuteBuffer(const char*, size_t, const char*, CStatus*, const char*); + +int32_t FrameScript_ExecuteFile(const char*, const char*, MD5_CTX*, CStatus*); + +void FrameScript_Flush(void); + +void FrameScript_GetColor(lua_State* L, int32_t idx, CImVector& color); + +lua_State* FrameScript_GetContext(void); + +const char* FrameScript_GetCurrentObject(lua_State*, int32_t); + +char const* FrameScript_GetText(const char*, int32_t, FRAMESCRIPT_GENDER); + +int32_t FrameScript_GetVariable(const char*, const char**); + +int32_t FrameScript_HandleError(lua_State*); + +int32_t FrameScript_Initialize(int32_t); + +void FrameScript_RegisterFunction(const char*, int32_t (*)(lua_State*)); + +void FrameScript_RegisterScriptEvent(FrameScript_Object*, FrameScript_EventObject*); + +void FrameScript_SetPluralRule(PLURAL_RULE); + +void FrameScript_SignalEvent(uint32_t, const char*, ...); + +void FrameScript_UnregisterScriptEvent(FrameScript_Object*, FrameScript_EventObject*); + +void GlueScriptEventsInitialize(void); + +void ScriptEventsInitialize(void); + +#endif diff --git a/src/ui/FrameScriptInternal.hpp b/src/ui/FrameScriptInternal.hpp new file mode 100644 index 0000000..4665ec7 --- /dev/null +++ b/src/ui/FrameScriptInternal.hpp @@ -0,0 +1,10 @@ +#ifndef UI_FRAME_SCRIPT_INTERNAL_HPP +#define UI_FRAME_SCRIPT_INTERNAL_HPP + +#include "util/Lua.hpp" + +namespace FrameScriptInternal { + extern luaL_Reg extra_funcs[31]; +} + +#endif diff --git a/src/ui/FrameScript_Object.cpp b/src/ui/FrameScript_Object.cpp new file mode 100644 index 0000000..36e9541 --- /dev/null +++ b/src/ui/FrameScript_Object.cpp @@ -0,0 +1,189 @@ +#include "ui/FrameScript_Object.hpp" +#include "util/Lua.hpp" +#include +#include + +int32_t FrameScript_Object::s_objectTypes = 0; + +int32_t FrameScript_Object::CreateScriptMetaTable(lua_State* L, void (*a2)(lua_State* L)) { + lua_createtable(L, 0, 0); + lua_pushstring(L, "__index"); + lua_createtable(L, 0, 0); + + a2(L); + + lua_settable(L, -3); + return luaL_ref(L, LUA_REGISTRYINDEX); +} + +void FrameScript_Object::FillScriptMethodTable(lua_State *L, FrameScript_Method methods[], int32_t count) { + for (int32_t i = 0; i < count; i++) { + lua_pushstring(L, methods[i].name); + lua_pushcclosure(L, methods[i].method, 0); + lua_settable(L, -3); + } +} + +FrameScript_Object::~FrameScript_Object() { + if (this->m_onEvent.luaRef) { + luaL_unref(FrameScript_GetContext(), LUA_REGISTRYINDEX, this->m_onEvent.luaRef); + } +} + +const char* FrameScript_Object::GetDisplayName() { + const char* name = this->GetName(); + return name ? name : ""; +} + +FrameScript_Object::ScriptIx* FrameScript_Object::GetScriptByName(const char* name, FrameScript_Object::ScriptData& data) { + if (!SStrCmpI(name, "OnEvent", STORM_MAX_STR)) { + data.wrapper = "return function(self,event,...) %s end"; + return &this->m_onEvent; + } + + return nullptr; +} + +int32_t FrameScript_Object::RegisterScriptEvent(const char* name) { + auto event = FrameScript::s_scriptEventsHash.Ptr(name); + + if (!event) { + return 0; + } + + if (event->pendingSignalCount) { + auto node = event->unregisterListeners.Head(); + + while (node) { + if (node->listener == this) { + break; + } + + node = node->Next(); + } + + if (node) { + event->unregisterListeners.DeleteNode(node); + } + } + + auto node = event->listeners.Head(); + + while (node) { + if (node->listener == this) { + break; + } + + node = node->Next(); + } + + if (!node) { + FrameScript_RegisterScriptEvent(this, event); + } + + return 1; +} + +void FrameScript_Object::RegisterScriptObject(const char* name) { + auto L = FrameScript_GetContext(); + + if (!this->lua_registered) { + // TODO + // v4 = lua_taintexpected++ == -1; + // v5 = lua_tainted; + // if (!v4 && !lua_taintedclosure) { + // lua_tainted = 0; + // } + + lua_createtable(L, 0, 0); + lua_pushnumber(L, 0.0); + lua_pushlightuserdata(L, this); + lua_rawset(L, -3); + + int32_t metatable = this->GetScriptMetaTable(); + lua_rawgeti(L, LUA_REGISTRYINDEX, metatable); + lua_setmetatable(L, -2); + + this->lua_objectRef = luaL_ref(L, LUA_REGISTRYINDEX); + + // TODO + // if (lua_taintexpected && !lua_taintedclosure) { + // lua_tainted = v5; + // } + // if (--lua_taintexpected <= 0) { + // lua_taintexpected = 0; + // } + } + + this->lua_registered++; + + if (name) { + int32_t v8 = 0; + + lua_pushstring(L, name); + lua_rawget(L, LUA_GLOBALSINDEX); + + if (lua_type(L, -1)) { + v8 = 1; + } + + lua_settop(L, -2); + + if (!v8) { + lua_rawgeti(L, LUA_REGISTRYINDEX, this->lua_objectRef); + lua_pushstring(L, name); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } + } +} + +void FrameScript_Object::RunScript(ScriptIx const& script, int32_t argCount, const char* a4) { + FrameScript_Execute(script.luaRef, this, argCount, a4 ? a4 : script.unk, nullptr); +} + +void FrameScript_Object::UnregisterScriptObject(const char* name) { + auto L = FrameScript_GetContext(); + + if (this->lua_registered > 0) { + this->lua_registered--; + + if (this->lua_registered == 0) { + lua_rawgeti(L, LUA_REGISTRYINDEX, this->lua_objectRef); + lua_pushnumber(L, 0.0); + lua_pushnil(L); + lua_rawset(L, -3); + lua_settop(L, -2); + luaL_unref(L, LUA_REGISTRYINDEX, this->lua_objectRef); + + this->lua_objectRef = -2; + } + } + + if (name) { + lua_pushnil(L); + lua_pushstring(L, name); + lua_insert(L, -2); + lua_rawset(L, LUA_GLOBALSINDEX); + } +} + +FrameScript_Object* FrameScript_GetObjectThis(lua_State* L, int32_t type) { + if (lua_type(L, 1) != LUA_TTABLE) { + luaL_error(L, "Attempt to find 'this' in non-table object (used '.' instead of ':' ?)"); + } + + lua_rawgeti(L, 1, 0); + auto object = static_cast(lua_touserdata(L, -1)); + lua_settop(L, -2); + + if (!object) { + luaL_error(L, "Attempt to find 'this' in non-framescript object"); + } + + if (!object->IsA(type)) { + luaL_error(L, "Wrong object type for member function"); + } + + return object; +} diff --git a/src/ui/FrameScript_Object.hpp b/src/ui/FrameScript_Object.hpp new file mode 100644 index 0000000..a69bf55 --- /dev/null +++ b/src/ui/FrameScript_Object.hpp @@ -0,0 +1,53 @@ +#ifndef UI_FRAME_SCRIPT_OBJECT_HPP +#define UI_FRAME_SCRIPT_OBJECT_HPP + +#include "ui/FrameScript.hpp" +#include + +class FrameScript_Object { + public: + // Structs + struct ScriptData { + const char* wrapper; + }; + + struct ScriptIx { + int32_t luaRef = 0; + const char* unk = nullptr; + }; + + class ScriptFunction { + public: + int32_t luaRef = 0; + }; + + // Static members + static int32_t s_objectTypes; + + // Static functions + static int32_t CreateScriptMetaTable(lua_State* L, void(*a2)(lua_State*)); + static void FillScriptMethodTable(lua_State* L, FrameScript_Method methods[], int32_t count); + + // Member variables + int32_t lua_registered = 0; + int32_t lua_objectRef = -2; + ScriptIx m_onEvent; + + // Virtual member functions + virtual ~FrameScript_Object(); + virtual char* GetName() = 0; + virtual int32_t GetScriptMetaTable() = 0; + virtual ScriptIx* GetScriptByName(const char* name, ScriptData& data); + virtual bool IsA(int32_t type) = 0; + + // Member functions + const char* GetDisplayName(); + int32_t RegisterScriptEvent(const char* name); + void RegisterScriptObject(const char* name); + void RunScript(ScriptIx const& script, int32_t argCount, const char* a4); + void UnregisterScriptObject(const char* name); +}; + +FrameScript_Object* FrameScript_GetObjectThis(lua_State* L, int32_t type); + +#endif diff --git a/src/ui/FrameXML.cpp b/src/ui/FrameXML.cpp new file mode 100644 index 0000000..af8c445 --- /dev/null +++ b/src/ui/FrameXML.cpp @@ -0,0 +1,633 @@ +#include "ui/FrameXML.hpp" +#include "gx/Coordinate.hpp" +#include "ui/CSimpleButton.hpp" +#include "ui/CSimpleCheckbox.hpp" +#include "ui/CSimpleEditBox.hpp" +#include "ui/CSimpleFont.hpp" +#include "ui/CSimpleFrame.hpp" +#include "ui/CSimpleHTML.hpp" +#include "ui/CSimpleModel.hpp" +#include "ui/CSimpleScrollFrame.hpp" +#include "ui/CSimpleSlider.hpp" +#include "util/CStatus.hpp" +#include "util/SFile.hpp" +#include +#include +#include +#include + +int32_t FrameXML::s_debugLevel; +TSHashTable FrameXML::s_factoryHash; +TSHashTable FrameXML::s_nodeHash; + +CSimpleFrame* Create_SimpleButton(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleButton), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleFrame::s_simpleButtonHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleButton(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleCheckButton(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleCheckbox), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleCheckbox::s_simpleCheckboxHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleCheckbox(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleEditBox(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleEditBox), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleEditBox::s_simpleEditBoxHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleEditBox(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleFrame(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleFrame), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleFrame::s_simpleFrameHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleFrame(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleMessageFrame(CSimpleFrame* parent) { + // TODO + + return nullptr; +} + +CSimpleFrame* Create_SimpleModel(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleModel), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleFrame::s_simpleModelHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleModel(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleScrollFrame(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleScrollFrame), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleScrollFrame::s_simpleScrollHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleScrollFrame(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleScrollingMessageFrame(CSimpleFrame* parent) { + // TODO + + return nullptr; +} + +CSimpleFrame* Create_SimpleSlider(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleSlider), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleSlider::s_simpleSliderHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleSlider(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleHTML(CSimpleFrame* parent) { + void* m = SMemAlloc(sizeof(CSimpleHTML), __FILE__, __LINE__, 0); + + // TODO + // void* m = CDataAllocator::GetData(CSimpleHTML::s_simpleHTMLHeap, 0, __FILE__, __LINE__); + + if (m) { + return new (m) CSimpleHTML(parent); + } else { + return nullptr; + } +} + +CSimpleFrame* Create_SimpleStatusBar(CSimpleFrame* parent) { + // TODO + + return nullptr; +} + +CSimpleFrame* Create_SimpleColorSelect(CSimpleFrame* parent) { + // TODO + + return nullptr; +} + +CSimpleFrame* Create_SimpleMovieFrame(CSimpleFrame* parent) { + // TODO + + return nullptr; +} + +XMLNode* FrameXML_AcquireHashNode(const char* name, const char*& tainted, bool& locked) { + HashedNode* hashedNode = FrameXML::s_nodeHash.Ptr(name); + + if (!hashedNode) { + return nullptr; + } + + locked = hashedNode->locked; + tainted = hashedNode->tainted; + hashedNode->locked = true; + + return hashedNode->node; +} + +int32_t FrameXML_CheckSignature(const char* tocPath, const char* a2, const unsigned char* key, unsigned char* digest) { + // TODO + + return 3; +} + +/** + * Find and return the appropriate parent name. + * + * Parent priorities: + * 1. parent attribute on local node + * 2. parent attribute on last inherited node with parent attribute present + */ +const char* FindParent(XMLNode* node) { + const char* parentName = node->GetAttributeByName("parent"); + + if (parentName && *parentName) { + return parentName; + } + + const char* inheritNames = node->GetAttributeByName("inherits"); + + if (!inheritNames || !*inheritNames) { + return nullptr; + } + + char inheritName[1024]; + + do { + SStrTokenize(&inheritNames, inheritName, 0x400u, " ,", 0); + + if (!*inheritName) { + break; + } + + auto hashedNode = FrameXML::s_nodeHash.Ptr(inheritName); + + if (hashedNode) { + auto inheritNode = hashedNode->node; + int32_t v10 = hashedNode->locked; + hashedNode->locked = true; + + if (inheritNode && !v10) { + parentName = inheritNode->GetAttributeByName("parent"); + FrameXML::s_nodeHash.Ptr(inheritName)->locked = false; + } + } + } while (*inheritName); + + return parentName; +} + +CSimpleFrame* FrameXML_CreateFrame(XMLNode* node, CSimpleFrame* parent, CStatus* status) { + if (FrameXML::s_debugLevel > 0) { + const char* name = node->GetAttributeByName("name"); + + if (name && *name) { + status->Add(STATUS_INFO, "-- Creating %s named %s", node->m_name.m_str, name); + } else { + status->Add(STATUS_INFO, "-- Creating unnamed %s", node->m_name.m_str); + } + } + + auto factoryNode = FrameXML::s_factoryHash.Ptr(node->m_name.m_str); + + if (!factoryNode) { + status->Add(STATUS_WARNING, "Unknown frame type: %s", node->m_name.m_str); + return nullptr; + } + + const char* parentName = FindParent(node); + + if (parentName && *parentName) { + int32_t parentType = CSimpleFrame::GetObjectType(); + parent = static_cast(CScriptObject::GetScriptObjectByName(parentName, parentType)); + + if (!parent) { + status->Add(STATUS_WARNING, "Couldn't find frame parent: %s", parentName); + } + } + + CSimpleFrame* frame = factoryNode->m_factory(parent); + + if (frame) { + if (factoryNode->m_unique) { + // TODO + // Delete the factory node + // sub_565030(factoryNode); + // FrameXML::s_factoryHash.InternalDelete(factoryNode); + } + + frame->PreLoadXML(node, status); + frame->LoadXML(node, status); + frame->PostLoadXML(node, status); + + return frame; + } else { + status->Add(STATUS_WARNING, "Unable to create frame type: %s", node->m_name.m_str); + return nullptr; + } +} + +int32_t FrameXML_CreateFrames(const char* tocPath, const char* a2, MD5_CTX* md5, CStatus* status) { + if (!status) { + status = new CStatus; + } + + char v15[260]; + v15[0] = 0; + memset(v15 + 1, 0, 259); + + const char* v5 = tocPath; + + const char* v6 = SStrChrR(tocPath, '\\'); + + if (v6) { + uintptr_t v7 = v6 - tocPath + 1; + uintptr_t v8 = v7; + + if (v7 < 259) { + SStrCopy(v15, tocPath, v7 + 1); + v15[v8] = 0; + } + } + + void* tocBuffer; + size_t tocBytes; + + if (!SFile::Load(nullptr, tocPath, &tocBuffer, &tocBytes, 1, 1, nullptr)) { + status->Add(STATUS_ERROR, "Couldn't open %s", a2); + return 0; + } + + MD5Update(md5, static_cast(tocBuffer), tocBytes); + + const char* tocData = static_cast(tocBuffer); + + // Advance past UTF-8 BOM if present + if (*tocData == static_cast(0xEF) && *(tocData + 1) == static_cast(0xBB) && *(tocData + 2) == static_cast(0xBF)) { + tocData = tocData + 3; + } + + CStatus v21; + char tocLine[1024]; + char tocEntryPath[260]; + + do { + SStrTokenize(&tocData, tocLine, 1024, "\r\n", 0); + + if (!*tocLine) { + break; + } + + if (*tocLine == '#') { + continue; + } + + SStrCopy(tocEntryPath, v15, 260); + SStrPack(tocEntryPath, tocLine, 260); + + size_t v10 = SStrLen(tocEntryPath); + + bool v11 = tocEntryPath[v10 - 1] == ' '; + char* v12 = &tocEntryPath[v10]; + + if (v11) { + do { + if (v12 <= tocEntryPath) { + break; + } + + --v12; + } while (*(v12 - 1) == ' '); + } + + *v12 = 0; + + FrameXML_ProcessFile(tocEntryPath, a2, md5, &v21); + + // TODO + // if (s_progressCallback && s_progressFiles < s_progressTotal) { + // ++s_progressFiles; + // v20 = s_progressTotal; + // v13 = (double)(unsigned int)s_progressFiles / (double)(unsigned int)s_progressTotal; + // s_progressCallback(LODWORD(v13), s_progressParam); + // v5 = a2; + // } + } while (*tocLine); + + // TODO + // XMLTree_Cleanup(); + + SFile::Unload(tocBuffer); + + // TODO + // if (s_debugLevel > 0 || v25 > 0) { + // v21.Prepend(STATUS_INFO, "** Loading table of contents %s", v5); + // } + + // TODO + // status->Add(v21); + + return 1; +} + +void FrameXML_FreeHashNodes() { + FrameXML::s_nodeHash.Clear(); +} + +XMLTree* FrameXML_LoadXML(const char* filePath, MD5_CTX* md5, CStatus* status) { + void* xmlBuffer; + size_t xmlBytes; + + if (SFile::Load(nullptr, filePath, &xmlBuffer, &xmlBytes, 0, 1, nullptr)) { + MD5Update(md5, static_cast(xmlBuffer), xmlBytes); + + XMLTree* tree = XMLTree_Load(static_cast(xmlBuffer), xmlBytes); + + if (!tree) { + status->Add(STATUS_ERROR, "Couldn't parse XML in %s", filePath); + } + + SFile::Unload(xmlBuffer); + + return tree; + } else { + status->Add(STATUS_ERROR, "Couldn't open %s", filePath); + + return nullptr; + } +} + +int32_t FrameXML_ProcessFile(const char* filePath, const char* a2, MD5_CTX* md5, CStatus* status) { + const char* v5 = filePath; + + char v25[260]; + char* i; + char* j; + + // Deal with relative file paths + if (SStrStr(filePath, "..")) { + SStrCopy(v25, filePath, 260); + + for (i = (char*)SStrStr(v25, ".."); i; i = (char*)SStrStr(v25, "..")) { + char v7 = *(i - 1); + + if (v7 != '\\' && v7 != '/') { + break; + } + + char v8 = i[2]; + + if (v8 != '\\' && v8 != '/') { + break; + } + + for (j = i - 2; j >= v25; --j) { + if (*j == '\\') { + break; + } + + if (*j == '/') { + break; + } + } + + SStrCopy(j + 1, i + 3, 0x7FFFFFFF); + } + + filePath = v25; + v5 = v25; + } + + const char* v10 = SStrChrR(v5, '.'); + + // If file ends in .lua, execute it and return + if (v10 && !SStrCmpI(v10, ".lua", 0x7FFFFFFFu)) { + return FrameScript_ExecuteFile(v5, a2, md5, status); + } + + // Assume all other files are xml + XMLTree* tree = FrameXML_LoadXML(v5, md5, status); + + if (!tree) { + return 0; + } + + CStatus unkStatus; + + XMLNode* node = XMLTree_GetRoot(tree)->m_child; + + char v26[271]; + char v27[264]; + + // TODO + // Should come from some kind of Lua headers + const char* lua_tainted = nullptr; + + while (node) { + // + if (!SStrCmpI(node->GetName(), "Include", 0x7FFFFFFFu)) { + const char* v14 = node->GetAttributeByName("file"); + + if (v14) { + const char* v15 = SStrChrR(v5, 92); + + if (v15) { + uint32_t v13 = v15 - v5 + 1; + + if (v13 < 260 ) { + SStrCopy(v27, v5, 260); + v27[v13] = 0; + SStrPack(v27, v14, 260); + } + } else { + SStrCopy(v27, v14, 260); + } + + FrameXML_ProcessFile(v27, a2, md5, &unkStatus); + } else { + unkStatus.Add(STATUS_ERROR, "Element 'Include' without file attribute"); + } + //