diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0607841922..37679e3f56 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -67,6 +67,7 @@ jobs: run: ci/build-appimage.sh - name: Upload artifacts + if: startsWith(github.event.ref, 'refs/tags/v') uses: actions/upload-artifact@v7 with: name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}} @@ -76,24 +77,27 @@ jobs: build-apple: name: Build Apple (${{matrix.name}}) - if: 'false' # TODO enable when CI is free - runs-on: macos-latest + runs-on: [self-hosted, macOS] strategy: fail-fast: false matrix: include: - - name: AppleClang macOS universal + - name: AppleClang macOS arm64 platform: macos - preset: x-macos-ci - artifact_name: macos-appleclang-universal - - name: AppleClang iOS arm64 - platform: ios - preset: x-ios-ci - artifact_name: ios-appleclang-arm64 - - name: AppleClang tvOS arm64 - platform: tvos - preset: x-tvos-ci - artifact_name: tvos-appleclang-arm64 + preset: x-macos-ci-arm64 + artifact_name: macos-appleclang-arm64 + # - name: AppleClang macOS x86_64 + # platform: macos + # preset: x-macos-ci-x86_64 + # artifact_name: macos-appleclang-x86_64 + # - name: AppleClang iOS arm64 + # platform: ios + # preset: x-ios-ci + # artifact_name: ios-appleclang-arm64 + # - name: AppleClang tvOS arm64 + # platform: tvos + # preset: x-tvos-ci + # artifact_name: tvos-appleclang-arm64 steps: - uses: actions/checkout@v6 @@ -101,22 +105,15 @@ jobs: fetch-depth: 0 submodules: recursive - - name: Update Homebrew - if: matrix.platform == 'tvos' - run: | - brew update - brew upgrade --formula - - name: Install dependencies + if: 'false' run: brew install cmake ninja - - name: Install markupsafe - if: matrix.platform == 'tvos' - run: pip3 install --break-system-packages markupsafe - - name: Install Rust iOS target if: matrix.platform == 'ios' - run: rustup target add aarch64-apple-ios + run: | + rustup toolchain install stable + rustup target add aarch64-apple-ios - name: Install Rust tvOS target if: matrix.platform == 'tvos' @@ -124,6 +121,12 @@ jobs: rustup toolchain install nightly rustup target add --toolchain nightly aarch64-apple-tvos + - name: Install Rust x86_64 macOS target + if: endsWith(matrix.preset, 'x86_64') + run: | + rustup toolchain install stable + rustup target add x86_64-apple-darwin + - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.9 @@ -134,6 +137,7 @@ jobs: run: cmake --build --preset ${{matrix.preset}} - name: Upload artifacts + if: startsWith(github.event.ref, 'refs/tags/v') uses: actions/upload-artifact@v7 with: name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}} @@ -199,6 +203,7 @@ jobs: run: cmake --build --preset x-windows-ci-${{matrix.preset}} - name: Upload artifacts + if: startsWith(github.event.ref, 'refs/tags/v') uses: actions/upload-artifact@v7 with: name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}} diff --git a/CMakeLists.txt b/CMakeLists.txt index 1dbaefd7d3..c99133738b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,9 @@ endif() message(STATUS "Dusk version set to ${DUSK_WC_DESCRIBE}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") project(dusk LANGUAGES C CXX VERSION ${DUSK_VERSION_STRING}) +if (APPLE) + enable_language(OBJC) +endif () if (APPLE AND NOT TVOS AND CMAKE_SYSTEM_NAME STREQUAL tvOS) # ios.toolchain.cmake hack for SDL set(TVOS ON) @@ -125,19 +128,37 @@ if (DUSK_MOVIE_SUPPORT) else () set(_jpeg_lib ${_jpeg_install_dir}/lib/libturbojpeg.a) endif () + set(_jpeg_cmake_args + -DCMAKE_INSTALL_PREFIX=${_jpeg_install_dir} + -DENABLE_SHARED=OFF + -DWITH_TURBOJPEG=ON + -DWITH_JAVA=OFF + ) + if (CMAKE_TOOLCHAIN_FILE) + get_filename_component(_jpeg_toolchain_file "${CMAKE_TOOLCHAIN_FILE}" ABSOLUTE BASE_DIR "${CMAKE_SOURCE_DIR}") + list(APPEND _jpeg_cmake_args -DCMAKE_TOOLCHAIN_FILE=${_jpeg_toolchain_file}) + endif () + set(_jpeg_passthrough_vars + CMAKE_BUILD_TYPE + CMAKE_C_COMPILER + CMAKE_C_COMPILER_LAUNCHER + CMAKE_MAKE_PROGRAM + CMAKE_MSVC_RUNTIME_LIBRARY + CMAKE_OSX_ARCHITECTURES + DEPLOYMENT_TARGET + ENABLE_ARC + ENABLE_BITCODE + PLATFORM + ) + foreach(_var IN LISTS _jpeg_passthrough_vars) + if (DEFINED ${_var}) + list(APPEND _jpeg_cmake_args -D${_var}=${${_var}}) + endif () + endforeach () ExternalProject_Add(libjpeg-turbo-ext URL https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.1.0.tar.gz URL_HASH SHA256=35fec2e1ddfb05ecf6d93e50bc57c1e54bc81c16d611ddf6eff73fff266d8285 - CMAKE_ARGS - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_INSTALL_PREFIX=${_jpeg_install_dir} - -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} - -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} - -DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER} - -DCMAKE_MSVC_RUNTIME_LIBRARY=${CMAKE_MSVC_RUNTIME_LIBRARY} - -DENABLE_SHARED=OFF - -DWITH_TURBOJPEG=ON - -DWITH_JAVA=OFF + CMAKE_ARGS ${_jpeg_cmake_args} BUILD_BYPRODUCTS ${_jpeg_lib} ) file(MAKE_DIRECTORY ${_jpeg_install_dir}/include) @@ -249,7 +270,7 @@ include(files.cmake) # TODO: version handling for res includes set(DUSK_BUNDLE_NAME Dusk) -set(DUSK_BUNDLE_IDENTIFIER dev.decomp.dusk) +set(DUSK_BUNDLE_IDENTIFIER dev.twilitrealm.dusk) set(DUSK_COMPANY_NAME "Twilit Realm") set(DUSK_FILE_DESCRIPTION "Dusk") set(DUSK_PRODUCT_NAME "Dusk") @@ -277,7 +298,7 @@ set(GAME_INCLUDE_DIRS ${CMAKE_BINARY_DIR}) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd - aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient) + aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt) list(APPEND GAME_LIBS libzstd_static) @@ -362,12 +383,14 @@ if (ANDROID) target_link_options(dusk PRIVATE "-Wl,-u,SDL_main") endif () -add_custom_command(TARGET dusk POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - "${CMAKE_SOURCE_DIR}/res" - "$/res" - COMMENT "Copying resources" -) +if (NOT APPLE) + add_custom_command(TARGET dusk POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${CMAKE_SOURCE_DIR}/res" + "$/res" + COMMENT "Copying resources" + ) +endif () if (WIN32) set(DUSK_WINDOWS_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/windows) @@ -401,44 +424,49 @@ endif () if (APPLE) if (IOS) set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios) - set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in) - file(GLOB_RECURSE DUSK_RESOURCE_FILES "${DUSK_RESOURCE_DIR}/Base.lproj/*") - endif () - if (IOS OR TVOS) - target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES}) - foreach (FILE ${DUSK_RESOURCE_FILES}) - file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE}) - get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) - set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") - endforeach () - set_target_properties( - dusk PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} - MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} - MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} - OUTPUT_NAME dusk - XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" - XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES" - ) - if (CMAKE_GENERATOR STREQUAL "Xcode") - set_target_properties(dusk PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST}) - elseif (DEFINED DUSK_INFO_PLIST) - set(MACOSX_BUNDLE_EXECUTABLE_NAME dusk) - set(MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER}) - set(MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME}) - set(MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING}) - set(MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING}) - set(DUSK_GENERATED_INFO_PLIST ${CMAKE_CURRENT_BINARY_DIR}/dusk.Info.plist) - configure_file(${DUSK_INFO_PLIST} ${DUSK_GENERATED_INFO_PLIST}) - add_custom_command( - TARGET dusk POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DUSK_GENERATED_INFO_PLIST} $/Info.plist - VERBATIM - ) - endif () + elseif (TVOS) + set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/tvos) + else () + set(DUSK_RESOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/platforms/macos) endif () + set(DUSK_INFO_PLIST ${DUSK_RESOURCE_DIR}/Info.plist.in) + file(GLOB_RECURSE DUSK_RESOURCE_FILES + "${DUSK_RESOURCE_DIR}/Assets.car" + "${DUSK_RESOURCE_DIR}/Base.lproj/*" + "${DUSK_RESOURCE_DIR}/Dusk.icns") + file(GLOB_RECURSE DUSK_APP_RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/res/*") + target_sources(dusk PRIVATE ${DUSK_RESOURCE_FILES}) + target_sources(dusk PRIVATE ${DUSK_APP_RESOURCE_FILES}) + foreach (FILE ${DUSK_RESOURCE_FILES}) + file(RELATIVE_PATH NEW_FILE "${DUSK_RESOURCE_DIR}" ${FILE}) + get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) + set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") + endforeach () + foreach (FILE ${DUSK_APP_RESOURCE_FILES}) + file(RELATIVE_PATH NEW_FILE "${CMAKE_CURRENT_SOURCE_DIR}" ${FILE}) + get_filename_component(NEW_FILE_PATH ${NEW_FILE} DIRECTORY) + set_property(SOURCE ${FILE} PROPERTY MACOSX_PACKAGE_LOCATION "Resources/${NEW_FILE_PATH}") + endforeach () + set_target_properties( + dusk PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_BUNDLE_NAME ${DUSK_BUNDLE_NAME} + MACOSX_BUNDLE_GUI_IDENTIFIER ${DUSK_BUNDLE_IDENTIFIER} + MACOSX_BUNDLE_BUNDLE_VERSION ${DUSK_VERSION_STRING} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${DUSK_SHORT_VERSION_STRING} + MACOSX_BUNDLE_INFO_PLIST ${DUSK_INFO_PLIST} + OUTPUT_NAME Dusk + XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "YES" + XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "YES" + ) +endif () + +if (IOS) + find_library(UIKIT_FRAMEWORK UIKit REQUIRED) + find_library(UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK UniformTypeIdentifiers REQUIRED) + target_sources(dusk PRIVATE src/dusk/ios/FileSelectDialog.m) + set_source_files_properties(src/dusk/ios/FileSelectDialog.m PROPERTIES COMPILE_FLAGS -fobjc-arc) + target_link_libraries(dusk PRIVATE ${UIKIT_FRAMEWORK} ${UNIFORM_TYPE_IDENTIFIERS_FRAMEWORK}) endif () include(extern/aurora/cmake/AuroraCopyRuntimeDLLs.cmake) @@ -487,7 +515,9 @@ if (TARGET crashpad_handler) endif () install(TARGETS ${BINARY_TARGETS} ${EXTRA_TARGETS} DESTINATION ${CMAKE_INSTALL_PREFIX}) aurora_install_runtime_dlls(dusk ${CMAKE_INSTALL_PREFIX}) -install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX}) +if (NOT APPLE) + install(DIRECTORY ${CMAKE_SOURCE_DIR}/res DESTINATION ${CMAKE_INSTALL_PREFIX}) +endif () if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) set(DEBUG_FILES_LIST "") foreach (target IN LISTS BINARY_TARGETS EXTRA_TARGETS) @@ -509,18 +539,22 @@ if (CMAKE_BUILD_TYPE STREQUAL Debug OR CMAKE_BUILD_TYPE STREQUAL RelWithDebInfo) endif () list(APPEND DEBUG_FILES_LIST "${output_name}") endforeach () - if (WIN32) - list(TRANSFORM DEBUG_FILES_LIST APPEND ".pdb") - list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) - install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND 7z a -t7z \"${CMAKE_INSTALL_PREFIX}/debug.7z\" ${DEBUG_FILES})") - elseif (APPLE) - list(TRANSFORM DEBUG_FILES_LIST APPEND ".dSYM") - list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) - install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar acfv \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") - elseif (UNIX) - list(TRANSFORM DEBUG_FILES_LIST APPEND ".dbg") - list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) - install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar -I \"xz -9 -T0\" -cvf \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + # This is a terrible hack to only run this on CI + # until I turn this into a script or something + if(DEFINED ENV{GITHUB_ENV}) + if (WIN32) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".pdb") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND 7z a -t7z \"${CMAKE_INSTALL_PREFIX}/debug.7z\" ${DEBUG_FILES})") + elseif (APPLE) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".dSYM") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar acfv \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + elseif (UNIX) + list(TRANSFORM DEBUG_FILES_LIST APPEND ".dbg") + list(JOIN DEBUG_FILES_LIST " " DEBUG_FILES) + install(CODE "execute_process(WORKING_DIRECTORY \"${CMAKE_INSTALL_PREFIX}\" COMMAND tar -I \"xz -9 -T0\" -cvf \"${CMAKE_INSTALL_PREFIX}/debug.tar.xz\" ${DEBUG_FILES})") + endif () endif () endif () foreach (target IN LISTS BINARY_TARGETS) diff --git a/CMakePresets.json b/CMakePresets.json index 44e75973b9..90307aa061 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -380,7 +380,40 @@ "inherits": [ "macos-default-relwithdebinfo", "ci" - ] + ], + "cacheVariables": { + "AURORA_DAWN_PROVIDER": "vendor", + "AURORA_NOD_PROVIDER": "vendor", + "CMAKE_DISABLE_FIND_PACKAGE_PkgConfig": { + "type": "BOOL", + "value": true + }, + "CMAKE_OSX_DEPLOYMENT_TARGET": "11.0", + "CMAKE_IGNORE_PREFIX_PATH": "/opt/homebrew", + "DUSK_MOVIE_SUPPORT": { + "type": "BOOL", + "value": false + } + } + }, + { + "name": "x-macos-ci-arm64", + "inherits": [ + "x-macos-ci" + ], + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "arm64" + } + }, + { + "name": "x-macos-ci-x86_64", + "inherits": [ + "x-macos-ci" + ], + "cacheVariables": { + "CMAKE_OSX_ARCHITECTURES": "x86_64", + "Rust_CARGO_TARGET": "x86_64-apple-darwin" + } }, { "name": "x-ios-ci", @@ -389,7 +422,11 @@ ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", + "DUSK_MOVIE_SUPPORT": { + "type": "BOOL", + "value": false + } } }, { @@ -399,7 +436,11 @@ ], "cacheVariables": { "CMAKE_C_COMPILER_LAUNCHER": "sccache", - "CMAKE_CXX_COMPILER_LAUNCHER": "sccache" + "CMAKE_CXX_COMPILER_LAUNCHER": "sccache", + "DUSK_MOVIE_SUPPORT": { + "type": "BOOL", + "value": false + } } }, { @@ -556,10 +597,19 @@ ] }, { - "name": "x-macos-ci", - "configurePreset": "x-macos-ci", - "description": "(Internal) macOS CI", - "displayName": "(Internal) macOS CI", + "name": "x-macos-ci-arm64", + "configurePreset": "x-macos-ci-arm64", + "description": "(Internal) macOS CI arm64", + "displayName": "(Internal) macOS CI arm64", + "targets": [ + "install" + ] + }, + { + "name": "x-macos-ci-x86_64", + "configurePreset": "x-macos-ci-x86_64", + "description": "(Internal) macOS CI x86_64", + "displayName": "(Internal) macOS CI x86_64", "targets": [ "install" ] @@ -567,8 +617,8 @@ { "name": "x-ios-ci", "configurePreset": "x-ios-ci", - "description": "(Internal) iOS CI", - "displayName": "(Internal) iOS CI", + "description": "(Internal) iOS CI arm64", + "displayName": "(Internal) iOS CI arm64", "targets": [ "install" ] @@ -576,8 +626,8 @@ { "name": "x-tvos-ci", "configurePreset": "x-tvos-ci", - "description": "(Internal) tvOS CI", - "displayName": "(Internal) tvOS CI", + "description": "(Internal) tvOS CI arm64", + "displayName": "(Internal) tvOS CI arm64", "targets": [ "install" ] diff --git a/extern/aurora b/extern/aurora index aa83f6d915..c21a7f75ab 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit aa83f6d91545c304b8f62a2965c7dcd1ec8b511b +Subproject commit c21a7f75ab1a2fd8ea896fa45b1005b2f903de77 diff --git a/files.cmake b/files.cmake index e91415b3be..a65b7572c4 100644 --- a/files.cmake +++ b/files.cmake @@ -1342,6 +1342,8 @@ set(DUSK_FILES src/dusk/endian.cpp src/dusk/extras.c src/dusk/extras.cpp + src/dusk/file_select.cpp + src/dusk/file_select.hpp src/dusk/frame_interpolation.cpp src/dusk/globals.cpp src/dusk/gyro.cpp diff --git a/include/m_Do/m_Do_graphic.h b/include/m_Do/m_Do_graphic.h index c6b22654fa..f921912656 100644 --- a/include/m_Do/m_Do_graphic.h +++ b/include/m_Do/m_Do_graphic.h @@ -125,8 +125,15 @@ public: #if TARGET_PC static f32 hudAspectScaleDown; static f32 hudAspectScaleUp; - static f32 ScaleHUDXLeft(f32 baseX) { return getMinXF() + baseX; } - static f32 ScaleHUDXRight(f32 baseX) { return -getMinXF() + baseX; } + static void updateSafeAreaBounds(); + static f32 getSafeMinXF() { return m_safeMinXF; } + static f32 getSafeMinYF() { return m_safeMinYF; } + static f32 getSafeWidthF() { return m_safeWidthF; } + static f32 getSafeHeightF() { return m_safeHeightF; } + static f32 getSafeMaxXF() { return m_safeMaxXF; } + static f32 getSafeMaxYF() { return m_safeMaxYF; } + static f32 ScaleHUDXLeft(f32 baseX) { return getSafeMinXF() + baseX; } + static f32 ScaleHUDXRight(f32 baseX) { return getSafeMaxXF() - FB_WIDTH_BASE + baseX; } #endif static void setBlureMtx(const Mtx m) { @@ -369,6 +376,15 @@ public: static int m_height; static f32 m_heightF; static f32 m_widthF; + + #if TARGET_PC + static f32 m_safeMinXF; + static f32 m_safeMinYF; + static f32 m_safeMaxXF; + static f32 m_safeMaxYF; + static f32 m_safeWidthF; + static f32 m_safeHeightF; + #endif #endif }; diff --git a/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp b/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp index dc0c5c56e8..0ee7d53fb7 100644 --- a/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp +++ b/libs/JSystem/src/JStudio/JStudio_JStage/object-actor.cpp @@ -314,10 +314,17 @@ void JStudio_JStage::TAdaptor_actor::getJSG_SRT_(JStudio::TControl const* pContr } void JStudio_JStage::TAdaptor_actor::TVVOutput_ANIMATION_FRAME_::operator()( - f32 param_1, JStudio::TAdaptor* adaptor) const { + f32 param_1, JStudio::TAdaptor* adaptor) const { +#if TARGET_PC + TAdaptor_actor* actor_adaptor = static_cast(adaptor); + JStage::TActor* actor = actor_adaptor->get_pJSG_(); + // field_0x8 is always hardcoded to either 305 or 309 + u32 idx = (field_0x8 == 305) ? actor_adaptor->field_0x130 : actor_adaptor->field_0x134; +#else JStage::TActor* actor = static_cast(adaptor)->get_pJSG_(); // not sure what this bit is u32 idx = *(u32*)(((uintptr_t)adaptor - 1) + field_0x8); +#endif u8 idx_lowBytes = idx; u8 idx_highBytes = idx >> 8; diff --git a/platforms/android/app/src/main/AndroidManifest.xml b/platforms/android/app/src/main/AndroidManifest.xml index d09b0d2785..e8c2bb29a8 100644 --- a/platforms/android/app/src/main/AndroidManifest.xml +++ b/platforms/android/app/src/main/AndroidManifest.xml @@ -15,7 +15,7 @@ android:allowBackup="true" android:hardwareAccelerated="true" android:appCategory="game" - android:icon="@android:drawable/sym_def_app_icon" + android:icon="@mipmap/icon" android:label="@string/app_name" android:theme="@android:style/Theme.NoTitleBar" android:enableOnBackInvokedCallback="false"> diff --git a/platforms/android/app/src/main/res/mipmap/icon.png b/platforms/android/app/src/main/res/mipmap/icon.png new file mode 100644 index 0000000000..05e11c9b41 Binary files /dev/null and b/platforms/android/app/src/main/res/mipmap/icon.png differ diff --git a/platforms/ios/Assets.car b/platforms/ios/Assets.car new file mode 100644 index 0000000000..5087cfe981 Binary files /dev/null and b/platforms/ios/Assets.car differ diff --git a/platforms/macos/Assets.car b/platforms/macos/Assets.car new file mode 100644 index 0000000000..b835eb03e0 Binary files /dev/null and b/platforms/macos/Assets.car differ diff --git a/platforms/macos/Dusk.icns b/platforms/macos/Dusk.icns new file mode 100644 index 0000000000..c684d8194f Binary files /dev/null and b/platforms/macos/Dusk.icns differ diff --git a/platforms/macos/Info.plist.in b/platforms/macos/Info.plist.in index c91435dc2e..b7bc7b706c 100644 --- a/platforms/macos/Info.plist.in +++ b/platforms/macos/Info.plist.in @@ -13,7 +13,9 @@ CFBundleExecutable ${MACOSX_BUNDLE_EXECUTABLE_NAME} CFBundleIconFile - mainicon.icns + Dusk + CFBundleIconName + Dusk CFBundleIdentifier ${MACOSX_BUNDLE_GUI_IDENTIFIER} CFBundleName diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index 6787c53de7..e6943baf96 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -4258,6 +4258,12 @@ int daAlink_c::createHeap() { return 0; } +#if TARGET_PC + // lets try to zero-initialize the arrays instead of having garbage values + std::memset(sp1C, 0, sizeof(J3DTransformInfo) * sp38); + std::memset(sp30, 0, sizeof(Quaternion) * sp38); +#endif + field_0x2060 = JKR_NEW mDoExt_MtxCalcOldFrame(sp1C, sp30); if (field_0x2060 == NULL) { return 0; diff --git a/src/d/actor/d_a_mant.cpp b/src/d/actor/d_a_mant.cpp index bcd7f05986..282502419f 100644 --- a/src/d/actor/d_a_mant.cpp +++ b/src/d/actor/d_a_mant.cpp @@ -12,6 +12,7 @@ #if TARGET_PC #include "dusk/dvd_asset.hpp" +#include "dusk/frame_interpolation.h" static u8* l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x1C00, 0x4000), true); return buf; } static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x5C00, 0x4000), true); return buf; } static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", 0x9C00, 0x60), true); return buf; } @@ -267,6 +268,37 @@ static void* tex_d[2] = { static char lbl_277_bss_0; +#if TARGET_PC +static void mant_build_anchor_frame(const cXyz& anchor_a, const cXyz& anchor_b, Mtx out) { + cXyz axis_x = anchor_b - anchor_a; + if (!axis_x.normalizeRS()) { + axis_x = cXyz::BaseX; + } + + cXyz helper = fabsf(axis_x.y) > 0.95f ? cXyz::BaseZ : cXyz::BaseY; + cXyz axis_z = axis_x.getCrossProduct(helper); + if (!axis_z.normalizeRS()) { + axis_z = cXyz::BaseZ; + } + + cXyz axis_y = axis_z.getCrossProduct(axis_x); + if (!axis_y.normalizeRS()) { + axis_y = cXyz::BaseY; + } + + const cXyz center = anchor_a + ((anchor_b - anchor_a) * 0.5f); + + const cXyz col[3] = { axis_x, axis_y, axis_z }; + const f32 t[3] = { center.x, center.y, center.z }; + for (int r = 0; r < 3; ++r) { + out[r][0] = (&col[0].x)[r]; + out[r][1] = (&col[1].x)[r]; + out[r][2] = (&col[2].x)[r]; + out[r][3] = t[r]; + } +} +#endif + void daMant_packet_c::draw() { #if TARGET_PC void* image = l_Egnd_mantTEX; @@ -290,8 +322,72 @@ void daMant_packet_c::draw() { GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_NRM, GX_CLR_RGB, GX_F32, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_F32, 0); +#if TARGET_PC + cXyz* draw_pos = &this->mNrm[0][0]; + { + const u8 curr_buffer = this->field_0x74; + const cXyz* curr_pos = &this->mPos[curr_buffer][0]; + const MtxP curr_frame = curr_buffer == 0 ? this->mMtx : this->mMtx2; + + Mtx curr_frame_inverse; + MTXInverse(curr_frame, curr_frame_inverse); + + const u8 prev_buffer = curr_buffer ^ 1; + const cXyz* prev_pos = &this->mPos[prev_buffer][0]; + Mtx prev_frame_inverse; + MTXInverse(prev_buffer == 0 ? this->mMtx : this->mMtx2, prev_frame_inverse); + + Mtx presented_frame; + MTXCopy(curr_frame, presented_frame); + + mant_class* mant_p = reinterpret_cast(reinterpret_cast(this) - offsetof(mant_class, field_0x0570)); + if (mant_p != NULL) { + b_gnd_class* parent = (b_gnd_class*)fopAcM_SearchByID(mant_p->parentActorID); + if (parent != NULL && parent->mpModelMorf != NULL) { + J3DModel* model = parent->mpModelMorf->getModel(); + if (model != NULL) { + MtxP src34 = model->getAnmMtx(34); + MtxP src25 = model->getAnmMtx(25); + Mtx joint_34_scratch; + Mtx joint_25_scratch; + MtxP joint_34 = dusk::frame_interp::lookup_replacement(src34, joint_34_scratch) ? joint_34_scratch : src34; + MtxP joint_25 = dusk::frame_interp::lookup_replacement(src25, joint_25_scratch) ? joint_25_scratch : src25; + + cXyz presented_anchor_a; + cXyz presented_anchor_b; + cXyz local_offset; + + MTXCopy(joint_34, *calc_mtx); + local_offset.set(10.0f, 5.0f, -17.0f); + MtxPosition(&local_offset, &presented_anchor_a); + + MTXCopy(joint_25, *calc_mtx); + local_offset.set(10.0f, 5.0f, 17.0f); + MtxPosition(&local_offset, &presented_anchor_b); + + mant_build_anchor_frame(presented_anchor_a, presented_anchor_b, presented_frame); + } + } + } + + const f32 step = dusk::frame_interp::get_interpolation_step(); + for (int i = 0; i < 169; ++i) { + cXyz curr_local; + MTXMultVec(curr_frame_inverse, &curr_pos[i], &curr_local); + + cXyz prev_local; + MTXMultVec(prev_frame_inverse, &prev_pos[i], &prev_local); + cXyz local = prev_local + ((curr_local - prev_local) * step); + + MTXMultVec(presented_frame, &local, &draw_pos[i]); + } + } + GXSETARRAY(GX_VA_POS, draw_pos, sizeof(mNrm[0]), 12, true); + GXSETARRAY(GX_VA_NRM, &l_normal, sizeof(l_normal), 12, false); +#else GXSETARRAY(GX_VA_POS, this->getPos(), sizeof(mPos[0]), 12, true); GXSETARRAY(GX_VA_NRM, this->getNrm(), sizeof(mNrm[0]), 12, true); +#endif GXSETARRAY(GX_VA_TEX0, &l_texCoord, sizeof(l_texCoord), 8, false); // TODO: set to true when converted to float literals GXSetZCompLoc(0); @@ -329,9 +425,14 @@ void daMant_packet_c::draw() { GXSetCullMode(GX_CULL_BACK); - GXLoadPosMtxImm(this->mMtx, GX_PNMTX0); Mtx MStack_54; +#if TARGET_PC + GXLoadPosMtxImm(j3dSys.getViewMtx(), GX_PNMTX0); + cMtx_inverseTranspose(j3dSys.getViewMtx(), MStack_54); +#else + GXLoadPosMtxImm(this->mMtx, GX_PNMTX0); cMtx_inverseTranspose(this->mMtx, MStack_54); +#endif GXLoadNrmMtxImm(MStack_54, GX_PNMTX0); GXCallDisplayList(l_Egnd_mantDL, 0x3e0); @@ -347,8 +448,13 @@ void daMant_packet_c::draw() { GXSetTevKColor(GX_KCOLOR0, COMPOUND_LITERAL(GXColor){0, 0, 0, 0}); GXSetCullMode(GX_CULL_FRONT); +#if TARGET_PC + GXLoadPosMtxImm(j3dSys.getViewMtx(), GX_PNMTX0); + cMtx_inverseTranspose(j3dSys.getViewMtx(), MStack_54); +#else GXLoadPosMtxImm(this->mMtx2, GX_PNMTX0); cMtx_inverseTranspose(this->mMtx2, MStack_54); +#endif GXLoadNrmMtxImm(MStack_54, GX_PNMTX0); GXCallDisplayList(l_Egnd_mantDL, 0x3e0); @@ -360,11 +466,13 @@ void daMant_packet_c::draw() { static int daMant_Draw(mant_class* i_this) { g_env_light.settingTevStruct(0, &i_this->current.pos, &i_this->tevStr); +#if !TARGET_PC MtxTrans(0.0f, 0.0f, 0.0f, 0.0f); cMtx_concat(j3dSys.getViewMtx(), *calc_mtx, i_this->field_0x0570.getMtx()); cMtx_concat(j3dSys.getViewMtx(), *calc_mtx, i_this->field_0x0570.getMtx2()); +#endif i_this->field_0x0570.setTevStr(&i_this->tevStr); @@ -635,8 +743,13 @@ static void mant_v_calc(mant_class* i_this) { } static void mant_move(mant_class* i_this) { +#if TARGET_PC + u8 uVar1 = i_this->field_0x0570.field_0x74 ^ 1; + cXyz* pcVar5 = &i_this->field_0x0570.mPos[uVar1][0]; +#else u8 uVar1 = i_this->field_0x0570.field_0x74; cXyz* pcVar5 = i_this->field_0x0570.getPos(); +#endif mant_v_calc(i_this); for (int i = 0; i < 13; i++) { for (int j = 0; j < 13; j++) { @@ -644,7 +757,12 @@ static void mant_move(mant_class* i_this) { } } +#if TARGET_PC + mant_build_anchor_frame(i_this->field_0x3928[0], i_this->field_0x3928[1], uVar1 == 0 ? i_this->field_0x0570.mMtx : i_this->field_0x0570.mMtx2); + i_this->field_0x0570.field_0x74 = uVar1; +#else DCStoreRangeNoSync(i_this->field_0x0570.getPos(), 0x7ec); +#endif } static int mant_cut_type; diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index ac28e46838..c3fcc806d5 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -147,7 +147,7 @@ void dBrightCheck_c::modeMove() { void dBrightCheck_c::brightCheckWide() { // Main Canvas mBrightCheck.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mBrightCheck.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mBrightCheck.Scr->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); // Right Square mBrightCheck.Scr->search(MULTI_CHAR('fuchi_1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); diff --git a/src/d/d_file_select.cpp b/src/d/d_file_select.cpp index be40b13d1c..550c5b3e52 100644 --- a/src/d/d_file_select.cpp +++ b/src/d/d_file_select.cpp @@ -3776,7 +3776,7 @@ bool dFile_select_c::yesnoWakuAlpahAnm(u8 param_1) { #if TARGET_PC void dFile_select_c::fileSelectWide() { mYnSel.ScrYn->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mYnSel.ScrYn->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mYnSel.ScrYn->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); mYnSel.ScrYn->search(MULTI_CHAR('w_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); mYnSel.ScrYn->search(MULTI_CHAR('f_no_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -3784,7 +3784,7 @@ void dFile_select_c::fileSelectWide() { mYnSel.ScrYn->search(MULTI_CHAR('f_yes_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); m3mSel.Scr3m->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - m3mSel.Scr3m->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + m3mSel.Scr3m->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); m3mSel.Scr3m->search(MULTI_CHAR('w_sta'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); m3mSel.Scr3m->search(MULTI_CHAR('f_sta'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -3794,7 +3794,7 @@ void dFile_select_c::fileSelectWide() { m3mSel.Scr3m->search(MULTI_CHAR('f_cop_t'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); fileSel.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - fileSel.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + fileSel.Scr->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); fileSel.Scr->search(MULTI_CHAR('t_for'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); fileSel.Scr->search(MULTI_CHAR('t_for1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -5584,7 +5584,13 @@ void dFile_select3D_c::createMirrorModel() { void dFile_select3D_c::toItem3Dpos(f32 param_0, f32 param_1, f32 param_2, cXyz* param_3) { Mtx adStack_98; Mtx auStack_c8; +#if TARGET_PC + param_0 = + (2.0f * ((param_0 - mDoGph_gInf_c::getSafeMinXF()) / mDoGph_gInf_c::getSafeWidthF()) - + 1.0f); +#else param_0 = (2.0f * ((param_0 - mDoGph_gInf_c::getMinXF()) / mDoGph_gInf_c::getWidthF()) - 1.0f); +#endif param_1 = (2.0f * ((param_1 - -100.0f) / FB_HEIGHT_BASE) - 1.0f); calcViewMtx(adStack_98); cMtx_inverse(adStack_98, auStack_c8); @@ -5601,4 +5607,3 @@ void dFile_select3D_c::calcViewMtx(Mtx param_0) { cXyz pos2(0.0f, 1.0f, 0.0f); cMtx_lookAt(param_0, &pos1, &cXyz::Zero, &pos2, 0); } - diff --git a/src/d/d_menu_collect.cpp b/src/d/d_menu_collect.cpp index c7e5bb7414..7ae416c4be 100644 --- a/src/d/d_menu_collect.cpp +++ b/src/d/d_menu_collect.cpp @@ -99,7 +99,7 @@ dMenu_Collect2D_c::~dMenu_Collect2D_c() { void dMenu_Collect2D_c::menuCollectWide() { // Main Canvas mpScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mpScreen->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mpScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); // Pieces of Heart mpScreen->search(MULTI_CHAR('heart_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); @@ -182,7 +182,7 @@ void dMenu_Collect2D_c::_create() { } #if TARGET_PC - mpScreenIcon->translate(-mDoGph_gInf_c::getMinXF(), 0.0f); + mpScreenIcon->translate(-mDoGph_gInf_c::getSafeMinXF(), 0.0f); #endif dPaneClass_showNullPane(mpScreenIcon); @@ -2706,8 +2706,14 @@ void dMenu_Collect3D_c::setupItem3D(Mtx param_0) { void dMenu_Collect3D_c::toItem3Dpos(f32 param_0, f32 param_1, f32 param_2, cXyz* param_3) { Mtx adStack_98; Mtx auStack_c8; +#if TARGET_PC + param_0 = + (2.0f * ((param_0 - mDoGph_gInf_c::getSafeMinXF()) / mDoGph_gInf_c::getSafeWidthF()) - + 1.0f); +#else param_0 = (2.0f * ((param_0 - mDoGph_gInf_c::getMinXF()) / mDoGph_gInf_c::getWidthF()) - 1.0f); +#endif param_1 = (2.0f * ((param_1 - -100.0f) / FB_HEIGHT_BASE) - 1.0f); calcViewMtx(adStack_98); MTXInverse(adStack_98, auStack_c8); diff --git a/src/d/d_menu_save.cpp b/src/d/d_menu_save.cpp index cbff78c659..557095915e 100644 --- a/src/d/d_menu_save.cpp +++ b/src/d/d_menu_save.cpp @@ -2779,7 +2779,7 @@ void dMenu_save_c::_draw() { #if TARGET_PC void dMenu_save_c::menuSaveWide() { mSaveSel.Scr->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); - mSaveSel.Scr->translate(mDoGph_gInf_c::getMinXF(), 0.0f); + mSaveSel.Scr->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); mSaveSel.Scr->search(MULTI_CHAR('t_for'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); mSaveSel.Scr->search(MULTI_CHAR('t_for1'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); diff --git a/src/dusk/file_select.cpp b/src/dusk/file_select.cpp new file mode 100644 index 0000000000..fcda233c56 --- /dev/null +++ b/src/dusk/file_select.cpp @@ -0,0 +1,91 @@ +#include "file_select.hpp" + +#include + +#include +#include + +#if defined(__APPLE__) +#include +#endif + +#if defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST +#define USE_IOS_DIALOG 1 +#include "ios/FileSelectDialog.h" +#else +#define USE_IOS_DIALOG 0 +#endif + +namespace dusk { +namespace { + +#if USE_IOS_DIALOG +struct IOSDialogCallbackState { + FileCallback callback; + void* userdata; +}; + +void onIOSDialogFinished(void* userdata, const char* path, const char* error) { + std::unique_ptr state(static_cast(userdata)); + + if (error != nullptr) { + state->callback(state->userdata, nullptr, error); + return; + } + + if (path == nullptr) { + state->callback(state->userdata, nullptr, nullptr); + return; + } + + state->callback(state->userdata, path, nullptr); +} +#else +struct SDLDialogCallbackState { + FileCallback callback; + void* userdata; +}; + +void onSDLDialogFinished(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { + std::unique_ptr state(static_cast(userdata)); + + if (filelist == nullptr) { + state->callback(state->userdata, nullptr, SDL_GetError()); + return; + } + + if (filelist[0] == nullptr) { + state->callback(state->userdata, nullptr, nullptr); + return; + } + + state->callback(state->userdata, filelist[0], nullptr); +} +#endif + +} // namespace + +void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window, + const SDL_DialogFileFilter* filters, int nfilters, const char* default_location, + bool allow_many) { + if (callback == nullptr) { + return; + } + +#if USE_IOS_DIALOG + auto state = std::make_unique(); + state->callback = callback; + state->userdata = userdata; + + Dusk_iOS_ShowFileSelect(&onIOSDialogFinished, state.release(), window, filters, nfilters, + default_location, allow_many); +#else + auto state = std::make_unique(); + state->callback = callback; + state->userdata = userdata; + + SDL_ShowOpenFileDialog(&onSDLDialogFinished, state.release(), window, filters, nfilters, + default_location, allow_many); +#endif +} +} // namespace dusk diff --git a/src/dusk/file_select.hpp b/src/dusk/file_select.hpp new file mode 100644 index 0000000000..175c5aa086 --- /dev/null +++ b/src/dusk/file_select.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +struct SDL_Window; + +namespace dusk { + +using FileCallback = void (*)(void* userdata, const char* path, const char* error); + +void ShowFileSelect(FileCallback callback, void* userdata, SDL_Window* window, + const SDL_DialogFileFilter* filters, int nfilters, const char* default_location, + bool allow_many); + +} // namespace dusk diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index d030ca9119..856b97f5ca 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -3,23 +3,24 @@ #include #include #include -#include -#include "fmt/format.h" +#define IMGUI_DEFINE_MATH_OPERATORS #include "imgui.h" #include +#include "fmt/format.h" #include "ImGuiConsole.hpp" - #include "JSystem/JUtility/JUTGamePad.h" +#include "SDL3/SDL_events.h" #include "SDL3/SDL_mouse.h" -#include "m_Do/m_Do_controller_pad.h" -#include "m_Do/m_Do_main.h" +#include "aurora/lib/window.hpp" +#include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" +#include "dusk/dusk.h" #include "dusk/main.h" #include "dusk/settings.h" -#include "dusk/audio/DuskAudioSystem.h" -#include "dusk/dusk.h" +#include "m_Do/m_Do_controller_pad.h" +#include "m_Do/m_Do_main.h" #include "tracy/Tracy.hpp" #if _WIN32 @@ -30,6 +31,30 @@ using namespace std::string_literals; using namespace std::string_view_literals; +namespace { +ImVec2 TouchEventToScreenPos(const SDL_TouchFingerEvent& touch) { + const AuroraWindowSize size = aurora::window::get_window_size(); + return ImVec2{ + touch.x * static_cast(size.width), + touch.y * static_cast(size.height), + }; +} + +ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { + while (window != nullptr) { + const bool canScrollX = window->ScrollMax.x > 0.0f; + const bool canScrollY = window->ScrollMax.y > 0.0f; + const bool canScrollWithMouse = (window->Flags & (ImGuiWindowFlags_NoScrollWithMouse | + ImGuiWindowFlags_NoMouseInputs)) == 0; + if ((canScrollX || canScrollY) && canScrollWithMouse) { + return window; + } + window = window->ParentWindow; + } + return nullptr; +} +} // namespace + namespace dusk { float ImGuiScale() { return 1.0f; } @@ -208,6 +233,51 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} + void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) { + if (!IsGameLaunched) { + return; + } + + switch (event.type) { + case SDL_EVENT_FINGER_DOWN: + if (!m_touchTapActive) { + m_touchTapActive = true; + m_touchTapMoved = false; + m_touchTapFingerId = event.tfinger.fingerID; + m_touchTapStartPos = TouchEventToScreenPos(event.tfinger); + } + break; + case SDL_EVENT_FINGER_MOTION: + if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { + const auto currentPos = TouchEventToScreenPos(event.tfinger); + const auto delta = currentPos - m_touchTapStartPos; + if (ImLengthSqr(delta) > 144.0f) { + m_touchTapMoved = true; + } + } + break; + case SDL_EVENT_FINGER_UP: + if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { + const bool shouldToggle = + !m_touchTapMoved && (m_isHidden || !ImGui::GetIO().WantCaptureMouse); + m_touchTapActive = false; + m_touchTapMoved = false; + if (shouldToggle) { + m_isHidden = !m_isHidden; + getSettings().backend.duskMenuOpen.setValue(!m_isHidden); + Save(); + } + } + break; + case SDL_EVENT_FINGER_CANCELED: + m_touchTapActive = false; + m_touchTapMoved = false; + break; + default: + break; + } + } + void ImGuiConsole::UpdateSettings() { getTransientSettings().skipFrameRateLimit = getSettings().game.enableTurboKeybind && ImGui::IsKeyDown(ImGuiKey_Tab); @@ -237,26 +307,31 @@ namespace dusk { if (!dusk::IsGameLaunched) { m_preLaunchWindow.draw(); } - - m_isHidden = getSettings().backend.duskMenuOpen; - CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); + m_isHidden = !getSettings().backend.duskMenuOpen; + bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); if (dusk::IsGameLaunched) { - if (getSettings().backend.duskMenuOpen != m_isHidden) { - m_isHidden = !getSettings().backend.duskMenuOpen; - getSettings().backend.duskMenuOpen.setValue(m_isHidden); - config::Save(); + const bool menuOpen = !m_isHidden; + if (getSettings().backend.duskMenuOpen != menuOpen) { + getSettings().backend.duskMenuOpen.setValue(menuOpen); + Save(); } } - if ((!dusk::IsGameLaunched || getSettings().backend.duskMenuOpen) && ImGui::BeginMainMenuBar()) { + if (showMenu && ImGui::BeginMainMenuBar()) { m_menuGame.draw(); m_menuEnhancements.draw(); m_menuTools.draw(); - ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 100.0f * ImGuiScale()); - ImGuiIO& io = ImGui::GetIO(); - ImGuiStringViewText(fmt::format(FMT_STRING("FPS: {:.2f}\n"), io.Framerate)); + const auto fpsLabel = + fmt::format(FMT_STRING("FPS: {:.2f}\n"), ImGui::GetIO().Framerate); + const auto fpsSize = + ImGui::CalcTextSize(fpsLabel.data(), fpsLabel.data() + fpsLabel.size()); + ImGui::SetCursorPosX( + ImMax(ImGui::GetCursorPosX(), ImGui::GetWindowWidth() - + ImGui::GetStyle().DisplaySafeAreaPadding.x - + fpsSize.x - ImGui::GetStyle().ItemSpacing.x)); + ImGuiStringViewText(fpsLabel); ImGui::EndMainMenuBar(); } @@ -267,10 +342,15 @@ namespace dusk { } if (dusk::IsGameLaunched && !m_isLaunchInitialized) { - ShowToast("Press F1 to toggle menu", 2.5f); + ShowToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ? + "Tap to toggle menu" : + "Press F1 to toggle menu", + 2.5f); m_isLaunchInitialized = true; } + UpdateDragScroll(); + m_menuGame.windowControllerConfig(); m_menuGame.windowInputViewer(); if (dusk::IsGameLaunched) { @@ -289,8 +369,7 @@ namespace dusk { DuskDebugPad(); // temporary, remove later // Only show cursor when menu or any windows are open - if ((!dusk::IsGameLaunched || getSettings().backend.duskMenuOpen) || ImGui::GetIO().MetricsRenderWindows > 0) - { + if (showMenu || ImGui::GetIO().MetricsRenderWindows > 0) { ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; // Imgui will re-show cursor. } else { @@ -306,6 +385,57 @@ namespace dusk { ShowPipelineProgress(); } + void ImGuiConsole::UpdateDragScroll() { + ImGuiContext& g = *ImGui::GetCurrentContext(); + ImGuiIO& io = ImGui::GetIO(); + + if (io.MouseSource != ImGuiMouseSource_TouchScreen) { + m_dragScrollWindow = nullptr; + return; + } + + if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) { + m_dragScrollWindow = nullptr; + return; + } + + if (io.WantTextInput || (g.ActiveId != 0 && g.InputTextState.ID == g.ActiveId)) { + m_dragScrollWindow = nullptr; + return; + } + + if (!ImGui::IsMouseDragging(ImGuiMouseButton_Left, io.MouseDragThreshold)) { + return; + } + + if (m_dragScrollWindow == nullptr) { + ImGuiWindow* hoveredWindow = nullptr; + ImGuiWindow* hoveredWindowUnderMovingWindow = nullptr; + ImGui::FindHoveredWindowEx(io.MousePos, false, &hoveredWindow, + &hoveredWindowUnderMovingWindow); + m_dragScrollWindow = FindDragScrollWindow(hoveredWindow); + m_dragScrollLastMousePos = io.MousePos; + } + + if (m_dragScrollWindow == nullptr) { + return; + } + + const auto mouseDelta = io.MousePos - m_dragScrollLastMousePos; + m_dragScrollLastMousePos = io.MousePos; + + if (mouseDelta.x != 0.0f && m_dragScrollWindow->ScrollMax.x > 0.0f) { + ImGui::SetScrollX(m_dragScrollWindow, + ImClamp(m_dragScrollWindow->Scroll.x - mouseDelta.x, 0.0f, + m_dragScrollWindow->ScrollMax.x)); + } + if (mouseDelta.y != 0.0f && m_dragScrollWindow->ScrollMax.y > 0.0f) { + ImGui::SetScrollY(m_dragScrollWindow, + ImClamp(m_dragScrollWindow->Scroll.y - mouseDelta.y, 0.0f, + m_dragScrollWindow->ScrollMax.y)); + } + } + bool ImGuiConsole::CheckMenuViewToggle(ImGuiKey key, bool& active) { if (ImGui::IsKeyPressed(key)) { active = !active; diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index 9cef53d0e4..9d733d939e 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -1,11 +1,13 @@ #ifndef DUSK_IMGUI_HPP #define DUSK_IMGUI_HPP -#include #include #include #include +#include +#include + #include "ImGuiFirstRunPreset.hpp" #include "ImGuiMenuEnhancements.hpp" #include "ImGuiMenuGame.hpp" @@ -13,10 +15,14 @@ #include "ImGuiPreLaunchWindow.hpp" #include "imgui.h" +union SDL_Event; +struct ImGuiWindow; + namespace dusk { class ImGuiConsole { public: ImGuiConsole(); + void HandleSDLEvent(const SDL_Event& event); void UpdateSettings(); void PreDraw(); void PostDraw(); @@ -36,6 +42,12 @@ private: bool m_isHidden = true; bool m_isLaunchInitialized = false; + bool m_touchTapActive = false; + bool m_touchTapMoved = false; + SDL_FingerID m_touchTapFingerId = 0; + ImVec2 m_touchTapStartPos = {}; + ImGuiWindow* m_dragScrollWindow = nullptr; + ImVec2 m_dragScrollLastMousePos = {}; std::deque m_toasts; ImGuiFirstRunPreset m_firstRunPreset; @@ -48,6 +60,7 @@ private: void ShowToasts(); void ShowPipelineProgress(); + void UpdateDragScroll(); }; extern ImGuiConsole g_imguiConsole; diff --git a/src/dusk/imgui/ImGuiEngine.cpp b/src/dusk/imgui/ImGuiEngine.cpp index 0c0b5e934d..46bfa8f3ab 100644 --- a/src/dusk/imgui/ImGuiEngine.cpp +++ b/src/dusk/imgui/ImGuiEngine.cpp @@ -3,12 +3,14 @@ #include #include #include +#include #include #include #include #include #include +#include "aurora/lib/window.hpp" #include "dusk/logging.h" #ifdef IMGUI_ENABLE_FREETYPE @@ -37,6 +39,20 @@ ImTextureID AddTexture(const char* assetName) { } return aurora_imgui_add_texture(image.width, image.height, image.data.get()); } + +ImVec2 GetDisplaySafeAreaPadding() { + SDL_Window* window = aurora::window::get_sdl_window(); + if (window == nullptr) { + return ImVec2(0.0f, 0.0f); + } + + SDL_Rect safeRect{}; + if (!SDL_GetWindowSafeArea(window, &safeRect)) { + return ImVec2(0.0f, 0.0f); + } + + return ImVec2(static_cast(safeRect.x), static_cast(safeRect.y)); +} } // namespace ImFont* ImGuiEngine::fontNormal; @@ -75,6 +91,7 @@ void ImGuiEngine_Initialize(float scale) { ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); io.FontGlobalScale = scale > 0.0f ? 1.0f / scale : 1.0f; + io.ConfigWindowsMoveFromTitleBarOnly = IsMobile; ImGuiEngine::fontNormal = CreateFont(std::floor(18.f * scale), GetAssetPath("Inter-Regular.ttf"), "Inter Regular"); @@ -104,6 +121,7 @@ void ImGuiEngine_Initialize(float scale) { style.PopupRounding = 7.0; style.TabBorderSize = 1.f; style.TabRounding = 3.f; + style.DisplaySafeAreaPadding = GetDisplaySafeAreaPadding(); auto* colors = style.Colors; colors[ImGuiCol_Text] = ImVec4(0.95f, 0.96f, 0.98f, 1.00f); diff --git a/src/dusk/imgui/ImGuiEngine.hpp b/src/dusk/imgui/ImGuiEngine.hpp index 3b3e3e0303..44077c9625 100644 --- a/src/dusk/imgui/ImGuiEngine.hpp +++ b/src/dusk/imgui/ImGuiEngine.hpp @@ -25,4 +25,10 @@ struct Image { uint32_t height; }; Image GetImage(const std::string& path); + +#if (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || defined(__ANDROID__) +inline constexpr bool IsMobile = true; +#else +inline constexpr bool IsMobile = false; +#endif } // namespace dusk diff --git a/src/dusk/imgui/ImGuiEventFlags.hpp b/src/dusk/imgui/ImGuiEventFlags.hpp new file mode 100644 index 0000000000..694336da03 --- /dev/null +++ b/src/dusk/imgui/ImGuiEventFlags.hpp @@ -0,0 +1,842 @@ +#ifndef DUSK_IMGUI_EVENTFLAGS_HPP +#define DUSK_IMGUI_EVENTFLAGS_HPP + +#include +#include + +struct duskImguiEventFlagEntry { + uint8_t byteIndex; + uint8_t bitIndex; + uint16_t flagID; + std::string flagName; + std::string description; + std::string location; +}; + +inline duskImguiEventFlagEntry duskImguiEventFlags[] = { + { 0x00, 0x80, 0x0080, "TEST_001", "Kakariko bridge portal warp hint", "Faron Woods" }, + { 0x00, 0x40, 0x0040, "TEST_002", "Big magma stone portal warp hint", "Death Mountain" }, + { 0x00, 0x20, 0x0020, "TEST_003", "Handed over tomato puree", "LV5 Dungeon" }, + { 0x00, 0x10, 0x0010, "TEST_004", "Handed over secret ingredient", "LV5 Dungeon" }, + { 0x00, 0x08, 0x0008, "F_0001", "Spoke to Renado after Colin returns", "Kakariko Village" }, + { 0x00, 0x04, 0x0004, "F_0002", "Lost wrestling match to elder goron for first time", "Death Mountain (room)" }, + { 0x00, 0x02, 0x0002, "F_0003", "Handed over tomato puree and left room", "LV5 Dungeon" }, + { 0x00, 0x01, 0x0001, "F_0004", "Handed over secret ingredient and left room", "LV5 Dungeon" }, + { 0x01, 0x80, 0x0180, "F_0005", "Gathered 14 Tears of Light in area 4", "Misc." }, + { 0x01, 0x40, 0x0140, "F_0006", "First conversation with Yeto in kitchen", "LV5 Dungeon" }, + { 0x01, 0x20, 0x0120, "F_0007", "Spoke to Yeta while holding cheese", "LV5 Dungeon" }, + { 0x01, 0x10, 0x0110, "F_0008", "First conversation with Fado at the farm on 1st day", "Ordon Village" }, + { 0x01, 0x08, 0x0108, "F_0009", "Approach secret entrance with Colin", "Ordon Woods" }, + { 0x01, 0x04, 0x0104, "F_0010", "First convo with Colin blocking path (forced)", "Ordon Village" }, + { 0x01, 0x02, 0x0102, "F_0011", "Fence jumping complete", "Ranch" }, + { 0x01, 0x01, 0x0101, "F_0012", "Get metal sword!", "Ordon Village" }, + { 0x02, 0x80, 0x0280, "F_0013", "2nd day - First time rampaging goat escapes", "Ordon Village" }, + { 0x02, 0x40, 0x0240, "F_0014", "sword tutorial ends", "Ordon Village" }, + { 0x02, 0x20, 0x0220, "F_0015", "Slingshot tutorial ends", "Ordon Village" }, + { 0x02, 0x10, 0x0210, "F_0016", "On 3rd day, start following Colin who is blocking path", "Ordon Village" }, + { 0x02, 0x08, 0x0208, "F_0017", "Spoke to beth right after sword tutorial", "Ordon Village" }, + { 0x02, 0x04, 0x0204, "F_0018", "Asked by Fado to jump fence", "Ranch" }, + { 0x02, 0x02, 0x0202, "F_0019", "Spoke with Ilia (Colin is there too) at the spring", "Ordon Woods" }, + { 0x02, 0x01, 0x0201, "F_0020", "First convo with Sera while shop is closed", "Ordon Village" }, + { 0x03, 0x80, 0x0380, "D_0001", "Stopped by squirrel in front of house at night", "Ordon Village" }, + { 0x03, 0x40, 0x0340, "F_0021", "2nd day: spoke with Pergie", "Ordon Village" }, + { 0x03, 0x20, 0x0320, "F_0022", "Start fence-jump on 1st day", "Ranch" }, + { 0x03, 0x10, 0x0310, "F_0023", "Called by Jaggle from below hill", "Ordon Village" }, + { 0x03, 0x08, 0x0308, "F_0024", "Spoke with Talo/Malo/Beth (before obtaining slingshot)", "Ordon Village" }, + { 0x03, 0x04, 0x0304, "F_0025", "Pass Uli's pick-up tutorial", "Ordon Village" }, + { 0x03, 0x02, 0x0302, "F_0026", "gave wooden sword to talo on 3rd day", "Ordon Village" }, + { 0x03, 0x01, 0x0301, "F_0027", "Uli tutorial ends (same whether pass or fail)", "Ordon Village" }, + { 0x04, 0x80, 0x0480, "F_0028", "spoke to yeta while holding pumpkin", "LV5 Dungeon" }, + { 0x04, 0x40, 0x0440, "F_0029", "2nd day - refused sword tutorial", "Ordon Village" }, + { 0x04, 0x20, 0x0420, "F_0030", "before sword tutorial - first conversation with Beth", "Ordon Village" }, + { 0x04, 0x10, 0x0410, "F_0031", "2nd day - Spoke to Uli bfore finding basket", "Ordon Village" }, + { 0x04, 0x08, 0x0408, "F_0032", "3rd day - First convo with fado (before forced goat chase)", "Ranch" }, + { 0x04, 0x04, 0x0404, "F_0033", "First day - spoke with Uli", "Ordon Village" }, + { 0x04, 0x02, 0x0402, "M_006", "3rd day - finished chasing goats, speak to Fado in free state", "Ranch" }, + { 0x04, 0x01, 0x0401, "M_007", "first conversation with Shad in basement (about the words of opening)", "Kakariko Village" }, + { 0x05, 0x80, 0x0580, "M_008", "cutscene - attacked by monsters at Ordon spring", "Kawagoe Cutscene" }, + { 0x05, 0x40, 0x0540, "M_009", "[cutscene: 6B] Prison escape - Midna rides on back", "Kawagoe Cutscene" }, + { 0x05, 0x20, 0x0520, "M_010", "[cutscene: 6A] Midna appears in the prison", "Kawagoe Cutscene" }, + { 0x05, 0x10, 0x0510, "M_011", "Midna removes wolf's chains in prison", "Inside Hyrule Castle" }, + { 0x05, 0x08, 0x0508, "M_012", "[cutscene: 7] Meet Princess Zelda at castle", "Kawagoe Cutscene" }, + { 0x05, 0x04, 0x0504, "M_013", "First heard about Twilight gate from Midna", "Misc." }, + { 0x05, 0x02, 0x0502, "M_014", "[cutscene: 8] First warped from castle by Midna", "Kawagoe Cutscene" }, + { 0x05, 0x01, 0x0501, "M_015", "Can use Midna's B charge attack", "Faron Woods" }, + { 0x06, 0x80, 0x0680, "M_016", "[cutscene: 9] Ordon village spirit appears", "Kawagoe Cutscene" }, + { 0x06, 0x40, 0x0640, "M_017", "[cutscene: 10] Dark Hyrule Forest - Midna again", "Kawagoe Cutscene" }, + { 0x06, 0x20, 0x0620, "M_018", "Brought Kakariko bridge back to original location", "main event" }, + { 0x06, 0x10, 0x0610, "M_019", "[cutscene: 11] forest spirit revived - Hero's birth", "Kawagoe Cutscene" }, + { 0x06, 0x08, 0x0608, "M_020", "[cutscene: ] Colin kidnapped : ON once watched", "Kawagoe Cutscene" }, + { 0x06, 0x04, 0x0604, "M_021", "First portal warp", "main event" }, + { 0x06, 0x02, 0x0602, "M_022", "LV1 dungeon clear (Midna creates warp hole)", "LV1 Dungeon" }, + { 0x06, 0x01, 0x0601, "M_023", "Epona rescued flag", "main event" }, + { 0x07, 0x80, 0x0780, "M_024", "[cutscene: 16] take back Colin", "Kawagoe Cutscene" }, + { 0x07, 0x40, 0x0740, "M_025", "First wresting match against fat (elder) goron", "Death Mountain (room)" }, + { 0x07, 0x20, 0x0720, "M_026", "Reunion with Bo (Watched cutscene before wrestle match)", "Ordon Village" }, + { 0x07, 0x10, 0x0710, "M_027", "[cutscene: 13] kids in the church (beast eyes)", "Kawagoe Cutscene" }, + { 0x07, 0x08, 0x0708, "M_028", "[cutscene: 14] restore mountain spirit - Reuinion with Colin et al.", "Kawagoe Cutscene" }, + { 0x07, 0x04, 0x0704, "M_029", "Win wrestle match against Gor Coron", "Death Mountain (room)" }, + { 0x07, 0x02, 0x0702, "M_030", "First conversation with Gor Coron", "Death Mountain (room)" }, + { 0x07, 0x01, 0x0701, "M_031", "LV2 dungeon clear", "LV2 Dungeon" }, + { 0x08, 0x80, 0x0880, "M_032", "Melted Zora river ice with magma rock", "main event" }, + { 0x08, 0x40, 0x0840, "M_033", "Start carriage guarding game", "main event" }, + { 0x08, 0x20, 0x0820, "M_034", "[cutscene: 19] Reunion with Ilia LV3", "Kawagoe Cutscene" }, + { 0x08, 0x10, 0x0810, "M_035", "[cutscene: 35] after carriage guarding event", "Kawagoe Cutscene" }, + { 0x08, 0x08, 0x0808, "M_036", "Begin carriage guarding (after joust revenge)", "main event" }, + { 0x08, 0x04, 0x0804, "M_037", "Got Zora armor from Zora queen", "Kakariko Village" }, + { 0x08, 0x02, 0x0802, "M_038", "[bow and arrow game] listen to hawkeye hint", "Kakariko Village" }, + { 0x08, 0x01, 0x0801, "M_039", "[Bow and arrow game] First time talking to Talo", "Kakariko Village" }, + { 0x09, 0x80, 0x0980, "M_040", "[bow and arrow game] Spoke to Talo after completing", "Kakariko Village" }, + { 0x09, 0x40, 0x0940, "M_041", "[Bow and Arrow game] First attempt", "Kakariko Village" }, + { 0x09, 0x20, 0x0920, "M_042", "[Bow and Arrow game] clear", "Kakariko Village" }, + { 0x09, 0x10, 0x0910, "M_043", "[Bow and Arrow game] Complete using Hawkeye", "Kakariko Village" }, + { 0x09, 0x08, 0x0908, "M_044", "[Barnes Bomb Shop] Bought premium pack", "Kakariko Village" }, + { 0x09, 0x04, 0x0904, "M_045", "LV3 Dungeon clear", "LV3 Dungeon" }, + { 0x09, 0x02, 0x0902, "M_046", "[Iza river descent] Get advice about boulder blocking river", "Zora's River" }, + { 0x09, 0x01, 0x0901, "M_047", "Iza Twilight - Talked after defeating shadow bugs", "Zora's River" }, + { 0x0A, 0x80, 0x0A80, "M_048", "Ran away while clearing rubble at hut", "Zora's River" }, + { 0x0A, 0x40, 0x0A40, "M_049", "Threw first rolling goron at death mountain", "Death Mountain" }, + { 0x0A, 0x20, 0x0A20, "M_050", "Joust bridge disappears", "main event" }, + { 0x0A, 0x10, 0x0A10, "M_051", "Shadow Kargorok (?) (Large) event complete (Horse grass appears in various places)", "main event" }, + { 0x0A, 0x08, 0x0A08, "M_052", "Horseback battle clear", "main event" }, + { 0x0A, 0x04, 0x0A04, "M_053", "Horseback battle cutscene", "main event" }, + { 0x0A, 0x02, 0x0A02, "M_054", "Joust / one-on-one battle cutscene", "main event" }, + { 0x0A, 0x01, 0x0A01, "M_055", "Did damage at least once during joust/one-on-one battle", "main event" }, + { 0x0B, 0x80, 0x0B80, "M_056", "Ignored Iza's concerns", "Ring field" }, + { 0x0B, 0x40, 0x0B40, "M_057", "View boar cutscene after defeating King Bulblin", "Desert" }, + { 0x0B, 0x20, 0x0B20, "M_058", "First time meeting Yeta (forced converation)", "LV5 Dungeon" }, + { 0x0B, 0x10, 0x0B10, "M_059", "received map from Yeta", "LV5 Dungeon" }, + { 0x0B, 0x08, 0x0B08, "M_060", "[Iza river descent] ", "Zora's River" }, + { 0x0B, 0x04, 0x0B04, "M_061", "[Iza river descent] Got explanation for clearing rubble 1 time", "Zora's River" }, + { 0x0B, 0x02, 0x0B02, "M_062", "[Iza] Says thanks after night stalker battle", "Zora's River" }, + { 0x0B, 0x01, 0x0B01, "M_063", "[Iza river descent] Finish job (First time descending river)", "Zora's River" }, + { 0x0C, 0x80, 0x0C80, "M_064", "Water returns to Hylia Lake (spoke with Queen Rutela)", "Zora's Domain" }, + { 0x0C, 0x40, 0x0C40, "M_065", "Spoke with Faron spirit after clearing LV1 dungeon", "Faron Woods" }, + //{ 0x0C, 0x20, 0x0C20, "M_066", "N/A", "N/A" }, + { 0x0C, 0x10, 0x0C10, "M_067", "Midna riding / not riding (ON == riding)", "main event" }, + { 0x0C, 0x08, 0x0C08, "M_068", "when OFF, wolf carries sword and shield on back", "main event" }, + { 0x0C, 0x04, 0x0C04, "M_069", "First conversation with child goron shop clerk", "Kakariko Village" }, + { 0x0C, 0x02, 0x0C02, "M_070", "[cutscene: 18] Lanayru spirit restored", "Kawagoe Cutscene" }, + { 0x0C, 0x01, 0x0C01, "M_071", "[cutscene: 20] Zant appears (during Midna's desperate hour)", "Kawagoe Cutscene" }, + { 0x0D, 0x80, 0x0D80, "M_072", "Get wooden shield", "Ordon Village" }, + { 0x0D, 0x40, 0x0D40, "M_073", "Spoke with Renado after guarding carriage [0030]", "Kakariko Village" }, + { 0x0D, 0x20, 0x0D20, "M_074", "Spoke with Renado after guarding carriage [0031]", "Kakariko Village" }, + { 0x0D, 0x10, 0x0D10, "M_075", "Forced conversation with Yeta after getting bedroom key", "LV5 Dungeon" }, + { 0x0D, 0x08, 0x0D08, "M_076", "First conversation with Castle Town Malo Mart shop clerk", "Misc." }, + { 0x0D, 0x04, 0x0D04, "M_077", "Get shadow crystal (can now transform)", "main event" }, + { 0x0D, 0x02, 0x0D02, "M_078", "Spoke with frog A", "Ordon Village" }, + { 0x0D, 0x01, 0x0D01, "M_079", "Ordon village night: Heard Z Jump dialogie in Pergie's house", "Ordon Village" }, + { 0x0E, 0x80, 0x0E80, "M_080", "Spoke with brown cucco", "Ordon Village" }, + { 0x0E, 0x40, 0x0E40, "M_081", "First conversation with Goron that shoots you up (shared with everyone)", "Death Mountain" }, + { 0x0E, 0x20, 0x0E20, "M_082", "Spoke with spring Goron A", "Death Mountain" }, + { 0x0E, 0x10, 0x0E10, "M_083", "Heard Fyer's talk after water restored", "Lake Hylia" }, + { 0x0E, 0x08, 0x0E08, "M_084", "Complete sequence of shopping at Malo Mart first time", "Kakariko Village" }, + { 0x0E, 0x04, 0x0E04, "M_085", "Midna dialogue right before Boss Bug's Tear of Light appears", "Twilight Ring field" }, + { 0x0E, 0x02, 0x0E02, "M_086", "Show Boss Bug's Tear of Light on the map", "Twilight Ring field" }, + { 0x0E, 0x01, 0x0E01, "M_087", "Ilia memory event start", "Ring field" }, + { 0x0F, 0x80, 0x0F80, "M_088", "Get Renado's Letter", "Kakariko Village" }, + { 0x0F, 0x40, 0x0F40, "M_089", "First time entering doctor's office (forced conversation)", "Castle Town" }, + { 0x0F, 0x20, 0x0F20, "M_090", "Spoke to town doctor before showing receipt", "Castle Town" }, + { 0x0F, 0x10, 0x0F10, "M_091", "Buy out fundraiser amount (Malo becomes nice)", "Kakariko Village" }, + { 0x0F, 0x08, 0x0F08, "M_092", "Warped Eldin Bridge", "Ring field" }, + { 0x0F, 0x04, 0x0F04, "M_093", "First conversation with Fyer after desert's debut", "Lake Hylia" }, + { 0x0F, 0x02, 0x0F02, "M_094", "First time visiting Rizu's hut after completing river job (forced conversation)", "Zora's River" }, + { 0x0F, 0x01, 0x0F01, "M_095", "First time meeting Coro (obtain lantern)", "Faron Woods" }, + { 0x10, 0x80, 0x1080, "M_096", "3rd day: spoke with Pergie", "Ordon Village" }, + { 0x10, 0x40, 0x1040, "F_0034", "first conversation wtih Rusl", "Ordon Village" }, + { 0x10, 0x20, 0x1020, "F_0035", "F0003: Spoke to Colin while he is stopping hors (doesn't have fishing rod)", "Ordon Village" }, + { 0x10, 0x10, 0x1010, "M_001", "Opening cutscene", "Kawagoe Cutscene" }, + { 0x10, 0x08, 0x1008, "M_003", "F0003: Spoke to Colin while he is stopping hors (has fishing rod)", "Ordon Village" }, + { 0x10, 0x04, 0x1004, "F_0036", "Spoke to Jaggle using L-focus before climbing vines?", "Ordon Village" }, + { 0x10, 0x02, 0x1002, "F_0037", "Jaggle - Spoke on the hill?", "Ordon Village" }, + { 0x10, 0x01, 0x1001, "F_0038", "Opening (2nd day) cat returns home", "Ordon Village" }, + { 0x11, 0x80, 0x1180, "F_0039", "Warned by Hanch after climbing vines on 2nd day", "Ordon Village" }, + { 0x11, 0x40, 0x1140, "F_0040", "2nd day: Spoke to Jaggle after blowing on whistle", "Ordon Village" }, + { 0x11, 0x20, 0x1120, "F_0041", "Opening 2nd day - After this is turned ON Hanch is attacked by bees", "Ordon Village" }, + { 0x11, 0x10, 0x1110, "F_0042", "Spoke with Yeta right after arriving at bedroom", "LV5 Dungeon" }, + { 0x11, 0x08, 0x1108, "F_0043", "First visit after fundrasing funds drop to 200", "Kakariko Village" }, + { 0x11, 0x04, 0x1104, "F_0044", "Accepted sword tutorial first time", "Ordon Village" }, + { 0x11, 0x02, 0x1102, "F_0045", "Opening 3rd day - spoke with Uli", "Ordon Village" }, + { 0x11, 0x01, 0x1101, "F_0046", "Spoke with Sera after saving(lt;) failing(gt;) cat", "Ordon Village" }, + { 0x12, 0x80, 0x1280, "F_0047", "First visit after Ordon Village shop opens", "Ordon Village" }, + { 0x12, 0x40, 0x1240, "F_0048", "Uli's pick-up tutorial ", "Ordon Village" }, + { 0x12, 0x20, 0x1220, "F_0049", "Uli's pick up tutorial ", "Ordon Village" }, + { 0x12, 0x10, 0x1210, "F_0050", "Saw cutscene for getting iron boots", "Ordon Village" }, + { 0x12, 0x08, 0x1208, "F_0051", "Spoke to Sera in the shop after saving cat", "Ordon Village" }, + { 0x12, 0x04, 0x1204, "F_0052", "Had 2nd conversaton with Sera before saving cat", "Ordon Village" }, + { 0x12, 0x02, 0x1202, "F_0053", "Saw night stalker appearance cutscene", "Faron Woods" }, + { 0x12, 0x01, 0x1201, "F_0054", "Lost wrestling match with elder goron while wearing iron boots", "Death Mountain" }, + { 0x13, 0x80, 0x1380, "F_0055", "Received Vessel of Light from Faron spirit", "Faron Woods" }, + { 0x13, 0x40, 0x1340, "F_0056", "Lost to elder goron 2+ times", "Death Mountain" }, + { 0x13, 0x20, 0x1320, "F_0057", "[cutscene: 17] Part with the children", "Kawagoe Cutscene" }, + { 0x13, 0x10, 0x1310, "F_0058", "Listened to voices on other side of door in Telma's shop", "Castle Town" }, + { 0x13, 0x08, 0x1308, "F_0059", "Conversation after getting spirit and tears of light (darkness cleared) <- probably unused ...", "+ Scary flag" }, + { 0x13, 0x04, 0x1304, "F_0060", "First conversation with Hozu in the World of Light", "Zora's River" }, + { 0x13, 0x02, 0x1302, "F_0061", "Heard spring goron and shopkeeper rumers after winning wrestling match against elder goron", "Death Mountain" }, + { 0x13, 0x01, 0x1301, "F_0062", "Abandoned taking Fyer's cannon after paying", "Lake Hylia" }, + { 0x14, 0x80, 0x1480, "F_0063", "Used Fyer's cannon for first time", "Lake Hylia" }, + { 0x14, 0x40, 0x1440, "F_0064", "first convo with yeta after obtaining tomato puree", "LV5 Dungeon" }, + { 0x14, 0x20, 0x1420, "F_0065", "Yeta adds last symbol onto map", "LV5 Dungeon" }, + { 0x14, 0x10, 0x1410, "F_0066", "First saw Goron cutscene on mountain path", "Death Mountain" }, + { 0x14, 0x08, 0x1408, "F_0067", "Recieved milk jar (1/2) from Sera", "Ordon Village" }, + //{ 0x14, 0x04, 0x1404, "F_0068", "N/A", "N/A" }, + { 0x14, 0x02, 0x1402, "F_0069", "F0048: Thanked by Colen for clearing path", "Ordon Village" }, + { 0x14, 0x01, 0x1401, "F_0070", "Colin went deep into the woods", "Ordon Woods" }, + { 0x15, 0x80, 0x1580, "M_002", "[cutscene: 2] Met with Ilia (brings horse to spring)", "Kawagoe Cutscene" }, + { 0x15, 0x40, 0x1540, "F_0071", "Cannot warp to Lanayru", "Twilight Ring field" }, + { 0x15, 0x20, 0x1520, "F_0072", "Knocked down large beehive with hawk", "Ordon Village" }, + { 0x15, 0x10, 0x1510, "F_0073", "Attacked after charging at large beehive", "Ordon Village" }, + { 0x15, 0x08, 0x1508, "F_0074", "Hanch attacked by bees", "Ordon Village" }, + { 0x15, 0x04, 0x1504, "F_0075", "Angered Jaggle by destroying pumpkin", "Ordon Village" }, + { 0x15, 0x02, 0x1502, "F_0076", "Spoke to Hanch in lake", "Ordon Village" }, + { 0x15, 0x01, 0x1501, "F_0077", "First converstaion with Agetha inside", "Castle Town" }, + { 0x16, 0x80, 0x1680, "F_0078", "Hanch returned to land after jumping into lake", "Ordon Village" }, + { 0x16, 0x40, 0x1640, "F_0079", "2nd Day - successful knocked down rampaging mountain goat", "Ordon Village" }, + { 0x16, 0x20, 0x1620, "F_0080", "Completed all of mountain goat rampage event", "Ordon Village" }, + { 0x16, 0x10, 0x1610, "F_0081", "20 mountain goats rampaged", "Ordon Village" }, + { 0x16, 0x08, 0x1608, "F_0082", "Completed coversation with Bo after 20th mountain goat's rampage", "Ordon Village" }, + { 0x16, 0x04, 0x1604, "F_0083", "Deliver letter from Agetha", "Letter" }, + { 0x16, 0x02, 0x1602, "F_0084", "Opening days 2&3: knocked down a beehive with slingshot", "Ordon Village" }, + { 0x16, 0x01, 0x1601, "F_0085", "Rusl appears at woods entrance", "Ordon Village" }, + { 0x17, 0x80, 0x1780, "F_0086", "Spoke with Hanch after knocking down beehive with hawk", "Ordon Village" }, + { 0x17, 0x40, 0x1740, "F_0087", "Left search area after first conversation with Pergie", "Ordon Village" }, + { 0x17, 0x20, 0x1720, "F_0088", "Spoke to Beth after quitting sword tutorial", "Ordon Village" }, + { 0x17, 0x10, 0x1710, "F_0089", "Talked to village chief for first time", "Ordon Village" }, + { 0x17, 0x08, 0x1708, "F_0090", "F:1126 - South - Spoke with Agetha's stalker (before talking with Agetha inside)", "Castle Town" }, + { 0x17, 0x04, 0x1704, "F_0091", "F:1126 - South - Spoke with Agetha's stalker (after talking with Agetha inside)", "Ordon Village" }, + { 0x17, 0x02, 0x1702, "F_0092", "F:1127 - South - Spoke with the Hyrule soldier guide", "Castle Town" }, + { 0x17, 0x01, 0x1701, "F_0093", "F:1128 - South - Spoke with female clerk at vegetable stand", "Castle Town" }, + { 0x18, 0x80, 0x1880, "F_0094", "Talo went after the monkey", "Ordon Village" }, + { 0x18, 0x40, 0x1840, "F_0095", "Spoke to Fado before mountain goat rampage", "Ranch" }, + { 0x18, 0x20, 0x1820, "F_0096", "Have spoken to Bo with 1 health", "Ordon Village" }, + { 0x18, 0x10, 0x1810, "F_0097", "First conversation with dog eavesdrop hint", "Ordon Village" }, + { 0x18, 0x08, 0x1808, "F_0202", "Rusl / Wolf fails to get sword", "Ordon Village" }, + { 0x18, 0x04, 0x1804, "F_0203", "First tried to steal from unnmaned shop (Havent checked donation box)", "Shop" }, + { 0x18, 0x02, 0x1802, "F_0204", "Talked to Midna from across the bars in the sewer", "Inside Hyrule Castle" }, + { 0x18, 0x01, 0x1801, "F_0205", "Heard Rusl and Uli talking in Ordon village at night", "Ordon Village" }, + { 0x19, 0x80, 0x1980, "F_0206", "Barrier of darkness tag: tried to enter without getting sword and shield", "Ordon Woods" }, + { 0x19, 0x40, 0x1940, "F_0207", "Viewed Hanch, the Hawker cutscene", "Ordon Village" }, + { 0x19, 0x20, 0x1920, "F_0208", "Hanch leapt after being startled by wolf", "Ordon Village" }, + { 0x19, 0x10, 0x1910, "F_0209", "Escape after failing to eavesdrop to Bo and Jaggle", "Ordon Village" }, + { 0x19, 0x08, 0x1908, "F_0210", "F0004: Try to get on horse when Colin is stopping horse", "Ordon Village" }, + { 0x19, 0x04, 0x1904, "F_0211", "Successfully eavesdrop on Bo and Jaggle", "Ordon Village" }, + { 0x19, 0x02, 0x1902, "F_0212", "Conversation wtih Colin in front of Ordon springs gate", "Ordon Woods" }, + { 0x19, 0x01, 0x1901, "F_0213", "Called by spirit after NS fight at Kakariko entrance", "Kakariko Village" }, + { 0x1A, 0x80, 0x1A80, "F_0214", " First heard Midna's hint about Z", "Inside Hyrule Castle" }, + { 0x1A, 0x40, 0x1A40, "F_0215", "Spoke with cat on roof", "Ordon Village" }, + { 0x1A, 0x20, 0x1A20, "F_0216", "Spoke with Ordon village cucco B (white)", "Ordon Village" }, + { 0x1A, 0x10, 0x1A10, "F_0217", "First conversation after speaking with Coro again after clearing up twilight", "Faron Woods" }, + { 0x1A, 0x08, 0x1A08, "F_0218", "Bought jar of oil from Coro", "Faron Woods" }, + { 0x1A, 0x04, 0x1A04, "F_0219", "Heard Agetha say \"but you have some\"", "Castle Town" }, + { 0x1A, 0x02, 0x1A02, "F_0220", "First time - Talked to one of the Hyrule soldiers underneath castle in Twilight", "Inside Hyrule Castle" }, + { 0x1A, 0x01, 0x1A01, "F_0221", "Received vessel of light from spirit", "Kakariko Village" }, + { 0x1B, 0x80, 0x1B80, "F_0222", "Finished Coro ignite event", "Faron Woods" }, + { 0x1B, 0x40, 0x1B40, "F_0223", "Listened to Coro's dialogue before defeating shadow bugs", "Farron Woods inside woodcutter's shop" }, + { 0x1B, 0x20, 0x1B20, "F_0224", "Flag for lantern guide monkey cutscene", "Faron Woods" }, + { 0x1B, 0x10, 0x1B10, "F_0225", "Lanter guide monkey doesn't come out a second time", "Faron Woods" }, + { 0x1B, 0x08, 0x1B08, "F_0226", "Get lantern back from monkey", "Faron Woods" }, + { 0x1B, 0x04, 0x1B04, "F_0227", "Try to leave mist woods after lantern taken", "Faron Woods" }, + { 0x1B, 0x02, 0x1B02, "F_0228", "Listened to goron's complaint at the entrance to twilight mountain path", "Death Mountain" }, + { 0x1B, 0x01, 0x1B01, "F_0229", "Try to leave the mist woods area without lantern (after returning monkey)", "Faron Woods" }, + { 0x1C, 0x80, 0x1C80, "F_0230", "Twilight - listened to goron B's complaints", "Death Mountain" }, + { 0x1C, 0x40, 0x1C40, "F_0231", "Did first wrestle match with Bo", "Ordon Village" }, + { 0x1C, 0x20, 0x1C20, "F_0232", "get iron boots from Bo", "Ordon Village" }, + { 0x1C, 0x10, 0x1C10, "F_0233", "Win practice battle with Bo (before getting boots)", "Ordon Village" }, + { 0x1C, 0x08, 0x1C08, "F_234", "Attacked by Trill", "Faron Woods" }, + { 0x1C, 0x04, 0x1C04, "F_0235", "Giant game clear", "Sacred Grove" }, + { 0x1C, 0x02, 0x1C02, "F_0236", "Make Midna angry after doing wrong destination for Kakariko bridge warp", "Misc." }, + { 0x1C, 0x01, 0x1C01, "F_0237", "Entered Malo Mart (chatted with Malo)", "Kakariko Village" }, + { 0x1D, 0x80, 0x1D80, "F_0238", "Destroyed all puppets (monkey girl event before sacred grove)", "Faron Woods" }, + { 0x1D, 0x40, 0x1D40, "T_0239", "Spoke with Fyer (start dark carge)", "Lake Hylia" }, + { 0x1D, 0x20, 0x1D20, "F_0240", "Speak with Barnes while heas making bombs", "Kakariko Village" }, + { 0x1D, 0x10, 0x1D10, "F_0241", "First conversation with Barnes after bomb shop open", "Kakariko Village" }, + { 0x1D, 0x08, 0x1D08, "F_0242", "Spoke with the dazed chief goron after Fyrus battle", "LV2 Dungeon" }, + { 0x1D, 0x04, 0x1D04, "F_0243", "Speak with Luda wiping Colin's sweat", "Kakariko Village" }, + { 0x1D, 0x02, 0x1D02, "F_0244", "Speak to Talo while COlin is wiping sweat", "Kakariko Village" }, + { 0x1D, 0x01, 0x1D01, "MAP_VISIBLE", "Area map show/hide", "2D Map" }, + { 0x1E, 0x80, 0x1E80, "F_0246", "Malo Mart fundraiser (and carying spring water) start", "Kakariko Village" }, + { 0x1E, 0x40, 0x1E40, "F_0247", "First conversation with Malo (shopping complete) after helping Malo Mart fundraiser", "Kakariko Village" }, + { 0x1E, 0x20, 0x1E20, "F_0248", "Speak with Coro after clearing Forest Temple", "Faron Woods" }, + { 0x1E, 0x10, 0x1E10, "F_0249", "Try to leave after paying for Flight by Fowl (first time only)", "Lake Hylia" }, + { 0x1E, 0x08, 0x1E08, "F_0250", "[cutscene: 21] reunion with Zelda / Midna revived (Ganon wall appears)", "Kawagoe Cutscene" }, + { 0x1E, 0x04, 0x1E04, "F_0251", "Speak again with Sera before finding kids (forced conversation)", "Ordon Village" }, + { 0x1E, 0x02, 0x1E02, "F_0252", "Spoke to Sera while kids are missing (dealing with shop)", "Ordon Village" }, + { 0x1E, 0x01, 0x1E01, "F_0253", "Stuck to magnet lift at least once", "For E3 2006" }, + { 0x1F, 0x80, 0x1F80, "F_0254", "Hit boss's weak spot at least once", "For E3 2006" }, + { 0x1F, 0x40, 0x1F40, "F_0255", "Boss exhausted (grabbing chains) only on during state (normally off)", "For E3 2006" }, + { 0x1F, 0x20, 0x1F20, "F_0256", "Knocked down boss at leased once", "For E3 2006" }, + { 0x1F, 0x10, 0x1F10, "F_0257", "Only ON when boss is in hollow state (normally off, changes in real time)", "For E3 2006" }, + { 0x1F, 0x08, 0x1F08, "F_0258", "Heard hint right above heavy switch", "For E3 2006" }, + { 0x1F, 0x04, 0x1F04, "F_0259", "Heard first forced dialogue from Midna", "For E3 2006" }, + { 0x1F, 0x02, 0x1F02, "F_0260", "First time speaking to Resistance Rusl", "Castle Town" }, + { 0x1F, 0x01, 0x1F01, "F_0261", "First conversation with Resistance Auru (made fun of)", "Castle Town" }, + { 0x20, 0x80, 0x2080, "F_0262", "First conversation with Resistance Shad (made fun of)", "Castle Town" }, + { 0x20, 0x40, 0x2040, "F_0263", "First conversation with Resistance Ashei (3) (made fun of)", "Castle Town" }, + { 0x20, 0x20, 0x2020, "F_0264", "get master sword", "Kawagoe Cutscene" }, + { 0x20, 0x10, 0x2010, "F_0265", "LV4 dungeon clear", "LV4 Dungeon," }, + { 0x20, 0x08, 0x2008, "F_0266", "LV5 dungeon clear", "LV5 Dungeon" }, + { 0x20, 0x04, 0x2004, "F_0267", "LV6 dungeon clear", "LV6 Dungeon" }, + { 0x20, 0x02, 0x2002, "F_0268", "LV7 dungeon clear", "LV7 dungeon" }, + { 0x20, 0x01, 0x2001, "F_0269", "First conversation with Telma after getting master sword", "Castle Town" }, + { 0x21, 0x80, 0x2180, "F_0270", "Gave Renado's letter to Telma", "Castle Town" }, + { 0x21, 0x40, 0x2140, "F_0271", "Talked with Telma again after the other conversation after getting master sword", "Castle Town" }, + { 0x21, 0x20, 0x2120, "F_0272", "Saw Auru's location on map", "Castle Town" }, + { 0x21, 0x10, 0x2110, "F_0273", "Saw Ashei's location on map", "Castle Town" }, + { 0x21, 0x08, 0x2108, "F_0274", "Saw Rusl's location on map", "Castle Town" }, + { 0x21, 0x04, 0x2104, "F_0275", "Saw Shad's location on map", "Castle Town" }, + { 0x21, 0x02, 0x2102, "F_0276", "Heard conversation with Louise about stolen wood carving", "Castle Town" }, + { 0x21, 0x01, 0x2101, "F_0277", "Hear conversation between Telma and Ilia in Telma's shop (Twilight)", "Castle Town" }, + { 0x22, 0x80, 0x2280, "F_0278", "Received pendant from Impaz", "Hidden Village" }, + { 0x22, 0x40, 0x2240, "F_0279", "Saw cutscene about scent of kids from wooden sword", "Ring field" }, + { 0x22, 0x20, 0x2220, "F_0280", "Saw cutscene about Ilia's scent from pouch", "Ring field" }, + { 0x22, 0x10, 0x2210, "F_0281", "Malo Mart opens in Castle Town", "Shop" }, + { 0x22, 0x08, 0x2208, "F_282", "First conversation with Yeto at peak after LV5 dungeon clear", "Snowpeak mountain" }, + { 0x22, 0x04, 0x2204, "F_283", "Get wood carving", "Ring field" }, + { 0x22, 0x02, 0x2202, "F_284", "Already have a score recorded for Plumm's game", "Lake Hylia" }, + { 0x22, 0x01, 0x2201, "F_285", "First conversation with Plumm as wolf", "Lake Hylia" }, + { 0x23, 0x80, 0x2380, "F_286", "Cleared Plumm's attraction (get heart piece)", "Lake Hylia" }, + { 0x23, 0x40, 0x2340, "F_287", "Handed wood carving to Ilia", "Kakariko Village" }, + { 0x23, 0x20, 0x2320, "F_288", "[cutscene: ] Ilia gets her memories back", "Kawagoe Cutscene" }, + { 0x23, 0x10, 0x2310, "F_289", "Heard conversation about entering double clawshot game LV1", "Castle Town" }, + { 0x23, 0x08, 0x2308, "F_290", "Double clawshot game LV1 cleared", "Castle Town" }, + { 0x23, 0x04, 0x2304, "M_097", "Can use magic", "main event" }, + { 0x23, 0x02, 0x2302, "F_0292", "Star Game 2 first experience", "Castle Town" }, + { 0x23, 0x01, 0x2301, "F_0293", "Star Game 2 cleared", "Castle Town" }, + { 0x24, 0x80, 0x2480, "F_0294", "Shaman - Prayers reached the heavens (heart piece obtained complete)", "Castle Town" }, + { 0x24, 0x40, 0x2440, "F_0295", "Watched meeting Louise event when sneaking into Telma's shop (wolf)", "Castle Town" }, + { 0x24, 0x20, 0x2420, "F_0296", "Spoke with Louise after kicked being kicked out of Telma's shop", "Castle Town" }, + { 0x24, 0x10, 0x2410, "F_0297", "First spoke with Chudley store clerk", "Castle Town" }, + { 0x24, 0x08, 0x2408, "F_0298", "Spoke with Auru after completing LV4 dungeon", "Castle Town" }, + { 0x24, 0x04, 0x2404, "F_0299", "Spoke with Shad at Telma's shop after getting master sword", "Castle Town" }, + { 0x24, 0x02, 0x2402, "F_0300", "Heard about Sky People from Shad at Telma's shop", "Castle Town" }, + { 0x24, 0x01, 0x2401, "F_0301", "Spoke with Shad after LV7 dungeon clear", "Castle Town" }, + { 0x25, 0x80, 0x2580, "F_0302", "Saw cutscene of Shad casting spells underneat Kakariko Village", "Kakariko Village" }, + { 0x25, 0x40, 0x2540, "F_0303", "Saw Shad's spell 2", "Kakariko Village" }, + { 0x25, 0x20, 0x2520, "F_0304", "Spoke with Auru at Hylia Lake", "Lake Hylia" }, + { 0x25, 0x10, 0x2510, "F_0305", "Heard about Fyer from Auru (desert cannon ON)", "Lake Hylia" }, + { 0x25, 0x08, 0x2508, "F_0306", "Used Fyer's cannon to go to desert", "Desert" }, + { 0x25, 0x04, 0x2504, "F_0307", "Spoke with Yeto at top of mountain as wolf", "Snowpeak mountain" }, + { 0x25, 0x02, 0x2502, "F_0308", "Watched first meeting event with Yeto at top of mountain (human)", "Snowpeak mountain" }, + { 0x25, 0x01, 0x2501, "F_0309", "First conversation with Agetha inside (gives golden bug capture quest)", "Castle Town" }, + { 0x26, 0x80, 0x2680, "F_0310", "Hand Auru's note to Fyer", "Lake Hylia" }, + { 0x26, 0x40, 0x2640, "F_0311", "LV8 Dungeon use 1", "LV8 Dungeon" }, + { 0x26, 0x20, 0x2620, "F_0312", "LV8 Dungeon use 2", "LV8 Dungeon" }, + { 0x26, 0x10, 0x2610, "F_0313", "LV8 Dungeon use 3", "LV8 Dungeon" }, + { 0x26, 0x08, 0x2608, "F_0314", "LV8 Dungeon use 4", "LV8 Dungeon" }, + { 0x26, 0x04, 0x2604, "F_0315", "LV8 Dungeon use 5", "LV8 Dungeon" }, + { 0x26, 0x02, 0x2602, "F_0316", "LV8 Dungeon use 6", "LV8 Dungeon" }, + { 0x26, 0x01, 0x2601, "F_0317", "LV8 Dungeon use 7", "LV8 Dungeon" }, + { 0x27, 0x80, 0x2780, "F_0318", "LV8 Dungeon use 8", "LV8 Dungeon" }, + { 0x27, 0x40, 0x2740, "F_0319", "LV8 Dungeon use 9", "LV8 Dungeon" }, + { 0x27, 0x20, 0x2720, "F_0320", "LV8 Dungeon use 10", "LV8 Dungeon" }, + { 0x27, 0x10, 0x2710, "F_0321", "Showed reciept to town doctor", "Castle Town" }, + { 0x27, 0x08, 0x2708, "F_0322", "Flow 0010 Spoke with Zora in Zora armor", "Zora's Domain" }, + { 0x27, 0x04, 0x2704, "F_0323", "Flow 0020 Spoke with Zora", "Zora's Domain" }, + { 0x27, 0x02, 0x2702, "F_0324", "Flow 0023 Speak with Zora before blowing up magma rock", "Zora's Domain" }, + { 0x27, 0x01, 0x2701, "F_0325", "Flow 0023 Speak with Zora after blowing up magma rock", "Zora's Domain" }, + { 0x28, 0x80, 0x2880, "F_0326", "LV8 mini-boss defeated flag", "LV8 Dungeon" }, + { 0x28, 0x40, 0x2840, "F_0327", "First time warped by Ooccoo Sr.", "Misc." }, + { 0x28, 0x20, 0x2820, "F_0328", "Talk again with Jaggle after finding kids", "Ordon Village" }, + { 0x28, 0x10, 0x2810, "F_0329", "Jaggle brings up shield", "Ordon Village" }, + { 0x28, 0x08, 0x2808, "F_0330", "Meet again with Uli for the first time (first forced conversation)", "Ordon Village" }, + { 0x28, 0x04, 0x2804, "F_0331", "Meet again and talk with Uli after finding kids (1st time)", "Ordon Village" }, + { 0x28, 0x02, 0x2802, "F_0332", "Meet again and talk with Uli after finding kids (2nd time)", "Ordon Village" }, + { 0x28, 0x01, 0x2801, "F_0333", "Uli brings up the sword", "Ordon Village" }, + { 0x29, 0x80, 0x2980, "F_0334", "Spoke with Ashei at Telma's shop after getting master sword", "Castle Town" }, + { 0x29, 0x40, 0x2940, "F_0335", "Obtained scribble from Ashei at mountain pass", "Snowpeak mountain" }, + { 0x29, 0x20, 0x2920, "F_0336", "Blew up molten rock bomb ", "Zora's Domain" }, + { 0x29, 0x10, 0x2910, "F_0337", "Listened to old ladies A/B conversation (wolf / Ralis hint FLOW104)", "Castle Town" }, + { 0x29, 0x08, 0x2908, "F_0338", "Obtained 1 secret techinques - Shield attack", "Secret techniques" }, + { 0x29, 0x04, 0x2904, "F_0339", "Obtained 2 secret techinques", "Secret techniques" }, + { 0x29, 0x02, 0x2902, "F_0340", "Obtained 3 secret techinques", "Secret techniques" }, + { 0x29, 0x01, 0x2901, "F_0341", "Obtained 4 secret techinques", "Secret techniques" }, + { 0x2A, 0x80, 0x2A80, "F_0342", "Obtained 5 secret techinques", "Secret techniques" }, + { 0x2A, 0x40, 0x2A40, "F_0343", "Obtained 6 secret techinques", "Secret techniques" }, + { 0x2A, 0x20, 0x2A20, "F_0344", "Obtained 7 secret techinques", "Secret techniques" }, + { 0x2A, 0x10, 0x2A10, "F_0345", "Opening 2nd day - lit Coro's pot", "Faron Woods" }, + { 0x2A, 0x08, 0x2A08, "F_0346", "Turned down Auru's request to go to the desert", "Lake Hylia" }, + { 0x2A, 0x04, 0x2A04, "F_0347", "Spoke with Ashei after LV5 dungeon clear ", "Castle Town" }, + { 0x2A, 0x02, 0x2A02, "F_0348", "Spoke with Zora soldier in front of LV3 dungeon (before bombing entrance)", "Lake Hylia" }, + { 0x2A, 0x01, 0x2A01, "F_0349", "Spoke with Zora soldier in front of LV3 dungeon (after bombing entrance)", "Lake Hylia" }, + { 0x2B, 0x80, 0x2B80, "F_0350", "Showed wood carving doll to Renado", "Kakariko Village" }, + { 0x2B, 0x40, 0x2B40, "F_0351", "First meeting with Agether outside (haven't met inside yet) : gives bug even quest", "Ring field" }, + { 0x2B, 0x20, 0x2B20, "F_0352", "Second conversation with Agetha (outside)", "Ring field" }, + { 0x2B, 0x10, 0x2B10, "F_0353", "Conversation with lady complaining about Chudley's shop", "Castle Town" }, + { 0x2B, 0x08, 0x2B08, "F_0354", "[cutscene] Mirror complete", "Kawagoe Cutscene" }, + { 0x2B, 0x04, 0x2B04, "F_0355", "Showed wood carving to village doctor", "Castle Town" }, + { 0x2B, 0x02, 0x2B02, "F_0356", "Spoke with village doctor after Ilia's memories restored", "Castle Town" }, + { 0x2B, 0x01, 0x2B01, "F_0357", "Spoke with Telma's guardian goron after she came back", "Death Mountain" }, + { 0x2C, 0x80, 0x2C80, "F_0358", "Spoke with Telma after Ilia's memories restored", "Castle Town" }, + { 0x2C, 0x40, 0x2C40, "F_0359", "Spoke with Ilia while she's watching over Ralis", "Kakariko Village" }, + { 0x2C, 0x20, 0x2C20, "F_0360", "Conversation with fortune teller as wolf", "Castle Town" }, + { 0x2C, 0x10, 0x2C10, "F_0361", "Spun the spinning pillars", "LV4 Dungeon," }, + { 0x2C, 0x08, 0x2C08, "F_0362", "Properly spoke with Resistance Rusl again (2nd time)", "Castle Town" }, + { 0x2C, 0x04, 0x2C04, "F_0363", "Stole sword from Rusl in Ordon Village at night (wolf)", "Ordon Village" }, + { 0x2C, 0x02, 0x2C02, "F_0364", "Listened to monkey girl's laments (Twilight)", "Faron Woods" }, + { 0x2C, 0x01, 0x2C01, "F_0365", "Spoke with Gor Liggs after LV dungeon clear", "Death Mountain (room)" }, + { 0x2D, 0x80, 0x2D80, "F_0366", "Spoke with Gor Liggs after Ilia's memories restored", "Death Mountain (room)" }, + { 0x2D, 0x40, 0x2D40, "F_0367", "Spoke with goron blocking mountain path after LV2 dungeon clear", "Death Mountain" }, + { 0x2D, 0x20, 0x2D20, "F_0368", "First conversation wtih goron in front of hotel", "Kakariko Village" }, + { 0x2D, 0x10, 0x2D10, "F_0369", "First conversation with goron in front of watch tower", "Kakariko Village" }, + { 0x2D, 0x08, 0x2D08, "F_0370", "First converstaion with Karakiko springs goron (adult)", "Kakariko Village" }, + { 0x2D, 0x04, 0x2D04, "F_0371", "First conversation wtih goron in front of bomb shop (while only selling bombs)", "Kakariko Village" }, + { 0x2D, 0x02, 0x2D02, "F_0372", "Tried to by milk before saving cat", "Ordon Village" }, + { 0x2D, 0x01, 0x2D01, "F_0373", "Coversation with Darbus after Ilia's memories restored", "Death Mountain" }, + { 0x2E, 0x80, 0x2E80, "F_0374", "Conversation with Darbus in wrestling room after LV2 dungeon clear", "Death Mountain" }, + { 0x2E, 0x40, 0x2E40, "F_0375", "First conversation with fundraising goron at Malo Mart", "Kakariko Village" }, + { 0x2E, 0x20, 0x2E20, "F_0376", "Gathered funds for bridge repair! (set by program after raising funds)", "Kakariko Village" }, + { 0x2E, 0x10, 0x2E10, "F_0377", "Goron spring water shop open!", "Castle Town" }, + { 0x2E, 0x08, 0x2E08, "F_0378", "Darbus destroyed hidden village boulder", "Ring field" }, + { 0x2E, 0x04, 0x2E04, "F_0379", "Completed golden bugs", "N/A" }, + { 0x2E, 0x02, 0x2E02, "F_0380", "Complete first meeting with Agetha (Recieved bug collection quest)", "N/A" }, + { 0x2E, 0x01, 0x2E01, "F_0381", "First conversation with parent goron after spring water shop opens", "Castle Town" }, + { 0x2F, 0x80, 0x2F80, "F_0382", "Listened to laments of fallen goron (while bridge broken)", "Castle Town" }, + { 0x2F, 0x40, 0x2F40, "F_0383", "Spoke with child goron after spring water shop opens", "Castle Town" }, + { 0x2F, 0x20, 0x2F20, "F_0384", "Spring water shop - Spoke with child goron while shop closed", "Castle Town" }, + { 0x2F, 0x10, 0x2F10, "F_0385", "First conversation with Barnes after stocking water bombs", "Kakariko Village" }, + { 0x2F, 0x08, 0x2F08, "F_0386", "Conversation with Barnes after he starts selling Bomblings and landmines", "Kakariko Village" }, + { 0x2F, 0x04, 0x2F04, "F_0387", "Learned scent of medicine", "Castle Town" }, + { 0x2F, 0x02, 0x2F02, "F_0388", "Graveyard - Spoke with Ralis after changing scene (before obtaining earrings)", "Kakariko Village" }, + { 0x2F, 0x01, 0x2F01, "F_0389", "First conversation with goron digging hole in south", "Ring field" }, + { 0x30, 0x80, 0x3080, "F_0390", "First conversation after saving Jovani", "Castle Town" }, + { 0x30, 0x40, 0x3040, "F_0391", "Gave spring water to goron south of castle town", "Ring field" }, + { 0x30, 0x20, 0x3020, "F_0392", "spoke with goron digging hole after opening caslt town south road", "Ring field" }, + { 0x30, 0x10, 0x3010, "F_0393", "First conversation at Poe shop (generic Poe appearance)", "Castle Town" }, + { 0x30, 0x08, 0x3008, "F_0394", "Received first key from chibi elder (204)", "LV2 Dungeon" }, + { 0x30, 0x04, 0x3004, "F_0395", "First conversation with Gor Liggs in Kakariko Village", "Kakariko Village" }, + { 0x30, 0x02, 0x3002, "F_0396", "Failed to carry hot spring water (speak with elder to reset)", "Kakariko Village" }, + { 0x30, 0x01, 0x3001, "F_0397", "Gor Liggs conversation sequence B", "Kakariko Village" }, + { 0x31, 0x80, 0x3180, "F_0398", "Gor Liggs conversation sequence C", "Kakariko Village" }, + { 0x31, 0x40, 0x3140, "F_0399", "Conversation with Gor Liggs - hot spring game available", "Kakariko Village" }, + { 0x31, 0x20, 0x3120, "F_0400", "Warped sky cannon to Lake Hylia", "Kakariko Village" }, + { 0x31, 0x10, 0x3110, "F_0401", "Beetle (M)", "Golden bugs" }, + { 0x31, 0x08, 0x3108, "F_0402", "Beetle (F)", "Golden bugs" }, + { 0x31, 0x04, 0x3104, "F_0403", "Butterfly (M)", "Golden bugs" }, + { 0x31, 0x02, 0x3102, "F_0404", "Butterfly (F)", "Golden bugs" }, + { 0x31, 0x01, 0x3101, "F_0405", "Stag beetle (M)", "Golden bugs" }, + { 0x32, 0x80, 0x3280, "F_0406", "Stag beetle (F)", "Golden bugs" }, + { 0x32, 0x40, 0x3240, "F_0407", "Grasshopper (M)", "Golden bugs" }, + { 0x32, 0x20, 0x3220, "F_0408", "Grasshopper (F)", "Golden bugs" }, + { 0x32, 0x10, 0x3210, "F_0409", "Phasmid (M)", "Golden bugs" }, + { 0x32, 0x08, 0x3208, "F_0410", "Phasmid (F)", "Golden bugs" }, + { 0x32, 0x04, 0x3204, "F_0411", "Pill bug (M)", "Golden bugs" }, + { 0x32, 0x02, 0x3202, "F_0412", "Pill bug (F)", "Golden bugs" }, + { 0x32, 0x01, 0x3201, "F_0413", "Mantis (M)", "Golden bugs" }, + { 0x33, 0x80, 0x3380, "F_0414", "Mantis (F)", "Golden bugs" }, + { 0x33, 0x40, 0x3340, "F_0415", "Ladybug (M)", "Golden bugs" }, + { 0x33, 0x20, 0x3320, "F_0416", "Ladybug (F)", "Golden bugs" }, + { 0x33, 0x10, 0x3310, "F_0417", "Snail (M)", "Golden bugs" }, + { 0x33, 0x08, 0x3308, "F_0418", "Snail (F)", "Golden bugs" }, + { 0x33, 0x04, 0x3304, "F_0419", "Dragonfly (M)", "Golden bugs" }, + { 0x33, 0x02, 0x3302, "F_0420", "Dragonfly (F)", "Golden bugs" }, + { 0x33, 0x01, 0x3301, "F_0421", "Ant (M)", "Golden bugs" }, + { 0x34, 0x80, 0x3480, "F_0422", "Ant (F)", "Golden bugs" }, + { 0x34, 0x40, 0x3440, "F_0423", "Dayfly (M)", "Golden bugs" }, + { 0x34, 0x20, 0x3420, "F_0424", "Dayfly (F)", "Golden bugs" }, + { 0x34, 0x10, 0x3410, "F_0425", "[Captured in bottle] Beetle (M)", "Golden bugs" }, + { 0x34, 0x08, 0x3408, "F_0426", "[Captured in bottle] Beetle (F)", "Golden bugs" }, + { 0x34, 0x04, 0x3404, "F_0427", "[Captured in bottle] Butterfly (M)", "Golden bugs" }, + { 0x34, 0x02, 0x3402, "F_0428", "[Captured in bottle] Butterfly (F)", "Golden bugs" }, + { 0x34, 0x01, 0x3401, "F_0429", "[Captured in bottle] Stag beetle (M)", "Golden bugs" }, + { 0x35, 0x80, 0x3580, "F_0430", "[Captured in bottle] Stag beetle (F)", "Golden bugs" }, + { 0x35, 0x40, 0x3540, "F_0431", "[Captured in bottle] Grasshopper (M)", "Golden bugs" }, + { 0x35, 0x20, 0x3520, "F_0432", "[Captured in bottle] Grasshopper (F)", "Golden bugs" }, + { 0x35, 0x10, 0x3510, "F_0433", "[Captured in bottle] Phasmid (M)", "Golden bugs" }, + { 0x35, 0x08, 0x3508, "F_0434", "[Captured in bottle] Phasmid (F)", "Golden bugs" }, + { 0x35, 0x04, 0x3504, "F_0435", "[Captured in bottle] Pill bug (M)", "Golden bugs" }, + { 0x35, 0x02, 0x3502, "F_0436", "[Captured in bottle] Pill bug (F)", "Golden bugs" }, + { 0x35, 0x01, 0x3501, "F_0437", "[Captured in bottle] Mantis (M)", "Golden bugs" }, + { 0x36, 0x80, 0x3680, "F_0438", "[Captured in bottle] Mantis (F)", "Golden bugs" }, + { 0x36, 0x40, 0x3640, "F_0439", "[Captured in bottle] Ladybug (M)", "Golden bugs" }, + { 0x36, 0x20, 0x3620, "F_0440", "[Captured in bottle] Ladybug (F)", "Golden bugs" }, + { 0x36, 0x10, 0x3610, "F_0441", "[Captured in bottle] Snail (M)", "Golden bugs" }, + { 0x36, 0x08, 0x3608, "F_0442", "[Captured in bottle] Snail (F)", "Golden bugs" }, + { 0x36, 0x04, 0x3604, "F_0443", "[Captured in bottle] Dragonfly (M)", "Golden bugs" }, + { 0x36, 0x02, 0x3602, "F_0444", "[Captured in bottle] Dragonfly (F)", "Golden bugs" }, + { 0x36, 0x01, 0x3601, "F_0445", "[Captured in bottle] Ant (M)", "Golden bugs" }, + { 0x37, 0x80, 0x3780, "F_0446", "[Captured in bottle] Ant (F)", "Golden bugs" }, + { 0x37, 0x40, 0x3740, "F_0447", "[Captured in bottle] Dayfly (M)", "Golden bugs" }, + { 0x37, 0x20, 0x3720, "F_0448", "[Captured in bottle] Dayfly (F)", "Golden bugs" }, + { 0x37, 0x10, 0x3710, "F_0449", "Talk with Gor Liggs again - first time after talking iwth Hot spring water parent goron (before fundraising)", "Kakariko Village" }, + { 0x37, 0x08, 0x3708, "F_0450", "Double Clawshot shop final stage first conversation", "Castle Town" }, + { 0x37, 0x04, 0x3704, "F_0453", "Postman first appears", "Ring field" }, + { 0x37, 0x02, 0x3702, "F_0454", "Recieved 3rd key from fundraising elder (206)", "LV2 Dungeon" }, + { 0x37, 0x01, 0x3701, "F_0455", "Recieved 3rd key from fundraising elder (205)", "LV2 Dungeon" }, + { 0x38, 0x80, 0x3880, "F_0456", "First time meeting with Jovani", "Castle Town" }, + { 0x38, 0x40, 0x3840, "F_0457", "Revived cat", "Castle Town" }, + { 0x38, 0x20, 0x3820, "F_0458", "Coversation with Jovani after collecting 60 ghosts", "Castle Town" }, + { 0x38, 0x10, 0x3810, "F_0459", "Coversation with Gengle after collecting 40 ghosts", "Castle Town" }, + { 0x38, 0x08, 0x3808, "F_0460", "Coversation with Gengle after collecting 50 ghosts", "Castle Town" }, + { 0x38, 0x04, 0x3804, "F_0461", "First time entered fishing house", "Fishing" }, + { 0x38, 0x02, 0x3802, "F_0462", "Reserved for fishing", "Fishing" }, + { 0x38, 0x01, 0x3801, "F_0463", "Reserved for fishing", "Fishing" }, + { 0x39, 0x80, 0x3980, "F_0464", "Reserved for fishing", "Fishing" }, + { 0x39, 0x40, 0x3940, "F_0465", "Reserved for fishing", "Fishing" }, + { 0x39, 0x20, 0x3920, "F_0466", "Reserved for fishing", "Fishing" }, + //{ 0x39, 0x10, 0x3910, "F_0467", "N/A", "Fishing" }, + { 0x39, 0x08, 0x3908, "F_0468", "Reserved for fishing", "Fishing" }, + { 0x39, 0x04, 0x3904, "F_0469", "Reserved for fishing", "Fishing" }, + { 0x39, 0x02, 0x3902, "F_0470", "Reserved for fishing", "Fishing" }, + { 0x39, 0x01, 0x3901, "F_0471", "Finished 2 wrestling matches against Bo", "Ordon Village" }, + { 0x3A, 0x80, 0x3A80, "F_0472", "Distant howling complete (for secret technique 2)", "Secret techniques" }, + { 0x3A, 0x40, 0x3A40, "F_0473", "Distant howling complete (for secret technique 3)", "Secret techniques" }, + { 0x3A, 0x20, 0x3A20, "F_0474", "Distant howling complete (for secret technique 4)", "Secret techniques" }, + { 0x3A, 0x10, 0x3A10, "F_0475", "Distant howling complete (for secret technique 5)", "Secret techniques" }, + { 0x3A, 0x08, 0x3A08, "F_0476", "Distant howling complete (for secret technique 6)", "Secret techniques" }, + { 0x3A, 0x04, 0x3A04, "F_0477", "Distant howling complete (for secret technique 7)", "Secret techniques" }, + { 0x3A, 0x02, 0x3A02, "F_0478", "Spoke to Ralis who returned to Zora's domain", "Zora's Domain" }, + { 0x3A, 0x01, 0x3A01, "F_0479", "First conversation with Ralis in front of grave", "Kakariko Village" }, + { 0x3B, 0x80, 0x3B80, "F_0480", "Received Coral Earrings from Ralis", "Kakariko Village" }, + { 0x3B, 0x40, 0x3B40, "F_0481", "Beat Yeta at snowboard first time", "Snowpeak mountain" }, + { 0x3B, 0x20, 0x3B20, "F_0482", "After beating Yeto, first conversation with Yeta at peack (challanged to a match)", "N/A" }, + { 0x3B, 0x10, 0x3B10, "F_0483", "First beat Yeta at snowboard (heart piece)", "Snowpeak mountain" }, + { 0x3B, 0x08, 0x3B08, "F_0484", "Completed sky canon repairs!", "Lake Hylia" }, + { 0x3B, 0x04, 0x3B04, "F_0485", "Heard Fyer talk about repairs 1 time", "Lake Hylia" }, + { 0x3B, 0x02, 0x3B02, "F_0486", "Spoke with Luda while Ralis is visiting graveyard", "Kakariko Village" }, + { 0x3B, 0x01, 0x3B01, "F_0487", "Spoke with Luda after Ralis returns home", "Kakariko Village" }, + { 0x3C, 0x80, 0x3C80, "F_0488", "Conversation with goron in front of bomb shop - underwater bomb appears", "Kakariko Village" }, + { 0x3C, 0x40, 0x3C40, "F_0489", "Conversation with goron in front of bomb shop - all bomb types appear", "Kakariko Village" }, + { 0x3C, 0x20, 0x3C20, "F_0490", "Spoke with cucco (thinks Link will eat him)", "Ordon Village" }, + { 0x3C, 0x10, 0x3C10, "F_0491", "Spoke with mini-boss (magnet goron) after LV2 dungeon clear", "LV2 Dungeon" }, + { 0x3C, 0x08, 0x3C08, "F_0492", "Gold wolf disappearance 2", "Secret techniques" }, + { 0x3C, 0x04, 0x3C04, "F_0493", "Gold wolf disappearance 3", "Secret techniques" }, + { 0x3C, 0x02, 0x3C02, "F_0494", "Gold wolf disappearance 4", "Secret techniques" }, + { 0x3C, 0x01, 0x3C01, "F_0495", "Gold wolf disappearance 5", "Secret techniques" }, + { 0x3D, 0x80, 0x3D80, "F_0496", "Gold wolf disappearance 6", "Secret techniques" }, + { 0x3D, 0x40, 0x3D40, "F_0497", "Gold wolf disappearance 7", "Secret techniques" }, + { 0x3D, 0x20, 0x3D20, "F_0498", "Spoke with Beth after Lalis is revived", "Kakariko Village" }, + { 0x3D, 0x10, 0x3D10, "F_0499", "Saved magma goron", "Zora's Domain" }, + { 0x3D, 0x08, 0x3D08, "F_0500", "Met Uli before finding kids (look at sleeping Rusl)", "Ordon Village" }, + { 0x3D, 0x04, 0x3D04, "F_0501", "Cave of Ordeals - B10 first arrival", "sub-dungeon" }, + { 0x3D, 0x02, 0x3D02, "F_0502", "Cave of Ordeals - B20 first arrival", "sub-dungeon" }, + { 0x3D, 0x01, 0x3D01, "F_0503", "Cave of Ordeals - B30 first arrival", "sub-dungeon" }, + { 0x3E, 0x80, 0x3E80, "F_0504", "Cave of Ordeals - B40 first arrival", "sub-dungeon" }, + { 0x3E, 0x40, 0x3E40, "F_0505", "Cave of Ordeals - B50 first arrival (clear)", "sub-dungeon" }, + { 0x3E, 0x20, 0x3E20, "F_0506", "Only met Ooccoo Sr. - not yet son (shared LV1-LV5)", "N/A" }, + { 0x3E, 0x10, 0x3E10, "F_0507", "Also met the son in dungeon with first Ooccoo Sr. meeting - turns 506 OFF (shared LV1-LV5)", "N/A" }, + { 0x3E, 0x08, 0x3E08, "F_0508", "Meb Ooccoo Sr. B - doesnt turn OFF (shared LV1-LV5)", "Ooccoo Sr. stuff" }, + { 0x3E, 0x04, 0x3E04, "F_0509", "Met Ooccoo Sr. second time", "Ooccoo Sr. stuff" }, + { 0x3E, 0x02, 0x3E02, "F_0510", "Watched LV7 dungion start cutscene", "LV7 dungeon" }, + { 0x3E, 0x01, 0x3E01, "F_0513", "Spoke with Colin when neither Ilia nor Ralis are doing well", "Kakariko Village" }, + { 0x3F, 0x80, 0x3F80, "F_0514", "Spoke with Colin after Ralis is revived but before Ilia is revived", "Kakariko Village" }, + { 0x3F, 0x40, 0x3F40, "F_0515", "Spoke with Colin after Ilia and Ralis are revived", "Kakariko Village" }, + { 0x3F, 0x20, 0x3F20, "F_0516", "Told Uli directly about having found kids", "Ordon Village" }, + { 0x3F, 0x10, 0x3F10, "F_0517", "Had normal conversation 1 with Uli after finding kids (before Colin kidnapped)", "Ordon Village" }, + { 0x3F, 0x08, 0x3F08, "F_0518", "Colin kidnapped ~ Heard about giving letter to Colin from sleeping Uli", "Ordon Village" }, + { 0x3F, 0x04, 0x3F04, "F_0519", "Spoke with Juggle after finding children", "Ordon Village" }, + { 0x3F, 0x02, 0x3F02, "F_0520", "Spoke with Sera after finding children", "Ordon Village" }, + { 0x3F, 0x01, 0x3F01, "F_0521", "Spoke with Sera across the counter after finding children (her script is shortened after this)", "Ordon Village" }, + { 0x40, 0x80, 0x4080, "F_0522", "Heard about Sacred Grove from saved monkey girl", "Faron Woods" }, + { 0x40, 0x40, 0x4040, "F_0523", "Spoke with Hanch before finding children", "Ordon Village" }, + { 0x40, 0x20, 0x4020, "F_0524", "Spoke with Hanch after finding children", "Ordon Village" }, + { 0x40, 0x10, 0x4010, "F_0525", "First conversation with underwater Zora bomb seller", "Lake Hylia" }, + { 0x40, 0x08, 0x4008, "F_0526", "[Cutscene: 23] Midna reveals her true form", "Kawagoe Cutscene" }, + { 0x40, 0x04, 0x4004, "F_0527", "Refused Resistance Rusl's request", "Faron Woods" }, + { 0x40, 0x02, 0x4002, "F_0528", "Resistance Rusl summoned golden cucco", "Faron Woods" }, + { 0x40, 0x01, 0x4001, "F_0529", "Spoke with Rusl after clearing LV6 dungion", "Castle Town" }, + { 0x41, 0x80, 0x4180, "F_0530", "Spoke with Pergie after finding children", "Ordon Village" }, + { 0x41, 0x40, 0x4140, "F_0531", "Spoke with Pergie while children are kidnapped", "Ordon Village" }, + { 0x41, 0x20, 0x4120, "F_0532", "Pergie butts in about a shield", "Ordon Village" }, + { 0x41, 0x10, 0x4110, "F_0533", "Spoke with Fado after finding children", "Ordon Village" }, + { 0x41, 0x08, 0x4108, "F_0534", "Spoke with Fado before finding children", "Ordon Village" }, + { 0x41, 0x04, 0x4104, "F_0535", "Spoke with goron in water after recieving reward (bomb bag)", "Zora's Domain" }, + { 0x41, 0x02, 0x4102, "F_0536", "Spoke with goron in water before recieving reward", "Zora's Domain" }, + { 0x41, 0x01, 0x4101, "F_0537", "Had Coro and wolf conversation", "Faron Woods" }, + { 0x42, 0x80, 0x4280, "F_0538", "Spoke with sky person Oocoo Sr. after clearing LV7 dungeon", "LV7 dungeon" }, + { 0x42, 0x40, 0x4240, "F_0539", "Receieved heart piece from Fado for mountain goat chase", "Ordon Village" }, + { 0x42, 0x20, 0x4220, "F_0540", "Heard hint from Midna right after first portal warp", "Faron Woods" }, + //{ 0x42, 0x10, 0x4210, "F_0541", "N/A", "N/A" }, + { 0x42, 0x08, 0x4208, "F_0542", "[Cutscene] Ganon wall disappears (Midna goes crazy)", "Kawagoe Cutscene" }, + { 0x42, 0x04, 0x4204, "F_0543", "Met Resistance Rusl again in the woods", "Faron Woods" }, + { 0x42, 0x02, 0x4202, "F_0544", "Watched cutscene of monkey girl running away after being attacked by puppet", "Faron Woods" }, + { 0x42, 0x01, 0x4201, "F_0545", "Watched cutscene of Ooccoo Sr. parting (after LV6)", "Sacred Grove" }, + { 0x43, 0x80, 0x4380, "F_0546", "East - Spoke with soldier in front of east gate about light (while bridge is broken)", "Castle Town" }, + { 0x43, 0x40, 0x4340, "F_0547", "conversation with Jovani after collecting 40 ghosts", "Castle Town" }, + { 0x43, 0x20, 0x4320, "F_0548", "Opening 3rd day - spoke with Jaggle", "Ordon Village" }, + { 0x43, 0x10, 0x4310, "F_0549", "Heard old lady A, B, soldier set (talking about water) (when there's no water)", "Castle Town" }, + { 0x43, 0x08, 0x4308, "F_0550", "Gain ability to use sense", "main event" }, + { 0x43, 0x04, 0x4304, "F_0551", "LV8 Dungeon control use 1", "LV8 Dungeon" }, + { 0x43, 0x02, 0x4302, "F_0552", "LV8 Dungeon control use 2", "LV8 Dungeon" }, + { 0x43, 0x01, 0x4301, "F_0553", "LV8 Dungeon control use 3", "LV8 Dungeon" }, + { 0x44, 0x80, 0x4480, "F_0554", "LV8 Dungeon control use 4", "LV8 Dungeon" }, + { 0x44, 0x40, 0x4440, "F_0555", "LV8 Dungeon control use 5", "LV8 Dungeon" }, + { 0x44, 0x20, 0x4420, "F_0556", "LV8 Dungeon control use 6", "LV8 Dungeon" }, + { 0x44, 0x10, 0x4410, "F_0557", "LV8 Dungeon control use 7", "LV8 Dungeon" }, + { 0x44, 0x08, 0x4408, "F_0558", "LV8 Dungeon control use 8", "LV8 Dungeon" }, + { 0x44, 0x04, 0x4404, "F_0559", "LV8 Dungeon control use 9", "LV8 Dungeon" }, + { 0x44, 0x02, 0x4402, "F_0560", "LV8 Dungeon control use 10", "LV8 Dungeon" }, + { 0x44, 0x01, 0x4401, "F_0561", "LV8 Dungeon control use 11", "LV8 Dungeon" }, + { 0x45, 0x80, 0x4580, "F_0562", "LV8 Dungeon control use 12", "LV8 Dungeon" }, + { 0x45, 0x40, 0x4540, "F_0563", "Brought back Gengle (after getting 50 souls, dealt with by program)", "Misc." }, + { 0x45, 0x20, 0x4520, "F_0564", "Heard story from solder at Telma's shop (during Twilight)", "Castle Town" }, + { 0x45, 0x10, 0x4510, "F_0565", "2nd Day Complete", "N/A" }, + { 0x45, 0x08, 0x4508, "F_0566", "Letter from Ooccoo Sr. came in OK (no son)", "Ooccoo Sr. stuff" }, + { 0x45, 0x04, 0x4504, "F_0567", "Letter from Ooccoo Sr. came in OK (yes son)", "Ooccoo Sr. stuff" }, + { 0x45, 0x02, 0x4502, "F_0568", "Broke Iza's pots (first offence)", "Zora's River" }, + { 0x45, 0x01, 0x4501, "F_0569", "Compensation demands from Iza (resets if paid)", "Zora's River" }, + { 0x46, 0x80, 0x4680, "F_0570", "Cleared LV8 dungeon", "LV8 Dungeon" }, + { 0x46, 0x40, 0x4640, "F_0571", "Refuse Talo's request (for wooden sword) on 3rd day (first time)", "Ordon Village" }, + { 0x46, 0x20, 0x4620, "F_0572", "Refuse Talo's request (for wooden sword) on 3rd day (again)", "Ordon Village" }, + { 0x46, 0x10, 0x4610, "F_0573", "1st Day - Fado moves to the farm (disappears from in front of Link's house)", "Ordon Village" }, + { 0x46, 0x08, 0x4608, "F_0574", "1st Day - Start mountain goat chasing in evening", "Ranch" }, + { 0x46, 0x04, 0x4604, "F_0575", "1st Day - End mountain goat chasing in evening", "Ranch" }, + { 0x46, 0x02, 0x4602, "F_0576", "2nd Day - Spoke with Fado", "Ordon Village" }, + { 0x46, 0x01, 0x4601, "F_0577", "2nd Day - Retrieved basket from monkey (hit hawk)", "Ordon Village" }, + { 0x47, 0x80, 0x4780, "F_0578", "Spoke to Beth on 3rd day", "Ordon Village" }, + { 0x47, 0x40, 0x4740, "F_0579", "Opening (2nd day) First time talking with Hanch before being attacked by bees", "Ordon Village" }, + { 0x47, 0x20, 0x4720, "F_0580", "1st Day - Blew on Epona's reed whistle at Ordon Spring", "Ordon Village" }, + { 0x47, 0x10, 0x4710, "F_0581", "2nd Day - First time calling the hawk using the grass whistle", "Ordon Village" }, + { 0x47, 0x08, 0x4708, "F_0582", "1st Day - spoke to Ilia before blowing the reed whistle", "Ordon Woods" }, + { 0x47, 0x04, 0x4704, "F_0583", "1st day - Spoke to Ilia after blowing the reed whistle", "Ordon Woods" }, + { 0x47, 0x02, 0x4702, "F_0584", "1st Day - spoke to Rusl", "Ordon Village" }, + { 0x47, 0x01, 0x4701, "F_0585", "1st day - Spoke to Bo", "Ordon Village" }, + { 0x48, 0x80, 0x4880, "F_0586", "Spoke to Bo after defeating mountain goat", "Ordon Village" }, + { 0x48, 0x40, 0x4840, "F_0587", "successfully defeated rampaging mountain goat", "Ordon Village" }, + { 0x48, 0x20, 0x4820, "F_0588", "First time letting rampaging mountain goat escape", "Ordon Village" }, + { 0x48, 0x10, 0x4810, "F_0589", "Sera - spoke on first day", "Ordon Village" }, + { 0x48, 0x08, 0x4808, "F_0590", "2nd Day - Spoke to Malo during Talo's disappearance (after getting lantern)", "Ordon Village" }, + { 0x48, 0x04, 0x4804, "F_0591", "2nd Day - Spoke to Malo right after Talo disappears", "Ordon Woods" }, + { 0x48, 0x02, 0x4802, "F_0592", "2nd Day - Spoke with Beth during Talo's disappearance (after getting lantern)", "Ordon Village" }, + { 0x48, 0x01, 0x4801, "F_0593", "2nd Day - Conversation with Beth right after Talo's disappearance", "Ordon Woods" }, + { 0x49, 0x80, 0x4980, "F_0594", "2nd Day - Colin (conversation after receiving lantern from Coro)", "Ordon Village" }, + { 0x49, 0x40, 0x4940, "F_0595", "2nd Day - Conversation with Colin after sword tutorial and after horse rejection", "Ordon Village" }, + { 0x49, 0x20, 0x4920, "F_0596", "2nd Day - Conversation with Colin after sword tutorial, before horse rejection", "Ordon Village" }, + { 0x49, 0x10, 0x4910, "F_0597", "2nd Day - First time speaking with Bo (after successfully defeating mountain goat)", "Ordon Village" }, + { 0x49, 0x08, 0x4908, "F_0598", "2nd Day - First conversation with Bo", "Ordon Village" }, + { 0x49, 0x04, 0x4904, "F_0599", "2nd Day - tried to enter Bo's house", "Ordon Village" }, + { 0x49, 0x02, 0x4902, "F_0600", "Purchase slingshot", "Ordon Village" }, + { 0x49, 0x01, 0x4901, "F_0601", "Spoke to imprisoned Talo", "Faron Woods" }, + { 0x4A, 0x40, 0x4A40, "F_0700", "First day ends", "N/A" }, + { 0x4A, 0x20, 0x4A20, "F_0701", "Talo discovers monkey in Link's house garden", "N/A" }, + { 0x4A, 0x10, 0x4A10, "F_0702", "Confirmed kidnapped Talo", "Ordon Village" }, + { 0x4A, 0x08, 0x4A08, "F_0606", "Giants switched places", "Sacred Grove" }, + { 0x4A, 0x04, 0x4A04, "F_0607", "Quit Slingshot tutorial", "Ordon Village" }, + { 0x4A, 0x02, 0x4A02, "F_0608", "Began Slingshot tutorial", "Ordon Village" }, + { 0x4A, 0x01, 0x4A01, "F_0609", "Slingshot Tutorial - First time hitting scarcrow torso", "Ordon Village" }, + { 0x4B, 0x80, 0x4B80, "F_0610", "Slingshot Tutorial - Hit it without using focus", "Ordon Village" }, + { 0x4B, 0x40, 0x4B40, "F_0611", "Slingshot Tutorial - Hit it using focusing", "Ordon Village" }, + { 0x4B, 0x20, 0x4B20, "F_0612", "Pachinco Tutorial - Spoke before hitting target", "Ordon Village" }, + { 0x4B, 0x10, 0x4B10, "F_0613", "Slingshot Tutorial - Spoke before getting 2 scarecrow heards", "Ordon Village" }, + { 0x4B, 0x08, 0x4B08, "F_0614", "2nd Day - Heard forced conversation immediately after the slignshot tutorial", "Ordon Village" }, + { 0x4B, 0x04, 0x4B04, "F_0615", "Recieved vessel of light from Lanayru spirit", "Lake Hylia" }, + { 0x4B, 0x02, 0x4B02, "F_0616", "3rd Day - Spoke to Talo/Malo after handing over wooden sword (1st time)", "Ordon Village" }, + { 0x4B, 0x01, 0x4B01, "F_0617", "3rd Day - Spoke to Talo/Malo after handing over wooden sword (2nd time)", "Ordon Village" }, + { 0x4C, 0x80, 0x4C80, "F_0618", "Scooped bee larva into bottle on opening 2nd Day ", "Ordon Village" }, + { 0x4C, 0x40, 0x4C40, "F_0619", "Spoke to Zora soldier (near cannon) in Lake Hylia", "Lake Hylia" }, + { 0x4C, 0x20, 0x4C20, "F_0620", "First caught a Reek Fish", "Zora's Domain" }, + { 0x4C, 0x10, 0x4C10, "F_0621", "Spoke to Hanch on 3rd day (knocked down beehive on 2nd day)", "Ordon Village" }, + { 0x4C, 0x08, 0x4C08, "F_0622", "Opening 2nd day: Spoke to Hanch before being attacked by bees", "Ordon Village" }, + { 0x4C, 0x04, 0x4C04, "F_0623", "Opening 3rd day: Spoke to Hanch before being attacked by bees", "Ordon Village" }, + { 0x4C, 0x02, 0x4C02, "F_0624", "3rd day: Warned by Hanch when climbing vines", "Ordon Village" }, + { 0x4C, 0x01, 0x4C01, "F_0625", "Saved Talo and a monkey", "Faron Woods" }, + { 0x4D, 0x80, 0x4D80, "F_0626", "Received a heart piece from Jovani", "Castle Town" }, + { 0x4D, 0x40, 0x4D40, "F_0627", "Spoke with Jovani at the bar", "Castle Town" }, + { 0x4D, 0x20, 0x4D20, "F_0628", "Received 200 Rupees from Gengle", "Castle Town" }, + { 0x4D, 0x10, 0x4D10, "F_0629", "First conversation iwth Gengle after speaking with Jovani at the bar", "Castle Town" }, + { 0x4D, 0x08, 0x4D08, "F_0630", "(Cutscene 4 - ?) Right after Link is captured (wolf)", "Kawagoe Cutscene" }, + { 0x4A, 0x80, 0x4A80, "F_0631", "Heard when zooming in on fish tank (1)", "Fishing Pond (inside)" }, + { 0x4D, 0x04, 0x4D04, "F_0632", "Heard first time zooming in on fish tank (2)", "Fishing Pond (inside)" }, + { 0x4D, 0x02, 0x4D02, "F_0633", "Heard second time zooming in on fish tank (2)", "Fishing Pond (inside)" }, + { 0x4D, 0x01, 0x4D01, "F_0634", "Heard zooming in on lure (no frog)", "Fishing Pond (inside)" }, + { 0x4E, 0x80, 0x4E80, "F_0635", "Heard zooming in on lure (yes frog)", "Fishing Pond (inside)" }, + { 0x4E, 0x40, 0x4E40, "F_0636", "Heard first time zooming in on frog lure", "Fishing Pond (inside)" }, + { 0x4E, 0x20, 0x4E20, "F_0637", "Cleard all of roll goal game (get frog lure)", "Fishing Pond (inside)" }, + { 0x4E, 0x10, 0x4E10, "F_0638", "Heard first time zooming in on canoe", "Fishing Pond (inside)" }, + { 0x4E, 0x08, 0x4E08, "F_0639", "Heard first time zooming in on hat", "Fishing Pond (inside)" }, + { 0x4E, 0x04, 0x4E04, "F_0640", "Heard first time zooming in on pot", "Fishing Pond (inside)" }, + { 0x4E, 0x02, 0x4E02, "F_0641", "Heard First time zooming in on rug", "Fishing Pond (inside)" }, + { 0x4E, 0x01, 0x4E01, "F_0642", "Heard first time zooming in on book", "Fishing Pond (inside)" }, + { 0x4F, 0x80, 0x4F80, "F_0643", "Heard first time zooming in on old man's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x40, 0x4F40, "F_0644", "Heard first time zooming in on Coro's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x20, 0x4F20, "F_0645", "Heard First time zoomin in on Iza's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x10, 0x4F10, "F_0646", "Heard first time zooming in on Hena's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x08, 0x4F08, "F_0647", "Heard second time zooming in on Hena's photo", "Fishing Pond (inside)" }, + { 0x4F, 0x04, 0x4F04, "F_0648", "Heard Hena's photo 1,2 (additional story)", "Fishing Pond (inside)" }, + { 0x4F, 0x02, 0x4F02, "F_0649", "Heard first time zooming in on Link's picture", "Fishing Pond (inside)" }, + { 0x4F, 0x01, 0x4F01, "F_0650", "Heard second time zooming in on Link's picture", "Fishing Pond (inside)" }, + { 0x50, 0x80, 0x5080, "F_0651", "Caught Greengill with bobber first time", "Fishing" }, + { 0x50, 0x40, 0x5040, "F_0652", "Caught Hylian Bass with bobber first time", "Fishing" }, + { 0x50, 0x20, 0x5020, "F_0653", "Caught Hylian Pike with bobber first time", "Fishing" }, + { 0x50, 0x10, 0x5010, "F_0654", "Caught Hylian Loach using bobber first time", "Fishing" }, + { 0x50, 0x08, 0x5008, "F_0655", "Caught an Ordon Catfish using bobber first time", "Fishing" }, + { 0x50, 0x04, 0x5004, "F_0656", "Caught something (any fish) using lure first time", "Fishing" }, + { 0x50, 0x02, 0x5002, "F_0657", "Caught Hylian Loach in front of Hena first time", "Fishing" }, + { 0x50, 0x01, 0x5001, "F_0658", "Recieved large wallet from Agitha", "Castle Town" }, + { 0x51, 0x80, 0x5180, "F_0659", "Hear information about spirit spring in Lake Hylia from Twilight Soldier or map", "Castle Town" }, + { 0x51, 0x40, 0x5140, "F_0660", "Twilight Lake Hylia Listened to Zora soldier A talk", "Lake Hylia" }, + { 0x51, 0x20, 0x5120, "F_0661", "Twilight Lake Hylia Listened to Zora soldier B&C talk", "Lake Hylia" }, + { 0x51, 0x10, 0x5110, "F_0662", "Omit rules after clearing roll goal game once", "Fishing Pond (inside)" }, + { 0x51, 0x08, 0x5108, "F_0663", "Listened to Purdy's unnecessary words", "Fishing Pond (inside)" }, + { 0x51, 0x04, 0x5104, "F_0664", "After clearing roll goal game / first attempt", "Fishing Pond (inside)" }, + { 0x51, 0x02, 0x5102, "F_0665", "First zoom in Roll goal game", "Fishing Pond (inside)" }, + { 0x51, 0x01, 0x5101, "F_0666", "Showed Yeto sketch to Zora soldier next to Snowpeak Mountain", "Zora's Domain" }, + { 0x52, 0x80, 0x5280, "F_0667", "Showed Yeto sketch to average Zora soldier (generic)", "Zora's Domain" }, + { 0x52, 0x40, 0x5240, "F_0668", "Showed Yeto sketch to Zora civilian (generic)", "Zora's Domain" }, + { 0x52, 0x20, 0x5220, "F_0669", "Pulled on Fyrus chains at least once", "LV2 Dungeon" }, + { 0x52, 0x10, 0x5210, "F_0670", "Hitting knocked-down Fyrus", "LV2 Dungeon" }, + { 0x52, 0x08, 0x5208, "F_0671", "Through magnet goron into lava once", "LV2 Dungeon" }, + { 0x52, 0x04, 0x5204, "F_0672", "First time seeing magnet goron tumbling", "LV2 Dungeon" }, + { 0x52, 0x02, 0x5202, "F_0673", "Heard hint about Fyrus's weakness (F0215)", "LV2 Dungeon" }, + { 0x52, 0x01, 0x5201, "F_0674", "Light - Spoke with cafe table: townsperson A1", "Castle Town" }, + { 0x53, 0x80, 0x5380, "F_0675", "Light - cafe counter: Spoke with townsgirl A1 (shop clerk)", "Castle Town" }, + { 0x53, 0x40, 0x5340, "F_0676", "Light - Spoke with Cafe customer: Townsperson F1 (just the man)", "Castle Town" }, + { 0x53, 0x20, 0x5320, "F_0677", "Light - Spoke with Cafe customer B: Townsperson D1 and Man B1", "Castle Town" }, + { 0x53, 0x10, 0x5310, "F_0678", "Spoke with Group A (Town girl C2, Boy B1, Town girl D1) in front of fountain", "Castle Town" }, + { 0x53, 0x08, 0x5308, "F_0679", "Cheated during Roll goal game", "Fishing Pond (inside)" }, + { 0x53, 0x04, 0x5304, "F_0680", "Heard Midna's hint after cutscene of ghost escaping", "LV4 Dungeon," }, + { 0x53, 0x02, 0x5302, "F_0681", "Heard Midna's hint after defeating first ghost", "LV4 Dungeon," }, + { 0x53, 0x01, 0x5301, "F_0682", "First conversation with Oocca Shopkeeper", "LV7 dungeon" }, + { 0x54, 0x80, 0x5480, "F_0683", "Spoke with Group B (Old lady A1, Boy A1, Town girl B1) in central square", "Castle Town" }, + { 0x54, 0x40, 0x5440, "F_0684", "Look at R00 statue using sense", "LV6 Dungeon" }, + { 0x54, 0x20, 0x5420, "F_0685", "(Cutscene 32) Sage appears, get first Mirror of Twilight shard", "Kawagoe Cutscene" }, + { 0x54, 0x10, 0x5410, "F_0686", "Get fused shadow piece (final mask)", "LV8 Dungeon" }, + { 0x54, 0x08, 0x5408, "F_0687", "Spoke with town girl D1 in front of south road hot spring shop (CLOSED)", "Castle Town" }, + { 0x54, 0x04, 0x5404, "F_0688", "Spoke with town girl D1 in front of south road hot spring shop (OPEN)", "Castle Town" }, + { 0x54, 0x02, 0x5402, "F_0689", "South Road - Spoke with meat shop villager man C1", "Castle Town" }, + { 0x54, 0x01, 0x5401, "F_0690", "South Road - Spoke with woman A1 in front of fortune teller's shop", "Castle Town" }, + { 0x55, 0x80, 0x5580, "F_0691", "Spoke with south alley children before guarding carriage", "Castle Town" }, + { 0x55, 0x40, 0x5540, "F_0692", "Spoke with south alley children after guarding carriage", "Castle Town" }, + { 0x55, 0x20, 0x5520, "F_0693", "First conversation with Plumm while human", "Lake Hylia" }, + { 0x55, 0x10, 0x5510, "F_0694", "Conversation with Jovani after collecting 20 ghosts", "Castle Town" }, + { 0x55, 0x08, 0x5508, "F_0695", "First time frog lure slips out", "Fishing" }, + { 0x55, 0x04, 0x5504, "F_0696", "Spoke with inquiring Hyrule guard before saving Jovani", "Castle Town" }, + { 0x55, 0x02, 0x5502, "F_0697", "Spoke with inquiring Hyrule guard after saving Jovani", "N/A" }, + { 0x55, 0x01, 0x5501, "F_0698", "Spoke with woman B1 in front of south vegetable stand (before guarding carriage)", "Castle Town" }, + { 0x56, 0x80, 0x5680, "F_0699", "Spoke with woman B1 in front of south vegetable stand (after guarding carriage)", "Castle Town" }, + { 0x56, 0x40, 0x5640, "F_0700B", "South Vegetable vendor - Spoke with Villager D2 (before guarding carriage)", "Castle Town" }, + { 0x56, 0x20, 0x5620, "F_0701B", "South Vegetable vendor - Spoke with Villager D2 (after guarding carriage)", "N/A" }, + { 0x56, 0x10, 0x5610, "F_0702B", "South Baker - Spoke with Boy A2", "Castle Town" }, + { 0x56, 0x08, 0x5608, "F_0703", "Caught Reek fish for first time while fishing", "Fishing" }, + { 0x56, 0x04, 0x5604, "F_0704", "Listened to Iza in Twilight before domain is thawed", "Zora's River" }, + { 0x56, 0x02, 0x5602, "F_0705", "First conversation with Kaeru (frog) during Midna's desperate hour", "Castle Town" }, + { 0x56, 0x01, 0x5601, "F_0706", "First conversation wtih Torako (cat) during Midna's desperate hour", "Castle Town" }, + { 0x57, 0x80, 0x5780, "F_0707", "Midna's desperate hour, first conversation with Mii (cat)", "Castle Town" }, + { 0x57, 0x40, 0x5740, "F_0708", "First conversaton with doctor after completing spirit", "Castle Town" }, + { 0x57, 0x20, 0x5720, "F_0709", "Midna's desperate hour, first conversation wtih Stephanie (cat)", "Castle Town" }, + { 0x57, 0x10, 0x5710, "F_0715", "Speak with dog to west", "Castle Town" }, + { 0x57, 0x08, 0x5708, "F_0716", "Speak with dog to the east", "Castle Town" }, + { 0x57, 0x04, 0x5704, "F_0717", "Wist - Speak to the dog owner", "Castle Town" }, + { 0x57, 0x02, 0x5702, "F_0718", "South - Spoke to old lady at the vegetable stand (Chudley)", "Castle Town" }, + { 0x57, 0x01, 0x5701, "F_0719", "South - Spoke to old lady at the vegetable stand (Malo Mart)", "Castle Town" }, + { 0x58, 0x80, 0x5880, "F_0720", "South - Spoke to fruit stand vendor (before guarding carriage)", "Castle Town" }, + { 0x58, 0x40, 0x5840, "F_0721", "South - Spoke to fruit stand vendor (after guarding carriage)", "Castle Town" }, + { 0x58, 0x20, 0x5820, "F_0722", "East - Spoke to man at the T-shaped street", "Castle Town" }, + { 0x58, 0x10, 0x5810, "F_0723", "East - Spoke to man in the alley", "Castle Town" }, + { 0x58, 0x08, 0x5808, "F_0724", "East - Spoke to two ladies gossiping", "Castle Town" }, + { 0x58, 0x04, 0x5804, "F_0725", "Speak to Renado who is announcing Shad's visit", "Kakariko Village" }, + { 0x58, 0x02, 0x5802, "F_0726", "Talk to Iza as wolf", "Zora's River" }, + { 0x58, 0x01, 0x5801, "F_0727", "First conversation with shoe-shine boy", "Castle Town" }, + { 0x59, 0x80, 0x5980, "F_0728", "Refused entry into Chudley's shop because of dirty shoes", "Castle Town" }, + { 0x59, 0x40, 0x5940, "F_0729", "Right after postman's \"Hey!\"", "Ring field" }, + { 0x59, 0x20, 0x5920, "F_0730", "Rode Iza's boat for the first time", "Zora's River" }, + { 0x59, 0x10, 0x5910, "F_0732", "Spoke with Cucco (After completing Ilia's memory event)", "Hidden Village" }, + { 0x59, 0x08, 0x5908, "F_0733", "Recieved Heart piece reward from Iza for going down the river", "Zora's River" }, + { 0x59, 0x04, 0x5904, "F_0734", "Spoke with wild duck", "Fishing" }, + { 0x59, 0x02, 0x5902, "F_0735", "Spoke with domestic duck", "Fishing" }, + { 0x59, 0x01, 0x5901, "F_0736", "Heard Midna's hint / monkey hint", "LV1 Dungeon" }, + { 0x5A, 0x80, 0x5A80, "F_0737", "First conversation with Udo (cat) during Midna's desperate hour", "Castle Town" }, + { 0x5A, 0x40, 0x5A40, "F_0738", "South - Spoke with female customer at vegetable stand", "Castle Town" }, + { 0x5A, 0x20, 0x5A20, "F_0739", "South - Spoke with old customer at fruit stand", "Castle Town" }, + { 0x5A, 0x10, 0x5A10, "F_0740", "Spoke with child Goron (selling lantern oil) (before Ganon wall)", "Castle Town" }, + { 0x5A, 0x08, 0x5A08, "F_0741", "Spoke with child Goron (selling lantern oil) (after Ganon wall)", "Castle Town" }, + { 0x5A, 0x04, 0x5A04, "F_0742", "Spoke with child Goron (selling red potion)", "Castle Town" }, + { 0x5A, 0x02, 0x5A02, "F_0743", "Spoke with adult Goron (30 arrows) (before Ganon wall)", "Castle Town" }, + { 0x5A, 0x01, 0x5A01, "F_0744", "Spoke with adult Goron (30 arrows) (after Ganon wall)", "Castle Town" }, + { 0x5B, 0x80, 0x5B80, "F_0745", "Spoke with adult Goron (Hylia shield)", "Castle Town" }, + { 0x5B, 0x40, 0x5B40, "F_0746", "Spoke with Cucco A", "Kakariko Village" }, + { 0x5B, 0x20, 0x5B20, "F_0747", "Spoke with Cucco B", "Kakariko Village" }, + { 0x5B, 0x10, 0x5B10, "F_0748", "First conversation with hint hawk", "Ring field" }, + { 0x5B, 0x08, 0x5B08, "F_0749", "After clearing cat game (Heart piece is buried, whether it is obtained depends on the player)", "Hidden Village" }, + { 0x5B, 0x04, 0x5B04, "F_0750", "First conversation with Captain Cucco after beginning cat game activities", "Hidden Village" }, + { 0x5B, 0x02, 0x5B02, "F_0751", "Heard detailed explaination from Captain Cucco for the first time", "Hidden Village" }, + { 0x5B, 0x01, 0x5B01, "F_0752", "Beat cat game again", "Hidden Village" }, + { 0x5C, 0x80, 0x5C80, "F_0753", "Scooped Coro's bad soup for the first time", "Faron Woods" }, + { 0x5C, 0x40, 0x5C40, "F_0754", "Heard Midna's hint / Monkey hint 2 times (SAVE72=ON)", "LV1 Dungeon" }, + { 0x5C, 0x20, 0x5C20, "F_0755", "Twilight Heard Iza's line right after shadow bug appears", "Zora's River" }, + { 0x5C, 0x10, 0x5C10, "F_0756", "Join with Ooccoo Sr.", "LV7 dungeon" }, + { 0x5C, 0x08, 0x5C08, "F_0757", "Had Trill and wolf conversation", "Faron Woods" }, + { 0x5C, 0x04, 0x5C04, "F_0758", "Stole from unmanned shop (For use in Trill wolf conversation. Resets after speaking)", "Faron Woods" }, + { 0x5C, 0x02, 0x5C02, "F_0759", "Opening (3rd day) first conversation with Sera", "Ordon Village" }, + { 0x5C, 0x01, 0x5C01, "B_BTN_GUIDE", "B-button guide", "Misc." }, + { 0x5D, 0x80, 0x5D80, "J_BTN_GUIDE", "X-button guide", "Misc." }, + { 0x5D, 0x40, 0x5D40, "F_0768", "Heard forced conversation with Midna after Hylia Lake introduction cutscene", "Lake Hylia" }, + { 0x5D, 0x20, 0x5D20, "F_0769", "Forced conversation with Midna right after first arrival at upper Zora's river", "Zora's River" }, + { 0x5D, 0x10, 0x5D10, "F_0770", "Twilight (frozen) Heard forced conversation after first visit cutscene", "Zora's Domain" }, + { 0x5D, 0x08, 0x5D08, "F_0771", "Unmanned Shop : last payment stolen", "Faron Woods" }, + { 0x5D, 0x04, 0x5D04, "F_0772", "Unmanned Shop : last payment too little", "Faron Woods" }, + { 0x5D, 0x02, 0x5D02, "F_0773", "Unmanned Shop : last payment too much", "Faron Woods" }, + { 0x5D, 0x01, 0x5D01, "F_0774", "Listened to Midna's conversation after seeing frozen Zora", "Zora's Domain" }, + { 0x5E, 0x80, 0x5E80, "F_0775", "Heard forced conversation with Midna after first Fyer's cannon", "Lake Hylia" }, + { 0x5E, 0x40, 0x5E40, "F_0776", "Link first turned to wolf due to fog in LV8 dungeon", "LV8 Dungeon" }, + { 0x5E, 0x20, 0x5E20, "F_0777", "Spoke to Epona", "Misc." }, + { 0x5E, 0x10, 0x5E10, "F_0778", "Heard forced conversation with Midna after story about spirits after clearing LV1 dungeon", "Faron Woods" }, + { 0x5E, 0x08, 0x5E08, "F_0779", "Heard one hint from Midna while bridge is burning", "Lake Hylia" }, + { 0x5E, 0x04, 0x5E04, "F_0780", "Heard Ilia talk about the messenger from the sky after her memories are restored", "Kakariko Village" }, + { 0x5E, 0x02, 0x5E02, "F_0781", "Spoke to Ilia in church after clearing LV6", "Kakariko Village" }, + { 0x5E, 0x01, 0x5E01, "F_0782", "Spoke to Renado after LV3 complete, before LV6 complete", "Kakariko Village" }, + { 0x5F, 0x80, 0x5F80, "F_0783", "Showed dominion rod to Impaz", "Hidden Village" }, + { 0x5F, 0x40, 0x5F40, "F_0784", "Had normal conversation with Shad after he returns to church basement", "Kakariko Village" }, + { 0x5F, 0x20, 0x5F20, "F_0785", "Shad leaves after attempting to warp sky cannon", "Kakariko Village" }, + { 0x5F, 0x10, 0x5F10, "F_0786", "Forced conversation with Shad when he sees the sky cannon", "Kakariko Village" }, + { 0x5F, 0x08, 0x5F08, "F_0787", "Stopped by Midna when trying to warp the sky cannon", "Kakariko Village" }, + { 0x5F, 0x04, 0x5F04, "F_0788", "Talked to Zora going up the waterfall", "Zora's River" }, + { 0x5F, 0x02, 0x5F02, "F_0789", "Grabbed sweet apple 1 time", "Castle Town" }, + { 0x5F, 0x01, 0x5F01, "F_0790", "Sera faces the cat (outside conversation area when 520 is ON)", "Ordon Village" }, + { 0x60, 0x80, 0x6080, "F_0791", "Sky character 1", "Sky character" }, + { 0x60, 0x40, 0x6040, "F_0792", "Sky character 2", "Sky character" }, + { 0x60, 0x20, 0x6020, "F_0793", "Sky character 3", "Sky character" }, + { 0x60, 0x10, 0x6010, "F_0794", "Sky character 4", "Sky character" }, + { 0x60, 0x08, 0x6008, "F_0795", "Sky character 5", "Sky character" }, + { 0x60, 0x04, 0x6004, "F_0796", "Sky character 6", "Sky character" }, + { 0x60, 0x02, 0x6002, "F_0797", "Have selected \"Take Cannon / Repair\" at Fyer's cannon (when there's no other business)", "Lake Hylia" }, + { 0x60, 0x01, 0x6001, "F_0798", "Heard about Zora from Fyer", "Lake Hylia" }, + { 0x61, 0x80, 0x6180, "F_0799", "Listened to the fallen Goron's complaints (after opening bridge)", "Ring field" }, + { 0x61, 0x40, 0x6140, "F_0800", "After returning to Ordon Woods, until Midna comes out of the shadows (If 800 is ON, Midna can't be called)", "Kawagoe Cutscene" }, + { 0x61, 0x20, 0x6120, "F_0801", "First learned Reek Fish scent", "Zora's Domain" }, + { 0x61, 0x10, 0x6110, "F_0802", "Trill attacks when stealing", "Faron Woods" }, + { 0x61, 0x08, 0x6108, "F_0803", "Talked to Old Lady Impaz after going to Sky", "Hidden Village" }, + { 0x61, 0x04, 0x6104, "F_0804", "Heard Midna's forced conversation immediately after plunge into Eldin Twilight", "Twilight Ring field" }, + { 0x61, 0x02, 0x6102, "F_0805", "Bought Hylian shield from Malo Mart", "Kakariko Village" }, + { 0x61, 0x01, 0x6101, "F_0806", "Talked to Hanch on the first day", "Ordon Village" }, + { 0x62, 0x80, 0x6280, "F_0807", "Talked to Colin on the first day", "Ordon Village" }, + { 0x62, 0x40, 0x6240, "F_0808", "Talked to Beth on the first day", "Ordon Village" }, + { 0x62, 0x20, 0x6220, "F_0809", "3 groupie girls (first conversation where they become Link's groupies)", "Castle Town" }, + { 0x62, 0x10, 0x6210, "F_0810", "Learned ghost's scent", "LV4 Dungeon" }, + { 0x62, 0x08, 0x6208, "F_0811", "Attempted Star Game for the first time", "Castle Town" }, + //{ 0x62, 0x04, 0x6204, "F_0812", "N/A", "N/A" }, + //{ 0x62, 0x02, 0x6202, "F_0813", "N/A", "N/A" }, + //{ 0x62, 0x01, 0x6201, "F_0814", "N/A", "N/A" }, + //{ 0x63, 0x80, 0x6380, "F_0815", "N/A", "N/A" }, + //{ 0x63, 0x40, 0x6340, "F_0816", "N/A", "N/A" }, + //{ 0x63, 0x20, 0x6320, "F_0817", "N/A", "N/A" }, + //{ 0x63, 0x10, 0x6310, "F_0818", "N/A", "N/A" }, + //{ 0x63, 0x08, 0x6308, "F_0819", "N/A", "N/A" }, + //{ 0x63, 0x04, 0x6304, "F_0820", "N/A", "N/A" }, + { 0x63, 0x02, 0x6302, "KORO2_ALLCLEAR", "After all stages (8-8) of roll goal game cleared", "Fishing" }, +}; + + +struct MultiBitEventFlag { + uint8_t byteInd; + const char* description; +}; + +inline const MultiBitEventFlag duskImguiU8Events[] = { + { 0xf2, "Ordon Catfish size record in Fishing Hole (in cm)" }, + { 0xf3, "Hylian Pike size record in Fishing Hole (in cm)" }, + { 0xf4, "Hylian Loach size record in Fishing Hole (in cm)" }, + { 0xf5, "Hyrule Bass size record in Fishing Hole (in cm)" }, + { 0xf6, "Number of Rollgoal Levels beaten" }, +}; + +inline const MultiBitEventFlag duskImguiU16Events[] = { + { 0xf7, "Rupees donated to Charlo" }, + { 0xf9, "Rupees donated towards the current Malo Mart donation goal" }, +}; + +inline const MultiBitEventFlag duskImguiSwappedU16Events[] = { + { 0xfb, "Rupees paid to Trill" }, + { 0xfd, "Rupees owed to Trill" }, +}; + +#endif // !DUSK_IMGUI_EVENTFLAGS_HPP \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index c40e362db8..b0c20b4d46 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -1,6 +1,7 @@ #include "fmt/format.h" #include "imgui.h" +#include "ImGuiEngine.hpp" #include "ImGuiConsole.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiConfig.hpp" @@ -32,15 +33,17 @@ namespace dusk { void ImGuiMenuGame::draw() { if (ImGui::BeginMenu("Game")) { if (ImGui::BeginMenu("Graphics")) { - if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) { - ToggleFullscreen(); - } + if (!IsMobile) { + if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) { + ToggleFullscreen(); + } - if (ImGui::MenuItem("Default Window Size")) { - getSettings().video.enableFullscreen.setValue(false); - VISetWindowFullscreen(false); - VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2); - VICenterWindow(); + if (ImGui::MenuItem("Default Window Size")) { + getSettings().video.enableFullscreen.setValue(false); + VISetWindowFullscreen(false); + VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2); + VICenterWindow(); + } } bool vsync = getSettings().video.enableVsync; @@ -147,7 +150,7 @@ namespace dusk { JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; } - if (ImGui::MenuItem("Exit")) { + if (!IsMobile && ImGui::MenuItem("Exit")) { dusk::IsRunning = false; } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index c24dbd8247..ecbd41e158 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -4,13 +4,13 @@ #include "ImGuiEngine.hpp" #include "ImGuiPreLaunchWindow.hpp" +#include "../file_select.hpp" +#include "../iso_validate.hpp" #include "ImGuiConsole.hpp" #include "dusk/main.h" #include "dusk/settings.h" -#include "../iso_validate.hpp" #include -#include #include #include "aurora/lib/internal.hpp" @@ -46,30 +46,22 @@ static std::string ShowIsoInvalidError(const iso::ValidationError code) { } } -void fileDialogCallback(void* userdata, const char* const* filelist, [[maybe_unused]] int filter) { +void fileDialogCallback(void* userdata, const char* path, const char* error) { auto* self = static_cast(userdata); - self->m_errorString.clear(); - if (filelist != nullptr) { - if (filelist[0] == nullptr) { - // Cancelled - self->m_selectedIsoPath.clear(); - } else { - const auto path = filelist[0]; - const auto ret = iso::validate(path); - if (ret != iso::ValidationError::Success) { - self->m_selectedIsoPath.clear(); - self->m_errorString = std::move(ShowIsoInvalidError(ret)); - return; - } - self->m_selectedIsoPath = path; - getSettings().backend.isoPath.setValue(path); - config::Save(); - } - } else { - // Error occurred + if (error != nullptr) { self->m_selectedIsoPath.clear(); - self->m_errorString = fmt::format("File dialog error: {}", SDL_GetError()); + self->m_errorString = fmt::format("File dialog error: {}", error); + return; } + + if (path == nullptr) { + self->m_selectedIsoPath.clear(); + return; + } + + self->m_selectedIsoPath = path; + getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); + config::Save(); } ImGuiPreLaunchWindow::ImGuiPreLaunchWindow() = default; @@ -144,9 +136,9 @@ void ImGuiPreLaunchWindow::drawMainMenu() { } if (ImGuiButtonCenter("Select disc image...")) { - SDL_ShowOpenFileDialog(&fileDialogCallback, this, aurora::window::get_sdl_window(), - skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), - nullptr, false); + ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), + skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, + false); } } else { if (ImGuiButtonCenter("Start game")) { @@ -187,9 +179,9 @@ void ImGuiPreLaunchWindow::drawOptions() { ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); if (ImGui::Button("Set")) { - SDL_ShowOpenFileDialog(&fileDialogCallback, this, aurora::window::get_sdl_window(), - skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), - nullptr, false); + ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), + skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, + false); } AuroraBackend configuredBackend = BACKEND_AUTO; diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index 2c679308a2..5802c07b47 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -4,6 +4,7 @@ #include "ImGuiConsole.hpp" #include "ImGuiSaveEditor.hpp" +#include "ImGuiEventFlags.hpp" #include "d/d_com_inf_game.h" #include "d/d_item_data.h" @@ -1419,20 +1420,130 @@ namespace dusk { if (ImGui::TreeNode("Event Flags")) { dSv_event_c& event = dComIfGs_getSaveData()->mEvent; - for (int e = 0; e < 255; e++) { - ImGui::Text("%03d ", e); - ImGui::SameLine(80.0f); - for (int i = 7; i >= 0; i--) { - bool flag = event.mEvent[e] & (1 << i); - if (ImGui::Checkbox(fmt::format("##event{0}{1}", e, i).c_str(), &flag)) { - if (flag) - event.mEvent[e] |= (1 << i); - else - event.mEvent[e] &= ~(1 << i); + + static ImGuiTextFilter filter; + filter.Draw(); // Search bar + + ImVec2 flagTableSize = {700, 400}; + if (ImGui::BeginTable("Events", 4, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX | ImGuiTableFlags_Sortable, + flagTableSize)) + { + ImGui::TableSetupScrollFreeze(0, 1); + constexpr int COLUMN_FLAG = 0, COLUMN_NAME = 1, COLUMN_LOC = 2, COLUMN_DESC = 3; + ImGui::TableSetupColumn("Flag"); + ImGui::TableSetupColumn("Name"); + ImGui::TableSetupColumn("Location"); + ImGui::TableSetupColumn("Description"); + ImGui::TableHeadersRow(); + + // if we're sorting by whether the flag is set or not, + // we want to re-sort whenever a flag updates, which means every frame cuz we don't know when it changes. + // otherwise only re-sort when the sort is dirty + if (auto* sort = ImGui::TableGetSortSpecs(); + sort != nullptr && sort->SpecsCount > 0 && + (sort->SpecsDirty || sort->Specs[0].ColumnIndex == COLUMN_FLAG)) + { + auto column = sort->Specs->ColumnIndex; + const auto cmp = [&](const duskImguiEventFlagEntry& l, + const duskImguiEventFlagEntry& r) -> bool { + switch (column) { + case COLUMN_FLAG: + return (bool)event.isEventBit(l.flagID) < + (bool)event.isEventBit(r.flagID); + case COLUMN_NAME: + return l.flagName < r.flagName; + case COLUMN_LOC: + return l.location < r.location; + case COLUMN_DESC: + return l.description < r.description; + } + return false; + }; + + const auto direction = sort->Specs[0].SortDirection; + + if (direction == ImGuiSortDirection_Ascending) { + std::sort(std::begin(duskImguiEventFlags), std::end(duskImguiEventFlags), cmp); + } else { + std::sort(std::rbegin(duskImguiEventFlags), std::rend(duskImguiEventFlags), cmp); } - ImGui::SameLine(); + sort->SpecsDirty = false; } - ImGui::NewLine(); + + for (const auto& e : duskImguiEventFlags) { + if (!filter.PassFilter((e.location + "\n" + e.description + "\n" + e.flagName).c_str())) + { + continue; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool flag = event.getEventReg(e.flagID); + if (ImGui::Checkbox(("##" + e.flagName).c_str(), &flag)) { + if (flag) { + event.onEventBit(e.flagID); + } else { + event.offEventBit(e.flagID); + } + } + + ImGui::TableNextColumn(); + ImGui::Text(e.flagName.c_str()); + ImGui::TableNextColumn(); + ImGui::Text(e.location.c_str()); + ImGui::TableNextColumn(); + ImGui::Text(e.description.c_str()); + } + ImGui::EndTable(); + } + + // event values that are stored as u8s in the event flags + for (const auto& e : duskImguiU8Events) { + int v = event.mEvent[e.byteInd]; + if (ImGui::InputInt(e.description, &v)) { + v = std::clamp(v, 0, 0xff); + event.mEvent[e.byteInd] = (u8)v; + } + } + + // event values that are stored as u16s in the event flags + for (const auto& e : duskImguiU16Events) { + int v = (event.mEvent[e.byteInd] << 8) | event.mEvent[e.byteInd + 1]; + if (ImGui::InputInt(e.description, &v)) { + v = std::clamp(v, 0, 0xffff); + event.mEvent[e.byteInd] = (u8)(v >> 8); + event.mEvent[e.byteInd + 1] = (u8)v; + } + } + + // event values that are stored as swapped u16s in the event flags + for (const auto& e : duskImguiSwappedU16Events) { + int v = (event.mEvent[e.byteInd + 1] << 8) | event.mEvent[e.byteInd]; + if (ImGui::InputInt(e.description, &v)) { + v = std::clamp(v, 0, 0xffff); + event.mEvent[e.byteInd + 1] = (u8)(v >> 8); + event.mEvent[e.byteInd] = (u8)v; + } + } + + if (ImGui::TreeNode("Event Matrix")) { + for (int e = 0; e < 255; e++) { + ImGui::Text("%03d ", e); + ImGui::SameLine(80.0f); + for (int i = 7; i >= 0; i--) { + bool flag = event.mEvent[e] & (1 << i); + if (ImGui::Checkbox(fmt::format("##event{0}{1}", e, i).c_str(), &flag)) { + if (flag) + event.mEvent[e] |= (1 << i); + else + event.mEvent[e] &= ~(1 << i); + } + ImGui::SameLine(); + } + ImGui::NewLine(); + } + ImGui::TreePop(); } ImGui::TreePop(); } diff --git a/src/dusk/ios/FileSelectDialog.h b/src/dusk/ios/FileSelectDialog.h new file mode 100644 index 0000000000..0495b397b1 --- /dev/null +++ b/src/dusk/ios/FileSelectDialog.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +struct SDL_Window; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*IOSFileCallback)(void* userdata, const char* path, const char* error); + +void Dusk_iOS_ShowFileSelect(IOSFileCallback callback, void* userdata, SDL_Window* window, + const SDL_DialogFileFilter* filters, int nfilters, + const char* default_location, bool allow_many); + +#ifdef __cplusplus +} +#endif diff --git a/src/dusk/ios/FileSelectDialog.m b/src/dusk/ios/FileSelectDialog.m new file mode 100644 index 0000000000..e8efa91a69 --- /dev/null +++ b/src/dusk/ios/FileSelectDialog.m @@ -0,0 +1,151 @@ +#include "FileSelectDialog.h" + +#import +#import +#import +#import + +#include +#include +#include +#include + +static void *g_picker_delegate_key = &g_picker_delegate_key; + +static void RunOnMainThread(void (^block)(void)) +{ + if ([NSThread isMainThread]) { + block(); + } else { + dispatch_sync(dispatch_get_main_queue(), block); + } +} + +static NSError *MakeError(NSString *message) +{ + return [NSError errorWithDomain:@"org.twilitrealm.dusk.file-select" + code:1 + userInfo:@{NSLocalizedDescriptionKey: message}]; +} + +static UIViewController *FindTopViewController(UIViewController *controller) +{ + UIViewController *current = controller; + while (current.presentedViewController != nil) { + current = current.presentedViewController; + } + return current; +} + +static UIViewController *PresenterFromWindow(SDL_Window *window) +{ + if (window == nil) { + return nil; + } + + const SDL_PropertiesID props = SDL_GetWindowProperties(window); + if (props == 0) { + return nil; + } + + UIWindow *uiwindow = (__bridge UIWindow *)SDL_GetPointerProperty( + props, SDL_PROP_WINDOW_UIKIT_WINDOW_POINTER, NULL); + if (uiwindow == nil || uiwindow.rootViewController == nil) { + return nil; + } + + return FindTopViewController(uiwindow.rootViewController); +} + +static NSURL *InitialDirectoryURL(const char *default_location) +{ + if (default_location == NULL || *default_location == '\0') { + return nil; + } + + NSString *path = [NSString stringWithUTF8String:default_location]; + NSURL *url = [NSURL fileURLWithPath:path]; + if ([path hasSuffix:@"/"]) { + return url; + } + + return [url URLByDeletingLastPathComponent]; +} + +@interface DocumentPickerDelegate : NSObject + +@property(nonatomic, assign) IOSFileCallback callback; +@property(nonatomic, assign) void *userdata; + +@end + +@implementation DocumentPickerDelegate + +- (void)finishWithPath:(const char *)path error:(const char *)error { + if (self.callback != NULL) { + self.callback(self.userdata, path, error); + } +} + +- (void)documentPicker:(UIDocumentPickerViewController *)controller +didPickDocumentsAtURLs:(NSArray *)urls +{ + NSURL *url = urls.firstObject; + if (url == nil) { + [self finishWithPath:NULL error:NULL]; + return; + } + + [self finishWithPath:url.path.UTF8String error:NULL]; + (void)controller; +} + +- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller +{ + [self finishWithPath:NULL error:NULL]; + (void)controller; +} + +@end + +void Dusk_iOS_ShowFileSelect(IOSFileCallback callback, void *userdata, + SDL_Window *window, + const SDL_DialogFileFilter *filters, int nfilters, + const char *default_location, + bool allow_many) +{ + RunOnMainThread(^{ + @autoreleasepool { + UIViewController *presenter = PresenterFromWindow(window); + if (presenter == nil) { + callback(userdata, NULL, "Failed to find an iOS view controller for the file picker."); + return; + } + + NSLog(@"[ShowFileSelect] presenting picker from %@", NSStringFromClass([presenter class])); + + UIDocumentPickerViewController *picker = + [[UIDocumentPickerViewController alloc] + initForOpeningContentTypes:@[ UTTypeItem ] + asCopy:YES]; + picker.allowsMultipleSelection = allow_many ? YES : NO; + picker.shouldShowFileExtensions = YES; + + NSURL *directory_url = InitialDirectoryURL(default_location); + if (directory_url != nil) { + picker.directoryURL = directory_url; + } + + DocumentPickerDelegate *delegate = [DocumentPickerDelegate new]; + delegate.callback = callback; + delegate.userdata = userdata; + picker.delegate = delegate; + objc_setAssociatedObject(picker, g_picker_delegate_key, delegate, + OBJC_ASSOCIATION_RETAIN_NONATOMIC); + + [presenter presentViewController:picker animated:YES completion:nil]; + (void)filters; + (void)nfilters; + } + }); +} diff --git a/src/m_Do/m_Do_graphic.cpp b/src/m_Do/m_Do_graphic.cpp index d774a7de7d..a56e448645 100644 --- a/src/m_Do/m_Do_graphic.cpp +++ b/src/m_Do/m_Do_graphic.cpp @@ -47,6 +47,8 @@ #endif #if TARGET_PC +#include +#include "aurora/lib/window.hpp" #include "d/actor/d_a_horse.h" #include "dusk/dusk.h" #include "dusk/endian.h" @@ -636,8 +638,9 @@ void mDoGph_gInf_c::setTvSize() { m_invScale = 1.0f / m_scale; #if TARGET_PC - hudAspectScaleDown = 1.3571428f / mDoGph_gInf_c::getAspect(); - hudAspectScaleUp = 1.0f / hudAspectScaleDown; + updateSafeAreaBounds(); + hudAspectScaleUp = getSafeWidthF() / FB_WIDTH_BASE; + hudAspectScaleDown = FB_WIDTH_BASE / getSafeWidthF(); #endif } @@ -765,6 +768,88 @@ void mDoGph_gInf_c::setWideZoomLightProjection(Mtx& m) { #if TARGET_PC f32 mDoGph_gInf_c::hudAspectScaleDown = 1.0f; f32 mDoGph_gInf_c::hudAspectScaleUp = 1.0f; +f32 mDoGph_gInf_c::m_safeMinXF = 0.0f; +f32 mDoGph_gInf_c::m_safeMinYF = 0.0f; +f32 mDoGph_gInf_c::m_safeMaxXF = FB_WIDTH_BASE; +f32 mDoGph_gInf_c::m_safeMaxYF = FB_HEIGHT_BASE; +f32 mDoGph_gInf_c::m_safeWidthF = FB_WIDTH_BASE; +f32 mDoGph_gInf_c::m_safeHeightF = FB_HEIGHT_BASE; + +void mDoGph_gInf_c::updateSafeAreaBounds() { + m_safeMinXF = m_minXF; + m_safeMinYF = m_minYF; + m_safeMaxXF = m_maxXF; + m_safeMaxYF = m_maxYF; + m_safeWidthF = m_widthF; + m_safeHeightF = m_heightF; + + SDL_Window* window = aurora::window::get_sdl_window(); + if (window == NULL) { + return; + } + + const AuroraWindowSize windowSize = aurora::window::get_window_size(); + const f32 windowWidth = static_cast(windowSize.width); + const f32 windowHeight = static_cast(windowSize.height); + if (windowWidth <= 0.0f || windowHeight <= 0.0f) { + return; + } + + SDL_Rect safeRect{}; + if (!SDL_GetWindowSafeArea(window, &safeRect)) { + return; + } + + if (windowSize.native_fb_width == 0 || windowSize.native_fb_height == 0 || + windowSize.fb_width == 0 || windowSize.fb_height == 0) + { + return; + } + + const f32 nativeScaleX = static_cast(windowSize.native_fb_width) / windowWidth; + const f32 nativeScaleY = static_cast(windowSize.native_fb_height) / windowHeight; + + const f32 safeLeft = static_cast(safeRect.x) * nativeScaleX; + const f32 safeTop = static_cast(safeRect.y) * nativeScaleY; + const f32 safeRight = static_cast(safeRect.x + safeRect.w) * nativeScaleX; + const f32 safeBottom = static_cast(safeRect.y + safeRect.h) * nativeScaleY; + + const f32 viewportLeft = + (static_cast(windowSize.native_fb_width) - static_cast(windowSize.fb_width)) * + 0.5f; + const f32 viewportTop = + (static_cast(windowSize.native_fb_height) - static_cast(windowSize.fb_height)) * + 0.5f; + const f32 viewportRight = viewportLeft + static_cast(windowSize.fb_width); + const f32 viewportBottom = viewportTop + static_cast(windowSize.fb_height); + + const f32 leftInset = std::max(0.0f, safeLeft - viewportLeft) * + (m_widthF / static_cast(windowSize.fb_width)); + const f32 topInset = std::max(0.0f, safeTop - viewportTop) * + (m_heightF / static_cast(windowSize.fb_height)); + const f32 rightInset = std::max(0.0f, viewportRight - safeRight) * + (m_widthF / static_cast(windowSize.fb_width)); + const f32 bottomInset = std::max(0.0f, viewportBottom - safeBottom) * + (m_heightF / static_cast(windowSize.fb_height)); + + const f32 safeMinXF = m_minXF + leftInset; + const f32 safeMinYF = m_minYF + topInset; + const f32 safeMaxXF = m_maxXF - rightInset; + const f32 safeMaxYF = m_maxYF - bottomInset; + const f32 safeWidthF = safeMaxXF - safeMinXF; + const f32 safeHeightF = safeMaxYF - safeMinYF; + + if (safeWidthF <= 0.0f || safeHeightF <= 0.0f) { + return; + } + + m_safeMinXF = safeMinXF; + m_safeMinYF = safeMinYF; + m_safeMaxXF = safeMaxXF; + m_safeMaxYF = safeMaxYF; + m_safeWidthF = safeWidthF; + m_safeHeightF = safeHeightF; +} void mDoGph_gInf_c::setWindowSize(AuroraWindowSize const& size) { JUTVideo::getManager()->setWindowSize(size); diff --git a/src/m_Do/m_Do_lib.cpp b/src/m_Do/m_Do_lib.cpp index 93df5c23cc..b541490353 100644 --- a/src/m_Do/m_Do_lib.cpp +++ b/src/m_Do/m_Do_lib.cpp @@ -154,7 +154,7 @@ void mDoLib_project(Vec* src, Vec* dst, JGeometry::TBox2 viewport) { xSize = FB_WIDTH; } else { #if TARGET_PC - xOffset = mDoGph_gInf_c::getMinXF(); + xOffset = mDoGph_gInf_c::getSafeMinXF(); xSize = viewport.f.x * mDoGph_gInf_c::hudAspectScaleUp; #else xOffset = viewport.i.x; diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 715f9d3c84..784aea3f26 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -133,6 +133,9 @@ bool launchUILoop() { const AuroraEvent* event = aurora_update(); while (event != nullptr && event->type != AURORA_NONE) { switch (event->type) { + case AURORA_SDL_EVENT: + dusk::g_imguiConsole.HandleSDLEvent(event->sdl); + break; case AURORA_WINDOW_RESIZED: preLaunchUIWindowSize = event->windowSize; break; @@ -202,9 +205,9 @@ void main01(void) { if (preLaunchUIWindowSize.width != 0) mDoGph_gInf_c::setWindowSize(preLaunchUIWindowSize); - constexpr double kSimStepSeconds = 1.0 / 30.0; + constexpr float kSimStepSeconds = 1.0 / 30.0; auto previous_time = std::chrono::steady_clock::now(); - double accumulator = kSimStepSeconds; + float accumulator = kSimStepSeconds; do { // 1. Update Window Events @@ -213,6 +216,9 @@ void main01(void) { switch (event->type) { case AURORA_NONE: goto eventsDone; + case AURORA_SDL_EVENT: + dusk::g_imguiConsole.HandleSDLEvent(event->sdl); + break; case AURORA_WINDOW_RESIZED: mDoGph_gInf_c::setWindowSize(event->windowSize); break; @@ -229,7 +235,7 @@ void main01(void) { eventsDone:; auto current_time = std::chrono::steady_clock::now(); - double frame_seconds = std::chrono::duration(current_time - previous_time).count(); + float frame_seconds = std::chrono::duration(current_time - previous_time).count(); previous_time = current_time; accumulator += frame_seconds; @@ -243,12 +249,12 @@ void main01(void) { if (dusk::getSettings().game.enableFrameInterpolation && !dusk::getTransientSettings().skipFrameRateLimit) { dusk::frame_interp::notify_presentation_frame(); - while (accumulator >= kSimStepSeconds) { + if (accumulator >= kSimStepSeconds) { mDoCPd_c::read(); dusk::gyro::read(kSimStepSeconds); fapGm_Execute(); mDoAud_Execute(); - accumulator -= kSimStepSeconds; + accumulator = 0.0f; } dusk::frame_interp::interpolate(static_cast(accumulator / kSimStepSeconds)); { @@ -256,7 +262,7 @@ void main01(void) { cAPIGph_Painter(); } } else { - accumulator = 0.0; + accumulator = 0.0f; // Game Inputs mDoCPd_c::read();