diff --git a/.gitattributes b/.gitattributes index 04926c6b..430dfc15 100644 --- a/.gitattributes +++ b/.gitattributes @@ -9,3 +9,5 @@ **/*.png binary **/*.svg text eol=lf **/*.desktop text eol=lf +assets/widescreen/** filter=lfs diff=lfs merge=lfs -text +assets/hdmusic/** filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb0698b0..41bd1271 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: fail-fast: false matrix: include: - - { name: 'Linux', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: true, linux: true, werror: true, clang-tidy: true } + - { name: 'Linux', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: true, linux: true, werror: true, clang-tidy: true, build-assets: true } - { name: 'Linux (Debug)', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: true, linux: true, werror: true, clang-tidy: true, debug: true } - { name: 'MSVC (x86)', os: 'windows-latest', generator: 'Ninja', dx5: true, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64_x86' } - { name: 'MSVC (x64)', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64' } @@ -43,10 +43,11 @@ jobs: - { name: 'msys2 mingw32', os: 'windows-latest', generator: 'Ninja', dx5: false, config: false, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw32', msys-env: 'mingw-w64-i686', shell: 'msys2 {0}' } - { name: 'msys2 mingw64', os: 'windows-latest', generator: 'Ninja', dx5: false, config: true, mingw: true, werror: true, clang-tidy: true, msystem: 'mingw64', msys-env: 'mingw-w64-x86_64', shell: 'msys2 {0}' } - { name: 'macOS', os: 'macos-latest', generator: 'Ninja', dx5: false, config: true, brew: true, werror: true, clang-tidy: false } - - { name: 'iOS', os: 'macos-15', generator: 'Xcode', dx5: false, config: false, brew: true, werror: true, clang-tidy: false, cmake-args: '-DCMAKE_SYSTEM_NAME=iOS' } + - { name: 'iOS', os: 'macos-15', generator: 'Xcode', dx5: false, config: false, brew: true, werror: true, clang-tidy: false, cmake-args: '-DCMAKE_SYSTEM_NAME=iOS', ios: true } - { name: 'Emscripten', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, emsdk: true, werror: true, clang-tidy: false, cmake-wrapper: 'emcmake' } - { name: 'Nintendo 3DS', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, n3ds: true, werror: true, clang-tidy: false, container: 'devkitpro/devkitarm:latest', cmake-args: '-DCMAKE_TOOLCHAIN_FILE=/opt/devkitpro/cmake/3DS.cmake' } - { name: 'Xbox One', os: 'windows-latest', generator: 'Visual Studio 17 2022', dx5: false, config: false, msvc: true, werror: false, clang-tidy: false, vc-arch: 'amd64', cmake-args: '-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.26100.0', xbox-one: true} + - { name: 'Android', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, android: true, werror: true, clang-tidy: false,} - { name: 'Vita', os: 'ubuntu-latest', generator: 'Ninja', dx5: false, config: false, vita: true, werror: true, clang-tidy: false, cmake-args: '--preset vita'} steps: - name: Setup vcvars @@ -94,8 +95,15 @@ jobs: if: ${{ matrix.brew }} run: | brew update + brew uninstall cmake || true brew install cmake ninja llvm qt6 echo "LLVM_ROOT=$(brew --prefix llvm)/bin" >> $GITHUB_ENV + + - name: Use latest Xcode + if: ${{ matrix.ios }} + uses: maxim-lobanov/setup-xcode@v1.6.0 + with: + xcode-version: latest-stable - name: Setup Emscripten uses: mymindstorm/setup-emsdk@master @@ -119,23 +127,64 @@ jobs: - uses: actions/checkout@v4 + - name: Checkout LFS + if: ${{ matrix.build-assets }} + run: | + git lfs pull + + - name: Setup Java (Android) + if: ${{ matrix.android }} + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '17' + + - name: Get CMake (Android) + if: ${{ matrix.android }} + uses: lukka/get-cmake@latest + with: + cmakeVersion: 3.30.5 + + - name: Build (Android) + if: ${{ matrix.android }} + env: + SIGNING_KEY_ALIAS: ${{ secrets.keyAlias }} + SIGNING_KEY_PASSWORD: ${{ secrets.keyPassword }} + SIGNING_STORE_PASSWORD: ${{ secrets.keystorePassword }} + SIGNING_STORE_FILE: ${{ github.workspace }}/release.keystore + run: | + echo "${{ secrets.keystore }}" | base64 -d > release.keystore + cd android-project + ./gradlew $([ -n "$SIGNING_KEY_ALIAS" ] && echo packageRelease || echo assembleDebug ) \ + --info \ + -PcmakeArgs="-DCMAKE_BUILD_TYPE=Release \ + -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ + -DISLE_BUILD_CONFIG=${{ !!matrix.config }} \ + -DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \ + -DISLE_WERROR=${{ !!matrix.werror }} \ + -DISLE_DEBUG=${{ matrix.debug || 'OFF' }} \ + -Werror=dev" + - name: Configure (CMake) + if: ${{ !matrix.android }} run: | ${{ matrix.cmake-wrapper || '' }} cmake -S . -B build -G "${{ matrix.generator }}" \ ${{ matrix.cmake-args || '' }} \ -DCMAKE_BUILD_TYPE=Release \ -DISLE_USE_DX5=${{ !!matrix.dx5 }} \ -DISLE_BUILD_CONFIG=${{ !!matrix.config }} \ + -DISLE_BUILD_ASSETS=${{ !!matrix.build-assets }} \ -DENABLE_CLANG_TIDY=${{ !!matrix.clang-tidy }} \ -DISLE_WERROR=${{ !!matrix.werror }} \ -DISLE_DEBUG=${{ matrix.debug || 'OFF' }} \ -Werror=dev - name: Build (CMake) + if: ${{ !matrix.android }} run: cmake --build build --verbose --config Release - name: Package (CPack) - if: ${{ !matrix.n3ds && !matrix.vita }} + if: ${{ !matrix.n3ds && !matrix.android && !matrix.vita }} run: | cd build success=0 @@ -191,6 +240,16 @@ jobs: mkdir dist mv *.vpk dist/ + - name: Package (Android) + if: ${{ matrix.android }} + run: | + mkdir -p build/dist + mv android-project/app/build/outputs/apk/*/*.apk build/dist/ + + - name: Package Assets Separately + if: matrix.build-assets + run: (cd build/assets && zip -r ../dist/isle-assets.zip .) + - name: Upload Build Artifacts uses: actions/upload-artifact@v4 with: @@ -200,6 +259,7 @@ jobs: build/dist/*.AppImage build/dist/*.3dsx build/dist/*.cia + build/dist/*.apk build/dist/*.vpk flatpak: diff --git a/.gitignore b/.gitignore index 1610ba3d..a4bd8a41 100644 --- a/.gitignore +++ b/.gitignore @@ -6,12 +6,14 @@ Release/ /.idea .env .venv +.gradle env/ venv/ ENV/ VENV/ env.bak/ venv.bak/ +local.properties /build/ /build_debug/ /legobin/ diff --git a/.gitmodules b/.gitmodules index 931b1f4f..e2befcdb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "imgui"] path = 3rdparty/imgui url = https://github.com/ocornut/imgui +[submodule "3rdparty/libweaver"] + path = 3rdparty/libweaver + url = https://github.com/isledecomp/SIEdit diff --git a/.lfsconfig b/.lfsconfig new file mode 100644 index 00000000..98f14551 --- /dev/null +++ b/.lfsconfig @@ -0,0 +1,2 @@ +[lfs] + url = https://f9622702b3bbcac0705052a10e62a5a5:31adabe20fe975a919bd1909a6f598f37b0d6486225efa84b38da1aaf27cbd35@assets.isle.pizza/a70a70ae5ebee06d2333bb1132711de1.r2.cloudflarestorage.com/assets diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 1e153bd9..54259e63 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -1,4 +1,5 @@ set(CMAKE_C_CLANG_TIDY) +set(CMAKE_CXX_CLANG_TIDY) if(DOWNLOAD_DEPENDENCIES) include(FetchContent) @@ -72,3 +73,23 @@ target_include_directories(imgui PUBLIC ${imgui_SOURCE_DIR}) target_link_libraries(imgui PUBLIC SDL3::Headers) target_link_libraries(imgui PRIVATE SDL3::SDL3) set_property(TARGET imgui PROPERTY CXX_CLANG_TIDY "") + +if(DOWNLOAD_DEPENDENCIES) + include(FetchContent) + FetchContent_Populate( + libweaver + URL https://github.com/isledecomp/SIEdit/archive/ae447259389f3bf8273c7e7a4844743faf7cbdb8.tar.gz + URL_MD5 dee68424fde8db6d5cef3b9034a8151f + ) +else() + set(libweaver_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libweaver") +endif() + +add_library(libweaver STATIC + ${libweaver_SOURCE_DIR}/lib/core.cpp + ${libweaver_SOURCE_DIR}/lib/file.cpp + ${libweaver_SOURCE_DIR}/lib/interleaf.cpp + ${libweaver_SOURCE_DIR}/lib/object.cpp + ${libweaver_SOURCE_DIR}/lib/sitypes.cpp +) +target_include_directories(libweaver PUBLIC ${libweaver_SOURCE_DIR}/lib) diff --git a/3rdparty/libweaver b/3rdparty/libweaver new file mode 160000 index 00000000..ae447259 --- /dev/null +++ b/3rdparty/libweaver @@ -0,0 +1 @@ +Subproject commit ae447259389f3bf8273c7e7a4844743faf7cbdb8 diff --git a/CMakeLists.txt b/CMakeLists.txt index c6f5ec0b..11daf040 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,13 +42,14 @@ find_program(SDL_SHADERCROSS_BIN NAMES "shadercross") find_package(Python3 3.12 COMPONENTS Interpreter) option(ISLE_BUILD_APP "Build isle application" ON) +option(ISLE_BUILD_ASSETS "Build assets from the /assets directory" OFF) option(ISLE_ASAN "Enable Address Sanitizer" OFF) option(ISLE_UBSAN "Enable Undefined Behavior Sanitizer" OFF) option(ISLE_WERROR "Treat warnings as errors" OFF) cmake_dependent_option(ISLE_DEBUG "Enable imgui debug" ON "NOT VITA" OFF) cmake_dependent_option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" "${NOT_MINGW}" "WIN32;CMAKE_SIZEOF_VOID_P EQUAL 4" OFF) cmake_dependent_option(ISLE_MINIWIN "Use miniwin" ON "NOT ISLE_USE_DX5" OFF) -cmake_dependent_option(ISLE_EXTENSIONS "Use extensions" ON "NOT ISLE_USE_DX5" OFF) +cmake_dependent_option(ISLE_EXTENSIONS "Use extensions" ON "NOT ISLE_USE_DX5;NOT WINDOWS_STORE" OFF) cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "MSVC OR ISLE_MINIWIN;NOT NINTENDO_3DS;NOT WINDOWS_STORE;NOT VITA" OFF) cmake_dependent_option(ISLE_COMPILE_SHADERS "Compile shaders" ON "SDL_SHADERCROSS_BIN;TARGET Python3::Interpreter" OFF) cmake_dependent_option(CMAKE_POSITION_INDEPENDENT_CODE "Build with -fPIC" ON "NOT VITA" OFF) @@ -72,24 +73,29 @@ if (DOWNLOAD_DEPENDENCIES) # FetchContent downloads and configures dependencies message(STATUS "Fetching SDL3 and iniparser. This might take a while...") include(FetchContent) - if (WINDOWS_STORE) - FetchContent_Declare( - SDL3 - GIT_REPOSITORY "https://github.com/Helloyunho/SDL3-uwp.git" - GIT_TAG "main" - UPDATE_DISCONNECTED TRUE - EXCLUDE_FROM_ALL - ) + + if(ANDROID) + # Built by Gradle + find_package(SDL3 REQUIRED CONFIG COMPONENTS Shared) else() - FetchContent_Declare( + if (WINDOWS_STORE) + FetchContent_Declare( + SDL3 + GIT_REPOSITORY "https://github.com/Helloyunho/SDL3-uwp.git" + GIT_TAG "main" + EXCLUDE_FROM_ALL + ) + else() + FetchContent_Declare( SDL3 GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" GIT_TAG "main" UPDATE_DISCONNECTED TRUE EXCLUDE_FROM_ALL - ) + ) + endif() + FetchContent_MakeAvailable(SDL3) endif() - FetchContent_MakeAvailable(SDL3) FetchContent_Declare( iniparser @@ -493,14 +499,23 @@ if (NOT ISLE_MINIWIN) endif() if (ISLE_EXTENSIONS) + target_link_libraries(lego1 PRIVATE libweaver) target_compile_definitions(lego1 PUBLIC EXTENSIONS) target_sources(lego1 PRIVATE extensions/src/extensions.cpp + extensions/src/siloader.cpp extensions/src/textureloader.cpp ) endif() if (ISLE_BUILD_APP) + + if (ANDROID) + function(add_executable TARGET PLATFORM) + add_library(${TARGET} SHARED ${ARGN}) + endfunction() + endif() + add_executable(isle WIN32 ISLE/res/isle.rc ISLE/isleapp.cpp @@ -580,6 +595,11 @@ if (ISLE_BUILD_APP) ISLE/ios/config.cpp ) endif() + if (ANDROID) + target_sources(isle PRIVATE + ISLE/android/config.cpp + ) + endif() if(VITA) target_sources(isle PRIVATE ISLE/vita/config.cpp @@ -653,6 +673,34 @@ if (ISLE_BUILD_CONFIG) endif() endif() +if(ISLE_BUILD_ASSETS) + message(STATUS "Asset building is enabled") + set(GENERATED_ASSETS_DIR "${CMAKE_BINARY_DIR}/assets") + set(GENERATED_ASSETS_DEPFILE "${GENERATED_ASSETS_DIR}/assets.d") + + add_executable(asset_generator EXCLUDE_FROM_ALL + assets/main.cpp + ) + target_link_libraries(asset_generator PRIVATE libweaver) + target_include_directories(asset_generator PRIVATE "${CMAKE_SOURCE_DIR}/util" "${CMAKE_SOURCE_DIR}/LEGO1/omni/include" "${CMAKE_SOURCE_DIR}/LEGO1" "${CMAKE_SOURCE_DIR}/LEGO1/lego/sources") + + add_custom_command( + OUTPUT ${GENERATED_ASSETS_DIR}/.stamp + DEPFILE ${GENERATED_ASSETS_DEPFILE} + COMMAND ${CMAKE_COMMAND} -E make_directory ${GENERATED_ASSETS_DIR} + COMMAND $ ${GENERATED_ASSETS_DIR} ${GENERATED_ASSETS_DEPFILE} + COMMAND ${CMAKE_COMMAND} -E touch ${GENERATED_ASSETS_DIR}/.stamp + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/assets + DEPENDS asset_generator + COMMENT "Generating assets into ${GENERATED_ASSETS_DIR}/" + VERBATIM + ) + + add_custom_target(build_assets ALL + DEPENDS ${GENERATED_ASSETS_DIR}/.stamp + ) +endif() + if (ISLE_MINIWIN) set_property(TARGET ${isle_targets} APPEND PROPERTY LINK_LIBRARIES "miniwin") endif() @@ -682,6 +730,9 @@ if (MSVC) if (TARGET isle-config) target_compile_options(isle-config PRIVATE "-Zc:__cplusplus") endif() + if (TARGET asset_generator) + target_compile_options(asset_generator PRIVATE "-Zc:__cplusplus") + endif() endif() endif() @@ -717,7 +768,7 @@ endif() set(install_extra_targets) if(DOWNLOAD_DEPENDENCIES) get_property(sdl3_type TARGET SDL3::SDL3 PROPERTY TYPE) - if(sdl3_type STREQUAL "SHARED_LIBRARY") + if(sdl3_type STREQUAL "SHARED_LIBRARY" AND NOT ANDROID) list(APPEND install_extra_targets "SDL3-shared") endif() endif() diff --git a/CONFIG/qt/AboutDlg.cpp b/CONFIG/qt/AboutDlg.cpp index a853ee52..651b382b 100644 --- a/CONFIG/qt/AboutDlg.cpp +++ b/CONFIG/qt/AboutDlg.cpp @@ -7,12 +7,9 @@ DECOMP_SIZE_ASSERT(CDialog, 0x60) DECOMP_SIZE_ASSERT(CAboutDialog, 0x60) -// FIXME: disable dialog resizing - // FUNCTION: CONFIG 0x00403c20 CAboutDialog::CAboutDialog() : QDialog() { m_ui = new Ui::AboutDialog; m_ui->setupUi(this); - layout()->setSizeConstraint(QLayout::SetFixedSize); } diff --git a/CONFIG/qt/MainDlg.cpp b/CONFIG/qt/MainDlg.cpp index 5ae140c3..73a85ab3 100644 --- a/CONFIG/qt/MainDlg.cpp +++ b/CONFIG/qt/MainDlg.cpp @@ -65,6 +65,7 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->sound3DCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckbox3DSound); connect(m_ui->rumbleCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxRumble); connect(m_ui->textureCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxTexture); + connect(m_ui->customAssetsCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxCustomAssets); connect(m_ui->touchComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TouchControlsChanged); connect(m_ui->transitionTypeComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TransitionTypeChanged); connect(m_ui->exFullResComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::ExclusiveResolutionChanged); @@ -81,6 +82,11 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->texturePathOpen, &QPushButton::clicked, this, &CMainDialog::SelectTexturePathDialog); connect(m_ui->texturePath, &QLineEdit::editingFinished, this, &CMainDialog::TexturePathEdited); + connect(m_ui->addCustomAssetPath, &QPushButton::clicked, this, &CMainDialog::AddCustomAssetPath); + connect(m_ui->removeCustomAssetPath, &QPushButton::clicked, this, &CMainDialog::RemoveCustomAssetPath); + connect(m_ui->customAssetPaths, &QListWidget::currentRowChanged, this, &CMainDialog::SelectedPathChanged); + connect(m_ui->customAssetPaths, &QListWidget::itemActivated, this, &CMainDialog::EditCustomAssetPath); + connect(m_ui->maxLoDSlider, &QSlider::valueChanged, this, &CMainDialog::MaxLoDChanged); connect(m_ui->maxLoDSlider, &QSlider::sliderMoved, this, &CMainDialog::MaxLoDChanged); connect(m_ui->maxActorsSlider, &QSlider::valueChanged, this, &CMainDialog::MaxActorsChanged); @@ -96,8 +102,6 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->yResSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::YResChanged); connect(m_ui->framerateSpinBox, &QSpinBox::valueChanged, this, &CMainDialog::FramerateChanged); - layout()->setSizeConstraint(QLayout::SetFixedSize); - if (currentConfigApp->m_ram_quality_limit != 0) { m_modified = true; const QString ramError = QString("Insufficient RAM!"); @@ -308,10 +312,29 @@ void CMainDialog::UpdateInterface() m_ui->savePath->setText(QString::fromStdString(currentConfigApp->m_save_path)); m_ui->textureCheckBox->setChecked(currentConfigApp->m_texture_load); - m_ui->texturePath->setText(QString::fromStdString(currentConfigApp->m_texture_path)); + QString texture_path = QString::fromStdString(currentConfigApp->m_texture_path); + if (texture_path.startsWith(QDir::separator())) { + texture_path.remove(0, 1); + } + m_ui->texturePath->setText(texture_path); m_ui->texturePath->setEnabled(currentConfigApp->m_texture_load); m_ui->texturePathOpen->setEnabled(currentConfigApp->m_texture_load); + m_ui->customAssetsCheckBox->setChecked(currentConfigApp->m_custom_assets_enabled); + m_ui->customAssetPathContainer->setEnabled(currentConfigApp->m_custom_assets_enabled); + m_ui->customAssetPaths->setEnabled(currentConfigApp->m_custom_assets_enabled); + m_ui->addCustomAssetPath->setEnabled(currentConfigApp->m_custom_assets_enabled); + m_ui->removeCustomAssetPath->setEnabled(false); + + m_ui->customAssetPaths->clear(); + assetPaths = QString::fromStdString(currentConfigApp->m_custom_asset_path).split(u','); + for (QString& path : assetPaths) { + if (path.startsWith(QDir::separator())) { + path.remove(0, 1); + } + } + m_ui->customAssetPaths->addItems(assetPaths); + m_ui->aspectRatioComboBox->setCurrentIndex(currentConfigApp->m_aspect_ratio); m_ui->xResSpinBox->setValue(currentConfigApp->m_x_res); m_ui->yResSpinBox->setValue(currentConfigApp->m_y_res); @@ -442,6 +465,13 @@ void CMainDialog::OnCheckboxTexture(bool checked) UpdateInterface(); } +void CMainDialog::OnCheckboxCustomAssets(bool checked) +{ + currentConfigApp->m_custom_assets_enabled = checked; + m_modified = true; + UpdateInterface(); +} + void CMainDialog::TouchControlsChanged(int index) { currentConfigApp->m_touch_scheme = index; @@ -475,14 +505,15 @@ void CMainDialog::SelectDataPathDialog() QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks ); - QDir data_dir = QDir(data_path); - - if (data_dir.exists()) { - currentConfigApp->m_cd_path = data_dir.absolutePath().toStdString(); - data_dir.cd(QString("DATA")); - data_dir.cd(QString("disk")); - currentConfigApp->m_base_path = data_dir.absolutePath().toStdString(); - m_modified = true; + if (!data_path.isEmpty()) { + QDir data_dir = QDir(data_path); + if (data_dir.exists()) { + currentConfigApp->m_cd_path = data_dir.absolutePath().toStdString(); + data_dir.cd(QString("DATA")); + data_dir.cd(QString("disk")); + currentConfigApp->m_base_path = data_dir.absolutePath().toStdString(); + m_modified = true; + } } UpdateInterface(); } @@ -497,11 +528,12 @@ void CMainDialog::SelectSavePathDialog() QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks ); - QDir save_dir = QDir(save_path); - - if (save_dir.exists()) { - currentConfigApp->m_save_path = save_dir.absolutePath().toStdString(); - m_modified = true; + if (!save_path.isEmpty()) { + QDir save_dir = QDir(save_path); + if (save_dir.exists()) { + currentConfigApp->m_save_path = save_dir.absolutePath().toStdString(); + m_modified = true; + } } UpdateInterface(); } @@ -517,13 +549,11 @@ void CMainDialog::DataPathEdited() currentConfigApp->m_base_path = data_dir.absolutePath().toStdString(); m_modified = true; } - UpdateInterface(); } void CMainDialog::SavePathEdited() { - QDir save_dir = QDir(m_ui->savePath->text()); if (save_dir.exists()) { @@ -563,7 +593,12 @@ void CMainDialog::AFChanged(int value) void CMainDialog::SelectTexturePathDialog() { + QDir data_path = QDir(QString::fromStdString(currentConfigApp->m_cd_path)); QString texture_path = QString::fromStdString(currentConfigApp->m_texture_path); + if (texture_path.startsWith(QDir::separator())) { + texture_path.remove(0, 1); + } + texture_path = data_path.absoluteFilePath(texture_path); texture_path = QFileDialog::getExistingDirectory( this, tr("Open Directory"), @@ -571,10 +606,10 @@ void CMainDialog::SelectTexturePathDialog() QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks ); - QDir texture_dir = QDir(texture_path); - - if (texture_dir.exists()) { - currentConfigApp->m_texture_path = texture_dir.absolutePath().toStdString(); + if (!texture_path.isEmpty() && data_path.exists(texture_path)) { + texture_path = data_path.relativeFilePath(texture_path); + texture_path.prepend(QDir::separator()); + currentConfigApp->m_texture_path = texture_path.toStdString(); m_modified = true; } UpdateInterface(); @@ -582,15 +617,83 @@ void CMainDialog::SelectTexturePathDialog() void CMainDialog::TexturePathEdited() { - QDir texture_dir = QDir(m_ui->texturePath->text()); + QString texture_path = m_ui->texturePath->text(); + QDir data_path = QDir(QString::fromStdString(currentConfigApp->m_cd_path)); - if (texture_dir.exists()) { - currentConfigApp->m_texture_path = texture_dir.absolutePath().toStdString(); + if (texture_path.startsWith(QDir::separator())) { + texture_path.remove(0, 1); + } + if (data_path.exists(data_path.absoluteFilePath(texture_path))) { + texture_path = data_path.relativeFilePath(texture_path); + texture_path.prepend(QDir::separator()); + currentConfigApp->m_texture_path = texture_path.toStdString(); m_modified = true; } UpdateInterface(); } +void CMainDialog::AddCustomAssetPath() +{ + QDir data_path = QDir(QString::fromStdString(currentConfigApp->m_cd_path)); + QStringList new_files = QFileDialog::getOpenFileNames( + this, + "Select one or more files to open", + data_path.absolutePath(), + "Interleaf files (*.si)" + ); + if (!new_files.isEmpty()) { + for (QString& item : new_files) { + item = data_path.relativeFilePath(item); + } + assetPaths += new_files; + m_modified = true; + } + UpdateAssetPaths(); +} + +void CMainDialog::RemoveCustomAssetPath() +{ + assetPaths.removeAt(m_ui->customAssetPaths->currentRow()); + m_modified = true; + UpdateAssetPaths(); +} + +void CMainDialog::SelectedPathChanged(int currentRow) +{ + m_ui->removeCustomAssetPath->setEnabled(currentRow != -1 ? true : false); +} + +void CMainDialog::EditCustomAssetPath() +{ + QDir data_path = QDir(QString::fromStdString(currentConfigApp->m_cd_path)); + QString prev_asset_path = assetPaths[m_ui->customAssetPaths->currentRow()]; + QString new_file = QFileDialog::getOpenFileName( + this, + "Open File", + data_path.absoluteFilePath(prev_asset_path), + "Interleaf files (*.si)" + ); + if (!new_file.isEmpty()) { + new_file = data_path.relativeFilePath(new_file); + assetPaths[m_ui->customAssetPaths->currentRow()] = new_file; + m_modified = true; + } + UpdateAssetPaths(); +} + +void CMainDialog::UpdateAssetPaths() +{ + assetPaths.removeDuplicates(); + + for (QString& path : assetPaths) { + if (!path.startsWith(QDir::separator())) { + path.prepend(QDir::separator()); + } + } + currentConfigApp->m_custom_asset_path = assetPaths.join(u',').toStdString(); + UpdateInterface(); +} + void CMainDialog::AspectRatioChanged(int index) { currentConfigApp->m_aspect_ratio = index; diff --git a/CONFIG/qt/MainDlg.h b/CONFIG/qt/MainDlg.h index 0aaeeb12..22cf7895 100644 --- a/CONFIG/qt/MainDlg.h +++ b/CONFIG/qt/MainDlg.h @@ -29,6 +29,7 @@ class CMainDialog : public QDialog { private: bool m_modified = false; bool m_advanced = false; + QStringList assetPaths = QStringList(); Ui::MainDialog* m_ui = nullptr; SDL_DisplayMode** displayModes; @@ -48,6 +49,7 @@ private slots: void OnCheckboxMusic(bool checked); void OnCheckboxRumble(bool checked); void OnCheckboxTexture(bool checked); + void OnCheckboxCustomAssets(bool checked); void TouchControlsChanged(int index); void TransitionTypeChanged(int index); void ExclusiveResolutionChanged(int index); @@ -64,6 +66,11 @@ private slots: void AFChanged(int value); void SelectTexturePathDialog(); void TexturePathEdited(); + void AddCustomAssetPath(); + void RemoveCustomAssetPath(); + void SelectedPathChanged(int currentRow); + void EditCustomAssetPath(); + void UpdateAssetPaths(); void XResChanged(int i); void YResChanged(int i); void AspectRatioChanged(int index); diff --git a/CONFIG/qt/config.cpp b/CONFIG/qt/config.cpp index 766cecd4..4e9d414d 100644 --- a/CONFIG/qt/config.cpp +++ b/CONFIG/qt/config.cpp @@ -89,7 +89,9 @@ bool CConfigApp::InitInstance() m_haptic = TRUE; m_touch_scheme = 2; m_texture_load = TRUE; - m_texture_path = "/textures/"; + m_texture_path = "textures/"; + m_custom_assets_enabled = TRUE; + m_custom_asset_path = "assets/widescreen.si"; int totalRamMiB = SDL_GetSystemRAM(); if (totalRamMiB < 12) { m_ram_quality_limit = 2; @@ -188,6 +190,8 @@ bool CConfigApp::ReadRegisterSettings() m_anisotropy = iniparser_getint(dict, "isle:Anisotropic", m_anisotropy); m_texture_load = iniparser_getboolean(dict, "extensions:texture loader", m_texture_load); m_texture_path = iniparser_getstring(dict, "texture loader:texture path", m_texture_path.c_str()); + m_custom_assets_enabled = iniparser_getboolean(dict, "extensions:si loader", m_custom_assets_enabled); + m_custom_asset_path = iniparser_getstring(dict, "si loader:files", m_custom_asset_path.c_str()); m_aspect_ratio = iniparser_getint(dict, "isle:Aspect Ratio", m_aspect_ratio); m_x_res = iniparser_getint(dict, "isle:Horizontal Resolution", m_x_res); m_y_res = iniparser_getint(dict, "isle:Vertical Resolution", m_y_res); @@ -365,6 +369,8 @@ void CConfigApp::WriteRegisterSettings() const iniparser_set(dict, "isle", NULL); iniparser_set(dict, "extensions", NULL); iniparser_set(dict, "texture loader", NULL); + iniparser_set(dict, "si loader", NULL); + if (m_device_enumerator->FormatDeviceName(buffer, m_driver, m_device) >= 0) { iniparser_set(dict, "isle:3D Device ID", buffer); } @@ -394,6 +400,9 @@ void CConfigApp::WriteRegisterSettings() const SetIniBool(dict, "extensions:texture loader", m_texture_load); iniparser_set(dict, "texture loader:texture path", m_texture_path.c_str()); + SetIniBool(dict, "extensions:si loader", m_custom_assets_enabled); + iniparser_set(dict, "si loader:files", m_custom_asset_path.c_str()); + SetIniBool(dict, "isle:Back Buffers in Video RAM", m_3d_video_ram); SetIniInt(dict, "isle:Island Quality", m_model_quality); @@ -451,8 +460,8 @@ CConfigApp g_theApp; int main(int argc, char* argv[]) { QApplication app(argc, argv); - QCoreApplication::setApplicationName("config"); - QCoreApplication::setApplicationVersion("1.0"); + QCoreApplication::setApplicationName("Isle-Config"); + QCoreApplication::setApplicationVersion("2.0"); QCommandLineParser parser; parser.setApplicationDescription("Configure LEGO Island"); @@ -470,7 +479,7 @@ int main(int argc, char* argv[]) if (parser.isSet(iniOption)) { g_theApp.SetIniPath(parser.value(iniOption).toStdString()); } - qInfo() << "Ini path =" << QString::fromStdString(g_theApp.GetIniPath()); + qInfo() << "INI path =" << QString::fromStdString(g_theApp.GetIniPath()); int result = 1; if (g_theApp.InitInstance()) { diff --git a/CONFIG/qt/config.h b/CONFIG/qt/config.h index c0dcb851..71e876f6 100644 --- a/CONFIG/qt/config.h +++ b/CONFIG/qt/config.h @@ -87,7 +87,9 @@ class CConfigApp { int m_texture_quality; bool m_music; bool m_texture_load; + bool m_custom_assets_enabled; std::string m_texture_path; + std::string m_custom_asset_path; std::string m_iniPath; std::string m_base_path; std::string m_cd_path; diff --git a/CONFIG/qt/res/about.ui b/CONFIG/qt/res/about.ui index a716ebc8..ce51ea2a 100644 --- a/CONFIG/qt/res/about.ui +++ b/CONFIG/qt/res/about.ui @@ -14,6 +14,9 @@ About Configure LEGO© Island + + QLayout::SizeConstraint::SetFixedSize + @@ -36,14 +39,14 @@ - Configure LEGO Island Version 1.0 + Configure LEGO Island Version 2.0 - Copyright © 1997 mindscape + Copyright © 2025 @@ -53,7 +56,7 @@ - QDialogButtonBox::Ok + QDialogButtonBox::StandardButton::Ok diff --git a/CONFIG/qt/res/add.svg b/CONFIG/qt/res/add.svg new file mode 100644 index 00000000..174d9847 --- /dev/null +++ b/CONFIG/qt/res/add.svg @@ -0,0 +1,46 @@ + + + + + + + + diff --git a/CONFIG/qt/res/config.qrc b/CONFIG/qt/res/config.qrc index a2fa0bcb..fc01d9e4 100644 --- a/CONFIG/qt/res/config.qrc +++ b/CONFIG/qt/res/config.qrc @@ -1,7 +1,9 @@ - - shark.png - lego1.png - lego2.png - - \ No newline at end of file + + add.svg + remove.svg + shark.png + lego1.png + lego2.png + + diff --git a/CONFIG/qt/res/maindialog.ui b/CONFIG/qt/res/maindialog.ui index e395159b..99931197 100644 --- a/CONFIG/qt/res/maindialog.ui +++ b/CONFIG/qt/res/maindialog.ui @@ -6,7 +6,7 @@ 0 0 - 600 + 640 480 @@ -24,6 +24,9 @@ :/lego1.png:/lego1.png + + QLayout::SizeConstraint::SetFixedSize + @@ -72,6 +75,12 @@ + + + 425 + 0 + + 0 @@ -80,30 +89,6 @@ Game - - - - - - Enable 3D positional audio effects. - - - 3D Sound - - - - - - - Enable in-game background music. - - - Music - - - - - @@ -283,6 +268,32 @@ Set this to the CD image root. + + + + + + + Enable 3D positional audio effects. + + + 3D Sound + + + + + + + Enable in-game background music. + + + Music + + + + + + @@ -766,7 +777,10 @@ The game will gradually increase the number of actors until this maximum is reac - x + + + + Qt::TextFormat::PlainText Qt::AlignmentFlag::AlignCenter @@ -1012,72 +1026,210 @@ The game will gradually increase the number of actors until this maximum is reac - - - Settings for Texture Loader extension. + + + true - - Texture Loader - - - - - - Enabled - - - - - - - false - - - Path to texture replacements. - - - textures/ - - - - - - - false - - - - 0 - 0 - - - - - 50 - 16777215 - - - - Open - - - - + + + + 0 + 0 + 449 + 369 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Settings for Texture Loader extension. + + + Texture Loader + + + + + + Enabled + + + + + + + false + + + Path to texture replacements. Relative to the Data Path. + + + textures/ + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Open + + + + + + + + + + Settings for Custom Assets. + + + Custom Assets + + + + + + Enabled + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 28 + 28 + + + + + 28 + 28 + + + + Add path. + + + + :/add.svg:/add.svg + + + + + + + false + + + Paths used by the SI Loader extension. + +Double-click a path to edit it. + + + QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow + + + + + + + false + + + + 28 + 28 + + + + + 28 + 28 + + + + Remove path. + + + + :/remove.svg:/remove.svg + + + + + + + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + - - - - Qt::Orientation::Vertical - - - - 20 - 40 - - - - @@ -1176,9 +1328,14 @@ The game will gradually increase the number of actors until this maximum is reac AFSlider touchComboBox rumbleCheckBox + scrollArea textureCheckBox texturePath texturePathOpen + customAssetsCheckBox + customAssetPaths + addCustomAssetPath + removeCustomAssetPath okButton launchButton cancelButton diff --git a/CONFIG/qt/res/remove.svg b/CONFIG/qt/res/remove.svg new file mode 100644 index 00000000..0e601431 --- /dev/null +++ b/CONFIG/qt/res/remove.svg @@ -0,0 +1,10 @@ + + + + diff --git a/CONFIG/vita/src/app.cpp b/CONFIG/vita/src/app.cpp index 45749f52..172309c4 100644 --- a/CONFIG/vita/src/app.cpp +++ b/CONFIG/vita/src/app.cpp @@ -17,199 +17,204 @@ paf::Plugin* g_configPlugin; sce::AppSettings* g_appSettings; sce::AppSettings::Interface* g_appSetIf; -struct Config { - paf::string m_base_path; - paf::string m_cd_path; - paf::string m_save_path; - int m_transition_type; - int m_texture_quality; - int m_model_quality; - int m_msaa; - int m_touch_scheme; - bool m_wide_view_angle; - bool m_music; - bool m_3d_sound; - bool m_haptic; - bool m_draw_cursor; - bool m_texture_load; - paf::string m_texture_path; - float m_max_lod; - int m_max_actors; - float m_frame_delta; +void merge_dicts(dictionary* dst, dictionary* src) { + for(int i = 0; i < src->n; i++) { + dictionary_set(dst, src->key[i], src->val[i]); + } +} - void Init() +struct setting_map { + const char* key_ini; + const char* key_app; + const char type; +}; + +// mapping from ini key to settings.xml key +const setting_map key_map[] = { + // Game + {"isle:diskpath", "disk_path", 's'}, + {"isle:cdpath", "cd_path", 's'}, + {"isle:savepath", "save_path", 's'}, + {"isle:Transition Type", "transition_type", 'i'}, + {"isle:Music", "music", 'b'}, + {"isle:3DSound", "3d_sound", 'b'}, + + // Graphics + {"isle:Island Texture", "island_texture_quality", 'i'}, + {"isle:Island Quality", "island_model_quality", 'i'}, + //{"isle:Max LOD", "max_lod", 'f'}, + //{"isle:Max Allowed Extras", "max_extras", 'i' }, + {"isle:MSAA", "msaa", 'i'}, + + // Controls + {"isle:Touch Scheme", "touch_control_scheme", 'i'}, + {"isle:Haptic", "rumble", 'b'}, + + // Extensions + {"extensions:texture loader", "texture_loader_extension", 'b'}, + {"texture loader:texture path", "texture_loader_path", 's'} +}; + +struct Config { + sce::AppSettings* settings; + dictionary* dict; + char buffer[128]; + +#define GetDictInt(x, name) x = iniparser_getint(this->dict, name, x) +#define GetDictFloat(x, name) x = iniparser_getdouble(this->dict, name, x) +#define GetDictString(x, name) { \ + const char* val = iniparser_getstring(this->dict, name, nullptr); \ + if (val != nullptr) { \ + x = val; \ + } \ + } +#define GetDictBool(x, name) x = iniparser_getboolean(this->dict, name, x) + +#define SetDictBool(NAME, VALUE) \ + { \ + const char* v = VALUE ? "true" : "false"; \ + sceClibPrintf("SetIniBool(%s, %s)\n", NAME, v); \ + iniparser_set(this->dict, NAME, v); \ + } +#define SetDictInt(NAME, VALUE) \ + { \ + sceClibSnprintf(buffer, sizeof(buffer), "%d", VALUE); \ + sceClibPrintf("SetIniInt(%s, %d)\n", NAME, VALUE); \ + iniparser_set(this->dict, NAME, buffer); \ + } +#define SetDictFloat(NAME, VALUE) \ + { \ + sceClibSnprintf(buffer, sizeof(buffer), "%f", VALUE); \ + sceClibPrintf("SetIniFloat(%s, %f)\n", NAME, VALUE); \ + iniparser_set(this->dict, NAME, buffer); \ + } +#define SetDictString(NAME, VALUE) \ + { \ + sceClibPrintf("SetString(%s, %s)\n", NAME, VALUE); \ + iniparser_set(this->dict, NAME, VALUE); \ + } + + void Init(sce::AppSettings* settings) { - m_frame_delta = 10.0f; - m_transition_type = 3; // 3: Mosaic - m_wide_view_angle = true; - m_music = true; - m_3d_sound = true; - m_haptic = true; - m_touch_scheme = 2; - m_texture_load = true; - m_texture_path = "/textures/"; - m_model_quality = 2; - m_texture_quality = 1; - m_msaa = 4; - m_max_lod = 3.5f; - m_max_actors = 20; + this->settings = settings; + dict = dictionary_new(0); + + // set defaults + SetDictInt("isle:Display Bit Depth", 32); + SetDictBool("isle:Flip Surfaces", false); + SetDictBool("isle:Full Screen", true); + SetDictBool("isle:Exclusive Full Screen", true); + SetDictBool("isle:Wide View Angle", true); + + SetDictInt("isle:Transition Type", 3); // 3: Mosaic + SetDictInt("isle:Touch Scheme", 2); + + SetDictBool("isle:3DSound", true); + SetDictBool("isle:Music", true); + SetDictBool("isle:Haptic", true); + + SetDictBool("isle:UseJoystick", true); + SetDictInt("isle:JoystickIndex", 0); + SetDictBool("isle:Draw Cursor", true); + + SetDictBool("extensions:texture loader", false); + SetDictString("texture loader:texture path", "textures/"); + + SetDictBool("isle:Back Buffers in Video RAM", true); + + SetDictInt("isle:Island Quality", 2); + SetDictInt("isle:Island Texture", 1); + SetDictInt("isle:MSAA", 4); + + SetDictFloat("isle:Max LOD", 3.5); + SetDictInt("isle:Max Allowed Extras", 20); + + SetDictInt("isle:Aspect Ratio", 0); + SetDictInt("isle:Horizontal Resolution", 640); + SetDictInt("isle:Vertical Resolution", 480); + SetDictFloat("isle:Frame Delta", 10.0f); } void LoadIni() { - dictionary* dict = iniparser_load(g_iniPath); - if (!dict) { - dict = dictionary_new(0); + dictionary* ini = iniparser_load(g_iniPath); + if(ini) { + merge_dicts(this->dict, ini); + iniparser_freedict(ini); } - -#define GET_INT(x, name) x = iniparser_getint(dict, name, x) -#define GET_FLOAT(x, name) x = iniparser_getdouble(dict, name, x) -#define GET_STRING(x, name) do { \ - const char* val = iniparser_getstring(dict, name, nullptr); \ - if(val != nullptr) x = val; \ -} while(0) -#define GET_BOOLEAN(x, name) x = iniparser_getboolean(dict, name, x) - - GET_STRING(m_base_path, "isle:diskpath"); - GET_STRING(m_cd_path, "isle:cdpath"); - GET_STRING(m_save_path, "isle:savepath"); - - // m_display_bit_depth = iniparser_getint(dict, "isle:Display Bit Depth", -1); - // GET_BOOLEAN(m_flip_surfaces, "isle:Flip Surfaces"); - // GET_BOOLEAN(m_full_screen, "isle:Full Screen"); - // GET_BOOLEAN(m_exclusive_full_screen, "isle:Exclusive Full Screen"); - GET_INT(m_transition_type, "isle:Transition Type"); - GET_INT(m_touch_scheme, "isle:Touch Scheme"); - // GET_BOOLEAN(m_3d_video_ram, "isle:Back Buffers in Video RAM"); - GET_BOOLEAN(m_wide_view_angle, "isle:Wide View Angle"); - GET_BOOLEAN(m_3d_sound, "isle:3DSound"); - GET_BOOLEAN(m_draw_cursor, "isle:Draw Cursor"); - GET_INT(m_model_quality, "isle:Island Quality"); - GET_INT(m_texture_quality, "isle:Island Texture"); - GET_INT(m_msaa, "isle:MSAA"); - // GET_BOOLEAN(m_use_joystick, "isle:UseJoystick"); - GET_BOOLEAN(m_haptic, "isle:Haptic"); - GET_BOOLEAN(m_music, "isle:Music"); - // GET_INT(m_joystick_index, "isle:JoystickIndex"); - GET_FLOAT(m_max_lod, "isle:Max LOD"); - GET_INT(m_max_actors, "isle:Max Allowed Extras"); - GET_BOOLEAN(m_texture_load, "extensions:texture loader"); - GET_STRING(m_texture_path, "texture loader:texture path"); - // GET_INT(m_aspect_ratio, "isle:Aspect Ratio"); - // GET_INT(m_x_res, "isle:Horizontal Resolution"); - // GET_INT(m_y_res, "isle:Vertical Resolution"); - GET_FLOAT(m_frame_delta, "isle:Frame Delta"); -#undef GET_INT -#undef GET_FLOAT -#undef GET_STRING -#undef GET_BOOLEAN - iniparser_freedict(dict); } bool SaveIni() { - dictionary* dict = dictionary_new(0); - - char buffer[128]; -#define SetIniBool(NAME, VALUE) iniparser_set(dict, NAME, VALUE ? "true" : "false") -#define SetIniInt(NAME, VALUE) \ - { \ - sceClibPrintf(buffer, "%d", VALUE); \ - iniparser_set(dict, NAME, buffer); \ - } -#define SetIniFloat(NAME, VALUE) \ - { \ - sceClibPrintf(buffer, "%f", VALUE); \ - iniparser_set(dict, NAME, buffer); \ - } -#define SetString(NAME, VALUE) iniparser_set(dict, NAME, VALUE) - - SetIniInt("isle:Display Bit Depth", 32); - SetIniBool("isle:Flip Surfaces", false); - SetIniBool("isle:Full Screen", true); - SetIniBool("isle:Exclusive Full Screen", true); - SetIniBool("isle:Wide View Angle", true); // option? - - SetIniInt("isle:Transition Type", m_transition_type); - SetIniInt("isle:Touch Scheme", m_touch_scheme); - - SetIniBool("isle:3DSound", m_3d_sound); - SetIniBool("isle:Music", m_music); - SetIniBool("isle:Haptic", m_haptic); - - SetIniBool("isle:UseJoystick", true); - SetIniInt("isle:JoystickIndex", 0); - SetIniBool("isle:Draw Cursor", m_draw_cursor); - - SetIniBool("extensions:texture loader", m_texture_load); - SetString("texture loader:texture path", m_texture_path.c_str()); - - SetIniBool("isle:Back Buffers in Video RAM", true); - - SetIniInt("isle:Island Quality", m_model_quality); - SetIniInt("isle:Island Texture", m_texture_quality); - SetIniInt("isle:MSAA", m_msaa); - - SetIniFloat("isle:Max LOD", m_max_lod); - SetIniInt("isle:Max Allowed Extras", m_max_actors); - - SetIniInt("isle:Aspect Ratio", 0); - SetIniInt("isle:Horizontal Resolution", 640); - SetIniInt("isle:Vertical Resolution", 480); - SetIniFloat("isle:Frame Delta", 10.0f); - -#undef SetIniBool -#undef SetIniInt -#undef SetIniFloat -#undef SetString - FILE* fd = fopen(g_iniPath, "w"); if (fd) { - iniparser_dump_ini(dict, fd); - } else { + iniparser_dump_ini(this->dict, fd); + } + else { sceClibPrintf("failed to write isle.ini\n"); } - iniparser_freedict(dict); - return true; } - void ToSettings(sce::AppSettings* appSettings) + void ToSettings() { - appSettings->SetString("data_path", this->m_base_path.c_str()); - appSettings->SetString("save_path", this->m_save_path.c_str()); - appSettings->SetInt("transition_type", this->m_transition_type); - appSettings->SetBool("music", this->m_music); - appSettings->SetBool("3d_sound", this->m_3d_sound); - appSettings->SetInt("island_texture_quality", this->m_texture_quality); - appSettings->SetInt("island_model_quality", this->m_model_quality); - appSettings->SetInt("msaa", this->m_msaa); - appSettings->SetInt("touch_control_scheme", this->m_touch_scheme); - appSettings->SetBool("rumble", this->m_haptic); - appSettings->SetBool("texture_loader_extension", this->m_texture_load); - appSettings->SetString("texture_loader_path", this->m_texture_path.c_str()); + const int len = sizeof(key_map) / sizeof(key_map[0]); + for(int i = 0; i < len; i++) { + const setting_map m = key_map[i]; + switch(m.type) { + case 'f': // float, AppSettings doesnt have float so just use string + case 's': { + const char* value = dictionary_get(this->dict, m.key_ini, ""); + this->settings->SetString(m.key_app, value); + break; + } + case 'i': { + int32_t value = iniparser_getint(this->dict, m.key_ini, 0); + this->settings->SetInt(m.key_app, value); + break; + } + case 'b': { + bool value = iniparser_getboolean(this->dict, m.key_ini, 0) == 1; + this->settings->SetBool(m.key_app, value); + break; + } + default: { + sceClibPrintf("invalid setting map entry %s %s %c\n", m.key_app, m.key_ini, m.type); + } + } + } } - void FromSettings(sce::AppSettings* appSettings) { - char text_buf[255]; - - #define GET_STRING(x, name) appSettings->GetString(name, text_buf, sizeof(text_buf), x.c_str()); x = text_buf; - - GET_STRING(this->m_base_path, "data_path"); - GET_STRING(this->m_save_path, "save_path"); - appSettings->GetInt("transition_type", &this->m_transition_type, this->m_transition_type); - printf("this->m_transition_type: %d\n", this->m_transition_type); - appSettings->GetBool("music", &this->m_music, this->m_music); - appSettings->GetBool("3d_sound", &this->m_3d_sound, this->m_3d_sound); - appSettings->GetInt("island_texture_quality", &this->m_texture_quality, this->m_texture_quality); - appSettings->GetInt("island_model_quality", &this->m_model_quality, this->m_model_quality); - appSettings->GetInt("msaa", &this->m_msaa, this->m_msaa); - appSettings->GetInt("touch_control_scheme", &this->m_touch_scheme, this->m_touch_scheme); - appSettings->GetBool("rumble", &this->m_haptic, this->m_haptic); - appSettings->GetBool("texture_loader_extension", &this->m_texture_load, this->m_texture_load); - GET_STRING(this->m_texture_path, "texture_loader_path"); - - #undef GET_STRING + void FromSettings() + { + const int len = sizeof(key_map) / sizeof(key_map[0]); + for(int i = 0; i < len; i++) { + const setting_map m = key_map[i]; + switch(m.type) { + case 'f': // float, AppSettings doesnt have float so just use string + case 's': { + const char* def = dictionary_get(this->dict, m.key_ini, ""); + this->settings->GetString(m.key_app, this->buffer, sizeof(this->buffer), def); + dictionary_set(this->dict, m.key_ini, buffer); + break; + } + case 'i': { + int32_t value = iniparser_getint(this->dict, m.key_ini, 0); + this->settings->GetInt(m.key_app, &value, value); + SetDictInt(m.key_ini, value); + break; + } + case 'b': { + bool value = iniparser_getboolean(this->dict, m.key_ini, 0) == 1; + this->settings->GetBool(m.key_app, &value, value); + SetDictBool(m.key_ini, value); + break; + } + default: { + sceClibPrintf("invalid setting map entry %s %s %c\n", m.key_app, m.key_ini, m.type); + } + } + } } }; @@ -254,7 +259,7 @@ int exit_type = 0; void save_and_exit() { - g_config.FromSettings(g_appSettings); + g_config.FromSettings(); g_config.SaveIni(); g_fw->RequestShutdown(); exit_type = 1; @@ -262,7 +267,7 @@ void save_and_exit() void save_and_launch() { - g_config.FromSettings(g_appSettings); + g_config.FromSettings(); g_config.SaveIni(); g_fw->RequestShutdown(); exit_type = 2; @@ -319,7 +324,7 @@ int32_t CBOnPress2(const char* elementId, const char* newValue) void CBOnTerm(int32_t result) { - if(exit_type == 0) { + if (exit_type == 0) { sceKernelExitProcess(0); } } @@ -340,9 +345,9 @@ int32_t CBOnGetSurface(paf::graph::Surface** surf, const char* elementId) void open_settings() { - g_config.Init(); + g_config.Init(g_appSettings); g_config.LoadIni(); - g_config.ToSettings(g_appSettings); + g_config.ToSettings(); sce::AppSettings::InterfaceCallbacks ifCb; ifCb.onStartPageTransitionCb = CBOnStartPageTransition; diff --git a/ISLE/android/config.cpp b/ISLE/android/config.cpp new file mode 100644 index 00000000..4883a239 --- /dev/null +++ b/ISLE/android/config.cpp @@ -0,0 +1,29 @@ +#include "config.h" + +#include "mxstring.h" + +#include +#include +#include +#include + +void Android_SetupDefaultConfigOverrides(dictionary* p_dictionary) +{ + SDL_Log("Overriding default config for Android"); + + const char* data = SDL_GetAndroidExternalStoragePath(); + MxString savedata = MxString(data) + "/saves/"; + + if (!SDL_GetPathInfo(savedata.GetData(), NULL)) { + SDL_CreateDirectory(savedata.GetData()); + } + + iniparser_set(p_dictionary, "isle:diskpath", data); + iniparser_set(p_dictionary, "isle:cdpath", data); + iniparser_set(p_dictionary, "isle:mediapath", data); + iniparser_set(p_dictionary, "isle:savepath", savedata.GetData()); + + // Default to Virtual Mouse + char buf[16]; + iniparser_set(p_dictionary, "isle:Touch Scheme", SDL_itoa(0, buf, 10)); +} diff --git a/ISLE/android/config.h b/ISLE/android/config.h new file mode 100644 index 00000000..5b4ac3d0 --- /dev/null +++ b/ISLE/android/config.h @@ -0,0 +1,8 @@ +#ifndef ANDROID_CONFIG_H +#define ANDROID_CONFIG_H + +#include "dictionary.h" + +void Android_SetupDefaultConfigOverrides(dictionary* p_dictionary); + +#endif // ANDROID_CONFIG_H diff --git a/ISLE/emscripten/filesystem.cpp b/ISLE/emscripten/filesystem.cpp index c2bfbc37..e5644608 100644 --- a/ISLE/emscripten/filesystem.cpp +++ b/ISLE/emscripten/filesystem.cpp @@ -1,6 +1,7 @@ #include "filesystem.h" #include "events.h" +#include "extensions/siloader.h" #include "extensions/textureloader.h" #include "legogamestate.h" #include "misc.h" @@ -107,6 +108,14 @@ void Emscripten_SetupFilesystem() Emscripten_SendExtensionProgress("HD Textures", (++i * 100) / sizeOfArray(g_textures)); } } + + if (Extensions::SiLoader::enabled) { + wasmfs_create_directory("/LEGO/extra", 0644, fetchfs); + + for (const auto& file : Extensions::SiLoader::files) { + registerFile(file.c_str()); + } + } #endif if (GameState()->GetSavePath() && *GameState()->GetSavePath() && !Emscripten_OPFSDisabled()) { diff --git a/ISLE/ios/config.cpp b/ISLE/ios/config.cpp index ee9349fa..22a17657 100644 --- a/ISLE/ios/config.cpp +++ b/ISLE/ios/config.cpp @@ -1,5 +1,7 @@ #include "config.h" +#include "mxstring.h" + #include #include #include @@ -11,15 +13,13 @@ void IOS_SetupDefaultConfigOverrides(dictionary* p_dictionary) // Use DevelopmentFiles path for disk and cd paths // It's good to use that path since user can easily // connect through SMB and copy the files - const char* documentFolder = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS); - char* diskPath = new char[strlen(documentFolder) + strlen("isle") + 1](); - strcpy(diskPath, documentFolder); - strcat(diskPath, "isle"); + MxString documentFolder = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS); + documentFolder += "isle"; - if (!SDL_GetPathInfo(diskPath, NULL)) { - SDL_CreateDirectory(diskPath); + if (!SDL_GetPathInfo(documentFolder.GetData(), NULL)) { + SDL_CreateDirectory(documentFolder.GetData()); } - iniparser_set(p_dictionary, "isle:diskpath", diskPath); - iniparser_set(p_dictionary, "isle:cdpath", diskPath); + iniparser_set(p_dictionary, "isle:diskpath", documentFolder.GetData()); + iniparser_set(p_dictionary, "isle:cdpath", documentFolder.GetData()); } diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 7f3e9958..0bf1b492 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -40,6 +40,7 @@ #include #include #include +#include #include #define SDL_MAIN_USE_CALLBACKS @@ -72,6 +73,10 @@ #include "ios/config.h" #endif +#ifdef ANDROID +#include "android/config.h" +#endif + #ifdef __vita__ #include "vita/config.h" #include "vita/messagebox.h" @@ -176,6 +181,7 @@ IsleApp::IsleApp() LegoOmni::CreateInstance(); + m_mediaPath = NULL; m_iniPath = NULL; m_maxLod = RealtimeView::GetUserMaxLOD(); m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20; @@ -202,25 +208,11 @@ IsleApp::~IsleApp() MxOmni::DestroyInstance(); } - if (m_hdPath) { - delete[] m_hdPath; - } - - if (m_cdPath) { - delete[] m_cdPath; - } - - if (m_deviceId) { - delete[] m_deviceId; - } - - if (m_savePath) { - delete[] m_savePath; - } - - if (m_mediaPath) { - delete[] m_mediaPath; - } + SDL_free(m_hdPath); + SDL_free(m_cdPath); + SDL_free(m_deviceId); + SDL_free(m_savePath); + SDL_free(m_mediaPath); } // FUNCTION: ISLE 0x401260 @@ -1014,6 +1006,8 @@ MxResult IsleApp::SetupWindow() return FAILURE; } + Lego()->LoadSiLoader(); + DetectGameVersion(); GameState()->SerializePlayersInfo(LegoStorage::c_read); GameState()->SerializeScoreHistory(LegoStorage::c_read); @@ -1066,33 +1060,33 @@ bool IsleApp::LoadConfig() { #ifdef IOS const char* prefPath = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS); -#else - char* prefPath = SDL_GetPrefPath("isledecomp", "isle"); -#endif - char* iniConfig; - -#ifdef __EMSCRIPTEN__ +#elif defined(ANDROID) + MxString androidPath = MxString(SDL_GetAndroidExternalStoragePath()) + "/"; + const char* prefPath = androidPath.GetData(); +#elif defined(EMSCRIPTEN) if (m_iniPath && !Emscripten_SetupConfig(m_iniPath)) { m_iniPath = NULL; } + char* prefPath = SDL_GetPrefPath("isledecomp", "isle"); +#else + char* prefPath = SDL_GetPrefPath("isledecomp", "isle"); #endif + MxString iniConfig; if (m_iniPath) { - iniConfig = new char[strlen(m_iniPath) + 1]; - strcpy(iniConfig, m_iniPath); + iniConfig = m_iniPath; } else if (prefPath) { - iniConfig = new char[strlen(prefPath) + strlen("isle.ini") + 1](); - strcat(iniConfig, prefPath); - strcat(iniConfig, "isle.ini"); + iniConfig = prefPath; + iniConfig += "isle.ini"; } else { - iniConfig = new char[strlen("isle.ini") + 1]; - strcpy(iniConfig, "isle.ini"); + iniConfig = "isle.ini"; } - SDL_Log("Reading configuration from \"%s\"", iniConfig); - dictionary* dict = iniparser_load(iniConfig); + SDL_Log("Reading configuration from \"%s\"", iniConfig.GetData()); + + dictionary* dict = iniparser_load(iniConfig.GetData()); // [library:config] // Load sane defaults if dictionary failed to load @@ -1103,20 +1097,20 @@ bool IsleApp::LoadConfig() } SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading sane defaults"); - FILE* iniFP = fopen(iniConfig, "wb"); + FILE* iniFP = fopen(iniConfig.GetData(), "wb"); if (!iniFP) { SDL_LogError( SDL_LOG_CATEGORY_APPLICATION, "Failed to write config at '%s': %s", - iniConfig, + iniConfig.GetData(), strerror(errno) ); return false; } char buf[32]; - dict = iniparser_load(iniConfig); + dict = iniparser_load(iniConfig.GetData()); iniparser_set(dict, "isle", NULL); iniparser_set(dict, "isle:diskpath", SDL_GetBasePath()); @@ -1168,11 +1162,15 @@ bool IsleApp::LoadConfig() #ifdef IOS IOS_SetupDefaultConfigOverrides(dict); #endif +#ifdef ANDROID + Android_SetupDefaultConfigOverrides(dict); +#endif + #ifdef __vita__ VITA_SetupDefaultConfigOverrides(dict); #endif iniparser_dump_ini(dict, iniFP); - SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig); + SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "New config written at '%s'", iniConfig.GetData()); fclose(iniFP); } @@ -1180,20 +1178,10 @@ bool IsleApp::LoadConfig() Emscripten_SetupDefaultConfigOverrides(dict); #endif - const char* hdPath = iniparser_getstring(dict, "isle:diskpath", SDL_GetBasePath()); - m_hdPath = new char[strlen(hdPath) + 1]; - strcpy(m_hdPath, hdPath); - MxOmni::SetHD(m_hdPath); - - const char* cdPath = iniparser_getstring(dict, "isle:cdpath", MxOmni::GetCD()); - m_cdPath = new char[strlen(cdPath) + 1]; - strcpy(m_cdPath, cdPath); - MxOmni::SetCD(m_cdPath); - - const char* mediaPath = iniparser_getstring(dict, "isle:mediapath", hdPath); - m_mediaPath = new char[strlen(mediaPath) + 1]; - strcpy(m_mediaPath, mediaPath); - + MxOmni::SetHD((m_hdPath = SDL_strdup(iniparser_getstring(dict, "isle:diskpath", SDL_GetBasePath())))); + MxOmni::SetCD((m_cdPath = SDL_strdup(iniparser_getstring(dict, "isle:cdpath", MxOmni::GetCD())))); + m_savePath = SDL_strdup(iniparser_getstring(dict, "isle:savepath", prefPath)); + m_mediaPath = SDL_strdup(iniparser_getstring(dict, "isle:mediapath", m_hdPath)); m_flipSurfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flipSurfaces); m_fullScreen = iniparser_getboolean(dict, "isle:Full Screen", m_fullScreen); m_exclusiveFullScreen = iniparser_getboolean(dict, "isle:Exclusive Full Screen", m_exclusiveFullScreen); @@ -1240,17 +1228,9 @@ bool IsleApp::LoadConfig() const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { - m_deviceId = new char[strlen(deviceId) + 1]; - strcpy(m_deviceId, deviceId); + m_deviceId = SDL_strdup(deviceId); } - // [library:config] - // The original game does not save any data if no savepath is given. - // Instead, we use SDLs prefPath as a default fallback and always save data. - const char* savePath = iniparser_getstring(dict, "isle:savepath", prefPath); - m_savePath = new char[strlen(savePath) + 1]; - strcpy(m_savePath, savePath); - #ifdef EXTENSIONS for (const char* key : Extensions::availableExtensions) { if (iniparser_getboolean(dict, key, 0)) { @@ -1270,11 +1250,12 @@ bool IsleApp::LoadConfig() #endif iniparser_freedict(dict); - delete[] iniConfig; -#ifndef IOS - SDL_free(prefPath); -#endif + [](auto path) { + if constexpr (std::is_same_v) { + SDL_free(path); + } + }(prefPath); return true; } @@ -1433,12 +1414,12 @@ SDL_AppResult IsleApp::ParseArguments(int argc, char** argv) consumed = 1; } else if (strcmp(argv[i], "--help") == 0) { - DisplayArgumentHelp(); + DisplayArgumentHelp(argv[0]); return SDL_APP_SUCCESS; } if (consumed <= 0) { SDL_Log("Invalid argument(s): %s", argv[i]); - DisplayArgumentHelp(); + DisplayArgumentHelp(argv[0]); return SDL_APP_FAILURE; } } @@ -1446,9 +1427,9 @@ SDL_AppResult IsleApp::ParseArguments(int argc, char** argv) return SDL_APP_CONTINUE; } -void IsleApp::DisplayArgumentHelp() +void IsleApp::DisplayArgumentHelp(const char* p_execName) { - SDL_Log("Usage: isle [options]"); + SDL_Log("Usage: %s [options]", p_execName); SDL_Log("Options:"); SDL_Log(" --ini Set custom path to .ini config"); #ifdef ISLE_DEBUG diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 66e8e6e1..0e0700e8 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -99,9 +99,9 @@ class IsleApp { const CursorBitmap* m_cursorCurrentBitmap; char* m_mediaPath; MxFloat m_cursorSensitivity; - void DisplayArgumentHelp(); + void DisplayArgumentHelp(const char* p_execName); - char* m_iniPath; + const char* m_iniPath; MxFloat m_maxLod; MxU32 m_maxAllowedExtras; MxTransitionManager::TransitionType m_transitionType; diff --git a/ISLE/isledebug.cpp b/ISLE/isledebug.cpp index 5b9bd590..ab3433a5 100644 --- a/ISLE/isledebug.cpp +++ b/ISLE/isledebug.cpp @@ -68,10 +68,10 @@ class DebugViewer { static void InsideBuildingManager() { auto buildingManager = Lego()->GetBuildingManager(); - ImGui::Text("nextVariant: %d", buildingManager->m_nextVariant); + ImGui::Text("nextVariant: %u", buildingManager->m_nextVariant); ImGui::Text("m_boundariesDetermined: %d", buildingManager->m_boundariesDetermined); ImGui::Text("m_hideAfterAnimation: %d", buildingManager->m_hideAfterAnimation); - ImGui::Text("#Animated Entries", buildingManager->m_numEntries); + ImGui::Text("#Animated Entries: %d", buildingManager->m_numEntries); if (buildingManager->m_numEntries) { if (ImGui::BeginTable("Animated Entries", 6, ImGuiTableFlags_Borders)) { ImGui::TableSetupColumn("ROI Name"); @@ -293,7 +293,7 @@ void IsleDebug_Render() ImGui::Text("Current act: %d", gameState->GetCurrentAct()); ImGui::Text("Loaded act: %d", gameState->GetLoadedAct()); ImGui::Text("Previous area: %d", gameState->m_previousArea); - ImGui::Text("Unknown 0x42c: %d", gameState->m_unk0x42c); + ImGui::Text("Saved previous area: %d", gameState->m_savedPreviousArea); ImGui::Value("Player count", gameState->m_playerCount); ImGui::TreePop(); } diff --git a/LEGO1/lego/legoomni/include/act2brick.h b/LEGO1/lego/legoomni/include/act2brick.h index 26c51f8c..e2a62ca1 100644 --- a/LEGO1/lego/legoomni/include/act2brick.h +++ b/LEGO1/lego/legoomni/include/act2brick.h @@ -8,6 +8,13 @@ // SIZE 0x194 class Act2Brick : public LegoPathActor { public: + enum { + e_removed = 0, + e_created = 1, + e_placed = 2, + e_atRest = 3, + }; + Act2Brick(); ~Act2Brick() override; // vtable+0x00 @@ -35,7 +42,7 @@ class Act2Brick : public LegoPathActor { MxResult Create(MxS32 p_index); void Remove(); - void FUN_1007a670(MxMatrix& p_param1, MxMatrix& p_param2, LegoPathBoundary* p_boundary); + void Place(MxMatrix& p_localToWorld, MxMatrix& p_endLocalToWorld, LegoPathBoundary* p_boundary); void PlayWhistleSound(); void StopWhistleSound(); void Mute(MxBool p_muted); @@ -44,12 +51,12 @@ class Act2Brick : public LegoPathActor { static const LegoChar* g_lodNames[]; static MxLong g_lastHitActorTime; - LegoCacheSound* m_whistleSound; // 0x154 - undefined m_unk0x158[0x0c]; // 0x158 - undefined4 m_unk0x164; // 0x164 - Mx3DPointFloat m_unk0x168; // 0x168 - Mx3DPointFloat m_unk0x17c; // 0x17c - MxS32 m_unk0x190; // 0x190 + LegoCacheSound* m_whistleSound; // 0x154 + undefined m_unk0x158[0x0c]; // 0x158 + MxU32 m_state; // 0x164 + Mx3DPointFloat m_localToWorldMovementStep; // 0x168 + Mx3DPointFloat m_endLocalToWorld; // 0x17c + MxS32 m_step; // 0x190 }; #endif // ACT2BRICK_H diff --git a/LEGO1/lego/legoomni/include/act3.h b/LEGO1/lego/legoomni/include/act3.h index 28129b4b..9172a6cc 100644 --- a/LEGO1/lego/legoomni/include/act3.h +++ b/LEGO1/lego/legoomni/include/act3.h @@ -18,14 +18,20 @@ class MxQuaternionTransformer; // SIZE 0x0c struct Act3ListElement { - MxU32 m_objectId; // 0x00 - undefined4 m_unk0x04; // 0x04 - MxBool m_hasStarted; // 0x08 + enum InsertMode { + e_replaceAction = 1, + e_queueAction = 2, + e_onlyIfEmpty = 3 + }; + + MxU32 m_objectId; // 0x00 + InsertMode m_insertMode; // 0x04 + MxBool m_hasStarted; // 0x08 Act3ListElement() {} - Act3ListElement(MxU32 p_objectId, undefined4 p_unk0x04, MxBool p_hasStarted) - : m_objectId(p_objectId), m_unk0x04(p_unk0x04), m_hasStarted(p_hasStarted) + Act3ListElement(MxU32 p_objectId, InsertMode p_insertMode, MxBool p_hasStarted) + : m_objectId(p_objectId), m_insertMode(p_insertMode), m_hasStarted(p_hasStarted) { } @@ -36,21 +42,15 @@ struct Act3ListElement { // SIZE 0x10 class Act3List : private list { public: - enum InsertMode { - e_replaceAction = 1, - e_queueAction = 2, - e_onlyIfEmpty = 3 - }; + Act3List() { m_cleared = FALSE; } - Act3List() { m_unk0x0c = 0; } - - void Insert(MxS32 p_objectId, InsertMode p_option); + void Insert(MxS32 p_objectId, Act3ListElement::InsertMode p_option); void DeleteActionWrapper(); void Clear(); void RemoveByObjectIdOrFirst(MxU32 p_objectId); private: - undefined4 m_unk0x0c; // 0x0c + MxU32 m_cleared; // 0x0c }; // VTABLE: LEGO1 0x100d4fc8 @@ -58,7 +58,14 @@ class Act3List : private list { // SIZE 0x0c class Act3State : public LegoState { public: - Act3State() { m_unk0x08 = 0; } + enum { + e_initial = 0, + e_ready = 1, + e_goodEnding = 2, + e_badEnding = 3, + }; + + Act3State() { m_state = Act3State::e_initial; } // FUNCTION: LEGO1 0x1000e2f0 MxBool IsSerializable() override { return FALSE; } @@ -80,11 +87,11 @@ class Act3State : public LegoState { // SYNTHETIC: LEGO1 0x1000e3c0 // Act3State::`scalar deleting destructor' - undefined4 GetUnknown0x08() { return m_unk0x08; } + MxU32 GetState() { return m_state; } // TODO: Most likely getters/setters are not used according to BETA. - undefined4 m_unk0x08; // 0x08 + MxU32 m_state; // 0x08 }; // VTABLE: LEGO1 0x100d9628 diff --git a/LEGO1/lego/legoomni/include/infocenter.h b/LEGO1/lego/legoomni/include/infocenter.h index 2839bc88..8c4f8aa9 100644 --- a/LEGO1/lego/legoomni/include/infocenter.h +++ b/LEGO1/lego/legoomni/include/infocenter.h @@ -119,25 +119,6 @@ struct InfocenterMapEntry { // SIZE 0x1d8 class Infocenter : public LegoWorld { public: - enum Cutscene { - e_noIntro = -1, - e_legoMovie, - e_mindscapeMovie, - e_introMovie, - e_outroMovie, - e_badEndMovie, - e_goodEndMovie - }; - - enum Character { - e_noCharacter = 0, - e_pepper, - e_mama, - e_papa, - e_nick, - e_laura - }; - Infocenter(); ~Infocenter() override; @@ -180,7 +161,7 @@ class Infocenter : public LegoWorld { void UpdateFrameHot(MxBool p_display); void Reset(); - void PlayCutscene(Cutscene p_entityId, MxBool p_scale); + void PlayCutscene(IntroScript::Script p_entityId, MxBool p_scale); void StopCutscene(); void UpdateEnabledGlowControl(MxS32 p_x, MxS32 p_y); @@ -198,7 +179,7 @@ class Infocenter : public LegoWorld { MxS16 m_selectedCharacter; // 0xfc InfocenterState* m_infocenterState; // 0x100 LegoGameState::Area m_destLocation; // 0x104 - Cutscene m_currentCutscene; // 0x108 + IntroScript::Script m_currentCutscene; // 0x108 Radio m_radio; // 0x10c MxStillPresenter* m_dragPresenter; // 0x11c InfocenterMapEntry m_glowInfo[7]; // 0x120 diff --git a/LEGO1/lego/legoomni/include/isle.h b/LEGO1/lego/legoomni/include/isle.h index 658c285c..8d76e78d 100644 --- a/LEGO1/lego/legoomni/include/isle.h +++ b/LEGO1/lego/legoomni/include/isle.h @@ -75,11 +75,11 @@ class Act1State : public LegoState { MxU32 GetState() { return m_state; } ElevatorFloor GetElevatorFloor() { return (ElevatorFloor) m_elevFloor; } - MxU8 GetUnknown21() { return m_unk0x021; } + MxBool IsSpawnInInfocenter() { return m_spawnInInfocenter; } void SetState(MxU32 p_state) { m_state = p_state; } void SetElevatorFloor(ElevatorFloor p_elevFloor) { m_elevFloor = p_elevFloor; } - void SetUnknown21(MxU8 p_unk0x21) { m_unk0x021 = p_unk0x21; } + void SetSpawnInInfocenter(MxBool p_spawnInInfocenter) { m_spawnInInfocenter = p_spawnInInfocenter; } // SYNTHETIC: LEGO1 0x10033960 // Act1State::`scalar deleting destructor' @@ -90,11 +90,11 @@ class Act1State : public LegoState { IsleScript::Script m_currentCptClickDialogue; // 0x014 MxU32 m_state; // 0x018 MxS16 m_elevFloor; // 0x01c - MxBool m_unk0x01e; // 0x01e - MxBool m_unk0x01f; // 0x01f + MxBool m_playingFloor2Animation; // 0x01e + MxBool m_switchedToArea; // 0x01f MxBool m_planeActive; // 0x020 - undefined m_unk0x021; // 0x021 - MxBool m_unk0x022; // 0x022 + MxBool m_spawnInInfocenter; // 0x021 + MxBool m_playedExitExplanation; // 0x022 undefined m_unk0x023; // 0x023 LegoNamedPlane m_motocyclePlane; // 0x024 LegoNamedPlane m_bikePlane; // 0x070 diff --git a/LEGO1/lego/legoomni/include/legoact2.h b/LEGO1/lego/legoomni/include/legoact2.h index f84e9b0d..23d05fae 100644 --- a/LEGO1/lego/legoomni/include/legoact2.h +++ b/LEGO1/lego/legoomni/include/legoact2.h @@ -15,9 +15,13 @@ class MxEndActionNotificationParam; // SIZE 0x10 class LegoAct2State : public LegoState { public: + enum { + c_badEnding = 104 + }; + LegoAct2State() { - m_unk0x08 = 0; + m_state = 0; m_enabled = FALSE; } ~LegoAct2State() override {} @@ -43,14 +47,14 @@ class LegoAct2State : public LegoState { // LegoAct2State::`scalar deleting destructor' // FUNCTION: BETA10 0x100151b0 - void SetUnknown0x08(undefined4 p_unk0x08) { m_unk0x08 = p_unk0x08; } + void SetState(MxU32 p_state) { m_state = p_state; } - undefined4 GetUnknown0x08() { return m_unk0x08; } + MxU32 GetState() { return m_state; } // TODO: Most likely getters/setters are not used according to BETA. (?) - undefined4 m_unk0x08; // 0x08 - MxBool m_enabled; // 0x0c + MxU32 m_state; // 0x08 + MxBool m_enabled; // 0x0c }; // VTABLE: LEGO1 0x100d82e0 diff --git a/LEGO1/lego/legoomni/include/legoanimactor.h b/LEGO1/lego/legoomni/include/legoanimactor.h index 5b752586..bfe03344 100644 --- a/LEGO1/lego/legoomni/include/legoanimactor.h +++ b/LEGO1/lego/legoomni/include/legoanimactor.h @@ -8,13 +8,13 @@ class LegoAnim; // SIZE 0x20 struct LegoAnimActorStruct { - LegoAnimActorStruct(float p_unk0x00, LegoAnim* p_AnimTreePtr, LegoROI** p_roiMap, MxU32 p_numROIs); + LegoAnimActorStruct(float p_worldSpeed, LegoAnim* p_AnimTreePtr, LegoROI** p_roiMap, MxU32 p_numROIs); ~LegoAnimActorStruct(); float GetDuration(); // FUNCTION: BETA10 0x1000fb10 - float GetUnknown0x00() { return m_unk0x00; } + float GetWorldSpeed() { return m_worldSpeed; } // FUNCTION: BETA10 0x10012210 LegoAnim* GetAnimTreePtr() { return m_AnimTreePtr; } @@ -23,7 +23,7 @@ struct LegoAnimActorStruct { LegoROI** GetROIMap() { return m_roiMap; } // TODO: Possibly private - float m_unk0x00; // 0x00 + float m_worldSpeed; // 0x00 LegoAnim* m_AnimTreePtr; // 0x04 LegoROI** m_roiMap; // 0x08 MxU32 m_numROIs; // 0x0c @@ -47,9 +47,14 @@ class LegoAnimActor : public virtual LegoPathActor { void Animate(float p_time) override; // vtable+0x70 void VTable0x74(Matrix4& p_transform) override; // vtable+0x74 - virtual MxResult FUN_1001c1f0(float& p_und); - virtual MxResult FUN_1001c360(float, Matrix4& p_transform); - virtual MxResult FUN_1001c450(LegoAnim* p_AnimTreePtr, float p_unk0x00, LegoROI** p_roiMap, MxU32 p_numROIs); + virtual MxResult GetTimeInCycle(float& p_timeInCycle); + virtual MxResult AnimateWithTransform(float p_time, Matrix4& p_transform); + virtual MxResult CreateAnimActorStruct( + LegoAnim* p_AnimTreePtr, + float p_worldSpeed, + LegoROI** p_roiMap, + MxU32 p_numROIs + ); virtual void ClearMaps(); // FUNCTION: LEGO1 0x1000fba0 diff --git a/LEGO1/lego/legoomni/include/legocachsound.h b/LEGO1/lego/legoomni/include/legocachsound.h index fd9df5b0..cf5e1e2e 100644 --- a/LEGO1/lego/legoomni/include/legocachsound.h +++ b/LEGO1/lego/legoomni/include/legocachsound.h @@ -13,6 +13,7 @@ class LegoCacheSound : public MxCore { public: LegoCacheSound(); + LegoCacheSound(LegoCacheSound& p_sound); ~LegoCacheSound() override; // vtable+0x00 // FUNCTION: LEGO1 0x10006580 @@ -49,6 +50,8 @@ class LegoCacheSound : public MxCore { void SetDistance(MxS32 p_min, MxS32 p_max); void MuteSilence(MxBool p_muted); void MuteStop(MxBool p_mute); + LegoCacheSound& operator=(LegoCacheSound& p_sound); + void CopyFrom(LegoCacheSound& p_sound); // SYNTHETIC: LEGO1 0x10006610 // SYNTHETIC: BETA10 0x100675b0 diff --git a/LEGO1/lego/legoomni/include/legoentity.h b/LEGO1/lego/legoomni/include/legoentity.h index a6d9d9ee..ed1dd202 100644 --- a/LEGO1/lego/legoomni/include/legoentity.h +++ b/LEGO1/lego/legoomni/include/legoentity.h @@ -76,9 +76,9 @@ class LegoEntity : public MxEntity { virtual void SwitchColor(LegoROI* p_roi); // vtable+0x48 virtual void SwitchMood(); // vtable+0x4c - void FUN_10010c30(); + void TransformPointOfView(); void SetType(MxU8 p_type); - void SetLocation(const Vector3& p_location, const Vector3& p_direction, const Vector3& p_up, MxBool p_und); + void SetLocation(const Vector3& p_location, const Vector3& p_direction, const Vector3& p_up, MxBool p_updateCamera); Mx3DPointFloat GetWorldDirection(); Mx3DPointFloat GetWorldUp(); Mx3DPointFloat GetWorldPosition(); diff --git a/LEGO1/lego/legoomni/include/legogamestate.h b/LEGO1/lego/legoomni/include/legogamestate.h index 44d5bc27..94f827a7 100644 --- a/LEGO1/lego/legoomni/include/legogamestate.h +++ b/LEGO1/lego/legoomni/include/legogamestate.h @@ -179,7 +179,7 @@ class LegoGameState { History(); void WriteScoreHistory(); MxResult Serialize(LegoStorage* p_storage); - ScoreItem* FindPlayerInScoreHistory(Username* p_player, MxS16 p_unk0x24, MxS32& p_unk0x2c); + ScoreItem* FindPlayerInScoreHistory(Username* p_player, MxS16 p_playerId, MxS32& p_playerScoreHistoryIndex); // FUNCTION: BETA10 0x1002c2b0 MxS16 GetCount() { return m_count; } @@ -269,7 +269,7 @@ class LegoGameState { MxBool m_isDirty; // 0x420 Area m_currentArea; // 0x424 Area m_previousArea; // 0x428 - Area m_unk0x42c; // 0x42c + Area m_savedPreviousArea; // 0x42c static const InternationalCharacter g_intCharacters[8]; }; diff --git a/LEGO1/lego/legoomni/include/legomain.h b/LEGO1/lego/legoomni/include/legomain.h index c5cb23af..8fdf9ecb 100644 --- a/LEGO1/lego/legoomni/include/legomain.h +++ b/LEGO1/lego/legoomni/include/legomain.h @@ -208,6 +208,7 @@ class LegoOmni : public MxOmni { void SetVersion10(MxBool p_version10) { m_version10 = p_version10; } MxBool IsVersion10() { return m_version10; } + LEGO1_EXPORT void LoadSiLoader(); // SYNTHETIC: LEGO1 0x10058b30 // SYNTHETIC: BETA10 0x1008f8d0 diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index 849662df..1b8d7254 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -72,7 +72,12 @@ MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id); void EnableAnimations(MxBool p_enable); void SetAppCursor(Cursor p_cursor); MxBool CanExit(); -MxBool RemoveFromWorld(MxAtomId& p_entityAtom, MxS32 p_entityId, MxAtomId& p_worldAtom, MxS32 p_worldEntityId); +MxBool RemoveFromWorld( + const MxAtomId& p_entityAtom, + MxS32 p_entityId, + const MxAtomId& p_worldAtom, + MxS32 p_worldEntityId +); MxS32 UpdateLightPosition(MxS32 p_increase); void SetLightPosition(MxS32 p_index); LegoNamedTexture* ReadNamedTexture(LegoStorage* p_storage); diff --git a/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h b/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h index b1a69dd0..d85656ca 100644 --- a/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h +++ b/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h @@ -48,6 +48,7 @@ class MxBackgroundAudioManager : public MxCore { virtual MxResult Create(MxAtomId& p_script, MxU32 p_frequencyMS); void Init(); + void Update(MxS32 p_targetVolume, MxS32 p_speed, MxPresenter::TickleState p_tickleState); void Stop(); void LowerVolume(); void RaiseVolume(); diff --git a/LEGO1/lego/legoomni/include/raceskel.h b/LEGO1/lego/legoomni/include/raceskel.h index 92859c68..938316a8 100644 --- a/LEGO1/lego/legoomni/include/raceskel.h +++ b/LEGO1/lego/legoomni/include/raceskel.h @@ -15,7 +15,7 @@ class RaceSkel : public LegoAnimActor { void ParseAction(char* p_extra) override; // vtable+0x20 - MxResult FUN_1001c360(float p_und, Matrix4& p_transform) override; + MxResult AnimateWithTransform(float p_time, Matrix4& p_transform) override; virtual void FUN_10071c80(Vector3& p_vec); diff --git a/LEGO1/lego/legoomni/src/actors/act2actor.cpp b/LEGO1/lego/legoomni/src/actors/act2actor.cpp index 799b196a..4062fef1 100644 --- a/LEGO1/lego/legoomni/src/actors/act2actor.cpp +++ b/LEGO1/lego/legoomni/src/actors/act2actor.cpp @@ -57,39 +57,39 @@ MxBool g_unk0x100f0f28 = FALSE; // GLOBAL: LEGO1 0x100f0f30 // GLOBAL: BETA10 0x101dbe48 -MxS32 g_unk0x100f0f30[] = {2, 23, 32, 66, 71, 72, 73, -1}; +MxS32 g_stage0Plants[] = {2, 23, 32, 66, 71, 72, 73, -1}; // GLOBAL: LEGO1 0x100f0f50 // GLOBAL: BETA10 0x101dbe68 -MxS32 g_unk0x100f0f50[] = {0, 7, 16, 18, 20, 21, 34, 49, 58, 59, 63, 65, 69, 74, -1}; +MxS32 g_stage1Plants[] = {0, 7, 16, 18, 20, 21, 34, 49, 58, 59, 63, 65, 69, 74, -1}; // GLOBAL: LEGO1 0x100f0f90 // GLOBAL: BETA10 0x101dbea8 -MxS32 g_unk0x100f0f90[] = {12, 19, 24, 48, 60, -1}; +MxS32 g_stage2Plants[] = {12, 19, 24, 48, 60, -1}; // GLOBAL: LEGO1 0x100f0fa8 // GLOBAL: BETA10 0x101dbec0 -MxS32 g_unk0x100f0fa8[] = {8, 15, 46, -1}; +MxS32 g_stage3Plants[] = {8, 15, 46, -1}; // GLOBAL: LEGO1 0x100f0fb8 // GLOBAL: BETA10 0x101dbed0 -MxS32 g_unk0x100f0fb8[] = {25, 26, 28, 29, 38, 39, 42, 50, 51, 56, -1}; +MxS32 g_stage4Plants[] = {25, 26, 28, 29, 38, 39, 42, 50, 51, 56, -1}; // GLOBAL: LEGO1 0x100f0fe8 // GLOBAL: BETA10 0x101dbf00 -MxS32 g_unk0x100f0fe8[] = {3, 40, 53, 55, -1}; +MxS32 g_stage5Plants[] = {3, 40, 53, 55, -1}; // GLOBAL: LEGO1 0x100f1000 // GLOBAL: BETA10 0x101dbf18 -MxS32 g_unk0x100f1000[] = {22, 33, 41, 45, 67, -1}; +MxS32 g_stage6Plants[] = {22, 33, 41, 45, 67, -1}; // GLOBAL: LEGO1 0x100f1018 // GLOBAL: BETA10 0x101dbf30 -MxS32 g_unk0x100f1018[] = {13, 30, 31, 62, -1}; +MxS32 g_stage7Plants[] = {13, 30, 31, 62, -1}; // GLOBAL: LEGO1 0x100f1030 // GLOBAL: BETA10 0x101dbf48 -MxS32 g_unk0x100f1030[] = {1, 27, 37, 44, 47, 54, 61, 64, -1}; +MxS32 g_stage8Plants[] = {1, 27, 37, 44, 47, 54, 61, 64, -1}; // --- End of indices into g_plantInfo --- @@ -144,7 +144,7 @@ void Act2Actor::SetROI(LegoROI* p_roi, MxBool p_bool1, MxBool p_bool2) void Act2Actor::FUN_10018980() { for (MxS32 i = 0; i < m_animMaps.size(); i++) { - if (m_animMaps[i]->GetUnknown0x00() == -1.0f) { + if (m_animMaps[i]->GetWorldSpeed() == -1.0f) { m_shootAnim = m_animMaps[i]; } } @@ -727,9 +727,9 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f0f30[i] != -1; i++) { - if (plantInfo[g_unk0x100f0f30[i]].m_counter) { - result = plantInfo[g_unk0x100f0f30[i]].m_entity; + for (i = 0; g_stage0Plants[i] != -1; i++) { + if (plantInfo[g_stage0Plants[i]].m_counter) { + result = plantInfo[g_stage0Plants[i]].m_entity; break; } } @@ -741,9 +741,9 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f0f50[i] != -1; i++) { - if (plantInfo[g_unk0x100f0f50[i]].m_counter) { - result = plantInfo[g_unk0x100f0f50[i]].m_entity; + for (i = 0; g_stage1Plants[i] != -1; i++) { + if (plantInfo[g_stage1Plants[i]].m_counter) { + result = plantInfo[g_stage1Plants[i]].m_entity; break; } } @@ -759,9 +759,9 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f0f90[i] != -1; i++) { - if (plantInfo[g_unk0x100f0f90[i]].m_counter) { - result = plantInfo[g_unk0x100f0f90[i]].m_entity; + for (i = 0; g_stage2Plants[i] != -1; i++) { + if (plantInfo[g_stage2Plants[i]].m_counter) { + result = plantInfo[g_stage2Plants[i]].m_entity; break; } } @@ -781,9 +781,9 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f0fa8[i] != -1; i++) { - if (plantInfo[g_unk0x100f0fa8[i]].m_counter) { - result = plantInfo[g_unk0x100f0fa8[i]].m_entity; + for (i = 0; g_stage3Plants[i] != -1; i++) { + if (plantInfo[g_stage3Plants[i]].m_counter) { + result = plantInfo[g_stage3Plants[i]].m_entity; break; } } @@ -799,9 +799,9 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f0fb8[i] != -1; i++) { - if (plantInfo[g_unk0x100f0fb8[i]].m_counter) { - result = plantInfo[g_unk0x100f0fb8[i]].m_entity; + for (i = 0; g_stage4Plants[i] != -1; i++) { + if (plantInfo[g_stage4Plants[i]].m_counter) { + result = plantInfo[g_stage4Plants[i]].m_entity; break; } } @@ -813,9 +813,9 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f0fe8[i] != -1; i++) { - if (plantInfo[g_unk0x100f0fe8[i]].m_counter) { - result = plantInfo[g_unk0x100f0fe8[i]].m_entity; + for (i = 0; g_stage5Plants[i] != -1; i++) { + if (plantInfo[g_stage5Plants[i]].m_counter) { + result = plantInfo[g_stage5Plants[i]].m_entity; break; } } @@ -827,9 +827,9 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f1000[i] != -1; i++) { - if (plantInfo[g_unk0x100f1000[i]].m_counter) { - result = plantInfo[g_unk0x100f1000[i]].m_entity; + for (i = 0; g_stage6Plants[i] != -1; i++) { + if (plantInfo[g_stage6Plants[i]].m_counter) { + result = plantInfo[g_stage6Plants[i]].m_entity; break; } } @@ -841,18 +841,18 @@ LegoEntity* Act2Actor::FUN_10019b90(MxBool* p_param) *p_param = TRUE; } else { - for (i = 0; g_unk0x100f1018[i] != -1; i++) { - if (plantInfo[g_unk0x100f1018[i]].m_counter) { - result = plantInfo[g_unk0x100f1018[i]].m_entity; + for (i = 0; g_stage7Plants[i] != -1; i++) { + if (plantInfo[g_stage7Plants[i]].m_counter) { + result = plantInfo[g_stage7Plants[i]].m_entity; break; } } } break; case 8: - for (i = 0; g_unk0x100f1030[i] != -1; i++) { - if (plantInfo[g_unk0x100f1030[i]].m_counter) { - result = plantInfo[g_unk0x100f1030[i]].m_entity; + for (i = 0; g_stage8Plants[i] != -1; i++) { + if (plantInfo[g_stage8Plants[i]].m_counter) { + result = plantInfo[g_stage8Plants[i]].m_entity; break; } } diff --git a/LEGO1/lego/legoomni/src/actors/act3actors.cpp b/LEGO1/lego/legoomni/src/actors/act3actors.cpp index 2926466f..6a0a1132 100644 --- a/LEGO1/lego/legoomni/src/actors/act3actors.cpp +++ b/LEGO1/lego/legoomni/src/actors/act3actors.cpp @@ -271,7 +271,7 @@ void Act3Cop::ParseAction(char* p_extra) } for (i = 0; i < m_animMaps.size(); i++) { - if (m_animMaps[i]->GetUnknown0x00() == -1.0f) { + if (m_animMaps[i]->GetWorldSpeed() == -1.0f) { m_eatAnim = m_animMaps[i]; } } @@ -559,7 +559,7 @@ void Act3Brickster::ParseAction(char* p_extra) ((Act3*) m_world)->SetBrickster(this); for (MxS32 i = 0; i < m_animMaps.size(); i++) { - if (m_animMaps[i]->GetUnknown0x00() == -1.0f) { + if (m_animMaps[i]->GetWorldSpeed() == -1.0f) { m_shootAnim = m_animMaps[i]; } } diff --git a/LEGO1/lego/legoomni/src/actors/bike.cpp b/LEGO1/lego/legoomni/src/actors/bike.cpp index fbdce39e..2924a2bd 100644 --- a/LEGO1/lego/legoomni/src/actors/bike.cpp +++ b/LEGO1/lego/legoomni/src/actors/bike.cpp @@ -111,8 +111,8 @@ void Bike::ActivateSceneActions() PlayMusic(JukeboxScript::c_InformationCenter_Music); Act1State* act1state = (Act1State*) GameState()->GetState("Act1State"); - if (!act1state->m_unk0x022) { - act1state->m_unk0x022 = TRUE; + if (!act1state->m_playedExitExplanation) { + act1state->m_playedExitExplanation = TRUE; MxMatrix mat(UserActor()->GetROI()->GetLocal2World()); mat.TranslateBy(mat[2][0] * 2.5, mat[2][1] + 0.7, mat[2][2] * 2.5); diff --git a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp index 5b701c46..03c97f7b 100644 --- a/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp +++ b/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp @@ -195,8 +195,8 @@ void DuneBuggy::ActivateSceneActions() PlayMusic(JukeboxScript::c_GarageArea_Music); Act1State* act1state = (Act1State*) GameState()->GetState("Act1State"); - if (!act1state->m_unk0x022) { - act1state->m_unk0x022 = TRUE; + if (!act1state->m_playedExitExplanation) { + act1state->m_playedExitExplanation = TRUE; MxMatrix mat(UserActor()->GetROI()->GetLocal2World()); mat.TranslateBy(mat[2][0] * 2.5, mat[2][1] + 0.7, mat[2][2] * 2.5); diff --git a/LEGO1/lego/legoomni/src/actors/helicopter.cpp b/LEGO1/lego/legoomni/src/actors/helicopter.cpp index fc2addb3..0337e96a 100644 --- a/LEGO1/lego/legoomni/src/actors/helicopter.cpp +++ b/LEGO1/lego/legoomni/src/actors/helicopter.cpp @@ -342,7 +342,7 @@ MxLong Helicopter::HandleEndAnim(LegoEndAnimNotificationParam& p_param) assert(m_world && m_world->GetCameraController()); m_world->GetCameraController()->SetWorldTransform(at, dir, up); - FUN_10010c30(); + TransformPointOfView(); result = 1; break; } @@ -388,13 +388,13 @@ void Helicopter::VTable0x74(Matrix4& p_transform) { if (m_userNavFlag) { m_roi->UpdateTransformationRelativeToParent(p_transform); - FUN_10010c30(); + TransformPointOfView(); } else { m_roi->SetLocal2World(p_transform); m_roi->WrappedUpdateWorldData(); if (m_cameraFlag) { - FUN_10010c30(); + TransformPointOfView(); } } } diff --git a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp index d366761e..efba4068 100644 --- a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp +++ b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp @@ -93,7 +93,7 @@ void IslePathActor::Enter() SetUserActor(this); FUN_1001b660(); - FUN_10010c30(); + TransformPointOfView(); } } @@ -152,7 +152,7 @@ void IslePathActor::Exit() } FUN_1001b660(); - FUN_10010c30(); + TransformPointOfView(); ResetViewVelocity(); } @@ -633,7 +633,7 @@ void IslePathActor::VTable0xec(MxMatrix p_transform, LegoPathBoundary* p_boundar m_roi->SetLocal2World(p_transform); if (m_cameraFlag) { ResetViewVelocity(); - FUN_10010c30(); + TransformPointOfView(); } } diff --git a/LEGO1/lego/legoomni/src/actors/jetski.cpp b/LEGO1/lego/legoomni/src/actors/jetski.cpp index cdde06ee..a85a5527 100644 --- a/LEGO1/lego/legoomni/src/actors/jetski.cpp +++ b/LEGO1/lego/legoomni/src/actors/jetski.cpp @@ -165,11 +165,11 @@ void Jetski::ActivateSceneActions() Act1State* act1state = (Act1State*) GameState()->GetState("Act1State"); if (!act1state->m_state) { - if (act1state->m_unk0x022) { + if (act1state->m_playedExitExplanation) { PlayCamAnim(this, FALSE, 68, TRUE); } else { - act1state->m_unk0x022 = TRUE; + act1state->m_playedExitExplanation = TRUE; LegoPathActor* user = UserActor(); if (user != NULL) { diff --git a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp index 3a4229b3..e09168b1 100644 --- a/LEGO1/lego/legoomni/src/actors/motorcycle.cpp +++ b/LEGO1/lego/legoomni/src/actors/motorcycle.cpp @@ -153,8 +153,8 @@ void Motocycle::ActivateSceneActions() PlayMusic(JukeboxScript::c_PoliceStation_Music); Act1State* act1state = (Act1State*) GameState()->GetState("Act1State"); - if (!act1state->m_unk0x022) { - act1state->m_unk0x022 = TRUE; + if (!act1state->m_playedExitExplanation) { + act1state->m_playedExitExplanation = TRUE; MxMatrix mat(UserActor()->GetROI()->GetLocal2World()); mat.TranslateBy(mat[2][0] * 2.5, mat[2][1] + 0.7, mat[2][2] * 2.5); diff --git a/LEGO1/lego/legoomni/src/actors/skateboard.cpp b/LEGO1/lego/legoomni/src/actors/skateboard.cpp index 70d1be72..09b847f7 100644 --- a/LEGO1/lego/legoomni/src/actors/skateboard.cpp +++ b/LEGO1/lego/legoomni/src/actors/skateboard.cpp @@ -151,8 +151,8 @@ void SkateBoard::ActivateSceneActions() if (m_act1state->m_state != Act1State::e_pizza) { PlayMusic(JukeboxScript::c_BeachBlvd_Music); - if (!m_act1state->m_unk0x022) { - m_act1state->m_unk0x022 = TRUE; + if (!m_act1state->m_playedExitExplanation) { + m_act1state->m_playedExitExplanation = TRUE; MxMatrix mat(UserActor()->GetROI()->GetLocal2World()); mat.TranslateBy(mat[2][0] * 2.5, mat[2][1] + 0.2, mat[2][2] * 2.5); diff --git a/LEGO1/lego/legoomni/src/audio/legocachsound.cpp b/LEGO1/lego/legoomni/src/audio/legocachsound.cpp index 8203a0df..f80ad4eb 100644 --- a/LEGO1/lego/legoomni/src/audio/legocachsound.cpp +++ b/LEGO1/lego/legoomni/src/audio/legocachsound.cpp @@ -272,6 +272,29 @@ void LegoCacheSound::MuteStop(MxBool p_muted) } } +// FUNCTION: BETA10 0x10066fa9 +LegoCacheSound::LegoCacheSound(LegoCacheSound& p_sound) +{ + CopyFrom(p_sound); +} + +// FUNCTION: BETA10 0x10067062 +LegoCacheSound& LegoCacheSound::operator=(LegoCacheSound& p_sound) +{ + if (this == &p_sound) { + return *this; + } + + CopyFrom(p_sound); + return *this; +} + +// FUNCTION: BETA10 0x1006709d +void LegoCacheSound::CopyFrom(LegoCacheSound& p_sound) +{ + assert(0); +} + // FUNCTION: LEGO1 0x10006d80 // FUNCTION: BETA10 0x100670e7 MxString LegoCacheSound::GetBaseFilename(MxString& p_path) diff --git a/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp b/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp index 567dd882..96617e52 100644 --- a/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp +++ b/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp @@ -119,7 +119,12 @@ void MxBackgroundAudioManager::FadeInPendingPresenter() MxS32 compare, volume; if (m_activePresenter == NULL) { - if (m_pendingPresenter) { + if (m_pendingPresenter && m_pendingPresenter->GetCurrentTickleState() >= MxPresenter::e_streaming) { + if (!m_pendingPresenter->IsEnabled()) { + m_pendingPresenter->Enable(TRUE); + m_pendingPresenter->SetTickleState(MxPresenter::e_streaming); + } + if (m_volumeSuppressionAmount != 0) { compare = 30; } @@ -221,6 +226,12 @@ void MxBackgroundAudioManager::StartAction(MxParam& p_param) m_action2.SetObjectId(m_pendingPresenter->GetAction()->GetObjectId()); m_targetVolume = ((MxDSSound*) (m_pendingPresenter->GetAction()))->GetVolume(); m_pendingPresenter->SetVolume(0); + + // Disabling the action here and starting it later once the actively presented music has been faded out. + // This was not necessary in retail because the streaming layer would implicitly not start another action + // before the previous one has ended (since it's all coming from JUKEBOX.SI), however since we now + // allow loading music from multiple SI files this would cause the new music to start immediately. + m_pendingPresenter->GetAction()->SetFlags(m_pendingPresenter->GetAction()->GetFlags() & ~MxDSAction::c_enabled); } // FUNCTION: LEGO1 0x1007f200 @@ -254,7 +265,8 @@ MxResult MxBackgroundAudioManager::PlayMusic( return SUCCESS; } - if (m_action2.GetObjectId() == -1 && m_action1.GetObjectId() != p_action.GetObjectId()) { + if (m_action2.GetObjectId() == -1 && + (m_action1.GetObjectId() != p_action.GetObjectId() || m_action1.GetAtomId() != p_action.GetAtomId())) { MxDSAction action; action.SetAtomId(GetCurrentAction().GetAtomId()); action.SetObjectId(GetCurrentAction().GetObjectId()); @@ -275,6 +287,10 @@ MxResult MxBackgroundAudioManager::PlayMusic( m_tickleState = p_tickleState; m_speed = p_speed; } + else { + m_action2.SetAtomId(MxAtomId()); + m_action2.SetObjectId(-1); + } return result; } @@ -282,6 +298,17 @@ MxResult MxBackgroundAudioManager::PlayMusic( return FAILURE; } +// FUNCTION: BETA10 0x100e92ec +void MxBackgroundAudioManager::Update(MxS32 p_targetVolume, MxS32 p_speed, MxPresenter::TickleState p_tickleState) +{ + assert(p_targetVolume >= 0 && p_targetVolume <= 100); + assert(p_speed > 0); + + m_tickleState = p_tickleState; + m_speed = p_speed; + m_targetVolume = p_targetVolume; +} + // FUNCTION: LEGO1 0x1007f470 // FUNCTION: BETA10 0x100e9388 void MxBackgroundAudioManager::Stop() diff --git a/LEGO1/lego/legoomni/src/common/legogamestate.cpp b/LEGO1/lego/legoomni/src/common/legogamestate.cpp index c97a5abc..b306e06a 100644 --- a/LEGO1/lego/legoomni/src/common/legogamestate.cpp +++ b/LEGO1/lego/legoomni/src/common/legogamestate.cpp @@ -167,7 +167,7 @@ LegoGameState::LegoGameState() m_jukeboxMusic = JukeboxScript::c_noneJukebox; m_currentArea = e_undefined; m_previousArea = e_undefined; - m_unk0x42c = e_undefined; + m_savedPreviousArea = e_undefined; m_playerCount = 0; m_isDirty = FALSE; m_loadedAct = e_actNotFound; @@ -326,7 +326,7 @@ MxResult LegoGameState::Save(MxULong p_slot) } } - area = m_unk0x42c; + area = m_savedPreviousArea; storage.WriteU16(area); SerializeScoreHistory(LegoFile::c_write); m_isDirty = FALSE; @@ -445,10 +445,10 @@ MxResult LegoGameState::Load(MxULong p_slot) storage.ReadS16(actArea); if (m_currentAct == e_act1) { - m_unk0x42c = e_undefined; + m_savedPreviousArea = e_undefined; } else { - m_unk0x42c = (Area) actArea; + m_savedPreviousArea = (Area) actArea; } result = SUCCESS; @@ -1205,7 +1205,7 @@ void LegoGameState::Init() } } - m_unk0x42c = e_undefined; + m_savedPreviousArea = e_undefined; } // FUNCTION: BETA10 0x10086510 @@ -1584,18 +1584,18 @@ void LegoGameState::History::WriteScoreHistory() // FUNCTION: BETA10 0x1008732a LegoGameState::ScoreItem* LegoGameState::History::FindPlayerInScoreHistory( LegoGameState::Username* p_player, - MxS16 p_unk0x24, - MxS32& p_unk0x2c + MxS16 p_playerId, + MxS32& p_playerScoreHistoryIndex ) { MxS32 i = 0; for (; i < m_count; i++) { - if (!memcmp(p_player, &m_scores[i].m_name, sizeof(*p_player)) && m_scores[i].m_playerId == p_unk0x24) { + if (!memcmp(p_player, &m_scores[i].m_name, sizeof(*p_player)) && m_scores[i].m_playerId == p_playerId) { break; } } - p_unk0x2c = i; + p_playerScoreHistoryIndex = i; if (i >= m_count) { return NULL; diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index fdc6755a..f8ef1bab 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -2,6 +2,7 @@ #include "3dmanager/lego3dmanager.h" #include "anim/legoanim.h" +#include "extensions/siloader.h" #include "isle.h" #include "isle_actions.h" #include "islepathactor.h" @@ -37,6 +38,8 @@ #include #include +using namespace Extensions; + // FUNCTION: LEGO1 0x1003dd70 // FUNCTION: BETA10 0x100d3410 LegoROI* PickROI(MxLong p_x, MxLong p_y) @@ -500,6 +503,12 @@ MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id) { LegoWorld* world = CurrentWorld(); + auto result = + Extension::Call(HandleRemove, SiLoader::StreamObject{p_atomId, p_id}, world).value_or(std::nullopt); + if (result) { + return result.value(); + } + if (world) { MxCore* object = world->Find(p_atomId, p_id); @@ -525,10 +534,21 @@ MxBool RemoveFromCurrentWorld(const MxAtomId& p_atomId, MxS32 p_id) } // FUNCTION: LEGO1 0x1003ee80 -MxBool RemoveFromWorld(MxAtomId& p_entityAtom, MxS32 p_entityId, MxAtomId& p_worldAtom, MxS32 p_worldEntityId) +MxBool RemoveFromWorld( + const MxAtomId& p_entityAtom, + MxS32 p_entityId, + const MxAtomId& p_worldAtom, + MxS32 p_worldEntityId +) { LegoWorld* world = FindWorld(p_worldAtom, p_worldEntityId); + auto result = Extension::Call(HandleRemove, SiLoader::StreamObject{p_entityAtom, p_entityId}, world) + .value_or(std::nullopt); + if (result) { + return result.value(); + } + if (world) { MxCore* object = world->Find(p_entityAtom, p_entityId); diff --git a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp index 7e3d9294..fde06e8f 100644 --- a/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp @@ -630,7 +630,6 @@ void MxTransitionManager::SetupCopyRect(LPDDSURFACEDESC p_ddsc) // Setup display surface if ((m_waitIndicator->GetAction()->GetFlags() & MxDSAction::c_bit5) != 0) { MxDisplaySurface* displaySurface = VideoManager()->GetDisplaySurface(); - MxBool und = FALSE; displaySurface->VTable0x2c( p_ddsc, m_waitIndicator->GetBitmap(), @@ -639,8 +638,7 @@ void MxTransitionManager::SetupCopyRect(LPDDSURFACEDESC p_ddsc) m_waitIndicator->GetLocation().GetX(), m_waitIndicator->GetLocation().GetY(), m_waitIndicator->GetWidth(), - m_waitIndicator->GetHeight(), - und + m_waitIndicator->GetHeight() ); } else { diff --git a/LEGO1/lego/legoomni/src/entity/act2brick.cpp b/LEGO1/lego/legoomni/src/entity/act2brick.cpp index 16e0d2eb..7fc1d64c 100644 --- a/LEGO1/lego/legoomni/src/entity/act2brick.cpp +++ b/LEGO1/lego/legoomni/src/entity/act2brick.cpp @@ -31,7 +31,7 @@ MxLong Act2Brick::g_lastHitActorTime = 0; Act2Brick::Act2Brick() { m_whistleSound = NULL; - m_unk0x164 = 0; + m_state = Act2Brick::e_removed; } // FUNCTION: LEGO1 0x1007a470 @@ -72,7 +72,7 @@ MxResult Act2Brick::Create(MxS32 p_index) m_roi->SetEntity(this); CurrentWorld()->Add(this); - m_unk0x164 = 1; + m_state = Act2Brick::e_created; return SUCCESS; } @@ -88,27 +88,27 @@ void Act2Brick::Remove() m_roi = NULL; } - m_unk0x164 = 0; + m_state = Act2Brick::e_removed; } // FUNCTION: LEGO1 0x1007a670 // FUNCTION: BETA10 0x10012c04 -void Act2Brick::FUN_1007a670(MxMatrix& p_param1, MxMatrix& p_param2, LegoPathBoundary* p_boundary) +void Act2Brick::Place(MxMatrix& p_localToWorld, MxMatrix& p_endLocalToWorld, LegoPathBoundary* p_boundary) { - m_unk0x17c = p_param2[3]; - m_unk0x168 = p_param2[3]; - m_unk0x168 -= p_param1[3]; - m_unk0x168 /= 8.0f; + m_endLocalToWorld = p_endLocalToWorld[3]; + m_localToWorldMovementStep = p_endLocalToWorld[3]; + m_localToWorldMovementStep -= p_localToWorld[3]; + m_localToWorldMovementStep /= 8.0f; - m_unk0x190 = 0; + m_step = 0; TickleManager()->RegisterClient(this, 20); - m_unk0x164 = 2; + m_state = Act2Brick::e_placed; CurrentWorld()->PlaceActor(this); p_boundary->AddActor(this); SetActorState(c_disabled); - m_roi->SetLocal2World(p_param1); + m_roi->SetLocal2World(p_localToWorld); m_roi->WrappedUpdateWorldData(); m_roi->SetVisibility(TRUE); } @@ -136,15 +136,15 @@ MxResult Act2Brick::HitActor(LegoPathActor* p_actor, MxBool) MxResult Act2Brick::Tickle() { MxMatrix local2world(m_roi->GetLocal2World()); - m_unk0x190++; + m_step++; - if (m_unk0x190 >= 8) { - local2world.SetTranslation(m_unk0x17c[0], m_unk0x17c[1], m_unk0x17c[2]); - m_unk0x164 = 3; + if (m_step >= 8) { + local2world.SetTranslation(m_endLocalToWorld[0], m_endLocalToWorld[1], m_endLocalToWorld[2]); + m_state = Act2Brick::e_atRest; TickleManager()->UnregisterClient(this); } else { - VPV3(local2world[3], local2world[3], m_unk0x168); + VPV3(local2world[3], local2world[3], m_localToWorldMovementStep); } m_roi->SetLocal2World(local2world); @@ -165,7 +165,7 @@ MxLong Act2Brick::Notify(MxParam& p_param) StopWhistleSound(); } - MxNotificationParam param(c_notificationType22, this); + MxNotificationParam param(c_notificationAct2Brick, this); NotificationManager()->Send(CurrentWorld(), param); return 1; } diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index 927d8125..73981958 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -158,7 +158,12 @@ void LegoEntity::SetROI(LegoROI* p_roi, MxBool p_bool1, MxBool p_bool2) // FUNCTION: LEGO1 0x100109b0 // FUNCTION: BETA10 0x1007e8b8 -void LegoEntity::SetLocation(const Vector3& p_location, const Vector3& p_direction, const Vector3& p_up, MxBool p_und) +void LegoEntity::SetLocation( + const Vector3& p_location, + const Vector3& p_direction, + const Vector3& p_up, + MxBool p_updateCamera +) { Mx3DPointFloat direction; Mx3DPointFloat up; @@ -185,14 +190,14 @@ void LegoEntity::SetLocation(const Vector3& p_location, const Vector3& p_directi m_roi->UpdateTransformationRelativeToParent(mat); VideoManager()->Get3DManager()->Moved(*m_roi); - if (p_und) { - FUN_10010c30(); + if (p_updateCamera) { + TransformPointOfView(); } } } // FUNCTION: LEGO1 0x10010c30 -void LegoEntity::FUN_10010c30() +void LegoEntity::TransformPointOfView() { LegoWorld* world = CurrentWorld(); diff --git a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp index 8ad9b428..4591fc88 100644 --- a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp @@ -870,7 +870,7 @@ MxLong LegoNavController::Notify(MxParam& p_param) } GameState()->SetCurrentAct(LegoGameState::e_act3); - act3State->m_unk0x08 = 2; + act3State->m_state = Act3State::e_goodEnding; GameState()->m_currentArea = LegoGameState::e_act3script; GameState()->SwitchArea(LegoGameState::e_infomain); break; @@ -884,7 +884,7 @@ MxLong LegoNavController::Notify(MxParam& p_param) } GameState()->SetCurrentAct(LegoGameState::e_act3); - act3State->m_unk0x08 = 3; + act3State->m_state = Act3State::e_badEnding; GameState()->m_currentArea = LegoGameState::e_act3script; GameState()->SwitchArea(LegoGameState::e_infomain); break; diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index 1ba828cb..fe4bbc63 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -1,6 +1,7 @@ #include "legoworld.h" #include "anim/legoanim.h" +#include "extensions/siloader.h" #include "legoanimationmanager.h" #include "legoanimpresenter.h" #include "legobuildingmanager.h" @@ -32,6 +33,8 @@ DECOMP_SIZE_ASSERT(LegoEntityListCursor, 0x10) DECOMP_SIZE_ASSERT(LegoCacheSoundList, 0x18) DECOMP_SIZE_ASSERT(LegoCacheSoundListCursor, 0x10) +using namespace Extensions; + // FUNCTION: LEGO1 0x1001ca40 LegoWorld::LegoWorld() : m_pathControllerList(TRUE) { @@ -636,6 +639,12 @@ MxCore* LegoWorld::Find(const char* p_class, const char* p_name) // FUNCTION: BETA10 0x100db3de MxCore* LegoWorld::Find(const MxAtomId& p_atom, MxS32 p_entityId) { + auto result = + Extension::Call(HandleFind, SiLoader::StreamObject{p_atom, p_entityId}, this).value_or(std::nullopt); + if (result) { + return result.value(); + } + LegoEntityListCursor entityCursor(m_entityList); LegoEntity* entity; diff --git a/LEGO1/lego/legoomni/src/main/legomain.cpp b/LEGO1/lego/legoomni/src/main/legomain.cpp index 4a2da536..666cd35d 100644 --- a/LEGO1/lego/legoomni/src/main/legomain.cpp +++ b/LEGO1/lego/legoomni/src/main/legomain.cpp @@ -1,6 +1,7 @@ #include "legomain.h" #include "3dmanager/lego3dmanager.h" +#include "extensions/siloader.h" #include "islepathactor.h" #include "legoanimationmanager.h" #include "legobuildingmanager.h" @@ -40,6 +41,8 @@ DECOMP_SIZE_ASSERT(LegoOmni::WorldContainer, 0x1c) DECOMP_SIZE_ASSERT(LegoWorldList, 0x18) DECOMP_SIZE_ASSERT(LegoWorldListCursor, 0x10) +using namespace Extensions; + // GLOBAL: LEGO1 0x100f6718 // GLOBAL: BETA10 0x101ee748 // STRING: LEGO1 0x100f6710 @@ -404,6 +407,8 @@ LegoOmni* LegoOmni::GetInstance() void LegoOmni::AddWorld(LegoWorld* p_world) { m_worldList->Append(p_world); + + Extension::Call(HandleWorld, p_world); } // FUNCTION: LEGO1 0x1005adb0 @@ -471,6 +476,11 @@ LegoWorld* LegoOmni::FindWorld(const MxAtomId& p_atom, MxS32 p_entityid) // STUB: BETA10 0x1008e93e void LegoOmni::DeleteObject(MxDSAction& p_dsAction) { + auto result = Extension::Call(HandleDelete, p_dsAction).value_or(std::nullopt); + if (result && result.value()) { + return; + } + if (p_dsAction.GetAtomId().GetInternal() != NULL) { LegoWorld* world = FindWorld(p_dsAction.GetAtomId(), p_dsAction.GetObjectId()); if (world) { @@ -660,6 +670,13 @@ void LegoOmni::CreateBackgroundAudio() // FUNCTION: BETA10 0x1008f7e0 MxResult LegoOmni::Start(MxDSAction* p_dsAction) { + { + auto result = Extension::Call(HandleStart, *p_dsAction).value_or(std::nullopt); + if (result) { + return result.value(); + } + } + MxResult result = MxOmni::Start(p_dsAction); #ifdef BETA10 this->m_action = *p_dsAction; @@ -714,3 +731,8 @@ void LegoOmni::Resume() MxOmni::Resume(); SetAppCursor(e_cursorArrow); } + +void LegoOmni::LoadSiLoader() +{ + Extension::Call(Load); +} diff --git a/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp b/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp index 0a1e6d98..4fdaebc6 100644 --- a/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp @@ -13,9 +13,14 @@ DECOMP_SIZE_ASSERT(LegoAnimActorStruct, 0x20) // FUNCTION: LEGO1 0x1001bf80 // FUNCTION: BETA10 0x1003dc10 -LegoAnimActorStruct::LegoAnimActorStruct(float p_unk0x00, LegoAnim* p_AnimTreePtr, LegoROI** p_roiMap, MxU32 p_numROIs) +LegoAnimActorStruct::LegoAnimActorStruct( + float p_worldSpeed, + LegoAnim* p_AnimTreePtr, + LegoROI** p_roiMap, + MxU32 p_numROIs +) { - m_unk0x00 = p_unk0x00; + m_worldSpeed = p_worldSpeed; m_AnimTreePtr = p_AnimTreePtr; m_roiMap = p_roiMap; m_numROIs = p_numROIs; @@ -50,22 +55,22 @@ LegoAnimActor::~LegoAnimActor() // FUNCTION: LEGO1 0x1001c1f0 // FUNCTION: BETA10 0x1003f240 -MxResult LegoAnimActor::FUN_1001c1f0(float& p_und) +MxResult LegoAnimActor::GetTimeInCycle(float& p_timeInCycle) { float duration = (float) m_animMaps[m_curAnim]->m_AnimTreePtr->GetDuration(); - p_und = m_actorTime - duration * ((MxS32) (m_actorTime / duration)); + p_timeInCycle = m_actorTime - duration * ((MxS32) (m_actorTime / duration)); return SUCCESS; } // FUNCTION: LEGO1 0x1001c240 void LegoAnimActor::VTable0x74(Matrix4& p_transform) { - float und; + float timeInCycle; LegoPathActor::VTable0x74(p_transform); if (m_curAnim >= 0) { - FUN_1001c1f0(und); - FUN_1001c360(und, p_transform); + GetTimeInCycle(timeInCycle); + AnimateWithTransform(timeInCycle, p_transform); } } @@ -81,10 +86,10 @@ void LegoAnimActor::Animate(float p_time) if (m_actorState == c_initial && !m_userNavFlag && m_worldSpeed <= 0) { if (m_curAnim >= 0) { - MxMatrix matrix(m_unk0xec); - float f; - FUN_1001c1f0(f); - FUN_1001c360(f, matrix); + MxMatrix transform(m_unk0xec); + float timeInCycle; + GetTimeInCycle(timeInCycle); + AnimateWithTransform(timeInCycle, transform); } m_lastTime = m_actorTime = p_time; @@ -96,9 +101,9 @@ void LegoAnimActor::Animate(float p_time) // FUNCTION: LEGO1 0x1001c360 // FUNCTION: BETA10 0x1003e2d3 -MxResult LegoAnimActor::FUN_1001c360(float p_und, Matrix4& p_transform) +MxResult LegoAnimActor::AnimateWithTransform(float p_time, Matrix4& p_transform) { - if (p_und >= 0) { + if (p_time >= 0) { assert((m_curAnim >= 0) && (m_curAnim < m_animMaps.size())); LegoROI** roiMap = m_animMaps[m_curAnim]->m_roiMap; @@ -133,11 +138,11 @@ MxResult LegoAnimActor::FUN_1001c360(float p_und, Matrix4& p_transform) } for (MxS32 j = 0; j < n->GetNumChildren(); j++) { - LegoROI::ApplyAnimationTransformation(n->GetChild(j), p_transform, p_und, roiMap); + LegoROI::ApplyAnimationTransformation(n->GetChild(j), p_transform, p_time, roiMap); } if (m_cameraFlag) { - FUN_10010c30(); + TransformPointOfView(); } } @@ -150,15 +155,20 @@ MxResult LegoAnimActor::FUN_1001c360(float p_und, Matrix4& p_transform) // FUNCTION: LEGO1 0x1001c450 // FUNCTION: BETA10 0x1003e590 -MxResult LegoAnimActor::FUN_1001c450(LegoAnim* p_AnimTreePtr, float p_unk0x00, LegoROI** p_roiMap, MxU32 p_numROIs) +MxResult LegoAnimActor::CreateAnimActorStruct( + LegoAnim* p_AnimTreePtr, + float p_worldSpeed, + LegoROI** p_roiMap, + MxU32 p_numROIs +) { // the capitalization of `p_AnimTreePtr` was taken from BETA10 assert(p_AnimTreePtr && p_roiMap); - LegoAnimActorStruct* laas = new LegoAnimActorStruct(p_unk0x00, p_AnimTreePtr, p_roiMap, p_numROIs); + LegoAnimActorStruct* laas = new LegoAnimActorStruct(p_worldSpeed, p_AnimTreePtr, p_roiMap, p_numROIs); for (vector::iterator it = m_animMaps.begin(); it != m_animMaps.end(); it++) { - if (p_unk0x00 < (*it)->m_unk0x00) { + if (p_worldSpeed < (*it)->m_worldSpeed) { m_animMaps.insert(it, laas); SetWorldSpeed(m_worldSpeed); return SUCCESS; @@ -196,12 +206,12 @@ void LegoAnimActor::SetWorldSpeed(MxFloat p_worldSpeed) if (m_animMaps.size() > 0) { m_curAnim = 0; - if (m_worldSpeed >= m_animMaps[m_animMaps.size() - 1]->m_unk0x00) { + if (m_worldSpeed >= m_animMaps[m_animMaps.size() - 1]->m_worldSpeed) { m_curAnim = m_animMaps.size() - 1; } else { for (MxU32 i = 0; i < m_animMaps.size(); i++) { - if (m_worldSpeed <= m_animMaps[i]->m_unk0x00) { + if (m_worldSpeed <= m_animMaps[i]->m_worldSpeed) { m_curAnim = i; break; } diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index 04c551d0..9bd32f6d 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -169,7 +169,7 @@ inline void LegoExtraActor::FUN_1002ad8a() m_assAnimP->FUN_1006d680(this, -20.0f); for (MxS32 i = 0; i < m_animMaps.size(); i++) { - if (m_animMaps[i]->GetUnknown0x00() == -20.0f) { + if (m_animMaps[i]->GetWorldSpeed() == -20.0f) { m_assAnim = new LegoAnimActorStruct(*m_animMaps[i]); break; } @@ -181,7 +181,7 @@ inline void LegoExtraActor::FUN_1002ad8a() m_disAnimP->FUN_1006d680(this, -21.0f); for (MxS32 i = 0; i < m_animMaps.size(); i++) { - if (m_animMaps[i]->GetUnknown0x00() == -21.0f) { + if (m_animMaps[i]->GetWorldSpeed() == -21.0f) { m_disAnim = new LegoAnimActorStruct(*m_animMaps[i]); break; } @@ -416,7 +416,7 @@ void LegoExtraActor::VTable0xc4() if (b) { float duration = m_animMaps[m_curAnim]->GetDuration(); MxMatrix matrix(m_unk0xec); - LegoAnimActor::FUN_1001c360(duration, matrix); + LegoAnimActor::AnimateWithTransform(duration, matrix); } } } diff --git a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp index 2e9ae767..c8e4338a 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathactor.cpp @@ -163,7 +163,7 @@ MxResult LegoPathActor::VTable0x88( } else { m_boundary->AddActor(this); - FUN_10010c30(); + TransformPointOfView(); } m_unk0xec = m_roi->GetLocal2World(); @@ -223,7 +223,7 @@ MxResult LegoPathActor::VTable0x84( if (m_cameraFlag && m_userNavFlag) { m_boundary->AddActor(this); - FUN_10010c30(); + TransformPointOfView(); } else { p5.EqualsCross(*p_boundary->GetUp(), p3); @@ -393,14 +393,14 @@ void LegoPathActor::VTable0x74(Matrix4& p_transform) { if (m_userNavFlag) { m_roi->WrappedSetLocal2WorldWithWorldDataUpdate(p_transform); - FUN_10010c30(); + TransformPointOfView(); } else { m_roi->WrappedSetLocal2WorldWithWorldDataUpdate(p_transform); m_roi->WrappedUpdateWorldData(); if (m_cameraFlag) { - FUN_10010c30(); + TransformPointOfView(); } } } @@ -746,7 +746,7 @@ void LegoPathActor::VTable0xa8() if (m_userNavFlag) { m_roi->WrappedSetLocal2WorldWithWorldDataUpdate(m_unk0xec); - FUN_10010c30(); + TransformPointOfView(); } } diff --git a/LEGO1/lego/legoomni/src/race/legoracers.cpp b/LEGO1/lego/legoomni/src/race/legoracers.cpp index d6b25088..8f2fdade 100644 --- a/LEGO1/lego/legoomni/src/race/legoracers.cpp +++ b/LEGO1/lego/legoomni/src/race/legoracers.cpp @@ -271,10 +271,10 @@ void LegoRaceCar::ParseAction(char* p_extra) MxS32 i; for (i = 0; i < m_animMaps.size(); i++) { - if (m_animMaps[i]->GetUnknown0x00() == -1.0f) { + if (m_animMaps[i]->GetWorldSpeed() == -1.0f) { m_skelKick1Anim = m_animMaps[i]; } - else if (m_animMaps[i]->GetUnknown0x00() == -2.0f) { + else if (m_animMaps[i]->GetWorldSpeed() == -2.0f) { m_skelKick2Anim = m_animMaps[i]; } } diff --git a/LEGO1/lego/legoomni/src/race/raceskel.cpp b/LEGO1/lego/legoomni/src/race/raceskel.cpp index 899efe28..2a4f937e 100644 --- a/LEGO1/lego/legoomni/src/race/raceskel.cpp +++ b/LEGO1/lego/legoomni/src/race/raceskel.cpp @@ -22,15 +22,15 @@ RaceSkel::~RaceSkel() // FUNCTION: LEGO1 0x10071b50 // FUNCTION: BETA10 0x100f13cf -MxResult RaceSkel::FUN_1001c360(float p_und, Matrix4& p_transform) +MxResult RaceSkel::AnimateWithTransform(float p_time, Matrix4& p_transform) { p_transform[3][0] = -630.0f; p_transform[3][1] = -4.688f; p_transform[3][2] = 323.0f; - m_animPosition = p_und; + m_animPosition = p_time; - return LegoAnimActor::FUN_1001c360(p_und, p_transform); + return LegoAnimActor::AnimateWithTransform(p_time, p_transform); } // FUNCTION: LEGO1 0x10071b90 diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 9e31e18b..42e7bb02 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -1438,7 +1438,7 @@ void LegoLocomotionAnimPresenter::FUN_1006d680(LegoAnimActor* p_actor, MxFloat p if (m_roiMap != NULL) { m_roiMapList->Append(m_roiMap); - p_actor->FUN_1001c450(m_anim, p_value, m_roiMap, m_roiMapSize); + p_actor->CreateAnimActorStruct(m_anim, p_value, m_roiMap, m_roiMapSize); m_roiMap = NULL; } diff --git a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp index fe56a353..b0e2a7bf 100644 --- a/LEGO1/lego/legoomni/src/video/legovideomanager.cpp +++ b/LEGO1/lego/legoomni/src/video/legovideomanager.cpp @@ -852,7 +852,7 @@ void LegoVideoManager::SetCursorBitmap(const CursorBitmap* p_cursorBitmap) m_cursorRect.bottom = p_cursorBitmap->height; m_cursorRect.right = p_cursorBitmap->width; - m_cursorSurface = MxDisplaySurface::CreateCursorSurface(p_cursorBitmap); + m_cursorSurface = MxDisplaySurface::CreateCursorSurface(p_cursorBitmap, m_videoParam.GetPalette()); if (m_cursorSurface == NULL) { m_drawCursor = FALSE; diff --git a/LEGO1/lego/legoomni/src/worlds/act3.cpp b/LEGO1/lego/legoomni/src/worlds/act3.cpp index 4a473611..bad2762e 100644 --- a/LEGO1/lego/legoomni/src/worlds/act3.cpp +++ b/LEGO1/lego/legoomni/src/worlds/act3.cpp @@ -110,14 +110,14 @@ Act3Script::Script g_unk0x100d95e8[] = {Act3Script::c_tlp053in_RunAnim, Act3Script::c_tlp064la_RunAnim, Act3Script::c_tlp068in_RunAnim}; // FUNCTION: LEGO1 0x10071d40 -void Act3List::Insert(MxS32 p_objectId, InsertMode p_option) +void Act3List::Insert(MxS32 p_objectId, Act3ListElement::InsertMode p_option) { - if (m_unk0x0c) { + if (m_cleared) { return; } switch (p_option) { - case InsertMode::e_replaceAction: + case Act3ListElement::InsertMode::e_replaceAction: if (!empty()) { DeleteActionWrapper(); push_back(Act3ListElement(p_objectId, p_option, FALSE)); @@ -127,7 +127,7 @@ void Act3List::Insert(MxS32 p_objectId, InsertMode p_option) push_back(Act3ListElement(p_objectId, p_option, TRUE)); } break; - case InsertMode::e_queueAction: + case Act3ListElement::InsertMode::e_queueAction: if (empty()) { push_back(Act3ListElement(p_objectId, p_option, TRUE)); InvokeAction(Extra::e_start, *g_act3Script, p_objectId, NULL); @@ -136,7 +136,7 @@ void Act3List::Insert(MxS32 p_objectId, InsertMode p_option) push_back(Act3ListElement(p_objectId, p_option, FALSE)); } break; - case InsertMode::e_onlyIfEmpty: + case Act3ListElement::InsertMode::e_onlyIfEmpty: if (empty()) { push_back(Act3ListElement(p_objectId, p_option, TRUE)); InvokeAction(Extra::e_start, *g_act3Script, p_objectId, NULL); @@ -154,7 +154,7 @@ void Act3List::DeleteActionWrapper() // FUNCTION: LEGO1 0x10071fb0 void Act3List::Clear() { - m_unk0x0c = 1; + m_cleared = TRUE; BackgroundAudioManager()->Stop(); if (empty()) { @@ -177,7 +177,7 @@ void Act3List::Clear() // FUNCTION: LEGO1 0x100720d0 void Act3List::RemoveByObjectIdOrFirst(MxU32 p_objectId) { - if (m_unk0x0c) { + if (m_cleared) { return; } @@ -215,7 +215,7 @@ void Act3List::RemoveByObjectIdOrFirst(MxU32 p_objectId) it++; while (it != end()) { - if ((*it).m_unk0x04 == 1) { + if ((*it).m_insertMode == Act3ListElement::e_replaceAction) { for (Act3List::iterator it2 = begin(); it2 != it; erase(it2++)) { if ((*it2).m_hasStarted) { DeleteActionWrapper(); @@ -469,14 +469,14 @@ void Act3::TriggerHitSound(undefined4 p_param1) m_bricksterDonutSound = 0; } - m_unk0x4220.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], Act3List::e_replaceAction); + m_unk0x4220.Insert(g_bricksterDonutSounds[m_bricksterDonutSound++], Act3ListElement::e_replaceAction); return; } default: return; } - m_unk0x4220.Insert(objectId, Act3List::e_onlyIfEmpty); + m_unk0x4220.Insert(objectId, Act3ListElement::e_onlyIfEmpty); } // FUNCTION: LEGO1 0x10072c30 @@ -633,22 +633,22 @@ MxLong Act3::Notify(MxParam& p_param) break; } case c_notificationKeyPress: - if (m_state->m_unk0x08 == 1 && ((LegoEventNotificationParam&) p_param).GetKey() == ' ') { + if (m_state->m_state == Act3State::e_ready && ((LegoEventNotificationParam&) p_param).GetKey() == ' ') { AnimationManager()->FUN_10061010(FALSE); return 1; } break; case c_notificationButtonUp: case c_notificationButtonDown: - if (m_state->m_unk0x08 == 1) { + if (m_state->m_state == Act3State::e_ready) { return 1; } break; case c_notificationEndAnim: - if (m_state->m_unk0x08 == 1) { + if (m_state->m_state == Act3State::e_ready) { assert(m_copter && m_brickster && m_cop1 && m_cop2); m_unk0x4220.RemoveByObjectIdOrFirst(0); - m_state->m_unk0x08 = 0; + m_state->m_state = Act3State::e_initial; Disable(TRUE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); m_copter->HandleClick(); m_copter->m_state->m_unk0x08 = 1; @@ -687,7 +687,7 @@ void Act3::ReadyWorld() AnimationManager() ->FUN_10060dc0(m_unk0x426c, NULL, TRUE, LegoAnimationManager::e_unk0, NULL, TRUE, FALSE, FALSE, FALSE); - m_state->m_unk0x08 = 1; + m_state->m_state = Act3State::e_ready; } // FUNCTION: LEGO1 0x10073300 @@ -759,7 +759,7 @@ void Act3::SetBrickster(Act3Brickster* p_brickster) // FUNCTION: LEGO1 0x10073400 void Act3::FUN_10073400() { - m_state->m_unk0x08 = 2; + m_state->m_state = Act3State::e_goodEnding; m_destLocation = LegoGameState::e_infomain; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); } @@ -767,7 +767,7 @@ void Act3::FUN_10073400() // FUNCTION: LEGO1 0x10073430 void Act3::FUN_10073430() { - m_state->m_unk0x08 = 3; + m_state->m_state = Act3State::e_badEnding; m_destLocation = LegoGameState::e_infomain; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); } @@ -797,7 +797,7 @@ void Act3::GoodEnding(const Matrix4& p_destination) EmitGameEvent(e_goodEnding); #else - m_state->m_unk0x08 = 2; + m_state->m_state = Act3State::e_goodEnding; GameState()->SwitchArea(LegoGameState::Area::e_infomain); #endif } diff --git a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp index 3e4eb728..45885c31 100644 --- a/LEGO1/lego/legoomni/src/worlds/infocenter.cpp +++ b/LEGO1/lego/legoomni/src/worlds/infocenter.cpp @@ -2,8 +2,10 @@ #include "act3.h" #include "credits_actions.h" +#include "extensions/siloader.h" #include "helicopter.h" #include "infomain_actions.h" +#include "intro_actions.h" #include "jukebox.h" #include "jukebox_actions.h" #include "legoact2.h" @@ -35,6 +37,8 @@ DECOMP_SIZE_ASSERT(Infocenter, 0x1d8) DECOMP_SIZE_ASSERT(InfocenterMapEntry, 0x18) DECOMP_SIZE_ASSERT(InfocenterState, 0x94) +using namespace Extensions; + // GLOBAL: LEGO1 0x100f76a0 const char* g_object2x4red = "2x4red"; @@ -128,13 +132,13 @@ InfomainScript::Script g_bricksterDialogue[2] = { // FUNCTION: LEGO1 0x1006ea20 Infocenter::Infocenter() { - m_selectedCharacter = e_noCharacter; + m_selectedCharacter = LegoActor::c_none; m_dragPresenter = NULL; m_infocenterState = NULL; m_frame = NULL; m_destLocation = LegoGameState::e_undefined; m_currentInfomainScript = InfomainScript::c_noneInfomain; - m_currentCutscene = e_noIntro; + m_currentCutscene = IntroScript::c_noneIntro; memset(&m_glowInfo, 0, sizeof(m_glowInfo)); @@ -220,7 +224,7 @@ MxResult Infocenter::Create(MxDSAction& p_dsAction) if (m_infocenterState->m_state == InfocenterState::e_selectedSave) { LegoGameState* state = GameState(); - state->m_previousArea = GameState()->m_unk0x42c; + state->m_previousArea = GameState()->m_savedPreviousArea; } InputManager()->Register(this); @@ -312,19 +316,19 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) GameState()->SetActor(m_selectedCharacter); switch (m_selectedCharacter) { - case e_pepper: + case LegoActor::c_pepper: PlayAction(InfomainScript::c_avo901in_RunAnim); break; - case e_mama: + case LegoActor::c_mama: PlayAction(InfomainScript::c_avo902in_RunAnim); break; - case e_papa: + case LegoActor::c_papa: PlayAction(InfomainScript::c_avo903in_RunAnim); break; - case e_nick: + case LegoActor::c_nick: PlayAction(InfomainScript::c_avo904in_RunAnim); break; - case e_laura: + case LegoActor::c_laura: PlayAction(InfomainScript::c_avo905in_RunAnim); break; default: @@ -338,7 +342,8 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) MxLong result = m_radio.Notify(p_param); - if (result || (action->GetAtomId() != m_atomId && action->GetAtomId() != *g_introScript)) { + if (result || (action->GetAtomId() != m_atomId && action->GetAtomId() != *g_introScript && + !Extension::Call(ReplacedIn, *action, m_atomId, *g_introScript).value_or(std::nullopt))) { return result; } @@ -350,23 +355,23 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) switch (m_infocenterState->m_state) { case InfocenterState::e_playCutscene: switch (m_currentCutscene) { - case e_legoMovie: - PlayCutscene(e_mindscapeMovie, FALSE); + case IntroScript::c_Lego_Movie: + PlayCutscene(IntroScript::c_Mindscape_Movie, FALSE); return 1; - case e_mindscapeMovie: - PlayCutscene(e_introMovie, TRUE); + case IntroScript::c_Mindscape_Movie: + PlayCutscene(IntroScript::c_Intro_Movie, TRUE); return 1; - case e_badEndMovie: + case IntroScript::c_BadEnd_Movie: StopCutscene(); m_infocenterState->m_state = InfocenterState::e_welcomeAnimation; PlayAction(InfomainScript::c_tic092in_RunAnim); - m_currentCutscene = e_noIntro; + m_currentCutscene = IntroScript::c_noneIntro; return 1; - case e_goodEndMovie: + case IntroScript::c_GoodEnd_Movie: StopCutscene(); m_infocenterState->m_state = InfocenterState::e_welcomeAnimation; PlayAction(InfomainScript::c_tic089in_RunAnim); - m_currentCutscene = e_noIntro; + m_currentCutscene = IntroScript::c_noneIntro; return 1; } @@ -374,7 +379,7 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) StopCutscene(); m_infocenterState->m_state = InfocenterState::e_welcomeAnimation; PlayAction(InfomainScript::c_iic001in_RunAnim); - m_currentCutscene = e_noIntro; + m_currentCutscene = IntroScript::c_noneIntro; if (!m_infocenterState->HasRegistered()) { m_bookAnimationTimer = 1; @@ -385,17 +390,17 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) m_infocenterState->m_state = InfocenterState::e_welcomeAnimation; switch (m_currentCutscene) { - case e_badEndMovie: + case IntroScript::c_BadEnd_Movie: PlayAction(InfomainScript::c_tic092in_RunAnim); break; - case e_goodEndMovie: + case IntroScript::c_GoodEnd_Movie: PlayAction(InfomainScript::c_tic089in_RunAnim); break; default: PlayAction(InfomainScript::c_iic001in_RunAnim); } - m_currentCutscene = e_noIntro; + m_currentCutscene = IntroScript::c_noneIntro; return 1; case InfocenterState::e_notRegistered: SetROIVisible(g_object2x4red, FALSE); @@ -412,7 +417,7 @@ MxLong Infocenter::HandleEndAction(MxEndActionNotificationParam& p_param) break; case InfocenterState::e_selectedCharacterAndDestination: if (action->GetObjectId() == m_currentInfomainScript) { - if (GameState()->GetCurrentAct() != LegoGameState::e_act3 && m_selectedCharacter != e_noCharacter) { + if (GameState()->GetCurrentAct() != LegoGameState::e_act3 && m_selectedCharacter != LegoActor::c_none) { GameState()->SetActor(m_selectedCharacter); } TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); @@ -462,7 +467,7 @@ void Infocenter::ReadyWorld() switch (m_infocenterState->m_state) { case InfocenterState::e_newState: - PlayCutscene(e_legoMovie, TRUE); + PlayCutscene(IntroScript::c_Lego_Movie, TRUE); m_infocenterState->m_state = InfocenterState::e_playCutscene; return; case InfocenterState::e_selectedSave: @@ -523,9 +528,9 @@ void Infocenter::ReadyWorld() LegoAct2State* state = (LegoAct2State*) GameState()->GetState("LegoAct2State"); GameState()->FindLoadedAct(); - if (state && state->GetUnknown0x08() == 0x68) { + if (state && state->GetState() == LegoAct2State::c_badEnding) { bg->Enable(TRUE); - PlayCutscene(e_badEndMovie, TRUE); + PlayCutscene(IntroScript::c_BadEnd_Movie, TRUE); m_infocenterState->m_state = InfocenterState::e_playCutscene; return; } @@ -568,16 +573,16 @@ void Infocenter::ReadyWorld() Act3State* state = (Act3State*) GameState()->GetState("Act3State"); GameState()->FindLoadedAct(); - if (state && state->GetUnknown0x08() == 3) { + if (state && state->GetState() == Act3State::e_badEnding) { bg->Enable(TRUE); - PlayCutscene(e_badEndMovie, TRUE); + PlayCutscene(IntroScript::c_BadEnd_Movie, TRUE); m_infocenterState->m_state = InfocenterState::e_playCutscene; return; } - if (state && state->GetUnknown0x08() == 2) { + if (state && state->GetState() == Act3State::e_goodEnding) { bg->Enable(TRUE); - PlayCutscene(e_goodEndMovie, TRUE); + PlayCutscene(IntroScript::c_GoodEnd_Movie, TRUE); m_infocenterState->m_state = InfocenterState::e_playCutscene; return; } @@ -759,19 +764,19 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) switch (m_dragPresenter->GetAction()->GetObjectId()) { case InfomainScript::c_PepperHot_Bitmap: - m_selectedCharacter = e_pepper; + m_selectedCharacter = LegoActor::c_pepper; break; case InfomainScript::c_MamaHot_Bitmap: - m_selectedCharacter = e_mama; + m_selectedCharacter = LegoActor::c_mama; break; case InfomainScript::c_PapaHot_Bitmap: - m_selectedCharacter = e_papa; + m_selectedCharacter = LegoActor::c_papa; break; case InfomainScript::c_NickHot_Bitmap: - m_selectedCharacter = e_nick; + m_selectedCharacter = LegoActor::c_nick; break; case InfomainScript::c_LauraHot_Bitmap: - m_selectedCharacter = e_laura; + m_selectedCharacter = LegoActor::c_laura; break; } @@ -780,7 +785,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) switch (control->GetAction()->GetObjectId()) { case InfomainScript::c_Pepper_Ctl: - if (m_selectedCharacter == e_pepper) { + if (m_selectedCharacter == LegoActor::c_pepper) { m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Pepper_All_Movie); @@ -788,7 +793,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) } break; case InfomainScript::c_Mama_Ctl: - if (m_selectedCharacter == e_mama) { + if (m_selectedCharacter == LegoActor::c_mama) { m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Mama_All_Movie); @@ -796,7 +801,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) } break; case InfomainScript::c_Papa_Ctl: - if (m_selectedCharacter == e_papa) { + if (m_selectedCharacter == LegoActor::c_papa) { m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Papa_All_Movie); @@ -804,7 +809,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) } break; case InfomainScript::c_Nick_Ctl: - if (m_selectedCharacter == e_nick) { + if (m_selectedCharacter == LegoActor::c_nick) { m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Nick_All_Movie); @@ -812,7 +817,7 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) } break; case InfomainScript::c_Laura_Ctl: - if (m_selectedCharacter == e_laura) { + if (m_selectedCharacter == LegoActor::c_laura) { m_radio.Stop(); BackgroundAudioManager()->Stop(); PlayAction(InfomainScript::c_Laura_All_Movie); @@ -830,19 +835,19 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) GameState()->SetActor(m_selectedCharacter); switch (m_selectedCharacter) { - case e_pepper: + case LegoActor::c_pepper: PlayAction(InfomainScript::c_avo901in_RunAnim); break; - case e_mama: + case LegoActor::c_mama: PlayAction(InfomainScript::c_avo902in_RunAnim); break; - case e_papa: + case LegoActor::c_papa: PlayAction(InfomainScript::c_avo903in_RunAnim); break; - case e_nick: + case LegoActor::c_nick: PlayAction(InfomainScript::c_avo904in_RunAnim); break; - case e_laura: + case LegoActor::c_laura: PlayAction(InfomainScript::c_avo905in_RunAnim); break; } @@ -901,23 +906,23 @@ MxU8 Infocenter::HandleButtonUp(MxS32 p_x, MxS32 p_y) } else { switch (m_selectedCharacter) { - case e_pepper: + case LegoActor::c_pepper: dialogueToPlay = InfomainScript::c_avo901in_RunAnim; GameState()->SetActorId(m_selectedCharacter); break; - case e_mama: + case LegoActor::c_mama: dialogueToPlay = InfomainScript::c_avo902in_RunAnim; GameState()->SetActorId(m_selectedCharacter); break; - case e_papa: + case LegoActor::c_papa: dialogueToPlay = InfomainScript::c_avo903in_RunAnim; GameState()->SetActorId(m_selectedCharacter); break; - case e_nick: + case LegoActor::c_nick: dialogueToPlay = InfomainScript::c_avo904in_RunAnim; GameState()->SetActorId(m_selectedCharacter); break; - case e_laura: + case LegoActor::c_laura: dialogueToPlay = InfomainScript::c_avo905in_RunAnim; GameState()->SetActorId(m_selectedCharacter); break; @@ -1089,7 +1094,7 @@ MxU8 Infocenter::HandleControl(LegoControlManagerNotificationParam& p_param) actionToPlay = GameState()->GetCurrentAct() != LegoGameState::e_act1 ? InfomainScript::c_GoTo_RegBook_Red : InfomainScript::c_GoTo_RegBook; m_radio.Stop(); - GameState()->m_unk0x42c = GameState()->m_previousArea; + GameState()->m_savedPreviousArea = GameState()->m_previousArea; InputManager()->DisableInputProcessing(); break; case InfomainScript::c_Mama_Ctl: @@ -1262,7 +1267,7 @@ MxResult Infocenter::Tickle() } // FUNCTION: LEGO1 0x10070c20 -void Infocenter::PlayCutscene(Cutscene p_entityId, MxBool p_scale) +void Infocenter::PlayCutscene(IntroScript::Script p_entityId, MxBool p_scale) { m_currentCutscene = p_entityId; @@ -1272,9 +1277,9 @@ void Infocenter::PlayCutscene(Cutscene p_entityId, MxBool p_scale) SetAppCursor(e_cursorNone); VideoManager()->GetDisplaySurface()->ClearScreen(); - if (m_currentCutscene != e_noIntro) { + if (m_currentCutscene != IntroScript::c_noneIntro) { // check if the cutscene is an ending - if (m_currentCutscene >= e_badEndMovie && m_currentCutscene <= e_goodEndMovie) { + if (m_currentCutscene >= IntroScript::c_BadEnd_Movie && m_currentCutscene <= IntroScript::c_GoodEnd_Movie) { Reset(); } @@ -1285,7 +1290,7 @@ void Infocenter::PlayCutscene(Cutscene p_entityId, MxBool p_scale) // FUNCTION: LEGO1 0x10070cb0 void Infocenter::StopCutscene() { - if (m_currentCutscene != e_noIntro) { + if (m_currentCutscene != IntroScript::c_noneIntro) { InvokeAction(Extra::ActionType::e_close, *g_introScript, m_currentCutscene, NULL); } @@ -1399,12 +1404,12 @@ void Infocenter::Reset() CharacterManager()->ReleaseAllActors(); GameState()->SetCurrentAct(LegoGameState::e_act1); GameState()->m_previousArea = LegoGameState::e_undefined; - GameState()->m_unk0x42c = LegoGameState::e_undefined; + GameState()->m_savedPreviousArea = LegoGameState::e_undefined; InitializeBitmaps(); - m_selectedCharacter = e_pepper; + m_selectedCharacter = LegoActor::c_pepper; - GameState()->SetActor(e_pepper); + GameState()->SetActor(LegoActor::c_pepper); HelicopterState* state = (HelicopterState*) GameState()->GetState("HelicopterState"); diff --git a/LEGO1/lego/legoomni/src/worlds/isle.cpp b/LEGO1/lego/legoomni/src/worlds/isle.cpp index 322bb680..8e3c0535 100644 --- a/LEGO1/lego/legoomni/src/worlds/isle.cpp +++ b/LEGO1/lego/legoomni/src/worlds/isle.cpp @@ -5,6 +5,7 @@ #include "bike.h" #include "carrace.h" #include "dunebuggy.h" +#include "extensions/siloader.h" #include "helicopter.h" #include "isle_actions.h" #include "islepathactor.h" @@ -42,6 +43,8 @@ DECOMP_SIZE_ASSERT(Act1State, 0x26c) DECOMP_SIZE_ASSERT(LegoNamedPlane, 0x4c) DECOMP_SIZE_ASSERT(Isle, 0x140) +using namespace Extensions; + // GLOBAL: LEGO1 0x100f1198 MxU32 g_isleFlags = 0x7f; @@ -213,6 +216,14 @@ MxLong Isle::HandleEndAction(MxEndActionNotificationParam& p_param) result = 1; } } + else if (auto replacedObject = Extension::Call(ReplacedIn, *p_param.GetAction(), *g_jukeboxScript).value_or(std::nullopt)) { + MxS32 script = replacedObject->second; + + if (script >= JukeboxScript::c_JBMusic1 && script <= JukeboxScript::c_JBMusic6) { + m_jukebox->StopAction((JukeboxScript::Script) script); + result = 1; + } + } else if (m_act1state->m_planeActive) { MxS32 script = p_param.GetAction()->GetObjectId(); @@ -244,15 +255,15 @@ void Isle::HandleElevatorEndAction() m_act1state->m_state = Act1State::e_none; break; case Act1State::c_floor2: - if (m_act1state->m_unk0x01e) { - m_act1state->m_unk0x01e = FALSE; + if (m_act1state->m_playingFloor2Animation) { + m_act1state->m_playingFloor2Animation = FALSE; m_act1state->m_state = Act1State::e_none; InputManager()->EnableInputProcessing(); } else { InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_Floor2, NULL); InputManager()->EnableInputProcessing(); - m_act1state->m_unk0x01e = TRUE; + m_act1state->m_playingFloor2Animation = TRUE; } break; case Act1State::c_floor3: @@ -268,10 +279,10 @@ void Isle::ReadyWorld() { LegoWorld::ReadyWorld(); - if (m_act1state->GetUnknown21()) { + if (m_act1state->IsSpawnInInfocenter()) { GameState()->SwitchArea(LegoGameState::e_infomain); m_act1state->SetState(Act1State::e_none); - m_act1state->SetUnknown21(0); + m_act1state->SetSpawnInInfocenter(FALSE); } else if (GameState()->GetLoadedAct() != LegoGameState::e_act1) { EnableAnimations(TRUE); @@ -318,7 +329,7 @@ MxLong Isle::HandleControl(LegoControlManagerNotificationParam& p_param) break; case Act1State::c_floor2: InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_Floor2, NULL); - m_act1state->m_unk0x01e = TRUE; + m_act1state->m_playingFloor2Animation = TRUE; break; case Act1State::c_floor3: InvokeAction(Extra::e_start, *g_isleScript, IsleScript::c_Elev3_2_Ride, NULL); @@ -907,7 +918,7 @@ MxLong Isle::HandleTransitionEnd() m_destLocation = LegoGameState::e_undefined; break; case LegoGameState::e_elevride: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; VariableTable()->SetVariable("VISIBILITY", "Hide infocen"); TransitionToOverlay( IsleScript::c_ElevRide_Background_Bitmap, @@ -961,7 +972,7 @@ MxLong Isle::HandleTransitionEnd() ); break; case LegoGameState::e_garadoor: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; VariableTable()->SetVariable("VISIBILITY", "Hide Gas"); TransitionToOverlay(IsleScript::c_GaraDoor_Background_Bitmap, JukeboxScript::c_JBMusic2, "LCAMZG1,90", FALSE); break; @@ -986,7 +997,7 @@ MxLong Isle::HandleTransitionEnd() SetIsWorldActive(TRUE); break; case LegoGameState::e_polidoor: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; VariableTable()->SetVariable("VISIBILITY", "Hide Policsta"); TransitionToOverlay( IsleScript::c_PoliDoor_Background_Bitmap, @@ -996,61 +1007,61 @@ MxLong Isle::HandleTransitionEnd() ); break; case LegoGameState::e_bike: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; TransitionToOverlay(IsleScript::c_BikeDashboard_Bitmap, JukeboxScript::c_MusicTheme1, NULL, TRUE); - if (!m_act1state->m_unk0x01f) { + if (!m_act1state->m_switchedToArea) { m_bike->ActivateSceneActions(); } break; case LegoGameState::e_dunecar: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; TransitionToOverlay(IsleScript::c_DuneCarFuelMeter, JukeboxScript::c_MusicTheme1, NULL, TRUE); - if (!m_act1state->m_unk0x01f) { + if (!m_act1state->m_switchedToArea) { m_dunebuggy->ActivateSceneActions(); } break; case LegoGameState::e_motocycle: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; TransitionToOverlay(IsleScript::c_MotoBikeDashboard_Bitmap, JukeboxScript::c_MusicTheme1, NULL, TRUE); - if (!m_act1state->m_unk0x01f) { + if (!m_act1state->m_switchedToArea) { m_motocycle->ActivateSceneActions(); } break; case LegoGameState::e_copter: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; TransitionToOverlay(IsleScript::c_HelicopterDashboard_Bitmap, JukeboxScript::c_MusicTheme1, NULL, TRUE); break; case LegoGameState::e_skateboard: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; TransitionToOverlay(IsleScript::c_SkatePizza_Bitmap, JukeboxScript::c_MusicTheme1, NULL, TRUE); - if (!m_act1state->m_unk0x01f) { + if (!m_act1state->m_switchedToArea) { m_skateboard->ActivateSceneActions(); } break; case LegoGameState::e_ambulance: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; m_act1state->m_state = Act1State::e_ambulance; TransitionToOverlay(IsleScript::c_AmbulanceFuelMeter, JukeboxScript::c_MusicTheme1, NULL, TRUE); - if (!m_act1state->m_unk0x01f) { + if (!m_act1state->m_switchedToArea) { m_ambulance->ActivateSceneActions(); } break; case LegoGameState::e_towtrack: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; m_act1state->m_state = Act1State::e_towtrack; TransitionToOverlay(IsleScript::c_TowFuelMeter, JukeboxScript::c_MusicTheme1, NULL, TRUE); - if (!m_act1state->m_unk0x01f) { + if (!m_act1state->m_switchedToArea) { m_towtrack->ActivateSceneActions(); } break; case LegoGameState::e_jetski: - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; TransitionToOverlay( (IsleScript::Script) m_jetski->GetJetskiDashboardStreamId(), JukeboxScript::c_MusicTheme1, @@ -1058,7 +1069,7 @@ MxLong Isle::HandleTransitionEnd() TRUE ); - if (!m_act1state->m_unk0x01f) { + if (!m_act1state->m_switchedToArea) { m_jetski->ActivateSceneActions(); } break; @@ -1078,7 +1089,7 @@ void Isle::TransitionToOverlay( MxBool p_setCamera ) { - if (m_act1state->m_unk0x01f) { + if (m_act1state->m_switchedToArea) { MxPresenter* presenter = (MxPresenter*) Find(m_atomId, p_script); if (presenter != NULL && presenter->GetCurrentTickleState() == MxPresenter::e_repeating) { @@ -1100,7 +1111,7 @@ void Isle::TransitionToOverlay( Disable(FALSE, LegoOmni::c_disableInput | LegoOmni::c_disable3d | LegoOmni::c_clearScreen); SetAppCursor(e_cursorArrow); m_destLocation = LegoGameState::e_undefined; - m_act1state->m_unk0x01f = FALSE; + m_act1state->m_switchedToArea = FALSE; } else { NotificationManager()->Send(this, MxNotificationParam(c_notificationTransitioned, NULL)); @@ -1110,7 +1121,7 @@ void Isle::TransitionToOverlay( GameState()->SwitchArea(m_destLocation); GameState()->StopArea(LegoGameState::e_previousArea); NotificationManager()->Send(this, MxNotificationParam(c_notificationTransitioned, NULL)); - m_act1state->m_unk0x01f = TRUE; + m_act1state->m_switchedToArea = TRUE; } } @@ -1229,9 +1240,9 @@ MxBool Isle::Escape() break; } - if (m_act1state->m_unk0x01e == TRUE) { + if (m_act1state->m_playingFloor2Animation == TRUE) { InvokeAction(Extra::e_stop, *g_isleScript, IsleScript::c_Floor2, NULL); - m_act1state->m_unk0x01e = FALSE; + m_act1state->m_playingFloor2Animation = FALSE; } m_act1state->m_elevFloor = Act1State::c_floor1; @@ -1309,18 +1320,18 @@ Act1State::Act1State() { m_elevFloor = Act1State::c_floor1; m_state = Act1State::e_initial; - m_unk0x01e = FALSE; + m_playingFloor2Animation = FALSE; m_cptClickDialogue = Playlist((MxU32*) g_cptClickDialogue, sizeOfArray(g_cptClickDialogue), Playlist::e_loop); - m_unk0x01f = FALSE; + m_switchedToArea = FALSE; m_planeActive = FALSE; m_currentCptClickDialogue = IsleScript::c_noneIsle; - m_unk0x022 = FALSE; + m_playedExitExplanation = FALSE; m_helicopterWindshield = NULL; m_helicopterJetLeft = NULL; m_helicopterJetRight = NULL; m_helicopter = NULL; m_jetskiFront = NULL; - m_unk0x021 = 1; + m_spawnInInfocenter = 1; m_jetskiWindshield = NULL; m_jetski = NULL; m_dunebuggyFront = NULL; @@ -1419,7 +1430,7 @@ MxResult Act1State::Serialize(LegoStorage* p_storage) } p_storage->WriteS16(m_cptClickDialogue.m_nextIndex); - p_storage->WriteU8(m_unk0x022); + p_storage->WriteU8(m_playedExitExplanation); } else if (p_storage->IsReadMode()) { if (strcmp(m_helicopterPlane.m_name.GetData(), "")) { @@ -1467,7 +1478,7 @@ MxResult Act1State::Serialize(LegoStorage* p_storage) } p_storage->ReadS16(m_cptClickDialogue.m_nextIndex); - p_storage->ReadU8(m_unk0x022); + p_storage->ReadU8(m_playedExitExplanation); } return SUCCESS; @@ -1500,7 +1511,7 @@ MxBool Act1State::Reset() m_motocyclePlane.m_name = ""; m_bikePlane.m_name = ""; m_skateboardPlane.m_name = ""; - m_unk0x022 = FALSE; + m_playedExitExplanation = FALSE; m_helicopterPlane.m_name = ""; if (m_helicopterWindshield) { diff --git a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp index c04bbdbc..fea291ba 100644 --- a/LEGO1/lego/legoomni/src/worlds/legoact2.cpp +++ b/LEGO1/lego/legoomni/src/worlds/legoact2.cpp @@ -127,7 +127,7 @@ MxResult LegoAct2::Create(MxDSAction& p_dsAction) } m_gameState = state; - m_gameState->m_unk0x08 = 0; + m_gameState->m_state = 0; switch (GameState()->GetLoadedAct()) { case LegoGameState::e_act2: @@ -294,7 +294,7 @@ MxLong LegoAct2::Notify(MxParam& p_param) result = 1; break; } - case c_notificationType22: + case c_notificationAct2Brick: SoundManager()->GetCacheSoundManager()->Play("28bng", NULL, FALSE); m_unk0x10c1++; @@ -647,7 +647,7 @@ MxLong LegoAct2::HandlePathStruct(LegoPathStructNotificationParam& p_param) local2world[3][1] += 1.5; local2world2[3][1] -= 0.1; - m_bricks[m_nextBrick - 1].FUN_1007a670(local2world, local2world2, boundary); + m_bricks[m_nextBrick - 1].Place(local2world, local2world2, boundary); } return 0; @@ -671,7 +671,7 @@ MxResult LegoAct2::FUN_100516b0() local2world[3][1] += 1.3; local2world2[3][1] -= 0.1; - brick.FUN_1007a670(local2world, local2world2, boundary); + brick.Place(local2world, local2world2, boundary); m_nextBrick++; m_unk0x10c4 = 9; m_unk0x10d0 = 0; @@ -933,7 +933,7 @@ MxResult LegoAct2::BadEnding() LegoPathActor* actor = m_unk0x1138; actor->SetActorState(LegoPathActor::c_disabled); - m_gameState->SetUnknown0x08(104); + m_gameState->SetState(LegoAct2State::c_badEnding); m_destLocation = LegoGameState::e_infomain; TransitionManager()->StartTransition(MxTransitionManager::e_mosaic, 50, FALSE, FALSE); diff --git a/LEGO1/omni/include/mxdisplaysurface.h b/LEGO1/omni/include/mxdisplaysurface.h index aa30b543..e0d1a240 100644 --- a/LEGO1/omni/include/mxdisplaysurface.h +++ b/LEGO1/omni/include/mxdisplaysurface.h @@ -61,8 +61,7 @@ class MxDisplaySurface : public MxCore { MxS32 p_right, MxS32 p_bottom, MxS32 p_width, - MxS32 p_height, - MxBool p_RLE + MxS32 p_height ); // vtable+0x2c virtual void VTable0x30( MxBitmap* p_bitmap, @@ -71,8 +70,7 @@ class MxDisplaySurface : public MxCore { MxS32 p_right, MxS32 p_bottom, MxS32 p_width, - MxS32 p_height, - MxBool p_RLE + MxS32 p_height ); // vtable+0x30 virtual void Display( MxS32 p_left, @@ -92,23 +90,12 @@ class MxDisplaySurface : public MxCore { ); // vtable+0x44 void ClearScreen(); - static LPDIRECTDRAWSURFACE CreateCursorSurface(const CursorBitmap* p_cursorBitmap); + static LPDIRECTDRAWSURFACE CreateCursorSurface(const CursorBitmap* p_cursorBitmap, MxPalette* p_palette); static LPDIRECTDRAWSURFACE CopySurface(LPDIRECTDRAWSURFACE p_src); LPDIRECTDRAWSURFACE GetDirectDrawSurface1() { return m_ddSurface1; } LPDIRECTDRAWSURFACE GetDirectDrawSurface2() { return m_ddSurface2; } MxVideoParam& GetVideoParam() { return m_videoParam; } - - void DrawTransparentRLE( - MxU8*& p_bitmapData, - MxU8*& p_surfaceData, - MxU32 p_bitmapSize, - MxS32 p_width, - MxS32 p_height, - MxLong p_pitch, - MxU8 p_bpp - ); - LPDIRECTDRAWSURFACE FUN_100bc8b0(MxS32 p_width, MxS32 p_height); private: diff --git a/LEGO1/omni/include/mxdsbuffer.h b/LEGO1/omni/include/mxdsbuffer.h index a988ca57..09f6f826 100644 --- a/LEGO1/omni/include/mxdsbuffer.h +++ b/LEGO1/omni/include/mxdsbuffer.h @@ -91,6 +91,12 @@ class MxDSBuffer : public MxCore { void SetUnk30(MxDSStreamingAction* p_unk0x30) { m_unk0x30 = p_unk0x30; } + void SetSourceBuffer(MxDSBuffer* p_sourceBuffer) + { + m_sourceBuffer = p_sourceBuffer; + m_sourceBuffer->AddRef(NULL); + } + // SYNTHETIC: LEGO1 0x100c6510 // SYNTHETIC: BETA10 0x10158530 // MxDSBuffer::`scalar deleting destructor' @@ -107,6 +113,7 @@ class MxDSBuffer : public MxCore { MxU32 m_writeOffset; // 0x28 MxU32 m_bytesRemaining; // 0x2c MxDSStreamingAction* m_unk0x30; // 0x30 + MxDSBuffer* m_sourceBuffer; }; #endif // MXDSBUFFER_H diff --git a/LEGO1/omni/include/mxnotificationparam.h b/LEGO1/omni/include/mxnotificationparam.h index c2a95de8..a7c8fc19 100644 --- a/LEGO1/omni/include/mxnotificationparam.h +++ b/LEGO1/omni/include/mxnotificationparam.h @@ -29,7 +29,7 @@ enum NotificationId { c_notificationPathStruct = 19, // 100d6230 c_notificationType20 = 20, c_notificationNewPresenter = 21, - c_notificationType22 = 22, + c_notificationAct2Brick = 22, c_notificationType23 = 23, c_notificationTransitioned = 24 }; diff --git a/LEGO1/omni/include/mxstring.h b/LEGO1/omni/include/mxstring.h index b4b25262..fad2e4ad 100644 --- a/LEGO1/omni/include/mxstring.h +++ b/LEGO1/omni/include/mxstring.h @@ -9,7 +9,7 @@ // SIZE 0x10 class MxString : public MxCore { public: - MxString(); + LEGO1_EXPORT MxString(); MxString(const MxString& p_str); LEGO1_EXPORT MxString(const char* p_str); MxString(const char* p_str, MxU16 p_maxlen); diff --git a/LEGO1/omni/src/notify/mxnotificationmanager.cpp b/LEGO1/omni/src/notify/mxnotificationmanager.cpp index 064f38f3..373c8aef 100644 --- a/LEGO1/omni/src/notify/mxnotificationmanager.cpp +++ b/LEGO1/omni/src/notify/mxnotificationmanager.cpp @@ -2,6 +2,7 @@ #include "compat.h" #include "decomp.h" +#include "extensions/siloader.h" #include "mxautolock.h" #include "mxmisc.h" #include "mxnotificationparam.h" @@ -12,6 +13,8 @@ DECOMP_SIZE_ASSERT(MxNotification, 0x08); DECOMP_SIZE_ASSERT(MxNotificationManager, 0x40); +using namespace Extensions; + // FUNCTION: LEGO1 0x100ac220 MxNotification::MxNotification(MxCore* p_target, const MxNotificationParam& p_param) { @@ -105,6 +108,11 @@ MxResult MxNotificationManager::Tickle() while (m_sendList->size() != 0) { MxNotification* notif = m_sendList->front(); m_sendList->pop_front(); + + if (notif->GetParam()->GetNotification() == c_notificationEndAction) { + Extension::Call(HandleEndAction, (MxEndActionNotificationParam&) *notif->GetParam()); + } + notif->GetTarget()->Notify(*notif->GetParam()); delete notif; } @@ -159,6 +167,11 @@ void MxNotificationManager::FlushPending(MxCore* p_listener) while (pending.size() != 0) { notif = pending.front(); pending.pop_front(); + + if (notif->GetParam()->GetNotification() == c_notificationEndAction) { + Extension::Call(HandleEndAction, (MxEndActionNotificationParam&) *notif->GetParam()); + } + notif->GetTarget()->Notify(*notif->GetParam()); delete notif; } diff --git a/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp b/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp index b3fad5fc..1f3b7d36 100644 --- a/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp +++ b/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp @@ -32,7 +32,7 @@ MxResult MxDiskStreamProviderThread::Run() MxResult MxDiskStreamProviderThread::StartWithTarget(MxDiskStreamProvider* p_target) { m_target = p_target; - return Start(0x1000, 0); + return Start(0, 0); } // FUNCTION: LEGO1 0x100d0f70 diff --git a/LEGO1/omni/src/stream/mxdsbuffer.cpp b/LEGO1/omni/src/stream/mxdsbuffer.cpp index 0d49741a..732f9fac 100644 --- a/LEGO1/omni/src/stream/mxdsbuffer.cpp +++ b/LEGO1/omni/src/stream/mxdsbuffer.cpp @@ -28,6 +28,7 @@ MxDSBuffer::MxDSBuffer() m_bytesRemaining = 0; m_mode = e_preallocated; m_unk0x30 = 0; + m_sourceBuffer = NULL; } // FUNCTION: LEGO1 0x100c6530 @@ -36,6 +37,10 @@ MxDSBuffer::~MxDSBuffer() { assert(m_referenceCount == 0); + if (m_sourceBuffer) { + m_sourceBuffer->ReleaseRef(NULL); + } + if (m_pBuffer != NULL) { switch (m_mode) { case e_allocate: @@ -267,6 +272,28 @@ MxResult MxDSBuffer::ParseChunk( return FAILURE; } + // START FIX: Ref-Counting Backpressure for Split Chunks + // + // PROBLEM: When a `DS_CHUNK_SPLIT` is found, the temporary `MxStreamChunk` + // header that holds a reference to the source buffer is immediately + // destroyed. This prematurely releases the reference, causing the source + // buffer's ref-count to drop to zero. + // + // EFFECT: The source buffer is freed immediately instead of being kept + // alive on the m_list0x74 "keep-alive" list. This breaks the natural + // ref-counting backpressure mechanism, as the controller is blind to the + // downstream workload and keeps reading new data from the stream at full + // speed, eventually leading to a memory leak. + // + // SOLUTION: We explicitly link the new reassembly buffer to the original + // source buffer. We then add an artificial reference to the source buffer. + // This reference is designed to be released by the reassembly buffer's + // destructor, ensuring the source buffer is kept alive for the correct + // duration and that the backpressure system functions as intended. + if (p_header->GetBuffer()) { + buffer->SetSourceBuffer(p_header->GetBuffer()); + } + MxU16* flags = MxStreamChunk::IntoFlags(buffer->GetBuffer()); *flags = p_header->GetChunkFlags() & ~DS_CHUNK_SPLIT; @@ -409,9 +436,7 @@ MxU8 MxDSBuffer::ReleaseRef(MxDSChunk*) // FUNCTION: LEGO1 0x100c6ee0 void MxDSBuffer::AddRef(MxDSChunk* p_chunk) { - if (p_chunk) { - m_referenceCount++; - } + m_referenceCount++; } // FUNCTION: LEGO1 0x100c6ef0 diff --git a/LEGO1/omni/src/stream/mxstreamer.cpp b/LEGO1/omni/src/stream/mxstreamer.cpp index bb5f1cb5..8693ba6e 100644 --- a/LEGO1/omni/src/stream/mxstreamer.cpp +++ b/LEGO1/omni/src/stream/mxstreamer.cpp @@ -66,7 +66,7 @@ MxStreamController* MxStreamer::Open(const char* p_name, MxU16 p_lookupType) MxStreamController* stream = NULL; - if (GetOpenStream(p_name)) { + if ((stream = GetOpenStream(p_name))) { goto done; } diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 71a3808e..4ebccb58 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -327,6 +327,7 @@ void MxDisplaySurface::SetPalette(MxPalette* p_palette) } } +#ifndef MINIWIN MxS32 bitCount = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount; if (bitCount == 8) { return; @@ -378,6 +379,7 @@ void MxDisplaySurface::SetPalette(MxPalette* p_palette) m_32bitPal[i] = red | green | blue | alpha; } } +#endif } // FUNCTION: LEGO1 0x100bacc0 @@ -412,7 +414,13 @@ void MxDisplaySurface::VTable0x28( ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; ddsd.dwWidth = p_width; ddsd.dwHeight = p_height; +#ifdef MINIWIN + ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); + ddsd.ddpfPixelFormat.dwFlags = DDPF_PALETTEINDEXED8 | DDPF_RGB; + ddsd.ddpfPixelFormat.dwRGBBitCount = 8; +#else ddsd.ddpfPixelFormat = m_surfaceDesc.ddpfPixelFormat; +#endif ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; LPDIRECTDRAWSURFACE tempSurface = nullptr; @@ -422,6 +430,25 @@ void MxDisplaySurface::VTable0x28( return; } +#ifdef MINIWIN + MxBITMAPINFO* bmi = p_bitmap->GetBitmapInfo(); + if (bmi) { + PALETTEENTRY pe[256]; + for (int i = 0; i < 256; i++) { + pe[i].peRed = bmi->m_bmiColors[i].rgbRed; + pe[i].peGreen = bmi->m_bmiColors[i].rgbGreen; + pe[i].peBlue = bmi->m_bmiColors[i].rgbBlue; + pe[i].peFlags = PC_NONE; + } + + LPDIRECTDRAWPALETTE palette = nullptr; + if (draw->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &palette, NULL) == DD_OK && palette) { + tempSurface->SetPalette(palette); + palette->Release(); + } + } +#endif + if (m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount != 32) { DDCOLORKEY colorKey; if (m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount == 8) { @@ -450,7 +477,7 @@ void MxDisplaySurface::VTable0x28( MxU8* data = p_bitmap->GetStart(p_left, p_top); - MxS32 bytesPerPixel = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount / 8; + MxS32 bytesPerPixel = tempDesc.ddpfPixelFormat.dwRGBBitCount / 8; MxU8* surface = (MxU8*) tempDesc.lpSurface; MxLong stride = (bytesPerPixel == 1) ? GetAdjustedStride(p_bitmap) : -p_width + GetAdjustedStride(p_bitmap); @@ -502,8 +529,7 @@ void MxDisplaySurface::VTable0x30( MxS32 p_right, MxS32 p_bottom, MxS32 p_width, - MxS32 p_height, - MxBool p_RLE + MxS32 p_height ) { if (!GetRectIntersection( @@ -526,7 +552,13 @@ void MxDisplaySurface::VTable0x30( ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_PIXELFORMAT | DDSD_CAPS; ddsd.dwWidth = p_width; ddsd.dwHeight = p_height; +#ifdef MINIWIN + ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); + ddsd.ddpfPixelFormat.dwFlags = DDPF_PALETTEINDEXED8 | DDPF_RGB; + ddsd.ddpfPixelFormat.dwRGBBitCount = 8; +#else ddsd.ddpfPixelFormat = m_surfaceDesc.ddpfPixelFormat; +#endif ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; LPDIRECTDRAW draw = MVideoManager()->GetDirectDraw(); @@ -535,6 +567,25 @@ void MxDisplaySurface::VTable0x30( return; } +#ifdef MINIWIN + MxBITMAPINFO* bmi = p_bitmap->GetBitmapInfo(); + if (bmi) { + PALETTEENTRY pe[256]; + for (int i = 1; i < 256; i++) { + pe[i].peRed = bmi->m_bmiColors[i].rgbRed; + pe[i].peGreen = bmi->m_bmiColors[i].rgbGreen; + pe[i].peBlue = bmi->m_bmiColors[i].rgbBlue; + pe[i].peFlags = PC_NONE; + } + + LPDIRECTDRAWPALETTE palette = nullptr; + if (draw->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &palette, NULL) == DD_OK && palette) { + tempSurface->SetPalette(palette); + palette->Release(); + } + } +#endif + DDCOLORKEY colorKey; colorKey.dwColorSpaceLowValue = colorKey.dwColorSpaceHighValue = 0; tempSurface->SetColorKey(DDCKEY_SRCBLT, &colorKey); @@ -550,38 +601,32 @@ void MxDisplaySurface::VTable0x30( MxU8* data = p_bitmap->GetStart(p_left, p_top); - MxS32 bytesPerPixel = m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount / 8; + MxS32 bytesPerPixel = tempDesc.ddpfPixelFormat.dwRGBBitCount / 8; MxU8* surface = (MxU8*) tempDesc.lpSurface; - if (p_RLE) { - MxS32 size = p_bitmap->GetBmiHeader()->biSizeImage; - DrawTransparentRLE(data, surface, size, p_width, p_height, tempDesc.lPitch, bytesPerPixel * 8); - } - else { - MxLong stride = -p_width + GetAdjustedStride(p_bitmap); - MxLong length = -bytesPerPixel * p_width + tempDesc.lPitch; + MxLong stride = -p_width + GetAdjustedStride(p_bitmap); + MxLong length = -bytesPerPixel * p_width + tempDesc.lPitch; - for (MxS32 i = 0; i < p_height; i++) { - for (MxS32 j = 0; j < p_width; j++) { - if (*data != 0) { - switch (bytesPerPixel) { - case 1: - *surface = *data; - break; - case 2: - *(MxU16*) surface = m_16bitPal[*data]; - break; - default: - *(MxU32*) surface = m_32bitPal[*data]; - break; - } + for (MxS32 i = 0; i < p_height; i++) { + for (MxS32 j = 0; j < p_width; j++) { + if (*data != 0) { + switch (bytesPerPixel) { + case 1: + *surface = *data; + break; + case 2: + *(MxU16*) surface = m_16bitPal[*data]; + break; + default: + *(MxU32*) surface = m_32bitPal[*data]; + break; } - data++; - surface += bytesPerPixel; } - data += stride; - surface += length; + data++; + surface += bytesPerPixel; } + data += stride; + surface += length; } tempSurface->Unlock(NULL); @@ -591,161 +636,6 @@ void MxDisplaySurface::VTable0x30( tempSurface->Release(); } -// FUNCTION: LEGO1 0x100bb500 -// FUNCTION: BETA10 0x10140cd6 -void MxDisplaySurface::DrawTransparentRLE( - MxU8*& p_bitmapData, - MxU8*& p_surfaceData, - MxU32 p_bitmapSize, - MxS32 p_width, - MxS32 p_height, - MxLong p_pitch, - MxU8 p_bpp -) -{ - /* Assumes partial RLE for the bitmap: only the skipped pixels are compressed. - The drawn pixels are uncompressed. The procedure is: - 1. Read 3 bytes from p_bitmapData. Skip this many pixels on the surface. - 2. Read 3 bytes from p_bitmapData. Draw this many pixels on the surface. - 3. Repeat until the end of p_bitmapData is reached. */ - - MxU8* end = p_bitmapData + p_bitmapSize; - MxU8* surfCopy = p_surfaceData; // unused? - - // The total number of pixels drawn or skipped - MxU32 count = 0; - - // Used in both 8 and 16 bit branches - MxU32 skipCount; - MxU32 drawCount; - MxU32 t; - - if (p_bpp == 16) { - // DECOMP: why goto? - goto sixteen_bit; - } - - while (p_bitmapData < end) { - skipCount = *p_bitmapData++; - t = *p_bitmapData++; - skipCount += t << 8; - t = *p_bitmapData++; - skipCount += t << 16; - - MxS32 rowRemainder = p_width - count % p_width; - count += skipCount; - - if (skipCount >= rowRemainder) { - p_surfaceData += rowRemainder; // skip the rest of this row - skipCount -= rowRemainder; - p_surfaceData += p_pitch - p_width; // seek to start of next row - p_surfaceData += p_pitch * (skipCount / p_width); // skip entire rows if any - } - - // skip any pixels at the start of this row - p_surfaceData += skipCount % p_width; - if (p_bitmapData >= end) { - break; - } - - drawCount = *p_bitmapData++; - t = *p_bitmapData++; - drawCount += t << 8; - t = *p_bitmapData++; - drawCount += t << 16; - - rowRemainder = p_width - count % p_width; - count += drawCount; - - if (drawCount >= rowRemainder) { - memcpy(p_surfaceData, p_bitmapData, rowRemainder); - p_surfaceData += rowRemainder; - p_bitmapData += rowRemainder; - - drawCount -= rowRemainder; - - // seek to start of bitmap on this screen row - p_surfaceData += p_pitch - p_width; - MxS32 rows = drawCount / p_width; - - for (MxU32 i = 0; i < rows; i++) { - memcpy(p_surfaceData, p_bitmapData, p_width); - p_bitmapData += p_width; - p_surfaceData += p_pitch; - } - } - - MxS32 tail = drawCount % p_width; - memcpy(p_surfaceData, p_bitmapData, tail); - p_surfaceData += tail; - p_bitmapData += tail; - } - return; - -sixteen_bit: - while (p_bitmapData < end) { - skipCount = *p_bitmapData++; - t = *p_bitmapData++; - skipCount += t << 8; - t = *p_bitmapData++; - skipCount += t << 16; - - MxS32 rowRemainder = p_width - count % p_width; - count += skipCount; - - if (skipCount >= rowRemainder) { - p_surfaceData += 2 * rowRemainder; - skipCount -= rowRemainder; - p_surfaceData += p_pitch - 2 * p_width; - p_surfaceData += p_pitch * (skipCount / p_width); - } - - p_surfaceData += 2 * (skipCount % p_width); - if (p_bitmapData >= end) { - break; - } - - drawCount = *p_bitmapData++; - t = *p_bitmapData++; - drawCount += t << 8; - t = *p_bitmapData++; - drawCount += t << 16; - - rowRemainder = p_width - count % p_width; - count += drawCount; - - if (drawCount >= rowRemainder) { - // memcpy - for (MxU32 j = 0; j < rowRemainder; j++) { - *((MxU16*) p_surfaceData) = m_16bitPal[*p_bitmapData++]; - p_surfaceData += 2; - } - - drawCount -= rowRemainder; - - p_surfaceData += p_pitch - 2 * p_width; - MxS32 rows = drawCount / p_width; - - for (MxU32 i = 0; i < rows; i++) { - // memcpy - for (MxS32 j = 0; j < p_width; j++) { - *((MxU16*) p_surfaceData) = m_16bitPal[*p_bitmapData++]; - p_surfaceData += 2; - } - - p_surfaceData += p_pitch - 2 * p_width; - } - } - - MxS32 tail = drawCount % p_width; - // memcpy - for (MxS32 j = 0; j < tail; j++) { - *((MxU16*) p_surfaceData) = m_16bitPal[*p_bitmapData++]; - p_surfaceData += 2; - } - } -} - // FUNCTION: LEGO1 0x100bba50 void MxDisplaySurface::Display(MxS32 p_left, MxS32 p_top, MxS32 p_left2, MxS32 p_top2, MxS32 p_width, MxS32 p_height) { @@ -829,7 +719,13 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::VTable0x44( ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT; ddsd.dwWidth = p_bitmap->GetBmiWidth(); ddsd.dwHeight = p_bitmap->GetBmiHeightAbs(); +#ifdef MINIWIN + ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); + ddsd.ddpfPixelFormat.dwFlags = DDPF_PALETTEINDEXED8 | DDPF_RGB; + ddsd.ddpfPixelFormat.dwRGBBitCount = 8; +#else ddsd.ddpfPixelFormat = m_surfaceDesc.ddpfPixelFormat; +#endif ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; *p_ret = 0; ddsd.ddsCaps.dwCaps |= DDSCAPS_SYSTEMMEMORY; @@ -852,6 +748,29 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::VTable0x44( } if (surface) { +#ifdef MINIWIN + MxBITMAPINFO* bmi = p_bitmap->GetBitmapInfo(); + if (bmi) { + PALETTEENTRY pe[256]; + for (int i = 0; i < 256; i++) { + pe[i].peRed = bmi->m_bmiColors[i].rgbRed; + pe[i].peGreen = bmi->m_bmiColors[i].rgbGreen; + pe[i].peBlue = bmi->m_bmiColors[i].rgbBlue; + pe[i].peFlags = PC_NONE; + } + if (p_transparent) { + pe[0].peRed = 0; + pe[0].peGreen = 0; + pe[0].peBlue = 0; + } + LPDIRECTDRAWPALETTE palette = nullptr; + if (draw->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &palette, NULL) == DD_OK && palette) { + surface->SetPalette(palette); + palette->Release(); + } + } +#endif + memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); @@ -971,6 +890,21 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CopySurface(LPDIRECTDRAWSURFACE p_src) return NULL; } +#ifdef MINIWIN + LPDIRECTDRAWPALETTE srcPalette = nullptr; + if (p_src->GetPalette(&srcPalette) == DD_OK && srcPalette) { + PALETTEENTRY pe[256]; + if (srcPalette->GetEntries(0, 0, 256, pe) == DD_OK) { + LPDIRECTDRAWPALETTE newPalette = nullptr; + if (draw->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALLOW256, pe, &newPalette, NULL) == DD_OK) { + newSurface->SetPalette(newPalette); + newPalette->Release(); + } + } + srcPalette->Release(); + } +#endif + RECT rect = {0, 0, (LONG) ddsd.dwWidth, (LONG) ddsd.dwHeight}; if (newSurface->BltFast(0, 0, p_src, &rect, DDBLTFAST_WAIT) != DD_OK) { @@ -1084,8 +1018,7 @@ void MxDisplaySurface::VTable0x2c( MxS32 p_right, MxS32 p_bottom, MxS32 p_width, - MxS32 p_height, - MxBool p_RLE + MxS32 p_height ) { // DECOMP: Almost an exact copy of VTable0x28, except that it uses the argument DDSURFACEDESC @@ -1111,38 +1044,25 @@ void MxDisplaySurface::VTable0x2c( MxLong destStride = p_desc->lPitch; MxU8* dest = (MxU8*) p_desc->lpSurface + bytesPerPixel * p_right + (p_bottom * destStride); - if (p_RLE) { - DrawTransparentRLE( - src, - dest, - p_bitmap->GetBmiHeader()->biSizeImage, - p_width, - p_height, - destStride, - m_surfaceDesc.ddpfPixelFormat.dwRGBBitCount - ); - } - else { - MxLong srcStride = GetAdjustedStride(p_bitmap); - MxLong srcSkip = srcStride - p_width; - MxLong destSkip = destStride - bytesPerPixel * p_width; - for (MxS32 i = 0; i < p_height; i++, src += srcSkip, dest += destSkip) { - for (MxS32 j = 0; j < p_width; j++, src++) { - if (*src != 0) { - switch (bytesPerPixel) { - case 1: - *dest = *src; - break; - case 2: - *(MxU16*) dest = m_16bitPal[*src]; - break; - default: - *(MxU32*) dest = m_32bitPal[*src]; - break; - } + MxLong srcStride = GetAdjustedStride(p_bitmap); + MxLong srcSkip = srcStride - p_width; + MxLong destSkip = destStride - bytesPerPixel * p_width; + for (MxS32 i = 0; i < p_height; i++, src += srcSkip, dest += destSkip) { + for (MxS32 j = 0; j < p_width; j++, src++) { + if (*src != 0) { + switch (bytesPerPixel) { + case 1: + *dest = *src; + break; + case 2: + *(MxU16*) dest = m_16bitPal[*src]; + break; + default: + *(MxU32*) dest = m_32bitPal[*src]; + break; } - dest += bytesPerPixel; } + dest += bytesPerPixel; } } } @@ -1183,11 +1103,10 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::FUN_100bc8b0(MxS32 p_width, MxS32 p_height return surface; } -LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_cursorBitmap) +LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_cursorBitmap, MxPalette* p_palette) { LPDIRECTDRAWSURFACE newSurface = NULL; IDirectDraw* draw = MVideoManager()->GetDirectDraw(); - MVideoManager(); DDSURFACEDESC ddsd; memset(&ddsd, 0, sizeof(ddsd)); @@ -1197,14 +1116,19 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ return NULL; } - MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; - MxBool isAlphaAvailable = ((ddsd.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS) && - (ddsd.ddpfPixelFormat.dwRGBAlphaBitMask != 0); - ddsd.dwWidth = p_cursorBitmap->width; ddsd.dwHeight = p_cursorBitmap->height; ddsd.dwFlags = DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_VIDEOMEMORY | DDSCAPS_OFFSCREENPLAIN; +#ifdef MINIWIN + ddsd.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT); + ddsd.ddpfPixelFormat.dwFlags = DDPF_PALETTEINDEXED8 | DDPF_RGB; + ddsd.ddpfPixelFormat.dwRGBBitCount = 8; +#endif + + MxS32 bytesPerPixel = ddsd.ddpfPixelFormat.dwRGBBitCount / 8; + MxBool isAlphaAvailable = ((ddsd.ddpfPixelFormat.dwFlags & DDPF_ALPHAPIXELS) == DDPF_ALPHAPIXELS) && + (ddsd.ddpfPixelFormat.dwRGBAlphaBitMask != 0); if (draw->CreateSurface(&ddsd, &newSurface, NULL) != DD_OK) { ddsd.ddsCaps.dwCaps &= ~DDSCAPS_VIDEOMEMORY; @@ -1215,6 +1139,10 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ } } +#ifdef MINIWIN + newSurface->SetPalette(p_palette->CreateNativePalette()); +#endif + memset(&ddsd, 0, sizeof(ddsd)); ddsd.dwSize = sizeof(ddsd); @@ -1242,6 +1170,8 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ else { pixel = isBlack ? 0 : 0xff; } + surface[x + y * p_cursorBitmap->width] = pixel; + break; } case 2: { MxU16* surface = (MxU16*) ddsd.lpSurface; @@ -1284,8 +1214,7 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ switch (bytesPerPixel) { case 1: { DDCOLORKEY colorkey; - colorkey.dwColorSpaceHighValue = 0x10; - colorkey.dwColorSpaceLowValue = 0x10; + colorkey.dwColorSpaceHighValue = colorkey.dwColorSpaceLowValue = 0x10; newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); break; } diff --git a/LEGO1/omni/src/video/mxvideopresenter.cpp b/LEGO1/omni/src/video/mxvideopresenter.cpp index 38662f05..ee03ab71 100644 --- a/LEGO1/omni/src/video/mxvideopresenter.cpp +++ b/LEGO1/omni/src/video/mxvideopresenter.cpp @@ -200,22 +200,6 @@ inline MxS32 MxVideoPresenter::PrepareRects(RECT& p_rectDest, RECT& p_rectSrc) return -1; } - if (p_rectDest.bottom > 480) { - p_rectDest.bottom = 480; - } - - if (p_rectDest.right > 640) { - p_rectDest.right = 640; - } - - if (p_rectSrc.bottom > 480) { - p_rectSrc.bottom = 480; - } - - if (p_rectSrc.right > 640) { - p_rectSrc.right = 640; - } - int height, width; if ((height = (p_rectDest.bottom - p_rectDest.top) + 1) <= 1 || (width = (p_rectDest.right - p_rectDest.left) + 1) <= 1) { @@ -269,8 +253,7 @@ void MxVideoPresenter::PutFrame() rect.GetLeft(), rect.GetTop(), m_frameBitmap->GetBmiWidth(), - m_frameBitmap->GetBmiHeightAbs(), - TRUE + m_frameBitmap->GetBmiHeightAbs() ); } } @@ -296,7 +279,7 @@ void MxVideoPresenter::PutFrame() } } else { - displaySurface->VTable0x30(m_frameBitmap, 0, 0, GetX(), GetY(), GetWidth(), GetHeight(), FALSE); + displaySurface->VTable0x30(m_frameBitmap, 0, 0, GetX(), GetY(), GetWidth(), GetHeight()); } } else if (m_surface) { diff --git a/README.md b/README.md index 95237c85..435e7628 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ Please note: this project is primarily dedicated to achieving platform independe | Nintendo 3DS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | Xbox One | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | | iOS | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | +| Android | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | +| Playstation Vita | [![CI](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml/badge.svg)](https://github.com/isledecomp/isle-portable/actions/workflows/ci.yml) | We are actively working to support more platforms. If you have experience with a particular platform, we encourage you to contribute to `isle-portable`. You can find a [list of ongoing efforts](https://github.com/isledecomp/isle-portable/wiki/Work%E2%80%90in%E2%80%90progress-ports) in our Wiki. @@ -43,7 +45,7 @@ To achieve our goal of platform independence, we need to replace any Windows-onl | WinMM, DirectSound (Audio) | [SDL3](https://www.libsdl.org/), [miniaudio](https://miniaud.io/) | ✅ | [Remarks](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3Aaudio%5D%22&type=code) | | DirectDraw (2D video) | [SDL3](https://www.libsdl.org/) | ✅ | [Remarks](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3A2d%5D%22&type=code) | | [Smacker](https://github.com/isledecomp/isle/tree/master/3rdparty/smacker) | [libsmacker](https://github.com/foxtacles/libsmacker) | ✅ | [Remarks](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable%20%22%2F%2F%20%5Blibrary%3Alibsmacker%5D%22&type=code) | -| Direct3D (3D video) | [SDL3 (Vulkan, Metal, D3D12)](https://www.libsdl.org/), D3D9, OpenGL, OpenGL ES, Software | ✅ | [Remarks](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3A3d%5D%22&type=code) | +| Direct3D (3D video) | [SDL3 (Vulkan, Metal, D3D12)](https://www.libsdl.org/), D3D9, OpenGL 1.1, OpenGL ES 2.0, OpenGL ES 3.0, Software | ✅ | [Remarks](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3A3d%5D%22&type=code) | | Direct3D Retained Mode | Custom re-implementation | ✅ | [Remarks](https://github.com/search?q=repo%3Aisledecomp%2Fisle-portable+%22%2F%2F+%5Blibrary%3Aretained%5D%22&type=code) | | [SmartHeap](https://github.com/isledecomp/isle/tree/master/3rdparty/smartheap) | Default memory allocator | - | - | diff --git a/android-project/.gitignore b/android-project/.gitignore new file mode 100644 index 00000000..bcd50fa6 --- /dev/null +++ b/android-project/.gitignore @@ -0,0 +1,5 @@ +.cxx +.gradle +local.properties +build +*.aar diff --git a/android-project/app/build.gradle b/android-project/app/build.gradle new file mode 100644 index 00000000..1a73e4b9 --- /dev/null +++ b/android-project/app/build.gradle @@ -0,0 +1,101 @@ +plugins { + id 'com.android.application' +} +def androidProject = projectDir.parentFile + +android { + namespace "org.legoisland.isle" + compileSdk 35 + + defaultConfig { + applicationId 'org.legoisland.isle' + minSdk 21 + targetSdk 35 + versionCode 1 + versionName '1.0' + + externalNativeBuild { + cmake { + arguments '-DANDROID_STL=c++_shared', "-DFETCHCONTENT_BASE_DIR=${androidProject}/build/_deps" + if (project.hasProperty('cmakeArgs')) { + project.cmakeArgs.split(" ").each {arg -> arguments arg} + } + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + } + externalNativeBuild { + cmake { + version "3.30.5" + path '../../CMakeLists.txt' + } + } + + signingConfigs { + register("release") { + enableV4Signing = true + keyAlias = System.getenv("SIGNING_KEY_ALIAS") + keyPassword = System.getenv("SIGNING_KEY_PASSWORD") + System.getenv("SIGNING_STORE_FILE")?.with { storeFile = file(it) } + storePassword = System.getenv("SIGNING_STORE_PASSWORD") + } + } + buildTypes { + release { + minifyEnabled true + signingConfig = signingConfigs.getByName("release") +// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + prefab true + } +} + +afterEvaluate { + def androidPath = System.getenv("ANDROID_HOME") ?: android.sdkDirectory + def ndkPath = System.getenv("ANDROID_NDK_HOME") ?: android.ndkDirectory + + tasks.named('compileSDL3AndroidArchive').configure { + environment "ANDROID_HOME", androidPath + environment "ANDROID_NDK_HOME", ndkPath + } +} + +def aarDest = project.providers.provider { file("${projectDir}/libs/SDL3.aar") } +tasks.register('downloadSDL3', Exec) { + workingDir = androidProject + commandLine 'cmake', '-P', 'downloadSDL3.cmake' +} +tasks.register('compileSDL3AndroidArchive', Exec) { + workingDir = androidProject + dependsOn(downloadSDL3) + + def sdl3Dir = "build/_deps/sdl3-src" + commandLine 'python', "${sdl3Dir}/build-scripts/build-release.py", + '--actions', 'android', + '--fast', '--force', + "--root=${sdl3Dir}", "--android-api=${android.defaultConfig.minSdk}" + + doLast { + def aarFile = file("${androidProject}/${sdl3Dir}/build-android").listFiles().find { + it.name.endsWith(".aar") + } + aarDest.get().parentFile.mkdirs() + aarFile.renameTo(aarDest.get()) + } +} +tasks.register('ensureSDL3') { + // if DOWNLOAD_DEPENDENCIES=OFF download the version appropriate + // 'devel android zip' and place the .aar at `aarDest` + if (aarDest.get().exists()) { return false } + dependsOn(compileSDL3AndroidArchive) +} + +dependencies { + implementation files('libs/SDL3.aar').builtBy(ensureSDL3) +} diff --git a/android-project/app/src/main/AndroidManifest.xml b/android-project/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..7d76ba78 --- /dev/null +++ b/android-project/app/src/main/AndroidManifest.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android-project/app/src/main/ic_launcher-playstore.png b/android-project/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..d38af7d8 Binary files /dev/null and b/android-project/app/src/main/ic_launcher-playstore.png differ diff --git a/android-project/app/src/main/java/org/legoisland/isle/IsleActivity.java b/android-project/app/src/main/java/org/legoisland/isle/IsleActivity.java new file mode 100644 index 00000000..4e556c5b --- /dev/null +++ b/android-project/app/src/main/java/org/legoisland/isle/IsleActivity.java @@ -0,0 +1,9 @@ +package org.legoisland.isle; + +import org.libsdl.app.SDLActivity; + +public class IsleActivity extends SDLActivity { + protected String[] getLibraries() { + return new String[] { "SDL3", "lego1", "isle" }; + } +} diff --git a/android-project/app/src/main/res/drawable/ic_launcher_foreground.xml b/android-project/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 00000000..14440e6a --- /dev/null +++ b/android-project/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android-project/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android-project/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..7353dbd1 --- /dev/null +++ b/android-project/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android-project/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android-project/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..7353dbd1 --- /dev/null +++ b/android-project/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android-project/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/android-project/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 00000000..f4d86bca Binary files /dev/null and b/android-project/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/android-project/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/android-project/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 00000000..60a5c417 Binary files /dev/null and b/android-project/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/android-project/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/android-project/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 00000000..4a3da27a Binary files /dev/null and b/android-project/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/android-project/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/android-project/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 00000000..047b4028 Binary files /dev/null and b/android-project/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/android-project/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/android-project/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 00000000..22fb6853 Binary files /dev/null and b/android-project/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/android-project/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/android-project/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..043c7aa7 Binary files /dev/null and b/android-project/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/android-project/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/android-project/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 00000000..baea92cd Binary files /dev/null and b/android-project/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/android-project/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/android-project/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..5555f64d Binary files /dev/null and b/android-project/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/android-project/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/android-project/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 00000000..8df86ac9 Binary files /dev/null and b/android-project/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/android-project/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/android-project/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 00000000..320691a8 Binary files /dev/null and b/android-project/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/android-project/app/src/main/res/values/ic_launcher_background.xml b/android-project/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..5e8204d2 --- /dev/null +++ b/android-project/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #39A0D9 + \ No newline at end of file diff --git a/android-project/app/src/main/res/values/strings.xml b/android-project/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..85a6dca0 --- /dev/null +++ b/android-project/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Lego Island + diff --git a/android-project/app/src/main/res/values/styles.xml b/android-project/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..ff6c9d2c --- /dev/null +++ b/android-project/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/android-project/build.gradle b/android-project/build.gradle new file mode 100644 index 00000000..586a2bbd --- /dev/null +++ b/android-project/build.gradle @@ -0,0 +1,4 @@ +plugins { + id 'com.android.application' version '8.7.0' apply false + id 'com.android.library' version '8.7.0' apply false +} diff --git a/android-project/downloadSDL3.cmake b/android-project/downloadSDL3.cmake new file mode 100644 index 00000000..77479e86 --- /dev/null +++ b/android-project/downloadSDL3.cmake @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.25...4.0 FATAL_ERROR) + +include(FetchContent) + +set(FETCHCONTENT_BASE_DIR "build/_deps") + +FetchContent_Populate( + SDL3 + GIT_REPOSITORY "https://github.com/libsdl-org/SDL.git" + GIT_TAG "main" + SOURCE_DIR "build/_deps/sdl3-src" + BINARY_DIR "build/_deps/sdl3-build" +) diff --git a/android-project/gradle.properties b/android-project/gradle.properties new file mode 100644 index 00000000..d5e8af31 --- /dev/null +++ b/android-project/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true diff --git a/android-project/gradle/wrapper/gradle-wrapper.jar b/android-project/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/android-project/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android-project/gradle/wrapper/gradle-wrapper.properties b/android-project/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..8c558f26 --- /dev/null +++ b/android-project/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/android-project/gradlew b/android-project/gradlew new file mode 100755 index 00000000..4f906e0c --- /dev/null +++ b/android-project/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/android-project/gradlew.bat b/android-project/gradlew.bat new file mode 100644 index 00000000..ac1b06f9 --- /dev/null +++ b/android-project/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android-project/settings.gradle b/android-project/settings.gradle new file mode 100644 index 00000000..36e93e45 --- /dev/null +++ b/android-project/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "legoisland" +include ':app' diff --git a/assets/badend/BadEnd_Smk.smk b/assets/badend/BadEnd_Smk.smk new file mode 100644 index 00000000..401a7e6d Binary files /dev/null and b/assets/badend/BadEnd_Smk.smk differ diff --git a/assets/badend/BadEnd_Wav.wav b/assets/badend/BadEnd_Wav.wav new file mode 100644 index 00000000..33d0d207 Binary files /dev/null and b/assets/badend/BadEnd_Wav.wav differ diff --git a/assets/hdmusic/Act3Music_HD.wav b/assets/hdmusic/Act3Music_HD.wav new file mode 100644 index 00000000..0480014b --- /dev/null +++ b/assets/hdmusic/Act3Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7c3e3080ea1af626bc202d091ead654ef38ac8e27779c2908dca390f3106ced3 +size 14201934 diff --git a/assets/hdmusic/BeachBlvd_Music_HD.wav b/assets/hdmusic/BeachBlvd_Music_HD.wav new file mode 100644 index 00000000..9385be34 --- /dev/null +++ b/assets/hdmusic/BeachBlvd_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1143a85549ca76ec8edccc164e8c54d803d7eb9a5bddd5c0387ce102b5397f2c +size 26919040 diff --git a/assets/hdmusic/Beach_Music_HD.wav b/assets/hdmusic/Beach_Music_HD.wav new file mode 100644 index 00000000..a198fec1 --- /dev/null +++ b/assets/hdmusic/Beach_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9e84733243a6d1d3044aab8032cf5d2b5769b1e3e3f0787c3b3bf18689c9f535 +size 22490016 diff --git a/assets/hdmusic/BrickHunt_HD.wav b/assets/hdmusic/BrickHunt_HD.wav new file mode 100644 index 00000000..4aaa8721 --- /dev/null +++ b/assets/hdmusic/BrickHunt_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cfbd44517186635ad270a971e9328f65890b94258c456aafda6658830147dce2 +size 33980544 diff --git a/assets/hdmusic/BrickstrChase_HD.wav b/assets/hdmusic/BrickstrChase_HD.wav new file mode 100644 index 00000000..f7222876 --- /dev/null +++ b/assets/hdmusic/BrickstrChase_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a158faeb0b651da3c0663cfa871851ad226dae02577be97d049bc4f84e4d9251 +size 14614784 diff --git a/assets/hdmusic/Cave_Music_HD.wav b/assets/hdmusic/Cave_Music_HD.wav new file mode 100644 index 00000000..b02ed9da --- /dev/null +++ b/assets/hdmusic/Cave_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6ae808110329a64ae06a96b08210bea8938f92ac0006b850a62b941541339359 +size 12214400 diff --git a/assets/hdmusic/CentralRoads_Music_HD.wav b/assets/hdmusic/CentralRoads_Music_HD.wav new file mode 100644 index 00000000..43c0e5ee --- /dev/null +++ b/assets/hdmusic/CentralRoads_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39bd028528e1c9e2c9cea0b090c3c54f78b1889135a863c09080b90d3b13b3d2 +size 34113102 diff --git a/assets/hdmusic/Hospital_Music_HD.wav b/assets/hdmusic/Hospital_Music_HD.wav new file mode 100644 index 00000000..718d46c7 --- /dev/null +++ b/assets/hdmusic/Hospital_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f253c05dcf77d5b7d5109f2e95ac5af85ee7a699a8689d8c276951617bbfd0e6 +size 40701386 diff --git a/assets/hdmusic/InfoCenter_3rd_Floor_Music_HD.wav b/assets/hdmusic/InfoCenter_3rd_Floor_Music_HD.wav new file mode 100644 index 00000000..0320b078 --- /dev/null +++ b/assets/hdmusic/InfoCenter_3rd_Floor_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:de7bf7bc1512aa6430b35e94c952c9b815b13ceded533f458ff9806a3915f948 +size 20201550 diff --git a/assets/hdmusic/InformationCenter_Music_HD.wav b/assets/hdmusic/InformationCenter_Music_HD.wav new file mode 100644 index 00000000..f896b392 --- /dev/null +++ b/assets/hdmusic/InformationCenter_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c1ade0044cef699f526898cd44c34f426f3f057312f8d457ebc4b454096bdb0 +size 27256398 diff --git a/assets/hdmusic/JBMusic1_HD.wav b/assets/hdmusic/JBMusic1_HD.wav new file mode 100644 index 00000000..a07f7354 --- /dev/null +++ b/assets/hdmusic/JBMusic1_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5c11ed75397b677a069cd9302bec3e1178067b030c42a47ef6853cb9212ff716 +size 22200448 diff --git a/assets/hdmusic/JBMusic2_HD.wav b/assets/hdmusic/JBMusic2_HD.wav new file mode 100644 index 00000000..cdb64575 --- /dev/null +++ b/assets/hdmusic/JBMusic2_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8964ca0fb9698abab90398bfa62f04e52830b647af8b4014a129edd9fbfdae47 +size 28735604 diff --git a/assets/hdmusic/JBMusic3_HD.wav b/assets/hdmusic/JBMusic3_HD.wav new file mode 100644 index 00000000..cf22253b --- /dev/null +++ b/assets/hdmusic/JBMusic3_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98b02da1c3900a9e906b88692ecb2087f2091b4af7303565eaeff242cdf7c313 +size 21653144 diff --git a/assets/hdmusic/JBMusic4_HD.wav b/assets/hdmusic/JBMusic4_HD.wav new file mode 100644 index 00000000..acb73671 --- /dev/null +++ b/assets/hdmusic/JBMusic4_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13c787e61647e22a1f4535d2491cbad5638c835142c075ebe6a39964ff769466 +size 24699000 diff --git a/assets/hdmusic/JBMusic5_HD.wav b/assets/hdmusic/JBMusic5_HD.wav new file mode 100644 index 00000000..69ca50f6 --- /dev/null +++ b/assets/hdmusic/JBMusic5_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3b744d204a6bcb3c659f490fc9caa6a2bff96e6c91ab7ba8770508b208f0fffd +size 12828932 diff --git a/assets/hdmusic/JBMusic6_HD.wav b/assets/hdmusic/JBMusic6_HD.wav new file mode 100644 index 00000000..a5235fce --- /dev/null +++ b/assets/hdmusic/JBMusic6_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8779860718e07a6093c979d77ec1723dcef93762bc7759e960e70ee730b66fb3 +size 10059690 diff --git a/assets/hdmusic/Jail_Music_HD.wav b/assets/hdmusic/Jail_Music_HD.wav new file mode 100644 index 00000000..a9f323c3 --- /dev/null +++ b/assets/hdmusic/Jail_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b39644468a172178738aa8854b514580f2b098a810dba127b2f5ae25534fe87e +size 12140672 diff --git a/assets/hdmusic/JetskiRace_Music_HD.wav b/assets/hdmusic/JetskiRace_Music_HD.wav new file mode 100644 index 00000000..eac69605 --- /dev/null +++ b/assets/hdmusic/JetskiRace_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:82ccad0d9877ccf915fc050d9b245785cf6bcb2406a6fcb9f6bdb9a75a3fc8c4 +size 11367404 diff --git a/assets/hdmusic/Park_Music_HD.wav b/assets/hdmusic/Park_Music_HD.wav new file mode 100644 index 00000000..83c4f4da --- /dev/null +++ b/assets/hdmusic/Park_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6b645676140604d078be9cb1dc7d5f0ee17700878ff50a5280b426616898be64 +size 16089216 diff --git a/assets/hdmusic/PoliceStation_Music_HD.wav b/assets/hdmusic/PoliceStation_Music_HD.wav new file mode 100644 index 00000000..6a4dd58c --- /dev/null +++ b/assets/hdmusic/PoliceStation_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59c5ad73804bf813a852a5486e6ab13739a16542d06c736c28c164694ed424fd +size 10070544 diff --git a/assets/hdmusic/RaceTrackRoad_Music_HD.wav b/assets/hdmusic/RaceTrackRoad_Music_HD.wav new file mode 100644 index 00000000..813d33b3 --- /dev/null +++ b/assets/hdmusic/RaceTrackRoad_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:002c09354642fd8757ea999d62badf0b346f5ab1d4fb6b6ce31e471b0a6bdb1a +size 33341560 diff --git a/assets/hdmusic/ResidentalArea_Music_HD.wav b/assets/hdmusic/ResidentalArea_Music_HD.wav new file mode 100644 index 00000000..923ee0dc --- /dev/null +++ b/assets/hdmusic/ResidentalArea_Music_HD.wav @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f24f80d0979e1794d42000b55e95f0674be196af94c30abfa2a8d971e106a309 +size 15794304 diff --git a/assets/main.cpp b/assets/main.cpp new file mode 100644 index 00000000..0900f715 --- /dev/null +++ b/assets/main.cpp @@ -0,0 +1,311 @@ +#include "mxdsaction.h" + +#include +#include +#include +#include +#include + +si::Interleaf::Version version = si::Interleaf::Version2_2; +uint32_t bufferSize = 65536; +uint32_t bufferCount = 8; + +std::string out; +std::ofstream depfile; +si::MemoryBuffer mxHd; + +void CreateWidescreen() +{ + std::string result = out + "/widescreen.si"; + struct AssetView { + std::string name; + std::string extra; + int32_t z; + }; + const AssetView widescreenBitmaps[] = { + {"GaraDoor_Background_Wide", + "World:current, StartWith:\\Lego\\Scripts\\Isle\\Isle;1160, RemoveWith:\\Lego\\Scripts\\Isle\\Isle;1161", + -10}, + {"Police_Background_Wide", + "World:\\lego\\scripts\\police\\police;0, StartWith:\\Lego\\Scripts\\Police\\Police;0, " + "RemoveWith:\\Lego\\Scripts\\Police\\Police;0", + 10}, + {"Hospital_Background_Wide", + "World:\\lego\\scripts\\hospital\\hospital;0, StartWith:\\Lego\\Scripts\\Hospital\\Hospital;0, " + "RemoveWith:\\Lego\\Scripts\\Hospital\\Hospital;0", + 10}, + {"SeaView_Background_Wide", + "World:\\Lego\\Scripts\\Isle\\Isle;0, StartWith:\\Lego\\Scripts\\Isle\\Isle;1140, " + "RemoveWith:\\Lego\\Scripts\\Isle\\Isle;1141", + -10}, + {"ElevRide_Background_Wide", + "World:\\Lego\\Scripts\\Isle\\Isle;0, StartWith:\\Lego\\Scripts\\Isle\\Isle;1050, " + "RemoveWith:\\Lego\\Scripts\\Isle\\Isle;1051", + -10}, + {"Observe_Background_Wide", + "World:current, StartWith:\\Lego\\Scripts\\Isle\\Isle;1118, " + "RemoveWith:\\Lego\\Scripts\\Isle\\Isle;1119", + -10}, + {"ElevDown_Background_Wide", + "World:\\Lego\\Scripts\\Isle\\Isle;0, StartWith:\\Lego\\Scripts\\Isle\\Isle;1145, " + "RemoveWith:\\Lego\\Scripts\\Isle\\Isle;1146", + -10}, + {"ElevOpen_Background_Wide", + "World:\\Lego\\Scripts\\Isle\\Isle;0, StartWith:\\Lego\\Scripts\\Isle\\Isle;1114, " + "RemoveWith:\\Lego\\Scripts\\Isle\\Isle;1115", + -10}, + {"PoliDoor_Background_Wide", + "World:\\Lego\\Scripts\\Isle\\Isle;0, StartWith:\\Lego\\Scripts\\Isle\\Isle;1150, " + "RemoveWith:\\Lego\\Scripts\\Isle\\Isle;1151", + -10} + }; + + si::Interleaf si; + mxHd.seek(0, si::MemoryBuffer::SeekStart); + si.Read(&mxHd); + + int i = 0; + for (const AssetView& asset : widescreenBitmaps) { + si::Object* object = new si::Object; + std::string file = std::string("widescreen/") + asset.name + ".bmp"; + + object->id_ = i; + object->type_ = si::MxOb::Bitmap; + object->flags_ = MxDSAction::c_enabled | MxDSAction::c_bit4; + object->duration_ = -1; + object->loops_ = 1; + object->extra_ = si::bytearray(asset.extra.c_str(), asset.extra.length() + 1); + object->presenter_ = "MxStillPresenter"; + object->name_ = asset.name; + object->filetype_ = si::MxOb::STL; + object->location_ = si::Vector3(-240, 0.0, asset.z); + object->direction_ = si::Vector3(0, 0, 0); + object->up_ = si::Vector3(0, 1, 0); + + if (!object->ReplaceWithFile(file.c_str())) { + abort(); + } + + si.AppendChild(object); + depfile << result << ": " << (std::filesystem::current_path() / file).string() << std::endl; + i++; + } + + si.Write(result.c_str()); +} + +void CreateHDMusic() +{ + std::string result = out + "/hdmusic.si"; + struct AssetView { + std::string name; + std::string extra; + uint32_t duration; + uint32_t loops; + uint32_t flags; + }; + const AssetView wavAudio[] = { + {"BrickstrChase_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;3", + 82850, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"BrickHunt_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;4", + 192630, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"ResidentalArea_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;5", + 89540, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"BeachBlvd_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;6", + 152600, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"Cave_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;7", + 69240, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"CentralRoads_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;8", + 193380, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"Jail_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;9", + 68820, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"Hospital_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;10", + 211990, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"InformationCenter_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;11", + 154510, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"PoliceStation_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;12", + 57090, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"Park_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;13", + 91210, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"RaceTrackRoad_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;16", + 189000, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"Beach_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;17", + 127490, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"JetskiRace_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;19", + 64440, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"Act3Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;20", + 80510, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3}, + {"JBMusic1_HD", "Replace:\\Lego\\Scripts\\Isle\\Jukebox;55", 125850, 1, MxDSAction::c_enabled}, + {"JBMusic2_HD", "Replace:\\Lego\\Scripts\\Isle\\Jukebox;56", 162900, 1, MxDSAction::c_enabled}, + {"JBMusic3_HD", "Replace:\\Lego\\Scripts\\Isle\\Jukebox;57", 122750, 1, MxDSAction::c_enabled}, + {"JBMusic4_HD", "Replace:\\Lego\\Scripts\\Isle\\Jukebox;58", 140000, 1, MxDSAction::c_enabled}, + {"JBMusic5_HD", "Replace:\\Lego\\Scripts\\Isle\\Jukebox;59", 72720, 1, MxDSAction::c_enabled}, + {"JBMusic6_HD", "Replace:\\Lego\\Scripts\\Isle\\Jukebox;60", 57030, 1, MxDSAction::c_enabled}, + {"InfoCenter_3rd_Floor_Music_HD", + "Replace:\\Lego\\Scripts\\Isle\\Jukebox;61", + 114520, + 10000, + MxDSAction::c_enabled | MxDSAction::c_bit3} + }; + + si::Interleaf si; + mxHd.seek(0, si::MemoryBuffer::SeekStart); + si.Read(&mxHd); + + int i = 0; + for (const AssetView& asset : wavAudio) { + si::Object* object = new si::Object; + std::string file = std::string("hdmusic/") + asset.name + ".wav"; + + object->id_ = i; + object->type_ = si::MxOb::Sound; + object->flags_ = asset.flags; + object->duration_ = asset.duration * asset.loops; + object->loops_ = asset.loops; + object->extra_ = si::bytearray(asset.extra.c_str(), asset.extra.length() + 1); + object->presenter_ = "MxWavePresenter"; + object->name_ = asset.name; + object->filetype_ = si::MxOb::WAV; + object->location_ = si::Vector3(0, 0, 0); + object->direction_ = si::Vector3(0, 0, 1); + object->up_ = si::Vector3(0, 1, 0); + object->volume_ = 79; + + if (!object->ReplaceWithFile(file.c_str())) { + abort(); + } + + si.AppendChild(object); + depfile << result << ": " << (std::filesystem::current_path() / file).string() << std::endl; + i++; + } + + si.Write(result.c_str()); +} + +void CreateBadEnd() +{ + std::string result = out + "/badend.si"; + + si::Interleaf si; + mxHd.seek(0, si::MemoryBuffer::SeekStart); + si.Read(&mxHd); + + si::Object* composite = new si::Object; + std::string extra = "Replace:\\lego\\scripts\\intro:4"; + composite->id_ = 0; + composite->type_ = si::MxOb::Presenter; + composite->flags_ = MxDSAction::c_enabled; + composite->duration_ = 0; + composite->loops_ = 1; + composite->extra_ = si::bytearray(extra.c_str(), extra.length() + 1); + composite->presenter_ = "MxCompositeMediaPresenter"; + composite->name_ = "BadEnd_Movie"; + si.AppendChild(composite); + + si::Object* smk = new si::Object; + std::string file = std::string("badend/BadEnd_Smk.smk"); + smk->id_ = 1; + smk->type_ = si::MxOb::Video; + smk->flags_ = MxDSAction::c_enabled; + smk->duration_ = 75100; + smk->loops_ = 1; + smk->presenter_ = "MxSmkPresenter"; + smk->name_ = "BadEnd_Smk"; + smk->filetype_ = si::MxOb::SMK; + smk->location_ = si::Vector3(0, 0, -10000); + smk->direction_ = si::Vector3(0, 0, 1); + smk->up_ = si::Vector3(0, 1, 0); + smk->unknown29_ = 1; // Palette management = yes + if (!smk->ReplaceWithFile(file.c_str())) { + abort(); + } + + composite->AppendChild(smk); + depfile << result << ": " << (std::filesystem::current_path() / file).string() << std::endl; + + si::Object* wav = new si::Object; + file = std::string("badend/BadEnd_Wav.wav"); + wav->id_ = 2; + wav->type_ = si::MxOb::Sound; + wav->flags_ = MxDSAction::c_enabled; + wav->duration_ = 77800; + wav->loops_ = 1; + wav->presenter_ = "MxWavePresenter"; + wav->name_ = "BadEnd_Wav"; + wav->filetype_ = si::MxOb::WAV; + wav->location_ = si::Vector3(0, 0, 0); + wav->direction_ = si::Vector3(0, 0, 1); + wav->up_ = si::Vector3(0, 1, 0); + wav->volume_ = 79; + if (!wav->ReplaceWithFile(file.c_str())) { + abort(); + } + + composite->AppendChild(wav); + depfile << result << ": " << (std::filesystem::current_path() / file).string() << std::endl; + + si.Write(result.c_str()); +} + +int main(int argc, char* argv[]) +{ + out = argv[1]; + depfile = std::ofstream(argv[2]); + + mxHd.WriteU32(si::RIFF::MxHd); + mxHd.WriteU32(3 * sizeof(uint32_t)); + mxHd.WriteU32(version); + mxHd.WriteU32(bufferSize); + mxHd.WriteU32(bufferCount); + + CreateWidescreen(); + CreateHDMusic(); + CreateBadEnd(); + return 0; +} diff --git a/assets/widescreen/ElevDown_Background_Wide.bmp b/assets/widescreen/ElevDown_Background_Wide.bmp new file mode 100644 index 00000000..40c8ea3c --- /dev/null +++ b/assets/widescreen/ElevDown_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd8a32181a46005b9a8aa376a0ca76055cbbd6abbe335c612644d709fdf6cd82 +size 538678 diff --git a/assets/widescreen/ElevOpen_Background_Wide.bmp b/assets/widescreen/ElevOpen_Background_Wide.bmp new file mode 100644 index 00000000..50bbf944 --- /dev/null +++ b/assets/widescreen/ElevOpen_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd369911ec29fbd722f6e03025ced48d0292b0a7378f78692a36f9473df0390f +size 538678 diff --git a/assets/widescreen/ElevRide_Background_Wide.bmp b/assets/widescreen/ElevRide_Background_Wide.bmp new file mode 100644 index 00000000..00ada149 --- /dev/null +++ b/assets/widescreen/ElevRide_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ee0a4b4556a3c8f30a42b7c1d02a72fd96518115a72ece87646f718b162cf11 +size 538678 diff --git a/assets/widescreen/GaraDoor_Background_Wide.bmp b/assets/widescreen/GaraDoor_Background_Wide.bmp new file mode 100644 index 00000000..8f557e6b --- /dev/null +++ b/assets/widescreen/GaraDoor_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d245fde39c95f788a9406f2c4bbc94680d566cef925f03883a0824e3485c23d +size 538678 diff --git a/assets/widescreen/Hospital_Background_Wide.bmp b/assets/widescreen/Hospital_Background_Wide.bmp new file mode 100644 index 00000000..bd19d634 --- /dev/null +++ b/assets/widescreen/Hospital_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:69b26d76561c82f439644fdabaa5e1d443b474a7573af65e63af905851e44477 +size 538678 diff --git a/assets/widescreen/Observe_Background_Wide.bmp b/assets/widescreen/Observe_Background_Wide.bmp new file mode 100644 index 00000000..7b45f8d9 --- /dev/null +++ b/assets/widescreen/Observe_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cdfb2999353fc90f8f39dca03085c35bbd4ca3ca814ba6d3d5d3fc758e37adb5 +size 538678 diff --git a/assets/widescreen/PoliDoor_Background_Wide.bmp b/assets/widescreen/PoliDoor_Background_Wide.bmp new file mode 100644 index 00000000..0ce93779 --- /dev/null +++ b/assets/widescreen/PoliDoor_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca030a7316d400204fc99ee0ea2956189a5787e3d0f1fd75ceb9a10d2d3d428d +size 538678 diff --git a/assets/widescreen/Police_Background_Wide.bmp b/assets/widescreen/Police_Background_Wide.bmp new file mode 100644 index 00000000..dcbfcc50 --- /dev/null +++ b/assets/widescreen/Police_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc2cfb5d0522d2cc4cfbaf74d6b1b5f1f2e0512f613e398849a2359d537deff5 +size 538678 diff --git a/assets/widescreen/SeaView_Background_Wide.bmp b/assets/widescreen/SeaView_Background_Wide.bmp new file mode 100644 index 00000000..71fd368d --- /dev/null +++ b/assets/widescreen/SeaView_Background_Wide.bmp @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd0279cb8392ea8a116f0bf0e21eb879abc0ed4e1573571e1e21caa57ffd47a0 +size 538678 diff --git a/extensions/include/extensions/extensions.h b/extensions/include/extensions/extensions.h index 6c6f4f99..9f02a281 100644 --- a/extensions/include/extensions/extensions.h +++ b/extensions/include/extensions/extensions.h @@ -9,7 +9,7 @@ namespace Extensions { -constexpr const char* availableExtensions[] = {"extensions:texture loader"}; +constexpr const char* availableExtensions[] = {"extensions:texture loader", "extensions:si loader"}; LEGO1_EXPORT void Enable(const char* p_key, std::map p_options); diff --git a/extensions/include/extensions/siloader.h b/extensions/include/extensions/siloader.h new file mode 100644 index 00000000..e581f6b1 --- /dev/null +++ b/extensions/include/extensions/siloader.h @@ -0,0 +1,95 @@ +#pragma once + +#include "extensions/extensions.h" +#include "legoworld.h" +#include "mxactionnotificationparam.h" +#include "mxatom.h" + +#include +#include + +namespace si +{ +class Core; +} + +namespace Extensions +{ +class SiLoader { +public: + typedef std::pair StreamObject; + + static void Initialize(); + static bool Load(); + static std::optional HandleFind(StreamObject p_object, LegoWorld* world); + static std::optional HandleStart(MxDSAction& p_action); + static MxBool HandleWorld(LegoWorld* p_world); + static std::optional HandleRemove(StreamObject p_object, LegoWorld* world); + static std::optional HandleDelete(MxDSAction& p_action); + static MxBool HandleEndAction(MxEndActionNotificationParam& p_param); + + template + static std::optional ReplacedIn(MxDSAction& p_action, Args... p_args); + + static std::map options; + static std::vector files; + static std::vector directives; + static bool enabled; + +private: + static std::vector> startWith; + static std::vector> removeWith; + static std::vector> replace; + static std::vector> prepend; + static std::vector fullScreenMovie; + static std::vector disable3d; + + static bool LoadFile(const char* p_file); + static bool LoadDirective(const char* p_directive); + static MxStreamController* OpenStream(const char* p_file); + static void ParseExtra(const MxAtomId& p_atom, si::Core* p_core); + static bool IsWorld(const StreamObject& p_object); +}; + +#ifdef EXTENSIONS +template +std::optional SiLoader::ReplacedIn(MxDSAction& p_action, Args... p_args) +{ + StreamObject object{p_action.GetAtomId(), p_action.GetObjectId()}; + auto checkAtomId = [&p_action, &object](const auto& p_atomId) -> std::optional { + for (const auto& key : replace) { + if (key.second == object && key.first.first == p_atomId) { + return key.first; + } + } + + return std::nullopt; + }; + + std::optional result; + ((void) (!result.has_value() && (result = checkAtomId(p_args), true)), ...); + return result; +} + +constexpr auto Load = &SiLoader::Load; +constexpr auto HandleFind = &SiLoader::HandleFind; +constexpr auto HandleStart = &SiLoader::HandleStart; +constexpr auto HandleWorld = &SiLoader::HandleWorld; +constexpr auto HandleRemove = &SiLoader::HandleRemove; +constexpr auto HandleDelete = &SiLoader::HandleDelete; +constexpr auto HandleEndAction = &SiLoader::HandleEndAction; +constexpr auto ReplacedIn = [](auto&&... args) { return SiLoader::ReplacedIn(std::forward(args)...); }; +#else +constexpr decltype(&SiLoader::Load) Load = nullptr; +constexpr decltype(&SiLoader::HandleFind) HandleFind = nullptr; +constexpr decltype(&SiLoader::HandleStart) HandleStart = nullptr; +constexpr decltype(&SiLoader::HandleWorld) HandleWorld = nullptr; +constexpr decltype(&SiLoader::HandleRemove) HandleRemove = nullptr; +constexpr decltype(&SiLoader::HandleDelete) HandleDelete = nullptr; +constexpr decltype(&SiLoader::HandleEndAction) HandleEndAction = nullptr; +constexpr auto ReplacedIn = [](auto&&... args) -> std::optional { + ((void) args, ...); + return std::nullopt; +}; +#endif +}; // namespace Extensions diff --git a/extensions/src/extensions.cpp b/extensions/src/extensions.cpp index 779c0547..2eb276c6 100644 --- a/extensions/src/extensions.cpp +++ b/extensions/src/extensions.cpp @@ -1,5 +1,6 @@ #include "extensions/extensions.h" +#include "extensions/siloader.h" #include "extensions/textureloader.h" #include @@ -13,6 +14,11 @@ void Extensions::Enable(const char* p_key, std::map p_ TextureLoader::enabled = true; TextureLoader::Initialize(); } + else if (!SDL_strcasecmp(p_key, "extensions:si loader")) { + SiLoader::options = std::move(p_options); + SiLoader::enabled = true; + SiLoader::Initialize(); + } SDL_Log("Enabled extension: %s", p_key); break; diff --git a/extensions/src/siloader.cpp b/extensions/src/siloader.cpp new file mode 100644 index 00000000..4e3a7beb --- /dev/null +++ b/extensions/src/siloader.cpp @@ -0,0 +1,391 @@ +#include "extensions/siloader.h" + +#include "mxdsaction.h" +#include "mxmisc.h" +#include "mxstreamer.h" + +#include +#include + +using namespace Extensions; + +const char prependedMarker[] = ";;prepended;;"; + +std::map SiLoader::options; +std::vector SiLoader::files; +std::vector SiLoader::directives; +std::vector> SiLoader::startWith; +std::vector> SiLoader::removeWith; +std::vector> SiLoader::replace; +std::vector> SiLoader::prepend; +std::vector SiLoader::fullScreenMovie; +std::vector SiLoader::disable3d; +bool SiLoader::enabled = false; + +void SiLoader::Initialize() +{ + char* files = SDL_strdup(options["si loader:files"].c_str()); + char* saveptr; + + for (char* file = SDL_strtok_r(files, ",\n\r ", &saveptr); file; file = SDL_strtok_r(NULL, ",\n\r ", &saveptr)) { + SiLoader::files.emplace_back(file); + } + + char* directives = SDL_strdup(options["si loader:directives"].c_str()); + + for (char* directive = SDL_strtok_r(directives, ",\n\r ", &saveptr); directive; + directive = SDL_strtok_r(NULL, ",\n\r ", &saveptr)) { + SiLoader::directives.emplace_back(directive); + } + + SDL_free(files); + SDL_free(directives); +} + +bool SiLoader::Load() +{ + for (const auto& file : files) { + LoadFile(file.c_str()); + } + + for (const auto& directive : directives) { + LoadDirective(directive.c_str()); + } + + return true; +} + +std::optional SiLoader::HandleFind(StreamObject p_object, LegoWorld* world) +{ + for (const auto& key : replace) { + if (key.first == p_object) { + return world->Find(key.second.first, key.second.second); + } + } + + return std::nullopt; +} + +std::optional SiLoader::HandleStart(MxDSAction& p_action) +{ + StreamObject object{p_action.GetAtomId(), p_action.GetObjectId()}; + auto start = [](const StreamObject& p_object, MxDSAction& p_in, MxDSAction& p_out) -> MxResult { + if (!OpenStream(p_object.first.GetInternal())) { + return FAILURE; + } + + p_out.SetAtomId(p_object.first); + p_out.SetObjectId(p_object.second); + p_out.SetUnknown24(p_in.GetUnknown24()); + p_out.SetNotificationObject(p_in.GetNotificationObject()); + p_out.SetOrigin(p_in.GetOrigin()); + return Start(&p_out); + }; + + for (const auto& key : startWith) { + if (key.first == object && !IsWorld(key.first)) { + MxDSAction action; + start(key.second, p_action, action); + } + } + + for (const auto& key : replace) { + if (key.first == object) { + MxDSAction action; + MxResult result = start(key.second, p_action, action); + + if (result == SUCCESS) { + p_action.SetUnknown24(action.GetUnknown24()); + } + + return result; + } + } + + if (p_action.GetExtraLength() == 0 || !SDL_strstr(p_action.GetExtraData(), prependedMarker)) { + for (const auto& key : prepend) { + if (key.first == object) { + MxDSAction action; + MxResult result = start(key.second, p_action, action); + + if (result == SUCCESS) { + p_action.SetUnknown24(action.GetUnknown24()); + } + + return result; + } + } + } + + if (std::find(fullScreenMovie.begin(), fullScreenMovie.end(), object) != fullScreenMovie.end()) { + VideoManager()->EnableFullScreenMovie(TRUE); + } + + if (std::find(disable3d.begin(), disable3d.end(), object) != disable3d.end()) { + VideoManager()->FUN_1007c520(); + } + + return std::nullopt; +} + +MxBool SiLoader::HandleWorld(LegoWorld* p_world) +{ + StreamObject object{p_world->GetAtomId(), p_world->GetEntityId()}; + auto start = [](const StreamObject& p_object, MxDSAction& p_out) { + if (!OpenStream(p_object.first.GetInternal())) { + return; + } + + p_out.SetAtomId(p_object.first); + p_out.SetObjectId(p_object.second); + p_out.SetUnknown24(-1); + Start(&p_out); + }; + + for (const auto& key : startWith) { + if (key.first == object) { + MxDSAction action; + start(key.second, action); + } + } + + return TRUE; +} + +std::optional SiLoader::HandleRemove(StreamObject p_object, LegoWorld* world) +{ + for (const auto& key : removeWith) { + if (key.first == p_object) { + RemoveFromWorld(key.second.first, key.second.second, world->GetAtomId(), world->GetEntityId()); + } + } + + for (const auto& key : replace) { + if (key.first == p_object) { + return RemoveFromWorld(key.second.first, key.second.second, world->GetAtomId(), world->GetEntityId()); + } + } + + return std::nullopt; +} + +std::optional SiLoader::HandleDelete(MxDSAction& p_action) +{ + StreamObject object{p_action.GetAtomId(), p_action.GetObjectId()}; + auto deleteObject = [](const StreamObject& p_object, MxDSAction& p_in, MxDSAction& p_out) { + p_out.SetAtomId(p_object.first); + p_out.SetObjectId(p_object.second); + p_out.SetUnknown24(p_in.GetUnknown24()); + p_out.SetNotificationObject(p_in.GetNotificationObject()); + p_out.SetOrigin(p_in.GetOrigin()); + DeleteObject(p_out); + }; + + for (const auto& key : removeWith) { + if (key.first == object) { + MxDSAction action; + deleteObject(key.second, p_action, action); + } + } + + for (const auto& key : replace) { + if (key.first == object) { + MxDSAction action; + deleteObject(key.second, p_action, action); + p_action.SetUnknown24(action.GetUnknown24()); + return TRUE; + } + } + + return std::nullopt; +} + +MxBool SiLoader::HandleEndAction(MxEndActionNotificationParam& p_param) +{ + StreamObject object{p_param.GetAction()->GetAtomId(), p_param.GetAction()->GetObjectId()}; + auto start = [](const StreamObject& p_object, MxDSAction& p_in, MxDSAction& p_out) -> MxResult { + if (!OpenStream(p_object.first.GetInternal())) { + return FAILURE; + } + + p_out.SetAtomId(p_object.first); + p_out.SetObjectId(p_object.second); + p_out.SetUnknown24(-1); + p_out.SetNotificationObject(p_in.GetNotificationObject()); + p_out.SetOrigin(p_in.GetOrigin()); + p_out.AppendExtra(sizeof(prependedMarker), prependedMarker); + return Start(&p_out); + }; + + if (std::find(fullScreenMovie.begin(), fullScreenMovie.end(), object) != fullScreenMovie.end()) { + VideoManager()->EnableFullScreenMovie(FALSE); + } + + if (!p_param.GetSender() || !p_param.GetSender()->IsA("MxCompositePresenter")) { + for (const auto& key : prepend) { + if (key.second == object) { + MxDSAction action; + start(key.first, *p_param.GetAction(), action); + } + } + } + + return TRUE; +} + +bool SiLoader::LoadFile(const char* p_file) +{ + si::Interleaf si; + MxStreamController* controller; + + MxString path = MxString(MxOmni::GetHD()) + p_file; + path.MapPathToFilesystem(); + if (si.Read(path.GetData(), si::Interleaf::ObjectsOnly | si::Interleaf::NoInfo) != si::Interleaf::ERROR_SUCCESS) { + path = MxString(MxOmni::GetCD()) + p_file; + path.MapPathToFilesystem(); + if (si.Read(path.GetData(), si::Interleaf::ObjectsOnly | si::Interleaf::NoInfo) != + si::Interleaf::ERROR_SUCCESS) { + SDL_Log("Could not parse SI file %s", p_file); + return false; + } + } + + if (!(controller = OpenStream(p_file))) { + return false; + } + + ParseExtra(controller->GetAtom(), &si); + return true; +} + +bool SiLoader::LoadDirective(const char* p_directive) +{ + char originAtom[256], targetAtom[256]; + uint32_t originId, targetId; + + if (SDL_sscanf( + p_directive, + "StartWith:%255[^:;]%*[:;]%u%*[:;]%255[^:;]%*[:;]%u", + originAtom, + &originId, + targetAtom, + &targetId + ) == 4) { + startWith.emplace_back( + StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId}, + StreamObject{MxAtomId{targetAtom, e_lowerCase2}, targetId} + ); + } + else if (SDL_sscanf(p_directive, "RemoveWith:%255[^:;]%*[:;]%u%*[:;]%255[^:;]%*[:;]%u", originAtom, &originId, targetAtom, &targetId) == 4) { + removeWith.emplace_back( + StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId}, + StreamObject{MxAtomId{targetAtom, e_lowerCase2}, targetId} + ); + } + else if (SDL_sscanf(p_directive, "Replace:%255[^:;]%*[:;]%u%*[:;]%255[^:;]%*[:;]%u", originAtom, &originId, targetAtom, &targetId) == 4) { + replace.emplace_back( + StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId}, + StreamObject{MxAtomId{targetAtom, e_lowerCase2}, targetId} + ); + } + else if (SDL_sscanf(p_directive, "Prepend:%255[^:;]%*[:;]%u%*[:;]%255[^:;]%*[:;]%u", originAtom, &originId, targetAtom, &targetId) == 4) { + prepend.emplace_back( + StreamObject{MxAtomId{targetAtom, e_lowerCase2}, targetId}, + StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId} + ); + } + else if (SDL_sscanf(p_directive, "FullScreenMovie:%255[^:;]%*[:;]%u", originAtom, &originId) == 2) { + fullScreenMovie.emplace_back(StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId}); + } + else if (SDL_sscanf(p_directive, "Disable3d:%255[^:;]%*[:;]%u", originAtom, &originId) == 2) { + disable3d.emplace_back(StreamObject{MxAtomId{originAtom, e_lowerCase2}, originId}); + } + + return true; +} + +MxStreamController* SiLoader::OpenStream(const char* p_file) +{ + MxStreamController* controller; + + if (!(controller = Streamer()->Open(p_file, MxStreamer::e_diskStream))) { + SDL_Log("Could not load SI file %s", p_file); + return nullptr; + } + + return controller; +} + +void SiLoader::ParseExtra(const MxAtomId& p_atom, si::Core* p_core) +{ + for (si::Core* child : p_core->GetChildren()) { + if (si::Object* object = dynamic_cast(child)) { + if (object->type() != si::MxOb::Null) { + std::string extra(object->extra_.data(), object->extra_.size()); + const char* directive; + char atom[256]; + uint32_t id; + + if ((directive = SDL_strstr(extra.c_str(), "StartWith:"))) { + if (SDL_sscanf(directive, "StartWith:%255[^:;]%*[:;]%u", atom, &id) == 2) { + startWith.emplace_back( + StreamObject{MxAtomId{atom, e_lowerCase2}, id}, + StreamObject{p_atom, object->id_} + ); + } + } + + if ((directive = SDL_strstr(extra.c_str(), "RemoveWith:"))) { + if (SDL_sscanf(directive, "RemoveWith:%255[^:;]%*[:;]%u", atom, &id) == 2) { + removeWith.emplace_back( + StreamObject{MxAtomId{atom, e_lowerCase2}, id}, + StreamObject{p_atom, object->id_} + ); + } + } + + if ((directive = SDL_strstr(extra.c_str(), "Replace:"))) { + if (SDL_sscanf(directive, "Replace:%255[^:;]%*[:;]%u", atom, &id) == 2) { + replace.emplace_back( + StreamObject{MxAtomId{atom, e_lowerCase2}, id}, + StreamObject{p_atom, object->id_} + ); + } + } + + if ((directive = SDL_strstr(extra.c_str(), "Prepend:"))) { + if (SDL_sscanf(directive, "Prepend:%255[^:;]%*[:;]%u", atom, &id) == 2) { + prepend.emplace_back( + StreamObject{p_atom, object->id_}, + StreamObject{MxAtomId{atom, e_lowerCase2}, id} + ); + } + } + + if ((directive = SDL_strstr(extra.c_str(), "FullScreenMovie"))) { + fullScreenMovie.emplace_back(StreamObject{MxAtomId{atom, e_lowerCase2}, id}); + } + + if ((directive = SDL_strstr(extra.c_str(), "Disable3d"))) { + disable3d.emplace_back(StreamObject{MxAtomId{atom, e_lowerCase2}, id}); + } + } + } + + ParseExtra(p_atom, child); + } +} + +bool SiLoader::IsWorld(const StreamObject& p_object) +{ + // The convention in LEGO Island is that world objects are always at ID 0 + if (p_object.second == 0) { + for (int i = 0; i < LegoOmni::e_numWorlds; i++) { + if (p_object.first == *Lego()->GetWorldAtom((LegoOmni::World) i)) { + return true; + } + } + } + + return false; +} diff --git a/miniwin/CMakeLists.txt b/miniwin/CMakeLists.txt index 36f6f31c..625d5261 100644 --- a/miniwin/CMakeLists.txt +++ b/miniwin/CMakeLists.txt @@ -44,7 +44,19 @@ if(NOT (VITA OR WINDOWS_STORE)) message(STATUS "🧩 OpenGL 1.x support not enabled — needs OpenGL") endif() - find_library(OPENGL_ES3_LIBRARY NAMES GLESv2) + find_library(OPENGL_ES2_LIBRARY NAMES GLESv2) + if(EMSCRIPTEN OR OPENGL_ES2_LIBRARY) + message(STATUS "Found OpenGL: enabling OpenGL ES 2.x renderer") + target_sources(miniwin PRIVATE src/d3drm/backends/opengles2/renderer.cpp) + list(APPEND GRAPHICS_BACKENDS USE_OPENGLES2) + if(OPENGL_ES2_LIBRARY) + target_link_libraries(miniwin PRIVATE ${OPENGL_ES2_LIBRARY}) + endif() + else() + message(STATUS "🧩 OpenGL ES 2.x support not enabled") + endif() + + find_library(OPENGL_ES3_LIBRARY NAMES GLESv3 GLESv2) if(EMSCRIPTEN OR OPENGL_ES3_LIBRARY) message(STATUS "Found OpenGL: enabling OpenGL ES 3.x renderer") target_sources(miniwin PRIVATE src/d3drm/backends/opengles3/renderer.cpp) diff --git a/miniwin/src/d3drm/backends/opengles2/renderer.cpp b/miniwin/src/d3drm/backends/opengles2/renderer.cpp new file mode 100644 index 00000000..2b46190b --- /dev/null +++ b/miniwin/src/d3drm/backends/opengles2/renderer.cpp @@ -0,0 +1,898 @@ +#include "d3drmrenderer_opengles2.h" +#include "meshutils.h" + +#include +#include +#include +#include +#include + +static GLuint CompileShader(GLenum type, const char* source) +{ + GLuint shader = glCreateShader(type); + glShaderSource(shader, 1, &source, nullptr); + glCompileShader(shader); + GLint success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + GLint logLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + std::vector log(logLength); + glGetShaderInfoLog(shader, logLength, nullptr, log.data()); + SDL_Log("Shader compile error: %s", log.data()); + } + else { + SDL_Log("CompileShader (%s)", SDL_GetError()); + } + glDeleteShader(shader); + return 0; + } + return shader; +} + +struct SceneLightGLES2 { + float color[4]; + float position[4]; + float direction[4]; +}; + +Direct3DRMRenderer* OpenGLES2Renderer::Create(DWORD width, DWORD height, float anisotropic) +{ + // We have to reset the attributes here after having enumerated the + // OpenGL ES 2.0 renderer, or else SDL gets very confused by SDL_GL_DEPTH_SIZE + // call below when on an EGL-based backend, and crashes with EGL_BAD_MATCH. + SDL_GL_ResetAttributes(); + // But ResetAttributes resets it to 16. + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + + if (!DDWindow) { + SDL_Log("No window handler"); + return nullptr; + } + + SDL_GLContext context = SDL_GL_CreateContext(DDWindow); + if (!context) { + SDL_Log("SDL_GL_CreateContext: %s", SDL_GetError()); + return nullptr; + } + + if (!SDL_GL_MakeCurrent(DDWindow, context)) { + SDL_GL_DestroyContext(context); + return nullptr; + } + + glDepthFunc(GL_LEQUAL); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + glFrontFace(GL_CW); + + const char* vertexShaderSource = R"( + attribute vec3 a_position; + attribute vec3 a_normal; + attribute vec2 a_texCoord; + + uniform mat4 u_modelViewMatrix; + uniform mat3 u_normalMatrix; + uniform mat4 u_projectionMatrix; + + varying vec3 v_viewPos; + varying vec3 v_normal; + varying vec2 v_texCoord; + + void main() { + vec4 viewPos = u_modelViewMatrix * vec4(a_position, 1.0); + gl_Position = u_projectionMatrix * viewPos; + v_viewPos = viewPos.xyz; + v_normal = normalize(u_normalMatrix * a_normal); + v_texCoord = a_texCoord; + } + )"; + + const char* fragmentShaderSource = R"( + precision mediump float; + + struct SceneLight { + vec4 color; + vec4 position; + vec4 direction; + }; + + uniform SceneLight u_lights[3]; + uniform int u_lightCount; + + varying vec3 v_viewPos; + varying vec3 v_normal; + varying vec2 v_texCoord; + + uniform float u_shininess; + uniform vec4 u_color; + uniform int u_useTexture; + uniform sampler2D u_texture; + + void main() { + vec3 diffuse = vec3(0.0); + vec3 specular = vec3(0.0); + + for (int i = 0; i < 3; ++i) { + if (i >= u_lightCount) break; + + vec3 lightColor = u_lights[i].color.rgb; + + if (u_lights[i].position.w == 0.0 && u_lights[i].direction.w == 0.0) { + diffuse += lightColor; + continue; + } + + vec3 lightVec; + if (u_lights[i].direction.w == 1.0) { + lightVec = -normalize(u_lights[i].direction.xyz); + } + else { + lightVec = u_lights[i].position.xyz - v_viewPos; + } + lightVec = normalize(lightVec); + + float dotNL = max(dot(v_normal, lightVec), 0.0); + if (dotNL > 0.0) { + // Diffuse contribution + diffuse += dotNL * lightColor; + + // Specular + if (u_shininess > 0.0 && u_lights[i].direction.w == 1.0) { + vec3 viewVec = normalize(-v_viewPos); + vec3 H = normalize(lightVec + viewVec); + float dotNH = max(dot(v_normal, H), 0.0); + float spec = pow(dotNH, u_shininess); + specular += spec * lightColor; + } + } + } + + vec4 finalColor = u_color; + finalColor.rgb = clamp(diffuse * u_color.rgb + specular, 0.0, 1.0); + if (u_useTexture != 0) { + vec4 texel = texture2D(u_texture, v_texCoord); + finalColor.rgb = clamp(texel.rgb * finalColor.rgb, 0.0, 1.0); + finalColor.a = texel.a; + } + + gl_FragColor = finalColor; + } + )"; + + GLuint vs = CompileShader(GL_VERTEX_SHADER, vertexShaderSource); + GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource); + + GLuint shaderProgram = glCreateProgram(); + glAttachShader(shaderProgram, vs); + glAttachShader(shaderProgram, fs); + glBindAttribLocation(shaderProgram, 0, "a_position"); + glBindAttribLocation(shaderProgram, 1, "a_normal"); + glBindAttribLocation(shaderProgram, 2, "a_texCoord"); + glLinkProgram(shaderProgram); + glDeleteShader(vs); + glDeleteShader(fs); + + return new OpenGLES2Renderer(width, height, anisotropic, context, shaderProgram); +} + +GLES2MeshCacheEntry GLES2UploadMesh(const MeshGroup& meshGroup, bool forceUV = false) +{ + GLES2MeshCacheEntry cache{&meshGroup, meshGroup.version}; + + cache.flat = meshGroup.quality == D3DRMRENDER_FLAT || meshGroup.quality == D3DRMRENDER_UNLITFLAT; + + std::vector vertices; + if (cache.flat) { + FlattenSurfaces( + meshGroup.vertices.data(), + meshGroup.vertices.size(), + meshGroup.indices.data(), + meshGroup.indices.size(), + meshGroup.texture != nullptr || forceUV, + vertices, + cache.indices + ); + } + else { + vertices = meshGroup.vertices; + cache.indices.resize(meshGroup.indices.size()); + std::transform(meshGroup.indices.begin(), meshGroup.indices.end(), cache.indices.begin(), [](DWORD index) { + return static_cast(index); + }); + } + + std::vector texcoords; + if (meshGroup.texture || forceUV) { + texcoords.resize(vertices.size()); + std::transform(vertices.begin(), vertices.end(), texcoords.begin(), [](const D3DRMVERTEX& v) { + return v.texCoord; + }); + } + std::vector positions(vertices.size()); + std::transform(vertices.begin(), vertices.end(), positions.begin(), [](const D3DRMVERTEX& v) { + return v.position; + }); + std::vector normals(vertices.size()); + std::transform(vertices.begin(), vertices.end(), normals.begin(), [](const D3DRMVERTEX& v) { return v.normal; }); + + glGenBuffers(1, &cache.vboPositions); + glBindBuffer(GL_ARRAY_BUFFER, cache.vboPositions); + glBufferData(GL_ARRAY_BUFFER, positions.size() * sizeof(D3DVECTOR), positions.data(), GL_STATIC_DRAW); + + glGenBuffers(1, &cache.vboNormals); + glBindBuffer(GL_ARRAY_BUFFER, cache.vboNormals); + glBufferData(GL_ARRAY_BUFFER, normals.size() * sizeof(D3DVECTOR), normals.data(), GL_STATIC_DRAW); + + if (meshGroup.texture || forceUV) { + glGenBuffers(1, &cache.vboTexcoords); + glBindBuffer(GL_ARRAY_BUFFER, cache.vboTexcoords); + glBufferData(GL_ARRAY_BUFFER, texcoords.size() * sizeof(TexCoord), texcoords.data(), GL_STATIC_DRAW); + } + + glGenBuffers(1, &cache.ibo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache.ibo); + glBufferData( + GL_ELEMENT_ARRAY_BUFFER, + cache.indices.size() * sizeof(cache.indices[0]), + cache.indices.data(), + GL_STATIC_DRAW + ); + + return cache; +} + +bool OpenGLES2Renderer::UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI) +{ + SDL_Surface* surf = source; + if (source->format != SDL_PIXELFORMAT_RGBA32) { + surf = SDL_ConvertSurface(source, SDL_PIXELFORMAT_RGBA32); + if (!surf) { + return false; + } + } + + glGenTextures(1, &outTexId); + glBindTexture(GL_TEXTURE_2D, outTexId); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, surf->w, surf->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, surf->pixels); + + if (isUI) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + if (m_anisotropic > 1.0f) { + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, m_anisotropic); + } + glGenerateMipmap(GL_TEXTURE_2D); + } + + if (surf != source) { + SDL_DestroySurface(surf); + } + + return true; +} + +OpenGLES2Renderer::OpenGLES2Renderer( + DWORD width, + DWORD height, + float anisotropic, + SDL_GLContext context, + GLuint shaderProgram +) + : m_context(context), m_shaderProgram(shaderProgram), m_anisotropic(anisotropic) +{ + glGenFramebuffers(1, &m_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + + bool anisoAvailable = SDL_GL_ExtensionSupported("GL_EXT_texture_filter_anisotropic"); + GLfloat maxAniso = 0.0f; + if (anisoAvailable) { + glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &maxAniso); + } + if (m_anisotropic > maxAniso) { + m_anisotropic = maxAniso; + } + SDL_Log( + "Anisotropic is %s. Requested: %f, active: %f, max aniso: %f", + m_anisotropic > 1.0f ? "on" : "off", + anisotropic, + m_anisotropic, + maxAniso + ); + + m_virtualWidth = width; + m_virtualHeight = height; + ViewportTransform viewportTransform = {1.0f, 0.0f, 0.0f}; + Resize(width, height, viewportTransform); + + SDL_Surface* dummySurface = SDL_CreateSurface(1, 1, SDL_PIXELFORMAT_RGBA32); + if (!dummySurface) { + SDL_Log("Failed to create surface: %s", SDL_GetError()); + return; + } + if (!SDL_LockSurface(dummySurface)) { + SDL_Log("Failed to lock surface: %s", SDL_GetError()); + SDL_DestroySurface(dummySurface); + return; + } + ((Uint32*) dummySurface->pixels)[0] = 0xFFFFFFFF; + SDL_UnlockSurface(dummySurface); + + UploadTexture(dummySurface, m_dummyTexture, false); + if (!m_dummyTexture) { + SDL_DestroySurface(dummySurface); + SDL_Log("Failed to create surface: %s", SDL_GetError()); + return; + } + SDL_DestroySurface(dummySurface); + + m_posLoc = glGetAttribLocation(m_shaderProgram, "a_position"); + m_normLoc = glGetAttribLocation(m_shaderProgram, "a_normal"); + m_texLoc = glGetAttribLocation(m_shaderProgram, "a_texCoord"); + m_colorLoc = glGetUniformLocation(m_shaderProgram, "u_color"); + m_shinLoc = glGetUniformLocation(m_shaderProgram, "u_shininess"); + m_lightCountLoc = glGetUniformLocation(m_shaderProgram, "u_lightCount"); + m_useTextureLoc = glGetUniformLocation(m_shaderProgram, "u_useTexture"); + m_textureLoc = glGetUniformLocation(m_shaderProgram, "u_texture"); + for (int i = 0; i < 3; ++i) { + std::string base = "u_lights[" + std::to_string(i) + "]"; + u_lightLocs[i][0] = glGetUniformLocation(m_shaderProgram, (base + ".color").c_str()); + u_lightLocs[i][1] = glGetUniformLocation(m_shaderProgram, (base + ".position").c_str()); + u_lightLocs[i][2] = glGetUniformLocation(m_shaderProgram, (base + ".direction").c_str()); + } + m_modelViewMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_modelViewMatrix"); + m_normalMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_normalMatrix"); + m_projectionMatrixLoc = glGetUniformLocation(m_shaderProgram, "u_projectionMatrix"); + + m_uiMesh.vertices = { + {{0.0f, 0.0f, 0.0f}, {0, 0, -1}, {0.0f, 0.0f}}, + {{1.0f, 0.0f, 0.0f}, {0, 0, -1}, {1.0f, 0.0f}}, + {{1.0f, 1.0f, 0.0f}, {0, 0, -1}, {1.0f, 1.0f}}, + {{0.0f, 1.0f, 0.0f}, {0, 0, -1}, {0.0f, 1.0f}} + }; + m_uiMesh.indices = {0, 1, 2, 0, 2, 3}; + m_uiMeshCache = GLES2UploadMesh(m_uiMesh, true); + + glUseProgram(m_shaderProgram); +} + +OpenGLES2Renderer::~OpenGLES2Renderer() +{ + SDL_DestroySurface(m_renderedImage); + glDeleteTextures(1, &m_dummyTexture); + glDeleteProgram(m_shaderProgram); + glDeleteTextures(1, &m_colorTarget); + glDeleteRenderbuffers(1, &m_depthTarget); + glDeleteFramebuffers(1, &m_fbo); + SDL_GL_DestroyContext(m_context); +} + +void OpenGLES2Renderer::PushLights(const SceneLight* lightsArray, size_t count) +{ + if (count > 3) { + SDL_Log("Unsupported number of lights (%d)", static_cast(count)); + count = 3; + } + + m_lights.assign(lightsArray, lightsArray + count); +} + +void OpenGLES2Renderer::SetFrustumPlanes(const Plane* frustumPlanes) +{ +} + +void OpenGLES2Renderer::SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) +{ + memcpy(&m_projection, projection, sizeof(D3DRMMATRIX4D)); +} + +struct TextureDestroyContextGLS2 { + OpenGLES2Renderer* renderer; + Uint32 textureId; +}; + +void OpenGLES2Renderer::AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture) +{ + auto* ctx = new TextureDestroyContextGLS2{this, id}; + texture->AddDestroyCallback( + [](IDirect3DRMObject* obj, void* arg) { + auto* ctx = static_cast(arg); + auto& cache = ctx->renderer->m_textures[ctx->textureId]; + if (cache.glTextureId != 0) { + glDeleteTextures(1, &cache.glTextureId); + cache.glTextureId = 0; + cache.texture = nullptr; + } + delete ctx; + }, + ctx + ); +} + +Uint32 OpenGLES2Renderer::GetTextureId(IDirect3DRMTexture* iTexture, bool isUI, float scaleX, float scaleY) +{ + SDL_GL_MakeCurrent(DDWindow, m_context); + auto texture = static_cast(iTexture); + auto surface = static_cast(texture->m_surface); + + for (Uint32 i = 0; i < m_textures.size(); ++i) { + auto& tex = m_textures[i]; + if (tex.texture == texture) { + if (tex.version != texture->m_version) { + glDeleteTextures(1, &tex.glTextureId); + if (UploadTexture(surface->m_surface, tex.glTextureId, isUI)) { + tex.version = texture->m_version; + } + } + return i; + } + } + + GLuint texId; + if (!UploadTexture(surface->m_surface, texId, isUI)) { + return NO_TEXTURE_ID; + } + + for (Uint32 i = 0; i < m_textures.size(); ++i) { + auto& tex = m_textures[i]; + if (!tex.texture) { + tex.texture = texture; + tex.version = texture->m_version; + tex.glTextureId = texId; + tex.width = surface->m_surface->w; + tex.height = surface->m_surface->h; + AddTextureDestroyCallback(i, texture); + return i; + } + } + + m_textures.push_back( + {texture, texture->m_version, texId, (uint16_t) surface->m_surface->w, (uint16_t) surface->m_surface->h} + ); + AddTextureDestroyCallback((Uint32) (m_textures.size() - 1), texture); + return (Uint32) (m_textures.size() - 1); +} + +struct GLES2MeshDestroyContext { + OpenGLES2Renderer* renderer; + Uint32 id; +}; + +void OpenGLES2Renderer::AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh) +{ + auto* ctx = new GLES2MeshDestroyContext{this, id}; + mesh->AddDestroyCallback( + [](IDirect3DRMObject*, void* arg) { + auto* ctx = static_cast(arg); + auto& cache = ctx->renderer->m_meshs[ctx->id]; + cache.meshGroup = nullptr; + glDeleteBuffers(1, &cache.vboPositions); + glDeleteBuffers(1, &cache.vboNormals); + glDeleteBuffers(1, &cache.vboTexcoords); + glDeleteBuffers(1, &cache.ibo); + delete ctx; + }, + ctx + ); +} + +Uint32 OpenGLES2Renderer::GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) +{ + for (Uint32 i = 0; i < m_meshs.size(); ++i) { + auto& cache = m_meshs[i]; + if (cache.meshGroup == meshGroup) { + if (cache.version != meshGroup->version) { + cache = std::move(GLES2UploadMesh(*meshGroup)); + } + return i; + } + } + + auto newCache = GLES2UploadMesh(*meshGroup); + + for (Uint32 i = 0; i < m_meshs.size(); ++i) { + auto& cache = m_meshs[i]; + if (!cache.meshGroup) { + cache = std::move(newCache); + AddMeshDestroyCallback(i, mesh); + return i; + } + } + + m_meshs.push_back(std::move(newCache)); + AddMeshDestroyCallback((Uint32) (m_meshs.size() - 1), mesh); + return (Uint32) (m_meshs.size() - 1); +} + +HRESULT OpenGLES2Renderer::BeginFrame() +{ + SDL_GL_MakeCurrent(DDWindow, m_context); + m_dirty = true; + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + + glEnable(GL_CULL_FACE); + glDisable(GL_BLEND); + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + + SceneLightGLES2 lightData[3]; + int lightCount = std::min(static_cast(m_lights.size()), 3); + + for (int i = 0; i < lightCount; ++i) { + const auto& src = m_lights[i]; + lightData[i].color[0] = src.color.r; + lightData[i].color[1] = src.color.g; + lightData[i].color[2] = src.color.b; + lightData[i].color[3] = src.color.a; + + lightData[i].position[0] = src.position.x; + lightData[i].position[1] = src.position.y; + lightData[i].position[2] = src.position.z; + lightData[i].position[3] = src.positional; + + lightData[i].direction[0] = src.direction.x; + lightData[i].direction[1] = src.direction.y; + lightData[i].direction[2] = src.direction.z; + lightData[i].direction[3] = src.directional; + } + + for (int i = 0; i < lightCount; ++i) { + glUniform4fv(u_lightLocs[i][0], 1, lightData[i].color); + glUniform4fv(u_lightLocs[i][1], 1, lightData[i].position); + glUniform4fv(u_lightLocs[i][2], 1, lightData[i].direction); + } + glUniform1i(m_lightCountLoc, lightCount); + return DD_OK; +} + +void OpenGLES2Renderer::EnableTransparency() +{ + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDepthMask(GL_FALSE); +} + +void OpenGLES2Renderer::SubmitDraw( + DWORD meshId, + const D3DRMMATRIX4D& modelViewMatrix, + const D3DRMMATRIX4D& worldMatrix, + const D3DRMMATRIX4D& viewMatrix, + const Matrix3x3& normalMatrix, + const Appearance& appearance +) +{ + auto& mesh = m_meshs[meshId]; + + glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelViewMatrix[0][0]); + glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &normalMatrix[0][0]); + glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &m_projection[0][0]); + + glUniform4f( + m_colorLoc, + appearance.color.r / 255.0f, + appearance.color.g / 255.0f, + appearance.color.b / 255.0f, + appearance.color.a / 255.0f + ); + + glUniform1f(m_shinLoc, appearance.shininess); + + if (appearance.textureId != NO_TEXTURE_ID) { + glUniform1i(m_useTextureLoc, 1); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_textures[appearance.textureId].glTextureId); + glUniform1i(m_textureLoc, 0); + } + else { + glUniform1i(m_useTextureLoc, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_dummyTexture); + glUniform1i(m_textureLoc, 0); + } + + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboPositions); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboNormals); + glEnableVertexAttribArray(m_normLoc); + glVertexAttribPointer(m_normLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + if (appearance.textureId != NO_TEXTURE_ID) { + glBindBuffer(GL_ARRAY_BUFFER, mesh.vboTexcoords); + glEnableVertexAttribArray(m_texLoc); + glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + } + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh.ibo); + glDrawElements(GL_TRIANGLES, static_cast(mesh.indices.size()), GL_UNSIGNED_SHORT, nullptr); + + glDisableVertexAttribArray(m_normLoc); + glDisableVertexAttribArray(m_texLoc); +} + +HRESULT OpenGLES2Renderer::FinalizeFrame() +{ + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + + return DD_OK; +} + +void OpenGLES2Renderer::Resize(int width, int height, const ViewportTransform& viewportTransform) +{ + SDL_GL_MakeCurrent(DDWindow, m_context); + m_width = width; + m_height = height; + m_viewportTransform = viewportTransform; + if (m_renderedImage) { + SDL_DestroySurface(m_renderedImage); + } + m_renderedImage = SDL_CreateSurface(m_width, m_height, SDL_PIXELFORMAT_RGBA32); + + if (m_colorTarget) { + glDeleteTextures(1, &m_colorTarget); + m_colorTarget = 0; + } + if (m_depthTarget) { + glDeleteRenderbuffers(1, &m_depthTarget); + m_depthTarget = 0; + } + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + + // Create color texture + glGenTextures(1, &m_colorTarget); + glBindTexture(GL_TEXTURE_2D, m_colorTarget); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_colorTarget, 0); + + // Create depth renderbuffer + glGenRenderbuffers(1, &m_depthTarget); + glBindRenderbuffer(GL_RENDERBUFFER, m_depthTarget); + + if (SDL_GL_ExtensionSupported("GL_OES_depth24")) { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24_OES, width, height); + } + else if (SDL_GL_ExtensionSupported("GL_OES_depth32")) { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32_OES, width, height); + } + else { + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); + } + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_depthTarget); + GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER); + if (status != GL_FRAMEBUFFER_COMPLETE) { + SDL_Log("FBO incomplete: 0x%X", status); + } + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + + glViewport(0, 0, m_width, m_height); +} + +void OpenGLES2Renderer::Clear(float r, float g, float b) +{ + SDL_GL_MakeCurrent(DDWindow, m_context); + m_dirty = true; + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glClearColor(r, g, b, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +void OpenGLES2Renderer::Flip() +{ + SDL_GL_MakeCurrent(DDWindow, m_context); + if (!m_dirty) { + return; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glDisable(GL_DEPTH_TEST); + glFrontFace(GL_CCW); + glDepthMask(GL_FALSE); + + glUniform4f(m_colorLoc, 1.0f, 1.0f, 1.0f, 1.0f); + glUniform1f(m_shinLoc, 0.0f); + + float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; + float blank[] = {0.0f, 0.0f, 0.0f, 0.0f}; + glUniform4fv(u_lightLocs[0][0], 1, ambient); + glUniform4fv(u_lightLocs[0][1], 1, blank); + glUniform4fv(u_lightLocs[0][2], 1, blank); + glUniform1i(m_lightCountLoc, 1); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_colorTarget); + glUniform1i(m_textureLoc, 0); + glUniform1i(m_useTextureLoc, 1); + + D3DRMMATRIX4D projection; + D3DRMMATRIX4D modelViewMatrix = { + {(float) m_width, 0.0f, 0.0f, 0.0f}, + {0.0f, (float) -m_height, 0.0f, 0.0f}, + {0.0f, 0.0f, 1.0f, 0.0f}, + {0.0f, (float) m_height, 0.0f, 1.0f} + }; + glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelViewMatrix[0][0]); + Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}; + glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]); + CreateOrthographicProjection((float) m_width, (float) m_height, projection); + glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &projection[0][0]); + + glDisable(GL_SCISSOR_TEST); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); + glEnableVertexAttribArray(m_texLoc); + glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); + glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); + + glDisableVertexAttribArray(m_texLoc); + + SDL_GL_SwapWindow(DDWindow); + glFrontFace(GL_CW); + m_dirty = false; +} + +void OpenGLES2Renderer::Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) +{ + SDL_GL_MakeCurrent(DDWindow, m_context); + m_dirty = true; + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + + glDisable(GL_DEPTH_TEST); + glDepthMask(GL_FALSE); + + float ambient[] = {1.0f, 1.0f, 1.0f, 1.0f}; + float blank[] = {0.0f, 0.0f, 0.0f, 0.0f}; + glUniform4fv(u_lightLocs[0][0], 1, ambient); + glUniform4fv(u_lightLocs[0][1], 1, blank); + glUniform4fv(u_lightLocs[0][2], 1, blank); + glUniform1i(m_lightCountLoc, 1); + + glUniform4f(m_colorLoc, color.r, color.g, color.b, color.a); + glUniform1f(m_shinLoc, 0.0f); + + SDL_Rect expandedDstRect; + if (textureId != NO_TEXTURE_ID) { + const GLES2TextureCacheEntry& texture = m_textures[textureId]; + float scaleX = static_cast(dstRect.w) / srcRect.w; + float scaleY = static_cast(dstRect.h) / srcRect.h; + expandedDstRect = { + static_cast(std::round(dstRect.x - srcRect.x * scaleX)), + static_cast(std::round(dstRect.y - srcRect.y * scaleY)), + static_cast(std::round(texture.width * scaleX)), + static_cast(std::round(texture.height * scaleY)) + }; + + glUniform1i(m_useTextureLoc, 1); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture.glTextureId); + glUniform1i(m_textureLoc, 0); + } + else { + expandedDstRect = dstRect; + glUniform1i(m_useTextureLoc, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, m_dummyTexture); + glUniform1i(m_textureLoc, 0); + } + + D3DRMMATRIX4D modelView, projection; + Create2DTransformMatrix( + expandedDstRect, + m_viewportTransform.scale, + m_viewportTransform.offsetX, + m_viewportTransform.offsetY, + modelView + ); + + glUniformMatrix4fv(m_modelViewMatrixLoc, 1, GL_FALSE, &modelView[0][0]); + Matrix3x3 identity = {{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}; + glUniformMatrix3fv(m_normalMatrixLoc, 1, GL_FALSE, &identity[0][0]); + CreateOrthographicProjection((float) m_width, (float) m_height, projection); + glUniformMatrix4fv(m_projectionMatrixLoc, 1, GL_FALSE, &projection[0][0]); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glEnable(GL_SCISSOR_TEST); + glScissor( + static_cast(std::round(dstRect.x * m_viewportTransform.scale + m_viewportTransform.offsetX)), + m_height - static_cast( + std::round((dstRect.y + dstRect.h) * m_viewportTransform.scale + m_viewportTransform.offsetY) + ), + static_cast(std::round(dstRect.w * m_viewportTransform.scale)), + static_cast(std::round(dstRect.h * m_viewportTransform.scale)) + ); + + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboPositions); + glEnableVertexAttribArray(m_posLoc); + glVertexAttribPointer(m_posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ARRAY_BUFFER, m_uiMeshCache.vboTexcoords); + glEnableVertexAttribArray(m_texLoc); + glVertexAttribPointer(m_texLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_uiMeshCache.ibo); + glDrawElements(GL_TRIANGLES, static_cast(m_uiMeshCache.indices.size()), GL_UNSIGNED_SHORT, nullptr); + + glDisableVertexAttribArray(m_texLoc); + glDisable(GL_SCISSOR_TEST); +} + +void OpenGLES2Renderer::Download(SDL_Surface* target) +{ + glFinish(); + + glBindFramebuffer(GL_FRAMEBUFFER, m_fbo); + glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, m_renderedImage->pixels); + + SDL_Rect srcRect = { + static_cast(m_viewportTransform.offsetX), + static_cast(m_viewportTransform.offsetY), + static_cast(target->w * m_viewportTransform.scale), + static_cast(target->h * m_viewportTransform.scale), + }; + + SDL_Surface* bufferClone = SDL_CreateSurface(target->w, target->h, SDL_PIXELFORMAT_RGBA32); + if (!bufferClone) { + SDL_Log("SDL_CreateSurface: %s", SDL_GetError()); + return; + } + + SDL_BlitSurfaceScaled(m_renderedImage, &srcRect, bufferClone, nullptr, SDL_SCALEMODE_NEAREST); + + // Flip image vertically into target + SDL_Rect rowSrc = {0, 0, bufferClone->w, 1}; + SDL_Rect rowDst = {0, 0, bufferClone->w, 1}; + for (int y = 0; y < bufferClone->h; ++y) { + rowSrc.y = y; + rowDst.y = bufferClone->h - 1 - y; + SDL_BlitSurface(bufferClone, &rowSrc, target, &rowDst); + } + + SDL_DestroySurface(bufferClone); +} + +void OpenGLES2Renderer::SetDither(bool dither) +{ + if (dither) { + glEnable(GL_DITHER); + } + else { + glDisable(GL_DITHER); + } +} diff --git a/miniwin/src/d3drm/d3drmrenderer.cpp b/miniwin/src/d3drm/d3drmrenderer.cpp index e185c7dc..b30c5b23 100644 --- a/miniwin/src/d3drm/d3drmrenderer.cpp +++ b/miniwin/src/d3drm/d3drmrenderer.cpp @@ -2,6 +2,9 @@ #ifdef USE_OPENGL1 #include "d3drmrenderer_opengl1.h" #endif +#ifdef USE_OPENGLES2 +#include "d3drmrenderer_opengles2.h" +#endif #ifdef USE_OPENGLES3 #include "d3drmrenderer_opengles3.h" #endif @@ -47,6 +50,11 @@ Direct3DRMRenderer* CreateDirect3DRMRenderer( ); } #endif +#ifdef USE_OPENGLES2 + if (SDL_memcmp(guid, &OpenGLES2_GUID, sizeof(GUID)) == 0) { + return OpenGLES2Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetAnisotropic()); + } +#endif #ifdef USE_OPENGL1 if (SDL_memcmp(guid, &OpenGL1_GUID, sizeof(GUID)) == 0) { return OpenGL1Renderer::Create(DDSDesc.dwWidth, DDSDesc.dwHeight, d3d->GetMSAASamples()); @@ -78,6 +86,9 @@ void Direct3DRMRenderer_EnumDevices(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICE #ifdef USE_OPENGLES3 OpenGLES3Renderer_EnumDevice(d3d, cb, ctx); #endif +#ifdef USE_OPENGLES2 + OpenGLES2Renderer_EnumDevice(d3d, cb, ctx); +#endif #ifdef USE_OPENGL1 OpenGL1Renderer_EnumDevice(d3d, cb, ctx); #endif diff --git a/miniwin/src/internal/d3drmrenderer_opengles2.h b/miniwin/src/internal/d3drmrenderer_opengles2.h new file mode 100644 index 00000000..6a95d73f --- /dev/null +++ b/miniwin/src/internal/d3drmrenderer_opengles2.h @@ -0,0 +1,128 @@ +#pragma once + +#include "d3drmrenderer.h" +#include "d3drmtexture_impl.h" +#include "ddraw_impl.h" + +#include +#include +#include + +DEFINE_GUID(OpenGLES2_GUID, 0x682656F3, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06); + +struct GLES2TextureCacheEntry { + IDirect3DRMTexture* texture; + Uint32 version; + GLuint glTextureId; + uint16_t width; + uint16_t height; +}; + +struct GLES2MeshCacheEntry { + const MeshGroup* meshGroup; + int version; + bool flat; + + std::vector indices; + GLuint vboPositions; + GLuint vboNormals; + GLuint vboTexcoords; + GLuint ibo; +}; + +class OpenGLES2Renderer : public Direct3DRMRenderer { +public: + static Direct3DRMRenderer* Create(DWORD width, DWORD height, float anisotropic); + OpenGLES2Renderer(DWORD width, DWORD height, float anisotropic, SDL_GLContext context, GLuint shaderProgram); + ~OpenGLES2Renderer() override; + + void PushLights(const SceneLight* lightsArray, size_t count) override; + void SetProjection(const D3DRMMATRIX4D& projection, D3DVALUE front, D3DVALUE back) override; + void SetFrustumPlanes(const Plane* frustumPlanes) override; + Uint32 GetTextureId(IDirect3DRMTexture* texture, bool isUI, float scaleX, float scaleY) override; + Uint32 GetMeshId(IDirect3DRMMesh* mesh, const MeshGroup* meshGroup) override; + HRESULT BeginFrame() override; + void EnableTransparency() override; + void SubmitDraw( + DWORD meshId, + const D3DRMMATRIX4D& modelViewMatrix, + const D3DRMMATRIX4D& worldMatrix, + const D3DRMMATRIX4D& viewMatrix, + const Matrix3x3& normalMatrix, + const Appearance& appearance + ) override; + HRESULT FinalizeFrame() override; + void Resize(int width, int height, const ViewportTransform& viewportTransform) override; + void Clear(float r, float g, float b) override; + void Flip() override; + void Draw2DImage(Uint32 textureId, const SDL_Rect& srcRect, const SDL_Rect& dstRect, FColor color) override; + void Download(SDL_Surface* target) override; + void SetDither(bool dither) override; + +private: + void AddTextureDestroyCallback(Uint32 id, IDirect3DRMTexture* texture); + void AddMeshDestroyCallback(Uint32 id, IDirect3DRMMesh* mesh); + bool UploadTexture(SDL_Surface* source, GLuint& outTexId, bool isUI); + + MeshGroup m_uiMesh; + GLES2MeshCacheEntry m_uiMeshCache; + std::vector m_textures; + std::vector m_meshs; + D3DRMMATRIX4D m_projection; + SDL_Surface* m_renderedImage = nullptr; + bool m_dirty = false; + std::vector m_lights; + SDL_GLContext m_context; + float m_anisotropic; + GLuint m_fbo; + GLuint m_colorTarget; + GLuint m_depthTarget; + GLuint m_shaderProgram; + GLuint m_dummyTexture; + GLint m_posLoc; + GLint m_normLoc; + GLint m_texLoc; + GLint m_colorLoc; + GLint m_shinLoc; + GLint m_lightCountLoc; + GLint m_useTextureLoc; + GLint m_textureLoc; + GLint u_lightLocs[3][3]; + GLint m_modelViewMatrixLoc; + GLint m_normalMatrixLoc; + GLint m_projectionMatrixLoc; + ViewportTransform m_viewportTransform; +}; + +inline static void OpenGLES2Renderer_EnumDevice(const IDirect3DMiniwin* d3d, LPD3DENUMDEVICESCALLBACK cb, void* ctx) +{ + Direct3DRMRenderer* device = OpenGLES2Renderer::Create(640, 480, d3d->GetAnisotropic()); + if (!device) { + return; + } + + D3DDEVICEDESC halDesc = {}; + halDesc.dcmColorModel = D3DCOLOR_RGB; + halDesc.dwFlags = D3DDD_DEVICEZBUFFERBITDEPTH; + halDesc.dwDeviceZBufferBitDepth = DDBD_16; + halDesc.dwDeviceRenderBitDepth = DDBD_32; + halDesc.dpcTriCaps.dwTextureCaps = D3DPTEXTURECAPS_PERSPECTIVE; + halDesc.dpcTriCaps.dwShadeCaps = D3DPSHADECAPS_ALPHAFLATBLEND; + halDesc.dpcTriCaps.dwTextureFilterCaps = D3DPTFILTERCAPS_LINEAR; + + const char* extensions = (const char*) glGetString(GL_EXTENSIONS); + if (extensions) { + if (strstr(extensions, "GL_OES_depth24")) { + halDesc.dwDeviceZBufferBitDepth |= DDBD_24; + } + if (strstr(extensions, "GL_OES_depth32")) { + halDesc.dwDeviceZBufferBitDepth |= DDBD_32; + } + } + + delete device; + + D3DDEVICEDESC helDesc = {}; + + EnumDevice(cb, ctx, "OpenGL ES 2.0 HAL", &halDesc, &helDesc, OpenGLES2_GUID); +} diff --git a/packaging/ios/CMakeLists.txt b/packaging/ios/CMakeLists.txt index 418ad518..834b75d6 100644 --- a/packaging/ios/CMakeLists.txt +++ b/packaging/ios/CMakeLists.txt @@ -15,7 +15,7 @@ if(ISLE_BUILD_APP) "${CMAKE_CURRENT_BINARY_DIR}/isle/Info.plist" @ONLY ) - set(RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/isle/LaunchScreen.storyboard" "${CMAKE_CURRENT_SOURCE_DIR}/isle/Assets.xcassets") + set(RESOURCE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/isle/Assets.xcassets") target_sources(${ISLE_TARGET_NAME} PRIVATE ${RESOURCE_FILES}) set_source_files_properties(${RESOURCE_FILES} TARGET_DIRECTORY isle diff --git a/packaging/ios/isle/Info.plist.in b/packaging/ios/isle/Info.plist.in index 714324a3..c5d5a80b 100644 --- a/packaging/ios/isle/Info.plist.in +++ b/packaging/ios/isle/Info.plist.in @@ -27,7 +27,7 @@ CFBundleVersion @MACOSX_BUNDLE_BUNDLE_VERSION@ UILaunchStoryboardName - LaunchScreen + NSHighResolutionCapable CSResourcesFileMapped diff --git a/packaging/ios/isle/LaunchScreen.storyboard b/packaging/ios/isle/LaunchScreen.storyboard deleted file mode 100644 index ad167a8a..00000000 --- a/packaging/ios/isle/LaunchScreen.storyboard +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..9bee1096 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +includeBuild('android-project') diff --git a/tools/ncc/skip.yml b/tools/ncc/skip.yml index 322cded0..e18250e9 100644 --- a/tools/ncc/skip.yml +++ b/tools/ncc/skip.yml @@ -77,4 +77,5 @@ fccType: "Re-defined Windows name" SDL_KeyboardID_v: "SDL-based name" SDL_MouseID_v: "SDL-based name" SDL_JoystickID_v: "SDL-based name" -SDL_TouchID_v: "SDL-based name" \ No newline at end of file +SDL_TouchID_v: "SDL-based name" +Load: "Not a variable but function name" \ No newline at end of file