| 1741 | } |
| 1742 | |
| 1743 | bool GameApplication::Remount(const char* newModPath) |
| 1744 | { |
| 1745 | if (!CanRemount()) |
| 1746 | { |
| 1747 | LOG_WARN(Core, "Re-mount refused: a mission is active"); |
| 1748 | return false; |
| 1749 | } |
| 1750 | |
| 1751 | LOG_INFO(Core, "Re-mounting game content (mod='{}')", newModPath != nullptr ? newModPath : ""); |
| 1752 | |
| 1753 | // Mod set to fall back to if the new one fails to load (RStringB is returned |
| 1754 | // by value, so this survives the SetModPath below). |
| 1755 | const auto prevModPath = Poseidon::ModSystem::GetModList(); |
| 1756 | |
| 1757 | // Loading screen on the live window — ProgressSystem is Application-owned and |
| 1758 | // survives the teardown below. |
| 1759 | ProgressStart_Wrapper(IDS_LOAD_INIT); |
| 1760 | |
| 1761 | // Stop the main loop drawing the world while the content layer is gone. |
| 1762 | // UnloadGameData frees GWorld / GLandscape; a render frame that reaches |
| 1763 | // Landscape::DrawGround -> LandCache::Segment on the freed terrain cache is a |
| 1764 | // use-after-free. RenderFrame is gated on m_canRender (GameLoop.cpp), so |
| 1765 | // clearing it here closes the window; EnableRendering() restores it only once |
| 1766 | // a load succeeds. |
| 1767 | GApp->m_canRender = false; |
| 1768 | |
| 1769 | // Trash the game-data layer, keeping window/device/memory/logging alive. |
| 1770 | extern void UnloadGameData(bool keepEngine); |
| 1771 | UnloadGameData(/*keepEngine*/ true); |
| 1772 | |
| 1773 | // Drop + rebuild GPU resources tied to the old content (no-op on headless). |
| 1774 | if (GEngine) |
| 1775 | { |
| 1776 | GEngine->ResetForRemount(); |
| 1777 | } |
| 1778 | |
| 1779 | // Swap the active mod set, then reload everything from scratch. |
| 1780 | Poseidon::ModSystem::SetModPath(newModPath != nullptr ? newModPath : ""); |
| 1781 | |
| 1782 | if (!LoadGameData()) |
| 1783 | { |
| 1784 | // Bad / unsupported mod (e.g. a Workshop package the unpacker rejected). |
| 1785 | // Roll back to the previous set so the app returns to a usable, rendering |
| 1786 | // state instead of a frozen half-mount that the next render frame would |
| 1787 | // crash on (LandCache::Segment on a freed cache), then report failure to |
| 1788 | // the caller so it can surface the error. |
| 1789 | LOG_ERROR(Core, "Re-mount reload failed for mod '{}' — rolling back", newModPath != nullptr ? newModPath : ""); |
| 1790 | UnloadGameData(/*keepEngine*/ true); |
| 1791 | if (GEngine) |
| 1792 | { |
| 1793 | GEngine->ResetForRemount(); |
| 1794 | } |
| 1795 | Poseidon::ModSystem::SetModPath(prevModPath); |
| 1796 | if (LoadGameData()) |
| 1797 | { |
| 1798 | if (GWorld) |
| 1799 | { |
| 1800 | GWorld->StartIntro(); |
nothing calls this directly
no test coverage detected