diff --git a/CMakeLists.txt b/CMakeLists.txt index c1db3f7f..4342352f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -529,6 +529,9 @@ if (ISLE_BUILD_APP) # Allow unconditional include of miniwin/miniwindevice.h target_link_libraries(isle PRIVATE miniwin-headers) + # Vector math + target_link_libraries(isle PRIVATE Vec::Vec) + # Link DSOUND and WINMM if (WIN32) target_link_libraries(isle PRIVATE winmm) @@ -788,7 +791,7 @@ if(NINTENDO_3DS) DESCRIPTION "LEGO Island for the Nintendo 3DS" AUTHOR "isledecomp/isle-portable" VERSION "${PROJECT_VERSION}" - ICON "ISLE/res/3ds/icon.png" + ICON "${CMAKE_SOURCE_DIR}/packaging/3ds/icon.png" ) ctr_create_3dsx(isle SMDH isle.smdh) @@ -796,10 +799,10 @@ if(NINTENDO_3DS) add_custom_command( OUTPUT "isle.bnr" COMMAND "${BANNERTOOL}" makebanner - -i "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.png" - -a "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.wav" + -i "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.png" + -a "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.wav" -o "isle.bnr" - DEPENDS "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.png" "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/banner.wav" + DEPENDS "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.png" "${CMAKE_SOURCE_DIR}/packaging/3ds/banner.wav" VERBATIM ) @@ -809,14 +812,14 @@ if(NINTENDO_3DS) -f cia -exefslogo -o "isle.cia" - -rsf "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/template.rsf" + -rsf "${CMAKE_SOURCE_DIR}/packaging/3ds/template.rsf" -major "${CMAKE_PROJECT_VERSION_MAJOR}" -minor "${CMAKE_PROJECT_VERSION_MINOR}" -micro 0 -icon "isle.smdh" -banner "isle.bnr" -elf "isle.elf" - DEPENDS "${CMAKE_SOURCE_DIR}/ISLE/res/3ds/template.rsf" "isle.smdh" "isle.bnr" + DEPENDS "${CMAKE_SOURCE_DIR}/packaging/3ds/template.rsf" "isle.smdh" "isle.bnr" COMMENT "Building CIA executable target isle.cia" VERBATIM ) diff --git a/CONFIG/qt/MainDlg.cpp b/CONFIG/qt/MainDlg.cpp index 828395bd..dfba47ea 100644 --- a/CONFIG/qt/MainDlg.cpp +++ b/CONFIG/qt/MainDlg.cpp @@ -57,8 +57,10 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->devicesList, &QListWidget::currentRowChanged, this, &CMainDialog::OnList3DevicesSelectionChanged); connect(m_ui->musicCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxMusic); connect(m_ui->sound3DCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckbox3DSound); - connect(m_ui->joystickCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxJoystick); connect(m_ui->fullscreenCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxFullscreen); + connect(m_ui->rumbleCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxRumble); + connect(m_ui->textureCheckBox, &QCheckBox::toggled, this, &CMainDialog::OnCheckboxTexture); + connect(m_ui->touchComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TouchControlsChanged); connect(m_ui->transitionTypeComboBox, &QComboBox::currentIndexChanged, this, &CMainDialog::TransitionTypeChanged); connect(m_ui->okButton, &QPushButton::clicked, this, &CMainDialog::accept); connect(m_ui->cancelButton, &QPushButton::clicked, this, &CMainDialog::reject); @@ -70,8 +72,13 @@ CMainDialog::CMainDialog(QWidget* pParent) : QDialog(pParent) connect(m_ui->dataPath, &QLineEdit::editingFinished, this, &CMainDialog::DataPathEdited); connect(m_ui->savePath, &QLineEdit::editingFinished, this, &CMainDialog::SavePathEdited); + connect(m_ui->texturePathOpen, &QPushButton::clicked, this, &CMainDialog::SelectTexturePathDialog); + connect(m_ui->texturePath, &QLineEdit::editingFinished, this, &CMainDialog::TexturePathEdited); + 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); + connect(m_ui->maxActorsSlider, &QSlider::sliderMoved, this, &CMainDialog::MaxActorsChanged); layout()->setSizeConstraint(QLayout::SetFixedSize); } @@ -115,7 +122,9 @@ bool CMainDialog::OnInitDialog() m_ui->devicesList->setCurrentRow(selected); m_ui->maxLoDSlider->setValue((int) currentConfigApp->m_max_lod * 10); + m_ui->LoDNum->setNum((int) currentConfigApp->m_max_lod * 10); m_ui->maxActorsSlider->setValue(currentConfigApp->m_max_actors); + m_ui->maxActorsNum->setNum(currentConfigApp->m_max_actors); UpdateInterface(); return true; } @@ -210,12 +219,18 @@ void CMainDialog::UpdateInterface() else { m_ui->textureQualityHighRadioButton->setChecked(true); } - m_ui->joystickCheckBox->setChecked(currentConfigApp->m_use_joystick); m_ui->musicCheckBox->setChecked(currentConfigApp->m_music); m_ui->fullscreenCheckBox->setChecked(currentConfigApp->m_full_screen); + m_ui->rumbleCheckBox->setChecked(currentConfigApp->m_haptic); + m_ui->touchComboBox->setCurrentIndex(currentConfigApp->m_touch_scheme); m_ui->transitionTypeComboBox->setCurrentIndex(currentConfigApp->m_transition_type); m_ui->dataPath->setText(QString::fromStdString(currentConfigApp->m_cd_path)); 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)); + + m_ui->texturePath->setEnabled(currentConfigApp->m_texture_load); + m_ui->texturePathOpen->setEnabled(currentConfigApp->m_texture_load); } // FUNCTION: CONFIG 0x004045e0 @@ -275,14 +290,6 @@ void CMainDialog::OnRadiobuttonTextureHighQuality(bool checked) } } -// FUNCTION: CONFIG 0x00404790 -void CMainDialog::OnCheckboxJoystick(bool checked) -{ - currentConfigApp->m_use_joystick = checked; - m_modified = true; - UpdateInterface(); -} - // FUNCTION: CONFIG 0x004048c0 void CMainDialog::OnCheckboxMusic(bool checked) { @@ -298,6 +305,27 @@ void CMainDialog::OnCheckboxFullscreen(bool checked) UpdateInterface(); } +void CMainDialog::OnCheckboxRumble(bool checked) +{ + currentConfigApp->m_haptic = checked; + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::OnCheckboxTexture(bool checked) +{ + currentConfigApp->m_texture_load = checked; + m_modified = true; + UpdateInterface(); +} + +void CMainDialog::TouchControlsChanged(int index) +{ + currentConfigApp->m_touch_scheme = index; + m_modified = true; + UpdateInterface(); +} + void CMainDialog::TransitionTypeChanged(int index) { currentConfigApp->m_transition_type = index; @@ -376,11 +404,43 @@ void CMainDialog::SavePathEdited() void CMainDialog::MaxLoDChanged(int value) { currentConfigApp->m_max_lod = static_cast(value) / 10.0f; + m_ui->LoDNum->setNum(value); m_modified = true; } void CMainDialog::MaxActorsChanged(int value) { currentConfigApp->m_max_actors = value; + m_ui->maxActorsNum->setNum(value); m_modified = true; } + +void CMainDialog::SelectTexturePathDialog() +{ + QString texture_path = QString::fromStdString(currentConfigApp->m_texture_path); + texture_path = QFileDialog::getExistingDirectory( + this, + tr("Open Directory"), + texture_path, + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks + ); + + QDir texture_dir = QDir(texture_path); + + if (texture_dir.exists()) { + currentConfigApp->m_texture_path = texture_dir.absolutePath().toStdString(); + m_modified = true; + } + UpdateInterface(); +} + +void CMainDialog::TexturePathEdited() +{ + QDir texture_dir = QDir(m_ui->texturePath->text()); + + if (texture_dir.exists()) { + currentConfigApp->m_texture_path = texture_dir.absolutePath().toStdString(); + m_modified = true; + } + UpdateInterface(); +} diff --git a/CONFIG/qt/MainDlg.h b/CONFIG/qt/MainDlg.h index 72062486..40156790 100644 --- a/CONFIG/qt/MainDlg.h +++ b/CONFIG/qt/MainDlg.h @@ -40,9 +40,11 @@ private slots: void OnRadiobuttonModelHighQuality(bool checked); void OnRadiobuttonTextureLowQuality(bool checked); void OnRadiobuttonTextureHighQuality(bool checked); - void OnCheckboxJoystick(bool checked); void OnCheckboxMusic(bool checked); void OnCheckboxFullscreen(bool checked); + void OnCheckboxRumble(bool checked); + void OnCheckboxTexture(bool checked); + void TouchControlsChanged(int index); void TransitionTypeChanged(int index); void accept() override; void reject() override; @@ -53,6 +55,8 @@ private slots: void SavePathEdited(); void MaxLoDChanged(int value); void MaxActorsChanged(int value); + void SelectTexturePathDialog(); + void TexturePathEdited(); }; // SYNTHETIC: CONFIG 0x00403de0 diff --git a/CONFIG/qt/config.cpp b/CONFIG/qt/config.cpp index b4435298..c88f779b 100644 --- a/CONFIG/qt/config.cpp +++ b/CONFIG/qt/config.cpp @@ -78,6 +78,10 @@ bool CConfigApp::InitInstance() m_3d_video_ram = FALSE; m_joystick_index = -1; m_display_bit_depth = 16; + m_haptic = TRUE; + m_touch_scheme = 2; + m_texture_load = TRUE; + m_texture_path = "/textures/"; int totalRamMiB = SDL_GetSystemRAM(); if (totalRamMiB < 12) { m_3d_sound = FALSE; @@ -155,6 +159,7 @@ bool CConfigApp::ReadRegisterSettings() m_flip_surfaces = iniparser_getboolean(dict, "isle:Flip Surfaces", m_flip_surfaces); m_full_screen = iniparser_getboolean(dict, "isle:Full Screen", m_full_screen); m_transition_type = iniparser_getint(dict, "isle:Transition Type", m_transition_type); + m_touch_scheme = iniparser_getint(dict, "isle:Touch Scheme", m_touch_scheme); m_3d_video_ram = iniparser_getboolean(dict, "isle:Back Buffers in Video RAM", m_3d_video_ram); m_wide_view_angle = iniparser_getboolean(dict, "isle:Wide View Angle", m_wide_view_angle); m_3d_sound = iniparser_getboolean(dict, "isle:3DSound", m_3d_sound); @@ -162,10 +167,13 @@ bool CConfigApp::ReadRegisterSettings() m_model_quality = iniparser_getint(dict, "isle:Island Quality", m_model_quality); m_texture_quality = iniparser_getint(dict, "isle:Island Texture", m_texture_quality); m_use_joystick = iniparser_getboolean(dict, "isle:UseJoystick", m_use_joystick); + m_haptic = iniparser_getboolean(dict, "isle:Haptic", m_haptic); m_music = iniparser_getboolean(dict, "isle:Music", m_music); m_joystick_index = iniparser_getint(dict, "isle:JoystickIndex", m_joystick_index); m_max_lod = iniparser_getdouble(dict, "isle:Max LOD", m_max_lod); m_max_actors = iniparser_getint(dict, "isle:Max Allowed Extras", m_max_actors); + 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()); iniparser_freedict(dict); return true; } @@ -230,6 +238,14 @@ bool CConfigApp::ValidateSettings() m_max_actors = 20; is_modified = TRUE; } + if (!m_use_joystick) { + m_use_joystick = true; + is_modified = TRUE; + } + if (m_touch_scheme < 0 || m_touch_scheme > 2) { + m_touch_scheme = 2; + is_modified = TRUE; + } return is_modified; } @@ -298,6 +314,8 @@ void CConfigApp::WriteRegisterSettings() const dictionary* dict = dictionary_new(0); iniparser_set(dict, "isle", NULL); + iniparser_set(dict, "extensions", NULL); + iniparser_set(dict, "texture loader", NULL); if (m_device_enumerator->FormatDeviceName(buffer, m_driver, m_device) >= 0) { iniparser_set(dict, "isle:3D Device ID", buffer); } @@ -311,14 +329,19 @@ void CConfigApp::WriteRegisterSettings() const SetIniBool(dict, "isle:Wide View Angle", m_wide_view_angle); SetIniInt(dict, "isle:Transition Type", m_transition_type); + SetIniInt(dict, "isle:Touch Scheme", m_touch_scheme); SetIniBool(dict, "isle:3DSound", m_3d_sound); SetIniBool(dict, "isle:Music", m_music); + SetIniBool(dict, "isle:Haptic", m_haptic); SetIniBool(dict, "isle:UseJoystick", m_use_joystick); SetIniInt(dict, "isle:JoystickIndex", m_joystick_index); SetIniBool(dict, "isle:Draw Cursor", m_draw_cursor); + SetIniBool(dict, "extensions:texture loader", m_texture_load); + iniparser_set(dict, "texture loader:texture path", m_texture_path.c_str()); + SetIniBool(dict, "isle:Back Buffers in Video RAM", m_3d_video_ram); SetIniInt(dict, "isle:Island Quality", m_model_quality); diff --git a/CONFIG/qt/config.h b/CONFIG/qt/config.h index dffc2b6c..796a5659 100644 --- a/CONFIG/qt/config.h +++ b/CONFIG/qt/config.h @@ -71,16 +71,20 @@ class CConfigApp { bool m_3d_sound; bool m_draw_cursor; bool m_use_joystick; + bool m_haptic; int m_joystick_index; int m_model_quality; int m_texture_quality; bool m_music; + bool m_texture_load; + std::string m_texture_path; std::string m_iniPath; std::string m_base_path; std::string m_cd_path; std::string m_save_path; float m_max_lod; int m_max_actors; + int m_touch_scheme; }; extern CConfigApp g_theApp; diff --git a/CONFIG/qt/res/maindialog.ui b/CONFIG/qt/res/maindialog.ui index 66e2b52e..526c31d8 100644 --- a/CONFIG/qt/res/maindialog.ui +++ b/CONFIG/qt/res/maindialog.ui @@ -6,8 +6,8 @@ 0 0 - 575 - 600 + 550 + 700 @@ -23,7 +23,7 @@ :/lego1.png:/lego1.png - + @@ -56,13 +56,16 @@ false - - Qt::AlignmentFlag::AlignCenter - + + + 0 + 0 + + @@ -89,7 +92,7 @@ - 55 + 50 16777215 @@ -108,7 +111,7 @@ - 55 + 50 16777215 @@ -129,17 +132,15 @@ Save Path: - Qt::TextFormat::PlainText - - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + Qt::PlainText - Path to the game data files. Set this to the CD image root. + Path to the game data files. +Set this to the CD image root. @@ -162,10 +163,7 @@ Data Path: - Qt::TextFormat::PlainText - - - Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter + Qt::PlainText @@ -187,13 +185,166 @@ 0 + + + + + 0 + 0 + + + + Maximum number of LEGO actors to exist in the world at a time. +The game will gradually increase the number of actors until this maximum is reached and while performance is acceptable. + + + Maximum Actors (5..40) + + + + + + + 0 + 0 + + + + 5 + + + 40 + + + 5 + + + 20 + + + 20 + + + false + + + Qt::Horizontal + + + QSlider::NoTicks + + + 5 + + + + + + + + 0 + 0 + + + + + 15 + 0 + + + + 20 + + + + + + + + + + + 0 + 0 + + + + + 0 + 70 + + + + Maximum Level of Detail (LOD). +A higher setting will cause higher quality textures to be drawn regardless of distance. + + + Maximum LOD + + + + + + + 0 + 0 + + + + 50 + + + 5 + + + 10 + + + 35 + + + false + + + Qt::Horizontal + + + QSlider::NoTicks + + + 10 + + + + + + + + 0 + 0 + + + + + 15 + 0 + + + + 35 + + + + + + - - - 16777215 - 120 - + + + 0 + 0 + Set 3D model detail level. @@ -204,8 +355,14 @@ + + Broken, not recommended. + + + color: rgb(255, 0, 0); + - Low + Low - BROKEN! @@ -226,63 +383,26 @@ - - - - - 0 - 70 - - - - Maximum Level of Detail (LOD). A higher setting will cause higher quality textures to be drawn regardless of distance. - - - Maximum LOD - - - Qt::AlignmentFlag::AlignCenter - - - - - - 50 - - - 5 - - - 10 - - - 35 - - - false - - - Qt::Orientation::Horizontal - - - QSlider::TickPosition::TicksBothSides - - - 10 - - - - - - + + true + + + + 0 + 0 + + Set texture detail level. Island Texture Quality + + false + 0 @@ -307,58 +427,24 @@ - - - - Maximum number of LEGO actors to exist in the world at a time. The game will gradually increase the number of actors until this maximum is reached and while performance is acceptable. - - - Maximum Actors (5..40) - - - Qt::AlignmentFlag::AlignCenter - - - - - - 5 - - - 40 - - - 5 - - - 20 - - - 20 - - - false - - - Qt::Orientation::Horizontal - - - QSlider::TickPosition::TicksBothSides - - - 5 - - - - - - + + + 0 + 0 + + + + 0 + + + 0 + @@ -379,16 +465,6 @@ - - - - Enable joystick and gamepad support for LEGO Island. - - - Use Joystick - - - @@ -399,80 +475,201 @@ + + + + true + + + Enable controller rumble. + + + Rumble + + + true + + + - - - Transition Type - + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Sets the transition effect to be used in game. + + + Transition Type + + + + + + Mosaic + + + 3 + + + + Idle - Broken + + + + + No Animation + + + + + Dissolve + + + + + Mosaic + + + + + Wipe Down + + + + + Windows + + + + + Unknown - Broken + + + + + + + + + + + <html><head/><body><p><span style=" font-weight:600;">Virtual Gamepad (Recommended):</span> Slide your finger to move and turn.</p><p><span style=" font-weight:600;">Virtual Arrow Keys:</span> Tap screen areas to move. The top moves forward, the bottom turns or moves back.</p><p><span style=" font-weight:600;">Virtual Mouse:</span> Emulates classic mouse controls with touch.</p></body></html> + + + Touch Control Scheme + + + + + + 2 + + + + Virtual Mouse + + + + + Virtual Arrow Keys + + + + + Virtual Gamepad + + + + + + + + - - - Sets the transition effect to be used in game. + + + Texture Loader Extension - - Idle - Broken - - - - - - - Idle - Broken - - - - - No Animation - - - - - Dissolve - - - - - Mosaic - - - - - Wipe Down - - - - - Windows - - - - - Unknown - Broken - - + + + + + false + + + Path to texture replacements. + + + textures/ + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + Open + + + + + + + Enabled + + + + - - - 16777215 - 225 - + + + 0 + 0 + 3D graphics device used to render the game. - - - - Qt::AlignmentFlag::AlignCenter + Graphics Devices false @@ -490,28 +687,6 @@ 6 - - - - - 0 - 0 - - - - - 0 - 25 - - - - Direct 3D Devices - - - Qt::AlignmentFlag::AlignCenter - - - @@ -526,17 +701,17 @@ 16777215 - 125 + 100 - QAbstractItemView::EditTrigger::NoEditTriggers + QAbstractItemView::NoEditTriggers true - QAbstractItemView::SelectionBehavior::SelectRows + QAbstractItemView::SelectItems @@ -545,9 +720,15 @@ + + + 0 + 0 + + - 30 + 10 0 @@ -616,8 +797,6 @@ maxActorsSlider sound3DCheckBox musicCheckBox - joystickCheckBox - fullscreenCheckBox devicesList okButton launchButton diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index 612d8f8f..220de06b 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -37,8 +37,10 @@ #include "tgl/d3drm/impl.h" #include "viewmanager/viewmanager.h" +#include #include #include +#include #define SDL_MAIN_USE_CALLBACKS #include @@ -180,6 +182,8 @@ IsleApp::IsleApp() m_maxAllowedExtras = m_islandQuality <= 1 ? 10 : 20; m_transitionType = MxTransitionManager::e_mosaic; m_cursorSensitivity = 4; + m_touchScheme = LegoInputManager::e_gamepad; + m_haptic = TRUE; } // FUNCTION: ISLE 0x4011a0 @@ -443,17 +447,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) return SDL_APP_CONTINUE; } - // [library:window] - // Remaining functionality to be implemented: - // WM_TIMER - use SDL_Timer functionality instead - -#ifdef __EMSCRIPTEN__ - // Workaround for the fact we are getting both mouse & touch events on mobile devices running Emscripten. - // On desktops, we are only getting mouse events, but a touch device (pen_input) may also be present... - // See: https://github.com/libsdl-org/SDL/issues/13161 - static bool detectedTouchEvents = false; -#endif - switch (event->type) { case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: case SDL_EVENT_MOUSE_MOTION: @@ -638,11 +631,7 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) g_mouseWarped = FALSE; break; } -#ifdef __EMSCRIPTEN__ - if (detectedTouchEvents) { - break; - } -#endif + g_mousemoved = TRUE; if (InputManager()) { @@ -665,16 +654,18 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; case SDL_EVENT_FINGER_MOTION: { -#ifdef __EMSCRIPTEN__ - detectedTouchEvents = true; -#endif g_mousemoved = TRUE; float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { - InputManager()->QueueEvent(c_notificationMouseMove, LegoEventNotificationParam::c_lButtonState, x, y, 0); + MxU8 modifier = LegoEventNotificationParam::c_lButtonState; + if (InputManager()->HandleTouchEvent(event, g_isle->GetTouchScheme())) { + modifier |= LegoEventNotificationParam::c_motionHandled; + } + + InputManager()->QueueEvent(c_notificationMouseMove, modifier, x, y, 0); } g_lastMouseX = x; @@ -688,11 +679,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) break; } case SDL_EVENT_MOUSE_BUTTON_DOWN: -#ifdef __EMSCRIPTEN__ - if (detectedTouchEvents) { - break; - } -#endif g_mousedown = TRUE; if (InputManager()) { @@ -706,15 +692,13 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; case SDL_EVENT_FINGER_DOWN: { -#ifdef __EMSCRIPTEN__ - detectedTouchEvents = true; -#endif g_mousedown = TRUE; float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; if (InputManager()) { + InputManager()->HandleTouchEvent(event, g_isle->GetTouchScheme()); InputManager()->QueueEvent(c_notificationButtonDown, LegoEventNotificationParam::c_lButtonState, x, y, 0); } @@ -729,17 +713,6 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) break; } case SDL_EVENT_MOUSE_BUTTON_UP: -#ifdef __EMSCRIPTEN__ - if (detectedTouchEvents) { - // Abusing the fact (bug?) that we are always getting mouse events on Emscripten. - // This functionality should be enabled in a more general way with touch events, - // but SDL touch event's don't have a "double tap" indicator right now. - if (event->button.clicks == 2) { - InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE); - } - break; - } -#endif g_mousedown = FALSE; if (InputManager()) { @@ -753,15 +726,15 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) } break; case SDL_EVENT_FINGER_UP: { -#ifdef __EMSCRIPTEN__ - detectedTouchEvents = true; -#endif g_mousedown = FALSE; float x = SDL_clamp(event->tfinger.x, 0, 1) * g_targetWidth; float y = SDL_clamp(event->tfinger.y, 0, 1) * g_targetHeight; + g_isle->DetectDoubleTap(event->tfinger); + if (InputManager()) { + InputManager()->HandleTouchEvent(event, g_isle->GetTouchScheme()); InputManager()->QueueEvent(c_notificationButtonUp, 0, x, y, 0); } break; @@ -807,6 +780,11 @@ SDL_AppResult SDL_AppEvent(void* appstate, SDL_Event* event) SDL_Log("Game started"); } } + else if (event->user.type == g_legoSdlEvents.m_hitActor && g_isle->GetHaptic()) { + if (InputManager()) { + InputManager()->HandleRumbleEvent(); + } + } return SDL_APP_CONTINUE; } @@ -1057,6 +1035,8 @@ bool IsleApp::LoadConfig() iniparser_set(dict, "isle:Max LOD", buf); iniparser_set(dict, "isle:Max Allowed Extras", SDL_itoa(m_maxAllowedExtras, buf, 10)); iniparser_set(dict, "isle:Transition Type", SDL_itoa(m_transitionType, buf, 10)); + iniparser_set(dict, "isle:Touch Scheme", SDL_itoa(m_touchScheme, buf, 10)); + iniparser_set(dict, "isle:Haptic", m_haptic ? "true" : "false"); #ifdef EXTENSIONS iniparser_set(dict, "extensions", NULL); @@ -1130,6 +1110,8 @@ bool IsleApp::LoadConfig() m_maxAllowedExtras = iniparser_getint(dict, "isle:Max Allowed Extras", m_maxAllowedExtras); m_transitionType = (MxTransitionManager::TransitionType) iniparser_getint(dict, "isle:Transition Type", m_transitionType); + m_touchScheme = (LegoInputManager::TouchScheme) iniparser_getint(dict, "isle:Touch Scheme", m_touchScheme); + m_haptic = iniparser_getboolean(dict, "isle:Haptic", m_haptic); const char* deviceId = iniparser_getstring(dict, "isle:3D Device ID", NULL); if (deviceId != NULL) { @@ -1145,13 +1127,19 @@ bool IsleApp::LoadConfig() strcpy(m_savePath, savePath); #ifdef EXTENSIONS - std::vector keys; - keys.resize(iniparser_getsecnkeys(dict, "extensions")); - iniparser_getseckeys(dict, "extensions", keys.data()); - - for (const char* key : keys) { + for (const char* key : Extensions::availableExtensions) { if (iniparser_getboolean(dict, key, 0)) { - Extensions::Enable(key); + std::vector extensionKeys; + const char* section = SDL_strchr(key, ':') + 1; + extensionKeys.resize(iniparser_getsecnkeys(dict, section)); + iniparser_getseckeys(dict, section, extensionKeys.data()); + + std::map extensionDict; + for (const char* key : extensionKeys) { + extensionDict[key] = iniparser_getstring(dict, key, NULL); + } + + Extensions::Enable(key, std::move(extensionDict)); } } #endif @@ -1478,3 +1466,26 @@ void IsleApp::MoveVirtualMouseViaJoystick() } } } + +void IsleApp::DetectDoubleTap(const SDL_TouchFingerEvent& p_event) +{ + typedef std::pair> LastTap; + + const MxU32 doubleTapMs = 500; + const float doubleTapDist = 0.001; + static LastTap lastTap = {0, {0, 0}}; + + LastTap currentTap = {p_event.timestamp, {p_event.x, p_event.y}}; + if (SDL_NS_TO_MS(currentTap.first - lastTap.first) < doubleTapMs && + DISTSQRD2(currentTap.second, lastTap.second) < doubleTapDist) { + + if (InputManager()) { + InputManager()->QueueEvent(c_notificationKeyPress, SDLK_SPACE, 0, 0, SDLK_SPACE); + } + + lastTap = {0, {0, 0}}; + } + else { + lastTap = currentTap; + } +} diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 7054f6d1..4bec7dd6 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -3,6 +3,7 @@ #include "cursor.h" #include "lego1_export.h" +#include "legoinputmanager.h" #include "legoutils.h" #include "mxtransitionmanager.h" #include "mxtypes.h" @@ -53,6 +54,8 @@ class IsleApp { MxS32 GetDrawCursor() { return m_drawCursor; } MxS32 GetGameStarted() { return m_gameStarted; } MxFloat GetCursorSensitivity() { return m_cursorSensitivity; } + LegoInputManager::TouchScheme GetTouchScheme() { return m_touchScheme; } + MxBool GetHaptic() { return m_haptic; } void SetWindowActive(MxS32 p_windowActive) { m_windowActive = p_windowActive; } void SetGameStarted(MxS32 p_gameStarted) { m_gameStarted = p_gameStarted; } @@ -62,6 +65,7 @@ class IsleApp { MxResult VerifyFilesystem(); void DetectGameVersion(); void MoveVirtualMouseViaJoystick(); + void DetectDoubleTap(const SDL_TouchFingerEvent& p_event); private: char* m_hdPath; // 0x00 @@ -102,6 +106,8 @@ class IsleApp { MxFloat m_maxLod; MxU32 m_maxAllowedExtras; MxTransitionManager::TransitionType m_transitionType; + LegoInputManager::TouchScheme m_touchScheme; + MxBool m_haptic; }; extern IsleApp* g_isle; diff --git a/ISLE/res/3ds/banner.wav b/ISLE/res/3ds/banner.wav deleted file mode 100644 index ce7849e7..00000000 Binary files a/ISLE/res/3ds/banner.wav and /dev/null differ diff --git a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h index a88c62f0..5569280c 100644 --- a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h +++ b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h @@ -18,6 +18,7 @@ class LegoEventNotificationParam : public MxNotificationParam { c_rButtonState = 2, c_modKey1 = 4, c_modKey2 = 8, + c_motionHandled = 16, }; // FUNCTION: LEGO1 0x10028690 diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 5f3052c1..ee87b50a 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -18,6 +18,8 @@ #include #endif +#include + class LegoCameraController; class LegoControlManager; class LegoWorld; @@ -89,6 +91,12 @@ class LegoInputManager : public MxPresenter { c_upOrDown = c_up | c_down }; + enum TouchScheme { + e_mouse = 0, + e_arrowKeys, + e_gamepad, + }; + LegoInputManager(); ~LegoInputManager() override; @@ -144,6 +152,8 @@ class LegoInputManager : public MxPresenter { void GetKeyboardState(); MxResult GetNavigationKeyStates(MxU32& p_keyFlags); MxResult GetNavigationTouchStates(MxU32& p_keyFlags); + LEGO1_EXPORT MxBool HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme); + LEGO1_EXPORT MxBool HandleRumbleEvent(); // SYNTHETIC: LEGO1 0x1005b8d0 // LegoInputManager::`scalar deleting destructor' @@ -171,6 +181,10 @@ class LegoInputManager : public MxPresenter { MxBool m_useJoystick; // 0x334 MxBool m_unk0x335; // 0x335 MxBool m_unk0x336; // 0x336 + + std::map m_touchOrigins; + std::map m_touchFlags; + std::map> m_touchLastMotion; }; // TEMPLATE: LEGO1 0x10028850 diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index 2df6ad4a..b29a336c 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -71,6 +71,7 @@ LegoNamedTexture* ReadNamedTexture(LegoStorage* p_storage); void WriteDefaultTexture(LegoStorage* p_storage, const char* p_name); void WriteNamedTexture(LegoStorage* p_storage, LegoNamedTexture* p_namedTexture); void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture); +void HitActorEvent(); // FUNCTION: BETA10 0x100260a0 inline void StartIsleAction(IsleScript::Script p_objectId) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index ff45676b..0a07a7c0 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -782,3 +782,10 @@ void LoadFromNamedTexture(LegoNamedTexture* p_namedTexture) textureInfo->LoadBits(p_namedTexture->GetTexture()->GetImage()->GetBits()); } } + +void HitActorEvent() +{ + SDL_Event event; + event.user.type = g_legoSdlEvents.m_hitActor; + SDL_PushEvent(&event); +} diff --git a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp index 563ba615..3e977d5b 100644 --- a/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp @@ -41,6 +41,10 @@ MxResult LegoCameraController::Create() // FUNCTION: BETA10 0x10067852 MxLong LegoCameraController::Notify(MxParam& p_param) { + if (((LegoEventNotificationParam&) p_param).GetModifier() & LegoEventNotificationParam::c_motionHandled) { + return SUCCESS; + } + switch (((MxNotificationParam&) p_param).GetNotification()) { case c_notificationDragEnd: { if (((((LegoEventNotificationParam&) p_param).GetModifier()) & LegoEventNotificationParam::c_lButtonState) == diff --git a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp index 27b0f814..7c80e78e 100644 --- a/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp +++ b/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp @@ -528,42 +528,116 @@ void LegoInputManager::EnableInputProcessing() MxResult LegoInputManager::GetNavigationTouchStates(MxU32& p_keyStates) { - int count; - SDL_TouchID* touchDevices = SDL_GetTouchDevices(&count); + for (auto& [fingerID, touchFlags] : m_touchFlags) { + p_keyStates |= touchFlags; - if (touchDevices) { - auto applyFingerNavigation = [&p_keyStates](SDL_TouchID p_touchId) { - int count; - SDL_Finger** fingers = SDL_GetTouchFingers(p_touchId, &count); + // We need to clear these as they are not meant to be persistent in e_gamepad mode. + if (m_touchOrigins.count(fingerID) && m_touchLastMotion.count(fingerID)) { + const MxU32 inactivityThreshold = 3; - if (fingers) { - for (int i = 0; i < count; i++) { - if (fingers[i]->y > 3.0 / 4.0) { - if (fingers[i]->x < 1.0 / 3.0) { - p_keyStates |= c_left; - } - else if (fingers[i]->x > 2.0 / 3.0) { - p_keyStates |= c_right; - } - else { - p_keyStates |= c_down; - } - } - else { - p_keyStates |= c_up; - } - } - - SDL_free(fingers); + if (m_touchLastMotion[fingerID].first++ > inactivityThreshold) { + touchFlags &= ~c_left; + touchFlags &= ~c_right; + m_touchOrigins[fingerID].x = m_touchLastMotion[fingerID].second.x; } - }; - - for (int i = 0; i < count; i++) { - applyFingerNavigation(touchDevices[i]); } - - SDL_free(touchDevices); } return SUCCESS; } + +MxBool LegoInputManager::HandleTouchEvent(SDL_Event* p_event, TouchScheme p_touchScheme) +{ + const SDL_TouchFingerEvent& event = p_event->tfinger; + + switch (p_touchScheme) { + case e_mouse: + // Handled in LegoCameraController + return FALSE; + case e_arrowKeys: + switch (p_event->type) { + case SDL_EVENT_FINGER_UP: + m_touchFlags.erase(event.fingerID); + break; + case SDL_EVENT_FINGER_DOWN: + case SDL_EVENT_FINGER_MOTION: + m_touchFlags[event.fingerID] = 0; + + if (event.y > 3.0 / 4.0) { + if (event.x < 1.0 / 3.0) { + m_touchFlags[event.fingerID] |= c_left; + } + else if (event.x > 2.0 / 3.0) { + m_touchFlags[event.fingerID] |= c_right; + } + else { + m_touchFlags[event.fingerID] |= c_down; + } + } + else { + m_touchFlags[event.fingerID] |= c_up; + } + break; + } + break; + case e_gamepad: + switch (p_event->type) { + case SDL_EVENT_FINGER_DOWN: + m_touchOrigins[event.fingerID] = {event.x, event.y}; + break; + case SDL_EVENT_FINGER_UP: + m_touchOrigins.erase(event.fingerID); + m_touchFlags.erase(event.fingerID); + break; + case SDL_EVENT_FINGER_MOTION: + if (m_touchOrigins.count(event.fingerID)) { + const float activationThreshold = 0.03f; + m_touchFlags[event.fingerID] &= ~c_down; + m_touchFlags[event.fingerID] &= ~c_up; + + const float deltaY = event.y - m_touchOrigins[event.fingerID].y; + if (SDL_fabsf(deltaY) > activationThreshold) { + if (deltaY > 0) { + m_touchFlags[event.fingerID] |= c_down; + } + else if (deltaY < 0) { + m_touchFlags[event.fingerID] |= c_up; + } + } + + const float deltaX = event.x - m_touchOrigins[event.fingerID].x; + if (SDL_fabsf(deltaX) > activationThreshold && event.dx) { + if (event.dx > 0) { + m_touchFlags[event.fingerID] |= c_right; + m_touchFlags[event.fingerID] &= ~c_left; + } + else if (event.dx < 0) { + m_touchFlags[event.fingerID] |= c_left; + m_touchFlags[event.fingerID] &= ~c_right; + } + + m_touchLastMotion[event.fingerID] = {0, {event.x, event.y}}; + } + } + break; + } + break; + } + + return TRUE; +} + +MxBool LegoInputManager::HandleRumbleEvent() +{ + if (m_joystick != NULL && SDL_GamepadConnected(m_joystick) == TRUE) { + const Uint16 frequency = 65535 / 2; + const Uint32 durationMs = 700; + SDL_RumbleGamepad(m_joystick, frequency, frequency, durationMs); + } + else { + return FALSE; + } + + // Add support for SDL Haptic API + return TRUE; +} diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index 0a888b46..7f640326 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -235,6 +235,7 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) assert(m_roi); assert(SoundManager()->GetCacheSoundManager()); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); + HitActorEvent(); m_scheduledTime = Timer()->GetTime() + m_disAnim->GetDuration(); m_prevWorldSpeed = GetWorldSpeed(); VTable0xc4(); @@ -248,6 +249,7 @@ MxResult LegoExtraActor::HitActor(LegoPathActor* p_actor, MxBool p_bool) LegoROI* roi = GetROI(); assert(roi); SoundManager()->GetCacheSoundManager()->Play("crash5", m_roi->GetName(), FALSE); + HitActorEvent(); VTable0xc4(); SetActorState(c_two | c_noCollide); Mx3DPointFloat dir = p_actor->GetWorldDirection(); diff --git a/LEGO1/omni/include/mxutilities.h b/LEGO1/omni/include/mxutilities.h index c5237bb0..33754f3c 100644 --- a/LEGO1/omni/include/mxutilities.h +++ b/LEGO1/omni/include/mxutilities.h @@ -10,6 +10,7 @@ struct LegoSdlEvents { Uint32 m_windowsMessage; Uint32 m_presenterProgress; + Uint32 m_hitActor; }; LEGO1_EXPORT extern LegoSdlEvents g_legoSdlEvents; diff --git a/LEGO1/omni/src/main/mxomni.cpp b/LEGO1/omni/src/main/mxomni.cpp index d7b76847..b6081b16 100644 --- a/LEGO1/omni/src/main/mxomni.cpp +++ b/LEGO1/omni/src/main/mxomni.cpp @@ -163,9 +163,10 @@ MxResult MxOmni::Create(MxOmniCreateParam& p_param) } { - Uint32 event = SDL_RegisterEvents(2); + Uint32 event = SDL_RegisterEvents(3); g_legoSdlEvents.m_windowsMessage = event + 0; g_legoSdlEvents.m_presenterProgress = event + 1; + g_legoSdlEvents.m_hitActor = event + 2; } result = SUCCESS; diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index 2902c4fe..f676dd0a 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -1258,7 +1258,7 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ MxS32 pixel; if (!isOpaque) { - pixel = RGB8888_CREATE(0, 0, 0, 0); // Transparent pixel + pixel = RGB8888_CREATE(0xff, 0, 0xff, 0); // Transparent pixel } else { pixel = isBlack ? RGB8888_CREATE(0, 0, 0, 0xff) : RGB8888_CREATE(0xff, 0xff, 0xff, 0xff); @@ -1288,6 +1288,10 @@ LPDIRECTDRAWSURFACE MxDisplaySurface::CreateCursorSurface(const CursorBitmap* p_ break; } default: { + DDCOLORKEY colorkey; + colorkey.dwColorSpaceHighValue = RGB8888_CREATE(0xff, 0, 0xff, 0); + colorkey.dwColorSpaceLowValue = RGB8888_CREATE(0xff, 0, 0xff, 0); + newSurface->SetColorKey(DDCKEY_SRCBLT, &colorkey); break; } } diff --git a/docker/emscripten/Dockerfile b/docker/emscripten/Dockerfile index 707b3c7a..40c17cce 100644 --- a/docker/emscripten/Dockerfile +++ b/docker/emscripten/Dockerfile @@ -1,4 +1,4 @@ -FROM emscripten/emsdk:latest AS builder +FROM emscripten/emsdk:4.0.10 AS builder ARG CMAKE_VERSION=3.29.3 diff --git a/extensions/include/extensions/extensions.h b/extensions/include/extensions/extensions.h index 215bf80a..6c6f4f99 100644 --- a/extensions/include/extensions/extensions.h +++ b/extensions/include/extensions/extensions.h @@ -3,6 +3,7 @@ #include "lego1_export.h" #include +#include #include #include @@ -10,7 +11,7 @@ namespace Extensions { constexpr const char* availableExtensions[] = {"extensions:texture loader"}; -LEGO1_EXPORT void Enable(const char* p_key); +LEGO1_EXPORT void Enable(const char* p_key, std::map p_options); template struct Extension { diff --git a/extensions/include/extensions/textureloader.h b/extensions/include/extensions/textureloader.h index 649c6112..c476c7ff 100644 --- a/extensions/include/extensions/textureloader.h +++ b/extensions/include/extensions/textureloader.h @@ -3,16 +3,24 @@ #include "extensions/extensions.h" #include "legotextureinfo.h" +#include +#include + namespace Extensions { class TextureLoader { public: + static void Initialize(); static bool PatchTexture(LegoTextureInfo* p_textureInfo); + + static std::map options; static bool enabled; -private: - static constexpr const char* texturePath = "/textures/"; + static constexpr std::array, 1> defaults = { + {{"texture loader:texture path", "/textures/"}} + }; +private: static SDL_Surface* FindTexture(const char* p_name); }; diff --git a/extensions/src/extensions.cpp b/extensions/src/extensions.cpp index e1d0c389..779c0547 100644 --- a/extensions/src/extensions.cpp +++ b/extensions/src/extensions.cpp @@ -4,12 +4,14 @@ #include -void Extensions::Enable(const char* p_key) +void Extensions::Enable(const char* p_key, std::map p_options) { for (const char* key : availableExtensions) { if (!SDL_strcasecmp(p_key, key)) { if (!SDL_strcasecmp(p_key, "extensions:texture loader")) { + TextureLoader::options = std::move(p_options); TextureLoader::enabled = true; + TextureLoader::Initialize(); } SDL_Log("Enabled extension: %s", p_key); diff --git a/extensions/src/textureloader.cpp b/extensions/src/textureloader.cpp index dd96105e..07b53253 100644 --- a/extensions/src/textureloader.cpp +++ b/extensions/src/textureloader.cpp @@ -2,8 +2,18 @@ using namespace Extensions; +std::map TextureLoader::options; bool TextureLoader::enabled = false; +void TextureLoader::Initialize() +{ + for (const auto& option : defaults) { + if (!options.count(option.first.data())) { + options[option.first.data()] = option.second; + } + } +} + bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) { SDL_Surface* surface = FindTexture(p_textureInfo->m_name); @@ -88,6 +98,7 @@ bool TextureLoader::PatchTexture(LegoTextureInfo* p_textureInfo) SDL_Surface* TextureLoader::FindTexture(const char* p_name) { SDL_Surface* surface; + const char* texturePath = options["texture loader:texture path"].c_str(); MxString path = MxString(MxOmni::GetHD()) + texturePath + p_name + ".bmp"; path.MapPathToFilesystem(); diff --git a/ISLE/res/3ds/banner.png b/packaging/3ds/banner.png similarity index 100% rename from ISLE/res/3ds/banner.png rename to packaging/3ds/banner.png diff --git a/packaging/3ds/banner.wav b/packaging/3ds/banner.wav new file mode 100644 index 00000000..8eeb79ba Binary files /dev/null and b/packaging/3ds/banner.wav differ diff --git a/ISLE/res/3ds/icon.png b/packaging/3ds/icon.png similarity index 100% rename from ISLE/res/3ds/icon.png rename to packaging/3ds/icon.png diff --git a/ISLE/res/3ds/logo.bcma.lz b/packaging/3ds/logo.bcma.lz similarity index 100% rename from ISLE/res/3ds/logo.bcma.lz rename to packaging/3ds/logo.bcma.lz diff --git a/ISLE/res/3ds/template.rsf b/packaging/3ds/template.rsf similarity index 100% rename from ISLE/res/3ds/template.rsf rename to packaging/3ds/template.rsf