diff --git a/CHANGELOG.md b/CHANGELOG.md index 22fb0e42d5..659d902a82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Fix: [#1763] Title music does not stop when unchecked in options window. - Fix: [#1772] Toggling edge scrolling option does not work. - Fix: [#1798] Memory leak when resizing the window. +- Fix: [#1842] Track and Dock objects incorrectly unloaded causing packing issues. - Change: [#1823] Prevent edge scroll if the window has no input focus. 23.01 (2023-01-25) diff --git a/src/OpenLoco/src/Objects/Object.h b/src/OpenLoco/src/Objects/Object.h index 8442d6cc52..9879c381b4 100644 --- a/src/OpenLoco/src/Objects/Object.h +++ b/src/OpenLoco/src/Objects/Object.h @@ -50,6 +50,7 @@ namespace OpenLoco { private: static constexpr char cFF = static_cast(0xFF); + static constexpr uint32_t kFuzzyFlagsMask = 0xFFFE0000; public: uint32_t flags = 0xFFFFFFFF; @@ -71,6 +72,11 @@ namespace OpenLoco return static_cast(flags & 0x3F); } + constexpr uint32_t getFuzzyFlags() const + { + return flags & kFuzzyFlagsMask; + } + constexpr bool isCustom() const { return getSourceGame() == 0; diff --git a/src/OpenLoco/src/Objects/ObjectManager.cpp b/src/OpenLoco/src/Objects/ObjectManager.cpp index 84e522bb5d..95e14f25b9 100644 --- a/src/OpenLoco/src/Objects/ObjectManager.cpp +++ b/src/OpenLoco/src/Objects/ObjectManager.cpp @@ -153,6 +153,51 @@ namespace OpenLoco::ObjectManager return std::nullopt; } + // 0x0047206C + // Returns std::nullopt if not loaded + std::optional findObjectHandleFuzzy(const ObjectHeader& header) + { + auto res = findObjectHandle(header); + if (res.has_value()) + { + return res; + } + + auto objectType = header.getType(); + const auto& typedObjectList = getRepositoryItem(objectType); + auto maxObjectsForType = getMaxObjects(objectType); + for (LoadedObjectId i = 0; i < maxObjectsForType; i++) + { + auto obj = typedObjectList.objects[i]; + if (obj == nullptr || obj != reinterpret_cast(-1)) + { + continue; + } + const auto& objHeader = typedObjectList.objectEntryExtendeds[i]; + + if (!objHeader.isCustom()) + { + continue; + } + if (header.getType() != objHeader.getType()) + { + continue; + } + if (header.getName() != objHeader.getName()) + { + continue; + } + if (header.getFuzzyFlags() != objHeader.getFuzzyFlags()) + { + continue; + } + + return { LoadedObjectHandle{ objectType, i } }; + } + + return std::nullopt; + } + enum class ObjectProcedure { load, diff --git a/src/OpenLoco/src/Objects/ObjectManager.h b/src/OpenLoco/src/Objects/ObjectManager.h index 572fef4aab..88cf7d0898 100644 --- a/src/OpenLoco/src/Objects/ObjectManager.h +++ b/src/OpenLoco/src/Objects/ObjectManager.h @@ -157,6 +157,9 @@ namespace OpenLoco::ObjectManager bool isTemporaryObjectLoad(); std::optional findObjectHandle(const ObjectHeader& header); + // Calls findObjectHandle and if can't find performs a secondary check with slightly looser + // definitions of what a matching custom header is (no checksum, partial flags) + std::optional findObjectHandleFuzzy(const ObjectHeader& header); void reloadAll(); ObjectHeader& getHeader(const LoadedObjectHandle& handle); std::vector getHeaders(); diff --git a/src/OpenLoco/src/Objects/TrackObject.cpp b/src/OpenLoco/src/Objects/TrackObject.cpp index 5f194357a2..5c491c5973 100644 --- a/src/OpenLoco/src/Objects/TrackObject.cpp +++ b/src/OpenLoco/src/Objects/TrackObject.cpp @@ -2,7 +2,9 @@ #include "Drawing/SoftwareDrawingEngine.h" #include "Graphics/Colour.h" #include "Graphics/Gfx.h" +#include "ObjectImageTable.h" #include "ObjectManager.h" +#include "ObjectStringTable.h" #include namespace OpenLoco @@ -59,36 +61,121 @@ namespace OpenLoco // 0x004A6A5F void TrackObject::load(const LoadedObjectHandle& handle, stdx::span data, ObjectManager::DependentObjects* dependencies) { - Interop::registers regs; - regs.esi = Interop::X86Pointer(this); - regs.ebx = handle.id; - regs.ecx = enumValue(handle.type); - Interop::call(0x004A6A5F, regs); - if (dependencies != nullptr) + auto remainingData = data.subspan(sizeof(TrackObject)); + + auto strRes = ObjectManager::loadStringTable(remainingData, handle, 0); + name = strRes.str; + remainingData = remainingData.subspan(strRes.tableLength); + + // NOTE: These aren't dependent (objects unless otherwise stated) as this object can load without the + // related object. + // Load compatible roads/tracks + compatibleTracks = 0; + compatibleRoads = 0; + for (auto i = 0; i < numCompatible; ++i) + { + ObjectHeader modHeader = *reinterpret_cast(remainingData.data()); + auto res = ObjectManager::findObjectHandle(modHeader); + if (res.has_value()) + { + if (res->type == ObjectType::track) + { + compatibleTracks |= 1U << res->id; + } + else if (res->type == ObjectType::road) + { + compatibleRoads |= 1U << res->id; + } + } + remainingData = remainingData.subspan(sizeof(ObjectHeader)); + } + + // Load Extra + std::fill(std::begin(mods), std::end(mods), 0xFF); + for (auto i = 0U, index = 0U; i < numMods; ++i) + { + ObjectHeader modHeader = *reinterpret_cast(remainingData.data()); + auto res = ObjectManager::findObjectHandle(modHeader); + if (res.has_value() && res->type == ObjectType::trackExtra) + { + mods[index++] = res->id; + } + remainingData = remainingData.subspan(sizeof(ObjectHeader)); + } + + // Load Signals + signals = 0; + for (auto i = 0; i < numSignals; ++i) + { + ObjectHeader modHeader = *reinterpret_cast(remainingData.data()); + auto res = ObjectManager::findObjectHandle(modHeader); + if (res.has_value() && res->type == ObjectType::trackSignal) + { + signals |= 1U << res->id; + } + remainingData = remainingData.subspan(sizeof(ObjectHeader)); + } + + // Load Tunnel (DEPENDENT OBJECT) + tunnel = 0xFF; { - auto* depObjs = Interop::addr<0x0050D158, uint8_t*>(); - dependencies->required.resize(*depObjs++); - if (!dependencies->required.empty()) + ObjectHeader unkHeader = *reinterpret_cast(remainingData.data()); + if (dependencies != nullptr) { - std::copy(reinterpret_cast(depObjs), reinterpret_cast(depObjs) + dependencies->required.size(), dependencies->required.data()); - depObjs += sizeof(ObjectHeader) * dependencies->required.size(); + dependencies->required.push_back(unkHeader); } - dependencies->willLoad.resize(*depObjs++); - if (!dependencies->willLoad.empty()) + auto res = ObjectManager::findObjectHandle(unkHeader); + if (res.has_value()) { - std::copy(reinterpret_cast(depObjs), reinterpret_cast(depObjs) + dependencies->willLoad.size(), dependencies->willLoad.data()); + tunnel = res->id; } + remainingData = remainingData.subspan(sizeof(ObjectHeader)); } + + // Load bridges (DEPENDENT OBJECT) + std::fill(std::begin(bridges), std::end(bridges), 0xFF); + for (auto i = 0U; i < numBridges; ++i) + { + ObjectHeader bridgeHeader = *reinterpret_cast(remainingData.data()); + if (dependencies != nullptr) + { + dependencies->required.push_back(bridgeHeader); + } + auto res = ObjectManager::findObjectHandle(bridgeHeader); + if (res.has_value()) + { + bridges[i] = res->id; + } + remainingData = remainingData.subspan(sizeof(ObjectHeader)); + } + + // Load stations + std::fill(std::begin(stations), std::end(stations), 0xFF); + for (auto i = 0U; i < numStations; ++i) + { + ObjectHeader stationHeader = *reinterpret_cast(remainingData.data()); + auto res = ObjectManager::findObjectHandle(stationHeader); + if (res.has_value()) + { + stations[i] = res->id; + } + remainingData = remainingData.subspan(sizeof(ObjectHeader)); + } + + auto imgRes = ObjectManager::loadImageTable(remainingData); + image = imgRes.imageOffset; + assert(remainingData.size() == imgRes.tableLength); } // 0x004A6C2D void TrackObject::unload() { name = 0; - var_10 = 0; + compatibleTracks = 0; + compatibleRoads = 0; std::fill(std::begin(mods), std::end(mods), 0); - var_0E = 0; - var_1B = 0; + signals = 0; + tunnel = 0; image = 0; std::fill(std::begin(bridges), std::end(bridges), 0); std::fill(std::begin(stations), std::end(stations), 0); diff --git a/src/OpenLoco/src/Objects/TrackObject.h b/src/OpenLoco/src/Objects/TrackObject.h index 73282f3c26..1e5b068800 100644 --- a/src/OpenLoco/src/Objects/TrackObject.h +++ b/src/OpenLoco/src/Objects/TrackObject.h @@ -51,26 +51,26 @@ namespace OpenLoco TrackObjectPieceFlags trackPieces; // 0x02 uint16_t stationTrackPieces; // 0x04 uint8_t var_06; - uint8_t numCompatible; // 0x07 - uint8_t numMods; // 0x08 - uint8_t numSignals; // 0x09 - uint8_t mods[4]; // 0x0A - uint16_t var_0E; // ?compatible signals bitset? - uint16_t var_10; - uint8_t pad_12[0x14 - 0x12]; - int16_t buildCostFactor; // 0x14 - int16_t sellCostFactor; // 0x16 - int16_t tunnelCostFactor; // 0x18 - uint8_t costIndex; // 0x1A - uint8_t var_1B; - uint16_t curveSpeed; // 0x1C - uint32_t image; // 0x1E - TrackObjectFlags flags; // 0x22 - uint8_t numBridges; // 0x24 - uint8_t bridges[7]; // 0x25 - uint8_t numStations; // 0x2C - uint8_t stations[7]; // 0x2D - uint8_t displayOffset; // 0x34 + uint8_t numCompatible; // 0x07 + uint8_t numMods; // 0x08 + uint8_t numSignals; // 0x09 + uint8_t mods[4]; // 0x0A + uint16_t signals; // 0x0E bitset + uint16_t compatibleTracks; // 0x10 bitset + uint16_t compatibleRoads; // 0x12 bitset + int16_t buildCostFactor; // 0x14 + int16_t sellCostFactor; // 0x16 + int16_t tunnelCostFactor; // 0x18 + uint8_t costIndex; // 0x1A + uint8_t tunnel; // 0x1B + uint16_t curveSpeed; // 0x1C + uint32_t image; // 0x1E + TrackObjectFlags flags; // 0x22 + uint8_t numBridges; // 0x24 + uint8_t bridges[7]; // 0x25 + uint8_t numStations; // 0x2C + uint8_t stations[7]; // 0x2D + uint8_t displayOffset; // 0x34 uint8_t pad_35; void drawPreviewImage(Gfx::RenderTarget& rt, const int16_t x, const int16_t y) const; diff --git a/src/OpenLoco/src/Windows/Construction/Common.cpp b/src/OpenLoco/src/Windows/Construction/Common.cpp index 8b2c9b5d8a..e3627bb546 100644 --- a/src/OpenLoco/src/Windows/Construction/Common.cpp +++ b/src/OpenLoco/src/Windows/Construction/Common.cpp @@ -1477,20 +1477,20 @@ namespace OpenLoco::Ui::Windows::Construction auto currentYear = getCurrentYear(); auto trackObj = ObjectManager::get(trackType); auto signalCount = 0; - auto var_0E = trackObj->var_0E; - while (var_0E > 0) + auto signals = trackObj->signals; + while (signals > 0) { - auto ecx = Utility::bitScanForward(var_0E); - if (ecx == -1) + const auto signalId = Utility::bitScanForward(signals); + if (signalId == -1) break; - var_0E &= ~(1 << ecx); - auto signalObj = ObjectManager::get(ecx); + signals &= ~(1 << signalId); + auto signalObj = ObjectManager::get(signalId); if (currentYear > signalObj->obsoleteYear) continue; if (currentYear < signalObj->designedYear) continue; - signalList[signalCount] = ecx; + signalList[signalCount] = signalId; signalCount++; }