diff --git a/OpenRCT2.xcodeproj/project.pbxproj b/OpenRCT2.xcodeproj/project.pbxproj index 9d130b35e3..0b1f92a418 100644 --- a/OpenRCT2.xcodeproj/project.pbxproj +++ b/OpenRCT2.xcodeproj/project.pbxproj @@ -193,7 +193,6 @@ D44272341CC81B3200D84D28 /* network.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44271521CC81B3200D84D28 /* network.cpp */; }; D44272351CC81B3200D84D28 /* twitch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44271541CC81B3200D84D28 /* twitch.cpp */; }; D44272361CC81B3200D84D28 /* object_list.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271561CC81B3200D84D28 /* object_list.c */; }; - D44272371CC81B3200D84D28 /* object.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271571CC81B3200D84D28 /* object.c */; }; D44272381CC81B3200D84D28 /* openrct2.c in Sources */ = {isa = PBXBuildFile; fileRef = D44271591CC81B3200D84D28 /* openrct2.c */; }; D44272391CC81B3200D84D28 /* peep.c in Sources */ = {isa = PBXBuildFile; fileRef = D442715C1CC81B3200D84D28 /* peep.c */; }; D442723A1CC81B3200D84D28 /* staff.c in Sources */ = {isa = PBXBuildFile; fileRef = D442715E1CC81B3200D84D28 /* staff.c */; }; @@ -320,6 +319,27 @@ D45A395E1CF300AF00659A24 /* libSDL2.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B81CF3006400659A24 /* libSDL2.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D45A395F1CF300AF00659A24 /* libspeexdsp.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = D45A38B91CF3006400659A24 /* libspeexdsp.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; D45B202D1D1E92DB00B67CC7 /* custom_currency.c in Sources */ = {isa = PBXBuildFile; fileRef = D45B202C1D1E92DB00B67CC7 /* custom_currency.c */; }; + D464FEB91D31A64100CBABAC /* FileEnumerator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEB71D31A64100CBABAC /* FileEnumerator.cpp */; }; + D464FEBB1D31A65300CBABAC /* IStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEBA1D31A65300CBABAC /* IStream.cpp */; }; + D464FEBE1D31A66E00CBABAC /* MemoryStream.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEBC1D31A66E00CBABAC /* MemoryStream.cpp */; }; + D464FEC01D31A68800CBABAC /* Image.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEBF1D31A68800CBABAC /* Image.cpp */; }; + D464FEE51D31A6AA00CBABAC /* BannerObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEC21D31A6AA00CBABAC /* BannerObject.cpp */; }; + D464FEE61D31A6AA00CBABAC /* EntranceObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEC41D31A6AA00CBABAC /* EntranceObject.cpp */; }; + D464FEE71D31A6AA00CBABAC /* FootpathItemObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEC61D31A6AA00CBABAC /* FootpathItemObject.cpp */; }; + D464FEE81D31A6AA00CBABAC /* FootpathObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEC81D31A6AA00CBABAC /* FootpathObject.cpp */; }; + D464FEE91D31A6AA00CBABAC /* ImageTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FECA1D31A6AA00CBABAC /* ImageTable.cpp */; }; + D464FEEA1D31A6AA00CBABAC /* LargeSceneryObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FECC1D31A6AA00CBABAC /* LargeSceneryObject.cpp */; }; + D464FEEB1D31A6AA00CBABAC /* Object.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FECE1D31A6AA00CBABAC /* Object.cpp */; }; + D464FEEC1D31A6AA00CBABAC /* ObjectFactory.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FED01D31A6AA00CBABAC /* ObjectFactory.cpp */; }; + D464FEED1D31A6AA00CBABAC /* ObjectManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FED21D31A6AA00CBABAC /* ObjectManager.cpp */; }; + D464FEEE1D31A6AA00CBABAC /* ObjectRepository.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FED41D31A6AA00CBABAC /* ObjectRepository.cpp */; }; + D464FEEF1D31A6AA00CBABAC /* RideObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FED61D31A6AA00CBABAC /* RideObject.cpp */; }; + D464FEF01D31A6AA00CBABAC /* SceneryGroupObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FED81D31A6AA00CBABAC /* SceneryGroupObject.cpp */; }; + D464FEF11D31A6AA00CBABAC /* SmallSceneryObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEDB1D31A6AA00CBABAC /* SmallSceneryObject.cpp */; }; + D464FEF21D31A6AA00CBABAC /* StexObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEDD1D31A6AA00CBABAC /* StexObject.cpp */; }; + D464FEF31D31A6AA00CBABAC /* StringTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEDF1D31A6AA00CBABAC /* StringTable.cpp */; }; + D464FEF41D31A6AA00CBABAC /* WallObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEE11D31A6AA00CBABAC /* WallObject.cpp */; }; + D464FEF51D31A6AA00CBABAC /* WaterObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D464FEE31D31A6AA00CBABAC /* WaterObject.cpp */; }; D47304D51C4FF8250015C0EA /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D47304D41C4FF8250015C0EA /* libz.tbd */; }; D48A8D831D00272F00649DA7 /* TcpSocket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D48A8D811D00272F00649DA7 /* TcpSocket.cpp */; }; D49766831D03B9FE002222CD /* SoftwareDrawingEngine.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D49766811D03B9FE002222CD /* SoftwareDrawingEngine.cpp */; }; @@ -527,7 +547,6 @@ D44270E61CC81B3200D84D28 /* FileStream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = FileStream.hpp; sourceTree = ""; usesTabs = 0; }; D44270E71CC81B3200D84D28 /* Guard.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Guard.cpp; sourceTree = ""; usesTabs = 0; }; D44270E81CC81B3200D84D28 /* Guard.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Guard.hpp; sourceTree = ""; usesTabs = 0; }; - D44270E91CC81B3200D84D28 /* IDisposable.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IDisposable.hpp; sourceTree = ""; usesTabs = 0; }; D44270EA1CC81B3200D84D28 /* IStream.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = IStream.hpp; sourceTree = ""; usesTabs = 0; }; D44270EB1CC81B3200D84D28 /* Json.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Json.cpp; sourceTree = ""; usesTabs = 0; }; D44270EC1CC81B3200D84D28 /* Json.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = Json.hpp; sourceTree = ""; usesTabs = 0; }; @@ -629,7 +648,6 @@ D44271541CC81B3200D84D28 /* twitch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = twitch.cpp; sourceTree = ""; usesTabs = 0; }; D44271551CC81B3200D84D28 /* twitch.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = twitch.h; sourceTree = ""; usesTabs = 0; }; D44271561CC81B3200D84D28 /* object_list.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = object_list.c; sourceTree = ""; }; - D44271571CC81B3200D84D28 /* object.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = object.c; sourceTree = ""; }; D44271581CC81B3200D84D28 /* object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = object.h; sourceTree = ""; }; D44271591CC81B3200D84D28 /* openrct2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = openrct2.c; sourceTree = ""; }; D442715A1CC81B3200D84D28 /* openrct2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = openrct2.h; sourceTree = ""; }; @@ -935,6 +953,47 @@ D45A39571CF3007A00659A24 /* speexdsp_config_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = speexdsp_config_types.h; sourceTree = ""; }; D45A39581CF3007A00659A24 /* speexdsp_types.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = speexdsp_types.h; sourceTree = ""; }; D45B202C1D1E92DB00B67CC7 /* custom_currency.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = custom_currency.c; sourceTree = ""; }; + D464FEB71D31A64100CBABAC /* FileEnumerator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileEnumerator.cpp; sourceTree = ""; }; + D464FEB81D31A64100CBABAC /* FileEnumerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileEnumerator.h; sourceTree = ""; }; + D464FEBA1D31A65300CBABAC /* IStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = IStream.cpp; sourceTree = ""; }; + D464FEBC1D31A66E00CBABAC /* MemoryStream.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = MemoryStream.cpp; sourceTree = ""; }; + D464FEBD1D31A66E00CBABAC /* MemoryStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MemoryStream.h; sourceTree = ""; }; + D464FEBF1D31A68800CBABAC /* Image.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Image.cpp; sourceTree = ""; }; + D464FEC21D31A6AA00CBABAC /* BannerObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = BannerObject.cpp; sourceTree = ""; }; + D464FEC31D31A6AA00CBABAC /* BannerObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BannerObject.h; sourceTree = ""; }; + D464FEC41D31A6AA00CBABAC /* EntranceObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EntranceObject.cpp; sourceTree = ""; }; + D464FEC51D31A6AA00CBABAC /* EntranceObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EntranceObject.h; sourceTree = ""; }; + D464FEC61D31A6AA00CBABAC /* FootpathItemObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FootpathItemObject.cpp; sourceTree = ""; }; + D464FEC71D31A6AA00CBABAC /* FootpathItemObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FootpathItemObject.h; sourceTree = ""; }; + D464FEC81D31A6AA00CBABAC /* FootpathObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FootpathObject.cpp; sourceTree = ""; }; + D464FEC91D31A6AA00CBABAC /* FootpathObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FootpathObject.h; sourceTree = ""; }; + D464FECA1D31A6AA00CBABAC /* ImageTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ImageTable.cpp; sourceTree = ""; }; + D464FECB1D31A6AA00CBABAC /* ImageTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ImageTable.h; sourceTree = ""; }; + D464FECC1D31A6AA00CBABAC /* LargeSceneryObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LargeSceneryObject.cpp; sourceTree = ""; }; + D464FECD1D31A6AA00CBABAC /* LargeSceneryObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LargeSceneryObject.h; sourceTree = ""; }; + D464FECE1D31A6AA00CBABAC /* Object.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Object.cpp; sourceTree = ""; }; + D464FECF1D31A6AA00CBABAC /* Object.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Object.h; sourceTree = ""; }; + D464FED01D31A6AA00CBABAC /* ObjectFactory.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectFactory.cpp; sourceTree = ""; }; + D464FED11D31A6AA00CBABAC /* ObjectFactory.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectFactory.h; sourceTree = ""; }; + D464FED21D31A6AA00CBABAC /* ObjectManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectManager.cpp; sourceTree = ""; }; + D464FED31D31A6AA00CBABAC /* ObjectManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectManager.h; sourceTree = ""; }; + D464FED41D31A6AA00CBABAC /* ObjectRepository.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ObjectRepository.cpp; sourceTree = ""; }; + D464FED51D31A6AA00CBABAC /* ObjectRepository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectRepository.h; sourceTree = ""; }; + D464FED61D31A6AA00CBABAC /* RideObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RideObject.cpp; sourceTree = ""; }; + D464FED71D31A6AA00CBABAC /* RideObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RideObject.h; sourceTree = ""; }; + D464FED81D31A6AA00CBABAC /* SceneryGroupObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SceneryGroupObject.cpp; sourceTree = ""; }; + D464FED91D31A6AA00CBABAC /* SceneryGroupObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneryGroupObject.h; sourceTree = ""; }; + D464FEDA1D31A6AA00CBABAC /* SceneryObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SceneryObject.h; sourceTree = ""; }; + D464FEDB1D31A6AA00CBABAC /* SmallSceneryObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SmallSceneryObject.cpp; sourceTree = ""; }; + D464FEDC1D31A6AA00CBABAC /* SmallSceneryObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmallSceneryObject.h; sourceTree = ""; }; + D464FEDD1D31A6AA00CBABAC /* StexObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StexObject.cpp; sourceTree = ""; }; + D464FEDE1D31A6AA00CBABAC /* StexObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StexObject.h; sourceTree = ""; }; + D464FEDF1D31A6AA00CBABAC /* StringTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StringTable.cpp; sourceTree = ""; }; + D464FEE01D31A6AA00CBABAC /* StringTable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StringTable.h; sourceTree = ""; }; + D464FEE11D31A6AA00CBABAC /* WallObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WallObject.cpp; sourceTree = ""; }; + D464FEE21D31A6AA00CBABAC /* WallObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WallObject.h; sourceTree = ""; }; + D464FEE31D31A6AA00CBABAC /* WaterObject.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WaterObject.cpp; sourceTree = ""; }; + D464FEE41D31A6AA00CBABAC /* WaterObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WaterObject.h; sourceTree = ""; }; D47304D41C4FF8250015C0EA /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; D4895D321C23EFDD000CD788 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = distribution/osx/Info.plist; sourceTree = SOURCE_ROOT; }; D48A8D811D00272F00649DA7 /* TcpSocket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TcpSocket.cpp; sourceTree = ""; usesTabs = 0; }; @@ -1166,6 +1225,7 @@ D44271321CC81B3200D84D28 /* localisation */, D44271441CC81B3200D84D28 /* management */, D442714F1CC81B3200D84D28 /* network */, + D464FEC11D31A6AA00CBABAC /* object */, C686F8961CDBC37E009F9BFC /* paint */, D442715B1CC81B3200D84D28 /* peep */, D44271601CC81B3200D84D28 /* platform */, @@ -1188,7 +1248,6 @@ D44271161CC81B3200D84D28 /* input.c */, D44271301CC81B3200D84D28 /* intro.c */, D44271561CC81B3200D84D28 /* object_list.c */, - D44271571CC81B3200D84D28 /* object.c */, D44271591CC81B3200D84D28 /* openrct2.c */, D44271691CC81B3200D84D28 /* rct1.c */, D442716B1CC81B3200D84D28 /* rct2.c */, @@ -1293,16 +1352,20 @@ D44270E31CC81B3200D84D28 /* Diagnostics.cpp */, D44270E41CC81B3200D84D28 /* Diagnostics.hpp */, D44270E51CC81B3200D84D28 /* Exception.hpp */, + D464FEB71D31A64100CBABAC /* FileEnumerator.cpp */, + D464FEB81D31A64100CBABAC /* FileEnumerator.h */, D44270E61CC81B3200D84D28 /* FileStream.hpp */, D44270E71CC81B3200D84D28 /* Guard.cpp */, D44270E81CC81B3200D84D28 /* Guard.hpp */, - D44270E91CC81B3200D84D28 /* IDisposable.hpp */, + D464FEBA1D31A65300CBABAC /* IStream.cpp */, D44270EA1CC81B3200D84D28 /* IStream.hpp */, D44270EB1CC81B3200D84D28 /* Json.cpp */, D44270EC1CC81B3200D84D28 /* Json.hpp */, D44270ED1CC81B3200D84D28 /* List.hpp */, D44270EE1CC81B3200D84D28 /* Math.hpp */, D44270EF1CC81B3200D84D28 /* Memory.hpp */, + D464FEBC1D31A66E00CBABAC /* MemoryStream.cpp */, + D464FEBD1D31A66E00CBABAC /* MemoryStream.h */, D44270F01CC81B3200D84D28 /* Path.cpp */, D44270F11CC81B3200D84D28 /* Path.hpp */, D44270F21CC81B3200D84D28 /* Stopwatch.cpp */, @@ -1330,6 +1393,7 @@ D44271051CC81B3200D84D28 /* font.h */, D497668A1D03BAC8002222CD /* IDrawingContext.h */, D497668B1D03BAC8002222CD /* IDrawingEngine.h */, + D464FEBF1D31A68800CBABAC /* Image.cpp */, D44271061CC81B3200D84D28 /* line.c */, D49766841D03BAA5002222CD /* NewDrawing.cpp */, D49766851D03BAA5002222CD /* NewDrawing.h */, @@ -1805,6 +1869,48 @@ path = speex; sourceTree = ""; }; + D464FEC11D31A6AA00CBABAC /* object */ = { + isa = PBXGroup; + children = ( + D464FEC21D31A6AA00CBABAC /* BannerObject.cpp */, + D464FEC31D31A6AA00CBABAC /* BannerObject.h */, + D464FEC41D31A6AA00CBABAC /* EntranceObject.cpp */, + D464FEC51D31A6AA00CBABAC /* EntranceObject.h */, + D464FEC61D31A6AA00CBABAC /* FootpathItemObject.cpp */, + D464FEC71D31A6AA00CBABAC /* FootpathItemObject.h */, + D464FEC81D31A6AA00CBABAC /* FootpathObject.cpp */, + D464FEC91D31A6AA00CBABAC /* FootpathObject.h */, + D464FECA1D31A6AA00CBABAC /* ImageTable.cpp */, + D464FECB1D31A6AA00CBABAC /* ImageTable.h */, + D464FECC1D31A6AA00CBABAC /* LargeSceneryObject.cpp */, + D464FECD1D31A6AA00CBABAC /* LargeSceneryObject.h */, + D464FECE1D31A6AA00CBABAC /* Object.cpp */, + D464FECF1D31A6AA00CBABAC /* Object.h */, + D464FED01D31A6AA00CBABAC /* ObjectFactory.cpp */, + D464FED11D31A6AA00CBABAC /* ObjectFactory.h */, + D464FED21D31A6AA00CBABAC /* ObjectManager.cpp */, + D464FED31D31A6AA00CBABAC /* ObjectManager.h */, + D464FED41D31A6AA00CBABAC /* ObjectRepository.cpp */, + D464FED51D31A6AA00CBABAC /* ObjectRepository.h */, + D464FED61D31A6AA00CBABAC /* RideObject.cpp */, + D464FED71D31A6AA00CBABAC /* RideObject.h */, + D464FED81D31A6AA00CBABAC /* SceneryGroupObject.cpp */, + D464FED91D31A6AA00CBABAC /* SceneryGroupObject.h */, + D464FEDA1D31A6AA00CBABAC /* SceneryObject.h */, + D464FEDB1D31A6AA00CBABAC /* SmallSceneryObject.cpp */, + D464FEDC1D31A6AA00CBABAC /* SmallSceneryObject.h */, + D464FEDD1D31A6AA00CBABAC /* StexObject.cpp */, + D464FEDE1D31A6AA00CBABAC /* StexObject.h */, + D464FEDF1D31A6AA00CBABAC /* StringTable.cpp */, + D464FEE01D31A6AA00CBABAC /* StringTable.h */, + D464FEE11D31A6AA00CBABAC /* WallObject.cpp */, + D464FEE21D31A6AA00CBABAC /* WallObject.h */, + D464FEE31D31A6AA00CBABAC /* WaterObject.cpp */, + D464FEE41D31A6AA00CBABAC /* WaterObject.h */, + ); + path = object; + sourceTree = ""; + }; D497667F1D03B9FE002222CD /* engines */ = { isa = PBXGroup; children = ( @@ -2063,6 +2169,8 @@ C686F9381CDBC3B7009F9BFC /* observation_tower.c in Sources */, D44272A41CC81B3200D84D28 /* park.c in Sources */, C686F9391CDBC3B7009F9BFC /* space_rings.c in Sources */, + D464FEEF1D31A6AA00CBABAC /* RideObject.cpp in Sources */, + D464FEEA1D31A6AA00CBABAC /* LargeSceneryObject.cpp in Sources */, C686F9371CDBC3B7009F9BFC /* monorail_cycles.c in Sources */, D49766891D03BABB002222CD /* rain.cpp in Sources */, D44272441CC81B3200D84D28 /* cable_lift.c in Sources */, @@ -2098,12 +2206,14 @@ C686F9341CDBC3B7009F9BFC /* merry_go_round.c in Sources */, C686F8B81CDBC37E009F9BFC /* sprite.c in Sources */, D45B202D1D1E92DB00B67CC7 /* custom_currency.c in Sources */, + D464FEEB1D31A6AA00CBABAC /* Object.cpp in Sources */, C686F9221CDBC3B7009F9BFC /* stand_up_roller_coaster.c in Sources */, C686F94F1CDBC3B7009F9BFC /* dingy_slide.c in Sources */, D44272341CC81B3200D84D28 /* network.cpp in Sources */, C686F9151CDBC3B7009F9BFC /* junior_roller_coaster.c in Sources */, C686F9131CDBC3B7009F9BFC /* inverted_impulse_coaster.c in Sources */, D44272001CC81B3200D84D28 /* Diagnostics.cpp in Sources */, + D464FEF11D31A6AA00CBABAC /* SmallSceneryObject.cpp in Sources */, D44272471CC81B3200D84D28 /* ride_ratings.c in Sources */, D44272721CC81B3200D84D28 /* new_campaign.c in Sources */, C686F93A1CDBC3B7009F9BFC /* spiral_slide.c in Sources */, @@ -2120,10 +2230,12 @@ C686F9461CDBC3B7009F9BFC /* swinging_inverter_ship.c in Sources */, D442720B1CC81B3200D84D28 /* font.c in Sources */, C686F90D1CDBC3B7009F9BFC /* compact_inverted_coaster.c in Sources */, + D464FEF31D31A6AA00CBABAC /* StringTable.cpp in Sources */, C686F8B01CDBC37E009F9BFC /* path.c in Sources */, D44272361CC81B3200D84D28 /* object_list.c in Sources */, D44272581CC81B3200D84D28 /* demolish_ride_prompt.c in Sources */, D442723E1CC81B3200D84D28 /* posix.c in Sources */, + D464FEB91D31A64100CBABAC /* FileEnumerator.cpp in Sources */, D44271F91CC81B3200D84D28 /* CommandLine.cpp in Sources */, 00EFEE721CF1D80B0035213B /* NetworkKey.cpp in Sources */, C686F90B1CDBC3B7009F9BFC /* air_powered_vertical_coaster.c in Sources */, @@ -2136,6 +2248,8 @@ D44272381CC81B3200D84D28 /* openrct2.c in Sources */, C686F92C1CDBC3B7009F9BFC /* circus_show.c in Sources */, D44271FD1CC81B3200D84D28 /* cmdline_sprite.c in Sources */, + D464FEEE1D31A6AA00CBABAC /* ObjectRepository.cpp in Sources */, + D464FEF51D31A6AA00CBABAC /* WaterObject.cpp in Sources */, D442720C1CC81B3200D84D28 /* line.c in Sources */, C686F9321CDBC3B7009F9BFC /* haunted_house.c in Sources */, D442720A1CC81B3200D84D28 /* drawing_fast.cpp in Sources */, @@ -2144,20 +2258,22 @@ C61FB7241CF86356004CE991 /* NetworkUser.cpp in Sources */, D44272191CC81B3200D84D28 /* colour.c in Sources */, D442724C1CC81B3200D84D28 /* vehicle.c in Sources */, + D464FEED1D31A6AA00CBABAC /* ObjectManager.cpp in Sources */, D44272261CC81B3200D84D28 /* currency.c in Sources */, C686F92B1CDBC3B7009F9BFC /* car_ride.c in Sources */, D44272921CC81B3200D84D28 /* tooltip.c in Sources */, C686F91C1CDBC3B7009F9BFC /* mini_suspended_coaster.c in Sources */, D44271FA1CC81B3200D84D28 /* RootCommands.cpp in Sources */, D442726B1CC81B3200D84D28 /* map.c in Sources */, + D464FEE71D31A6AA00CBABAC /* FootpathItemObject.cpp in Sources */, D442721E1CC81B3200D84D28 /* Theme.cpp in Sources */, C686F9271CDBC3B7009F9BFC /* virginia_reel.c in Sources */, D44272271CC81B3200D84D28 /* date.c in Sources */, - D44272371CC81B3200D84D28 /* object.c in Sources */, D44272971CC81B3200D84D28 /* viewport.c in Sources */, C686F90F1CDBC3B7009F9BFC /* flying_roller_coaster.c in Sources */, D44272661CC81B3200D84D28 /* install_track.c in Sources */, D49766831D03B9FE002222CD /* SoftwareDrawingEngine.cpp in Sources */, + D464FEF21D31A6AA00CBABAC /* StexObject.cpp in Sources */, C686F9331CDBC3B7009F9BFC /* maze.c in Sources */, C686F9241CDBC3B7009F9BFC /* suspended_swinging_coaster.c in Sources */, C686F92D1CDBC3B7009F9BFC /* crooked_house.c in Sources */, @@ -2166,6 +2282,7 @@ D442723A1CC81B3200D84D28 /* staff.c in Sources */, D44272931CC81B3200D84D28 /* top_toolbar.c in Sources */, D43407DA1D0E14BE00C2B3D4 /* FillRectShader.cpp in Sources */, + D464FEBE1D31A66E00CBABAC /* MemoryStream.cpp in Sources */, D44271F61CC81B3200D84D28 /* audio.c in Sources */, D442728A1CC81B3200D84D28 /* tile_inspector.c in Sources */, D43407D91D0E14BE00C2B3D4 /* DrawLineShader.cpp in Sources */, @@ -2230,11 +2347,14 @@ C686F9431CDBC3B7009F9BFC /* motion_simulator.c in Sources */, D44272281CC81B3200D84D28 /* language.cpp in Sources */, D442729B1CC81B3200D84D28 /* climate.c in Sources */, + D464FEC01D31A68800CBABAC /* Image.cpp in Sources */, C686F9261CDBC3B7009F9BFC /* vertical_drop_roller_coaster.c in Sources */, + D464FEE91D31A6AA00CBABAC /* ImageTable.cpp in Sources */, D442726F1CC81B3200D84D28 /* multiplayer.c in Sources */, 007A05D21CFB2C8B00F419C3 /* NetworkPlayer.cpp in Sources */, D442725B1CC81B3200D84D28 /* editor_inventions_list.c in Sources */, D44272311CC81B3200D84D28 /* news_item.c in Sources */, + D464FEF41D31A6AA00CBABAC /* WallObject.cpp in Sources */, D44272011CC81B3200D84D28 /* Guard.cpp in Sources */, D43407E01D0E14BE00C2B3D4 /* TextureCache.cpp in Sources */, D44272951CC81B3200D84D28 /* track_manage.c in Sources */, @@ -2255,6 +2375,7 @@ C686F8B11CDBC37E009F9BFC /* scenery.c in Sources */, C686F9251CDBC3B7009F9BFC /* twister_roller_coaster.c in Sources */, D44272251CC81B3200D84D28 /* convert.c in Sources */, + D464FEEC1D31A6AA00CBABAC /* ObjectFactory.cpp in Sources */, D442727B1CC81B3200D84D28 /* ride.c in Sources */, D44272031CC81B3200D84D28 /* Path.cpp in Sources */, C686F9301CDBC3B7009F9BFC /* flying_saucers.c in Sources */, @@ -2267,6 +2388,7 @@ 007A05D01CFB2C8B00F419C3 /* NetworkGroup.cpp in Sources */, C686F9491CDBC3B7009F9BFC /* chairlift.c in Sources */, C686F9501CDBC3B7009F9BFC /* log_flume.c in Sources */, + D464FEE81D31A6AA00CBABAC /* FootpathObject.cpp in Sources */, D43407D61D0E14BE00C2B3D4 /* CopyFramebufferShader.cpp in Sources */, D44272241CC81B3200D84D28 /* intro.c in Sources */, D44272631CC81B3200D84D28 /* game_bottom_toolbar.c in Sources */, @@ -2284,14 +2406,17 @@ D44272731CC81B3200D84D28 /* new_ride.c in Sources */, D442721A1CC81B3200D84D28 /* console.c in Sources */, D44271FB1CC81B3200D84D28 /* ScreenshotCommands.cpp in Sources */, + D464FEBB1D31A65300CBABAC /* IStream.cpp in Sources */, D442729E1CC81B3200D84D28 /* fountain.c in Sources */, C686F94B1CDBC3B7009F9BFC /* minature_railway.c in Sources */, C686F94D1CDBC3B7009F9BFC /* suspended_monorail.c in Sources */, D44272131CC81B3200D84D28 /* editor.c in Sources */, D442729F1CC81B3200D84D28 /* map.c in Sources */, + D464FEF01D31A6AA00CBABAC /* SceneryGroupObject.cpp in Sources */, C686F9351CDBC3B7009F9BFC /* mini_golf.c in Sources */, D44272541CC81B3200D84D28 /* banner.c in Sources */, D44272A31CC81B3200D84D28 /* money_effect.c in Sources */, + D464FEE61D31A6AA00CBABAC /* EntranceObject.cpp in Sources */, D43407DE1D0E14BE00C2B3D4 /* OpenGLShaderProgram.cpp in Sources */, D43407DC1D0E14BE00C2B3D4 /* OpenGLDrawingEngine.cpp in Sources */, D44272761CC81B3200D84D28 /* options.c in Sources */, @@ -2327,6 +2452,7 @@ C686F9361CDBC3B7009F9BFC /* mini_helicopters.c in Sources */, C686F93F1CDBC3B7009F9BFC /* enterprise.c in Sources */, C686F91B1CDBC3B7009F9BFC /* mini_roller_coaster.c in Sources */, + D464FEE51D31A6AA00CBABAC /* BannerObject.cpp in Sources */, D44272391CC81B3200D84D28 /* peep.c in Sources */, D44272681CC81B3200D84D28 /* land_rights.c in Sources */, D44272601CC81B3200D84D28 /* error.c in Sources */, diff --git a/distribution/changelog.txt b/distribution/changelog.txt index 3d0f127baf..08a630a481 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -16,6 +16,9 @@ - Feature: Add Cheat that allows any track piece to use a chain lift. - Feature: Add Console command to set vehicle friction. - Feature: Add console command to set scenario initial cash. +- Feature: Objects are scanned from the user directory as well as the RCT2 directory. +- Feature: Objects directory is scanned recursively. +- Improve: Performance and reliability of loading objects. - Removed: BMP screenshots. - Removed: Intamin and Phoenix easter eggs. - Fix: [#1038] Guest List is out of order. diff --git a/openrct2.vcxproj b/openrct2.vcxproj index 1561101908..5d22df490c 100644 --- a/openrct2.vcxproj +++ b/openrct2.vcxproj @@ -42,8 +42,11 @@ + + + @@ -65,6 +68,7 @@ + @@ -115,7 +119,23 @@ - + + + + + + + + + + + + + + + + + @@ -337,14 +357,15 @@ + - + @@ -416,6 +437,24 @@ + + + + + + + + + + + + + + + + + + @@ -569,4 +608,4 @@ - + \ No newline at end of file diff --git a/src/addresses.h b/src/addresses.h index dce3c23723..5df03c3187 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -98,10 +98,6 @@ #define RCT2_ADDRESS_WATER_ENTRIES 0x009ADAE0 #define RCT2_ADDRESS_SCENARIO_TEXT_ENTRIES 0x009ADAE4 -#define RCT2_ADDRESS_INSTALLED_OBJECT_LIST 0x009ADAE8 -#define RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST 0x009ADAEC -#define RCT2_ADDRESS_TOTAL_NO_IMAGES 0x009ADAF0 - #define RCT2_ADDRESS_CURRENT_SOUND_DEVICE 0x009AF280 #define RCT2_ADDRESS_VEHICLE_SOUND_LIST 0x009AF288 @@ -479,6 +475,10 @@ #define RCT2_ADDRESS_RUN_INTRO_TICK_PART 0x009AC319 #define RCT2_ADDRESS_WINDOW_MAP_FLASHING_FLAGS 0x009AC861 +#define RCT2_ADDRESS_INSTALLED_OBJECT_LIST 0x009ADAE8 +#define RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST 0x009ADAEC +#define RCT2_ADDRESS_TOTAL_NO_IMAGES 0x009ADAF0 + #define RCT2_ADDRESS_TOOL_WINDOWNUMBER 0x009DE542 #define RCT2_ADDRESS_TOOL_WINDOWCLASS 0x009DE544 #define RCT2_ADDRESS_CURRENT_TOOL 0x009DE545 diff --git a/src/cmdline/CommandLine.cpp b/src/cmdline/CommandLine.cpp index d3801122b6..1a65a055e2 100644 --- a/src/cmdline/CommandLine.cpp +++ b/src/cmdline/CommandLine.cpp @@ -397,8 +397,7 @@ namespace CommandLine const CommandLineOptionDefinition * option = FindOption(options, optionName); if (option == nullptr) { - Console::Error::Write("Unknown option: --"); - Console::Error::WriteLine(optionName); + Console::Error::WriteLine("Unknown option: --%s", optionName); return false; } @@ -413,8 +412,7 @@ namespace CommandLine const char * valueString = nullptr; if (!argEnumerator->TryPopString(&valueString)) { - Console::Error::Write("Expected value for option: "); - Console::Error::WriteLine(optionName); + Console::Error::WriteLine("Expected value for option: %s", optionName); return false; } @@ -428,8 +426,7 @@ namespace CommandLine { if (option->Type == CMDLINE_TYPE_SWITCH) { - Console::Error::Write("Option is a switch: "); - Console::Error::WriteLine(optionName); + Console::Error::WriteLine("Option is a switch: %s", optionName); return false; } else @@ -456,9 +453,7 @@ namespace CommandLine option = FindOption(options, shortOption[0]); if (option == nullptr) { - Console::Error::Write("Unknown option: -"); - Console::Error::Write(shortOption[0]); - Console::Error::WriteLine(); + Console::Error::WriteLine("Unknown option: -%c", shortOption[0]); return false; } if (option->Type == CMDLINE_TYPE_SWITCH) @@ -483,9 +478,7 @@ namespace CommandLine const char * valueString = nullptr; if (!argEnumerator->TryPopString(&valueString)) { - Console::Error::Write("Expected value for option: "); - Console::Error::Write(option->ShortName); - Console::Error::WriteLine(); + Console::Error::WriteLine("Expected value for option: %c", option->ShortName); return false; } @@ -516,8 +509,7 @@ namespace CommandLine *((utf8 * *)option->OutAddress) = String::Duplicate(valueString); return true; default: - Console::Error::Write("Unknown CMDLINE_TYPE for: "); - Console::Error::WriteLine(option->LongName); + Console::Error::WriteLine("Unknown CMDLINE_TYPE for: %s", option->LongName); return false; } } diff --git a/src/cmdline/RootCommands.cpp b/src/cmdline/RootCommands.cpp index 64af14926c..1e59966549 100644 --- a/src/cmdline/RootCommands.cpp +++ b/src/cmdline/RootCommands.cpp @@ -312,8 +312,7 @@ static exitcode_t HandleCommandSetRCT2(CommandLineArgEnumerator * enumerator) Console::WriteLine("Checking path..."); if (!platform_directory_exists(path)) { - Console::Error::WriteFormat("The path '%s' does not exist", path); - Console::Error::WriteLine(); + Console::Error::WriteLine("The path '%s' does not exist", path); return EXITCODE_FAIL; } @@ -327,8 +326,7 @@ static exitcode_t HandleCommandSetRCT2(CommandLineArgEnumerator * enumerator) if (!platform_file_exists(pathG1Check)) { Console::Error::WriteLine("RCT2 path not valid."); - Console::Error::WriteFormat("Unable to find %s.", pathG1Check); - Console::Error::WriteLine(); + Console::Error::WriteLine("Unable to find %s.", pathG1Check); return EXITCODE_FAIL; } @@ -337,8 +335,7 @@ static exitcode_t HandleCommandSetRCT2(CommandLineArgEnumerator * enumerator) platform_resolve_user_data_path(); platform_get_user_directory(userPath, NULL); if (!platform_ensure_directory_exists(userPath)) { - Console::Error::WriteFormat("Unable to access or create directory '%s'.", userPath); - Console::Error::WriteLine(); + Console::Error::WriteLine("Unable to access or create directory '%s'.", userPath); return EXITCODE_FAIL; } diff --git a/src/core/Console.cpp b/src/core/Console.cpp index f1b8c937b2..d82064022d 100644 --- a/src/core/Console.cpp +++ b/src/core/Console.cpp @@ -91,10 +91,14 @@ namespace Console fputs(platform_get_new_line(), stderr); } - void WriteLine(const utf8 * str) + void WriteLine(const utf8 * format, ...) { - fputs(str, stderr); - WriteLine(); + va_list args; + + va_start(args, format); + vfprintf(stdout, format, args); + puts(""); + va_end(args); } } } \ No newline at end of file diff --git a/src/core/Console.hpp b/src/core/Console.hpp index 64ac7a765b..4f8171235b 100644 --- a/src/core/Console.hpp +++ b/src/core/Console.hpp @@ -36,6 +36,6 @@ namespace Console void Write(const utf8 * str); void WriteFormat(const utf8 * format, ...); void WriteLine(); - void WriteLine(const utf8 * str); + void WriteLine(const utf8 * format, ...); } } diff --git a/src/core/FileEnumerator.cpp b/src/core/FileEnumerator.cpp new file mode 100644 index 0000000000..c934d779c3 --- /dev/null +++ b/src/core/FileEnumerator.cpp @@ -0,0 +1,158 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "FileEnumerator.h" +#include "Memory.hpp" +#include "Path.hpp" +#include "String.hpp" + +extern "C" +{ + #include "../platform/platform.h" +} + +FileEnumerator::FileEnumerator(const utf8 * pattern, bool recurse) +{ + _rootPath = Memory::Allocate(MAX_PATH); + Path::GetDirectory(_rootPath, MAX_PATH, pattern); + _pattern = String::Duplicate(Path::GetFileName(pattern)); + _recurse = recurse; + + _fileInfo = Memory::Allocate(); + Memory::Set(_fileInfo, 0, sizeof(file_info)); + _path = Memory::Allocate(MAX_PATH); + + _fileHandle = INVALID_HANDLE; + + Reset(); +} + +FileEnumerator::~FileEnumerator() +{ + CloseHandles(); + Memory::Free(_path); + Memory::Free(_fileInfo); + Memory::Free(_pattern); + Memory::Free(_rootPath); +} + +void FileEnumerator::Reset() +{ + CloseHandles(); + + DirectoryState directoryState; + directoryState.Directory = String::Duplicate(_rootPath); + directoryState.Handle = INVALID_HANDLE; + _directoryStack.push(directoryState); +} + +bool FileEnumerator::Next() +{ + while (true) + { + while (_fileHandle == INVALID_HANDLE) + { + if (_directoryStack.size() == 0) + { + return false; + } + + DirectoryState directoryState = _directoryStack.top(); + if (directoryState.Handle == INVALID_HANDLE) + { + // Start enumerating files for this directory + utf8 pattern[MAX_PATH]; + String::Set(pattern, sizeof(pattern), directoryState.Directory); + Path::Append(pattern, sizeof(pattern), _pattern); + + _fileHandle = platform_enumerate_files_begin(pattern); + break; + } + else + { + // Next directory + utf8 name[MAX_PATH]; + if (platform_enumerate_directories_next(directoryState.Handle, name)) + { + DirectoryState newDirectoryState; + newDirectoryState.Handle = INVALID_HANDLE; + newDirectoryState.Directory = Memory::Allocate(MAX_PATH); + + String::Set(newDirectoryState.Directory, MAX_PATH, directoryState.Directory); + Path::Append(newDirectoryState.Directory, MAX_PATH, name); + + _directoryStack.push(newDirectoryState); + } + else + { + platform_enumerate_directories_end(directoryState.Handle); + Memory::Free(directoryState.Directory); + _directoryStack.pop(); + } + } + } + + // Next file + if (_fileHandle != INVALID_HANDLE) + { + if (platform_enumerate_files_next(_fileHandle, _fileInfo)) + { + String::Set(_path, MAX_PATH, _directoryStack.top().Directory); + Path::Append(_path, MAX_PATH, _fileInfo->path); + return true; + } + platform_enumerate_files_end(_fileHandle); + _fileHandle = INVALID_HANDLE; + } + + if (_recurse) + { + // Start enumerating sub-directories + DirectoryState * directoryState = &_directoryStack.top(); + directoryState->Handle = platform_enumerate_directories_begin(directoryState->Directory); + if (directoryState->Handle == INVALID_HANDLE) + { + Memory::Free(directoryState->Directory); + _directoryStack.pop(); + } + } + else + { + Memory::Free(_directoryStack.top().Directory); + _directoryStack.pop(); + return false; + } + } +} + +void FileEnumerator::CloseHandles() +{ + if (_fileHandle != INVALID_HANDLE) + { + platform_enumerate_files_end(_fileHandle); + _fileHandle = INVALID_HANDLE; + } + while (_directoryStack.size() > 0) + { + DirectoryState directoryState = _directoryStack.top(); + if (directoryState.Handle != INVALID_HANDLE) + { + platform_enumerate_directories_end(directoryState.Handle); + } + Memory::Free(directoryState.Directory); + _directoryStack.pop(); + } +} diff --git a/src/core/FileEnumerator.h b/src/core/FileEnumerator.h new file mode 100644 index 0000000000..d39b237c14 --- /dev/null +++ b/src/core/FileEnumerator.h @@ -0,0 +1,57 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include "../common.h" + +struct file_info; +class FileEnumerator +{ +private: + struct DirectoryState + { + utf8 * Directory; + int Handle; + }; + + // Enumeration options + utf8 * _rootPath; + utf8 * _pattern; + bool _recurse; + + // Enumeration state + int _fileHandle; + std::stack _directoryStack; + + // Current enumeration + file_info * _fileInfo; + utf8 * _path; + +public: + FileEnumerator(const utf8 * pattern, bool recurse); + ~FileEnumerator(); + + const file_info * GetFileInfo() const { return _fileInfo; } + const utf8 * GetPath() const { return _path; } + + void Reset(); + bool Next(); + +private: + void CloseHandles(); +}; diff --git a/src/core/FileStream.hpp b/src/core/FileStream.hpp index dce4ee866c..4716222a79 100644 --- a/src/core/FileStream.hpp +++ b/src/core/FileStream.hpp @@ -37,6 +37,7 @@ private: bool _canRead; bool _canWrite; bool _disposed; + uint64 _fileSize; public: FileStream(const utf8 * path, int fileMode) @@ -64,14 +65,10 @@ public: } _disposed = false; + _fileSize = SDL_RWsize(_file); } ~FileStream() - { - Dispose(); - } - - void Dispose() override { if (!_disposed) { @@ -83,7 +80,7 @@ public: bool CanRead() const override { return _canRead; } bool CanWrite() const override { return _canWrite; } - uint64 GetLength() const override { return SDL_RWsize(_file); } + uint64 GetLength() const override { return _fileSize; } uint64 GetPosition() const override { return SDL_RWtell(_file); } void SetPosition(uint64 position) override @@ -108,10 +105,15 @@ public: void Read(void * buffer, uint64 length) override { - if (SDL_RWread(_file, buffer, (size_t)length, 1) != 1) + uint64 remainingBytes = GetLength() - GetPosition(); + if (length <= remainingBytes) { - throw IOException("Attempted to read past end of file."); + if (SDL_RWread(_file, buffer, (size_t)length, 1) == 1) + { + return; + } } + throw IOException("Attempted to read past end of file."); } void Write(const void * buffer, uint64 length) override @@ -121,4 +123,10 @@ public: throw IOException("Unable to write to file."); } } + + uint64 TryRead(void * buffer, uint64 length) override + { + size_t readBytes = SDL_RWread(_file, buffer, 1, (size_t)length); + return readBytes; + } }; diff --git a/src/core/Guard.cpp b/src/core/Guard.cpp index 73c7ef2301..f88d871846 100644 --- a/src/core/Guard.cpp +++ b/src/core/Guard.cpp @@ -32,6 +32,19 @@ namespace Guard Console::Error::WriteLine(message); } +#if DEBUG + Debug::Break(); +#endif + assert(false); + } + + void Fail(const char * message) + { + if (message != nullptr) + { + Console::Error::WriteLine(message); + } + #if DEBUG Debug::Break(); #endif diff --git a/src/core/Guard.hpp b/src/core/Guard.hpp index 904aaf6f99..c4ba8cdefa 100644 --- a/src/core/Guard.hpp +++ b/src/core/Guard.hpp @@ -22,6 +22,7 @@ namespace Guard { void Assert(bool expression, const char * message = nullptr); + void Fail(const char * message = nullptr); template void ArgumentNotNull(T * argument, const char * message = nullptr) diff --git a/src/core/IStream.cpp b/src/core/IStream.cpp new file mode 100644 index 0000000000..5e9bf80c30 --- /dev/null +++ b/src/core/IStream.cpp @@ -0,0 +1,49 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include "IStream.hpp" +#include "Memory.hpp" +#include "String.hpp" + +utf8 * IStream::ReadString() +{ + std::vector result; + + uint8 ch; + while ((ch = ReadValue()) != 0) + { + result.push_back(ch); + } + result.push_back(0); + + utf8 * resultString = Memory::AllocateArray(result.size()); + Memory::CopyArray(resultString, result.data(), result.size()); + return resultString; +} + +void IStream::WriteString(utf8 * str) +{ + if (str == nullptr) + { + WriteValue(0); + } + else + { + size_t numBytes = String::SizeOf(str) + 1; + Write(str, numBytes); + } +} diff --git a/src/core/IStream.hpp b/src/core/IStream.hpp index 5240e9b604..4bf2e3e878 100644 --- a/src/core/IStream.hpp +++ b/src/core/IStream.hpp @@ -17,9 +17,8 @@ #pragma once #include "../common.h" - #include "Exception.hpp" -#include "IDisposable.hpp" +#include "Memory.hpp" enum { STREAM_SEEK_BEGIN, @@ -30,12 +29,12 @@ enum { /** * Represents a stream that can be read or written to. Implemented by types such as FileStream, NetworkStream or MemoryStream. */ -interface IStream : public IDisposable +interface IStream { /////////////////////////////////////////////////////////////////////////// // Interface methods /////////////////////////////////////////////////////////////////////////// - // virtual ~IStream() abstract; + virtual ~IStream() { } virtual bool CanRead() const abstract; virtual bool CanWrite() const abstract; @@ -48,6 +47,8 @@ interface IStream : public IDisposable virtual void Read(void * buffer, uint64 length) abstract; virtual void Write(const void * buffer, uint64 length) abstract; + virtual uint64 TryRead(void * buffer, uint64 length) abstract; + /////////////////////////////////////////////////////////////////////////// // Helper methods /////////////////////////////////////////////////////////////////////////// @@ -89,6 +90,23 @@ interface IStream : public IDisposable { Write(&value); } + + template + T * ReadArray(size_t count) + { + T * buffer = Memory::AllocateArray(count); + Read(buffer, sizeof(T) * count); + return buffer; + } + + template + void WriteArray(T * buffer, size_t count) + { + Write(buffer, sizeof(T) * count); + } + + utf8 * ReadString(); + void WriteString(utf8 * str); }; class IOException : public Exception diff --git a/src/core/Memory.hpp b/src/core/Memory.hpp index 4cebdd192a..761bd6b801 100644 --- a/src/core/Memory.hpp +++ b/src/core/Memory.hpp @@ -16,10 +16,8 @@ #pragma once -extern "C" -{ - #include "../common.h" -} +#include "../common.h" +#include "Guard.hpp" /** * Utility methods for memory management. Typically helpers and wrappers around the C standard library. @@ -80,9 +78,23 @@ namespace Memory T * Copy(T * dst, const T * src, size_t size) { if (size == 0) return (T*)dst; +#ifdef DEBUG + uintptr_t srcBegin = (uintptr_t)src; + uintptr_t srcEnd = srcBegin + size; + uintptr_t dstBegin = (uintptr_t)dst; + uintptr_t dstEnd = dstBegin + size; + Guard::Assert(srcEnd <= dstBegin || srcBegin >= dstEnd, "Source overlaps destination, try using Memory::Move."); +#endif return (T*)memcpy((void*)dst, (const void*)src, size); } + template + T * Move(T * dst, const T * src, size_t size) + { + if (size == 0) return (T*)dst; + return (T*)memmove((void*)dst, (const void*)src, size); + } + template T * Duplicate(const T * src, size_t size) { diff --git a/src/core/MemoryStream.cpp b/src/core/MemoryStream.cpp new file mode 100644 index 0000000000..785d1e5ce4 --- /dev/null +++ b/src/core/MemoryStream.cpp @@ -0,0 +1,194 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "Math.hpp" +#include "Memory.hpp" +#include "MemoryStream.h" + +MemoryStream::MemoryStream() +{ + _access = MEMORY_ACCESS_READ | + MEMORY_ACCESS_WRITE | + MEMORY_ACCESS_OWNER; + _dataCapacity = 0; + _dataSize = 0; + _data = nullptr; + _position = nullptr; +} + +MemoryStream::MemoryStream(const MemoryStream ©) +{ + _access = copy._access; + _dataCapacity = copy._dataCapacity; + _dataSize = copy._dataSize; + + if (_access == MEMORY_ACCESS_OWNER) + { + _data = Memory::Duplicate(copy._data, _dataCapacity); + _position = (void*)((uintptr_t)_data + copy.GetPosition()); + } +} + +MemoryStream::MemoryStream(size_t capacity) +{ + _access = MEMORY_ACCESS_READ | + MEMORY_ACCESS_WRITE | + MEMORY_ACCESS_OWNER; + _dataCapacity = capacity; + _dataSize = 0; + _data = Memory::Allocate(capacity); + _position = _data; +} + +MemoryStream::MemoryStream(void * data, size_t dataSize, uint32 access) +{ + _access = access; + _dataCapacity = dataSize; + _dataSize = dataSize; + _data = data; + _position = _data; +} + +MemoryStream::MemoryStream(const void * data, size_t dataSize) + : MemoryStream((void *)data, dataSize, MEMORY_ACCESS_READ) +{ +} + +MemoryStream::~MemoryStream() +{ + if (_access & MEMORY_ACCESS_OWNER) + { + Memory::Free(_data); + } + _dataCapacity = 0; + _dataSize = 0; + _data = nullptr; +} + +void * MemoryStream::GetData() const +{ + return Memory::Duplicate(_data, _dataSize); +} + +void * MemoryStream::TakeData() +{ + _access &= ~MEMORY_ACCESS_OWNER; + return _data; +} + +bool MemoryStream::CanRead() const +{ + return (_access & MEMORY_ACCESS_READ) != 0; +} + +bool MemoryStream::CanWrite() const +{ + return (_access & MEMORY_ACCESS_WRITE) != 0; +} + +uint64 MemoryStream::GetLength() const +{ + return _dataSize; +} + +uint64 MemoryStream::GetPosition() const +{ + return (uint64)((uintptr_t)_position - (uintptr_t)_data); +} + +void MemoryStream::SetPosition(uint64 position) +{ + Seek(position, STREAM_SEEK_BEGIN); +} + +void MemoryStream::Seek(sint64 offset, int origin) +{ + uint64 newPosition; + switch (origin) { + default: + case STREAM_SEEK_BEGIN: + newPosition = offset; + break; + case STREAM_SEEK_CURRENT: + newPosition = GetPosition() + offset; + break; + case STREAM_SEEK_END: + newPosition = _dataSize + offset; + break; + } + + if (newPosition > _dataSize) + { + throw IOException("New position out of bounds."); + } + _position = (void*)((uintptr_t)_data + (uintptr_t)newPosition); +} + +void MemoryStream::Read(void * buffer, uint64 length) +{ + uint64 position = GetPosition(); + if (position + length > _dataSize) + { + throw IOException("Attempted to read past end of stream."); + } + + Memory::Copy(buffer, _position, (size_t)length); + _position = (void*)((uintptr_t)_position + length); +} + +uint64 MemoryStream::TryRead(void * buffer, uint64 length) +{ + uint64 remainingBytes = GetLength() - GetPosition(); + uint64 bytesToRead = Math::Min(length, remainingBytes); + Read(buffer, bytesToRead); + return bytesToRead; +} + +void MemoryStream::Write(const void * buffer, uint64 length) +{ + uint64 position = GetPosition(); + uint64 nextPosition = position + length; + if (nextPosition > _dataCapacity) + { + if (_access & MEMORY_ACCESS_OWNER) + { + EnsureCapacity((size_t)nextPosition); + } + else + { + throw IOException("Attempted to write past end of stream."); + } + } + + Memory::Copy(_position, buffer, (size_t)length); + _position = (void*)((uintptr_t)_position + length); + _dataSize = Math::Max(_dataSize, (size_t)nextPosition); +} + +void MemoryStream::EnsureCapacity(size_t capacity) +{ + if (_dataCapacity < capacity) + { + size_t newCapacity = Math::Max(8, _dataCapacity); + while (newCapacity < capacity) + { + newCapacity *= 2; + } + + _dataCapacity = newCapacity; + Memory::Reallocate(_data, _dataCapacity); + } +} diff --git a/src/core/MemoryStream.h b/src/core/MemoryStream.h new file mode 100644 index 0000000000..762944075b --- /dev/null +++ b/src/core/MemoryStream.h @@ -0,0 +1,70 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" +#include "IStream.hpp" + +enum MEMORY_ACCESS +{ + MEMORY_ACCESS_READ = 1 << 0, + MEMORY_ACCESS_WRITE = 1 << 1, + MEMORY_ACCESS_OWNER = 1 << 2, +}; + +/** + * A stream for reading and writing to files. Wraps an SDL_RWops, SDL2's cross platform file stream. + */ +class MemoryStream : public IStream +{ +private: + uint16 _access; + size_t _dataCapacity; + size_t _dataSize; + void * _data; + void * _position; + +public: + MemoryStream(); + MemoryStream(const MemoryStream ©); + explicit MemoryStream(size_t capacity); + MemoryStream(void * data, size_t dataSize, uint32 access = MEMORY_ACCESS_READ); + MemoryStream(const void * data, size_t dataSize); + virtual ~MemoryStream(); + + void * GetData() const; + void * TakeData(); + + /////////////////////////////////////////////////////////////////////////// + // ISteam methods + /////////////////////////////////////////////////////////////////////////// + bool CanRead() const override; + bool CanWrite() const override; + + uint64 GetLength() const override; + uint64 GetPosition() const override; + void SetPosition(uint64 position) override; + void Seek(sint64 offset, int origin) override; + + void Read(void * buffer, uint64 length) override; + void Write(const void * buffer, uint64 length) override; + + uint64 TryRead(void * buffer, uint64 length) override; + +private: + void EnsureCapacity(size_t capacity); +}; diff --git a/src/core/Path.cpp b/src/core/Path.cpp index 1e368e46d4..b7cab48fd7 100644 --- a/src/core/Path.cpp +++ b/src/core/Path.cpp @@ -74,6 +74,8 @@ namespace Path utf8 * GetFileNameWithoutExtension(utf8 * buffer, size_t bufferSize, const utf8 * path) { + path = GetFileName(path); + const utf8 * lastDot = nullptr; const utf8 * ch = path; for (; *ch != '\0'; ch++) diff --git a/src/core/String.cpp b/src/core/String.cpp index 3a2a6df7f4..aea62f553a 100644 --- a/src/core/String.cpp +++ b/src/core/String.cpp @@ -14,6 +14,8 @@ *****************************************************************************/ #pragma endregion +#include + extern "C" { #include "../localisation/localisation.h" @@ -218,4 +220,45 @@ namespace String { return utf8_write_codepoint(dst, codepoint); } + + utf8 * Trim(utf8 * str) + { + utf8 * firstNonWhitespace = nullptr; + utf8 * lastNonWhitespace = nullptr; + + codepoint_t codepoint; + utf8 * ch = str; + utf8 * nextCh; + while ((codepoint = GetNextCodepoint(ch, &nextCh)) != '\0') + { + if (codepoint <= WCHAR_MAX && !iswspace((wchar_t)codepoint)) + { + if (firstNonWhitespace == nullptr) + { + firstNonWhitespace = ch; + } + lastNonWhitespace = ch; + } + ch = nextCh; + } + + if (firstNonWhitespace != nullptr && + firstNonWhitespace != str) + { + size_t newStringSize = ch - firstNonWhitespace; +#if DEBUG + size_t currentStringSize = String::SizeOf(str); + assert(newStringSize < currentStringSize); +#endif + + Memory::Move(str, firstNonWhitespace, newStringSize); + str[newStringSize] = '\0'; + } + else + { + *ch = '\0'; + } + + return str; + } } diff --git a/src/core/String.hpp b/src/core/String.hpp index ce734bc818..5a140dc1d2 100644 --- a/src/core/String.hpp +++ b/src/core/String.hpp @@ -64,4 +64,6 @@ namespace String codepoint_t GetNextCodepoint(utf8 * ptr, utf8 * * nextPtr = nullptr); codepoint_t GetNextCodepoint(const utf8 * ptr, const utf8 * * nextPtr = nullptr); utf8 * WriteCodepoint(utf8 * dst, codepoint_t codepoint); + + utf8 * Trim(utf8 * str); } diff --git a/src/drawing/Image.cpp b/src/drawing/Image.cpp new file mode 100644 index 0000000000..0fab47c9b9 --- /dev/null +++ b/src/drawing/Image.cpp @@ -0,0 +1,136 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include +#include "../core/Console.hpp" +#include "../core/Guard.hpp" +#include "../core/Memory.hpp" + +extern "C" +{ + #include "drawing.h" +} + +constexpr uint32 BASE_IMAGE_ID = 29294; +constexpr uint32 MAX_IMAGES = 262144; +constexpr uint32 INVALID_IMAGE_ID = UINT32_MAX; + +struct ImageList +{ + uint32 BaseId; + uint32 Count; +}; + +static bool _initialised = false; +static std::list _freeLists; + +#ifdef DEBUG +static std::list _allocatedLists; + +static bool AllocatedListContains(uint32 baseImageId, uint32 count) +{ + bool contains = std::any_of( + _allocatedLists.begin(), + _allocatedLists.end(), + [baseImageId, count](const ImageList &imageList) -> bool + { + return imageList.BaseId == baseImageId && imageList.Count == count; + }); + return contains; +} +#endif + +static uint32 AllocateImageList(uint32 count) +{ + Guard::Assert(count != 0); + + if (!_initialised) + { + _initialised = true; + _freeLists.clear(); + _freeLists.push_back({ BASE_IMAGE_ID, MAX_IMAGES }); +#ifdef DEBUG + _allocatedLists.clear(); +#endif + } + + for (auto it = _freeLists.begin(); it != _freeLists.end(); it++) + { + ImageList imageList = *it; + if (imageList.Count >= count) + { + _freeLists.erase(it); + if (imageList.Count > count) + { + ImageList remainder = { imageList.BaseId + count, + imageList.Count - count }; + _freeLists.push_back(remainder); + } + +#ifdef DEBUG + _allocatedLists.push_back({ imageList.BaseId, count }); +#endif + return imageList.BaseId; + } + } + return INVALID_IMAGE_ID; +} + +static void FreeImageList(uint32 baseImageId, uint32 count) +{ + Guard::Assert(_initialised); + Guard::Assert(baseImageId >= BASE_IMAGE_ID); + +#ifdef DEBUG + bool contains = AllocatedListContains(baseImageId, count); + Guard::Assert(contains); +#endif + + // TODO validate that this was an allocated list + _freeLists.push_back({ baseImageId, count }); +} + +extern "C" +{ + uint32 gfx_object_allocate_images(const rct_g1_element * images, uint32 count) + { + uint32 baseImageId = AllocateImageList(count); + if (baseImageId == INVALID_IMAGE_ID) + { + log_error("Reached maximum image limit."); + return INVALID_IMAGE_ID; + } + + uint32 imageId = baseImageId; + for (uint32 i = 0; i < count; i++) + { + g1Elements[imageId] = images[i]; + drawing_engine_invalidate_image(imageId); + imageId++; + } + + return baseImageId; + } + + void gfx_object_free_images(uint32 baseImageId, uint32 count) + { + if (baseImageId != 0) + { + FreeImageList(baseImageId, count); + } + } +} diff --git a/src/drawing/drawing.h b/src/drawing/drawing.h index f0461687e0..ab020b5d28 100644 --- a/src/drawing/drawing.h +++ b/src/drawing/drawing.h @@ -71,8 +71,11 @@ assert_struct_size(rct_g1_element_32bit, 0x10); enum { - G1_FLAG_BMP = (1 << 0), //No invisible sections - G1_FLAG_RLE_COMPRESSION = (1<<2), + G1_FLAG_BMP = (1 << 0), // Image data is encoded as raw pixels (no transparancy) + G1_FLAG_1 = (1 << 1), + G1_FLAG_RLE_COMPRESSION = (1 << 2), // Image data is encoded using RCT2's form of run length encoding + G1_FLAG_HAS_ZOOM_SPRITE = (1 << 4), // Use a different sprite for higher zoom levels + G1_FLAG_NO_ZOOM_DRAW = (1 << 5), // Does not get drawn at higher zoom levels (only zoom 0) }; enum { @@ -176,6 +179,8 @@ int gfx_load_g2(); void gfx_unload_g1(); void gfx_unload_g2(); rct_g1_element* gfx_get_g1_element(int image_id); +uint32 gfx_object_allocate_images(const rct_g1_element * images, uint32 count); +void gfx_object_free_images(uint32 baseImageId, uint32 count); void sub_68371D(); void FASTCALL gfx_rle_sprite_to_buffer(const uint8* RESTRICT source_bits_pointer, uint8* RESTRICT dest_bits_pointer, const uint8* RESTRICT palette_pointer, const rct_drawpixelinfo * RESTRICT dpi, int image_type, int source_y_start, int height, int source_x_start, int width); void FASTCALL gfx_draw_sprite(rct_drawpixelinfo *dpi, int image_id, int x, int y, uint32 tertiary_colour); diff --git a/src/drawing/engines/opengl/OpenGLAPI.cpp b/src/drawing/engines/opengl/OpenGLAPI.cpp index 42f661ae8b..8683262160 100644 --- a/src/drawing/engines/opengl/OpenGLAPI.cpp +++ b/src/drawing/engines/opengl/OpenGLAPI.cpp @@ -133,7 +133,7 @@ bool OpenGLAPI::Initialise() const char * failedProcName = TryLoadAllProcAddresses(); if (failedProcName != nullptr) { - Console::Error::WriteFormat("Failed to load %s.\n", failedProcName); + Console::Error::WriteLine("Failed to load %s.", failedProcName); return false; } #endif diff --git a/src/drawing/engines/opengl/OpenGLShaderProgram.cpp b/src/drawing/engines/opengl/OpenGLShaderProgram.cpp index 3cc4162078..922b3af625 100644 --- a/src/drawing/engines/opengl/OpenGLShaderProgram.cpp +++ b/src/drawing/engines/opengl/OpenGLShaderProgram.cpp @@ -51,7 +51,7 @@ OpenGLShader::OpenGLShader(const char * name, GLenum type) glGetShaderInfoLog(_id, sizeof(buffer), nullptr, buffer); glDeleteShader(_id); - Console::Error::WriteFormat("Error compiling %s\n", path); + Console::Error::WriteLine("Error compiling %s", path); Console::Error::WriteLine(buffer); throw Exception("Error compiling shader."); @@ -115,7 +115,7 @@ OpenGLShaderProgram::OpenGLShaderProgram(const char * name) GLsizei length; glGetProgramInfoLog(_id, sizeof(buffer), &length, buffer); - Console::Error::WriteFormat("Error linking %s\n", name); + Console::Error::WriteLine("Error linking %s", name); Console::Error::WriteLine(buffer); throw Exception("Failed to link OpenGL shader."); diff --git a/src/drawing/sprite.c b/src/drawing/sprite.c index fefb338e26..6400f91ecd 100644 --- a/src/drawing/sprite.c +++ b/src/drawing/sprite.c @@ -425,7 +425,7 @@ void FASTCALL gfx_draw_sprite_palette_set_software(rct_drawpixelinfo *dpi, int i rct_g1_element *g1_source = gfx_get_g1_element(image_element); - if ( dpi->zoom_level && (g1_source->flags & (1<<4)) ){ + if (dpi->zoom_level != 0 && (g1_source->flags & G1_FLAG_HAS_ZOOM_SPRITE)) { rct_drawpixelinfo zoomed_dpi = { .bits = dpi->bits, .x = dpi->x >> 1, @@ -439,7 +439,7 @@ void FASTCALL gfx_draw_sprite_palette_set_software(rct_drawpixelinfo *dpi, int i return; } - if ( dpi->zoom_level && (g1_source->flags & (1<<5)) ){ + if (dpi->zoom_level != 0 && (g1_source->flags & G1_FLAG_NO_ZOOM_DRAW)) { return; } @@ -554,7 +554,7 @@ void FASTCALL gfx_draw_sprite_palette_set_software(rct_drawpixelinfo *dpi, int i //Move the pointer to the start point of the source source_pointer += g1_source->width*source_start_y + source_start_x; - if (!(g1_source->flags & 0x02)){ + if (!(g1_source->flags & G1_FLAG_1)) { gfx_bmp_sprite_to_buffer(palette_pointer, unknown_pointer, source_pointer, dest_pointer, g1_source, dpi, height, width, image_type); return; } @@ -609,8 +609,8 @@ void FASTCALL gfx_draw_sprite_raw_masked_software(rct_drawpixelinfo *dpi, int x, rct_g1_element *imgMask = &g1Elements[maskImage & 0x7FFFF]; rct_g1_element *imgColour = &g1Elements[colourImage & 0x7FFFF]; - assert(imgMask->flags & 1); - assert(imgColour->flags & 1); + assert(imgMask->flags & G1_FLAG_BMP); + assert(imgColour->flags & G1_FLAG_BMP); if (dpi->zoom_level != 0) { // TODO implement other zoom levels (probably not used though) diff --git a/src/editor.c b/src/editor.c index e27861d91b..e1f6ad78cf 100644 --- a/src/editor.c +++ b/src/editor.c @@ -26,6 +26,7 @@ #include "management/finance.h" #include "management/news_item.h" #include "object.h" +#include "object/ObjectManager.h" #include "peep/staff.h" #include "platform/platform.h" #include "rct1.h" @@ -59,7 +60,8 @@ void editor_load() audio_pause_sounds(); audio_unpause_sounds(); - object_unload_all(); + object_manager_unload_all_objects(); + object_list_load(); map_init(150); banner_init(); reset_park_entrances(); @@ -124,15 +126,6 @@ void editor_convert_save_to_scenario_callback(int result) s6Info->objective_arg_3 = gScenarioObjectiveNumGuests; climate_reset(gClimate); - rct_stex_entry* stex = g_stexEntries[0]; - if ((int)stex != 0xFFFFFFFF) { - object_unload_chunk((rct_object_entry*)&object_entry_groups[OBJECT_TYPE_SCENARIO_TEXT].entries[0]); - reset_loaded_objects(); - - format_string(s6Info->details, STR_NO_DETAILS_YET, NULL); - s6Info->name[0] = 0; - } - gScreenFlags = SCREEN_FLAGS_SCENARIO_EDITOR; s6Info->editor_step = EDITOR_STEP_OBJECTIVE_SELECTION; s6Info->category = SCENARIO_CATEGORY_OTHER; @@ -154,7 +147,8 @@ void trackdesigner_load() gScreenFlags = SCREEN_FLAGS_TRACK_DESIGNER; gScreenAge = 0; - object_unload_all(); + object_manager_unload_all_objects(); + object_list_load(); map_init(150); set_all_land_owned(); banner_init(); @@ -192,7 +186,8 @@ void trackmanager_load() gScreenFlags = SCREEN_FLAGS_TRACK_MANAGER; gScreenAge = 0; - object_unload_all(); + object_manager_unload_all_objects(); + object_list_load(); map_init(150); set_all_land_owned(); banner_init(); @@ -369,13 +364,11 @@ static int editor_read_s6(const char *path) } SDL_RWclose(rw); - if (!load_success){ + if (!load_success) { log_error("failed to load all entries."); - set_load_objects_fail_reason(); return 0; } - reset_loaded_objects(); map_update_tile_pointers(); game_convert_strings_to_utf8(); @@ -469,15 +462,6 @@ static void editor_clear_map_for_editing() climate_reset(gClimate); - rct_stex_entry* stex = g_stexEntries[0]; - if ((int)stex != 0xFFFFFFFF) { - object_unload_chunk((rct_object_entry*)&object_entry_groups[OBJECT_TYPE_SCENARIO_TEXT].entries[0]); - reset_loaded_objects(); - - format_string(gS6Info->details, STR_NO_DETAILS_YET, NULL); - gS6Info->name[0] = 0; - } - news_item_init_queue(); } @@ -499,7 +483,7 @@ void editor_open_windows_for_current_step() return; if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) { - object_unload_all(); + object_manager_unload_all_objects(); } window_editor_object_selection_open(); @@ -559,21 +543,6 @@ static void editor_finalise_main_view() gfx_invalidate_screen(); } -static bool editor_check_object_group_at_least_one_selected(int objectType) -{ - uint32 numObjects = gInstalledObjectsCount; - rct_object_entry *entry = gInstalledObjects; - uint8 *objectFlag = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - for (uint32 i = 0; i < numObjects; i++) { - if ((entry->flags & 0x0F) == objectType && (*objectFlag & OBJECT_SELECTION_FLAG_SELECTED)) { - return true; - } - entry = object_get_next(entry); - objectFlag++; - } - return false; -} - /** * * rct2: 0x006AB9B8 diff --git a/src/editor.h b/src/editor.h index c7b479812a..98fb324777 100644 --- a/src/editor.h +++ b/src/editor.h @@ -42,4 +42,6 @@ void editor_open_windows_for_current_step(); bool editor_check_park(); int editor_check_object_selection(); +bool editor_check_object_group_at_least_one_selected(int objectType); + #endif diff --git a/src/interface/console.c b/src/interface/console.c index 998aa73a15..c5b0fc7e0a 100644 --- a/src/interface/console.c +++ b/src/interface/console.c @@ -30,6 +30,8 @@ #include "../input.h" #include "../network/twitch.h" #include "../object.h" +#include "../object/ObjectManager.h" +#include "../object/ObjectRepository.h" #include "../world/banner.h" #include "../world/climate.h" #include "../world/scenery.h" @@ -866,109 +868,70 @@ static int cc_twitch(const utf8 **argv, int argc) return 0; } -static void editor_load_selected_objects_console() -{ - uint8 *selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry *installed_entry = gInstalledObjects; - - if (gInstalledObjectsCount == 0) - return; - - for (int i = gInstalledObjectsCount; i != 0; i--, selection_flags++) { - if (*selection_flags & 1) { - uint8 entry_index, entry_type; - if (!find_object_in_entry_group(installed_entry, &entry_type, &entry_index)){ - int chunk_size; - if (!object_load_chunk(-1, installed_entry, &chunk_size)) { - log_error("Failed to load entry %.8s", installed_entry->name); - } - } - } - - installed_entry = object_get_next(installed_entry); - } -} - static int cc_load_object(const utf8 **argv, int argc) { if (argc > 0) { - utf8 path[MAX_PATH]; - - substitute_path(path, gRCT2AddressObjectDataPath, argv[0]); - strcat(path, ".DAT\0"); - - rct_object_entry entry; - if (object_load_entry(path, &entry)) { - uint8 type = entry.flags & 0xF; - uint8 index; - - if (check_object_entry(&entry)) { - if (!find_object_in_entry_group(&entry, &type, &index)){ - - int entryGroupIndex = 0; - for (; entryGroupIndex < object_entry_group_counts[type]; entryGroupIndex++){ - if (object_entry_groups[type].chunks[entryGroupIndex] == (uint8*)-1){ - break; - } - } - - if (entryGroupIndex >= object_entry_group_counts[type]) { - console_writeline_error("Too many objects of that type."); - } - else { - // Load the obect - if (!object_load_chunk(entryGroupIndex, &entry, NULL)) { - console_writeline_error("Could not load object file."); - } - else { - reset_loaded_objects(); - if (type == OBJECT_TYPE_RIDE) { - // Automatically research the ride so it's supported by the game. - rct_ride_entry *rideEntry; - int rideType; - - rideEntry = get_ride_entry(entryGroupIndex); - - for (int j = 0; j < 3; j++) { - rideType = rideEntry->ride_type[j]; - if (rideType != 255) - research_insert(true, 0x10000 | (rideType << 8) | entryGroupIndex, rideEntry->category[0]); - } - - gSilentResearch = true; - sub_684AC3(); - gSilentResearch = false; - } - else if (type == OBJECT_TYPE_SCENERY_SETS) { - rct_scenery_set_entry *scenerySetEntry; - - scenerySetEntry = get_scenery_group_entry(entryGroupIndex); - - research_insert(true, entryGroupIndex, RESEARCH_CATEGORY_SCENERYSET); - - gSilentResearch = true; - sub_684AC3(); - gSilentResearch = false; - } - scenery_set_default_placement_configuration(); - window_new_ride_init_vars(); - - RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_UPDATE_TICKS, uint16) = 0; - gfx_invalidate_screen(); - console_writeline("Object file loaded."); - } - } - } - else { - console_writeline_error("Object is already in scenario."); - } - } - else { - console_writeline_error("The object file was invalid."); - } + char name[9] = { 0 }; + memset(name, ' ', 8); + int i = 0; + for (const char * ch = argv[0]; *ch != '\0' && i < 8; ch++) { + name[i++] = *ch; } - else { - console_writeline_error("Could not find the object file."); + + const ObjectRepositoryItem * ori = object_repository_find_object_by_name(name); + if (ori == NULL) { + console_writeline_error("Could not find the object."); + return 1; } + + const rct_object_entry * entry = &ori->ObjectEntry; + void * loadedObject = object_manager_get_loaded_object(entry); + if (loadedObject != NULL) { + console_writeline_error("Object is already in scenario."); + return 1; + } + + loadedObject = object_manager_load_object(entry); + if (loadedObject == NULL) { + console_writeline_error("Unable to load object."); + return 1; + } + int groupIndex = object_manager_get_loaded_object_entry_index(loadedObject); + + uint8 objectType = entry->flags & 0x0F; + if (objectType == OBJECT_TYPE_RIDE) { + // Automatically research the ride so it's supported by the game. + rct_ride_entry *rideEntry; + int rideType; + + rideEntry = get_ride_entry(groupIndex); + + for (int j = 0; j < 3; j++) { + rideType = rideEntry->ride_type[j]; + if (rideType != 255) + research_insert(true, 0x10000 | (rideType << 8) | groupIndex, rideEntry->category[0]); + } + + gSilentResearch = true; + sub_684AC3(); + gSilentResearch = false; + } + else if (objectType == OBJECT_TYPE_SCENERY_SETS) { + rct_scenery_set_entry *scenerySetEntry; + + scenerySetEntry = get_scenery_group_entry(groupIndex); + + research_insert(true, groupIndex, RESEARCH_CATEGORY_SCENERYSET); + + gSilentResearch = true; + sub_684AC3(); + gSilentResearch = false; + } + scenery_set_default_placement_configuration(); + window_new_ride_init_vars(); + + RCT2_GLOBAL(RCT2_ADDRESS_WINDOW_UPDATE_TICKS, uint16) = 0; + gfx_invalidate_screen(); + console_writeline("Object file loaded."); } return 0; diff --git a/src/localisation/language.cpp b/src/localisation/language.cpp index 43c9e635f0..a3fb5c4489 100644 --- a/src/localisation/language.cpp +++ b/src/localisation/language.cpp @@ -14,6 +14,8 @@ *****************************************************************************/ #pragma endregion +#include +#include "../object/ObjectManager.h" #include "LanguagePack.h" extern "C" { @@ -188,13 +190,12 @@ bool language_open(int id) gCurrentTTFFontSet = &TTFFontCustom; bool font_initialised = ttf_initialise(); - if(!font_initialised) { + if (!font_initialised) { log_warning("Unable to initialise configured TrueType font -- falling back to Language default."); } else { // Objects and their localized strings need to be refreshed - reset_loaded_objects(); - - return 1; + GetObjectManager()->ResetObjects(); + return true; } } ttf_dispose(); @@ -219,8 +220,7 @@ bool language_open(int id) } // Objects and their localized strings need to be refreshed - reset_loaded_objects(); - + GetObjectManager()->ResetObjects(); return true; } @@ -315,133 +315,20 @@ static bool rct2_language_is_multibyte_charset(int languageId) case RCT2_LANGUAGE_ID_KOREAN: case RCT2_LANGUAGE_ID_CHINESE_TRADITIONAL: case RCT2_LANGUAGE_ID_CHINESE_SIMPLIFIED: + case RCT2_LANGUAGE_ID_8: return true; default: return false; } } -/* rct2: 0x006A9E24*/ -rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/*ecx*/, int index/*ebx*/, int tableindex/*edx*/) +utf8 *rct2_language_string_to_utf8(const char *src, int languageId) { - uint8 languageId, chosenLanguageId; - char *pString = nullptr; - int result = 0; - bool isBlank; - - while ((languageId = *(*pStringTable)++) != RCT2_LANGUAGE_ID_END) { - isBlank = true; - - // Strings that are just ' ' are set as invalid langauges. - // But if there is no real string then it will set the string as - // the blank string - for (char *ch = (char*)(*pStringTable); *ch != 0; ch++) { - if (!isblank(*ch)) { - isBlank = false; - break; - } - } - - if (isBlank) languageId = 0xFE; - - // This is the ideal situation. Language found - if (languageId == LanguagesDescriptors[gCurrentLanguage].rct2_original_id) { - chosenLanguageId = languageId; - pString = (char*)(*pStringTable); - result |= 1; - } - - // Just in case always load english into pString - if (languageId == RCT2_LANGUAGE_ID_ENGLISH_UK && !(result & 1)) { - chosenLanguageId = languageId; - pString = (char*)(*pStringTable); - result |= 2; - } - - // Failing that fall back to whatever is first string - if (!(result & 7)) { - chosenLanguageId = languageId; - pString = (char*)(*pStringTable); - if (!isBlank) result |= 4; - } - - // Skip over the actual string entry to get to the next entry - while (*(*pStringTable)++ != 0); - } - // Fall back in case language does not get set. - if (pString == NULL) - { - pString = (char*)(*pStringTable); - } - - char name[9]; - if (RCT2_GLOBAL(0x009ADAFC, uint8) == 0) { - memcpy(name, object_entry_groups[type].entries[index].name, 8); - } else { - memcpy(name, gTempObjectLoadName, 8); - } - name[8] = 0; - - rct_string_id stringId = _languageCurrent->GetObjectOverrideStringId(name, tableindex); - if (stringId != STR_NONE) { - return stringId; - } - - // If not scenario text - if (RCT2_GLOBAL(0x009ADAFC, uint8) == 0) { - int stringid = NONSTEX_BASE_STRING_ID; - for (int i = 0; i < type; i++) { - int nrobjects = object_entry_group_counts[i]; - int nrstringtables = ObjectTypeStringTableCount[i]; - stringid += nrobjects * nrstringtables; - } - stringid += index * ObjectTypeStringTableCount[type]; - // Used by the object list to allocate name in plugin.dat - RCT2_GLOBAL(RCT2_ADDRESS_CURR_OBJECT_BASE_STRING_ID, uint32) = stringid; - stringid += tableindex; - - // cache UTF-8 string - int cacheStringOffset = stringid - STEX_BASE_STRING_ID; - utf8 **cacheString = &_cachedObjectStrings[cacheStringOffset]; - if (*cacheString != nullptr) { - free(*cacheString); - } - if (rct2_language_is_multibyte_charset(chosenLanguageId)) { - *cacheString = convert_multibyte_charset(pString, chosenLanguageId); - } else { - *cacheString = win1252_to_utf8_alloc(pString); - } - utf8_trim_string(*cacheString); - - //put pointer in stringtable - _languageCurrent->SetString(stringid, *cacheString); - // Until all string related functions are finished copy - // to old array as well. - _languageOriginal[stringid] = *cacheString; - return stringid; - } else { - int stringid = STEX_BASE_STRING_ID + tableindex; - - // cache UTF-8 string - int cacheStringOffset = stringid - STEX_BASE_STRING_ID; - utf8 **cacheString = &_cachedObjectStrings[cacheStringOffset]; - if (*cacheString != nullptr) { - free(*cacheString); - } - if (rct2_language_is_multibyte_charset(chosenLanguageId)) { - *cacheString = convert_multibyte_charset(pString, chosenLanguageId); - } else { - *cacheString = win1252_to_utf8_alloc(pString); - } - utf8_trim_string(*cacheString); - - //put pointer in stringtable - _languageCurrent->SetString(stringid, *cacheString); - // Until all string related functions are finished copy - // to old array as well. - _languageOriginal[stringid] = *cacheString; - return stringid; - } + if (rct2_language_is_multibyte_charset(languageId)) { + return convert_multibyte_charset(src, languageId); + } else { + return win1252_to_utf8_alloc(src); + } } bool language_get_localised_scenario_strings(const utf8 *scenarioFilename, rct_string_id *outStringIds) @@ -455,4 +342,45 @@ bool language_get_localised_scenario_strings(const utf8 *scenarioFilename, rct_s outStringIds[2] != STR_NONE; } +static bool _availableObjectStringIdsInitialised = false; +static std::stack _availableObjectStringIds; + +rct_string_id language_allocate_object_string(const utf8 * target) +{ + if (!_availableObjectStringIdsInitialised) + { + _availableObjectStringIdsInitialised = true; + for (rct_string_id stringId = NONSTEX_BASE_STRING_ID + MAX_OBJECT_CACHED_STRINGS; stringId >= NONSTEX_BASE_STRING_ID; stringId--) + { + _availableObjectStringIds.push(stringId); + } + } + + rct_string_id stringId = _availableObjectStringIds.top(); + _availableObjectStringIds.pop(); + _languageCurrent->SetString(stringId, target); + return stringId; +} + +void language_free_object_string(rct_string_id stringId) +{ + if (stringId != 0) + { + if (_languageCurrent != nullptr) + { + _languageCurrent->SetString(stringId, nullptr); + } + _availableObjectStringIds.push(stringId); + } +} + +rct_string_id language_get_object_override_string_id(const char * identifier, uint8 index) +{ + if (_languageCurrent == nullptr) + { + return STR_NONE; + } + return _languageCurrent->GetObjectOverrideStringId(identifier, index); +} + } diff --git a/src/localisation/language.h b/src/localisation/language.h index 8e2d1f8511..c509c7b37e 100644 --- a/src/localisation/language.h +++ b/src/localisation/language.h @@ -69,8 +69,6 @@ const char *language_get_string(rct_string_id id); bool language_open(int id); void language_close_all(); -rct_string_id object_get_localised_text(uint8_t** pStringTable/*ebp*/, int type/*ecx*/, int index/*ebx*/, int tableindex/*edx*/); - uint32 utf8_get_next(const utf8 *char_ptr, const utf8 **nextchar_ptr); utf8 *utf8_write_codepoint(utf8 *dst, uint32 codepoint); int utf8_insert_codepoint(utf8 *dst, uint32 codepoint); @@ -81,6 +79,10 @@ int utf8_length(const utf8 *text); wchar_t *utf8_to_widechar(const utf8 *src); utf8 *widechar_to_utf8(const wchar_t *src); +utf8 *rct2_language_string_to_utf8(const char *src, int languageId); bool language_get_localised_scenario_strings(const utf8 *scenarioFilename, rct_string_id *outStringIds); +rct_string_id language_allocate_object_string(const utf8 * target); +void language_free_object_string(rct_string_id stringId); +rct_string_id language_get_object_override_string_id(const char * identifier, uint8 index); #endif diff --git a/src/management/research.c b/src/management/research.c index f23bf8ec58..9d6f2a79e6 100644 --- a/src/management/research.c +++ b/src/management/research.c @@ -643,3 +643,11 @@ static void ride_entry_set_invented(int rideEntryIndex) int bitIndex = rideEntryIndex & 0x1F; gResearchedRideEntries[quadIndex] |= 1 << bitIndex; } + +bool scenery_is_invented(uint16 sceneryItem) +{ + int quadIndex = sceneryItem >> 5; + int bitIndex = sceneryItem & 0x1F; + bool invented = (gResearchedSceneryItems[quadIndex] & ((uint32)1 << bitIndex)); + return invented; +} diff --git a/src/management/research.h b/src/management/research.h index 3ab2a3327b..bd46a59801 100644 --- a/src/management/research.h +++ b/src/management/research.h @@ -105,5 +105,6 @@ void research_insert_scenery_group_entry(uint8 entryIndex, bool researched); bool ride_type_is_invented(int rideType); bool ride_entry_is_invented(int rideEntryIndex); bool track_type_is_invented(uint8 rideType, int trackType); +bool scenery_is_invented(uint16 sceneryItem); #endif diff --git a/src/object.c b/src/object.c deleted file mode 100644 index ce0d7c86e3..0000000000 --- a/src/object.c +++ /dev/null @@ -1,1807 +0,0 @@ -#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers -/***************************************************************************** - * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. - * - * OpenRCT2 is the work of many authors, a full list can be found in contributors.md - * For more information, visit https://github.com/OpenRCT2/OpenRCT2 - * - * OpenRCT2 is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * A full copy of the GNU General Public License can be found in licence.txt - *****************************************************************************/ -#pragma endregion - -#include "addresses.h" -#include "config.h" -#include "drawing/drawing.h" -#include "localisation/localisation.h" -#include "object.h" -#include "object_list.h" -#include "platform/platform.h" -#include "rct1.h" -#include "ride/ride.h" -#include "scenario.h" -#include "util/sawyercoding.h" -#include "world/entrance.h" -#include "world/footpath.h" -#include "world/scenery.h" -#include "world/water.h" - -char gTempObjectLoadName[9] = { 0 }; -uint32 gTotalNoImages = 0; - -int object_load_entry(const utf8 *path, rct_object_entry *outEntry) -{ - SDL_RWops *file; - - file = SDL_RWFromFile(path, "rb"); - if (file == NULL) - return 0; - - if (SDL_RWread(file, outEntry, sizeof(rct_object_entry), 1) != 1) { - SDL_RWclose(file); - return 0; - } - - SDL_RWclose(file); - return 1; -} - -int object_load_file(int groupIndex, const rct_object_entry *entry, int* chunkSize, const rct_object_entry *installedObject) -{ - uint8 objectType; - rct_object_entry openedEntry; - char path[MAX_PATH]; - SDL_RWops* rw; - - substitute_path(path, gRCT2AddressObjectDataPath, (char*)installedObject + 16); - - log_verbose("loading object, %s", path); - - rw = SDL_RWFromFile(path, "rb"); - if (rw == NULL) - return 0; - - SDL_RWread(rw, &openedEntry, sizeof(rct_object_entry), 1); - if (!object_entry_compare(&openedEntry, entry)) { - SDL_RWclose(rw); - return 0; - } - - // Get chunk size - uint8 *installedObject_pointer = (uint8*)installedObject + 16; - // Skip file name - while (*installedObject_pointer++); - - // Read chunk size - *chunkSize = *((uint32*)installedObject_pointer); - uint8 *chunk; - - if (*chunkSize == 0xFFFFFFFF) { - chunk = (uint8*)malloc(0x600000); - assert(chunk != NULL); - *chunkSize = sawyercoding_read_chunk_with_size(rw, chunk, 0x600000); - chunk = realloc(chunk, *chunkSize); - } - else { - chunk = (uint8*)malloc(*chunkSize); - *chunkSize = sawyercoding_read_chunk_with_size(rw, chunk, *chunkSize); - } - SDL_RWclose(rw); - if (chunk == NULL) { - log_error("Failed to load object from %s of size %d", path, *chunkSize); - return 0; - } - - int calculatedChecksum = object_calculate_checksum(&openedEntry, chunk, *chunkSize); - - // Calculate and check checksum - if (calculatedChecksum != openedEntry.checksum && !gConfigGeneral.allow_loading_with_incorrect_checksum) { - log_error("Object Load failed due to checksum failure: calculated checksum %d, object says %d.", calculatedChecksum, (int)openedEntry.checksum); - free(chunk); - return 0; - - } - - objectType = openedEntry.flags & 0x0F; - - if (!object_test(objectType, chunk)) { - log_error("Object Load failed due to paint failure."); - free(chunk); - return 0; - } - - if (gTotalNoImages >= 0x4726E){ - log_error("Object Load failed due to too many images loaded."); - free(chunk); - return 0; - } - - void** chunk_list = object_entry_groups[objectType].chunks; - if (groupIndex == -1) { - for (groupIndex = 0; chunk_list[groupIndex] != (void*)-1; groupIndex++) { - if (groupIndex + 1 >= object_entry_group_counts[objectType]) { - log_error("Object Load failed due to too many objects of a certain type."); - free(chunk); - return 0; - } - } - } - chunk_list[groupIndex] = chunk; - - rct_object_entry_extended* extended_entry = &object_entry_groups[objectType].entries[groupIndex]; - - memcpy(extended_entry, &openedEntry, sizeof(rct_object_entry)); - extended_entry->chunk_size = *chunkSize; - - gLastLoadedObjectChunkData = chunk; - - if (RCT2_GLOBAL(0x9ADAFD, uint8) != 0) { - object_load(objectType, chunk, groupIndex); - } - return 1; -} - -/** - * - * rct2: 0x006A985D - */ -int object_load_chunk(int groupIndex, rct_object_entry *entry, int* chunkSize) -{ - // Alow chunkSize to be null - int tempChunkSize; - if (chunkSize == NULL) - chunkSize = &tempChunkSize; - - RCT2_GLOBAL(0xF42B64, uint32) = groupIndex; - - if (gInstalledObjectsCount == 0) { - log_error("Object Load failed due to no items installed check."); - return 1; - } - - rct_object_entry *installedObject = object_list_find(entry); - if (installedObject == NULL) { - log_error("object not installed"); - return 0; - } - - if (object_load_file(groupIndex, entry, chunkSize, installedObject)) - return 1; - - return 0; -} - -/** - * - * rct2: 0x006a9f42 - * ebx: file - * ebp: entry - */ -int write_object_file(SDL_RWops *rw, rct_object_entry* entry) -{ - uint8 entryGroupIndex = 0, type = 0; - uint8* chunk = 0; - - if (!find_object_in_entry_group(entry, &type, &entryGroupIndex))return 0; - - chunk = object_entry_groups[type].chunks[entryGroupIndex]; - - object_unload(type, chunk); - - rct_object_entry_extended* installed_entry = &object_entry_groups[type].entries[entryGroupIndex]; - uint8* dst_buffer = (uint8*)malloc(0x600000); - memcpy(dst_buffer, installed_entry, sizeof(rct_object_entry)); - - uint32 size_dst = sizeof(rct_object_entry); - - sawyercoding_chunk_header chunkHeader; - // Encoding type (not used anymore) - RCT2_GLOBAL(0x9E3CBD, uint8) = object_entry_group_encoding[type]; - - chunkHeader.encoding = object_entry_group_encoding[type]; - chunkHeader.length = installed_entry->chunk_size; - - - //Check if content of object file matches the stored checksum. If it does not, then fix it. - int calculated_checksum = object_calculate_checksum(entry, chunk, installed_entry->chunk_size); - if(entry->checksum != calculated_checksum) { - //Store the current length of the header - it's the offset at which we will write the extra bytes - int salt_offset = chunkHeader.length; - /*Allocate a new chunk 11 bytes longer. - I would just realloc the old one, but realloc can move the data, leaving dangling pointers - into the old buffer. If the chunk is only referenced in one place it would be safe to realloc - it and update that reference, but I don't know the codebase well enough to know if that's the - case, so to be on the safe side I copy it*/ - uint8* new_chunk = malloc(chunkHeader.length + 11); - memcpy(new_chunk,chunk, chunkHeader.length); - //It should be safe to update these in-place because they are local - chunkHeader.length += 11; - - /*Next work out which bits need to be flipped to make the current checksum match the one in the file - The bitwise rotation compensates for the rotation performed during the checksum calculation*/ - int bits_to_flip = entry->checksum ^ ((calculated_checksum << 25) | (calculated_checksum >> 7)); - /*Each set bit encountered during encoding flips one bit of the resulting checksum (so each bit of the checksum is an XOR - of bits from the file). Here, we take each bit that should be flipped in the checksum and set one of the bits in the data - that maps to it. 11 bytes is the minimum needed to touch every bit of the checksum - with less than that, you wouldn't - always be able to make the checksum come out to the desired target*/ - new_chunk[salt_offset] = (bits_to_flip & 0x00000001) << 7; - new_chunk[salt_offset + 1] = ((bits_to_flip & 0x00200000) >> 14); - new_chunk[salt_offset + 2] = ((bits_to_flip & 0x000007F8) >> 3); - new_chunk[salt_offset + 3] = ((bits_to_flip & 0xFF000000) >> 24); - new_chunk[salt_offset + 4] = ((bits_to_flip & 0x00100000) >> 13); - new_chunk[salt_offset + 5] = (bits_to_flip & 0x00000004) >> 2; - new_chunk[salt_offset + 6] = 0; - new_chunk[salt_offset + 7] = ((bits_to_flip & 0x000FF000) >> 12); - new_chunk[salt_offset + 8] = (bits_to_flip & 0x00000002) >> 1; - new_chunk[salt_offset + 9] = (bits_to_flip & 0x00C00000) >> 22; - new_chunk[salt_offset + 10] = (bits_to_flip & 0x00000800) >> 11; - - //Write modified chunk data - size_dst += sawyercoding_write_chunk_buffer(dst_buffer + sizeof(rct_object_entry),new_chunk,chunkHeader); - free(new_chunk); - } else { - //If the checksum matches, write chunk data - size_dst += sawyercoding_write_chunk_buffer(dst_buffer + sizeof(rct_object_entry), chunk, chunkHeader); - } - - SDL_RWwrite(rw, dst_buffer, 1, size_dst); - free(dst_buffer); - return 1; -} - -/** -* -* rct2: 0x006AA2B7 -*/ -int object_load_packed(SDL_RWops* rw) -{ - object_unload_all(); - - rct_object_entry entry; - - SDL_RWread(rw, &entry, 16, 1); - - uint8* chunk = (uint8*)malloc(0x600000); - uint32 chunkSize = sawyercoding_read_chunk(rw, chunk); - chunk = realloc(chunk, chunkSize); - - if (chunk == NULL){ - log_error("Failed to allocate memory for packed object."); - return 0; - } - - if (object_calculate_checksum(&entry, chunk, chunkSize) != entry.checksum){ - - if(gConfigGeneral.allow_loading_with_incorrect_checksum) { - log_warning("Checksum mismatch from packed object: %.8s", entry.name); - } else { - log_error("Checksum mismatch from packed object: %.8s", entry.name); - free(chunk); - return 0; - } - } - - int type = entry.flags & 0x0F; - - if (!object_test(type, chunk)) { - log_error("Packed object failed paint test."); - free(chunk); - return 0; - } - - if (gTotalNoImages >= 0x4726E){ - log_error("Packed object has too many images."); - free(chunk); - return 0; - } - - - int entryGroupIndex = 0; - - for (; entryGroupIndex < object_entry_group_counts[type]; entryGroupIndex++){ - if (object_entry_groups[type].chunks[entryGroupIndex] == (uint8*)-1){ - break; - } - } - - if (entryGroupIndex == object_entry_group_counts[type]){ - // This should never occur. Objects are not loaded before installing a - // packed object. So there is only one object loaded at this point. - log_error("Too many objects of the same type loaded."); - free(chunk); - return 0; - } - - // Copy the entry into the relevant entry group. - object_entry_groups[type].chunks[entryGroupIndex] = chunk; - rct_object_entry_extended* extended_entry = &object_entry_groups[type].entries[entryGroupIndex]; - memcpy(extended_entry, &entry, sizeof(rct_object_entry)); - extended_entry->chunk_size = chunkSize; - - // Ensure the entry does not already exist. - rct_object_entry *installedObject = gInstalledObjects; - if (gInstalledObjectsCount){ - for (uint32 i = 0; i < gInstalledObjectsCount; ++i){ - if (object_entry_compare(&entry, installedObject)){ - object_unload_all(); - return 0; - } - installedObject = object_get_next(installedObject); - } - } - - // Convert the entry name to a upper case path name - char path[MAX_PATH]; - char objectPath[9] = { 0 }; - for (int i = 0; i < 8; ++i){ - if (entry.name[i] != ' ') - objectPath[i] = toupper(entry.name[i]); - else - objectPath[i] = '\0'; - } - - substitute_path(path, gRCT2AddressObjectDataPath, objectPath); - // Require pointer to start of filename - char* last_char = path + strlen(path); - strcat(path, ".DAT"); - - // Check that file does not exist - // Adjust filename if it does. - for (; platform_file_exists(path);){ - for (char* curr_char = last_char - 1;; --curr_char){ - if (*curr_char == '\\'){ - substitute_path(path, gRCT2AddressObjectDataPath, "00000000.DAT"); - break; - } - if (*curr_char < '0') *curr_char = '0'; - else if (*curr_char == '9') *curr_char = 'A'; - else if (*curr_char == 'Z') *curr_char = '0'; - else (*curr_char)++; - if (*curr_char != '0') break; - } - } - - // Actually write the object to the file - SDL_RWops* rw_out = SDL_RWFromFile(path, "wb"); - if (rw_out != NULL){ - uint8 result = write_object_file(rw_out, &entry); - - SDL_RWclose(rw_out); - object_unload_all(); - - return result; - } - - object_unload_all(); - return 0; -} - -/** - * - * rct2: 0x006A9CAF - */ -void object_unload_chunk(rct_object_entry *entry) -{ - uint8 object_type, object_index; - if (!find_object_in_entry_group(entry, &object_type, &object_index)){ - return; - } - - uint8* chunk = object_entry_groups[object_type].chunks[object_index]; - - object_unload(object_type, chunk); - - free(chunk); - memset(&object_entry_groups[object_type].entries[object_index], 0, sizeof(rct_object_entry_extended)); - object_entry_groups[object_type].chunks[object_index] = (uint8*)-1; -} - -int object_entry_compare(const rct_object_entry *a, const rct_object_entry *b) -{ - // If an official object don't bother checking checksum - if ((a->flags & 0xF0) || (b->flags & 0xF0)) { - if ((a->flags & 0x0F) != (b->flags & 0x0F)) - return 0; - int match = memcmp(a->name, b->name, 8); - if (match) - return 0; - } - else { - if (a->flags != b->flags) - return 0; - int match = memcmp(a->name, b->name, 8); - if (match) - return 0; - if (a->checksum != b->checksum) - return 0; - } - - return 1; -} - -int object_calculate_checksum(const rct_object_entry *entry, const uint8 *data, int dataLength) -{ - const uint8 *entryBytePtr = (uint8*)entry; - - uint32 checksum = 0xF369A75B; - checksum ^= entryBytePtr[0]; - checksum = rol32(checksum, 11); - for (int i = 4; i < 12; i++) { - checksum ^= entryBytePtr[i]; - checksum = rol32(checksum, 11); - } - for (int i = 0; i < dataLength; i++) { - checksum ^= data[i]; - checksum = rol32(checksum, 11); - } - return (int)checksum; -} - -/** - * - * rct2: 0x006A9ED1 - */ -int object_chunk_load_image_directory(uint8_t** chunk) -{ - int image_start_no = gTotalNoImages; - - // First dword of chunk is no_images - int no_images = *((uint32_t*)(*chunk)); - *chunk += 4; - // Second dword of chunk is length of image data - int length_of_data = *((uint32_t*)(*chunk)); - *chunk += 4; - - gTotalNoImages = no_images + image_start_no; - - rct_g1_element* g1_dest = &g1Elements[image_start_no]; - - // After length of data is the start of all g1 element structs - rct_g1_element_32bit* g1_source = (rct_g1_element_32bit*)(*chunk); - - // After the g1 element structs is the actual images. - uintptr_t image_offset = no_images * sizeof(rct_g1_element_32bit) + (uintptr_t)g1_source; - - for (int i = 0; i < no_images; ++i) { - g1_dest->offset = (uint8*)(g1_source->offset + image_offset); - g1_dest->width = g1_source->width; - g1_dest->height = g1_source->height; - g1_dest->x_offset = g1_source->x_offset; - g1_dest->y_offset = g1_source->y_offset; - g1_dest->flags = g1_source->flags; - g1_dest->zoomed_offset = g1_source->zoomed_offset; - g1_dest++; - - drawing_engine_invalidate_image(image_start_no + i); - g1_source++; - } - - *chunk = ((uint8*)g1_source) + length_of_data; - - return image_start_no; -} - -typedef bool (*object_load_func)(void *objectEntry, uint32 entryIndex); -typedef void (*object_unload_func)(void *objectEntry); -typedef bool (*object_test_func)(void *objectEntry); -typedef void (*object_paint_func)(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y); -typedef rct_string_id (*object_desc_func)(void *objectEntry); - -/** - * Represents addresses for virtual object functions. - */ -typedef struct object_type_vtable { - object_load_func load; - object_unload_func unload; - object_test_func test; - object_paint_func paint; - object_desc_func desc; -} object_type_vtable; - -/////////////////////////////////////////////////////////////////////////////// -// Ride (rct2: 0x006E6E2A) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_ride_load(void *objectEntry, uint32 entryIndex) -{ - rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; - - // After rideEntry is 3 string tables - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + sizeof(rct_ride_entry)); - rideEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_RIDE, entryIndex, 0); - rideEntry->description = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_RIDE, entryIndex, 1); - - //TODO: Move to its own function when ride construction window is merged. - if (gConfigInterface.select_by_track_type) { - rideEntry->enabledTrackPieces = 0xFFFFFFFFFFFFFFFF; - } - - object_get_localised_text(&extendedEntryData, OBJECT_TYPE_RIDE, entryIndex, 2); - rideEntry->vehicle_preset_list = (vehicle_colour_preset_list*)extendedEntryData; - - // If Unknown struct size is 0xFF then there are 32 3 byte structures - uint8 unknown_size = *extendedEntryData++; - if (unknown_size != 0xFF) { - extendedEntryData += unknown_size * 3; - } else { - extendedEntryData += 0x60; - } - - sint8 *peep_loading_positions = (sint8*)extendedEntryData; - // Peep loading positions variable size - // 4 different vehicle subtypes are available - for (int i = 0; i < 4; i++){ - uint16 no_peep_positions = *extendedEntryData++; - // If no_peep_positions is 0xFF then no_peep_positions is a word - if (no_peep_positions == 0xFF) { - no_peep_positions = *((uint16*)extendedEntryData); - extendedEntryData += 2; - } - extendedEntryData += no_peep_positions; - } - - int images_offset = object_chunk_load_image_directory(&extendedEntryData); - rideEntry->images_offset = images_offset; - - int cur_vehicle_images_offset = images_offset + 3; - - for (int i = 0; i < 4; i++) { - rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[i]; - - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT) { - int al = 1; - if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_SWINGING) { - al = 13; - if ((vehicleEntry->flags_b & (VEHICLE_ENTRY_FLAG_B_5 | VEHICLE_ENTRY_FLAG_B_11)) != (VEHICLE_ENTRY_FLAG_B_5 | VEHICLE_ENTRY_FLAG_B_11)) { - al = 7; - if (!(vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_5)) { - if (!(vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_11)) { - al = 5; - if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_9) { - al = 3; - } - } - } - } - } - vehicleEntry->var_03 = al; - // 0x6DE90B - - al = 0x20; - if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_14)) { - al = 1; - if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_7) { - if (vehicleEntry->var_11 != 6) { - al = 2; - if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_7)) { - al = 4; - } - } - } - } - if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_12) { - al = vehicleEntry->special_frames; - } - vehicleEntry->var_02 = al; - // 0x6DE946 - - vehicleEntry->var_16 = vehicleEntry->var_02 * vehicleEntry->var_03; - vehicleEntry->base_image_id = cur_vehicle_images_offset; - int image_index = vehicleEntry->base_image_id; - - if (vehicleEntry->car_visual != VEHICLE_VISUAL_RIVER_RAPIDS) { - int b = vehicleEntry->var_16 * 32; - - if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_11) b /= 2; - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_15) b /= 8; - - image_index += b; - - // Incline 25 - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPES) { - vehicleEntry->var_20 = image_index; - b = vehicleEntry->var_16 * 72; - if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_14) - b = vehicleEntry->var_16 * 16; - - image_index += b; - } - - // Incline 60 - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_STEEP_SLOPES) { - vehicleEntry->var_24 = image_index; - b = vehicleEntry->var_16 * 80; - image_index += b; - } - - // Verticle - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES) { - vehicleEntry->var_28 = image_index; - b = vehicleEntry->var_16 * 116; - image_index += b; - } - - // Unknown - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES) { - vehicleEntry->var_2C = image_index; - b = vehicleEntry->var_16 * 24; - image_index += b; - } - - // Bank - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_BANKED) { - vehicleEntry->var_30 = image_index; - b = vehicleEntry->var_16 * 80; - image_index += b; - } - - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_INLINE_TWISTS) { - vehicleEntry->var_34 = image_index; - b = vehicleEntry->var_16 * 40; - image_index += b; - } - - // Track half? Up/Down - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS) { - vehicleEntry->var_38 = image_index; - b = vehicleEntry->var_16 * 128; - image_index += b; - } - - // Unknown - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS) { - vehicleEntry->var_3C = image_index; - b = vehicleEntry->var_16 * 16; - image_index += b; - } - - // Unknown - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS) { - vehicleEntry->var_40 = image_index; - b = vehicleEntry->var_16 * 16; - image_index += b; - } - - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS) { - vehicleEntry->var_44 = image_index; - b = vehicleEntry->var_16 * 128; - image_index += b; - } - - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS) { - vehicleEntry->var_48 = image_index; - b = vehicleEntry->var_16 * 16; - image_index += b; - } - - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_CORKSCREWS) { - vehicleEntry->var_4C = image_index; - b = vehicleEntry->var_16 * 80; - image_index += b; - } - - // Unknown - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION) { - vehicleEntry->var_1C = image_index; - b = vehicleEntry->var_16 * 12; - image_index += b; - } - - if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_14) { - // Same offset as above??? - vehicleEntry->var_4C = image_index; - b = vehicleEntry->var_16 * 32; - image_index += b; - } - } else { - image_index += vehicleEntry->var_16 * 36; - } - // No vehicle images - vehicleEntry->no_vehicle_images = image_index - cur_vehicle_images_offset; - - // Move the offset over this vehicles images. Including peeps - cur_vehicle_images_offset = image_index + vehicleEntry->no_seating_rows * vehicleEntry->no_vehicle_images; - // 0x6DEB0D - - if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_10)) { - int num_images = cur_vehicle_images_offset - vehicleEntry->base_image_id; - if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_13) { - num_images *= 2; - } - - set_vehicle_type_image_max_sizes(vehicleEntry, num_images); - } - - sint8 no_positions = *peep_loading_positions++; - if (no_positions == -1) { - // The no_positions is 16 bit skip over - peep_loading_positions += 2; - } - vehicleEntry->peep_loading_positions = peep_loading_positions; - } - } - - // 0x6DEB71 - if (RCT2_GLOBAL(0x9ADAFD, uint8) == 0) { - for (int i = 0; i < 3; i++) { - int dl = rideEntry->ride_type[i]; - if (dl == 0xFF) { - continue; - } - - uint8 *typeToRideEntryIndexMap = gTypeToRideEntryIndexMap; - while (dl >= 0) { - if (*typeToRideEntryIndexMap++ == 0xFF) { - dl--; - } - } - - typeToRideEntryIndexMap--; - uint8 previous_entry = entryIndex; - while (typeToRideEntryIndexMap < gTypeToRideEntryIndexMap + countof(gTypeToRideEntryIndexMap)){ - uint8 backup_entry = *typeToRideEntryIndexMap; - *typeToRideEntryIndexMap++ = previous_entry; - previous_entry = backup_entry; - } - } - } - - // 0x6DEBAA - if (RCT2_GLOBAL(0x9ADAF4, sint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - int di = rideEntry->ride_type[0] | (rideEntry->ride_type[1] << 8) | (rideEntry->ride_type[2] << 16); - - if ((rideEntry->flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE_NAME) && !rideTypeShouldLoseSeparateFlag(rideEntry)) { - di |= 0x1000000; - } - - RCT2_GLOBAL(0xF433DD, uint32) = di; - return true; -} - -static void object_type_ride_unload(void *objectEntry) -{ - rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; - rideEntry->name = 0; - rideEntry->description = 0; - rideEntry->images_offset = 0; - - for (int i = 0; i < 4; i++) { - rct_ride_entry_vehicle* rideVehicleEntry = &rideEntry->vehicles[i]; - - rideVehicleEntry->base_image_id = 0; - rideVehicleEntry->var_1C = 0; - rideVehicleEntry->var_20 = 0; - rideVehicleEntry->var_24 = 0; - rideVehicleEntry->var_28 = 0; - rideVehicleEntry->var_2C = 0; - rideVehicleEntry->var_30 = 0; - rideVehicleEntry->var_34 = 0; - rideVehicleEntry->var_38 = 0; - rideVehicleEntry->var_3C = 0; - rideVehicleEntry->var_40 = 0; - rideVehicleEntry->var_44 = 0; - rideVehicleEntry->var_48 = 0; - rideVehicleEntry->var_4C = 0; - rideVehicleEntry->no_vehicle_images = 0; - rideVehicleEntry->var_16 = 0; - - if (!(rideVehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_10)) { - rideVehicleEntry->sprite_width = 0; - rideVehicleEntry->sprite_height_negative = 0; - rideVehicleEntry->sprite_height_positive = 0; - } - rideVehicleEntry->var_02 = 0; - rideVehicleEntry->var_03 = 0; - rideVehicleEntry->peep_loading_positions = 0; - } - - rideEntry->vehicle_preset_list = NULL; -} - -static bool object_type_ride_test(void *objectEntry) -{ - rct_ride_entry* rideEntry = (rct_ride_entry*)objectEntry; - if (rideEntry->excitement_multipler > 75) return false; - if (rideEntry->intensity_multipler > 75) return false; - if (rideEntry->nausea_multipler > 75) return false; - return true; -} - -static void object_type_ride_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; - int imageId = rideEntry->images_offset; - if (rideEntry->ride_type[0] == 0xFF) { - imageId++; - if (rideEntry->ride_type[1] == 0xFF) { - imageId++; - } - } - gfx_draw_sprite(dpi, imageId, x - 56, y - 56, 0); -} - -static rct_string_id object_type_ride_desc(void *objectEntry) -{ - rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; - - // Get description - rct_string_id stringId = rideEntry->description; - if (!(rideEntry->flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE_NAME) || rideTypeShouldLoseSeparateFlag(rideEntry)) { - uint8 rideType = rideEntry->ride_type[0]; - if (rideType == 0xFF) { - rideType = rideEntry->ride_type[1]; - if (rideType == 0xFF) { - rideType = rideEntry->ride_type[2]; - } - } - stringId = 512 + rideType; - } - return stringId; -} - -static const object_type_vtable object_type_ride_vtable[] = { - object_type_ride_load, - object_type_ride_unload, - object_type_ride_test, - object_type_ride_paint, - object_type_ride_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Small Scenery (rct2: 0x006E3466) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_small_scenery_load(void *objectEntry, uint32 entryIndex) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x1C); - - sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_SMALL_SCENERY, entryIndex, 0); - sceneryEntry->small_scenery.scenery_tab_id = 0xFF; - if (*extendedEntryData != 0xFF) { - uint8 entry_type, entry_index; - if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) { - sceneryEntry->small_scenery.scenery_tab_id = entry_index; - } - } - - extendedEntryData += sizeof(rct_object_entry); - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG16){ - sceneryEntry->small_scenery.var_10 = (uint32)extendedEntryData; - while (*++extendedEntryData != 0xFF); - extendedEntryData++; - } - - sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); - if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - return true; -} - -static void object_type_small_scenery_unload(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - sceneryEntry->name = 0; - sceneryEntry->image = 0; - sceneryEntry->small_scenery.var_10 = 0; - sceneryEntry->small_scenery.scenery_tab_id = 0; -} - -static bool object_type_small_scenery_test(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - - if (sceneryEntry->small_scenery.price <= 0) return false; - if (sceneryEntry->small_scenery.removal_price > 0) return true; - - // Make sure you don't make a profit when placing then removing. - if (-sceneryEntry->small_scenery.removal_price > sceneryEntry->small_scenery.price) return false; - - return true; -} - -static void object_type_small_scenery_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - rct_drawpixelinfo clipDPI; - if (!clip_drawpixelinfo(&clipDPI, dpi, x - 56, y - 56, 112, 112)) { - return; - } - - int imageId = sceneryEntry->image; - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR) { - imageId |= 0x20D00000; - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) { - imageId |= 0x92000000; - } - } - - x = 56; - y = sceneryEntry->small_scenery.height / 4 + 78; - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_FULL_TILE) { - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_VOFFSET_CENTRE) { - y -= 12; - } - } - - gfx_draw_sprite(&clipDPI, imageId, x, y, 0); - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG10) { - imageId = sceneryEntry->image + 0x44500004; - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) { - imageId |= 0x92000000; - } - - gfx_draw_sprite(&clipDPI, imageId, x, y, 0); - } - - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG8) { - imageId = sceneryEntry->image + 4; - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) { - imageId |= 0x92000000; - } - - gfx_draw_sprite(&clipDPI, imageId, x, y, 0); - } -} - -static rct_string_id object_type_small_scenery_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_small_scenery_vtable[] = { - object_type_small_scenery_load, - object_type_small_scenery_unload, - object_type_small_scenery_test, - object_type_small_scenery_paint, - object_type_small_scenery_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Large Scenery (rct2: 0x006B92A7) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_large_scenery_load(void *objectEntry, uint32 entryIndex) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x1A); - - sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_LARGE_SCENERY, entryIndex, 0); - sceneryEntry->large_scenery.scenery_tab_id = 0xFF; - if (*extendedEntryData != 0xFF) { - uint8 entry_type, entry_index; - if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) { - sceneryEntry->large_scenery.scenery_tab_id = entry_index; - } - } - - extendedEntryData += sizeof(rct_object_entry); - if (sceneryEntry->large_scenery.flags & (1 << 2)) { - sceneryEntry->large_scenery.text = (rct_large_scenery_text*)extendedEntryData; - extendedEntryData += 1038; - } - - sceneryEntry->large_scenery.tiles = (rct_large_scenery_tile*)extendedEntryData; - - // skip over large scenery tiles - while (*((uint16*)extendedEntryData) != 0xFFFF){ - extendedEntryData += sizeof(rct_large_scenery_tile); - } - - extendedEntryData += 2; - - int imageId = object_chunk_load_image_directory(&extendedEntryData); - if (sceneryEntry->large_scenery.flags & (1 << 2)){ - sceneryEntry->large_scenery.text_image = imageId; - - uint8* edx = (uint8*)sceneryEntry->large_scenery.text; - if (!(edx[0xC] & 1)) { - imageId += edx[0xD] * 4; - } else{ - imageId += edx[0xD] * 2; - } - } - sceneryEntry->image = imageId; - if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - return true; -} - -static void object_type_large_scenery_unload(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - sceneryEntry->name = 0; - sceneryEntry->image = 0; - sceneryEntry->large_scenery.tiles = 0; - sceneryEntry->large_scenery.scenery_tab_id = 0; - sceneryEntry->large_scenery.text = 0; - sceneryEntry->large_scenery.text_image = 0; -} - -static bool object_type_large_scenery_test(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - - if (sceneryEntry->large_scenery.price <= 0) return false; - if (sceneryEntry->large_scenery.removal_price > 0) return true; - - // Make sure you don't make a profit when placing then removing. - if (-sceneryEntry->large_scenery.removal_price > sceneryEntry->large_scenery.price) return false; - - return true; -} - -static void object_type_large_scenery_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - - int imageId = sceneryEntry->image | 0xB2D00000; - gfx_draw_sprite(dpi, imageId, x, y - 39, 0); -} - -static rct_string_id object_type_large_scenery_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_large_scenery_vtable[] = { - object_type_large_scenery_load, - object_type_large_scenery_unload, - object_type_large_scenery_test, - object_type_large_scenery_paint, - object_type_large_scenery_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Wall (rct2: 0x006E5A25) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_wall_load(void *objectEntry, uint32 entryIndex) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0E); - - sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_WALLS, entryIndex, 0); - sceneryEntry->wall.scenery_tab_id = 0xFF; - if (*extendedEntryData != 0xFF){ - uint8 entry_type, entry_index; - if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) { - sceneryEntry->wall.scenery_tab_id = entry_index; - } - } - - extendedEntryData += sizeof(rct_object_entry); - sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); - - if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - return true; - -} - -static void object_type_wall_unload(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - sceneryEntry->name = 0; - sceneryEntry->image = 0; - sceneryEntry->wall.scenery_tab_id = 0; -} - -static bool object_type_wall_test(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - if (sceneryEntry->wall.price <= 0) return false; - return true; -} - -static void object_type_wall_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - rct_drawpixelinfo clipDPI; - if (!clip_drawpixelinfo(&clipDPI, dpi, x - 56, y - 56, 112, 112)) { - return; - } - - int imageId = sceneryEntry->image; - imageId |= 0x20D00000; - if (sceneryEntry->wall.flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) { - imageId |= 0x92000000; - } - - x = 70; - y = sceneryEntry->wall.height * 2 + 72; - gfx_draw_sprite(&clipDPI, imageId, x, y, 0); - - if (sceneryEntry->wall.flags & WALL_SCENERY_FLAG2){ - imageId = sceneryEntry->image + 0x44500006; - gfx_draw_sprite(&clipDPI, imageId, x, y, 0); - } else if (sceneryEntry->wall.flags & WALL_SCENERY_IS_DOOR){ - imageId++; - gfx_draw_sprite(&clipDPI, imageId, x, y, 0); - } -} - -static rct_string_id object_type_wall_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_wall_vtable[] = { - object_type_wall_load, - object_type_wall_unload, - object_type_wall_test, - object_type_wall_paint, - object_type_wall_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Banner (rct2: 0x006BA84E) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_banner_load(void *objectEntry, uint32 entryIndex) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0C); - - sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_BANNERS, entryIndex, 0); - sceneryEntry->banner.scenery_tab_id = 0xFF; - if (*extendedEntryData != 0xFF){ - uint8 entry_type, entry_index; - if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)){ - sceneryEntry->banner.scenery_tab_id = entry_index; - } - } - - extendedEntryData += sizeof(rct_object_entry); - sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); - - if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - return true; -} - -static void object_type_banner_unload(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - sceneryEntry->name = 0; - sceneryEntry->image = 0; - sceneryEntry->banner.scenery_tab_id = 0; -} - -static bool object_type_banner_test(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - if (sceneryEntry->banner.price <= 0) return false; - return true; -} - -static void object_type_banner_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - - int imageId = sceneryEntry->image | 0x20D00000; - gfx_draw_sprite(dpi, imageId, x, y, 0); - gfx_draw_sprite(dpi, imageId + 1, x, y, 0); -} - -static rct_string_id object_type_banner_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_banner_vtable[] = { - object_type_banner_load, - object_type_banner_unload, - object_type_banner_test, - object_type_banner_paint, - object_type_banner_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Path (rct2: 0x006A8621) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_path_load(void *objectEntry, uint32 entryIndex) -{ - rct_footpath_entry *pathEntry = (rct_footpath_entry*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0E); - - pathEntry->string_idx = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_PATHS, entryIndex, 0); - - int imageId = object_chunk_load_image_directory(&extendedEntryData); - pathEntry->image = imageId; - pathEntry->bridge_image = imageId + 109; - - if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - return true; -} - -static void object_type_path_unload(void *objectEntry) -{ - rct_footpath_entry *pathEntry = (rct_footpath_entry*)objectEntry; - pathEntry->string_idx = 0; - pathEntry->image = 0; - pathEntry->bridge_image = 0; -} - -static bool object_type_path_test(void *objectEntry) -{ - rct_footpath_entry *pathEntry = (rct_footpath_entry*)objectEntry; - if (pathEntry->var_0A >= 2) return false; - return true; -} - -static void object_type_path_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_footpath_entry *pathEntry = (rct_footpath_entry*)objectEntry; - gfx_draw_sprite(dpi, pathEntry->image + 71, x - 49, y - 17, 0); - gfx_draw_sprite(dpi, pathEntry->image + 72, x + 4, y - 17, 0); -} - -static rct_string_id object_type_path_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_path_vtable[] = { - object_type_path_load, - object_type_path_unload, - object_type_path_test, - object_type_path_paint, - object_type_path_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Path Item (rct2: 0x006A86E2) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_path_bit_load(void *objectEntry, uint32 entryIndex) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0E); - - sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_PATH_BITS, entryIndex, 0); - sceneryEntry->path_bit.scenery_tab_id = 0xFF; - if (*extendedEntryData != 0xFF) { - uint8 entry_type, entry_index; - if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)){ - sceneryEntry->path_bit.scenery_tab_id = entry_index; - } - } - - extendedEntryData += sizeof(rct_object_entry); - sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); - - if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - return true; -} - -static void object_type_path_bit_unload(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - sceneryEntry->name = 0; - sceneryEntry->image = 0; - sceneryEntry->path_bit.scenery_tab_id = 0; -} - -static bool object_type_path_bit_test(void *objectEntry) -{ - rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; - if (sceneryEntry->path_bit.price <= 0) return false; - return true; -} - -static void object_type_path_bit_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; - gfx_draw_sprite(dpi, sceneryEntry->image, x - 22, y - 24, 0); -} - -static rct_string_id object_type_path_bit_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_path_bit_vtable[] = { - object_type_path_bit_load, - object_type_path_bit_unload, - object_type_path_bit_test, - object_type_path_bit_paint, - object_type_path_bit_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Scenery Set (rct2: 0x006B93AA) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_scenery_set_load(void *objectEntry, uint32 entryIndex) -{ - rct_scenery_set_entry *scenerySetEntry = (rct_scenery_set_entry*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + sizeof(rct_scenery_set_entry)); - - scenerySetEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_SCENERY_SETS, entryIndex, 0); - - rct_object_entry *entryObjects = NULL; - uint8 *eax = RCT2_GLOBAL(0x9ADAF4, uint8*); - if ((uint32)eax != 0xFFFFFFFF){ - *((uint16*)eax) = 0; - entryObjects = (rct_object_entry*)(eax + 2); - } - - scenerySetEntry->entry_count = 0; - scenerySetEntry->var_107 = 0; - - for (; *extendedEntryData != 0xFF; extendedEntryData += sizeof(rct_object_entry)) { - scenerySetEntry->var_107++; - - if (entryObjects != NULL){ - memcpy(entryObjects, extendedEntryData, sizeof(rct_object_entry)); - entryObjects++; - (*(eax + 1))++; - } - uint8 entry_type; - uint8 entry_index = 0; - if (!find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) - continue; - - uint16 scenery_entry = entry_index; - - switch (entry_type){ - case OBJECT_TYPE_SMALL_SCENERY: - break; - case OBJECT_TYPE_LARGE_SCENERY: - scenery_entry |= 0x300; - break; - case OBJECT_TYPE_WALLS: - scenery_entry |= 0x200; - break; - case OBJECT_TYPE_PATH_BITS: - scenery_entry |= 0x100; - break; - default: - scenery_entry |= 0x400; - break; - } - - scenerySetEntry->scenery_entries[scenerySetEntry->entry_count++] = scenery_entry; - } - - extendedEntryData++; - scenerySetEntry->image = object_chunk_load_image_directory(&extendedEntryData); - - return true; -} - -static void object_type_scenery_set_unload(void *objectEntry) -{ - rct_scenery_set_entry *scenerySetEntry = (rct_scenery_set_entry*)objectEntry; - scenerySetEntry->name = 0; - scenerySetEntry->image = 0; - scenerySetEntry->entry_count = 0; - scenerySetEntry->var_107 = 0; - memset(scenerySetEntry->scenery_entries, 0, 256); -} - -static bool object_type_scenery_set_test(void *objectEntry) -{ - return true; -} - -static void object_type_scenery_set_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_scenery_set_entry *scenerySetEntry = (rct_scenery_set_entry*)objectEntry; - int imageId = scenerySetEntry->image + 0x20600001; - gfx_draw_sprite(dpi, imageId, x - 15, y - 14, 0); -} - -static rct_string_id object_type_scenery_set_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_scenery_set_vtable[] = { - object_type_scenery_set_load, - object_type_scenery_set_unload, - object_type_scenery_set_test, - object_type_scenery_set_paint, - object_type_scenery_set_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Park Entrance (rct2: 0x00666E42) -/////////////////////////////////////////////////////////////////////////////// - -bool object_type_park_entrance_load(void *objectEntry, uint32 entryIndex) -{ - rct_entrance_type *entranceType = (rct_entrance_type*)objectEntry; - uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + sizeof(rct_entrance_type)); - - entranceType->string_idx = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_PARK_ENTRANCE, entryIndex, 0); - entranceType->image_id = object_chunk_load_image_directory(&extendedEntryData); - - if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; - } - - return true; -} - -static void object_type_park_entrance_unload(void *objectEntry) -{ - rct_entrance_type *entranceType = (rct_entrance_type*)objectEntry; - entranceType->string_idx = 0; - entranceType->image_id = 0; -} - -static bool object_type_park_entrance_test(void *objectEntry) -{ - return true; -} - -static void object_type_park_entrance_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - rct_entrance_type *entranceType = (rct_entrance_type*)objectEntry; - - rct_drawpixelinfo clipDPI; - if (!clip_drawpixelinfo(&clipDPI, dpi, x - 56, y - 56, 112, 112)) { - return; - } - - int imageId = entranceType->image_id; - gfx_draw_sprite(&clipDPI, imageId + 1, 24, 68, 0); - gfx_draw_sprite(&clipDPI, imageId, 56, 84, 0); - gfx_draw_sprite(&clipDPI, imageId + 2, 88, 100, 0); -} - -static rct_string_id object_type_park_entrance_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_park_entrance_vtable[] = { - object_type_park_entrance_load, - object_type_park_entrance_unload, - object_type_park_entrance_test, - object_type_park_entrance_paint, - object_type_park_entrance_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Water (rct2: 0x006E6E2A) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_water_load(void *objectEntry, uint32 entryIndex) -{ - rct_water_type *waterEntry = (rct_water_type*)objectEntry; - - uint8 *pStringTable = (uint8*)((size_t)objectEntry + sizeof(rct_water_type)); - waterEntry->string_idx = object_get_localised_text(&pStringTable, OBJECT_TYPE_WATER, entryIndex, 0); - - int imageId = object_chunk_load_image_directory(&pStringTable); - waterEntry->image_id = imageId; - waterEntry->var_06 = imageId + 1; - waterEntry->var_0A = imageId + 4; - - if (RCT2_GLOBAL(0x009ADAF4, uint32) != 0xFFFFFFFF) { - *RCT2_GLOBAL(0x009ADAF4, uint16*) = 0; - } - - if (RCT2_GLOBAL(0x009ADAFD, uint8) == 0) { - load_palette(); - gfx_invalidate_screen(); - } - - return true; -} - -static void object_type_water_unload(void *objectEntry) -{ - rct_water_type *waterEntry = (rct_water_type*)objectEntry; - waterEntry->string_idx = 0; - waterEntry->image_id = 0; - waterEntry->var_06 = 0; - waterEntry->var_0A = 0; -} - -static bool object_type_water_test(void *objectEntry) -{ - return true; -} - -static void object_type_water_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - // Write (no image) - gfx_draw_string_centred(dpi, 3326, x, y, 0, NULL); -} - -static rct_string_id object_type_water_desc(void *objectEntry) -{ - return STR_NONE; -} - -static const object_type_vtable object_type_water_vtable[] = { - object_type_water_load, - object_type_water_unload, - object_type_water_test, - object_type_water_paint, - object_type_water_desc -}; - -/////////////////////////////////////////////////////////////////////////////// -// Stex (rct2: 0x0066B355) -/////////////////////////////////////////////////////////////////////////////// - -static bool object_type_stex_load(void *objectEntry, uint32 entryIndex) -{ - rct_stex_entry *stexEntry = (rct_stex_entry*)objectEntry; - uint8 *stringTable = (uint8*)((size_t)objectEntry + (size_t)0x08); - - stexEntry->scenario_name = object_get_localised_text(&stringTable, OBJECT_TYPE_SCENARIO_TEXT, entryIndex, 0); - stexEntry->park_name = object_get_localised_text(&stringTable, OBJECT_TYPE_SCENARIO_TEXT, entryIndex, 1); - stexEntry->details = object_get_localised_text(&stringTable, OBJECT_TYPE_SCENARIO_TEXT, entryIndex, 2); - - if (RCT2_GLOBAL(0x9ADAF4, int) != -1) { - RCT2_GLOBAL(0x9ADAF4, uint16*)[0] = 0; - } - - return true; -} - -static void object_type_stex_unload(void *objectEntry) -{ - rct_stex_entry *stexEntry = (rct_stex_entry*)objectEntry; - stexEntry->scenario_name = 0; - stexEntry->park_name = 0; - stexEntry->details = 0; -} - -static bool object_type_stex_test(void *objectEntry) -{ - return true; -} - -static void object_type_stex_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - // Write (no image) - gfx_draw_string_centred(dpi, 3326, x, y, 0, NULL); -} - -static rct_string_id object_type_stex_desc(void *objectEntry) -{ - rct_stex_entry *stexEntry = (rct_stex_entry*)objectEntry; - return stexEntry->details; -} - -static const object_type_vtable object_type_stex_vtable[] = { - object_type_stex_load, - object_type_stex_unload, - object_type_stex_test, - object_type_stex_paint, - object_type_stex_desc -}; - -/////////////////////////////////////////////////////////////////////////////// - -static const object_type_vtable * const object_type_vtables[] = { - object_type_ride_vtable, - object_type_small_scenery_vtable, - object_type_large_scenery_vtable, - object_type_wall_vtable, - object_type_banner_vtable, - object_type_path_vtable, - object_type_path_bit_vtable, - object_type_scenery_set_vtable, - object_type_park_entrance_vtable, - object_type_water_vtable, - object_type_stex_vtable -}; - -bool object_load(int type, void *objectEntry, uint32 entryIndex) -{ - assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); - const object_type_vtable *vtable = object_type_vtables[type]; - return vtable->load(objectEntry, entryIndex) ? 0 : 1; -} - -void object_unload(int type, void *objectEntry) -{ - assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); - const object_type_vtable *vtable = object_type_vtables[type]; - vtable->unload(objectEntry); -} - -bool object_test(int type, void *objectEntry) -{ - assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); - const object_type_vtable *vtable = object_type_vtables[type]; - return vtable->test(objectEntry); -} - -void object_paint(int type, void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) -{ - assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); - const object_type_vtable *vtable = object_type_vtables[type]; - vtable->paint(objectEntry, dpi, x, y); -} - -rct_string_id object_desc(int type, void *objectEntry) -{ - assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); - const object_type_vtable *vtable = object_type_vtables[type]; - return vtable->desc(objectEntry); -} - -/** - * - * rct2: 0x006A9428 - */ -int object_get_scenario_text(rct_object_entry *entry) -{ - rct_object_entry *installedObject = gInstalledObjects; - - installedObject = object_list_find(entry); - - if (installedObject == NULL){ - log_error("Object not found: %.8s", entry->name); - return 0; - } - - char path[MAX_PATH]; - char *objectPath = (char*)installedObject + 16; - substitute_path(path, gRCT2AddressObjectDataPath, objectPath); - - rct_object_entry openedEntry; - SDL_RWops* rw = SDL_RWFromFile(path, "rb"); - if (rw != NULL) { - SDL_RWread(rw, &openedEntry, sizeof(rct_object_entry), 1); - if (object_entry_compare(&openedEntry, entry)) { - - // Skip over the object entry - char *pos = (char*)installedObject + sizeof(rct_object_entry); - // Skip file name - while (*pos++); - - // Read chunk - int chunkSize = *((uint32*)pos); - - uint8 *chunk; - if (chunkSize == 0xFFFFFFFF) { - chunk = (uint8*)malloc(0x600000); - chunkSize = sawyercoding_read_chunk(rw, chunk); - chunk = realloc(chunk, chunkSize); - } - else { - chunk = (uint8*)malloc(chunkSize); - sawyercoding_read_chunk_with_size(rw, chunk, chunkSize); - } - SDL_RWclose(rw); - - // Calculate and check checksum - if (object_calculate_checksum(&openedEntry, chunk, chunkSize) != openedEntry.checksum) { - log_error("Opened object failed calculated checksum."); - free(chunk); - return 0; - } - - if (!object_test(openedEntry.flags & 0x0F, chunk)) { - // This is impossible for STEX entries to fail. - log_error("Opened object failed paint test."); - free(chunk); - return 0; - } - - // Save the real total images. - int total_no_images = gTotalNoImages; - - // This is being changed to force the images to be loaded into a different - // image id. - gTotalNoImages = 0x726E; - gStexTempChunk = (rct_stex_entry*)chunk; - // Not used anywhere. - RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_OBJECT, rct_object_entry) = openedEntry; - - // Tell text to be loaded into a different address - RCT2_GLOBAL(0x009ADAFC, uint8) = 255; - memcpy(gTempObjectLoadName, openedEntry.name, 8); - // Not used?? - RCT2_GLOBAL(0x009ADAFD, uint8) = 1; - object_load(openedEntry.flags & 0x0F, chunk, 0); - // Tell text to be loaded into normal address - RCT2_GLOBAL(0x009ADAFC, uint8) = 0; - // Not used?? - RCT2_GLOBAL(0x009ADAFD, uint8) = 0; - gTotalNoImages = total_no_images; - return 1; - } - log_error("Opened object didn't match."); - SDL_RWclose(rw); - return 0; - } - log_error("File failed to open."); - return 0; -} - -/** - * - * rct2: 0x006A982D - */ -void object_free_scenario_text() -{ - if (gStexTempChunk != NULL) { - free(gStexTempChunk); - gStexTempChunk = NULL; - } -} - -uintptr_t object_get_length(const rct_object_entry *entry) -{ - return (uintptr_t)object_get_next(entry) - (uintptr_t)entry; -} - -rct_object_entry *object_get_next(const rct_object_entry *entry) -{ - uint8 *pos = (uint8*)entry; - - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - // Skip size of chunk - pos += 4; - - // Skip required objects - pos += *pos * 16 + 1; - - // Skip theme objects - pos += *pos * 16 + 1; - - // Skip - pos += 4; - - return (rct_object_entry*)pos; -} - -char *object_get_name(rct_object_entry *entry) -{ - uint8 *pos = (uint8*)entry; - - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - return (char *)pos; -} diff --git a/src/object.h b/src/object.h index 3c9b1a3ea2..a0b6215c40 100644 --- a/src/object.h +++ b/src/object.h @@ -40,7 +40,7 @@ typedef enum{ OBJECT_SELECTION_FLAG_SELECTED = (1 << 0), OBJECT_SELECTION_FLAG_2 = (1 << 1), OBJECT_SELECTION_FLAG_IN_USE = (1 << 2), - OBJECT_SELECTION_FLAG_REQUIRED = (1 << 3), + // OBJECT_SELECTION_FLAG_REQUIRED = (1 << 3), // Unused feature OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED = (1 << 4), OBJECT_SELECTION_FLAG_6 = (1 << 5), OBJECT_SELECTION_FLAG_7 = (1 << 6), @@ -67,10 +67,15 @@ assert_struct_size(rct_object_entry, 0x10); * size: 0x14 */ typedef struct rct_object_entry_extended { - uint32 flags; - char name[8]; - uint32 checksum; - uint32 chunk_size; + union { + rct_object_entry entry; + struct { + uint32 flags; + char name[8]; + uint32 checksum; + uint32 chunk_size; + }; + }; } rct_object_entry_extended; assert_struct_size(rct_object_entry_extended, 0x14); @@ -100,50 +105,23 @@ assert_struct_size(rct_object_filters, 3); #pragma pack(pop) extern const rct_object_entry_group object_entry_groups[]; -extern char gTempObjectLoadName[9]; -extern uint32 gInstalledObjectsCount; -extern rct_object_entry *gInstalledObjects; -extern uint32 gNumInstalledRCT2Objects; -extern uint32 gNumInstalledCustomObjects; -extern uint32 gTotalNoImages; - -extern void *gLastLoadedObjectChunkData; - -int object_load_entry(const utf8 *path, rct_object_entry *outEntry); void object_list_load(); -void set_load_objects_fail_reason(); bool object_read_and_load_entries(SDL_RWops* rw); bool object_load_entries(rct_object_entry* entries); int object_load_packed(SDL_RWops* rw); -void object_unload_all(); +bool object_saved_packed(SDL_RWops* rw, const rct_object_entry * entry); -int check_object_entry(rct_object_entry *entry); -int object_load_file(int groupIndex, const rct_object_entry *entry, int* chunkSize, const rct_object_entry *installedObject); -int object_load_chunk(int groupIndex, rct_object_entry *entry, int* chunk_size); -void object_unload_chunk(rct_object_entry *entry); -int object_get_scenario_text(rct_object_entry *entry); -void object_free_scenario_text(); -uintptr_t object_get_length(const rct_object_entry *entry); -int object_entry_compare(const rct_object_entry *a, const rct_object_entry *b); -int object_calculate_checksum(const rct_object_entry *entry, const uint8 *data, int dataLength); -rct_object_entry *object_get_next(const rct_object_entry *entry); -int write_object_file(SDL_RWops* rw, rct_object_entry* entry); -void reset_loaded_objects(); -int find_object_in_entry_group(rct_object_entry* entry, uint8* entry_type, uint8* entry_index); +int check_object_entry(const rct_object_entry *entry); +bool object_entry_compare(const rct_object_entry *a, const rct_object_entry *b); +int object_calculate_checksum(const rct_object_entry * entry, const void * data, size_t dataLength); +int find_object_in_entry_group(const rct_object_entry* entry, uint8* entry_type, uint8* entry_index); void object_create_identifier_name(char* string_buffer, const rct_object_entry* object); rct_object_entry *object_list_find_by_name(const char *name); rct_object_entry *object_list_find(rct_object_entry *entry); -char *object_get_name(rct_object_entry *entry); - -rct_object_filters *get_object_filter(int index); - -bool object_load(int type, void *objectEntry, uint32 entryIndex); -void object_unload(int type, void *objectEntry); -bool object_test(int type, void *objectEntry); -void object_paint(int type, void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y); -rct_string_id object_desc(int type, void *objectEntry); +void object_entry_get_name(utf8 * buffer, size_t bufferSize, const rct_object_entry * entry); +void object_entry_get_name_fixed(utf8 * buffer, size_t bufferSize, const rct_object_entry * entry); #endif diff --git a/src/object/BannerObject.cpp b/src/object/BannerObject.cpp new file mode 100644 index 0000000000..4daa3d3fc5 --- /dev/null +++ b/src/object/BannerObject.cpp @@ -0,0 +1,78 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "../core/Memory.hpp" +#include "BannerObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +void BannerObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.banner.scrolling_mode = stream->ReadValue(); + _legacyType.banner.flags = stream->ReadValue(); + _legacyType.banner.price = stream->ReadValue(); + _legacyType.banner.scenery_tab_id = stream->ReadValue(); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + + rct_object_entry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(&sgEntry); + + GetImageTable()->Read(context, stream); + + // Validate properties + if (_legacyType.large_scenery.price <= 0) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Price can not be free or negative."); + } +} + +void BannerObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.name = language_allocate_object_string(GetName()); + _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); +} + +void BannerObject::Unload() +{ + language_free_object_string(_legacyType.name); + gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + + _legacyType.name = 0; + _legacyType.image = 0; +} + +void BannerObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + sint32 x = width / 2; + sint32 y = height / 2; + + uint32 imageId = 0x20D00000 | _legacyType.image; + gfx_draw_sprite(dpi, imageId + 0, x - 12, y + 8, 0); + gfx_draw_sprite(dpi, imageId + 1, x - 12, y + 8, 0); +} diff --git a/src/object/BannerObject.h b/src/object/BannerObject.h new file mode 100644 index 0000000000..aebac64ae0 --- /dev/null +++ b/src/object/BannerObject.h @@ -0,0 +1,41 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "SceneryObject.h" + +extern "C" +{ + #include "../world/scenery.h" +} + +class BannerObject : public SceneryObject +{ +private: + rct_scenery_entry _legacyType = { 0 }; + +public: + explicit BannerObject(const rct_object_entry &entry) : SceneryObject(entry) { }; + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; +}; diff --git a/src/object/EntranceObject.cpp b/src/object/EntranceObject.cpp new file mode 100644 index 0000000000..2d4656f9bd --- /dev/null +++ b/src/object/EntranceObject.cpp @@ -0,0 +1,66 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "EntranceObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +void EntranceObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.scrolling_mode = stream->ReadValue(); + _legacyType.text_height = stream->ReadValue(); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetImageTable()->Read(context, stream); +} + +void EntranceObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.string_idx = language_allocate_object_string(GetName()); + _legacyType.image_id = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); +} + +void EntranceObject::Unload() +{ + language_free_object_string(_legacyType.string_idx); + gfx_object_free_images(_legacyType.image_id, GetImageTable()->GetCount()); + + _legacyType.string_idx = 0; + _legacyType.image_id = 0; +} + +void EntranceObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + sint32 x = width / 2; + sint32 y = height / 2; + + uint32 imageId = _legacyType.image_id; + gfx_draw_sprite(dpi, imageId + 1, x - 32, y + 14, 0); + gfx_draw_sprite(dpi, imageId + 0, x + 0, y + 28, 0); + gfx_draw_sprite(dpi, imageId + 2, x + 32, y + 44, 0); +} diff --git a/src/object/EntranceObject.h b/src/object/EntranceObject.h new file mode 100644 index 0000000000..93e2d7763d --- /dev/null +++ b/src/object/EntranceObject.h @@ -0,0 +1,41 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Object.h" + +extern "C" +{ + #include "../world/entrance.h" +} + +class EntranceObject : public Object +{ +private: + rct_entrance_type _legacyType = { 0 }; + +public: + explicit EntranceObject(const rct_object_entry &entry) : Object(entry) { }; + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; +}; diff --git a/src/object/FootpathItemObject.cpp b/src/object/FootpathItemObject.cpp new file mode 100644 index 0000000000..7a24d7a6a1 --- /dev/null +++ b/src/object/FootpathItemObject.cpp @@ -0,0 +1,77 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "FootpathItemObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +void FootpathItemObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.path_bit.flags = stream->ReadValue(); + _legacyType.path_bit.draw_type = stream->ReadValue(); + _legacyType.path_bit.tool_id = stream->ReadValue(); + _legacyType.path_bit.price = stream->ReadValue(); + _legacyType.path_bit.scenery_tab_id = stream->ReadValue(); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + + rct_object_entry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(&sgEntry); + + GetImageTable()->Read(context, stream); + + // Validate properties + if (_legacyType.large_scenery.price <= 0) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Price can not be free or negative."); + } +} + +void FootpathItemObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.name = language_allocate_object_string(GetName()); + _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + + _legacyType.path_bit.scenery_tab_id = 0xFF; +} + +void FootpathItemObject::Unload() +{ + language_free_object_string(_legacyType.name); + gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + + _legacyType.name = 0; + _legacyType.image = 0; +} + +void FootpathItemObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + sint32 x = width / 2; + sint32 y = height / 2; + gfx_draw_sprite(dpi, _legacyType.image, x - 22, y - 24, 0); +} diff --git a/src/object/FootpathItemObject.h b/src/object/FootpathItemObject.h new file mode 100644 index 0000000000..889a309825 --- /dev/null +++ b/src/object/FootpathItemObject.h @@ -0,0 +1,41 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "SceneryObject.h" + +extern "C" +{ + #include "../world/scenery.h" +} + +class FootpathItemObject : public SceneryObject +{ +private: + rct_scenery_entry _legacyType = { 0 }; + +public: + explicit FootpathItemObject(const rct_object_entry &entry) : SceneryObject(entry) { }; + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; +}; diff --git a/src/object/FootpathObject.cpp b/src/object/FootpathObject.cpp new file mode 100644 index 0000000000..b892d2612f --- /dev/null +++ b/src/object/FootpathObject.cpp @@ -0,0 +1,73 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "FootpathObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" + #include "../world/footpath.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +void FootpathObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(10, STREAM_SEEK_CURRENT); + _legacyType.var_0A = stream->ReadValue(); + _legacyType.flags = stream->ReadValue(); + _legacyType.scrolling_mode = stream->ReadValue(); + stream->Seek(1, STREAM_SEEK_CURRENT); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetImageTable()->Read(context, stream); + + // Validate properties + if (_legacyType.var_0A > 1) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "VAR_0A can not be greater than 1."); + } +} + +void FootpathObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.string_idx = language_allocate_object_string(GetName()); + _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.bridge_image = _legacyType.image + 109; +} + +void FootpathObject::Unload() +{ + language_free_object_string(_legacyType.string_idx); + gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + + _legacyType.string_idx = 0; + _legacyType.image = 0; +} + +void FootpathObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + sint32 x = width / 2; + sint32 y = height / 2; + gfx_draw_sprite(dpi, _legacyType.image + 71, x - 49, y - 17, 0); + gfx_draw_sprite(dpi, _legacyType.image + 72, x + 4, y - 17, 0); +} diff --git a/src/object/FootpathObject.h b/src/object/FootpathObject.h new file mode 100644 index 0000000000..327a061538 --- /dev/null +++ b/src/object/FootpathObject.h @@ -0,0 +1,41 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Object.h" + +extern "C" +{ + #include "../world/footpath.h" +} + +class FootpathObject : public Object +{ +private: + rct_footpath_entry _legacyType = { 0 }; + +public: + explicit FootpathObject(const rct_object_entry &entry) : Object(entry) { }; + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; +}; diff --git a/src/object/ImageTable.cpp b/src/object/ImageTable.cpp new file mode 100644 index 0000000000..a8c81266d5 --- /dev/null +++ b/src/object/ImageTable.cpp @@ -0,0 +1,87 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/Console.hpp" +#include "../core/IStream.hpp" +#include "../core/Memory.hpp" +#include "ImageTable.h" +#include "Object.h" + +ImageTable::~ImageTable() +{ + Memory::Free(_data); + _data = nullptr; + _dataSize = 0; +} + +void ImageTable::Read(IReadObjectContext * context, IStream * stream) +{ + try + { + uint32 numImages = stream->ReadValue(); + uint32 imageDataSize = stream->ReadValue(); + + uint64 headerTableSize = numImages * 16; + uint64 remainingBytes = stream->GetLength() - stream->GetPosition() - headerTableSize; + if (remainingBytes > imageDataSize) + { + context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, "Image table size longer than expected."); + imageDataSize = (uint32)remainingBytes; + } + + _dataSize = imageDataSize; + _data = Memory::Reallocate(_data, _dataSize); + + // Read g1 element headers + uintptr_t imageDataBase = (uintptr_t)_data; + for (uint32 i = 0; i < numImages; i++) + { + rct_g1_element g1Element; + + uintptr_t imageDataOffset = (uintptr_t)stream->ReadValue(); + g1Element.offset = (uint8*)(imageDataBase + imageDataOffset); + + g1Element.width = stream->ReadValue(); + g1Element.height = stream->ReadValue(); + g1Element.x_offset = stream->ReadValue(); + g1Element.y_offset = stream->ReadValue(); + g1Element.flags = stream->ReadValue(); + g1Element.zoomed_offset = stream->ReadValue(); + + _entries.push_back(g1Element); + } + + // Read g1 element data + size_t readBytes = (size_t)stream->TryRead(_data, _dataSize); + + // If data is shorter than expected (some custom objects are unfortunately like that) + size_t unreadBytes = _dataSize - readBytes; + if (unreadBytes > 0) + { + void * ptr = (void*)(((uintptr_t)_data) + readBytes); + Memory::Set(ptr, 0, unreadBytes); + + context->LogWarning(OBJECT_ERROR_BAD_IMAGE_TABLE, "Image table size shorter than expected."); + } + + // TODO validate the image data to prevent crashes in-game + } + catch (Exception ex) + { + context->LogError(OBJECT_ERROR_BAD_IMAGE_TABLE, "Bad image table."); + throw; + } +} diff --git a/src/object/ImageTable.h b/src/object/ImageTable.h new file mode 100644 index 0000000000..4692254625 --- /dev/null +++ b/src/object/ImageTable.h @@ -0,0 +1,43 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include "../common.h" + +extern "C" +{ + #include "../drawing/drawing.h" +} + +interface IReadObjectContext; +interface IStream; + +class ImageTable +{ +private: + std::vector _entries; + void * _data = nullptr; + size_t _dataSize = 0; + +public: + ~ImageTable(); + + void Read(IReadObjectContext * context, IStream * stream); + const rct_g1_element * GetImages() const { return _entries.data(); } + uint32 GetCount() const { return _entries.size(); }; +}; \ No newline at end of file diff --git a/src/object/LargeSceneryObject.cpp b/src/object/LargeSceneryObject.cpp new file mode 100644 index 0000000000..47831c4e56 --- /dev/null +++ b/src/object/LargeSceneryObject.cpp @@ -0,0 +1,137 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "../core/Memory.hpp" +#include "LargeSceneryObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +LargeSceneryObject::~LargeSceneryObject() +{ + Memory::Free(_3dFont); + Memory::Free(_tiles); +} + +void LargeSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.large_scenery.tool_id = stream->ReadValue(); + _legacyType.large_scenery.flags = stream->ReadValue(); + _legacyType.large_scenery.price = stream->ReadValue(); + _legacyType.large_scenery.removal_price = stream->ReadValue(); + stream->Seek(5, STREAM_SEEK_CURRENT); + _legacyType.large_scenery.scenery_tab_id = 0xFF; + _legacyType.large_scenery.var_11 = stream->ReadValue(); + stream->Seek(4, STREAM_SEEK_CURRENT); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + + rct_object_entry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(&sgEntry); + + if (_legacyType.large_scenery.flags & (1 << 2)) + { + _3dFont = Memory::Allocate(); + stream->Read(_3dFont); + _legacyType.large_scenery.text = _3dFont; + } + + _tiles = ReadTiles(stream); + + GetImageTable()->Read(context, stream); + + // Validate properties + if (_legacyType.large_scenery.price <= 0) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Price can not be free or negative."); + } + if (_legacyType.large_scenery.removal_price <= 0) + { + // Make sure you don't make a profit when placing then removing. + money16 reimbursement = _legacyType.large_scenery.removal_price; + if (reimbursement > _legacyType.large_scenery.price) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Sell price can not be more than buy price."); + } + } +} + +void LargeSceneryObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.name = language_allocate_object_string(GetName()); + _baseImageId = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.image = _baseImageId; + + _legacyType.large_scenery.tiles = _tiles; + + if (_legacyType.large_scenery.flags & (1 << 2)) + { + _legacyType.large_scenery.text_image = _legacyType.image; + if (_3dFont->var_C & (1 << 0)) + { + _legacyType.image += (_3dFont->var_C >> 8) * 2; + } + else + { + _legacyType.image += (_3dFont->var_C >> 8) * 4; + } + } +} + +void LargeSceneryObject::Unload() +{ + language_free_object_string(_legacyType.name); + gfx_object_free_images(_baseImageId, GetImageTable()->GetCount()); + + _legacyType.name = 0; + _legacyType.image = 0; +} + +void LargeSceneryObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + sint32 x = width / 2; + sint32 y = (height / 2) - 39; + + uint32 imageId = 0xB2D00000 | _legacyType.image; + gfx_draw_sprite(dpi, imageId, x, y, 0); +} + +rct_large_scenery_tile * LargeSceneryObject::ReadTiles(IStream * stream) +{ + auto tiles = std::vector(); + + uint16 tilesEndMarker; + while ((tilesEndMarker = stream->ReadValue()) != 0xFFFF) + { + stream->Seek(-2, STREAM_SEEK_CURRENT); + auto tile = stream->ReadValue(); + tiles.push_back(tile); + } + tiles.push_back({ -1, -1, -1, 255, 0xFFFF }); + + return Memory::DuplicateArray(tiles.data(), tiles.size()); +} diff --git a/src/object/LargeSceneryObject.h b/src/object/LargeSceneryObject.h new file mode 100644 index 0000000000..ebe3a4c3a9 --- /dev/null +++ b/src/object/LargeSceneryObject.h @@ -0,0 +1,48 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "SceneryObject.h" + +extern "C" +{ + #include "../world/scenery.h" +} + +class LargeSceneryObject : public SceneryObject +{ +private: + rct_scenery_entry _legacyType = { 0 }; + uint32 _baseImageId = 0; + rct_large_scenery_text * _3dFont = nullptr; + rct_large_scenery_tile * _tiles = nullptr; + +public: + explicit LargeSceneryObject(const rct_object_entry &entry) : SceneryObject(entry) { }; + ~LargeSceneryObject(); + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; + +private: + static rct_large_scenery_tile * ReadTiles(IStream * stream); +}; diff --git a/src/object/Object.cpp b/src/object/Object.cpp new file mode 100644 index 0000000000..90f11bcd16 --- /dev/null +++ b/src/object/Object.cpp @@ -0,0 +1,71 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/Memory.hpp" +#include "../core/String.hpp" +#include "Object.h" + +extern "C" +{ + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +Object::Object(const rct_object_entry &entry) +{ + _objectEntry = entry; + + char name[9] = { 0 }; + Memory::Copy(name, entry.name, 8); + _identifier = String::Duplicate(name); +} + +Object::~Object() +{ + Memory::Free(_identifier); +} + +const utf8 * Object::GetOverrideString(uint8 index) const +{ + const char * identifier = GetIdentifier(); + rct_string_id stringId = language_get_object_override_string_id(identifier, index); + + const utf8 * result = nullptr; + if (stringId != STR_NONE) + { + result = language_get_string(stringId); + } + return result; +} + +const utf8 * Object::GetString(uint8 index) const +{ + const utf8 * sz = GetOverrideString(index); + if (sz == nullptr) + { + sz = GetStringTable()->GetString(index); + } + return sz != nullptr ? sz : ""; +} + +const utf8 * Object::GetName() const +{ + return GetString(OBJ_STRING_ID_NAME); +} diff --git a/src/object/Object.h b/src/object/Object.h new file mode 100644 index 0000000000..96f78ad046 --- /dev/null +++ b/src/object/Object.h @@ -0,0 +1,86 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" +#include "ImageTable.h" +#include "StringTable.h" + +extern "C" +{ + #include "../object.h" +} + +interface IStream; +struct ObjectRepositoryItem; +struct rct_drawpixelinfo; + +interface IReadObjectContext +{ + virtual ~IReadObjectContext() { } + + virtual void LogWarning(uint32 code, const utf8 * text) abstract; + virtual void LogError(uint32 code, const utf8 * text) abstract; +}; + +class Object +{ +private: + char * _identifier; + rct_object_entry _objectEntry; + StringTable _stringTable; + ImageTable _imageTable; + +protected: + StringTable * GetStringTable() { return &_stringTable; } + const StringTable * GetStringTable() const { return &_stringTable; } + ImageTable * GetImageTable() { return &_imageTable; } + + const utf8 * GetOverrideString(uint8 index) const; + const utf8 * GetString(uint8 index) const; + +public: + explicit Object(const rct_object_entry &entry); + virtual ~Object(); + + // Legacy data structures + const char * GetIdentifier() const { return _identifier; } + const rct_object_entry * GetObjectEntry() const { return &_objectEntry; } + virtual void * GetLegacyData() abstract; + + virtual void ReadLegacy(IReadObjectContext * context, IStream * stream) abstract; + virtual void Load() abstract; + virtual void Unload() abstract; + + virtual void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const { } + + virtual uint8 GetObjectType() const { return _objectEntry.flags & 0x0F; } + virtual const utf8 * GetName() const; + + virtual void SetRepositoryItem(ObjectRepositoryItem * item) const { } +}; + +enum OBJECT_ERROR : uint32 +{ + OBJECT_ERROR_OK, + OBJECT_ERROR_UNKNOWN, + OBJECT_ERROR_BAD_ENCODING, + OBJECT_ERROR_INVALID_PROPERTY, + OBJECT_ERROR_BAD_STRING_TABLE, + OBJECT_ERROR_BAD_IMAGE_TABLE, + OBJECT_ERROR_UNEXPECTED_EOF, +}; diff --git a/src/object/ObjectFactory.cpp b/src/object/ObjectFactory.cpp new file mode 100644 index 0000000000..080adb6bdc --- /dev/null +++ b/src/object/ObjectFactory.cpp @@ -0,0 +1,234 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/Console.hpp" +#include "../core/FileStream.hpp" +#include "../core/Memory.hpp" +#include "../core/MemoryStream.h" +#include "../core/String.hpp" +#include "../core/Path.hpp" +#include "BannerObject.h" +#include "EntranceObject.h" +#include "FootpathItemObject.h" +#include "FootpathObject.h" +#include "LargeSceneryObject.h" +#include "Object.h" +#include "ObjectFactory.h" +#include "RideObject.h" +#include "SceneryGroupObject.h" +#include "SmallSceneryObject.h" +#include "StexObject.h" +#include "WallObject.h" +#include "WaterObject.h" + +extern "C" +{ + #include "../object.h" + #include "../util/sawyercoding.h" +} + +class ReadObjectContext : public IReadObjectContext +{ +private: + utf8 * _objectName; + bool _wasWarning = false; + bool _wasError = false; + +public: + bool WasWarning() const { return _wasWarning; } + bool WasError() const { return _wasError; } + + ReadObjectContext(const utf8 * objectFileName) + { + _objectName = String::Duplicate(objectFileName); + } + + ~ReadObjectContext() override + { + Memory::Free(_objectName); + _objectName = nullptr; + } + + void LogWarning(uint32 code, const utf8 * text) override + { + _wasWarning = true; + + if (!String::IsNullOrEmpty(text)) + { + log_verbose("[%s] Warning: %s", _objectName, text); + } + } + + void LogError(uint32 code, const utf8 * text) override + { + _wasError = true; + + if (!String::IsNullOrEmpty(text)) + { + Console::Error::WriteLine("[%s] Error: %s", _objectName, text); + } + } +}; + +namespace ObjectFactory +{ + void ReadObjectLegacy(Object * object, IReadObjectContext * context, IStream * stream) + { + try + { + object->ReadLegacy(context, stream); + } + catch (IOException ex) + { + // TODO check that ex is really EOF and not some other error + context->LogError(OBJECT_ERROR_UNEXPECTED_EOF, "Unexpectedly reached end of file."); + } + catch (Exception ex) + { + context->LogError(OBJECT_ERROR_UNKNOWN, nullptr); + } + } + + MemoryStream * GetDecodedChunkStream(IReadObjectContext * context, SDL_RWops * file) + { + size_t bufferSize = 0x600000; + void * buffer = Memory::Allocate(bufferSize); + if (buffer == nullptr) + { + log_error("Unable to allocate data buffer."); + return nullptr; + } + + bufferSize = sawyercoding_read_chunk_with_size(file, (uint8 *)buffer, bufferSize); + if (bufferSize == SIZE_MAX) + { + context->LogError(OBJECT_ERROR_BAD_ENCODING, "Unable to decode chunk."); + Memory::Free(buffer); + return nullptr; + } + else + { + buffer = Memory::Reallocate(buffer, bufferSize); + return new MemoryStream(buffer, bufferSize, MEMORY_ACCESS_READ | MEMORY_ACCESS_OWNER); + } + } + + Object * CreateObjectFromLegacyFile(const utf8 * path) + { + Object * result = nullptr; + + SDL_RWops * file = SDL_RWFromFile(path, "rb"); + if (file != nullptr) + { + rct_object_entry entry; + if (SDL_RWread(file, &entry, sizeof(entry), 1) == 1) + { + result = CreateObject(entry); + if (result != nullptr) + { + utf8 objectName[9] = { 0 }; + Memory::Copy(objectName, entry.name, 8); + + auto readContext = ReadObjectContext(objectName); + auto chunkStream = GetDecodedChunkStream(&readContext, file); + if (chunkStream != nullptr) + { + ReadObjectLegacy(result, &readContext, chunkStream); + delete chunkStream; + } + + if (readContext.WasError()) + { + Console::Error::WriteLine("Error reading object: '%s'", path); + + delete result; + result = nullptr; + } + } + } + SDL_RWclose(file); + } + return result; + } + + Object * CreateObjectFromLegacyData(const rct_object_entry * entry, const void * data, size_t dataSize) + { + Guard::ArgumentNotNull(entry); + Guard::ArgumentNotNull(data); + + Object * result = CreateObject(*entry); + if (result != nullptr) + { + utf8 objectName[9]; + object_entry_get_name_fixed(objectName, sizeof(objectName), entry); + + auto readContext = ReadObjectContext(objectName); + auto chunkStream = MemoryStream(data, dataSize); + ReadObjectLegacy(result, &readContext, &chunkStream); + + if (readContext.WasError()) + { + delete result; + result = nullptr; + } + } + return result; + } + + Object * CreateObject(const rct_object_entry &entry) + { + Object * result = nullptr; + + uint8 objectType = entry.flags & 0x0F; + switch (objectType) { + case OBJECT_TYPE_RIDE: + result = new RideObject(entry); + break; + case OBJECT_TYPE_SMALL_SCENERY: + result = new SmallSceneryObject(entry); + break; + case OBJECT_TYPE_LARGE_SCENERY: + result = new LargeSceneryObject(entry); + break; + case OBJECT_TYPE_WALLS: + result = new WallObject(entry); + break; + case OBJECT_TYPE_BANNERS: + result = new BannerObject(entry); + break; + case OBJECT_TYPE_PATHS: + result = new FootpathObject(entry); + break; + case OBJECT_TYPE_PATH_BITS: + result = new FootpathItemObject(entry); + break; + case OBJECT_TYPE_SCENERY_SETS: + result = new SceneryGroupObject(entry); + break; + case OBJECT_TYPE_PARK_ENTRANCE: + result = new EntranceObject(entry); + break; + case OBJECT_TYPE_WATER: + result = new WaterObject(entry); + break; + case OBJECT_TYPE_SCENARIO_TEXT: + result = new StexObject(entry); + break; + } + + return result; + } +} diff --git a/src/core/IDisposable.hpp b/src/object/ObjectFactory.h similarity index 75% rename from src/core/IDisposable.hpp rename to src/object/ObjectFactory.h index 7a0492f877..455fa256b9 100644 --- a/src/core/IDisposable.hpp +++ b/src/object/ObjectFactory.h @@ -18,10 +18,11 @@ #include "../common.h" -/** - * Represents an object that can be disposed. So things can explicitly close resources before the destructor kicks in. - */ -interface IDisposable +class Object; + +namespace ObjectFactory { - virtual void Dispose() abstract; -}; + Object * CreateObjectFromLegacyFile(const utf8 * path); + Object * CreateObjectFromLegacyData(const rct_object_entry * entry, const void * data, size_t dataSize); + Object * CreateObject(const rct_object_entry &entry); +} diff --git a/src/object/ObjectManager.cpp b/src/object/ObjectManager.cpp new file mode 100644 index 0000000000..70b18484a9 --- /dev/null +++ b/src/object/ObjectManager.cpp @@ -0,0 +1,571 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include "../core/Console.hpp" +#include "../core/Memory.hpp" +#include "FootpathItemObject.h" +#include "LargeSceneryObject.h" +#include "Object.h" +#include "ObjectManager.h" +#include "ObjectRepository.h" +#include "SceneryGroupObject.h" +#include "SmallSceneryObject.h" +#include "WallObject.h" + +extern "C" +{ + #include "../object_list.h" +} + +class ObjectManager : public IObjectManager +{ +private: + IObjectRepository * _objectRepository; + Object * * _loadedObjects = nullptr; + +public: + ObjectManager(IObjectRepository * objectRepository) + { + _objectRepository = objectRepository; + } + + ~ObjectManager() override + { + SetNewLoadedObjectList(nullptr); + } + + Object * GetLoadedObject(size_t index) override + { + if (_loadedObjects == nullptr) + { + return nullptr; + } + return _loadedObjects[index]; + } + + Object * GetLoadedObject(const rct_object_entry * entry) override + { + Object * loadedObject = nullptr; + const ObjectRepositoryItem * ori = _objectRepository->FindObject(entry); + if (ori != nullptr) + { + loadedObject = ori->LoadedObject; + } + return loadedObject; + } + + uint8 GetLoadedObjectEntryIndex(const Object * object) override + { + uint8 result = UINT8_MAX; + size_t index = GetLoadedObjectIndex(object); + if (index != SIZE_MAX) + { + get_type_entry_index(index, nullptr, &result); + } + return result; + } + + Object * LoadObject(const rct_object_entry * entry) override + { + Object * loadedObject = nullptr; + const ObjectRepositoryItem * ori = _objectRepository->FindObject(entry); + if (ori != nullptr) + { + loadedObject = ori->LoadedObject; + if (loadedObject == nullptr) + { + uint8 objectType = entry->flags & 0x0F; + sint32 slot = FindSpareSlot(objectType); + if (slot != -1) + { + loadedObject = GetOrLoadObject(ori); + if (loadedObject != nullptr) + { + _loadedObjects[slot] = loadedObject; + UpdateLegacyLoadedObjectList(); + UpdateSceneryGroupIndexes(); + reset_type_to_ride_entry_index_map(); + } + } + } + } + return loadedObject; + } + + bool LoadObjects(const rct_object_entry * entries, size_t count) override + { + IObjectRepository * objectRepository = GetObjectRepository(); + + // Find all the required objects + size_t numRequiredObjects; + auto requiredObjects = new const ObjectRepositoryItem *[OBJECT_ENTRY_COUNT]; + if (!GetRequiredObjects(entries, requiredObjects, &numRequiredObjects)) + { + delete[] requiredObjects; + return false; + } + + // Create a new list of loaded objects + size_t numNewLoadedObjects; + Object * * loadedObjects = LoadObjects(requiredObjects, &numNewLoadedObjects); + + delete[] requiredObjects; + + if (loadedObjects == nullptr) + { + UnloadAll(); + return false; + } + else + { + SetNewLoadedObjectList(loadedObjects); + UpdateLegacyLoadedObjectList(); + UpdateSceneryGroupIndexes(); + reset_type_to_ride_entry_index_map(); + log_verbose("%u / %u new objects loaded", numNewLoadedObjects, numRequiredObjects); + return true; + } + } + + void UnloadObjects(const rct_object_entry * entries, size_t count) override + { + // TODO there are two performance issues here: + // - FindObject for every entry which is a dictionary lookup + // - GetLoadedObjectIndex for every entry which enumerates _loadedList + + size_t numObjectsUnloaded = 0; + for (size_t i = 0; i < count; i++) + { + const rct_object_entry * entry = &entries[i]; + const ObjectRepositoryItem * ori = _objectRepository->FindObject(entry); + if (ori != nullptr) + { + Object * loadedObject = ori->LoadedObject; + if (loadedObject != nullptr) + { + size_t index = GetLoadedObjectIndex(loadedObject); + + UnloadObject(loadedObject); + numObjectsUnloaded++; + } + } + } + + if (numObjectsUnloaded > 0) + { + UpdateLegacyLoadedObjectList(); + UpdateSceneryGroupIndexes(); + reset_type_to_ride_entry_index_map(); + } + } + + void UnloadAll() override + { + if (_loadedObjects != nullptr) + { + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + UnloadObject(_loadedObjects[i]); + } + } + UpdateLegacyLoadedObjectList(); + UpdateSceneryGroupIndexes(); + reset_type_to_ride_entry_index_map(); + } + + void ResetObjects() override + { + if (_loadedObjects != nullptr) + { + for (size_t i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + Object * loadedObject = _loadedObjects[i]; + if (loadedObject != nullptr) + { + loadedObject->Unload(); + loadedObject->Load(); + } + } + } + } + +private: + sint32 FindSpareSlot(uint8 objectType) + { + if (_loadedObjects != nullptr) + { + sint32 firstIndex = GetIndexFromTypeEntry(objectType, 0); + sint32 endIndex = firstIndex + object_entry_group_counts[objectType]; + for (sint32 i = firstIndex; i < endIndex; i++) + { + if (_loadedObjects[i] == nullptr) + { + return i; + } + } + } + return -1; + } + + size_t GetLoadedObjectIndex(const Object * object) + { + Guard::ArgumentNotNull(object); + + size_t result = SIZE_MAX; + if (_loadedObjects != nullptr) + { + for (size_t i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + if (_loadedObjects[i] == object) + { + result = i; + break; + } + } + } + return result; + } + + void SetNewLoadedObjectList(Object * * newLoadedObjects) + { + if (newLoadedObjects == nullptr) + { + UnloadAll(); + } + else + { + UnloadObjectsExcept(newLoadedObjects); + } + Memory::Free(_loadedObjects); + _loadedObjects = newLoadedObjects; + } + + void UnloadObject(Object * object) + { + if (object != nullptr) + { + // TODO try to prevent doing a repository search + const ObjectRepositoryItem * ori = _objectRepository->FindObject(object->GetObjectEntry()); + if (ori != nullptr) + { + _objectRepository->UnregisterLoadedObject(ori, object); + } + + object->Unload(); + delete object; + + // Because its possible to have the same loaded object for multiple + // slots, we have to make sure find and set all of them to nullptr + if (_loadedObjects != nullptr) + { + for (size_t i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + if (_loadedObjects[i] == object) + { + _loadedObjects[i] = nullptr; + } + } + } + } + } + + void UnloadObjectsExcept(Object * * newLoadedObjects) + { + if (_loadedObjects == nullptr) + { + return; + } + + // Build a hash set for quick checking + auto exceptSet = std::unordered_set(); + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + Object * object = newLoadedObjects[i]; + if (object != nullptr) + { + exceptSet.insert(object); + } + } + + // Unload objects that are not in the hash set + size_t totalObjectsLoaded = 0; + size_t numObjectsUnloaded = 0; + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + Object * object = _loadedObjects[i]; + if (object != nullptr) + { + totalObjectsLoaded++; + if (exceptSet.find(object) == exceptSet.end()) + { + UnloadObject(object); + numObjectsUnloaded++; + } + } + } + + log_verbose("%u / %u objects unloaded", numObjectsUnloaded, totalObjectsLoaded); + } + + void UpdateLegacyLoadedObjectList() + { + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + Object * loadedObject = nullptr; + if (_loadedObjects != nullptr) + { + loadedObject = _loadedObjects[i]; + } + + uint8 objectType, entryIndex; + get_type_entry_index(i, &objectType, &entryIndex); + + rct_object_entry_extended * legacyEntry = &object_entry_groups[objectType].entries[entryIndex]; + void * * legacyChunk = &object_entry_groups[objectType].chunks[entryIndex]; + if (loadedObject == nullptr) + { + Memory::Set(legacyEntry, 0xFF, sizeof(rct_object_entry_extended)); + *legacyChunk = (void *)-1; + } + else + { + legacyEntry->entry = *loadedObject->GetObjectEntry(); + legacyEntry->chunk_size = 0; + *legacyChunk = loadedObject->GetLegacyData(); + } + } + } + + void UpdateSceneryGroupIndexes() + { + if (_loadedObjects != nullptr) + { + for (size_t i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + Object * loadedObject = _loadedObjects[i]; + if (loadedObject != nullptr) + { + rct_scenery_entry * sceneryEntry; + switch (loadedObject->GetObjectType()) { + case OBJECT_TYPE_SMALL_SCENERY: + sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData(); + sceneryEntry->small_scenery.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); + break; + case OBJECT_TYPE_LARGE_SCENERY: + sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData(); + sceneryEntry->large_scenery.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); + break; + case OBJECT_TYPE_WALLS: + sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData(); + sceneryEntry->wall.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); + break; + case OBJECT_TYPE_BANNERS: + sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData(); + sceneryEntry->banner.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); + break; + case OBJECT_TYPE_PATH_BITS: + sceneryEntry = (rct_scenery_entry *)loadedObject->GetLegacyData(); + sceneryEntry->path_bit.scenery_tab_id = GetPrimarySceneryGroupEntryIndex(loadedObject); + break; + case OBJECT_TYPE_SCENERY_SETS: + auto sgObject = static_cast(loadedObject); + sgObject->UpdateEntryIndexes(); + break; + } + } + } + } + } + + uint8 GetPrimarySceneryGroupEntryIndex(Object * loadedObject) + { + auto sceneryObject = static_cast(loadedObject); + const rct_object_entry * primarySGEntry = sceneryObject->GetPrimarySceneryGroup(); + Object * sgObject = GetLoadedObject(primarySGEntry); + + uint8 entryIndex = 255; + if (sgObject != nullptr) + { + entryIndex = GetLoadedObjectEntryIndex(sgObject); + } + return entryIndex; + } + + bool GetRequiredObjects(const rct_object_entry * entries, + const ObjectRepositoryItem * * requiredObjects, + size_t * outNumRequiredObjects) + { + bool missingObjects = false; + size_t numRequiredObjects = 0; + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + const rct_object_entry * entry = &entries[i]; + const ObjectRepositoryItem * ori = nullptr; + if (check_object_entry(entry)) + { + ori = _objectRepository->FindObject(entry); + if (ori == nullptr) + { + missingObjects = true; + ReportMissingObject(entry); + } + numRequiredObjects++; + } + requiredObjects[i] = ori; + } + + if (outNumRequiredObjects != nullptr) + { + *outNumRequiredObjects = numRequiredObjects; + } + return !missingObjects; + } + + Object * * LoadObjects(const ObjectRepositoryItem * * requiredObjects, size_t * outNewObjectsLoaded) + { + size_t newObjectsLoaded = 0; + Object * * loadedObjects = Memory::AllocateArray(OBJECT_ENTRY_COUNT); + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) + { + Object * loadedObject = nullptr; + const ObjectRepositoryItem * ori = requiredObjects[i]; + if (ori != nullptr) + { + loadedObject = ori->LoadedObject; + if (loadedObject == nullptr) + { + loadedObject = GetOrLoadObject(ori); + if (loadedObject == nullptr) + { + ReportObjectLoadProblem(&ori->ObjectEntry); + Memory::Free(loadedObjects); + return nullptr; + } else { + newObjectsLoaded++; + } + } + } + loadedObjects[i] = loadedObject; + } + if (outNewObjectsLoaded != nullptr) + { + *outNewObjectsLoaded = newObjectsLoaded; + } + return loadedObjects; + } + + Object * GetOrLoadObject(const ObjectRepositoryItem * ori) + { + Object * loadedObject = ori->LoadedObject; + if (loadedObject == nullptr) + { + // Try to load object + loadedObject = _objectRepository->LoadObject(ori); + if (loadedObject != nullptr) + { + loadedObject->Load(); + + // Connect the ori to the registered object + _objectRepository->RegisterLoadedObject(ori, loadedObject); + } + } + return loadedObject; + } + + static void ReportMissingObject(const rct_object_entry * entry) + { + utf8 objName[9] = { 0 }; + Memory::Copy(objName, entry->name, 8); + Console::Error::WriteLine("[%s] Object not found.", objName); + } + + static void ReportObjectLoadProblem(const rct_object_entry * entry) + { + utf8 objName[9] = { 0 }; + Memory::Copy(objName, entry->name, 8); + Console::Error::WriteLine("[%s] Object could not be loaded.", objName); + } + + static sint32 GetIndexFromTypeEntry(uint8 objectType, uint8 entryIndex) + { + int result = 0; + for (uint8 i = 0; i < objectType; i++) + { + result += object_entry_group_counts[i]; + } + result += entryIndex; + return result; + } +}; + +static std::unique_ptr _objectManager; + +IObjectManager * GetObjectManager() +{ + if (_objectManager == nullptr) + { + IObjectRepository * objectRepository = GetObjectRepository(); + _objectManager = std::unique_ptr(new ObjectManager(objectRepository)); + } + return _objectManager.get(); +} + +extern "C" +{ + void * object_manager_get_loaded_object_by_index(size_t index) + { + IObjectManager * objectManager = GetObjectManager(); + Object * loadedObject = objectManager->GetLoadedObject(index); + return (void *)loadedObject; + } + + void * object_manager_get_loaded_object(const rct_object_entry * entry) + { + IObjectManager * objectManager = GetObjectManager(); + Object * loadedObject = objectManager->GetLoadedObject(entry); + return (void *)loadedObject; + } + + uint8 object_manager_get_loaded_object_entry_index(const void * loadedObject) + { + IObjectManager * objectManager = GetObjectManager(); + const Object * object = (const Object *)loadedObject; + uint8 entryIndex = objectManager->GetLoadedObjectEntryIndex(object); + return entryIndex; + } + + void * object_manager_load_object(const rct_object_entry * entry) + { + IObjectManager * objectManager = GetObjectManager(); + Object * loadedObject = objectManager->LoadObject(entry); + return (void *)loadedObject; + } + + void object_manager_unload_objects(const rct_object_entry * entries, size_t count) + { + IObjectManager * objectManager = GetObjectManager(); + objectManager->UnloadObjects(entries, count); + } + + void object_manager_unload_all_objects() + { + IObjectManager * objectManager = GetObjectManager(); + objectManager->UnloadAll(); + } +} diff --git a/src/object/ObjectManager.h b/src/object/ObjectManager.h new file mode 100644 index 0000000000..8dbeae0d86 --- /dev/null +++ b/src/object/ObjectManager.h @@ -0,0 +1,61 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + #include "../object.h" +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus + +class Object; + +interface IObjectManager +{ + virtual ~IObjectManager() { } + + virtual Object * GetLoadedObject(size_t index) abstract; + virtual Object * GetLoadedObject(const rct_object_entry * entry) abstract; + virtual uint8 GetLoadedObjectEntryIndex(const Object * object) abstract; + + virtual Object * LoadObject(const rct_object_entry * entry) abstract; + virtual bool LoadObjects(const rct_object_entry * entries, size_t count) abstract; + virtual void UnloadObjects(const rct_object_entry * entries, size_t count) abstract; + virtual void UnloadAll() abstract; + + virtual void ResetObjects() abstract; +}; + +IObjectManager * GetObjectManager(); + +#else + +void * object_manager_get_loaded_object_by_index(size_t index); +void * object_manager_get_loaded_object(const rct_object_entry * entry); +uint8 object_manager_get_loaded_object_entry_index(const void * loadedObject); +void * object_manager_load_object(const rct_object_entry * entry); +void object_manager_unload_objects(const rct_object_entry * entries, size_t count); +void object_manager_unload_all_objects(); + +#endif diff --git a/src/object/ObjectRepository.cpp b/src/object/ObjectRepository.cpp new file mode 100644 index 0000000000..1aff117211 --- /dev/null +++ b/src/object/ObjectRepository.cpp @@ -0,0 +1,989 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include +#include +#include + +#include "../common.h" +#include "../core/Console.hpp" +#include "../core/FileEnumerator.h" +#include "../core/FileStream.hpp" +#include "../core/Guard.hpp" +#include "../core/IStream.hpp" +#include "../core/Memory.hpp" +#include "../core/MemoryStream.h" +#include "../core/Path.hpp" +#include "../core/Stopwatch.hpp" +#include "../core/String.hpp" +#include "Object.h" +#include "ObjectFactory.h" +#include "ObjectManager.h" +#include "ObjectRepository.h" +#include "RideObject.h" +#include "StexObject.h" + +extern "C" +{ + #include "../config.h" + #include "../localisation/localisation.h" + #include "../object.h" + #include "../object_list.h" + #include "../platform/platform.h" + #include "../scenario.h" + #include "../util/sawyercoding.h" +} + +constexpr uint16 OBJECT_REPOSITORY_VERSION = 9; + +struct ObjectRepositoryHeader +{ + uint16 Version; + uint16 LanguageId; + uint32 TotalFiles; + uint64 TotalFileSize; + uint32 FileDateModifiedChecksum; + uint32 PathChecksum; + uint32 NumItems; +}; + +struct QueryDirectoryResult +{ + uint32 TotalFiles; + uint64 TotalFileSize; + uint32 FileDateModifiedChecksum; + uint32 PathChecksum; +}; + +struct ObjectEntryHash +{ + size_t operator()(const rct_object_entry &entry) const + { + uint32 hash = 5381; + for (int i = 0; i < 8; i++) + { + hash = ((hash << 5) + hash) + entry.name[i]; + } + return hash; + } +}; + +struct ObjectEntryEqual +{ + bool operator()(const rct_object_entry &lhs, const rct_object_entry &rhs) const + { + return memcmp(&lhs.name, &rhs.name, 8) == 0; + } +}; + +using ObjectEntryMap = std::unordered_map; + +static void ReportMissingObject(const rct_object_entry * entry); + +class ObjectRepository : public IObjectRepository +{ + std::vector _items; + QueryDirectoryResult _queryDirectoryResult; + ObjectEntryMap _itemMap; + uint16 _languageId; + +public: + ~ObjectRepository() + { + ClearItems(); + } + + void LoadOrConstruct() override + { + ClearItems(); + + _queryDirectoryResult = { 0 }; + + utf8 path[MAX_PATH]; + GetRCT2ObjectPath(path, sizeof(path)); + QueryDirectory(&_queryDirectoryResult, path); + GetUserObjectPath(path, sizeof(path)); + QueryDirectory(&_queryDirectoryResult, path); + + if (!Load()) + { + _languageId = gCurrentLanguage; + + Construct(); + Save(); + } + + // SortItems(); + } + + const size_t GetNumObjects() const override + { + return _items.size(); + } + + const ObjectRepositoryItem * GetObjects() const override + { + return _items.data(); + } + + const ObjectRepositoryItem * FindObject(const utf8 * name) const override + { + rct_object_entry entry = { 0 }; + utf8 entryName[9] = { ' ' }; + String::Set(entryName, sizeof(entryName), name); + Memory::Copy(entry.name, entryName, 8); + + auto kvp = _itemMap.find(entry); + if (kvp != _itemMap.end()) + { + return &_items[kvp->second]; + } + return nullptr; + } + + const ObjectRepositoryItem * FindObject(const rct_object_entry * objectEntry) const override + { + auto kvp = _itemMap.find(*objectEntry); + if (kvp != _itemMap.end()) + { + return &_items[kvp->second]; + } + return nullptr; + } + + Object * LoadObject(const ObjectRepositoryItem * ori) override + { + Guard::ArgumentNotNull(ori); + + Object * object = ObjectFactory::CreateObjectFromLegacyFile(ori->Path); + return object; + } + + void RegisterLoadedObject(const ObjectRepositoryItem * ori, Object * object) override + { + ObjectRepositoryItem * item = &_items[ori->Id]; + + Guard::Assert(item->LoadedObject == nullptr); + item->LoadedObject = object; + } + + void UnregisterLoadedObject(const ObjectRepositoryItem * ori, Object * object) override + { + ObjectRepositoryItem * item = &_items[ori->Id]; + if (item->LoadedObject == object) + { + item->LoadedObject = nullptr; + } + } + + void AddObject(const rct_object_entry * objectEntry, const void * data, size_t dataSize) override + { + utf8 objectName[9]; + object_entry_get_name_fixed(objectName, sizeof(objectName), objectEntry); + + // Check that the object is loadable before writing it + Object * object = ObjectFactory::CreateObjectFromLegacyData(objectEntry, data, dataSize); + if (object == nullptr) + { + Console::Error::WriteLine("[%s] Unable to export object.", objectName); + } + else + { + utf8 path[MAX_PATH]; + GetPathForNewObject(path, sizeof(path), objectName); + + log_verbose("Adding object: [%s]", objectName); + try + { + SaveObject(path, objectEntry, data, dataSize); + ScanObject(path); + } + catch (Exception ex) + { + Console::Error::WriteLine("Failed saving object: [%s] to '%s'.", objectName, path); + } + } + } + +private: + void ClearItems() + { + for (uint32 i = 0; i < _items.size(); i++) + { + FreeItem(&_items[i]); + } + _items.clear(); + _itemMap.clear(); + } + + void QueryDirectory(QueryDirectoryResult * result, const utf8 * directory) + { + utf8 pattern[MAX_PATH]; + String::Set(pattern, sizeof(pattern), directory); + Path::Append(pattern, sizeof(pattern), "*.dat"); + + auto fileEnumerator = FileEnumerator(pattern, true); + while (fileEnumerator.Next()) + { + const file_info * enumFileInfo = fileEnumerator.GetFileInfo(); + const utf8 * enumPath = fileEnumerator.GetPath(); + + result->TotalFiles++; + result->TotalFileSize += enumFileInfo->size; + result->FileDateModifiedChecksum ^= + (uint32)(enumFileInfo->last_modified >> 32) ^ + (uint32)(enumFileInfo->last_modified & 0xFFFFFFFF); + result->FileDateModifiedChecksum = ror32(result->FileDateModifiedChecksum, 5); + result->PathChecksum += GetPathChecksum(enumPath); + } + } + + void Construct() + { + utf8 objectDirectory[MAX_PATH]; + Path::GetDirectory(objectDirectory, sizeof(objectDirectory), gRCT2AddressObjectDataPath); + + Console::WriteLine("Scanning %lu objects...", _queryDirectoryResult.TotalFiles); + + auto stopwatch = Stopwatch(); + stopwatch.Start(); + + utf8 path[MAX_PATH]; + GetRCT2ObjectPath(path, sizeof(path)); + ScanDirectory(path); + GetUserObjectPath(path, sizeof(path)); + ScanDirectory(path); + + stopwatch.Stop(); + Console::WriteLine("Scanning complete in %.2f seconds.", stopwatch.GetElapsedMilliseconds() / 1000.0f); + } + + void ScanDirectory(const utf8 * directory) + { + utf8 pattern[MAX_PATH]; + String::Set(pattern, sizeof(pattern), directory); + Path::Append(pattern, sizeof(pattern), "*.dat"); + + auto fileEnumerator = FileEnumerator(pattern, true); + while (fileEnumerator.Next()) + { + const utf8 * enumPath = fileEnumerator.GetPath(); + ScanObject(enumPath); + } + } + + void ScanObject(const utf8 * path) + { + Object * object = ObjectFactory::CreateObjectFromLegacyFile(path); + if (object != nullptr) + { + ObjectRepositoryItem item = { 0 }; + item.ObjectEntry = *object->GetObjectEntry(); + item.Path = String::Duplicate(path); + item.Name = String::Duplicate(object->GetName()); + object->SetRepositoryItem(&item); + AddItem(&item); + + delete object; + } + } + + bool Load() + { + utf8 path[MAX_PATH]; + GetRepositoryPath(path, sizeof(path)); + + try + { + auto fs = FileStream(path, FILE_MODE_OPEN); + auto header = fs.ReadValue(); + + if (header.Version == OBJECT_REPOSITORY_VERSION && + header.LanguageId == gCurrentLanguage && + header.TotalFiles == _queryDirectoryResult.TotalFiles && + header.TotalFileSize == _queryDirectoryResult.TotalFileSize && + header.FileDateModifiedChecksum == _queryDirectoryResult.FileDateModifiedChecksum && + header.PathChecksum == _queryDirectoryResult.PathChecksum) + { + // Header matches, so the index is not out of date + + // Buffer the rest of file into memory to speed up item reading + size_t dataSize = (size_t)(fs.GetLength() - fs.GetPosition()); + void * data = fs.ReadArray(dataSize); + auto ms = MemoryStream(data, dataSize, MEMORY_ACCESS_READ | MEMORY_ACCESS_OWNER); + + // Read items + for (uint32 i = 0; i < header.NumItems; i++) + { + ObjectRepositoryItem item = ReadItem(&ms); + AddItem(&item); + } + return true; + } + Console::WriteLine("Object repository is out of date."); + return false; + } + catch (IOException ex) + { + return false; + } + } + + void Save() const + { + utf8 path[MAX_PATH]; + GetRepositoryPath(path, sizeof(path)); + + try + { + auto fs = FileStream(path, FILE_MODE_WRITE); + + // Write header + ObjectRepositoryHeader header; + header.Version = OBJECT_REPOSITORY_VERSION; + header.LanguageId = _languageId; + header.TotalFiles = _queryDirectoryResult.TotalFiles; + header.TotalFileSize = _queryDirectoryResult.TotalFileSize; + header.FileDateModifiedChecksum = _queryDirectoryResult.FileDateModifiedChecksum; + header.PathChecksum = _queryDirectoryResult.PathChecksum; + header.NumItems = _items.size(); + fs.WriteValue(header); + + // Write items + for (uint32 i = 0; i < header.NumItems; i++) + { + WriteItem(&fs, _items[i]); + } + } + catch (IOException ex) + { + log_error("Unable to write object repository index."); + } + } + + void SortItems() + { + std::sort(_items.begin(), _items.end(), [](const ObjectRepositoryItem &a, + const ObjectRepositoryItem &b) -> bool + { + return strcmp(a.Name, b.Name) < 0; + }); + + // Rebuild item map + _itemMap.clear(); + for (size_t i = 0; i < _items.size(); i++) + { + rct_object_entry entry = _items[i].ObjectEntry; + _itemMap[entry] = i; + } + } + + bool AddItem(ObjectRepositoryItem * item) + { + const ObjectRepositoryItem * conflict = FindObject(&item->ObjectEntry); + if (conflict == nullptr) + { + size_t index = _items.size(); + item->Id = index; + _items.push_back(*item); + _itemMap[item->ObjectEntry] = index; + return true; + } + else + { + Console::Error::WriteLine("Object conflict: '%s'", conflict->Path); + Console::Error::WriteLine(" : '%s'", item->Path); + return false; + } + } + + static ObjectRepositoryItem ReadItem(IStream * stream) + { + ObjectRepositoryItem item = { 0 }; + + item.ObjectEntry = stream->ReadValue(); + item.Path = stream->ReadString(); + item.Name = stream->ReadString(); + + switch (item.ObjectEntry.flags & 0x0F) { + case OBJECT_TYPE_RIDE: + item.RideFlags = stream->ReadValue(); + for (int i = 0; i < 2; i++) + { + item.RideCategory[i] = stream->ReadValue(); + } + for (int i = 0; i < 3; i++) + { + item.RideType[i] = stream->ReadValue(); + } + break; + case OBJECT_TYPE_SCENERY_SETS: + item.NumThemeObjects = stream->ReadValue(); + item.ThemeObjects = Memory::AllocateArray(item.NumThemeObjects); + for (uint16 i = 0; i < item.NumThemeObjects; i++) + { + item.ThemeObjects[i] = stream->ReadValue(); + } + break; + } + return item; + } + + static void WriteItem(IStream * stream, const ObjectRepositoryItem &item) + { + stream->WriteValue(item.ObjectEntry); + stream->WriteString(item.Path); + stream->WriteString(item.Name); + + switch (item.ObjectEntry.flags & 0x0F) { + case OBJECT_TYPE_RIDE: + stream->WriteValue(item.RideFlags); + for (int i = 0; i < 2; i++) + { + stream->WriteValue(item.RideCategory[i]); + } + for (int i = 0; i < 3; i++) + { + stream->WriteValue(item.RideType[i]); + } + break; + case OBJECT_TYPE_SCENERY_SETS: + stream->WriteValue(item.NumThemeObjects); + for (uint16 i = 0; i < item.NumThemeObjects; i++) + { + stream->WriteValue(item.ThemeObjects[i]); + } + break; + } + } + + static void FreeItem(ObjectRepositoryItem * item) + { + Memory::Free(item->Path); + Memory::Free(item->Name); + item->Path = nullptr; + item->Name = nullptr; + + uint8 objectType = item->ObjectEntry.flags & 0x0F; + switch (objectType) { + case OBJECT_TYPE_SCENERY_SETS: + Memory::Free(item->ThemeObjects); + item->ThemeObjects = nullptr; + break; + } + } + + static void SaveObject(const utf8 * path, + const rct_object_entry * entry, + const void * data, size_t dataSize, + bool fixChecksum = true) + { + if (fixChecksum) + { + int realChecksum = object_calculate_checksum(entry, data, dataSize); + if (realChecksum != entry->checksum) + { + char objectName[9]; + object_entry_get_name_fixed(objectName, sizeof(objectName), entry); + log_verbose("[%s] Incorrect checksum, adding salt bytes...", objectName); + + // Calculate the value of extra bytes that can be appended to the data so that the + // data is then valid for the object's checksum + size_t extraBytesCount = 0; + void * extraBytes = CalculateExtraBytesToFixChecksum(realChecksum, entry->checksum, &extraBytesCount); + + // Create new data blob with appended bytes + size_t newDataSize = dataSize + extraBytesCount; + void * newData = Memory::Allocate(newDataSize); + void * newDataSaltOffset = (void *)((uintptr_t)newData + dataSize); + Memory::Copy(newData, data, dataSize); + Memory::Copy(newDataSaltOffset, extraBytes, extraBytesCount); + + try + { + int newRealChecksum = object_calculate_checksum(entry, newData, newDataSize); + if (newRealChecksum != entry->checksum) + { + Guard::Fail("CalculateExtraBytesToFixChecksum failed to fix checksum."); + + // Save old data form + SaveObject(path, entry, data, dataSize, false); + } + else + { + // Save new data form + SaveObject(path, entry, newData, newDataSize, false); + } + Memory::Free(newData); + Memory::Free(extraBytes); + } + catch (Exception ex) + { + Memory::Free(newData); + Memory::Free(extraBytes); + throw; + } + return; + } + } + + // Encode data + uint8 objectType = entry->flags & 0x0F; + sawyercoding_chunk_header chunkHeader; + chunkHeader.encoding = object_entry_group_encoding[objectType]; + chunkHeader.length = dataSize; + uint8 * encodedDataBuffer = Memory::Allocate(0x600000); + size_t encodedDataSize = sawyercoding_write_chunk_buffer(encodedDataBuffer, (uint8 *)data, chunkHeader); + + // Save to file + try + { + auto fs = FileStream(path, FILE_MODE_WRITE); + fs.Write(entry, sizeof(rct_object_entry)); + fs.Write(encodedDataBuffer, encodedDataSize); + + Memory::Free(encodedDataBuffer); + } + catch (Exception ex) + { + Memory::Free(encodedDataBuffer); + throw; + } + } + + static void * CalculateExtraBytesToFixChecksum(int currentChecksum, int targetChecksum, size_t * outSize) + { + // Allocate 11 extra bytes to manipulate the checksum + uint8 * salt = Memory::Allocate(11); + if (outSize != nullptr) *outSize = 11; + + // Next work out which bits need to be flipped to make the current checksum match the one in the file + // The bitwise rotation compensates for the rotation performed during the checksum calculation*/ + int bitsToFlip = targetChecksum ^ ((currentChecksum << 25) | (currentChecksum >> 7)); + + // Each set bit encountered during encoding flips one bit of the resulting checksum (so each bit of the checksum is an + // XOR of bits from the file). Here, we take each bit that should be flipped in the checksum and set one of the bits in + // the data that maps to it. 11 bytes is the minimum needed to touch every bit of the checksum - with less than that, + // you wouldn't always be able to make the checksum come out to the desired target + salt[0] = (bitsToFlip & 0x00000001) << 7; + salt[1] = ((bitsToFlip & 0x00200000) >> 14); + salt[2] = ((bitsToFlip & 0x000007F8) >> 3); + salt[3] = ((bitsToFlip & 0xFF000000) >> 24); + salt[4] = ((bitsToFlip & 0x00100000) >> 13); + salt[5] = (bitsToFlip & 0x00000004) >> 2; + salt[6] = 0; + salt[7] = ((bitsToFlip & 0x000FF000) >> 12); + salt[8] = (bitsToFlip & 0x00000002) >> 1; + salt[9] = (bitsToFlip & 0x00C00000) >> 22; + salt[10] = (bitsToFlip & 0x00000800) >> 11; + + return salt; + } + + static void GetPathForNewObject(utf8 * buffer, size_t bufferSize, const char * name) + { + char normalisedName[9] = { 0 }; + for (int i = 0; i < 8; i++) + { + if (name[i] != ' ') + { + normalisedName[i] = toupper(name[i]); + } + else + { + normalisedName[i] = '\0'; + } + } + + GetUserObjectPath(buffer, bufferSize); + platform_ensure_directory_exists(buffer); + + Path::Append(buffer, bufferSize, normalisedName); + String::Append(buffer, bufferSize, ".DAT"); + + uint32 counter = 2; + for (; platform_file_exists(buffer);) + { + utf8 counterString[8]; + snprintf(counterString, sizeof(counterString), "-%02X", counter); + counter++; + + GetUserObjectPath(buffer, bufferSize); + Path::Append(buffer, bufferSize, normalisedName); + String::Append(buffer, bufferSize, counterString); + String::Append(buffer, bufferSize, ".DAT"); + } + } + + static void GetRepositoryPath(utf8 * buffer, size_t bufferSize) + { + platform_get_user_directory(buffer, nullptr); + strcat(buffer, "objects.idx"); + } + + static void GetRCT2ObjectPath(utf8 * buffer, size_t bufferSize) + { + Path::GetDirectory(buffer, bufferSize, gRCT2AddressObjectDataPath); + } + + static void GetUserObjectPath(utf8 * buffer, size_t bufferSize) + { + platform_get_user_directory(buffer, "object"); + } + + static uint32 GetPathChecksum(const utf8 * path) + { + uint32 hash = 0xD8430DED; + for (const utf8 * ch = path; *ch != '\0'; ch++) + { + hash += (*ch); + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } +}; + +static std::unique_ptr _objectRepository; + +IObjectRepository * GetObjectRepository() +{ + if (_objectRepository == nullptr) + { + _objectRepository = std::unique_ptr(new ObjectRepository()); + } + return _objectRepository.get(); +} + +static int GetObjectEntryIndex(uint8 objectType, uint8 entryIndex) +{ + int result = 0; + for (uint8 i = 0; i < objectType; i++) + { + result += object_entry_group_counts[i]; + } + result += entryIndex; + return result; +} + +extern "C" +{ + rct_object_entry * object_list_find(rct_object_entry * entry) + { + IObjectRepository * objRepo = GetObjectRepository(); + const ObjectRepositoryItem * item = objRepo->FindObject(entry); + return (rct_object_entry *)&item->ObjectEntry; + } + + rct_object_entry * object_list_find_by_name(const char * name) + { + IObjectRepository * objRepo = GetObjectRepository(); + const ObjectRepositoryItem * item = objRepo->FindObject(name); + return (rct_object_entry *)&item->ObjectEntry; + } + + void object_list_load() + { + IObjectRepository * objectRepository = GetObjectRepository(); + objectRepository->LoadOrConstruct(); + + IObjectManager * objectManager = GetObjectManager(); + objectManager->UnloadAll(); + } + + bool object_load_entries(rct_object_entry * entries) + { + log_verbose("loading required objects"); + + IObjectManager * objectManger = GetObjectManager(); + bool result = objectManger->LoadObjects(entries, OBJECT_ENTRY_COUNT); + + log_verbose("finished loading required objects"); + return result; + } + + void * object_repository_load_object(const rct_object_entry * objectEntry) + { + Object * object = nullptr; + IObjectRepository * objRepository = GetObjectRepository(); + const ObjectRepositoryItem * ori = objRepository->FindObject(objectEntry); + if (ori != nullptr) + { + object = objRepository->LoadObject(ori); + if (object != nullptr) + { + object->Load(); + } + } + return (void *)object; + } + + void scenario_translate(scenario_index_entry * scenarioEntry, const rct_object_entry * stexObjectEntry) + { + rct_string_id localisedStringIds[3]; + if (language_get_localised_scenario_strings(scenarioEntry->name, localisedStringIds)) + { + if (localisedStringIds[0] != STR_NONE) + { + String::Set(scenarioEntry->name, sizeof(scenarioEntry->name), language_get_string(localisedStringIds[0])); + } + if (localisedStringIds[2] != STR_NONE) + { + String::Set(scenarioEntry->details, sizeof(scenarioEntry->details), language_get_string(localisedStringIds[2])); + } + } + else + { + // Checks for a scenario string object (possibly for localisation) + if ((stexObjectEntry->flags & 0xFF) != 255) + { + IObjectRepository * objectRepository = GetObjectRepository(); + const ObjectRepositoryItem * ori = objectRepository->FindObject(stexObjectEntry); + if (ori != nullptr) + { + Object * object = objectRepository->LoadObject(ori); + if (object != nullptr) + { + StexObject * stexObject = static_cast(object); + const utf8 * scenarioName = stexObject->GetScenarioName(); + const utf8 * scenarioDetails = stexObject->GetScenarioDetails(); + + String::Set(scenarioEntry->name, sizeof(scenarioEntry->name), scenarioName); + String::Set(scenarioEntry->details, sizeof(scenarioEntry->details), scenarioDetails); + + delete object; + } + } + } + } + } + + int object_load_packed(SDL_RWops * rw) + { + IObjectRepository * objRepo = GetObjectRepository(); + + rct_object_entry entry; + SDL_RWread(rw, &entry, 16, 1); + + // Check if we already have this object + if (objRepo->FindObject(&entry) != nullptr) + { + sawyercoding_skip_chunk(rw); + } + else + { + // Read object and save to new file + uint8 * chunk = Memory::Allocate(0x600000); + if (chunk == nullptr) + { + log_error("Failed to allocate buffer for packed object."); + return 0; + } + + uint32 chunkSize = sawyercoding_read_chunk(rw, chunk); + chunk = Memory::Reallocate(chunk, chunkSize); + if (chunk == nullptr) + { + log_error("Failed to reallocate buffer for packed object."); + return 0; + } + + objRepo->AddObject(&entry, chunk, chunkSize); + + Memory::Free(chunk); + } + return 1; + } + + bool object_saved_packed(SDL_RWops * rw, const rct_object_entry * entry) + { + IObjectRepository * objectRepository = GetObjectRepository(); + const ObjectRepositoryItem * item = objectRepository->FindObject(entry); + if (item == nullptr) + { + return false; + } + + auto fs = FileStream(item->Path, FILE_MODE_OPEN); + rct_object_entry fileEntry = fs.ReadValue(); + if (!object_entry_compare(entry, &fileEntry)) + { + return false; + } + + sawyercoding_chunk_header chunkHeader = fs.ReadValue(); + uint8 * chunkData = fs.ReadArray(chunkHeader.length); + + if (SDL_RWwrite(rw, entry, sizeof(rct_object_entry), 1) != 1) + { + Memory::Free(chunkData); + return false; + } + if (SDL_RWwrite(rw, &chunkHeader, sizeof(sawyercoding_chunk_header), 1) != 1) + { + Memory::Free(chunkData); + return false; + } + if (SDL_RWwrite(rw, chunkData, chunkHeader.length, 1) != 1) + { + Memory::Free(chunkData); + return false; + } + + return true; + } + + size_t object_repository_get_items_count() + { + IObjectRepository * objectRepository = GetObjectRepository(); + return objectRepository->GetNumObjects(); + } + + const ObjectRepositoryItem * object_repository_get_items() + { + IObjectRepository * objectRepository = GetObjectRepository(); + return objectRepository->GetObjects(); + } + + const ObjectRepositoryItem * object_repository_find_object_by_entry(const rct_object_entry * entry) + { + IObjectRepository * objectRepository = GetObjectRepository(); + return objectRepository->FindObject(entry); + } + + const ObjectRepositoryItem * object_repository_find_object_by_name(const char * name) + { + IObjectRepository * objectRepository = GetObjectRepository(); + return objectRepository->FindObject(name); + } + + void object_delete(void * object) + { + if (object != nullptr) + { + Object * baseObject = (Object *)object; + baseObject->Unload(); + delete baseObject; + } + } + + const utf8 * object_get_description(const void * object) + { + const Object * baseObject = (const Object *)object; + switch (baseObject->GetObjectType()) { + case OBJECT_TYPE_RIDE: + { + const RideObject * rideObject = static_cast(baseObject); + return rideObject->GetDescription(); + } + case OBJECT_TYPE_SCENARIO_TEXT: + { + const StexObject * stexObject = static_cast(baseObject); + return stexObject->GetScenarioDetails(); + } + default: + return ""; + } + } + + const utf8 * object_get_capacity(const void * object) + { + const Object * baseObject = (const Object *)object; + switch (baseObject->GetObjectType()) { + case OBJECT_TYPE_RIDE: + { + auto rideObject = static_cast(baseObject); + return rideObject->GetCapacity(); + } + default: + return ""; + } + } + + void object_draw_preview(const void * object, rct_drawpixelinfo * dpi, sint32 width, sint32 height) + { + const Object * baseObject = (const Object *)object; + baseObject->DrawPreview(dpi, width, height); + } + + bool object_entry_compare(const rct_object_entry * a, const rct_object_entry * b) + { + // If an official object don't bother checking checksum + if ((a->flags & 0xF0) || (b->flags & 0xF0)) + { + if ((a->flags & 0x0F) != (b->flags & 0x0F)) + { + return 0; + } + int match = memcmp(a->name, b->name, 8); + if (match) + { + return 0; + } + } + else + { + if (a->flags != b->flags) + { + return 0; + } + int match = memcmp(a->name, b->name, 8); + if (match) + { + return 0; + } + if (a->checksum != b->checksum) + { + return 0; + } + } + return 1; + } + + int object_calculate_checksum(const rct_object_entry * entry, const void * data, size_t dataLength) + { + const uint8 *entryBytePtr = (uint8*)entry; + + uint32 checksum = 0xF369A75B; + checksum ^= entryBytePtr[0]; + checksum = rol32(checksum, 11); + for (int i = 4; i < 12; i++) + { + checksum ^= entryBytePtr[i]; + checksum = rol32(checksum, 11); + } + + uint8 * dataBytes = (uint8 *)data; + for (size_t i = 0; i < dataLength; i++) + { + checksum ^= dataBytes[i]; + checksum = rol32(checksum, 11); + } + + return (int)checksum; + } +} + +static void ReportMissingObject(const rct_object_entry * entry) +{ + utf8 objName[9] = { 0 }; + Memory::Copy(objName, entry->name, 8); + Console::Error::WriteLine("[%s] Object not found.", objName); +} diff --git a/src/object/ObjectRepository.h b/src/object/ObjectRepository.h new file mode 100644 index 0000000000..8f8eaf216e --- /dev/null +++ b/src/object/ObjectRepository.h @@ -0,0 +1,100 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "../common.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + #include "../object.h" +#ifdef __cplusplus +} +#endif + +#ifdef __cplusplus + class Object; +#else + typedef struct Object Object; +#endif + +typedef struct ObjectRepositoryItem +{ + size_t Id; + rct_object_entry ObjectEntry; + utf8 * Path; + utf8 * Name; + Object * LoadedObject; + union + { + struct + { + uint8 RideFlags; + uint8 RideCategory[2]; + uint8 RideType[3]; + }; + struct + { + uint16 NumThemeObjects; + rct_object_entry * ThemeObjects; + }; + }; +} ObjectRepositoryItem; + +#ifdef __cplusplus + +interface IObjectRepository +{ + virtual ~IObjectRepository() { } + + virtual void LoadOrConstruct() abstract; + virtual const size_t GetNumObjects() const abstract; + virtual const ObjectRepositoryItem * GetObjects() const abstract; + virtual const ObjectRepositoryItem * FindObject(const utf8 * name) const abstract; + virtual const ObjectRepositoryItem * FindObject(const rct_object_entry * objectEntry) const abstract; + + virtual Object * LoadObject(const ObjectRepositoryItem * ori) abstract; + virtual void RegisterLoadedObject(const ObjectRepositoryItem * ori, Object * object) abstract; + virtual void UnregisterLoadedObject(const ObjectRepositoryItem * ori, Object * object) abstract; + + virtual void AddObject(const rct_object_entry * objectEntry, + const void * data, + size_t dataSize) abstract; +}; + +IObjectRepository * GetObjectRepository(); + +#else + +size_t object_repository_get_items_count(); +const ObjectRepositoryItem * object_repository_get_items(); +const ObjectRepositoryItem * object_repository_find_object_by_entry(const rct_object_entry * entry); +const ObjectRepositoryItem * object_repository_find_object_by_name(const char * name); +void * object_repository_load_object(const rct_object_entry * objectEntry); + +void object_delete(void * object); +const utf8 * object_get_description(const void * object); +const utf8 * object_get_capacity(const void * object); +void object_draw_preview(const void * object, rct_drawpixelinfo * dpi, sint32 width, sint32 height); + +#endif + +enum ORI_RIDE_FLAG +{ + ORI_RIDE_FLAG_SEPARATE = 1 << 0, +}; diff --git a/src/object/RideObject.cpp b/src/object/RideObject.cpp new file mode 100644 index 0000000000..ca6d6d9756 --- /dev/null +++ b/src/object/RideObject.cpp @@ -0,0 +1,442 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "../core/Memory.hpp" +#include "../core/Util.hpp" +#include "ObjectRepository.h" +#include "RideObject.h" + +extern "C" +{ + #include "../config.h" + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" + #include "../rct1.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, + OBJ_STRING_ID_DESCRIPTION, + OBJ_STRING_ID_CAPACITY, +}; + +RideObject::~RideObject() +{ + for (int i = 0; i < 4; i++) + { + Memory::Free(_peepLoadingPositions[i]); + } +} + +void RideObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(8, STREAM_SEEK_CURRENT); + _legacyType.flags = stream->ReadValue(); + for (int i = 0; i < 3; i++) + { + _legacyType.ride_type[i] = stream->ReadValue(); + } + _legacyType.min_cars_in_train = stream->ReadValue(); + _legacyType.max_cars_in_train = stream->ReadValue(); + _legacyType.cars_per_flat_ride = stream->ReadValue(); + _legacyType.zero_cars = stream->ReadValue(); + _legacyType.tab_vehicle = stream->ReadValue(); + _legacyType.default_vehicle = stream->ReadValue(); + _legacyType.front_vehicle = stream->ReadValue(); + _legacyType.second_vehicle = stream->ReadValue(); + _legacyType.rear_vehicle = stream->ReadValue(); + _legacyType.third_vehicle = stream->ReadValue(); + _legacyType.pad_019 = stream->ReadValue(); + for (int i = 0; i < 4; i++) + { + rct_ride_entry_vehicle * entry = &_legacyType.vehicles[i]; + ReadLegacyVehicle(context, stream, entry); + } + stream->Seek(4, STREAM_SEEK_CURRENT); + _legacyType.excitement_multipler = stream->ReadValue(); + _legacyType.intensity_multipler = stream->ReadValue(); + _legacyType.nausea_multipler = stream->ReadValue(); + _legacyType.max_height = stream->ReadValue(); + _legacyType.enabledTrackPieces = stream->ReadValue(); + _legacyType.category[0] = stream->ReadValue(); + _legacyType.category[1] = stream->ReadValue(); + _legacyType.shop_item = stream->ReadValue(); + _legacyType.shop_item_secondary = stream->ReadValue(); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetStringTable()->Read(context, stream, OBJ_STRING_ID_DESCRIPTION); + + // TODO: Move to its own function when ride construction window is merged. + if (gConfigInterface.select_by_track_type) { + _legacyType.enabledTrackPieces = 0xFFFFFFFFFFFFFFFF; + } + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_CAPACITY); + + // Read preset colours, by default there are 32 + _presetColours.count = stream->ReadValue(); + if (_presetColours.count == 255) + { + _presetColours.count = 32; + } + for (uint8 i = 0; i < _presetColours.count; i++) + { + _presetColours.list[i] = stream->ReadValue(); + } + + // Read peep loading positions + for (int i = 0; i < 4; i++) + { + uint16 numPeepLoadingPositions = stream->ReadValue(); + if (numPeepLoadingPositions == 255) + { + numPeepLoadingPositions = stream->ReadValue(); + } + _peepLoadingPositions[i] = stream->ReadArray(numPeepLoadingPositions); + } + + GetImageTable()->Read(context, stream); + + // Validate properties + if (_legacyType.excitement_multipler > 75) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Excitement multiplier too high."); + } + if (_legacyType.intensity_multipler > 75) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Intensity multiplier too high."); + } + if (_legacyType.nausea_multipler > 75) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Nausea multiplier too high."); + } +} + +void RideObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.name = language_allocate_object_string(GetName()); + _legacyType.description = language_allocate_object_string(GetDescription()); + _legacyType.images_offset = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.vehicle_preset_list = &_presetColours; + + int cur_vehicle_images_offset = _legacyType.images_offset + 3; + for (int i = 0; i < 4; i++) + { + rct_ride_entry_vehicle * vehicleEntry = &_legacyType.vehicles[i]; + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT) + { + int al = 1; + if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_SWINGING) + { + al = 13; + if ((vehicleEntry->flags_b & (VEHICLE_ENTRY_FLAG_B_5 | VEHICLE_ENTRY_FLAG_B_11)) != (VEHICLE_ENTRY_FLAG_B_5 | VEHICLE_ENTRY_FLAG_B_11)) + { + al = 7; + if (!(vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_5)) + { + if (!(vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_11)) + { + al = 5; + if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_9) + { + al = 3; + } + } + } + } + } + vehicleEntry->var_03 = al; + // 0x6DE90B + al = 0x20; + if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_14)) + { + al = 1; + if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_7) + { + if (vehicleEntry->var_11 != 6) + { + al = 2; + if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_7)) + { + al = 4; + } + } + } + } + if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_12) + { + al = vehicleEntry->special_frames; + } + vehicleEntry->var_02 = al; + // 0x6DE946 + + vehicleEntry->var_16 = vehicleEntry->var_02 * vehicleEntry->var_03; + vehicleEntry->base_image_id = cur_vehicle_images_offset; + int image_index = vehicleEntry->base_image_id; + + if (vehicleEntry->car_visual != VEHICLE_VISUAL_RIVER_RAPIDS) + { + int b = vehicleEntry->var_16 * 32; + + if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_11) b /= 2; + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_15) b /= 8; + + image_index += b; + + // Incline 25 + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPES) + { + vehicleEntry->var_20 = image_index; + b = vehicleEntry->var_16 * 72; + if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_14) + { + b = vehicleEntry->var_16 * 16; + } + image_index += b; + } + + // Incline 60 + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_STEEP_SLOPES) + { + vehicleEntry->var_24 = image_index; + b = vehicleEntry->var_16 * 80; + image_index += b; + } + + // Verticle + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES) + { + vehicleEntry->var_28 = image_index; + b = vehicleEntry->var_16 * 116; + image_index += b; + } + + // Unknown + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES) + { + vehicleEntry->var_2C = image_index; + b = vehicleEntry->var_16 * 24; + image_index += b; + } + + // Bank + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_BANKED) + { + vehicleEntry->var_30 = image_index; + b = vehicleEntry->var_16 * 80; + image_index += b; + } + + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_INLINE_TWISTS) + { + vehicleEntry->var_34 = image_index; + b = vehicleEntry->var_16 * 40; + image_index += b; + } + + // Track half? Up/Down + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS) + { + vehicleEntry->var_38 = image_index; + b = vehicleEntry->var_16 * 128; + image_index += b; + } + + // Unknown + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS) + { + vehicleEntry->var_3C = image_index; + b = vehicleEntry->var_16 * 16; + image_index += b; + } + + // Unknown + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS) + { + vehicleEntry->var_40 = image_index; + b = vehicleEntry->var_16 * 16; + image_index += b; + } + + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS) + { + vehicleEntry->var_44 = image_index; + b = vehicleEntry->var_16 * 128; + image_index += b; + } + + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS) + { + vehicleEntry->var_48 = image_index; + b = vehicleEntry->var_16 * 16; + image_index += b; + } + + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_CORKSCREWS) + { + vehicleEntry->var_4C = image_index; + b = vehicleEntry->var_16 * 80; + image_index += b; + } + + // Unknown + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION) + { + vehicleEntry->var_1C = image_index; + b = vehicleEntry->var_16 * 12; + image_index += b; + } + + if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_14) + { + // Same offset as above??? + vehicleEntry->var_4C = image_index; + b = vehicleEntry->var_16 * 32; + image_index += b; + } + } + else + { + image_index += vehicleEntry->var_16 * 36; + } + + // No vehicle images + vehicleEntry->no_vehicle_images = image_index - cur_vehicle_images_offset; + + // Move the offset over this vehicles images. Including peeps + cur_vehicle_images_offset = image_index + vehicleEntry->no_seating_rows * vehicleEntry->no_vehicle_images; + // 0x6DEB0D + + if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_10)) + { + int num_images = cur_vehicle_images_offset - vehicleEntry->base_image_id; + if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_13) + { + num_images *= 2; + } + + set_vehicle_type_image_max_sizes(vehicleEntry, num_images); + } + vehicleEntry->peep_loading_positions = _peepLoadingPositions[i]; + } + } +} + +void RideObject::Unload() +{ + language_free_object_string(_legacyType.name); + language_free_object_string(_legacyType.description); + gfx_object_free_images(_legacyType.images_offset, GetImageTable()->GetCount()); + + _legacyType.name = 0; + _legacyType.description = 0; + _legacyType.images_offset = 0; +} + +void RideObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + uint32 imageId = _legacyType.images_offset; + if (_legacyType.ride_type[0] == 0xFF) + { + imageId++; + if (_legacyType.ride_type[1] == 0xFF) + { + imageId++; + } + } + gfx_draw_sprite(dpi, imageId, 0, 0, 0); +} + +const utf8 * RideObject::GetDescription() const +{ + return GetString(OBJ_STRING_ID_DESCRIPTION); +} + +const utf8 * RideObject::GetCapacity() const +{ + return GetString(OBJ_STRING_ID_CAPACITY); +} + +void RideObject::SetRepositoryItem(ObjectRepositoryItem * item) const +{ + for (int i = 0; i < 3; i++) + { + item->RideType[i] = _legacyType.ride_type[i]; + } + for (int i = 0; i < 2; i++) + { + item->RideCategory[i] = _legacyType.category[i]; + } + + uint8 flags = 0; + if ((_legacyType.flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE_NAME) && + !rideTypeShouldLoseSeparateFlag(&_legacyType)) + { + flags |= ORI_RIDE_FLAG_SEPARATE; + } + item->RideFlags = flags; +} + +void RideObject::ReadLegacyVehicle(IReadObjectContext * context, IStream * stream, rct_ride_entry_vehicle * vehicle) +{ + vehicle->rotation_frame_mask = stream->ReadValue(); + vehicle->var_02 = stream->ReadValue(); + vehicle->var_03 = stream->ReadValue(); + vehicle->spacing = stream->ReadValue(); + vehicle->car_friction = stream->ReadValue(); + vehicle->tab_height = stream->ReadValue(); + vehicle->num_seats = stream->ReadValue(); + vehicle->sprite_flags = stream->ReadValue(); + vehicle->sprite_width = stream->ReadValue(); + vehicle->sprite_height_negative = stream->ReadValue(); + vehicle->sprite_height_positive = stream->ReadValue(); + vehicle->var_11 = stream->ReadValue(); + vehicle->flags_a = stream->ReadValue(); + vehicle->flags_b = stream->ReadValue(); + vehicle->var_16 = stream->ReadValue(); + stream->Seek(4, STREAM_SEEK_CURRENT); + vehicle->var_1C = stream->ReadValue(); + vehicle->var_20 = stream->ReadValue(); + vehicle->var_24 = stream->ReadValue(); + vehicle->var_28 = stream->ReadValue(); + vehicle->var_2C = stream->ReadValue(); + vehicle->var_30 = stream->ReadValue(); + vehicle->var_34 = stream->ReadValue(); + vehicle->var_38 = stream->ReadValue(); + vehicle->var_3C = stream->ReadValue(); + vehicle->var_40 = stream->ReadValue(); + vehicle->var_44 = stream->ReadValue(); + vehicle->var_48 = stream->ReadValue(); + vehicle->var_4C = stream->ReadValue(); + vehicle->no_vehicle_images = stream->ReadValue(); + vehicle->no_seating_rows = stream->ReadValue(); + vehicle->spinning_inertia = stream->ReadValue(); + vehicle->spinning_friction = stream->ReadValue(); + vehicle->friction_sound_id = stream->ReadValue(); + vehicle->var_58 = stream->ReadValue(); + vehicle->sound_range = stream->ReadValue(); + vehicle->var_5A = stream->ReadValue(); + vehicle->powered_acceleration = stream->ReadValue(); + vehicle->powered_max_speed = stream->ReadValue(); + vehicle->car_visual = stream->ReadValue(); + vehicle->effect_visual = stream->ReadValue(); + vehicle->draw_order = stream->ReadValue(); + vehicle->special_frames = stream->ReadValue(); + stream->Seek(4, STREAM_SEEK_CURRENT); +} diff --git a/src/object/RideObject.h b/src/object/RideObject.h new file mode 100644 index 0000000000..0a152ea301 --- /dev/null +++ b/src/object/RideObject.h @@ -0,0 +1,52 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Object.h" + +extern "C" +{ + #include "../ride/ride.h" +} + +class RideObject : public Object +{ +private: + rct_ride_entry _legacyType = { 0 }; + vehicle_colour_preset_list _presetColours = { 0 }; + sint8 * _peepLoadingPositions[4] = { nullptr }; + +public: + explicit RideObject(const rct_object_entry &entry) : Object(entry) { }; + ~RideObject(); + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; + + const utf8 * GetDescription() const; + const utf8 * GetCapacity() const; + + void SetRepositoryItem(ObjectRepositoryItem * item) const override; + +private: + void ReadLegacyVehicle(IReadObjectContext * context, IStream * stream, rct_ride_entry_vehicle * vehicle); +}; diff --git a/src/object/SceneryGroupObject.cpp b/src/object/SceneryGroupObject.cpp new file mode 100644 index 0000000000..a0120220dc --- /dev/null +++ b/src/object/SceneryGroupObject.cpp @@ -0,0 +1,139 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "../core/Memory.hpp" +#include "ObjectManager.h" +#include "ObjectRepository.h" +#include "SceneryGroupObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +SceneryGroupObject::~SceneryGroupObject() +{ + Memory::Free(_items); +} + +void SceneryGroupObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + stream->Seek(0x80 * 2, STREAM_SEEK_CURRENT); + _legacyType.entry_count = stream->ReadValue(); + _legacyType.var_107 = stream->ReadValue(); + _legacyType.var_108 = stream->ReadValue(); + _legacyType.pad_109 = stream->ReadValue(); + _legacyType.var_10A = stream->ReadValue(); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + ReadItems(stream); + GetImageTable()->Read(context, stream); + + _legacyType.var_107 = _numItems; +} + +void SceneryGroupObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.name = language_allocate_object_string(GetName()); + _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.entry_count = 0; +} + +void SceneryGroupObject::Unload() +{ + language_free_object_string(_legacyType.name); + gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + + _legacyType.name = 0; + _legacyType.image = 0; +} + +void SceneryGroupObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + sint32 x = width / 2; + sint32 y = height / 2; + + uint32 imageId = _legacyType.image + 0x20600001; + gfx_draw_sprite(dpi, imageId, x - 15, y - 14, 0); +} + +void SceneryGroupObject::UpdateEntryIndexes() +{ + IObjectRepository * objectRepository = GetObjectRepository(); + IObjectManager * objectManager = GetObjectManager(); + + _legacyType.entry_count = 0; + for (uint32 i = 0; i < _numItems; i++) + { + const rct_object_entry * objectEntry = &_items[i]; + + const ObjectRepositoryItem * ori = objectRepository->FindObject(objectEntry); + if (ori == nullptr) continue; + if (ori->LoadedObject == nullptr) continue; + + uint16 sceneryEntry = objectManager->GetLoadedObjectEntryIndex(ori->LoadedObject); + Guard::Assert(sceneryEntry != UINT8_MAX); + + uint8 objectType = objectEntry->flags & 0x0F; + switch (objectType) { + case OBJECT_TYPE_SMALL_SCENERY: break; + case OBJECT_TYPE_LARGE_SCENERY: sceneryEntry |= 0x300; break; + case OBJECT_TYPE_WALLS: sceneryEntry |= 0x200; break; + case OBJECT_TYPE_PATH_BITS: sceneryEntry |= 0x100; break; + default: sceneryEntry |= 0x400; break; + } + + _legacyType.scenery_entries[_legacyType.entry_count] = sceneryEntry; + _legacyType.entry_count++; + } +} + +void SceneryGroupObject::SetRepositoryItem(ObjectRepositoryItem * item) const +{ + Memory::Free(item->ThemeObjects); + + item->NumThemeObjects = _numItems; + item->ThemeObjects = Memory::AllocateArray(_numItems); + for (uint32 i = 0; i < _numItems; i++) + { + item->ThemeObjects[i] = _items[i]; + } +} + +void SceneryGroupObject::ReadItems(IStream * stream) +{ + auto items = std::vector(); + + uint8 endMarker; + while ((endMarker = stream->ReadValue()) != 0xFF) + { + stream->Seek(-1, STREAM_SEEK_CURRENT); + rct_object_entry entry = stream->ReadValue(); + items.push_back(entry); + } + + _numItems = items.size(); + _items = Memory::DuplicateArray(items.data(), items.size()); +} diff --git a/src/object/SceneryGroupObject.h b/src/object/SceneryGroupObject.h new file mode 100644 index 0000000000..03b0697a8a --- /dev/null +++ b/src/object/SceneryGroupObject.h @@ -0,0 +1,52 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Object.h" + +extern "C" +{ + #include "../world/scenery.h" +} + +struct ObjectRepositoryItem; + +class SceneryGroupObject : public Object +{ +private: + rct_scenery_set_entry _legacyType = { 0 }; + uint32 _numItems = 0; + rct_object_entry * _items = nullptr; + +public: + explicit SceneryGroupObject(const rct_object_entry &entry) : Object(entry) { }; + ~SceneryGroupObject(); + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + void UpdateEntryIndexes(); + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; + + void SetRepositoryItem(ObjectRepositoryItem * item) const override; + +private: + void ReadItems(IStream * stream); +}; diff --git a/src/object/SceneryObject.h b/src/object/SceneryObject.h new file mode 100644 index 0000000000..612079f221 --- /dev/null +++ b/src/object/SceneryObject.h @@ -0,0 +1,34 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Object.h" + +class SceneryObject : public Object +{ +private: + rct_object_entry _primarySceneryGroupEntry = { 0 }; + +public: + explicit SceneryObject(const rct_object_entry &entry) : Object(entry) { } + virtual ~SceneryObject() { } + + const rct_object_entry * GetPrimarySceneryGroup() { return &_primarySceneryGroupEntry; } + +protected: + void SetPrimarySceneryGroup(const rct_object_entry * entry) { _primarySceneryGroupEntry = *entry; } +}; diff --git a/src/object/SmallSceneryObject.cpp b/src/object/SmallSceneryObject.cpp new file mode 100644 index 0000000000..f89b8d23a4 --- /dev/null +++ b/src/object/SmallSceneryObject.cpp @@ -0,0 +1,160 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "../core/Math.hpp" +#include "../core/Memory.hpp" +#include "SmallSceneryObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +SmallSceneryObject::~SmallSceneryObject() +{ + Memory::Free(_var10data); +} + +void SmallSceneryObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.small_scenery.flags = stream->ReadValue(); + _legacyType.small_scenery.height = stream->ReadValue(); + _legacyType.small_scenery.tool_id = stream->ReadValue(); + _legacyType.small_scenery.price = stream->ReadValue(); + _legacyType.small_scenery.removal_price = stream->ReadValue(); + stream->Seek(4, STREAM_SEEK_CURRENT); + _legacyType.small_scenery.var_14 = stream->ReadValue(); + _legacyType.small_scenery.var_16 = stream->ReadValue(); + _legacyType.small_scenery.var_18 = stream->ReadValue(); + _legacyType.small_scenery.scenery_tab_id = 0xFF; + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + + rct_object_entry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(&sgEntry); + + if (_legacyType.small_scenery.flags & SMALL_SCENERY_FLAG16) + { + _var10data = ReadVar10(stream); + } + + GetImageTable()->Read(context, stream); + + // Validate properties + if (_legacyType.small_scenery.price <= 0) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Price can not be free or negative."); + } + if (_legacyType.small_scenery.removal_price <= 0) + { + // Make sure you don't make a profit when placing then removing. + money16 reimbursement = _legacyType.small_scenery.removal_price; + if (reimbursement > _legacyType.small_scenery.price) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Sell price can not be more than buy price."); + } + } +} + +void SmallSceneryObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.name = language_allocate_object_string(GetName()); + _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + + _legacyType.small_scenery.scenery_tab_id = 0xFF; + + if (_legacyType.small_scenery.flags & SMALL_SCENERY_FLAG16) + { + _legacyType.small_scenery.var_10 = _var10data; + } +} + +void SmallSceneryObject::Unload() +{ + language_free_object_string(_legacyType.name); + gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + + _legacyType.name = 0; + _legacyType.image = 0; +} + +void SmallSceneryObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + uint32 flags = _legacyType.small_scenery.flags; + uint32 imageId = _legacyType.image; + if (flags & SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR) + { + imageId |= 0x20D00000; + if (flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) + { + imageId |= 0x92000000; + } + } + + sint32 x = width / 2; + sint32 y = (height / 2) + (_legacyType.small_scenery.height / 2); + y = Math::Min(y, height - 16); + + if ((flags & SMALL_SCENERY_FLAG_FULL_TILE) && + (flags & SMALL_SCENERY_FLAG_VOFFSET_CENTRE)) + { + y -= 12; + } + + gfx_draw_sprite(dpi, imageId, x, y, 0); + + if (_legacyType.small_scenery.flags & SMALL_SCENERY_FLAG_HAS_GLASS) + { + imageId = _legacyType.image + 0x44500004; + if (flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) + { + imageId |= 0x92000000; + } + gfx_draw_sprite(dpi, imageId, x, y, 0); + } + + if (flags & SMALL_SCENERY_FLAG_ANIMATED_FG) + { + imageId = _legacyType.image + 4; + if (flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) + { + imageId |= 0x92000000; + } + gfx_draw_sprite(dpi, imageId, x, y, 0); + } +} + +uint8 * SmallSceneryObject::ReadVar10(IStream * stream) +{ + uint8 b; + auto data = std::vector(); + data.push_back(stream->ReadValue()); + while ((b = stream->ReadValue()) != 0xFF) + { + data.push_back(b); + } + data.push_back(b); + return Memory::Duplicate(data.data(), data.size()); +} diff --git a/src/object/SmallSceneryObject.h b/src/object/SmallSceneryObject.h new file mode 100644 index 0000000000..a7f77b5e8b --- /dev/null +++ b/src/object/SmallSceneryObject.h @@ -0,0 +1,46 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "SceneryObject.h" + +extern "C" +{ + #include "../world/scenery.h" +} + +class SmallSceneryObject : public SceneryObject +{ +private: + rct_scenery_entry _legacyType = { 0 }; + uint8 * _var10data = nullptr; + +public: + explicit SmallSceneryObject(const rct_object_entry &entry) : SceneryObject(entry) { }; + ~SmallSceneryObject(); + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; + +private: + static uint8 * ReadVar10(IStream * stream); +}; diff --git a/src/object/StexObject.cpp b/src/object/StexObject.cpp new file mode 100644 index 0000000000..93e7471290 --- /dev/null +++ b/src/object/StexObject.cpp @@ -0,0 +1,91 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "StexObject.h" + +extern "C" +{ + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_SCENARIO_NAME, + OBJ_STRING_ID_PARK_NAME, + OBJ_STRING_ID_SCENARIO_DETAILS, +}; + +void StexObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.var_06 = stream->ReadValue(); + stream->Seek(1, STREAM_SEEK_CURRENT); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_SCENARIO_NAME); + GetStringTable()->Read(context, stream, OBJ_STRING_ID_PARK_NAME); + GetStringTable()->Read(context, stream, OBJ_STRING_ID_SCENARIO_DETAILS); +} + +void StexObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.scenario_name = language_allocate_object_string(GetScenarioName()); + _legacyType.park_name = language_allocate_object_string(GetParkName()); + _legacyType.details = language_allocate_object_string(GetScenarioDetails()); +} + +void StexObject::Unload() +{ + language_free_object_string(_legacyType.scenario_name); + language_free_object_string(_legacyType.park_name); + language_free_object_string(_legacyType.details); + + _legacyType.scenario_name = 0; + _legacyType.park_name = 0; + _legacyType.details = 0; +} + +void StexObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + // Write (no image) + sint32 x = width / 2; + sint32 y = height / 2; + gfx_draw_string_centred(dpi, 3326, x, y, 0, nullptr); +} + +const utf8 * StexObject::GetName() const +{ + return GetScenarioName(); +} + +const utf8 * StexObject::GetScenarioName() const +{ + const utf8 * name = GetStringTable()->GetString(OBJ_STRING_ID_SCENARIO_NAME); + return name != nullptr ? name : ""; +} + +const utf8 * StexObject::GetScenarioDetails() const +{ + const utf8 * name = GetStringTable()->GetString(OBJ_STRING_ID_SCENARIO_DETAILS); + return name != nullptr ? name : ""; +} + +const utf8 * StexObject::GetParkName() const +{ + const utf8 * name = GetStringTable()->GetString(OBJ_STRING_ID_PARK_NAME); + return name != nullptr ? name : ""; +} diff --git a/src/object/StexObject.h b/src/object/StexObject.h new file mode 100644 index 0000000000..b922951b63 --- /dev/null +++ b/src/object/StexObject.h @@ -0,0 +1,47 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Object.h" + +extern "C" +{ + #include "../scenario.h" +} + +class StexObject : public Object +{ +private: + rct_stex_entry _legacyType = { 0 }; + +public: + explicit StexObject(const rct_object_entry &entry) : Object(entry) { }; + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; + + const utf8 * GetName() const override; + + const utf8 * GetScenarioName() const; + const utf8 * GetScenarioDetails() const; + const utf8 * GetParkName() const; +}; diff --git a/src/object/StringTable.cpp b/src/object/StringTable.cpp new file mode 100644 index 0000000000..88ed280d1d --- /dev/null +++ b/src/object/StringTable.cpp @@ -0,0 +1,127 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include +#include "../core/IStream.hpp" +#include "../core/String.hpp" +#include "../localisation/LanguagePack.h" +#include "Object.h" +#include "StringTable.h" + +constexpr uint8 RCT2_LANGUAGE_ID_ENGLISH_UK = 0; +constexpr uint8 RCT2_LANGUAGE_ID_BLANK = 254; +constexpr uint8 RCT2_LANGUAGE_ID_END = 255; + +bool StringIsBlank(utf8 * str) +{ + for (utf8 * ch = str; *ch != '\0'; ch++) + { + if (!isblank(*ch)) + { + return false; + } + } + return true; +} + +StringTable::~StringTable() +{ + for (auto entry : _strings) + { + Memory::Free(entry.Text); + } +} + +void StringTable::Read(IReadObjectContext * context, IStream * stream, uint8 id) +{ + try + { + uint8 languageId; + while ((languageId = stream->ReadValue()) != RCT2_LANGUAGE_ID_END) + { + StringTableEntry entry; + entry.Id = id; + entry.LanguageId = languageId; + + char * stringAsWin1252 = stream->ReadString(); + utf8 * stringAsUtf8 = rct2_language_string_to_utf8(stringAsWin1252, languageId); + Memory::Free(stringAsWin1252); + + if (StringIsBlank(stringAsUtf8)) + { + entry.LanguageId = RCT2_LANGUAGE_ID_BLANK; + } + String::Trim(stringAsUtf8); + + entry.Text = stringAsUtf8; + _strings.push_back(entry); + } + } + catch (Exception ex) + { + context->LogError(OBJECT_ERROR_BAD_STRING_TABLE, "Bad string table."); + throw; + } + Sort(); +} + +const utf8 * StringTable::GetString(uint8 id) const +{ + for (auto &string : _strings) + { + if (string.Id == id) + { + return string.Text; + } + } + return nullptr; +} + +void StringTable::Sort() +{ + std::sort(_strings.begin(), _strings.end(), [](const StringTableEntry &a, const StringTableEntry &b) -> bool + { + if (a.Id == b.Id) + { + if (a.LanguageId == b.LanguageId) + { + return _strcmpi(a.Text, b.Text) == -1; + } + + uint8 currentLanguage = LanguagesDescriptors[gCurrentLanguage].rct2_original_id; + if (a.LanguageId == currentLanguage) + { + return true; + } + if (b.LanguageId == currentLanguage) + { + return false; + } + + if (a.LanguageId == RCT2_LANGUAGE_ID_ENGLISH_UK) + { + return true; + } + if (b.LanguageId == RCT2_LANGUAGE_ID_ENGLISH_UK) + { + return false; + } + + return a.LanguageId < b.LanguageId; + } + return a.Id < b.Id; + }); +} diff --git a/src/object/StringTable.h b/src/object/StringTable.h new file mode 100644 index 0000000000..8c336a8bce --- /dev/null +++ b/src/object/StringTable.h @@ -0,0 +1,43 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include +#include "../common.h" + +interface IReadObjectContext; +interface IStream; + +struct StringTableEntry +{ + uint8 Id; + uint8 LanguageId; + utf8 * Text; +}; + +class StringTable +{ +private: + std::vector _strings; + +public: + ~StringTable(); + + void Read(IReadObjectContext * context, IStream * stream, uint8 id); + void Sort(); + const utf8 * GetString(uint8 id) const; +}; \ No newline at end of file diff --git a/src/object/WallObject.cpp b/src/object/WallObject.cpp new file mode 100644 index 0000000000..93c5eee0bd --- /dev/null +++ b/src/object/WallObject.cpp @@ -0,0 +1,99 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "../core/Memory.hpp" +#include "WallObject.h" + +extern "C" +{ + #include "../drawing/drawing.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +void WallObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.wall.tool_id = stream->ReadValue(); + _legacyType.wall.flags = stream->ReadValue(); + _legacyType.wall.height = stream->ReadValue(); + _legacyType.wall.flags2 = stream->ReadValue(); + _legacyType.wall.price = stream->ReadValue(); + _legacyType.wall.scenery_tab_id = stream->ReadValue(); + _legacyType.wall.var_0D = stream->ReadValue(); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + + rct_object_entry sgEntry = stream->ReadValue(); + SetPrimarySceneryGroup(&sgEntry); + + GetImageTable()->Read(context, stream); + + // Validate properties + if (_legacyType.wall.price <= 0) + { + context->LogError(OBJECT_ERROR_INVALID_PROPERTY, "Price can not be free or negative."); + } +} + +void WallObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.name = language_allocate_object_string(GetName()); + _legacyType.image = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); +} + +void WallObject::Unload() +{ + language_free_object_string(_legacyType.name); + gfx_object_free_images(_legacyType.image, GetImageTable()->GetCount()); + + _legacyType.name = 0; + _legacyType.image = 0; +} + +void WallObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + sint32 x = width / 2; + sint32 y = height / 2; + + x += 14; + y += (_legacyType.wall.height * 2) + 16; + + uint32 imageId = 0x20D00000 | _legacyType.image; + if (_legacyType.wall.flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) + { + imageId |= 0x92000000; + } + + gfx_draw_sprite(dpi, imageId, x, y, 0); + + if (_legacyType.wall.flags & WALL_SCENERY_FLAG2) + { + imageId = _legacyType.image + 0x44500006; + gfx_draw_sprite(dpi, imageId, x, y, 0); + } + else if (_legacyType.wall.flags & WALL_SCENERY_IS_DOOR) + { + imageId++; + gfx_draw_sprite(dpi, imageId, x, y, 0); + } +} diff --git a/src/object/WallObject.h b/src/object/WallObject.h new file mode 100644 index 0000000000..3b3f44b95d --- /dev/null +++ b/src/object/WallObject.h @@ -0,0 +1,41 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "SceneryObject.h" + +extern "C" +{ + #include "../world/scenery.h" +} + +class WallObject : public SceneryObject +{ +private: + rct_scenery_entry _legacyType = { 0 }; + +public: + explicit WallObject(const rct_object_entry &entry) : SceneryObject(entry) { }; + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; +}; diff --git a/src/object/WaterObject.cpp b/src/object/WaterObject.cpp new file mode 100644 index 0000000000..cf2ee24a3c --- /dev/null +++ b/src/object/WaterObject.cpp @@ -0,0 +1,67 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#include "../core/IStream.hpp" +#include "WaterObject.h" + +extern "C" +{ + #include "../addresses.h" + #include "../localisation/localisation.h" +} + +enum OBJ_STRING_ID +{ + OBJ_STRING_ID_NAME, +}; + +void WaterObject::ReadLegacy(IReadObjectContext * context, IStream * stream) +{ + stream->Seek(6, STREAM_SEEK_CURRENT); + _legacyType.var_06 = stream->ReadValue(); + _legacyType.var_0A = stream->ReadValue(); + _legacyType.var_0E = stream->ReadValue(); + + GetStringTable()->Read(context, stream, OBJ_STRING_ID_NAME); + GetImageTable()->Read(context, stream); +} + +void WaterObject::Load() +{ + GetStringTable()->Sort(); + _legacyType.string_idx = language_allocate_object_string(GetName()); + _legacyType.image_id = gfx_object_allocate_images(GetImageTable()->GetImages(), GetImageTable()->GetCount()); + _legacyType.var_06 = _legacyType.image_id + 1; + _legacyType.var_0A = _legacyType.image_id + 4; + + load_palette(); + gfx_invalidate_screen(); +} + +void WaterObject::Unload() +{ + language_free_object_string(_legacyType.string_idx); + + _legacyType.string_idx = 0; +} + +void WaterObject::DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const +{ + // Write (no image) + sint32 x = width / 2; + sint32 y = height / 2; + gfx_draw_string_centred(dpi, 3326, x, y, 0, nullptr); +} diff --git a/src/object/WaterObject.h b/src/object/WaterObject.h new file mode 100644 index 0000000000..1c946d0372 --- /dev/null +++ b/src/object/WaterObject.h @@ -0,0 +1,41 @@ +#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers +/***************************************************************************** + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * OpenRCT2 is the work of many authors, a full list can be found in contributors.md + * For more information, visit https://github.com/OpenRCT2/OpenRCT2 + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * A full copy of the GNU General Public License can be found in licence.txt + *****************************************************************************/ +#pragma endregion + +#pragma once + +#include "Object.h" + +extern "C" +{ + #include "../world/water.h" +} + +class WaterObject : public Object +{ +private: + rct_water_type _legacyType = { 0 }; + +public: + explicit WaterObject(const rct_object_entry &entry) : Object(entry) { }; + + void * GetLegacyData() override { return &_legacyType; } + + void ReadLegacy(IReadObjectContext * context, IStream * stream) override; + void Load() override; + void Unload() override; + + void DrawPreview(rct_drawpixelinfo * dpi, sint32 width, sint32 height) const override; +}; diff --git a/src/object_list.c b/src/object_list.c index 34d866cfb6..36c244be38 100644 --- a/src/object_list.c +++ b/src/object_list.c @@ -15,32 +15,12 @@ #pragma endregion #include "addresses.h" -#include "config.h" #include "game.h" -#include "localisation/localisation.h" #include "object.h" #include "object_list.h" -#include "platform/platform.h" -#include "rct1.h" -#include "ride/track.h" -#include "ride/track_design.h" +#include "object/ObjectRepository.h" #include "util/sawyercoding.h" -#include "world/entrance.h" -#include "world/footpath.h" -#include "world/scenery.h" -#include "world/water.h" - -#define PLUGIN_VERSION 4 -#define FILTER_VERSION 1 - -typedef struct rct_plugin_header { - uint16 version; - uint32 total_files; - uint32 total_file_size; - uint32 date_modified_checksum; - uint32 object_list_size; - uint32 object_list_no_items; -} rct_plugin_header; +#include "util/util.h" // 98DA00 int object_entry_group_counts[] = { @@ -102,383 +82,7 @@ const rct_object_entry_group object_entry_groups[] = { (void**)(gStexEntries ), (rct_object_entry_extended*)(0x00F3F03C + (720 * 20)) // scenario text 0x009ADAE4, 0xF4287C }; -static int object_list_cache_load(int totalFiles, uint64 totalFileSize, int fileDateModifiedChecksum); -static int object_list_cache_save(int fileCount, uint64 totalFileSize, int fileDateModifiedChecksum, int currentItemOffset); - -void object_list_create_hash_table(); -static uint32 install_object_entry(rct_object_entry* entry, rct_object_entry* installed_entry, const char* path, rct_object_filters* filter); -static void load_object_filter(rct_object_entry* entry, uint8* chunk, rct_object_filters* filter); - -static rct_object_filters *_installedObjectFilters = NULL; - -rct_stex_entry *gStexTempChunk; - -uint32 gInstalledObjectsCount; -rct_object_entry *gInstalledObjects; -uint32 gNumInstalledRCT2Objects; -uint32 gNumInstalledCustomObjects; - -void *gLastLoadedObjectChunkData; - -static void get_plugin_path(utf8 *outPath) -{ - platform_get_user_directory(outPath, NULL); - strcat(outPath, "plugin.dat"); -} - -static uintptr_t object_get_length_cached(const rct_object_entry **entryCache, const size_t index) -{ - return (uintptr_t)entryCache[index + 1] - (uintptr_t)entryCache[index]; -} - -static rct_object_entry **_entryCache = NULL; - -static int object_comparator(const void *left, const void *right) -{ - const size_t leftIndex = *(const size_t *)left; - const size_t rightIndex = *(const size_t *)right; - const char *leftName = object_get_name(_entryCache[leftIndex]); - const char *rightName = object_get_name(_entryCache[rightIndex]); - return strcmp(leftName, rightName); -} - -static void object_list_sort() -{ - rct_object_entry **objectBuffer, *newBuffer, *entry, *destEntry; - rct_object_filters *newFilters = NULL, *destFilter = NULL; - int numObjects, bufferSize; - size_t entrySize; - - objectBuffer = &gInstalledObjects; - numObjects = gInstalledObjectsCount; - - - _entryCache = malloc((numObjects + 1)* sizeof(rct_object_entry*)); - size_t *sortLUT = malloc((numObjects + 1) * sizeof(size_t)); - entry = *objectBuffer; - // This loop initialises entry cache, so it doesn't have to be called 17M - // times, but only a few thousand. - int i = 0; - do { - _entryCache[i] = entry; - sortLUT[i] = i; - } while (i++ < numObjects && (entry = object_get_next(entry))); - qsort(sortLUT, numObjects, sizeof(size_t), object_comparator); - - // Get buffer size - bufferSize = (uintptr_t)entry - (uintptr_t)*objectBuffer; - - // Create new buffer - newBuffer = (rct_object_entry*)malloc(bufferSize); - destEntry = newBuffer; - if (_installedObjectFilters) { - newFilters = malloc(numObjects * sizeof(rct_object_filters)); - destFilter = newFilters; - } - - // Copy over sorted objects - for (int i = 0; i < numObjects; i++) { - entrySize = object_get_length_cached((const rct_object_entry **)_entryCache, sortLUT[i]); - memcpy(destEntry, _entryCache[sortLUT[i]], entrySize); - destEntry = (rct_object_entry*)((uintptr_t)destEntry + entrySize); - if (_installedObjectFilters) - destFilter[i] = _installedObjectFilters[sortLUT[i]]; - } - free(_entryCache); - free(sortLUT); - - // Replace old buffer - free(*objectBuffer); - *objectBuffer = newBuffer; - if (_installedObjectFilters) { - free(_installedObjectFilters); - _installedObjectFilters = newFilters; - } -} - -static uint32 object_list_count_custom_objects() -{ - uint32 numCustomObjects = 0; - rct_object_entry *object = gInstalledObjects; - for (uint32 i = 0; i < gInstalledObjectsCount; i++) { - if ((object->flags & 0xF0) == 0) { - numCustomObjects++; - } - object = object_get_next(object); - } - - gNumInstalledCustomObjects = numCustomObjects; - return numCustomObjects; -} - -/** - * - * rct2: 0x006A93CD - */ -static void object_list_examine() -{ - object_list_count_custom_objects(); - object_list_sort(); - object_list_create_hash_table(); -} - -/** - * - * rct2: 0x006A9FC0 - */ -void reset_loaded_objects() -{ - reset_type_to_ride_entry_index_map(); - - gTotalNoImages = 0xF26E; - - for (int type = 0; type < 11; ++type){ - for (int j = 0; j < object_entry_group_counts[type]; j++){ - uint8* chunk = object_entry_groups[type].chunks[j]; - if (chunk != (uint8*)-1) - object_load(type, chunk, j); - } - } -} - -static int object_list_query_directory(int *outTotalFiles, uint64 *outTotalFileSize, int *outFileDateModifiedChecksum) -{ - int enumFileHandle, totalFiles, fileDateModifiedChecksum; - uint64 totalFileSize; - file_info enumFileInfo; - - totalFiles = 0; - totalFileSize = 0; - fileDateModifiedChecksum = 0; - - // Enumerate through each object in the directory - enumFileHandle = platform_enumerate_files_begin(gRCT2AddressObjectDataPath); - if (enumFileHandle == INVALID_HANDLE) - return 0; - - while (platform_enumerate_files_next(enumFileHandle, &enumFileInfo)) { - totalFiles++; - totalFileSize += enumFileInfo.size; - fileDateModifiedChecksum ^= - (uint32)(enumFileInfo.last_modified >> 32) ^ - (uint32)(enumFileInfo.last_modified & 0xFFFFFFFF); - fileDateModifiedChecksum = ror32(fileDateModifiedChecksum, 5); - } - platform_enumerate_files_end(enumFileHandle); - - *outTotalFiles = totalFiles; - *outTotalFileSize = totalFileSize; - *outFileDateModifiedChecksum = fileDateModifiedChecksum; - return 1; -} - -/** - * - * rct2: 0x006A8B40 - */ -void object_list_load() -{ - int enumFileHandle, totalFiles, fileDateModifiedChecksum; - uint64 totalFileSize; - file_info enumFileInfo; - - int ok = object_list_query_directory(&totalFiles, &totalFileSize, &fileDateModifiedChecksum); - if (ok != 1) { - return; - } - - // Would move this into cache load, but its used further on - totalFiles = ror32(totalFiles, 24); - totalFiles = (totalFiles & ~0xFF) | 1; - totalFiles = rol32(totalFiles, 24); - - if (object_list_cache_load(totalFiles, totalFileSize, fileDateModifiedChecksum)) { - return; - } - - // Dispose installed object list - reset_loaded_objects(); - SafeFree(gInstalledObjects); - - gInstalledObjectsCount = 0; - gInstalledObjects = (rct_object_entry*)malloc(4096); - if (gInstalledObjects == NULL) { - log_error("Failed to allocate memory for object list"); - rct2_exit_reason(835, 3162); - return; - } - - uint32 fileCount = 0; - uint32 objectCount = 0; - size_t currentEntryOffset = 0; - gNumInstalledRCT2Objects = 0; - - log_verbose("building cache of available objects..."); - - if (_installedObjectFilters) { - free(_installedObjectFilters); - _installedObjectFilters = NULL; - } - - enumFileHandle = platform_enumerate_files_begin(gRCT2AddressObjectDataPath); - if (enumFileHandle != INVALID_HANDLE) { - size_t installedObjectsCapacity = 4096; - while (platform_enumerate_files_next(enumFileHandle, &enumFileInfo)) { - fileCount++; - - if ((installedObjectsCapacity - currentEntryOffset) <= 2842){ - installedObjectsCapacity += 4096; - gInstalledObjects = (rct_object_entry*)realloc(gInstalledObjects, installedObjectsCapacity); - if (gInstalledObjects == NULL) { - log_error("Failed to allocate memory for object list"); - rct2_exit_reason(835, 3162); - return; - } - } - - char path[MAX_PATH]; - substitute_path(path, gRCT2AddressObjectDataPath, enumFileInfo.path); - - rct_object_entry entry; - if (object_load_entry(path, &entry)) { - _installedObjectFilters = realloc(_installedObjectFilters, sizeof(rct_object_filters) * (objectCount + 1)); - - rct_object_entry *installedEntry = (rct_object_entry*)((size_t)gInstalledObjects + currentEntryOffset); - rct_object_filters filter; - size_t newEntrySize = install_object_entry(&entry, installedEntry, enumFileInfo.path, &filter); - if (newEntrySize != 0) { - _installedObjectFilters[objectCount] = filter; - objectCount++; - currentEntryOffset += newEntrySize; - } - } - } - platform_enumerate_files_end(enumFileHandle); - } - - reset_loaded_objects(); - - object_list_cache_save(fileCount, totalFileSize, fileDateModifiedChecksum, currentEntryOffset); - - // Reload track list - ride_list_item ride_list; - ride_list.entry_index = 0xFC; - ride_list.type = 0xFC; - // track_load_list(ride_list); - - object_list_examine(); -} - -static int object_list_cache_load(int totalFiles, uint64 totalFileSize, int fileDateModifiedChecksum) -{ - char path[MAX_PATH]; - SDL_RWops *file; - rct_plugin_header pluginHeader; - uint32 filterVersion = 0; - - log_verbose("loading object list cache (plugin.dat)"); - - get_plugin_path(path); - file = SDL_RWFromFile(path, "rb"); - if (file == NULL) { - log_verbose("Unable to load %s", path); - return 0; - } - - if (SDL_RWread(file, &pluginHeader, sizeof(rct_plugin_header), 1) == 1) { - // Check if object repository has changed in anyway - if ( - pluginHeader.version == PLUGIN_VERSION && - pluginHeader.total_files == totalFiles && - pluginHeader.total_file_size == totalFileSize && - pluginHeader.date_modified_checksum == fileDateModifiedChecksum - ) { - // Dispose installed object list - SafeFree(gInstalledObjects); - - // Read installed object list - gInstalledObjects = (rct_object_entry*)malloc(pluginHeader.object_list_size); - if (SDL_RWread(file, gInstalledObjects, pluginHeader.object_list_size, 1) == 1) { - gInstalledObjectsCount = pluginHeader.object_list_no_items; - - if (pluginHeader.object_list_no_items != (pluginHeader.total_files & 0xFFFFFF)) - log_error("Potential mismatch in file numbers. Possible corrupt file. Consider deleting plugin.dat."); - - if (SDL_RWread(file, &filterVersion, sizeof(filterVersion), 1) == 1) { - if (filterVersion == FILTER_VERSION) { - if (_installedObjectFilters != NULL) { - free(_installedObjectFilters); - } - _installedObjectFilters = malloc(sizeof(rct_object_filters) * pluginHeader.object_list_no_items); - if (SDL_RWread(file, _installedObjectFilters, sizeof(rct_object_filters) * pluginHeader.object_list_no_items, 1) == 1) { - SDL_RWclose(file); - reset_loaded_objects(); - object_list_examine(); - return 1; - } - } - } - log_info("Filter version updated... updating object list cache"); - } - } - else if (pluginHeader.version != PLUGIN_VERSION) { - log_info("Object list cache version different... updating"); - } - else if (pluginHeader.total_files != totalFiles) { - int fileCount = totalFiles - pluginHeader.total_files; - if (fileCount < 0) { - log_info("%d object removed... updating object list cache", abs(fileCount)); - } else { - log_info("%d object added... updating object list cache", fileCount); - } - } else if (pluginHeader.total_file_size != totalFileSize) { - log_info("Objects files size changed... updating object list cache"); - } else if (pluginHeader.date_modified_checksum != fileDateModifiedChecksum) { - log_info("Objects files have been updated... updating object list cache"); - } - - SDL_RWclose(file); - return 0; - } - - SDL_RWclose(file); - - log_error("loading object list cache failed"); - return 0; -} - -static int object_list_cache_save(int fileCount, uint64 totalFileSize, int fileDateModifiedChecksum, int currentItemOffset) -{ - utf8 path[MAX_PATH]; - SDL_RWops *file; - rct_plugin_header pluginHeader; - uint32 filterVersion = FILTER_VERSION; - - log_verbose("saving object list cache (plugin.dat)"); - - pluginHeader.version = PLUGIN_VERSION; - pluginHeader.total_files = fileCount | 0x01000000; - pluginHeader.total_file_size = (uint32)totalFileSize; - pluginHeader.date_modified_checksum = fileDateModifiedChecksum; - pluginHeader.object_list_size = currentItemOffset; - pluginHeader.object_list_no_items = gInstalledObjectsCount; - - get_plugin_path(path); - file = SDL_RWFromFile(path,"wb"); - if (file == NULL) { - log_error("Failed to save %s", path); - return 0; - } - - SDL_RWwrite(file, &pluginHeader, sizeof(rct_plugin_header), 1); - SDL_RWwrite(file, gInstalledObjects, pluginHeader.object_list_size, 1); - SDL_RWwrite(file, &filterVersion, sizeof(filterVersion), 1); - SDL_RWwrite(file, _installedObjectFilters, sizeof(rct_object_filters) * gInstalledObjectsCount, 1); - SDL_RWclose(file); - return 1; -} - -int check_object_entry(rct_object_entry *entry) +int check_object_entry(const rct_object_entry *entry) { uint32 *dwords = (uint32*)entry; return (0xFFFFFFFF & dwords[0] & dwords[1] & dwords[2] & dwords[3]) + 1 != 0; @@ -512,52 +116,6 @@ void object_create_identifier_name(char* string_buffer, const rct_object_entry* *string_buffer++ = '\0'; } -/** - * - * rct2: 0x675827 - */ -void set_load_objects_fail_reason() -{ - rct_object_entry *object; - memcpy(&object, gCommonFormatArgs, sizeof(rct_object_entry*)); - - int expansion = (object->flags & 0xFF) >> 4; - if (expansion == 0 || - expansion == 8 || - RCT2_GLOBAL(RCT2_ADDRESS_EXPANSION_FLAGS, uint16) & (1 << expansion) - ) { - char* string_buffer = RCT2_ADDRESS(0x9BC677, char); - - format_string(string_buffer, STR_MISSING_OBJECT_DATA_ID, 0); - - object_create_identifier_name(string_buffer, object); - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_PLACEHOLDER; - return; - } - - rct_string_id expansionNameId; - switch(expansion) { - case 1: // Wacky Worlds - expansionNameId = STR_OBJECT_FILTER_WW; - break; - case 2: // Time Twister - expansionNameId = STR_OBJECT_FILTER_TT; - break; - default: - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_REQUIRES_AN_ADDON_PACK; - return; - } - - char* string_buffer = RCT2_ADDRESS(0x9BC677, char); - - format_string(string_buffer, STR_REQUIRES_THE_FOLLOWING_ADDON_PACK, &expansionNameId); - - gErrorType = ERROR_TYPE_FILE_LOAD; - gErrorStringId = STR_PLACEHOLDER; -} - /** * * rct2: 0x006AA0C6 @@ -572,117 +130,13 @@ bool object_read_and_load_entries(SDL_RWops* rw) return result; } -bool object_load_entries(rct_object_entry* entries) -{ - log_verbose("loading required objects"); - - object_unload_all(); - - bool loadFailed = false; - // Load each object - for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) { - if (!check_object_entry(&entries[i])) { - continue; - } - - // Get entry group index - int entryGroupIndex = i; - for (int j = 0; j < countof(object_entry_group_counts); j++) { - if (entryGroupIndex < object_entry_group_counts[j]) - break; - entryGroupIndex -= object_entry_group_counts[j]; - } - - // Load the obect - if (!object_load_chunk(entryGroupIndex, &entries[i], NULL)) { - log_error("failed to load entry: %.8s", entries[i].name); - memcpy(gCommonFormatArgs, &entries[i], sizeof(rct_object_entry)); - loadFailed = true; - } - } - - if (loadFailed) { - object_unload_all(); - return false; - } - - log_verbose("finished loading required objects"); - return true; -} - - - -/** - * - * rct2: 0x006A9CE8 - */ -void object_unload_all() -{ - int i, j; - - for (i = 0; i < OBJECT_ENTRY_GROUP_COUNT; i++) - for (j = 0; j < object_entry_group_counts[i]; j++) - if (object_entry_groups[i].chunks[j] != (uint8*)0xFFFFFFFF) - object_unload_chunk((rct_object_entry*)&object_entry_groups[i].entries[j]); - - reset_loaded_objects(); -} - - - -uint32 _installedObjectHashTableSize; -rct_object_entry ** _installedObjectHashTable = NULL; - -uint32 _installedObjectHashTableCollisions; - -uint32 object_get_hash_code(rct_object_entry *object) -{ - uint32 hash = 5381; - for (int i = 0; i < 8; i++) - hash = ((hash << 5) + hash) + object->name[i]; - - return hash; -} - -void object_list_create_hash_table() -{ - rct_object_entry *installedObject; - int numInstalledObjects = gInstalledObjectsCount; - - if (_installedObjectHashTable != NULL) - free(_installedObjectHashTable); - - _installedObjectHashTableSize = max(8192, numInstalledObjects * 4); - _installedObjectHashTable = calloc(_installedObjectHashTableSize, sizeof(rct_object_entry*)); - _installedObjectHashTableCollisions = 0; - - installedObject = gInstalledObjects; - for (int i = 0; i < numInstalledObjects; i++) { - uint32 hash = object_get_hash_code(installedObject); - uint32 index = hash % _installedObjectHashTableSize; - - // Find empty slot - while (_installedObjectHashTable[index] != NULL) { - _installedObjectHashTableCollisions++; - index++; - if (index >= _installedObjectHashTableSize) index = 0; - } - - // Set hash table slot - _installedObjectHashTable[index] = installedObject; - - // Next installed object - installedObject = object_get_next(installedObject); - } -} - /** * * rct2: 0x006A9DA2 * bl = entry_index * ecx = entry_type */ -int find_object_in_entry_group(rct_object_entry* entry, uint8* entry_type, uint8* entry_index){ +int find_object_in_entry_group(const rct_object_entry* entry, uint8* entry_type, uint8* entry_index){ *entry_type = entry->flags & 0xF; rct_object_entry_group entry_group = object_entry_groups[*entry_type]; @@ -701,233 +155,58 @@ int find_object_in_entry_group(rct_object_entry* entry, uint8* entry_type, uint8 return 1; } -rct_object_entry *object_list_find_by_name(const char * name) -{ - rct_object_entry entry; - memcpy(entry.name, name, 8); - - uint32 hash = object_get_hash_code(&entry); - uint32 index = hash % _installedObjectHashTableSize; - - while (_installedObjectHashTable[index] != NULL) { - if (memcmp(_installedObjectHashTable[index]->name, entry.name, 8) == 0) - return _installedObjectHashTable[index]; - - index++; - if (index >= _installedObjectHashTableSize) index = 0; - } - - return NULL; -} - -rct_object_entry *object_list_find(rct_object_entry *entry) -{ - uint32 hash = object_get_hash_code(entry); - uint32 index = hash % _installedObjectHashTableSize; - - while (_installedObjectHashTable[index] != NULL) { - if (object_entry_compare( _installedObjectHashTable[index], entry)) - return _installedObjectHashTable[index]; - - index++; - if (index >= _installedObjectHashTableSize) index = 0; - } - - return NULL; -} - -rct_string_id object_get_name_string_id(rct_object_entry *entry, const void *chunk) -{ - int objectType = entry->flags & 0x0F; - switch (objectType) { - case OBJECT_TYPE_RIDE: - return ((rct_ride_entry*)chunk)->name; - case OBJECT_TYPE_SMALL_SCENERY: - case OBJECT_TYPE_LARGE_SCENERY: - case OBJECT_TYPE_WALLS: - case OBJECT_TYPE_BANNERS: - case OBJECT_TYPE_PATH_BITS: - return ((rct_scenery_entry*)chunk)->name; - case OBJECT_TYPE_PATHS: - return ((rct_footpath_entry*)chunk)->string_idx; - case OBJECT_TYPE_SCENERY_SETS: - return ((rct_scenery_set_entry*)chunk)->name; - case OBJECT_TYPE_PARK_ENTRANCE: - return ((rct_entrance_type*)chunk)->string_idx; - case OBJECT_TYPE_WATER: - return ((rct_water_type*)chunk)->string_idx; - case OBJECT_TYPE_SCENARIO_TEXT: - return ((rct_stex_entry*)chunk)->scenario_name; - default: - return STR_NONE; - } -} - -/** - * Installs an object_entry at the desired installed_entry address - * Returns the size of the new entry. Will return 0 on failure. - */ -static uint32 install_object_entry(rct_object_entry* entry, rct_object_entry* installed_entry, const char* path, rct_object_filters* filter){ - uint8* installed_entry_pointer = (uint8*) installed_entry; - - /** Copy all known information into the install entry **/ - memcpy(installed_entry_pointer, entry, sizeof(rct_object_entry)); - installed_entry_pointer += sizeof(rct_object_entry); - - strcpy((char *)installed_entry_pointer, path); - while (*installed_entry_pointer++); - - // Chunk size is set to unknown - *((sint32*)installed_entry_pointer) = -1; - // No unknown objects set to 0 - *(installed_entry_pointer + 4) = 0; - // No theme objects set to 0 - *((sint32*)(installed_entry_pointer + 5)) = 0; - *((uint16*)(installed_entry_pointer + 9)) = 0; - *((uint32*)(installed_entry_pointer + 11)) = 0; - - gTotalNoImages = 0xF26E; - - gInstalledObjectsCount++; - - // This is a variable used by object_load to decide if it should - // use object_paint on the entry. - RCT2_GLOBAL(0x009ADAFD, uint8) = 1; - - // Probably used by object paint. - RCT2_GLOBAL(0x009ADAF4, uint32) = 0xF42BDB; - - /** Use object_load_file to fill in missing chunk information **/ - int chunk_size; - if (!object_load_file(-1, entry, &chunk_size, installed_entry)){ - log_error("Object Load File failed. Potentially corrupt file: %.8s", entry->name); - RCT2_GLOBAL(0x009ADAF4, sint32) = -1; - RCT2_GLOBAL(0x009ADAFD, uint8) = 0; - gInstalledObjectsCount--; - return 0; - } - - // See above note - RCT2_GLOBAL(0x009ADAF4, sint32) = -1; - RCT2_GLOBAL(0x009ADAFD, uint8) = 0; - - if ((entry->flags & 0xF0) == 0x80) { - gNumInstalledRCT2Objects++; - if (gNumInstalledRCT2Objects > 772){ - log_error("Incorrect number of vanilla RCT2 objects."); - gNumInstalledRCT2Objects--; - gInstalledObjectsCount--; - object_unload_chunk(entry); - return 0; - } - } - *((sint32*)installed_entry_pointer) = chunk_size; - installed_entry_pointer += 4; - - uint8* chunk = (uint8*)gLastLoadedObjectChunkData; // Loaded in object_load - - load_object_filter(entry, chunk, filter); - - // Always extract only the vehicle type, since the track type is always displayed in the left column, to prevent duplicate track names. - rct_string_id nameStringId = object_get_name_string_id(entry, chunk); - if (nameStringId == STR_NONE) { - nameStringId = (rct_string_id)RCT2_GLOBAL(RCT2_ADDRESS_CURR_OBJECT_BASE_STRING_ID, uint32); - } - - strcpy((char *)installed_entry_pointer, language_get_string(nameStringId)); - while (*installed_entry_pointer++); - - // This is deceptive. Due to setting the total no images earlier to 0xF26E - // this is actually the no_images in this entry. - *((uint32*)installed_entry_pointer) = gTotalNoImages - 0xF26E; - installed_entry_pointer += 4; - - uint8* esi = RCT2_ADDRESS(0x00F42BDB, uint8); - uint8 num_unk_objects = *esi++; - *installed_entry_pointer++ = num_unk_objects; - if (num_unk_objects > 0) { - memcpy(installed_entry_pointer, esi, num_unk_objects * sizeof(rct_object_entry)); - installed_entry_pointer += num_unk_objects * sizeof(rct_object_entry); - esi += num_unk_objects * sizeof(rct_object_entry); - } - - uint8 no_theme_objects = *esi++; - *installed_entry_pointer++ = no_theme_objects; - if (no_theme_objects > 0) { - memcpy(installed_entry_pointer, esi, no_theme_objects * sizeof(rct_object_entry)); - installed_entry_pointer += no_theme_objects * sizeof(rct_object_entry); - } - - *((uint32*)installed_entry_pointer) = RCT2_GLOBAL(0x00F433DD, uint32); - installed_entry_pointer += 4; - - uint32 size_of_object = installed_entry_pointer - (uint8*)installed_entry; - - object_unload_chunk(entry); - - return size_of_object; -} - -static void load_object_filter(rct_object_entry* entry, uint8* chunk, rct_object_filters* filter) -{ - rct_ride_entry *rideType; - rct_ride_filters *rideFilter; - - switch (entry->flags & 0xF) { - case OBJECT_TYPE_RIDE: - rideType = ((rct_ride_entry*)chunk); - rideFilter = &(filter->ride); - - rideFilter->category[0] = rideType->category[0]; - rideFilter->category[1] = rideType->category[1]; - - for (int i = 0; i < 3; i++) { - rideFilter->ride_type = rideType->ride_type[i]; - if (rideFilter->ride_type != 255) - break; - } - break; - case OBJECT_TYPE_SMALL_SCENERY: - case OBJECT_TYPE_LARGE_SCENERY: - case OBJECT_TYPE_WALLS: - case OBJECT_TYPE_BANNERS: - case OBJECT_TYPE_PATHS: - case OBJECT_TYPE_PATH_BITS: - case OBJECT_TYPE_SCENERY_SETS: - case OBJECT_TYPE_PARK_ENTRANCE: - case OBJECT_TYPE_WATER: - case OBJECT_TYPE_SCENARIO_TEXT: - break; - } -} - -rct_object_filters *get_object_filter(int index) -{ - return &_installedObjectFilters[index]; -} - -void object_list_init() -{ - for (uint8 objectType = 0; objectType < OBJECT_ENTRY_GROUP_COUNT; objectType++) { - for (size_t i = 0; i < (size_t)object_entry_group_counts[objectType]; i++) { - object_entry_groups[objectType].chunks[i] = (void*)-1; - } - } -} - -void *get_loaded_object_entry(size_t index) +void get_type_entry_index(size_t index, uint8 * outObjectType, uint8 * outEntryIndex) { uint8 objectType = OBJECT_TYPE_RIDE; for (size_t i = 0; i < OBJECT_ENTRY_GROUP_COUNT; i++) { size_t groupCount = object_entry_group_counts[i]; if (index >= groupCount) { - index -= object_entry_group_counts[i]; + index -= groupCount; objectType++; } else { break; } } - void *entry = object_entry_groups[objectType].chunks[index]; + if (outObjectType != NULL) *outObjectType = objectType; + if (outEntryIndex != NULL) *outEntryIndex = (uint8)index; +} + +const rct_object_entry * get_loaded_object_entry(size_t index) +{ + uint8 objectType, entryIndex; + get_type_entry_index(index, &objectType, &entryIndex); + + rct_object_entry * entry = (rct_object_entry *)&(object_entry_groups[objectType].entries[entryIndex]); return entry; } + +void * get_loaded_object_chunk(size_t index) +{ + uint8 objectType, entryIndex; + get_type_entry_index(index, &objectType, &entryIndex); + + void *entry = object_entry_groups[objectType].chunks[entryIndex]; + return entry; +} + +void object_entry_get_name(utf8 * buffer, size_t bufferSize, const rct_object_entry * entry) +{ + size_t nameLength = 8; + const char * end = memchr(entry->name, ' ', 8); + if (end != NULL) + { + nameLength = (size_t)(end - entry->name); + } + + bufferSize = min(nameLength + 1, bufferSize); + memcpy(buffer, entry->name, bufferSize - 1); + buffer[bufferSize - 1] = 0; +} + +void object_entry_get_name_fixed(utf8 * buffer, size_t bufferSize, const rct_object_entry * entry) +{ + bufferSize = min(9, bufferSize); + memcpy(buffer, entry->name, bufferSize - 1); + buffer[bufferSize - 1] = 0; +} diff --git a/src/object_list.h b/src/object_list.h index 9b9b6effc7..ea30c27761 100644 --- a/src/object_list.h +++ b/src/object_list.h @@ -33,7 +33,6 @@ #define gStexEntries RCT2_ADDRESS(RCT2_ADDRESS_SCENARIO_TEXT_ENTRIES, rct_stex_entry*) #endif -extern rct_stex_entry *gStexTempChunk; - -void object_list_init(); -void *get_loaded_object_entry(size_t index); +void get_type_entry_index(size_t index, uint8 * outObjectType, uint8 * outEntryIndex); +const rct_object_entry * get_loaded_object_entry(size_t index); +void * get_loaded_object_chunk(size_t index); diff --git a/src/openrct2.c b/src/openrct2.c index 9838aecbf8..d99baf7f96 100644 --- a/src/openrct2.c +++ b/src/openrct2.c @@ -222,8 +222,6 @@ bool openrct2_initialise() audio_populate_devices(); } - object_list_init(); - if (!language_open(gConfigGeneral.language)) { log_error("Failed to open configured language..."); diff --git a/src/paint/map_element/scenery.c b/src/paint/map_element/scenery.c index 414e2ca30d..c8cacba205 100644 --- a/src/paint/map_element/scenery.c +++ b/src/paint/map_element/scenery.c @@ -130,7 +130,7 @@ void scenery_paint(uint8 direction, int height, rct_map_element* mapElement) { sub_98197C(baseImageid, x_offset, y_offset, boxlength.x, boxlength.y, boxlength.z - 1, height, boxoffset.x, boxoffset.y, boxoffset.z, get_current_rotation()); } - if (entry->small_scenery.flags & SMALL_SCENERY_FLAG10) { + if (entry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_GLASS) { if (dword_F64EB0 == 0) { // Draw translucent overlay: int image_id = (baseImageid & 0x7FFFF) + (((mapElement->properties.scenery.colour_1 & 0x1F) + 112) << 19) + 0x40000004; @@ -230,7 +230,7 @@ void scenery_paint(uint8 direction, int height, rct_map_element* mapElement) { esi &= entry->small_scenery.var_16; int image_id = 0; if (esi < entry->small_scenery.var_18) { - image_id = ((uint8*)entry->small_scenery.var_10)[esi]; + image_id = entry->small_scenery.var_10[esi]; } image_id = (image_id * 4) + direction + entry->image; if (entry->small_scenery.flags & (SMALL_SCENERY_FLAG21 | SMALL_SCENERY_FLAG17)) { diff --git a/src/platform/windows.c b/src/platform/windows.c index 262ee21903..f329066b81 100644 --- a/src/platform/windows.c +++ b/src/platform/windows.c @@ -417,7 +417,7 @@ bool platform_enumerate_directories_next(int handle, utf8 *path) if (enumFileInfo->handle == NULL) { fileHandle = FindFirstFileW(enumFileInfo->pattern, &enumFileInfo->data); - if (fileHandle != 0) { + if (fileHandle != INVALID_HANDLE_VALUE) { enumFileInfo->handle = fileHandle; } else { return false; diff --git a/src/rct1.c b/src/rct1.c index 04c2356359..cab41bb427 100644 --- a/src/rct1.c +++ b/src/rct1.c @@ -78,7 +78,7 @@ bool rct1_read_sv4(const char *path, rct1_s4 *s4) return success; } -bool rideTypeShouldLoseSeparateFlag(rct_ride_entry *rideEntry) +bool rideTypeShouldLoseSeparateFlag(const rct_ride_entry *rideEntry) { if (!gConfigInterface.select_by_track_type) { return false; diff --git a/src/rct1.h b/src/rct1.h index 4ae06a295e..8c07cdc058 100644 --- a/src/rct1.h +++ b/src/rct1.h @@ -745,7 +745,7 @@ bool rct1_read_sv4(const char *path, rct1_s4 *s4); void rct1_import_s4(rct1_s4 *s4); void rct1_fix_landscape(); int vehicle_preference_compare(uint8 rideType, const char * a, const char * b); -bool rideTypeShouldLoseSeparateFlag(rct_ride_entry *rideEntry); +bool rideTypeShouldLoseSeparateFlag(const rct_ride_entry *rideEntry); bool rct1_load_saved_game(const char *path); bool rct1_load_scenario(const char *path); diff --git a/src/rct1/S4Importer.cpp b/src/rct1/S4Importer.cpp index 00d67f96c4..dbdb0686bd 100644 --- a/src/rct1/S4Importer.cpp +++ b/src/rct1/S4Importer.cpp @@ -22,6 +22,7 @@ #include "../core/Path.hpp" #include "../core/String.hpp" #include "../core/Util.hpp" +#include "../object/ObjectManager.h" #include "Tables.h" extern "C" @@ -36,6 +37,7 @@ extern "C" #include "../management/finance.h" #include "../management/marketing.h" #include "../object.h" + #include "../object/ObjectManager.h" #include "../peep/staff.h" #include "../rct1.h" #include "../util/sawyercoding.h" @@ -110,7 +112,7 @@ void S4Importer::Initialise() // Do map initialisation, same kind of stuff done when loading scenario editor audio_pause_sounds(); audio_unpause_sounds(); - object_unload_all(); + GetObjectManager()->UnloadAll(); map_init(mapSize); banner_init(); reset_park_entrances(); @@ -734,12 +736,12 @@ void S4Importer::LoadObjects() })); LoadObjects(OBJECT_TYPE_PARK_ENTRANCE, List({ "PKENT1 " })); LoadObjects(OBJECT_TYPE_WATER, List({ "WTRCYAN " })); - - reset_loaded_objects(); } void S4Importer::LoadObjects(uint8 objectType, List entries) { + IObjectManager * objectManager = GetObjectManager(); + uint32 entryIndex = 0; for (const char * objectName : entries) { @@ -747,8 +749,9 @@ void S4Importer::LoadObjects(uint8 objectType, List entries) entry.flags = 0x00008000 + objectType; Memory::Copy(entry.name, objectName, 8); entry.checksum = 0; - - if (!object_load_chunk(entryIndex, &entry, NULL)) + + Object * object = objectManager->LoadObject(&entry); + if (object == nullptr) { log_error("Failed to load %s.", objectName); throw Exception("Failed to load object."); diff --git a/src/rct2.c b/src/rct2.c index b081a7db5b..98d4e536c0 100644 --- a/src/rct2.c +++ b/src/rct2.c @@ -35,6 +35,7 @@ #include "network/network.h" #include "network/twitch.h" #include "object.h" +#include "object/ObjectManager.h" #include "openrct2.h" #include "peep/staff.h" #include "platform/platform.h" @@ -140,7 +141,7 @@ void rct2_dispose() { gfx_unload_g2(); gfx_unload_g1(); - object_unload_all(); + object_manager_unload_all_objects(); } int rct2_init() diff --git a/src/rct2/S6Exporter.cpp b/src/rct2/S6Exporter.cpp index 3bbc8cfd92..e379c293c0 100644 --- a/src/rct2/S6Exporter.cpp +++ b/src/rct2/S6Exporter.cpp @@ -89,7 +89,7 @@ void S6Exporter::SaveScenario(SDL_RWops *rw) void S6Exporter::Save(SDL_RWops * rw, bool isScenario) { _s6.header.type = isScenario ? S6_TYPE_SCENARIO : S6_TYPE_SAVEDGAME; - _s6.header.num_packed_objects = scenario_get_num_packed_objects_to_write(); + _s6.header.num_packed_objects = ExportObjects ? scenario_get_num_packed_objects_to_write() : 0; _s6.header.version = S6_RCT2_VERSION; _s6.header.magic_number = S6_MAGIC_NUMBER; @@ -132,7 +132,7 @@ void S6Exporter::Save(SDL_RWops * rw, bool isScenario) // 3: Write available objects chunk chunkHeader.encoding = CHUNK_ENCODING_ROTATE; - chunkHeader.length = 721 * sizeof(rct_object_entry); + chunkHeader.length = OBJECT_ENTRY_COUNT * sizeof(rct_object_entry); encodedLength = sawyercoding_write_chunk_buffer(buffer, (uint8*)_s6.objects, chunkHeader); SDL_RWwrite(rw, buffer, encodedLength, 1); @@ -228,19 +228,18 @@ void S6Exporter::Export() { _s6.info = *gS6Info; - for (int i = 0; i < 721; i++) + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) { - rct_object_entry_extended *entry = &(RCT2_ADDRESS(0x00F3F03C, rct_object_entry_extended)[i]); - void *entryData = get_loaded_object_entry(i); + const rct_object_entry * entry = get_loaded_object_entry(i); + void * entryData = get_loaded_object_chunk(i); if (entryData == (void *)0xFFFFFFFF) { - memset(&_s6.objects[i], 0xFF, sizeof(rct_object_entry)); + Memory::Set(&_s6.objects[i], 0xFF, sizeof(rct_object_entry)); } else { _s6.objects[i] = *((rct_object_entry*)entry); } - } _s6.elapsed_months = gDateMonthsElapsed; @@ -508,7 +507,6 @@ extern "C" } delete s6exporter; - reset_loaded_objects(); gfx_invalidate_screen(); if (result && !(flags & S6_SAVE_FLAG_AUTOMATIC)) @@ -541,8 +539,6 @@ extern "C" return 0; } - reset_loaded_objects(); - // Write other data not in normal save files SDL_WriteLE32(rw, gGamePaused); SDL_WriteLE32(rw, _guestGenerationProbability); diff --git a/src/rct2/S6Importer.cpp b/src/rct2/S6Importer.cpp index c8f7b13c7d..edc5181a18 100644 --- a/src/rct2/S6Importer.cpp +++ b/src/rct2/S6Importer.cpp @@ -114,16 +114,9 @@ void S6Importer::LoadSavedGame(SDL_RWops *rw) // Read packed objects // TODO try to contain this more and not store objects until later - if (_s6.header.num_packed_objects > 0) { - int j = 0; - for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) - { - j += object_load_packed(rw); - } - if (j > 0) - { - object_list_load(); - } + for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) + { + object_load_packed(rw); } sawyercoding_read_chunk_safe(rw, &_s6.objects, sizeof(_s6.objects)); @@ -144,16 +137,9 @@ void S6Importer::LoadScenario(SDL_RWops *rw) // Read packed objects // TODO try to contain this more and not store objects until later - if (_s6.header.num_packed_objects > 0) { - int j = 0; - for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) - { - j += object_load_packed(rw); - } - if (j > 0) - { - object_list_load(); - } + for (uint16 i = 0; i < _s6.header.num_packed_objects; i++) + { + object_load_packed(rw); } sawyercoding_read_chunk_safe(rw, &_s6.objects, sizeof(_s6.objects)); @@ -372,10 +358,10 @@ void S6Importer::Import() // pad_13CE778 // Fix and set dynamic variables - if (!object_load_entries(_s6.objects)) { + if (!object_load_entries(_s6.objects)) + { throw ObjectLoadException(); } - reset_loaded_objects(); map_update_tile_pointers(); if (network_get_mode() == NETWORK_MODE_CLIENT) { @@ -423,7 +409,6 @@ extern "C" } catch (ObjectLoadException) { - set_load_objects_fail_reason(); } catch (Exception) { @@ -456,7 +441,8 @@ extern "C" } catch (ObjectLoadException) { - set_load_objects_fail_reason(); + gErrorType = ERROR_TYPE_FILE_LOAD; + gErrorStringId = STR_GAME_SAVE_FAILED; } catch (IOException) { @@ -489,7 +475,6 @@ extern "C" } catch (ObjectLoadException) { - set_load_objects_fail_reason(); } catch (Exception) { diff --git a/src/ride/ride.c b/src/ride/ride.c index f77c559b06..3f4cead7f8 100644 --- a/src/ride/ride.c +++ b/src/ride/ride.c @@ -250,8 +250,40 @@ rct_ride_entry *get_ride_entry_by_ride(rct_ride *ride) * * rct2: 0x006DED68 */ -void reset_type_to_ride_entry_index_map(){ - memset(gTypeToRideEntryIndexMap, 0xFF, 91); +void reset_type_to_ride_entry_index_map() +{ + uint8 maxRideEntries = 128; + uint8 maxRideTypes = 91; + size_t stride = maxRideEntries + 1; + uint8 * entryTypeTable = malloc(maxRideTypes * stride); + memset(entryTypeTable, 0xFF, maxRideTypes * stride); + + for (uint8 i = 0; i < maxRideEntries; i++) { + rct_ride_entry * rideEntry = get_ride_entry(i); + if (rideEntry == (rct_ride_entry *)-1) { + continue; + } + for (uint8 j = 0; j < 3; j++) { + uint8 rideType = rideEntry->ride_type[j]; + if (rideType < maxRideTypes) { + uint8 * entryArray = &entryTypeTable[rideType * stride]; + uint8 * nextEntry = memchr(entryArray, 0xFF, stride); + *nextEntry = i; + } + } + } + + uint8 * dst = gTypeToRideEntryIndexMap; + for (uint8 i = 0; i < maxRideTypes; i++) { + uint8 * entryArray = &entryTypeTable[i * stride]; + uint8 * entry = entryArray; + while (*entry != 0xFF) { + *dst++ = *entry++; + } + *dst++ = 0xFF; + } + + free(entryTypeTable); } uint8 *get_ride_entry_indices_for_ride_type(uint8 rideType) diff --git a/src/ride/track_design.c b/src/ride/track_design.c index 7be7cebbe3..e70f18d668 100644 --- a/src/ride/track_design.c +++ b/src/ride/track_design.c @@ -22,6 +22,7 @@ #include "../localisation/string_ids.h" #include "../management/finance.h" #include "../network/network.h" +#include "../object/ObjectManager.h" #include "../rct1.h" #include "../util/sawyercoding.h" #include "../util/util.h" @@ -319,42 +320,24 @@ void track_design_dispose(rct_track_td6 *td6) } } -uint32 *sub_6AB49A(rct_object_entry* entry) -{ - rct_object_entry* object_list_entry = object_list_find(entry); - - if (object_list_entry == NULL) return NULL; - - // Return the address of the last value of the list entry - return (((uint32*)object_get_next(object_list_entry)) - 1); -} - /** * * rct2: 0x006ABDB0 */ static void track_design_load_scenery_objects(rct_track_td6 *td6) { - uint8 entry_index = RCT2_GLOBAL(0xF44157, uint8); - rct_object_entry_extended* object_entry = &object_entry_groups[0].entries[entry_index]; + object_manager_unload_all_objects(); - rct_object_entry* copied_entry = RCT2_ADDRESS(0xF43414, rct_object_entry); - memcpy(copied_entry, object_entry, sizeof(rct_object_entry)); - - object_unload_all(); - object_load_chunk(-1, copied_entry, 0); - uint8 entry_type; - find_object_in_entry_group(copied_entry, &entry_type, &entry_index); - RCT2_GLOBAL(0xF44157, uint8) = entry_index; + // Load ride object + rct_object_entry * rideEntry = &td6->vehicle_object; + void * loadedObject = object_manager_load_object(rideEntry); + // Load scenery objects rct_td6_scenery_element *scenery = td6->scenery_elements; for (; (scenery->scenery_object.flags & 0xFF) != 0xFF; scenery++) { - if (!find_object_in_entry_group(&scenery->scenery_object, &entry_type, &entry_index)) { - object_load_chunk(-1, &scenery->scenery_object, 0); - } + rct_object_entry * sceneryEntry = &scenery->scenery_object; + object_manager_load_object(sceneryEntry); } - - reset_loaded_objects(); } /** diff --git a/src/scenario.c b/src/scenario.c index e8f5f7278f..2f10664b52 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -700,17 +700,14 @@ int scenario_prepare_for_save() */ int scenario_get_num_packed_objects_to_write() { - int i, count = 0; - rct_object_entry_extended *entry = (rct_object_entry_extended*)0x00F3F03C; - - for (i = 0; i < OBJECT_ENTRY_COUNT; i++, entry++) { - void *entryData = get_loaded_object_entry(i); - if (entryData == (void*)0xFFFFFFFF || (entry->flags & 0xF0)) { - continue; + int count = 0; + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) { + const rct_object_entry *entry = get_loaded_object_entry(i); + void *entryData = get_loaded_object_chunk(i); + if (entryData != (void*)0xFFFFFFFF && !(entry->flags & 0xF0)) { + count++; } - count++; } - return count; } @@ -720,18 +717,15 @@ int scenario_get_num_packed_objects_to_write() */ int scenario_write_packed_objects(SDL_RWops* rw) { - int i; - rct_object_entry_extended *entry = (rct_object_entry_extended*)0x00F3F03C; - for (i = 0; i < OBJECT_ENTRY_COUNT; i++, entry++) { - void *entryData = get_loaded_object_entry(i); - if (entryData == (void*)0xFFFFFFFF || (entry->flags & 0xF0)) { - continue; - } - if (!write_object_file(rw, (rct_object_entry*)entry)) { - return 0; + for (int i = 0; i < OBJECT_ENTRY_COUNT; i++) { + const rct_object_entry *entry = get_loaded_object_entry(i); + void *entryData = get_loaded_object_chunk(i); + if (entryData != (void*)0xFFFFFFFF && !(entry->flags & 0xF0)) { + if (!object_saved_packed(rw, entry)) { + return 0; + } } } - return 1; } @@ -745,7 +739,7 @@ int scenario_write_available_objects(FILE *file) int i, encodedLength; sawyercoding_chunk_header chunkHeader; - const int totalEntries = 721; + const int totalEntries = OBJECT_ENTRY_COUNT; const int bufferLength = totalEntries * sizeof(rct_object_entry); // Initialise buffers @@ -762,17 +756,14 @@ int scenario_write_available_objects(FILE *file) } // Write entries - rct_object_entry_extended *srcEntry = (rct_object_entry_extended*)0x00F3F03C; rct_object_entry *dstEntry = (rct_object_entry*)buffer; for (i = 0; i < OBJECT_ENTRY_COUNT; i++) { - void *entryData = get_loaded_object_entry(i); + void *entryData = get_loaded_object_chunk(i); if (entryData == (void*)0xFFFFFFFF) { memset(dstEntry, 0xFF, sizeof(rct_object_entry)); } else { - *dstEntry = *((rct_object_entry*)srcEntry); + *dstEntry = *get_loaded_object_entry(i); } - - srcEntry++; dstEntry++; } diff --git a/src/scenario.h b/src/scenario.h index dd35641e2a..b78057b8e8 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -490,6 +490,8 @@ bool scenario_get_source_desc(const utf8 *name, source_desc *outDesc); bool scenario_get_source_desc_by_id(uint8 id, source_desc *outDesc); void scenario_normalise_name(utf8 *name); +void scenario_translate(scenario_index_entry *scenarioEntry, const rct_object_entry *stexObjectEntry); + // RCT1 scenario index map enum { SC_UNIDENTIFIED = 255, diff --git a/src/scenario_list.c b/src/scenario_list.c index c92a9a520e..29819410d6 100644 --- a/src/scenario_list.c +++ b/src/scenario_list.c @@ -36,7 +36,6 @@ static void scenario_list_add(const utf8 *path, uint64 timestamp); static void scenario_list_sort(); static int scenario_list_sort_by_category(const void *a, const void *b); static int scenario_list_sort_by_index(const void *a, const void *b); -static void scenario_translate(scenario_index_entry *scenarioEntry, const rct_object_entry *stexObjectEntry); static bool scenario_scores_load(); static void scenario_scores_legacy_get_path(utf8 *outPath); @@ -183,29 +182,6 @@ static void scenario_list_add(const utf8 *path, uint64 timestamp) scenario_translate(newEntry, &s6Info.entry); } -static void scenario_translate(scenario_index_entry *scenarioEntry, const rct_object_entry *stexObjectEntry) -{ - rct_string_id localisedStringIds[3]; - if (language_get_localised_scenario_strings(scenarioEntry->name, localisedStringIds)) { - if (localisedStringIds[0] != STR_NONE) { - safe_strcpy(scenarioEntry->name, language_get_string(localisedStringIds[0]), 64); - } - if (localisedStringIds[2] != STR_NONE) { - safe_strcpy(scenarioEntry->details, language_get_string(localisedStringIds[2]), 256); - } - } else { - // Checks for a scenario string object (possibly for localisation) - if ((stexObjectEntry->flags & 0xFF) != 255) { - if (object_get_scenario_text((rct_object_entry*)stexObjectEntry)) { - rct_stex_entry* stex_entry = gStexTempChunk; - format_string(scenarioEntry->name, stex_entry->scenario_name, NULL); - format_string(scenarioEntry->details, stex_entry->details, NULL); - object_free_scenario_text(); - } - } - } -} - void scenario_list_dispose() { gScenarioListCapacity = 0; diff --git a/src/util/sawyercoding.c b/src/util/sawyercoding.c index d9622f4d8c..8f43c1f410 100644 --- a/src/util/sawyercoding.c +++ b/src/util/sawyercoding.c @@ -96,6 +96,20 @@ bool sawyercoding_read_chunk_safe(SDL_RWops *rw, void *dst, size_t dstLength) } } +bool sawyercoding_skip_chunk(SDL_RWops *rw) +{ + // Read chunk header + sawyercoding_chunk_header chunkHeader; + if (SDL_RWread(rw, &chunkHeader, sizeof(sawyercoding_chunk_header), 1) != 1) { + log_error("Unable to read chunk header!"); + return false; + } + + // Skip chunk data + SDL_RWseek(rw, chunkHeader.length, RW_SEEK_CUR); + return true; +} + /** * * rct2: 0x0067685F @@ -157,6 +171,10 @@ size_t sawyercoding_read_chunk_with_size(SDL_RWops* rw, uint8 *buffer, const siz } uint8* src_buffer = malloc(chunkHeader.length); + if (src_buffer == NULL) { + log_error("Unable to read chunk data!"); + return -1; + } // Read chunk data if (SDL_RWread(rw, src_buffer, chunkHeader.length, 1) != 1) { @@ -377,6 +395,7 @@ static size_t decode_chunk_rle_with_size(const uint8* src_buffer, uint8* dst_buf if (rleCodeByte & 128) { i++; count = 257 - rleCodeByte; + assert(dst + count <= dst_buffer + dstSize); memset(dst, src_buffer[i], count); dst = (uint8*)((uintptr_t)dst + count); } else { diff --git a/src/util/sawyercoding.h b/src/util/sawyercoding.h index 5160c125ce..d7bd3d07b2 100644 --- a/src/util/sawyercoding.h +++ b/src/util/sawyercoding.h @@ -50,6 +50,7 @@ enum { int sawyercoding_validate_checksum(SDL_RWops* rw); uint32 sawyercoding_calculate_checksum(const uint8* buffer, size_t length); bool sawyercoding_read_chunk_safe(SDL_RWops *rw, void *dst, size_t dstLength); +bool sawyercoding_skip_chunk(SDL_RWops *rw); size_t sawyercoding_read_chunk(SDL_RWops* rw, uint8 *buffer); size_t sawyercoding_read_chunk_with_size(SDL_RWops* rw, uint8 *buffer, const size_t buffer_size); size_t sawyercoding_write_chunk_buffer(uint8 *dst_file, uint8* buffer, sawyercoding_chunk_header chunkHeader); diff --git a/src/windows/editor_inventions_list.c b/src/windows/editor_inventions_list.c index fd0fe38ddd..83faf6dda4 100644 --- a/src/windows/editor_inventions_list.c +++ b/src/windows/editor_inventions_list.c @@ -17,16 +17,18 @@ #include "../addresses.h" #include "../cursors.h" #include "../input.h" +#include "../interface/themes.h" #include "../interface/widget.h" #include "../interface/window.h" #include "../localisation/localisation.h" #include "../management/research.h" #include "../object.h" -#include "../world/scenery.h" -#include "../interface/themes.h" +#include "../object/ObjectManager.h" +#include "../object/ObjectRepository.h" #include "../rct1.h" -#include "../util/util.h" #include "../sprites.h" +#include "../util/util.h" +#include "../world/scenery.h" #pragma region Widgets @@ -772,9 +774,6 @@ static void window_editor_inventions_list_paint(rct_window *w, rct_drawpixelinfo return; // Preview image - x = w->x + ((widget->left + widget->right) / 2) + 1; - y = w->y + ((widget->top + widget->bottom) / 2) + 1; - int objectEntryType = 7; int eax = researchItem->entryIndex & 0xFFFFFF; if (eax >= 0x10000) @@ -785,7 +784,22 @@ static void window_editor_inventions_list_paint(rct_window *w, rct_drawpixelinfo if (chunk == NULL || chunk == (void*)0xFFFFFFFF) return; - object_paint(objectEntryType, chunk, dpi, x, y); + rct_object_entry * entry = &object_entry_groups[objectEntryType].entries[researchItem->entryIndex & 0xFF].entry; + + // Draw preview + widget = &w->widgets[WIDX_PREVIEW]; + + void * object = object_manager_get_loaded_object(entry); + if (object != NULL) { + rct_drawpixelinfo clipDPI; + x = w->x + widget->left + 1; + y = w->y + widget->top + 1; + int width = widget->right - widget->left - 1; + int height = widget->bottom - widget->top - 1; + if (clip_drawpixelinfo(&clipDPI, dpi, x, y, width, height)) { + object_draw_preview(object, &clipDPI, width, height); + } + } // Item name x = w->x + ((widget->left + widget->right) / 2) + 1; diff --git a/src/windows/editor_object_selection.c b/src/windows/editor_object_selection.c index 36b9a6dd4a..497bfd375b 100644 --- a/src/windows/editor_object_selection.c +++ b/src/windows/editor_object_selection.c @@ -27,6 +27,8 @@ #include "../management/research.h" #include "../object.h" #include "../object_list.h" +#include "../object/ObjectManager.h" +#include "../object/ObjectRepository.h" #include "../rct1.h" #include "../ride/ride.h" #include "../ride/ride_data.h" @@ -219,18 +221,17 @@ static void window_editor_object_set_page(rct_window *w, int page); static void window_editor_object_selection_set_pressed_tab(rct_window *w); static void window_editor_object_selection_select_default_objects(); static void window_editor_object_selection_select_required_objects(); -static int window_editor_object_selection_select_object(uint8 bh, int flags, rct_object_entry *entry); -static int get_object_from_object_selection(uint8 object_type, int y, uint8 *object_selection_flags, rct_object_entry **installed_entry); +static int window_editor_object_selection_select_object(uint8 bh, int flags, const rct_object_entry *entry); +static int get_object_from_object_selection(uint8 object_type, int y); static void window_editor_object_selection_manage_tracks(); static void editor_load_selected_objects(); -static bool filter_selected(uint8* objectFlags); -static bool filter_string(rct_object_entry *entry, rct_object_filters *filter); -static bool filter_source(rct_object_entry *entry); -static bool filter_chunks(rct_object_entry *entry, rct_object_filters *filter); +static bool filter_selected(uint8 objectFlags); +static bool filter_string(const ObjectRepositoryItem * item); +static bool filter_source(const ObjectRepositoryItem * item); +static bool filter_chunks(const ObjectRepositoryItem * item); static void filter_update_counts(); void reset_selected_object_count_and_size(); -void reset_required_object_flags(); static int sub_6AB211(); static rct_object_entry RequiredSelectedObjects[] = { @@ -281,18 +282,36 @@ enum { RIDE_SORT_RIDE }; +enum { + DDIX_FILTER_RCT2, + DDIX_FILTER_WW, + DDIX_FILTER_TT, + DDIX_FILTER_CUSTOM, + DDIX_FILTER_SEPERATOR, + DDIX_FILTER_SELECTED, + DDIX_FILTER_NONSELECTED, +}; + typedef struct list_item { + const ObjectRepositoryItem * repositoryItem; rct_object_entry *entry; rct_object_filters *filter; uint8 *flags; } list_item; +static rct_string_id get_ride_type_string_id(const ObjectRepositoryItem * item); + typedef int (*sortFunc)(const void *, const void *); static int _numListItems = 0; static list_item *_listItems = NULL; static int _listSortType = RIDE_SORT_TYPE; static bool _listSortDescending = false; +static void * _loadedObject = NULL; +static uint8 * _objectSelectionFlags = NULL; +static int _numSelectedObjectsForType[11]; +static int _numAvailableObjectsForType[11]; +static bool _maxObjectsWasHit; static void visible_list_dispose() { @@ -305,8 +324,8 @@ static int visible_list_sort_ride_name(const void *rawA, const void *rawB) list_item *a = (list_item*)rawA; list_item *b = (list_item*)rawB; - const char *nameA = object_get_name(a->entry); - const char *nameB = object_get_name(b->entry); + const char *nameA = a->repositoryItem->Name; + const char *nameB = b->repositoryItem->Name; return strcmp(nameA, nameB); } @@ -315,8 +334,8 @@ static int visible_list_sort_ride_type(const void *rawA, const void *rawB) list_item *a = (list_item*)rawA; list_item *b = (list_item*)rawB; - const char *rideTypeA = language_get_string(2 + a->filter->ride.ride_type); - const char *rideTypeB = language_get_string(2 + b->filter->ride.ride_type); + const char *rideTypeA = language_get_string(get_ride_type_string_id(a->repositoryItem)); + const char *rideTypeB = language_get_string(get_ride_type_string_id(b->repositoryItem)); int result = strcmp(rideTypeA, rideTypeB); if (result != 0) return result; @@ -326,32 +345,33 @@ static int visible_list_sort_ride_type(const void *rawA, const void *rawB) static void visible_list_refresh(rct_window *w) { - int numObjects = gInstalledObjectsCount; + int numObjects = (int)object_repository_get_items_count(); visible_list_dispose(); + w->selected_list_item = -1; _listItems = malloc(numObjects * sizeof(list_item)); _numListItems = 0; list_item *currentListItem = &_listItems[0]; - rct_object_entry *entry = gInstalledObjects; - uint8 *itemFlags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); + const ObjectRepositoryItem *items = object_repository_get_items(); for (int i = 0; i < numObjects; i++) { - rct_object_filters *filter = get_object_filter(i); - int type = entry->flags & 0x0F; - if (type == w->selected_tab && !(*itemFlags & OBJECT_SELECTION_FLAG_6) - && filter_source(entry) - && filter_string(entry, filter) - && filter_chunks(entry, filter) - && filter_selected(itemFlags)) { - currentListItem->entry = entry; - currentListItem->filter = filter; - currentListItem->flags = itemFlags; - currentListItem++; - _numListItems++; + uint8 selectionFlags = _objectSelectionFlags[i]; + const ObjectRepositoryItem * item = &items[i]; + uint8 objectType = item->ObjectEntry.flags & 0x0F; + if (objectType == w->selected_tab && !(selectionFlags & OBJECT_SELECTION_FLAG_6) && + filter_source(item) && + filter_string(item) && + filter_chunks(item) && + filter_selected(selectionFlags) + ) { + rct_object_filters * filter = calloc(1, sizeof(rct_object_filters)); + currentListItem->repositoryItem = item; + currentListItem->entry = (rct_object_entry *)&item->ObjectEntry; + currentListItem->filter = filter; + currentListItem->flags = &_objectSelectionFlags[i]; + currentListItem++; + _numListItems++; } - - entry = object_get_next(entry); - itemFlags++; } _listItems = realloc(_listItems, _numListItems * sizeof(list_item)); @@ -442,64 +462,27 @@ void window_editor_object_selection_open() * * rct2: 0x006ABCD1 */ -static void setup_track_manager_objects(){ +static void setup_track_manager_objects() +{ uint8 ride_list[128] = { 0 }; - uint8* selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry* installedObject = gInstalledObjects; - uint16 num_objects = 0; - for (int i = gInstalledObjectsCount; i > 0; --i){ - uint8 object_type = installedObject->flags & 0xF; - if (object_type == OBJECT_TYPE_RIDE){ - *selection_flags |= OBJECT_SELECTION_FLAG_6; + int numObjects = object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); + for (int i = 0; i < numObjects; i++) { + uint8 * selectionFlags = &_objectSelectionFlags[i]; + const ObjectRepositoryItem * item = &items[i]; + uint8 object_type = item->ObjectEntry.flags & 0xF; + if (object_type == OBJECT_TYPE_RIDE) { + *selectionFlags |= OBJECT_SELECTION_FLAG_6; - uint8* pos = (uint8*)installedObject; - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - // Skip size of chunk - pos += 4; - - // Skip required objects - pos += *pos * 16 + 1; - - // Skip theme objects - pos += *pos * 16 + 1; - - for (uint8 j = 0; j < 3; j++){ - uint8 ride_type = pos[j]; - if (ride_type == 0xFF) - continue; - - if (!ride_type_has_flag(ride_type, RIDE_TYPE_FLAG_HAS_TRACK)) - continue; - - if (pos[3] & (1 << 0)){ - *selection_flags &= ~OBJECT_SELECTION_FLAG_6; + for (uint8 j = 0; j < 3; j++) { + uint8 rideType = item->RideType[j]; + if (rideType != 0xFF && ride_type_has_flag(rideType, RIDE_TYPE_FLAG_HAS_TRACK)) { + *selectionFlags &= ~OBJECT_SELECTION_FLAG_6; + break; } - else if (ride_list[ride_type] & (1 << 0)){ - continue; - } - else{ - ride_list[ride_type] |= (1 << 0); - *selection_flags &= ~OBJECT_SELECTION_FLAG_6; - } - num_objects++; - break; } } - - installedObject = object_get_next(installedObject); - selection_flags++; } } @@ -507,54 +490,27 @@ static void setup_track_manager_objects(){ * * rct2: 0x006ABC1E */ -static void setup_track_designer_objects(){ - uint8* selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry* installedObject = gInstalledObjects; - uint16 num_objects = 0; +static void setup_track_designer_objects() +{ + int numObjects = object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); + for (int i = 0; i < numObjects; i++) { + uint8 * selectionFlags = &_objectSelectionFlags[i]; + const ObjectRepositoryItem * item = &items[i]; + uint8 objectType = item->ObjectEntry.flags & 0xF; + if (objectType == OBJECT_TYPE_RIDE){ + *selectionFlags |= OBJECT_SELECTION_FLAG_6; - for (int i = gInstalledObjectsCount; i > 0; --i){ - uint8 object_type = installedObject->flags & 0xF; - if (object_type == OBJECT_TYPE_RIDE){ - *selection_flags |= OBJECT_SELECTION_FLAG_6; - - uint8* pos = (uint8*)installedObject; - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - // Skip size of chunk - pos += 4; - - // Skip required objects - pos += *pos * 16 + 1; - - // Skip theme objects - pos += *pos * 16 + 1; - - for (uint8 j = 0; j < 3; j++){ - uint8 ride_type = pos[j]; - if (ride_type == 0xFF) - continue; - - if (!(RideData4[ride_type].flags & RIDE_TYPE_FLAG4_11)) - continue; - - *selection_flags &= ~OBJECT_SELECTION_FLAG_6; - num_objects++; - break; + for (uint8 j = 0; j < 3; j++) { + uint8 rideType = item->RideType[j]; + if (rideType != 0xFF) { + if (RideData4[rideType].flags & RIDE_TYPE_FLAG4_11) { + *selectionFlags &= ~OBJECT_SELECTION_FLAG_6; + break; + } + } } } - - installedObject = object_get_next(installedObject); - selection_flags++; } } @@ -562,8 +518,8 @@ static void setup_track_designer_objects(){ * * rct2: 0x006AA82B */ -static void setup_in_use_selection_flags(){ - +static void setup_in_use_selection_flags() +{ for (uint8 object_type = 0; object_type < 11; object_type++){ for (uint16 i = 0; i < object_entry_group_counts[object_type]; i++){ RCT2_ADDRESS(0x0098DA38, uint8*)[object_type][i] = 0; @@ -634,34 +590,32 @@ static void setup_in_use_selection_flags(){ } } while (map_element_iterator_next(&iter)); - for (uint8 ride_index = 0; ride_index < 0xFF; ride_index++){ + for (uint8 ride_index = 0; ride_index < 0xFF; ride_index++) { rct_ride* ride = get_ride(ride_index); - if (ride->type == RIDE_TYPE_NULL) - continue; - - uint8 type = ride->subtype; - RCT2_ADDRESS(0x0098DA38, uint8*)[OBJECT_TYPE_RIDE][type] |= (1 << 0); + if (ride->type != RIDE_TYPE_NULL) { + uint8 type = ride->subtype; + RCT2_ADDRESS(0x0098DA38, uint8*)[OBJECT_TYPE_RIDE][type] |= (1 << 0); + } } - uint8* selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry* installedObject = gInstalledObjects; + int numObjects = (int)object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); + for (int i = 0; i < numObjects; i++) { + uint8 *selectionFlags = &_objectSelectionFlags[i]; + const ObjectRepositoryItem * item = &items[i]; + *selectionFlags &= ~OBJECT_SELECTION_FLAG_IN_USE; - for (int i = gInstalledObjectsCount; i > 0; --i){ - *selection_flags &= ~OBJECT_SELECTION_FLAG_IN_USE; - - uint8 entry_type, entry_index; - if (find_object_in_entry_group(installedObject, &entry_type, &entry_index)){ - if (RCT2_ADDRESS(0x0098DA38, uint8*)[entry_type][entry_index] & (1 << 0)){ - *selection_flags |= + uint8 entryType, entryIndex; + if (find_object_in_entry_group(&item->ObjectEntry, &entryType, &entryIndex)) { + if (RCT2_ADDRESS(0x0098DA38, uint8*)[entryType][entryIndex] & (1 << 0)) { + *selectionFlags |= OBJECT_SELECTION_FLAG_IN_USE | OBJECT_SELECTION_FLAG_SELECTED; } - if (RCT2_ADDRESS(0x0098DA38, uint8*)[entry_type][entry_index] & (1 << 1)){ - *selection_flags |= OBJECT_SELECTION_FLAG_SELECTED; + if (RCT2_ADDRESS(0x0098DA38, uint8*)[entryType][entryIndex] & (1 << 1)) { + *selectionFlags |= OBJECT_SELECTION_FLAG_SELECTED; } } - installedObject = object_get_next(installedObject); - selection_flags++; } } @@ -669,51 +623,46 @@ static void setup_in_use_selection_flags(){ * * rct2: 0x006AB211 */ -static int sub_6AB211(){ - uint32 total_objects = gInstalledObjectsCount; - - RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*) = malloc(total_objects); - - if (RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*) == NULL){ +static int sub_6AB211() +{ + int numObjects = (int)object_repository_get_items_count(); + _objectSelectionFlags = (uint8*)calloc(numObjects, sizeof(uint8)); + if (_objectSelectionFlags == NULL){ log_error("Failed to allocate memory for object flag list."); return 0; } - memset(RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*), 0, total_objects); - for (uint8 object_type = 0; object_type < 11; object_type++){ - RCT2_ADDRESS(0x00F433F7, uint16)[object_type] = 0; - RCT2_ADDRESS(0x00F433E1, uint16)[object_type] = 0; + for (uint8 objectType = 0; objectType < 11; objectType++) { + _numSelectedObjectsForType[objectType] = 0; + _numAvailableObjectsForType[objectType] = 0; } - rct_object_entry* installedObject = gInstalledObjects; - - for (int i = gInstalledObjectsCount; i > 0; --i){ - uint8 object_type = installedObject->flags & 0xF; - RCT2_ADDRESS(0x00F433E1, uint16)[object_type]++; - - installedObject = object_get_next(installedObject); + const ObjectRepositoryItem * items = object_repository_get_items(); + for (int i = 0; i < numObjects; i++) { + uint8 objectType = items[i].ObjectEntry.flags & 0xF; + _numAvailableObjectsForType[objectType]++; } - if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER){ + if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) { setup_track_designer_objects(); } - if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER){ + if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) { setup_track_manager_objects(); } setup_in_use_selection_flags(); reset_selected_object_count_and_size(); - if (!(gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER))){ + if (!(gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER))) { window_editor_object_selection_select_required_objects(); // To prevent it breaking in scenario mode. - if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) + if (gScreenFlags & SCREEN_FLAGS_SCENARIO_EDITOR) { window_editor_object_selection_select_default_objects(); + } } - reset_required_object_flags(); reset_selected_object_count_and_size(); return 1; } @@ -724,14 +673,14 @@ static int sub_6AB211(){ */ static void editor_object_flags_free() { - SafeFree(RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*)); + SafeFree(_objectSelectionFlags); } /** * * rct2: 0x00685791 */ -void remove_selected_objects_from_research(rct_object_entry* installedObject){ +void remove_selected_objects_from_research(const rct_object_entry* installedObject){ uint8 entry_type, entry_index; if (!find_object_in_entry_group(installedObject, &entry_type, &entry_index)) return; @@ -751,18 +700,25 @@ void remove_selected_objects_from_research(rct_object_entry* installedObject){ * * rct2: 0x006ABB66 */ -void unload_unselected_objects(){ - uint8* selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry* installedObject = gInstalledObjects; +void unload_unselected_objects() +{ + int numItems = object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); - for (int i = gInstalledObjectsCount; i > 0; --i){ - if (!(*selection_flags & OBJECT_SELECTION_FLAG_SELECTED)){ - remove_selected_objects_from_research(installedObject); - object_unload_chunk(installedObject); + size_t numObjectsToUnload = 0; + rct_object_entry * objectsToUnload = (rct_object_entry *)malloc(numItems * sizeof(rct_object_entry)); + + for (int i = 0; i < numItems; i++) { + if (!(_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED)) { + const rct_object_entry * entry = &items[i].ObjectEntry; + + remove_selected_objects_from_research(entry); + objectsToUnload[numObjectsToUnload++] = *entry; } - selection_flags++; - installedObject = object_get_next(installedObject); } + + object_manager_unload_objects(objectsToUnload, numObjectsToUnload); + free(objectsToUnload); } /** @@ -776,10 +732,11 @@ static void window_editor_object_selection_close(rct_window *w) unload_unselected_objects(); editor_load_selected_objects(); - reset_loaded_objects(); - object_free_scenario_text(); editor_object_flags_free(); + object_delete(_loadedObject); + _loadedObject = NULL; + if (gScreenFlags & SCREEN_FLAGS_EDITOR) { research_populate_list_random(); } @@ -837,7 +794,6 @@ static void window_editor_object_selection_mouseup(rct_window *w, int widgetInde w->selected_list_item = -1; w->object_entry = (rct_object_entry *) 0xFFFFFFFF; w->scrolls[0].v_top = 0; - object_free_scenario_text(); window_invalidate(w); break; case WIDX_FILTER_RIDE_TAB_TRANSPORT: @@ -857,7 +813,6 @@ static void window_editor_object_selection_mouseup(rct_window *w, int widgetInde w->selected_list_item = -1; w->object_entry = (rct_object_entry *) 0xFFFFFFFF; w->scrolls[0].v_top = 0; - object_free_scenario_text(); window_invalidate(w); break; @@ -869,7 +824,6 @@ static void window_editor_object_selection_mouseup(rct_window *w, int widgetInde case WIDX_INSTALL_TRACK: if (w->selected_list_item != -1) { w->selected_list_item = -1; - object_free_scenario_text(); } window_invalidate(w); @@ -911,16 +865,7 @@ static void window_editor_object_selection_resize(rct_window *w) { window_set_resize(w, 600, 400, 1200, 1000); } -enum -{ - DDIX_FILTER_RCT2, - DDIX_FILTER_WW, - DDIX_FILTER_TT, - DDIX_FILTER_CUSTOM, - DDIX_FILTER_SEPERATOR, - DDIX_FILTER_SELECTED, - DDIX_FILTER_NONSELECTED, -}; + void window_editor_object_selection_mousedown(int widgetIndex, rct_window*w, rct_widget* widget) { int num_items; @@ -1018,10 +963,13 @@ static void window_editor_object_selection_scroll_mousedown(rct_window *w, int s // when windows attempt to draw objects that don't exist any more window_close_all_except_class(WC_EDITOR_OBJECT_SELECTION); - uint8 object_selection_flags; - rct_object_entry* installed_entry; - int selected_object = get_object_from_object_selection((w->selected_tab & 0xFF), y, &object_selection_flags, &installed_entry); - if (selected_object == -1 || (object_selection_flags & OBJECT_SELECTION_FLAG_6)) + int selected_object = get_object_from_object_selection((w->selected_tab & 0xFF), y); + if (selected_object == -1) + return; + + list_item * listItem = &_listItems[selected_object]; + uint8 object_selection_flags = *listItem->flags; + if (object_selection_flags & OBJECT_SELECTION_FLAG_6) return; window_invalidate(w); @@ -1030,7 +978,7 @@ static void window_editor_object_selection_scroll_mousedown(rct_window *w, int s if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) { - if (!window_editor_object_selection_select_object(0, 1, installed_entry)) + if (!window_editor_object_selection_select_object(0, 1, listItem->entry)) return; // Close any other open windows such as options/colour schemes to prevent a crash. @@ -1047,8 +995,8 @@ static void window_editor_object_selection_scroll_mousedown(rct_window *w, int s if (!(object_selection_flags & OBJECT_SELECTION_FLAG_SELECTED)) ebx = 7; - RCT2_GLOBAL(0xF43411, uint8) = 0; - if (!window_editor_object_selection_select_object(0, ebx, installed_entry)) { + _maxObjectsWasHit = false; + if (!window_editor_object_selection_select_object(0, ebx, listItem->entry)) { rct_string_id error_title = ebx & 1 ? STR_UNABLE_TO_SELECT_THIS_OBJECT : STR_UNABLE_TO_DE_SELECT_THIS_OBJECT; @@ -1063,10 +1011,9 @@ static void window_editor_object_selection_scroll_mousedown(rct_window *w, int s window_invalidate(w); } - if (!RCT2_GLOBAL(0xF43411, uint8) & 1) - return; - - window_error_open(STR_WARNING_TOO_MANY_OBJECTS_SELECTED, STR_NOT_ALL_OBJECTS_IN_THIS_SCENERY_GROUP_COULD_BE_SELECTED); + if (_maxObjectsWasHit) { + window_error_open(STR_WARNING_TOO_MANY_OBJECTS_SELECTED, STR_NOT_ALL_OBJECTS_IN_THIS_SCENERY_GROUP_COULD_BE_SELECTED); + } } /** @@ -1075,26 +1022,30 @@ static void window_editor_object_selection_scroll_mousedown(rct_window *w, int s */ static void window_editor_object_selection_scroll_mouseover(rct_window *w, int scrollIndex, int x, int y) { - rct_object_entry *installedEntry; - int selectedObject; - uint8 objectSelectionFlags; + int selectedObject = get_object_from_object_selection(w->selected_tab & 0xFF, y); + if (selectedObject != -1) { + list_item * listItem = &_listItems[selectedObject]; + uint8 objectSelectionFlags = *listItem->flags; + if (objectSelectionFlags & OBJECT_SELECTION_FLAG_6) { + selectedObject = -1; + } + } + if (selectedObject != w->selected_list_item) { + w->selected_list_item = selectedObject; - selectedObject = get_object_from_object_selection( - w->selected_tab & 0xFF, y, &objectSelectionFlags, &installedEntry - ); - if (objectSelectionFlags & OBJECT_SELECTION_FLAG_6) - selectedObject = -1; + object_delete(_loadedObject); + _loadedObject = NULL; - if (selectedObject == w->selected_list_item) - return; + list_item * listItem = &_listItems[selectedObject]; + if (selectedObject == -1) { + w->object_entry = NULL; + } else { + w->object_entry = listItem->entry; + _loadedObject = object_repository_load_object(listItem->entry); + } - w->selected_list_item = selectedObject; - w->object_entry = installedEntry; - object_free_scenario_text(); - if (selectedObject != -1) - object_get_scenario_text(installedEntry); - - window_invalidate(w); + window_invalidate(w); + } } /** @@ -1263,8 +1214,8 @@ static void window_editor_object_selection_paint(rct_window *w, rct_drawpixelinf rct_widget *widget; rct_object_entry *highlightedEntry; rct_string_id stringId; - uint8 *text, source; - char *datName, *name, *stringBuffer; + uint8 source; + char *stringBuffer; /*if (w->selected_tab == WINDOW_OBJECT_SELECTION_PAGE_RIDE_VEHICLES_ATTRACTIONS) { gfx_fill_rect_inset(dpi, @@ -1332,7 +1283,7 @@ static void window_editor_object_selection_paint(rct_window *w, rct_drawpixelinf x = w->x + 3; y = w->y + w->height - 13; - numSelected = RCT2_ADDRESS(0x00F433F7, uint16)[w->selected_tab]; + numSelected = _numSelectedObjectsForType[w->selected_tab]; totalSelectable = object_entry_group_counts[w->selected_tab]; if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) totalSelectable = 4; @@ -1342,28 +1293,6 @@ static void window_editor_object_selection_paint(rct_window *w, rct_drawpixelinf gfx_draw_string_left(dpi, 3164, gCommonFormatArgs, 0, x, y); } - rct_stex_entry* stex_entry = gStexTempChunk; - - /*gfx_fill_rect_inset(dpi, - w->x + window_editor_object_selection_widgets[WIDX_FILTER_STRING_BUTTON].left, - w->y + window_editor_object_selection_widgets[WIDX_FILTER_STRING_BUTTON].top, - w->x + window_editor_object_selection_widgets[WIDX_FILTER_STRING_BUTTON].right, - w->y + window_editor_object_selection_widgets[WIDX_FILTER_STRING_BUTTON].bottom, - w->colours[1], - 0x30 - ); - - set_format_arg(0, uint32, (uint32)&_filter_string); - gfx_draw_string_left_clipped( - dpi, - 1170, - gCommonFormatArgs, - w->colours[1], - w->x + window_editor_object_selection_widgets[WIDX_FILTER_STRING_BUTTON].left + 1, - w->y + window_editor_object_selection_widgets[WIDX_FILTER_STRING_BUTTON].top, - w->x + window_editor_object_selection_widgets[WIDX_FILTER_STRING_BUTTON].right - );*/ - // Draw sort button text widget = &w->widgets[WIDX_LIST_SORT_TYPE]; if (widget->type != WWT_EMPTY) { @@ -1376,63 +1305,40 @@ static void window_editor_object_selection_paint(rct_window *w, rct_drawpixelinf gfx_draw_string_left_clipped(dpi, STR_OBJECTS_SORT_RIDE, &stringId, w->colours[1], w->x + widget->left + 1, w->y + widget->top + 1, widget->right - widget->left); } - if (w->selected_list_item == -1 || stex_entry == NULL) + if (w->selected_list_item == -1 || _loadedObject == NULL) return; + list_item *listItem = &_listItems[w->selected_list_item]; + highlightedEntry = w->object_entry; type = highlightedEntry->flags & 0x0F; // Draw preview widget = &w->widgets[WIDX_PREVIEW]; - x = w->x + (widget->left + widget->right) / 2 + 1; - y = w->y + (widget->top + widget->bottom) / 2 + 1; - object_paint(type, stex_entry, dpi, x, y); + { + rct_drawpixelinfo clipDPI; + x = w->x + widget->left + 1; + y = w->y + widget->top + 1; + int width = widget->right - widget->left - 1; + int height = widget->bottom - widget->top - 1; + if (clip_drawpixelinfo(&clipDPI, dpi, x, y, width, height)) { + object_draw_preview(_loadedObject, &clipDPI, width, height); + } + } // Draw name of object x = w->x + (widget->left + widget->right) / 2 + 1; y = w->y + widget->bottom + 3; width = w->width - w->widgets[WIDX_LIST].right - 6; - // Skip object dat name - text = (uint8*)(highlightedEntry + 1); - datName = (char*)text; - do { - text++; - } while (*(text - 1) != 0); - text += 4; - name = (char*)text; - RCT2_GLOBAL(0x009BC677, uint8) = 14; - stringId = STR_PLACEHOLDER; stringBuffer = (char*)language_get_string(STR_PLACEHOLDER) + 1; - if (gScreenFlags & (SCREEN_FLAGS_TRACK_DESIGNER | SCREEN_FLAGS_TRACK_MANAGER)) { - // Skip name - do { - text++; - } while (*(text - 1) != 0); - text += 4; - text += *text * 16 + 1; - text += *text * 16 + 1; - - if (RCT2_GLOBAL(text, uint32) & 0x1000000) { - strcpy(stringBuffer, name); - } else { - int eax = *text; - if (*text == 0xFF) { - eax = *(text + 1); - if (*(text + 1) == 0xFF) - eax = *(text + 2); - } - format_string(stringBuffer, eax + 2, NULL); - } - } else { - strcpy(stringBuffer, name); - } + strcpy(stringBuffer, listItem->repositoryItem->Name); gfx_draw_string_centred_clipped(dpi, stringId, NULL, 0, x, y, width); // Draw description of object - stringId = object_desc(type, stex_entry); + strcpy(stringBuffer, object_get_description(_loadedObject)); if (stringId != STR_NONE) { x = w->x + w->widgets[WIDX_LIST].right + 4; y += 15; @@ -1457,16 +1363,9 @@ static void window_editor_object_selection_paint(rct_window *w, rct_drawpixelinf // if (w->selected_tab == WINDOW_OBJECT_SELECTION_PAGE_RIDE_VEHICLES_ATTRACTIONS) { y = w->y + w->height - 3 - 12 - 14 - 14; - - rct_ride_entry *rideType = (rct_ride_entry*)stex_entry; - for (int i = 0; i < 3; i++) { - if (rideType->ride_type[i] == 255) - continue; - - stringId = 2 + rideType->ride_type[i]; - gfx_draw_string_right(dpi, stringId, NULL, 2, w->x + w->width - 5, y); - y -= 11; - } + stringId = get_ride_type_string_id(listItem->repositoryItem); + gfx_draw_string_right(dpi, stringId, NULL, 2, w->x + w->width - 5, y); + y -= 11; } //stringId = highlightedEntry->checksum @@ -1474,7 +1373,7 @@ static void window_editor_object_selection_paint(rct_window *w, rct_drawpixelinf // Draw object dat name stringId = STR_PLACEHOLDER; - strcpy(stringBuffer, datName); + strcpy(stringBuffer, path_get_filename(listItem->repositoryItem->Path)); gfx_draw_string_right(dpi, stringId, NULL, 0, w->x + w->width - 5, w->y + w->height - 3 - 12); } @@ -1513,7 +1412,7 @@ static void window_editor_object_selection_scrollpaint(rct_window *w, rct_drawpi x = 2; gCurrentFontSpriteBase = colour == 14 ? -2 : -1; colour2 = w->colours[1] & 0x7F; - if (*listItem->flags & (OBJECT_SELECTION_FLAG_IN_USE | OBJECT_SELECTION_FLAG_REQUIRED | OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED)) + if (*listItem->flags & (OBJECT_SELECTION_FLAG_IN_USE | OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED)) colour2 |= 0x40; gfx_draw_string(dpi, (char*)CheckBoxMarkString, colour2, x, y); @@ -1534,13 +1433,14 @@ static void window_editor_object_selection_scrollpaint(rct_window *w, rct_drawpi if (ridePage) { // Draw ride type - strcpy(buffer, language_get_string(2 + listItem->filter->ride.ride_type)); + rct_string_id rideTypeStringId = get_ride_type_string_id(listItem->repositoryItem); + strcpy(buffer, language_get_string(rideTypeStringId)); gfx_draw_string(dpi, bufferWithColour, colour, x, y); x = w->widgets[WIDX_LIST_SORT_RIDE].left - w->widgets[WIDX_LIST].left; } // Draw text - strcpy(buffer, object_get_name(listItem->entry)); + strcpy(buffer, listItem->repositoryItem->Name); if (gScreenFlags & SCREEN_FLAGS_TRACK_MANAGER) { while (*buffer != 0 && *buffer != 9) buffer++; @@ -1562,7 +1462,6 @@ static void window_editor_object_set_page(rct_window *w, int page) w->selected_list_item = -1; w->object_entry = (rct_object_entry *)0xFFFFFFFF; w->scrolls[0].v_top = 0; - object_free_scenario_text(); if (page == WINDOW_OBJECT_SELECTION_PAGE_RIDE_VEHICLES_ATTRACTIONS) { _listSortType = RIDE_SORT_TYPE; @@ -1590,13 +1489,11 @@ static void window_editor_object_selection_set_pressed_tab(rct_window *w) */ static void window_editor_object_selection_select_default_objects() { - int i; - - if (RCT2_GLOBAL(0x00F433F7, uint16) != 0) - return; - - for (i = 0; i < countof(DefaultSelectedObjects); i++) - window_editor_object_selection_select_object(0, 7, &DefaultSelectedObjects[i]); + if (_numSelectedObjectsForType[0] == 0) { + for (int i = 0; i < countof(DefaultSelectedObjects); i++) { + window_editor_object_selection_select_object(0, 7, &DefaultSelectedObjects[i]); + } + } } /** @@ -1615,131 +1512,19 @@ static void window_editor_object_selection_select_required_objects() * * rct2: 0x006AA770 */ -void reset_selected_object_count_and_size(){ - for (uint8 object_type = 0; object_type < 11; object_type++){ - RCT2_ADDRESS(0x00F433F7, uint16)[object_type] = 0; +void reset_selected_object_count_and_size() +{ + for (uint8 objectType = 0; objectType < 11; objectType++) { + _numSelectedObjectsForType[objectType] = 0; } - uint32 total_object_size = 0; - - uint8* selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry* installedObject = gInstalledObjects; - - for (int i = gInstalledObjectsCount; i > 0; --i){ - uint8 object_type = installedObject->flags & 0xF; - - if (*selection_flags & OBJECT_SELECTION_FLAG_SELECTED){ - uint8* pos = (uint8*)installedObject; - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - uint32 size_of_chunk = *((uint32*)pos); - RCT2_ADDRESS(0x00F433F7, uint16)[object_type]++; - total_object_size += size_of_chunk; + int numObjects = (int)object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); + for (int i = 0; i < numObjects; i++) { + uint8 objectType = items[i].ObjectEntry.flags & 0xF; + if (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED) { + _numSelectedObjectsForType[objectType]++; } - selection_flags++; - installedObject = object_get_next(installedObject); - } - - RCT2_GLOBAL(RCT2_ADDRESS_SELECTED_OBJECTS_FILE_SIZE, uint32) = total_object_size; -} - -/** - * - * rct2: 0x006AB863 - */ -void set_required_object_flags(rct_object_entry* required_object){ - uint8* selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry* installedObject = gInstalledObjects; - - for (int i = gInstalledObjectsCount; i > 0; --i){ - if (object_entry_compare(required_object, installedObject)){ - *selection_flags |= OBJECT_SELECTION_FLAG_REQUIRED; - - uint8* pos = (uint8*)installedObject; - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - // Skip size of chunk - pos += 4; - - uint8 no_required_objects = *pos++; - - required_object = (rct_object_entry*)pos; - for (; no_required_objects > 0; no_required_objects--){ - set_required_object_flags(required_object); - required_object++; - } - return; - } - - selection_flags++; - installedObject = object_get_next(installedObject); - } -} - -/** - * - * rct2: 0x006AB923 - */ -void reset_required_object_flags(){ - uint8* selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - for (int i = gInstalledObjectsCount; i > 0; --i){ - *selection_flags &= ~OBJECT_SELECTION_FLAG_REQUIRED; - selection_flags++; - } - - selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry* installedObject = gInstalledObjects; - - for (int i = gInstalledObjectsCount; i > 0; --i){ - if (*selection_flags & OBJECT_SELECTION_FLAG_SELECTED){ - uint8* pos = (uint8*)installedObject; - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - // Skip size of chunk - pos += 4; - - uint8 no_required_objects = *pos++; - - rct_object_entry* required_object = (rct_object_entry*)pos; - for (; no_required_objects > 0; no_required_objects--){ - set_required_object_flags(required_object); - required_object++; - } - - } - - selection_flags++; - installedObject = object_get_next(installedObject); } } @@ -1759,193 +1544,92 @@ void set_object_selection_error(uint8 is_master_object, rct_string_id error_msg) * * rct2: 0x006AB54F */ -static int window_editor_object_selection_select_object(uint8 bh, int flags, rct_object_entry *entry) +static int window_editor_object_selection_select_object(uint8 bh, int flags, const rct_object_entry *entry) { - uint8* selection_flags; - //if (bh == 0){ - // // Unsure what this does?? - // uint16 total_objects = 0; - // for (uint8 i = 0; i < 11; ++i){ - // total_objects += RCT2_ADDRESS(0x00F433E1, uint16)[i]; - // } - - // selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - // for (; total_objects != 0; total_objects--, selection_flags++){ - // uint8 select_flag = *selection_flags & ~OBJECT_SELECTION_FLAG_2; - // if (select_flag & OBJECT_SELECTION_FLAG_SELECTED){ - // select_flag |= OBJECT_SELECTION_FLAG_2; - // } - // } - //} - - selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - // There was previously a check to make sure the object list had an item - rct_object_entry* installedObject = gInstalledObjects; - - uint8 not_found = 1; - for (int i = gInstalledObjectsCount; i > 0; --i){ - if (object_entry_compare(entry, installedObject)){ - not_found = 0; - break; - } - - installedObject = object_get_next(installedObject); - selection_flags++; - } - if (not_found){ + int numObjects = (int)object_repository_get_items_count(); + const ObjectRepositoryItem * item = object_repository_find_object_by_entry(entry); + if (item == NULL) { set_object_selection_error(bh, 3169); return 0; } - if (!(flags & 1)){ - if (!(*selection_flags & OBJECT_SELECTION_FLAG_SELECTED)) - { - if (bh == 0){ - reset_required_object_flags(); - } + // Get repository item index + int index = -1; + const ObjectRepositoryItem * items = object_repository_get_items(); + for (int i = 0; i < numObjects; i++) { + if (&items[i] == item) { + index = i; + } + } + + uint8 * selectionFlags = &_objectSelectionFlags[index]; + if (!(flags & 1)) { + if (!(*selectionFlags & OBJECT_SELECTION_FLAG_SELECTED)) { return 1; } - else if (*selection_flags & OBJECT_SELECTION_FLAG_IN_USE){ + else if (*selectionFlags & OBJECT_SELECTION_FLAG_IN_USE) { set_object_selection_error(bh, 3173); return 0; } - else if (*selection_flags & OBJECT_SELECTION_FLAG_REQUIRED){ - set_object_selection_error(bh, 3174); - return 0; - } - else if (*selection_flags & OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED){ + else if (*selectionFlags & OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED) { set_object_selection_error(bh, 3175); return 0; } - uint8* pos = (uint8*)installedObject; - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - uint32 size_of_chunk = *((uint32*)pos); - // Skip size of chunk - pos += 4; - - // Skip required objects - pos += *pos * 16 + 1; - - uint8 no_theme_objects = *pos++; - - if (no_theme_objects != 0 && flags&(1 << 2)){ - rct_object_entry* theme_object = (rct_object_entry*)pos; - for (; no_theme_objects > 0; no_theme_objects--){ - window_editor_object_selection_select_object(++bh, flags, theme_object); - theme_object++; + if (flags & (1 << 2)) { + for (int j = 0; j < item->NumThemeObjects; j++) { + window_editor_object_selection_select_object(++bh, flags, &item->ThemeObjects[j]); } } - RCT2_GLOBAL(RCT2_ADDRESS_SELECTED_OBJECTS_FILE_SIZE, uint32) -= size_of_chunk; - uint8 object_type = installedObject->flags & 0xF; - RCT2_ADDRESS(0x00F433F7, uint16)[object_type]--; - *selection_flags &= ~OBJECT_SELECTION_FLAG_SELECTED; - if (bh == 0){ - reset_required_object_flags(); - } + uint8 objectType = item->ObjectEntry.flags & 0xF; + _numSelectedObjectsForType[objectType]--; + *selectionFlags &= ~OBJECT_SELECTION_FLAG_SELECTED; return 1; - } - else{ - if (bh == 0){ - if (flags & (1 << 3)){ - *selection_flags |= OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED; + } else { + if (bh == 0) { + if (flags & (1 << 3)) { + *selectionFlags |= OBJECT_SELECTION_FLAG_ALWAYS_REQUIRED; } } - if (*selection_flags & OBJECT_SELECTION_FLAG_SELECTED){ - if (bh == 0){ - reset_required_object_flags(); - } + if (*selectionFlags & OBJECT_SELECTION_FLAG_SELECTED) { return 1; } - uint8 object_type = installedObject->flags & 0xF; - uint16 no_objects = object_entry_group_counts[object_type]; - if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER){ - no_objects = 4; + uint8 objectType = item->ObjectEntry.flags & 0xF; + uint16 maxObjects = object_entry_group_counts[objectType]; + if (gScreenFlags & SCREEN_FLAGS_TRACK_DESIGNER) { + maxObjects = 4; } - if (no_objects <= RCT2_ADDRESS(0x00F433F7, uint16)[object_type]){ + if (maxObjects <= _numSelectedObjectsForType[objectType]) { set_object_selection_error(bh, 3171); return 0; } - uint8* pos = (uint8*)installedObject; - // Skip sizeof(rct_object_entry) - pos += 16; - - // Skip filename - while (*pos++); - - // Skip no of images - pos += 4; - - // Skip name - while (*pos++); - - uint32 size_of_chunk = *((uint32*)pos); - // Skip size of chunk - pos += 4; - - uint8 num_required_objects = *pos++; - rct_object_entry* required_objects = (rct_object_entry*)pos; - for (; num_required_objects != 0; num_required_objects--){ - if (!window_editor_object_selection_select_object(++bh, flags, required_objects)){ - if (bh != 0){ - reset_selected_object_count_and_size(); - } - return 0; - } - required_objects++; - } - pos = (uint8*)required_objects; - - uint8 num_theme_objects = *pos++; - rct_object_entry* theme_object = (rct_object_entry*)pos; - for (; num_theme_objects != 0; num_theme_objects--){ - if (flags & (1 << 2)){ - if (!window_editor_object_selection_select_object(++bh, flags, theme_object)){ - RCT2_GLOBAL(0x00F43411, uint8) |= 1; + if (objectType == OBJECT_TYPE_SCENERY_SETS && (flags & (1 << 2))) { + for (uint16 j = 0; j < item->NumThemeObjects; j++) { + if (!window_editor_object_selection_select_object(++bh, flags, &item->ThemeObjects[j])) { + _maxObjectsWasHit = true; } } - theme_object++; } - if (bh != 0 && !(flags&(1 << 1))){ - object_create_identifier_name((char*)0x009BC95A, installedObject); + if (bh != 0 && !(flags & (1 << 1))) { + object_create_identifier_name((char*)0x009BC95A, &item->ObjectEntry); set_format_arg(0, uint32, 0x009BC95A); set_object_selection_error(bh, 3172); return 0; } - if (RCT2_GLOBAL(RCT2_ADDRESS_SELECTED_OBJECTS_FILE_SIZE, uint32) + size_of_chunk > 0x40000){ - set_object_selection_error(bh, 3170); - return 0; - } - - if (no_objects <= RCT2_ADDRESS(0x00F433F7, uint16)[object_type]){ + if (maxObjects <= _numSelectedObjectsForType[objectType]) { set_object_selection_error(bh, 3171); return 0; } - RCT2_GLOBAL(RCT2_ADDRESS_SELECTED_OBJECTS_FILE_SIZE, uint32) += size_of_chunk; - RCT2_ADDRESS(0x00F433F7, uint16)[object_type]++; + _numSelectedObjectsForType[objectType]++; - *selection_flags |= OBJECT_SELECTION_FLAG_SELECTED; - if (bh == 0){ - reset_required_object_flags(); - } + *selectionFlags |= OBJECT_SELECTION_FLAG_SELECTED; return 1; } } @@ -1958,15 +1642,12 @@ static int window_editor_object_selection_select_object(uint8 bh, int flags, rct * * rct2: 0x006AA703 */ -static int get_object_from_object_selection(uint8 object_type, int y, uint8 *object_selection_flags, rct_object_entry **installed_entry) +static int get_object_from_object_selection(uint8 object_type, int y) { int listItemIndex = y / 12; if (listItemIndex < 0 || listItemIndex >= _numListItems) return -1; - list_item *listItem = &_listItems[listItemIndex]; - *object_selection_flags = *listItem->flags; - *installed_entry = listItem->entry; return listItemIndex; } @@ -2000,8 +1681,6 @@ static void window_editor_object_selection_manage_tracks() int entry_index = 0; for (; ((int)object_entry_groups[0].chunks[entry_index]) == -1; ++entry_index); - RCT2_GLOBAL(0xF44157, uint8) = entry_index; - rct_ride_entry* ride_entry = get_ride_entry(entry_index); uint8* ride_type_array = &ride_entry->ride_type[0]; @@ -2020,38 +1699,32 @@ static void window_editor_object_selection_manage_tracks() */ static void editor_load_selected_objects() { - uint8 *selection_flags = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - rct_object_entry *installed_entry = gInstalledObjects; - - if (gInstalledObjectsCount == 0) - return; - - for (int i = gInstalledObjectsCount; i != 0; i--, selection_flags++) { - if (*selection_flags & OBJECT_SELECTION_FLAG_SELECTED) { - uint8 entry_index, entry_type; - if (!find_object_in_entry_group(installed_entry, &entry_type, &entry_index)){ - int chunk_size; - if (!object_load_chunk(-1, installed_entry, &chunk_size)) { - log_error("Failed to load entry %.8s", installed_entry->name); + int numItems = (int)object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); + for (int i = 0; i < numItems; i++) { + if (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED) { + const ObjectRepositoryItem * item = &items[i]; + const rct_object_entry * entry = &item->ObjectEntry; + void * loadedObject = object_manager_get_loaded_object(entry); + if (loadedObject == NULL) { + loadedObject = object_manager_load_object(entry); + if (loadedObject == NULL) { + log_error("Failed to load entry %.8s", entry->name); } - - // For in game use (cheat) - if (!(gScreenFlags & SCREEN_FLAGS_EDITOR)) { - // Defaults selected items to researched. - if (find_object_in_entry_group(installed_entry, &entry_type, &entry_index)) { - if (entry_type == OBJECT_TYPE_RIDE) { - rct_ride_entry* rideType = get_ride_entry(entry_index); - research_insert(1, 0x10000 | (rideType->ride_type[0] << 8) | entry_index, rideType->category[0]); - } - else if (entry_type == OBJECT_TYPE_SCENERY_SETS) { - research_insert(1, entry_index, RESEARCH_CATEGORY_SCENERYSET); - } + else if (!(gScreenFlags & SCREEN_FLAGS_EDITOR)) { + // Defaults selected items to researched (if in-game) + uint8 objectType = entry->flags & 0x0F; + uint8 entryIndex = object_manager_get_loaded_object_entry_index(loadedObject); + if (objectType == OBJECT_TYPE_RIDE) { + rct_ride_entry *rideType = get_ride_entry(entryIndex); + research_insert(1, 0x10000 | (rideType->ride_type[0] << 8) | entryIndex, rideType->category[0]); + } + else if (objectType == OBJECT_TYPE_SCENERY_SETS) { + research_insert(1, entryIndex, RESEARCH_CATEGORY_SCENERYSET); } } } } - - installed_entry = object_get_next(installed_entry); } } @@ -2100,14 +1773,15 @@ static void window_editor_object_selection_textinput(rct_window *w, int widgetIn window_invalidate(w); } -static bool filter_selected(uint8* objectFlag) { +static bool filter_selected(uint8 objectFlag) +{ if (_FILTER_SELECTED == _FILTER_NONSELECTED) { return true; } - if (_FILTER_SELECTED && *objectFlag & OBJECT_SELECTION_FLAG_SELECTED) { + if (_FILTER_SELECTED && objectFlag & OBJECT_SELECTION_FLAG_SELECTED) { return true; } - else if (_FILTER_NONSELECTED && !(*objectFlag & OBJECT_SELECTION_FLAG_SELECTED)) { + else if (_FILTER_NONSELECTED && !(objectFlag & OBJECT_SELECTION_FLAG_SELECTED)) { return true; } else { @@ -2115,26 +1789,26 @@ static bool filter_selected(uint8* objectFlag) { } } -static bool filter_string(rct_object_entry *entry, rct_object_filters *filter) +static bool filter_string(const ObjectRepositoryItem * item) { // Nothing to search for if (_filter_string[0] == '\0') return true; // Object doesn't have a name - char *name = object_get_name(entry); - if (name[0] == '\0') + utf8 *name = item->Name; + if (name == NULL || name[0] == '\0') return false; // Get ride type - const char *ride_type = language_get_string(2 + filter->ride.ride_type); + const char *rideTypeName = language_get_string(get_ride_type_string_id(item)); // Get object name (ride/vehicle for rides) and type name (rides only) char name_lower[MAX_PATH]; char type_lower[MAX_PATH]; char filter_lower[sizeof(_filter_string)]; safe_strcpy(name_lower, name, MAX_PATH); - safe_strcpy(type_lower, ride_type, MAX_PATH); + safe_strcpy(type_lower, rideTypeName, MAX_PATH); safe_strcpy(filter_lower, _filter_string, sizeof(_filter_string)); // Make use of lowercase characters only @@ -2145,30 +1819,37 @@ static bool filter_string(rct_object_entry *entry, rct_object_filters *filter) for (int i = 0; filter_lower[i] != '\0'; i++) filter_lower[i] = (char)tolower(filter_lower[i]); - return strstr(name_lower, filter_lower) != NULL || (((entry->flags & 0x0F) == OBJECT_TYPE_RIDE) && strstr(type_lower, filter_lower) != NULL); + return strstr(name_lower, filter_lower) != NULL || (((item->ObjectEntry.flags & 0x0F) == OBJECT_TYPE_RIDE) && strstr(type_lower, filter_lower) != NULL); } -static bool filter_source(rct_object_entry *entry) +static bool filter_source(const ObjectRepositoryItem * item) { if (_FILTER_ALL) return true; - uint8 source = (entry->flags & 0xF0) >> 4; + uint8 source = (item->ObjectEntry.flags & 0xF0) >> 4; return (_FILTER_RCT2 && source == 8) || (_FILTER_WW && source == 1) || (_FILTER_TT && source == 2) || (_FILTER_CUSTOM && source != 8 && source != 1 && source != 2); } -static bool filter_chunks(rct_object_entry *entry, rct_object_filters *filter) +static bool filter_chunks(const ObjectRepositoryItem * item) { - switch (entry->flags & 0x0F) { + switch (item->ObjectEntry.flags & 0x0F) { case OBJECT_TYPE_RIDE: if(!gConfigInterface.select_by_track_type) { - if (_filter_flags & (1 << (filter->ride.category[0] + 5))) + if (_filter_flags & (1 << (item->RideCategory[0] + 5))) return true; - if (_filter_flags & (1 << (filter->ride.category[1] + 5))) + if (_filter_flags & (1 << (item->RideCategory[1] + 5))) return true; } else { - if (_filter_flags & (1 << (gRideCategories[filter->ride.ride_type] + 5))) + uint8 rideType = 0; + for (int i = 0; i < 3; i++) { + if (item->RideType[i] != 255) { + rideType = item->RideType[i]; + break; + } + } + if (_filter_flags & (1 << (gRideCategories[rideType] + 5))) return true; } return false; @@ -2179,24 +1860,51 @@ static bool filter_chunks(rct_object_entry *entry, rct_object_filters *filter) static void filter_update_counts() { if (!_FILTER_ALL || strlen(_filter_string) > 0) { - rct_object_entry *installed_entry = gInstalledObjects; - rct_object_filters *filter; - uint8 *objectFlag = RCT2_GLOBAL(RCT2_ADDRESS_EDITOR_OBJECT_FLAGS_LIST, uint8*); - uint8 type; + uint8 *selectionFlags = _objectSelectionFlags; for (int i = 0; i < 11; i++) { _filter_object_counts[i] = 0; } - for (uint32 i = 0; i < gInstalledObjectsCount; i++) { - filter = get_object_filter(i); - type = installed_entry->flags & 0xF; - if (filter_source(installed_entry) - && filter_string(installed_entry, filter) - && filter_chunks(installed_entry, filter) - && filter_selected(objectFlag)) { - _filter_object_counts[type]++; + + size_t numObjects = object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); + for (size_t i = 0; i < numObjects; i++) { + const ObjectRepositoryItem * item = &items[i]; + if (filter_source(item) && + filter_string(item) && + filter_chunks(item) && + filter_selected(*selectionFlags) + ) { + uint8 objectType = item->ObjectEntry.flags & 0xF; + _filter_object_counts[objectType]++; } - installed_entry = object_get_next(installed_entry); - objectFlag++; + selectionFlags++; } } } + +static rct_string_id get_ride_type_string_id(const ObjectRepositoryItem * item) +{ + rct_string_id result = STR_NONE; + for (int i = 0; i < 3; i++) { + uint8 rideType = item->RideType[i]; + if (rideType != 255) { + result = 2 + rideType; + break; + } + } + return result; +} + +bool editor_check_object_group_at_least_one_selected(int checkObjectType) +{ + int numObjects = (int)object_repository_get_items_count(); + const ObjectRepositoryItem * items = object_repository_get_items(); + + for (int i = 0; i < numObjects; i++) { + uint8 objectType = items[i].ObjectEntry.flags & 0x0F; + if (checkObjectType == objectType && (_objectSelectionFlags[i] & OBJECT_SELECTION_FLAG_SELECTED)) { + return true; + } + } + return false; +} diff --git a/src/windows/install_track.c b/src/windows/install_track.c index f24e16f298..8d5da61a57 100644 --- a/src/windows/install_track.c +++ b/src/windows/install_track.c @@ -24,6 +24,7 @@ #include "../ride/ride.h" #include "../ride/track.h" #include "../ride/track_design.h" +#include "../object/ObjectManager.h" #include "../sprites.h" #include "../util/util.h" #include "error.h" @@ -108,12 +109,12 @@ void window_install_track_open(const utf8 *path) return; } - object_unload_all(); + object_manager_unload_all_objects(); if (_trackDesign->type == RIDE_TYPE_NULL){ log_error("Failed to load track (ride type null): %s", path); return; } - if (!object_load_chunk(0, &_trackDesign->vehicle_object, NULL)){ + if (object_manager_load_object(&_trackDesign->vehicle_object) == NULL) { log_error("Failed to load track (vehicle load fail): %s", path); return; } diff --git a/src/windows/options.c b/src/windows/options.c index 12b4e2e718..6371f05282 100644 --- a/src/windows/options.c +++ b/src/windows/options.c @@ -1536,10 +1536,7 @@ static void window_options_invalidate(rct_window *w) w->disabled_widgets |= (1ULL << WIDX_REAL_NAME_CHECKBOX); // save plugin data checkbox: visible or not - if (gNumInstalledCustomObjects == 0) - window_options_misc_widgets[WIDX_SAVE_PLUGIN_DATA_CHECKBOX].type = WWT_EMPTY; - else - window_options_misc_widgets[WIDX_SAVE_PLUGIN_DATA_CHECKBOX].type = WWT_CHECKBOX; + window_options_misc_widgets[WIDX_SAVE_PLUGIN_DATA_CHECKBOX].type = WWT_CHECKBOX; widget_set_checkbox_value(w, WIDX_REAL_NAME_CHECKBOX, gParkFlags & PARK_FLAGS_SHOW_REAL_GUEST_NAMES); widget_set_checkbox_value(w, WIDX_SAVE_PLUGIN_DATA_CHECKBOX, gConfigGeneral.save_plugin_data); diff --git a/src/windows/ride.c b/src/windows/ride.c index 71e11a1a49..d7512c97e9 100644 --- a/src/windows/ride.c +++ b/src/windows/ride.c @@ -26,6 +26,8 @@ #include "../interface/window.h" #include "../localisation/date.h" #include "../localisation/localisation.h" +#include "../object/ObjectManager.h" +#include "../object/ObjectRepository.h" #include "../peep/staff.h" #include "../ride/ride.h" #include "../ride/ride_data.h" @@ -2590,7 +2592,6 @@ static void window_ride_vehicle_paint(rct_window *w, rct_drawpixelinfo *dpi) { rct_ride *ride; rct_ride_entry *rideEntry; - rct_string_id stringId; int x, y; sint16 factor; @@ -2608,8 +2609,14 @@ static void window_ride_vehicle_paint(rct_window *w, rct_drawpixelinfo *dpi) y += 5; // Capacity - stringId = rideEntry->description + 1; - gfx_draw_string_left(dpi, STR_CAPACITY, &stringId, 0, x, y); + void * loadedObject = object_manager_get_loaded_object_by_index(ride->subtype); + if (loadedObject != NULL) + { + const utf8 * capacity = object_get_capacity(loadedObject); + set_format_arg(0, rct_string_id, STR_STRING); + set_format_arg(2, utf8 *, capacity); + gfx_draw_string_left(dpi, STR_CAPACITY, gCommonFormatArgs, 0, x, y); + } y += 15; if ((!(rideEntry->flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE) || rideTypeShouldLoseSeparateFlag(rideEntry)) && var_496(w) > 1) { diff --git a/src/windows/scenery.c b/src/windows/scenery.c index dcc3b93c81..8e12837f8d 100644 --- a/src/windows/scenery.c +++ b/src/windows/scenery.c @@ -187,7 +187,7 @@ void window_scenery_update_scroll(rct_window *w); */ void init_scenery_entry(rct_scenery_entry *sceneryEntry, int index, uint8 sceneryTabId) { - if (gResearchedSceneryItems[index >> 5] & (1UL << (index & 0x1F))) { + if (scenery_is_invented(index)) { if (sceneryTabId != 0xFF) { for (int i = 0; i < SCENERY_ENTRIES_BY_TAB; i++) { if (window_scenery_tab_entries[sceneryTabId][i] == -1) @@ -199,7 +199,7 @@ void init_scenery_entry(rct_scenery_entry *sceneryEntry, int index, uint8 scener } } - for (int i = 0; i < 0x13; i++) { + for (int i = 0; i < 19; i++) { int counter = 0; while (window_scenery_tab_entries[i][counter] != -1) @@ -213,10 +213,10 @@ void init_scenery_entry(rct_scenery_entry *sceneryEntry, int index, uint8 scener } for (int i = 0; i < SCENERY_ENTRIES_BY_TAB; i++) { - if (window_scenery_tab_entries[0x13][i] == -1) + if (window_scenery_tab_entries[19][i] == -1) { - window_scenery_tab_entries[0x13][i] = index; - window_scenery_tab_entries[0x13][i + 1] = -1; + window_scenery_tab_entries[19][i] = index; + window_scenery_tab_entries[19][i + 1] = -1; break; } } @@ -229,24 +229,21 @@ void init_scenery_entry(rct_scenery_entry *sceneryEntry, int index, uint8 scener */ void init_scenery() { - bool enabledScenerySets[0x14] = { false }; + bool enabledScenerySets[20] = { false }; - for (int scenerySetIndex = 0; scenerySetIndex < 0x14; scenerySetIndex++) { + for (int scenerySetIndex = 0; scenerySetIndex < 20; scenerySetIndex++) { window_scenery_tab_entries[scenerySetIndex][0] = -1; - if (scenerySetIndex == 0x13) + if (scenerySetIndex == 19) continue; rct_scenery_set_entry* scenerySetEntry = get_scenery_group_entry(scenerySetIndex); - if ((uint32)scenerySetEntry == 0xFFFFFFFF) + if (scenerySetEntry == (rct_scenery_set_entry *)-1) continue; int sceneryTabEntryCount = 0; - for (int i = 0; i < scenerySetEntry->entry_count; i++) { uint16 sceneryEntryId = scenerySetEntry->scenery_entries[i]; - uint32 ecx = gResearchedSceneryItems[sceneryEntryId >> 5]; - uint32 edx = 1u << (sceneryEntryId & 0x1F); - if (ecx & edx) { + if (scenery_is_invented(sceneryEntryId)) { window_scenery_tab_entries[scenerySetIndex][sceneryTabEntryCount] = sceneryEntryId; window_scenery_tab_entries[scenerySetIndex][++sceneryTabEntryCount] = -1; } else { @@ -257,7 +254,7 @@ void init_scenery() // small scenery for (uint16 sceneryId = 0; sceneryId < 0xFC; sceneryId++) { - if ((uint32)get_small_scenery_entry(sceneryId) == 0xFFFFFFFF) + if (get_small_scenery_entry(sceneryId) == (rct_scenery_entry *)-1) continue; rct_scenery_entry* sceneryEntry = get_small_scenery_entry(sceneryId); @@ -268,7 +265,7 @@ void init_scenery() for (int sceneryId = 0x300; sceneryId < 0x380; sceneryId++) { int largeSceneryIndex = sceneryId - 0x300; - if ((uint32)get_large_scenery_entry(largeSceneryIndex) == 0xFFFFFFFF) + if (get_large_scenery_entry(largeSceneryIndex) == (rct_scenery_entry *)-1) continue; rct_scenery_entry* sceneryEntry = get_large_scenery_entry(largeSceneryIndex); @@ -279,7 +276,7 @@ void init_scenery() for (int sceneryId = 0x200; sceneryId < 0x280; sceneryId++) { int wallSceneryIndex = sceneryId - 0x200; - if ((uint32)get_wall_entry(wallSceneryIndex) == 0xFFFFFFFF) + if (get_wall_entry(wallSceneryIndex) == (rct_scenery_entry *)-1) continue; rct_scenery_entry* sceneryEntry = get_wall_entry(wallSceneryIndex); @@ -290,7 +287,7 @@ void init_scenery() for (int sceneryId = 0x400; sceneryId < 0x420; sceneryId++) { int bannerIndex = sceneryId - 0x400; - if ((uint32)get_banner_entry(bannerIndex) == 0xFFFFFFFF) + if (get_banner_entry(bannerIndex) == (rct_scenery_entry *)-1) continue; rct_scenery_entry* sceneryEntry = get_banner_entry(bannerIndex); @@ -301,7 +298,7 @@ void init_scenery() for (int sceneryId = 0x100; sceneryId < 0x10F; sceneryId++) { int pathBitIndex = sceneryId - 0x100; - if ((uint32)get_footpath_item_entry(pathBitIndex) == 0xFFFFFFFF) + if (get_footpath_item_entry(pathBitIndex) == (rct_scenery_entry *)-1) continue; rct_scenery_entry* sceneryEntry = get_footpath_item_entry(pathBitIndex); @@ -317,7 +314,7 @@ void init_scenery() for (int scenerySetId = 0; scenerySetId < 19; scenerySetId++) { rct_scenery_set_entry* sceneryEntry = get_scenery_group_entry(scenerySetId); - if ((uint32)sceneryEntry == 0xFFFFFFFF) + if (sceneryEntry == (rct_scenery_set_entry *)-1) continue; tabIndexes[usedValues] = scenerySetId; @@ -986,7 +983,7 @@ void window_scenery_invalidate(rct_window *w) } else if (tabSelectedSceneryId < 0x100) { sceneryEntry = get_small_scenery_entry(tabSelectedSceneryId); - if (sceneryEntry->small_scenery.flags & (SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR | SMALL_SCENERY_FLAG10)) { + if (sceneryEntry->small_scenery.flags & (SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR | SMALL_SCENERY_FLAG_HAS_GLASS)) { window_scenery_widgets[WIDX_SCENERY_PRIMARY_COLOUR_BUTTON].type = WWT_COLOURBTN; if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) @@ -1210,7 +1207,7 @@ void window_scenery_scrollpaint(rct_window *w, rct_drawpixelinfo *dpi, int scrol gfx_draw_sprite(&clipdpi, imageId, 0x20, spriteTop, w->colours[1]); } - if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG8) { + if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_ANIMATED_FG) { imageId = (sceneryEntry->image + gWindowSceneryRotation) + 4; gfx_draw_sprite(&clipdpi, imageId, 0x20, spriteTop, w->colours[1]); } diff --git a/src/windows/top_toolbar.c b/src/windows/top_toolbar.c index 7eaf2913b0..ebcc082b6b 100644 --- a/src/windows/top_toolbar.c +++ b/src/windows/top_toolbar.c @@ -910,7 +910,7 @@ static void repaint_scenery_tool_down(sint16 x, sint16 y, sint16 widgetIndex){ // If can't repaint if (!(scenery_entry->small_scenery.flags & (SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR | - SMALL_SCENERY_FLAG10))) + SMALL_SCENERY_FLAG_HAS_GLASS))) return; gGameCommandErrorTitle = STR_CANT_REPAINT_THIS; diff --git a/src/world/scenery.h b/src/world/scenery.h index 0f46018a55..6e95c654a8 100644 --- a/src/world/scenery.h +++ b/src/world/scenery.h @@ -28,13 +28,15 @@ typedef struct rct_small_scenery_entry { uint8 tool_id; // 0x0B sint16 price; // 0x0C sint16 removal_price; // 0x0E - uint32 var_10; + uint8 *var_10; uint16 var_14; uint16 var_16; uint16 var_18; uint8 scenery_tab_id; // 0x1A } rct_small_scenery_entry; +#ifdef PLATFORM_32BIT assert_struct_size(rct_small_scenery_entry, 21); +#endif typedef enum { SMALL_SCENERY_FLAG_FULL_TILE = (1 << 0), // 0x1 @@ -44,9 +46,9 @@ typedef enum { SMALL_SCENERY_FLAG_ANIMATED = (1 << 4), // 0x10 SMALL_SCENERY_FLAG6 = (1 << 5), // 0x20 SMALL_SCENERY_FLAG_CAN_BE_WATERED = (1 << 6), // 0x40 - SMALL_SCENERY_FLAG8 = (1 << 7), // 0x80 + SMALL_SCENERY_FLAG_ANIMATED_FG = (1 << 7), // 0x80 SMALL_SCENERY_FLAG9 = (1 << 8), // 0x100 - SMALL_SCENERY_FLAG10 = (1 << 9), // 0x200 + SMALL_SCENERY_FLAG_HAS_GLASS = (1 << 9), // 0x200 SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR = (1 << 10), // 0x400 SMALL_SCENERY_FLAG12 = (1 << 11), // 0x800 SMALL_SCENERY_FLAG13 = (1 << 12), // 0x1000