diff --git a/Aerofoil/GpSystemServices_Win32.cpp b/Aerofoil/GpSystemServices_Win32.cpp index 88d8e29..717a945 100644 --- a/Aerofoil/GpSystemServices_Win32.cpp +++ b/Aerofoil/GpSystemServices_Win32.cpp @@ -268,6 +268,16 @@ bool GpSystemServices_Win32::HasNativeFileManager() const return true; } +GpOperatingSystem_t GpSystemServices_Win32::GetOperatingSystem() const +{ + return GpOperatingSystems::kWindows; +} + +GpOperatingSystemFlavor_t GpSystemServices_Win32::GetOperatingSystemFlavor() const +{ + return GpOperatingSystemFlavors::kGeneric; +} + unsigned int GpSystemServices_Win32::GetCPUCount() const { SYSTEM_INFO sysInfo; diff --git a/Aerofoil/GpSystemServices_Win32.h b/Aerofoil/GpSystemServices_Win32.h index cb0f274..2d91611 100644 --- a/Aerofoil/GpSystemServices_Win32.h +++ b/Aerofoil/GpSystemServices_Win32.h @@ -35,6 +35,8 @@ public: bool IsFullscreenPreferred() const override; bool IsFullscreenOnStartup() const override; bool HasNativeFileManager() const override; + GpOperatingSystem_t GetOperatingSystem() const override; + GpOperatingSystemFlavor_t GetOperatingSystemFlavor() const override; unsigned int GetCPUCount() const override; void SetTextInputEnabled(bool isEnabled) override; bool IsTextInputEnabled() const override; diff --git a/AerofoilAndroid/app/jni/main/GpSystemServices_Android.cpp b/AerofoilAndroid/app/jni/main/GpSystemServices_Android.cpp index 468abba..2d20739 100644 --- a/AerofoilAndroid/app/jni/main/GpSystemServices_Android.cpp +++ b/AerofoilAndroid/app/jni/main/GpSystemServices_Android.cpp @@ -90,6 +90,16 @@ bool GpSystemServices_Android::HasNativeFileManager() const return false; } +GpOperatingSystem_t GpSystemServices_Android::GetOperatingSystem() const +{ + return GpOperatingSystems::kAndroid; +} + +GpOperatingSystemFlavor_t GpSystemServices_Android::GetOperatingSystemFlavor() const +{ + return GpOperatingSystems::kGeneric; +} + unsigned int GpSystemServices_Android::GetCPUCount() const { return SDL_GetCPUCount(); diff --git a/AerofoilAndroid/app/jni/main/GpSystemServices_Android.h b/AerofoilAndroid/app/jni/main/GpSystemServices_Android.h index 881cbbf..603ea59 100644 --- a/AerofoilAndroid/app/jni/main/GpSystemServices_Android.h +++ b/AerofoilAndroid/app/jni/main/GpSystemServices_Android.h @@ -16,6 +16,8 @@ public: bool IsFullscreenPreferred() const override; bool IsFullscreenOnStartup() const override; bool HasNativeFileManager() const override; + GpOperatingSystem_t GetOperatingSystem() const override; + GpOperatingSystemFlavor_t GetOperatingSystemFlavor() const override; unsigned int GetCPUCount() const override; void SetTextInputEnabled(bool isEnabled) override; bool IsTextInputEnabled() const override; diff --git a/AerofoilWeb/GpSystemServices_Web.cpp b/AerofoilWeb/GpSystemServices_Web.cpp index 545fe63..6acbc8f 100644 --- a/AerofoilWeb/GpSystemServices_Web.cpp +++ b/AerofoilWeb/GpSystemServices_Web.cpp @@ -171,6 +171,16 @@ bool GpSystemServices_Web::HasNativeFileManager() const return false; } +GpOperatingSystem_t GpSystemServices_Web::GetOperatingSystem() const +{ + return GpOperatingSystems::kWeb; +} + +GpOperatingSystemFlavor_t GpSystemServices_Web::GetOperatingSystemFlavor() const +{ + return GpOperatingSystems::kGeneric; +} + unsigned int GpSystemServices_Web::GetCPUCount() const { return SDL_GetCPUCount(); diff --git a/AerofoilWeb/GpSystemServices_Web.h b/AerofoilWeb/GpSystemServices_Web.h index 4aafd63..03fb41f 100644 --- a/AerofoilWeb/GpSystemServices_Web.h +++ b/AerofoilWeb/GpSystemServices_Web.h @@ -19,6 +19,8 @@ public: bool IsFullscreenPreferred() const override; bool IsFullscreenOnStartup() const override; bool HasNativeFileManager() const override; + GpOperatingSystem_t GetOperatingSystem() const; + GpOperatingSystemFlavor_t GetOperatingSystemFlavor() const; unsigned int GetCPUCount() const override; void SetTextInputEnabled(bool isEnabled) override; bool IsTextInputEnabled() const override; diff --git a/AerofoilX/GpSystemServices_X.cpp b/AerofoilX/GpSystemServices_X.cpp index 80f5247..98ebaed 100644 --- a/AerofoilX/GpSystemServices_X.cpp +++ b/AerofoilX/GpSystemServices_X.cpp @@ -99,6 +99,16 @@ bool GpSystemServices_X::HasNativeFileManager() const return true; } +GpOperatingSystem_t GpSystemServices_X::GetOperatingSystem() const +{ + return GpOperatingSystems::kLinux; +} + +GpOperatingSystemFlavor_t GpSystemServices_X::GetOperatingSystemFlavor() const +{ + return GpOperatingSystems::kGeneric; +} + unsigned int GpSystemServices_X::GetCPUCount() const { return SDL_GetCPUCount(); diff --git a/AerofoilX/GpSystemServices_X.h b/AerofoilX/GpSystemServices_X.h index 704eeab..b694568 100644 --- a/AerofoilX/GpSystemServices_X.h +++ b/AerofoilX/GpSystemServices_X.h @@ -19,6 +19,8 @@ public: bool IsFullscreenPreferred() const override; bool IsFullscreenOnStartup() const override; bool HasNativeFileManager() const override; + GpOperatingSystem_t GetOperatingSystem() const override; + GpOperatingSystemFlavor_t GetOperatingSystemFlavor() const override; unsigned int GetCPUCount() const override; void SetTextInputEnabled(bool isEnabled) override; bool IsTextInputEnabled() const override; diff --git a/GpApp/Externs.h b/GpApp/Externs.h index 2be06cc..abb4ac7 100644 --- a/GpApp/Externs.h +++ b/GpApp/Externs.h @@ -76,6 +76,7 @@ namespace PortabilityLayer #define iCoordinateWindow 21 #define iExportGliderPROHouse 1 +#define iDownloadHouse 2 //-------------------------------------------------------------- Structs /* diff --git a/GpApp/GliderProtos.h b/GpApp/GliderProtos.h index 2f4aa90..0b7be6c 100644 --- a/GpApp/GliderProtos.h +++ b/GpApp/GliderProtos.h @@ -120,12 +120,12 @@ void DoGoToDialog (void); void ConvertHouseVer1To2 (void); void ShiftWholeHouse (SInt16); void ExportHouse (void); +void DownloadHouse (void); void DoHouseInfo (void); // --- HouseInfo.c Boolean OpenHouse (Boolean load); // --- HouseIO.c Boolean OpenSpecificHouse (const VFileSpec &); -Boolean SaveHouseAs (void); Boolean ReadHouse (GpIOStream *houseStream, bool untrusted); Boolean WriteHouse (Boolean); Boolean CloseHouse (void); diff --git a/GpApp/HouseIO.cpp b/GpApp/HouseIO.cpp index 966b9b7..37e2985 100644 --- a/GpApp/HouseIO.cpp +++ b/GpApp/HouseIO.cpp @@ -25,6 +25,8 @@ #include "PLStringCompare.h" #include "PLPasStr.h" +#include "CombinedTimestamp.h" +#include "DeflateCodec.h" #include "MacFileInfo.h" #include "GpIOStream.h" #include "GpVector.h" @@ -32,6 +34,7 @@ #include "QDPictOpcodes.h" #include "QDPixMap.h" #include "PLStandardColors.h" +#include "ZipFile.h" #define kSaveChangesAlert 1002 #define kSaveChanges 1 @@ -3515,10 +3518,16 @@ ExportHouseResult_t TryExportHouseToStream(GpIOStream *stream) ByteSwapHouse(house, houseSize, true); if (!stream->WriteExact(house, houseType::kBinaryDataSize)) + { + ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; + } if (!stream->WriteExact(house->rooms, sizeof(roomType) * nRooms)) + { + ByteSwapHouse(house, houseSize, false); return ExportHouseResults::kIOError; + } ByteSwapHouse(house, houseSize, false); @@ -3610,3 +3619,218 @@ void ExportHouse(void) break; } } + +ExportHouseResult_t TryDownloadHouseToStream(GpIOStream *stream) +{ + PortabilityLayer::MacFileProperties mfp; + mfp.m_createdTimeMacEpoch = PLDrivers::GetSystemServices()->GetTime(); + memcpy(mfp.m_fileCreator, "ozm5", 4); + memcpy(mfp.m_fileType, "gliH", 4); + mfp.m_finderFlags = 0; + mfp.m_modifiedTimeMacEpoch = mfp.m_createdTimeMacEpoch; + mfp.m_protected = 0; + mfp.m_xPos = 0; + mfp.m_yPos = 0; + + PortabilityLayer::MacFilePropertiesSerialized mfps; + mfps.Serialize(mfp); + + unsigned int year, month, day, hour, minute, second; + PortabilityLayer::CombinedTimestamp ts; + PLDrivers::GetSystemServices()->GetLocalDateTime(year, month, day, hour, minute, second); + + ts.SetLocalYear(year); + ts.m_localDay = day; + ts.m_localHour = hour; + ts.m_localMinute = minute; + ts.m_localMonth = month; + ts.m_localSecond = second; + + uint16_t dosDate, dosTime; + ts.GetAsMSDOSTimestamp(dosDate, dosTime); + + const uint32_t metaSize = sizeof(mfps.m_data); + const uint32_t metaCRC = PortabilityLayer::DeflateContext::CRC32(0, mfps.m_data, metaSize); + + const char *metaPackagedName = PortabilityLayer::MacFilePropertiesSerialized::GetPackagedName(); + GpUFilePos_t metaLHPos = 0; + PortabilityLayer::ZipFileLocalHeader metaLH; + metaLH.m_signature = PortabilityLayer::ZipFileLocalHeader::kSignature; + metaLH.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; + metaLH.m_flags = 0; + metaLH.m_method = PortabilityLayer::ZipConstants::kStoredMethod; + metaLH.m_modificationTime = dosTime; + metaLH.m_modificationDate = dosDate; + metaLH.m_crc = metaCRC; + metaLH.m_compressedSize = metaSize; + metaLH.m_uncompressedSize = metaSize; + metaLH.m_fileNameLength = strlen(metaPackagedName); + metaLH.m_extraFieldLength = 0; + + if (!stream->WriteExact(&metaLH, sizeof(metaLH))) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(metaPackagedName, strlen(metaPackagedName))) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(mfps.m_data, metaSize)) + return ExportHouseResults::kIOError; + + houseType *house = *thisHouse; + const size_t houseSize = thisHouse.MMBlock()->m_size; + const size_t nRooms = house->nRooms; + const size_t houseDataSize = houseType::kBinaryDataSize + sizeof(roomType) * nRooms; + + ByteSwapHouse(house, houseSize, true); + + uint32_t houseCRC = PortabilityLayer::DeflateContext::CRC32(0, house, houseType::kBinaryDataSize); + houseCRC = PortabilityLayer::DeflateContext::CRC32(houseCRC, house->rooms, sizeof(roomType) * nRooms); + + const uint32_t totalhouseSize = houseType::kBinaryDataSize + sizeof(roomType) * nRooms; + + GpUFilePos_t houseLHPos = 0; + + const char *dataPackagedName = "!data"; + + PortabilityLayer::ZipFileLocalHeader houseLH; + houseLH.m_signature = PortabilityLayer::ZipFileLocalHeader::kSignature; + houseLH.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; + houseLH.m_flags = 0; + houseLH.m_method = PortabilityLayer::ZipConstants::kStoredMethod; + houseLH.m_modificationTime = dosTime; + houseLH.m_modificationDate = dosDate; + houseLH.m_crc = houseCRC; + houseLH.m_compressedSize = totalhouseSize; + houseLH.m_uncompressedSize = totalhouseSize; + houseLH.m_fileNameLength = strlen(dataPackagedName); + houseLH.m_extraFieldLength = 0; + + if (!stream->WriteExact(&houseLH, sizeof(houseLH))) + { + ByteSwapHouse(house, houseSize, false); + return ExportHouseResults::kIOError; + } + + if (!stream->WriteExact(dataPackagedName, strlen(dataPackagedName))) + { + ByteSwapHouse(house, houseSize, false); + return ExportHouseResults::kIOError; + } + + if (!stream->WriteExact(house, houseType::kBinaryDataSize)) + { + ByteSwapHouse(house, houseSize, false); + return ExportHouseResults::kIOError; + } + + if (!stream->WriteExact(house->rooms, sizeof(roomType) * nRooms)) + { + ByteSwapHouse(house, houseSize, false); + return ExportHouseResults::kIOError; + } + + ByteSwapHouse(house, houseSize, false); + + GpUFilePos_t cdirStart = stream->Tell(); + + PortabilityLayer::ZipCentralDirectoryFileHeader metaCDir; + metaCDir.m_signature = PortabilityLayer::ZipCentralDirectoryFileHeader::kSignature; + metaCDir.m_versionCreated = PortabilityLayer::ZipConstants::kCompressedRequiredVersion;; + metaCDir.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; + metaCDir.m_flags = 0; + metaCDir.m_method = PortabilityLayer::ZipConstants::kStoredMethod; + metaCDir.m_modificationTime = dosTime; + metaCDir.m_modificationDate = dosDate; + metaCDir.m_crc = metaCRC; + metaCDir.m_compressedSize = metaSize; + metaCDir.m_uncompressedSize = metaSize; + metaCDir.m_fileNameLength = strlen(metaPackagedName); + metaCDir.m_extraFieldLength = 0; + metaCDir.m_commentLength = 0; + metaCDir.m_diskNumber = 0; + metaCDir.m_internalAttributes = 0; + metaCDir.m_externalAttributes = PortabilityLayer::ZipConstants::kArchivedAttributes; + metaCDir.m_localHeaderOffset = static_cast(metaLHPos); + + if (!stream->WriteExact(&metaCDir, sizeof(metaCDir))) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(metaPackagedName, strlen(metaPackagedName))) + return ExportHouseResults::kIOError; + + PortabilityLayer::ZipCentralDirectoryFileHeader dataCDir; + dataCDir.m_signature = PortabilityLayer::ZipCentralDirectoryFileHeader::kSignature; + dataCDir.m_versionCreated = PortabilityLayer::ZipConstants::kCompressedRequiredVersion;; + dataCDir.m_versionRequired = PortabilityLayer::ZipConstants::kStoredRequiredVersion; + dataCDir.m_flags = 0; + dataCDir.m_method = PortabilityLayer::ZipConstants::kStoredMethod; + dataCDir.m_modificationTime = dosTime; + dataCDir.m_modificationDate = dosDate; + dataCDir.m_crc = metaCRC; + dataCDir.m_compressedSize = houseDataSize; + dataCDir.m_uncompressedSize = houseDataSize; + dataCDir.m_fileNameLength = strlen(dataPackagedName); + dataCDir.m_extraFieldLength = 0; + dataCDir.m_commentLength = 0; + dataCDir.m_diskNumber = 0; + dataCDir.m_internalAttributes = 0; + dataCDir.m_externalAttributes = PortabilityLayer::ZipConstants::kArchivedAttributes; + dataCDir.m_localHeaderOffset = static_cast(houseLHPos); + + if (!stream->WriteExact(&dataCDir, sizeof(dataCDir))) + return ExportHouseResults::kIOError; + + if (!stream->WriteExact(dataPackagedName, strlen(dataPackagedName))) + return ExportHouseResults::kIOError; + + PortabilityLayer::ZipEndOfCentralDirectoryRecord eocd; + eocd.m_signature = PortabilityLayer::ZipEndOfCentralDirectoryRecord::kSignature; + eocd.m_thisDiskNumber = 0; + eocd.m_centralDirDisk = 0; + eocd.m_numCentralDirRecordsThisDisk = 2; + eocd.m_numCentralDirRecords = 2; + eocd.m_centralDirectorySizeBytes = static_cast(stream->Tell() - cdirStart); + eocd.m_centralDirStartOffset = static_cast(cdirStart); + eocd.m_commentLength = 0; + + if (!stream->WriteExact(&eocd, sizeof(eocd))) + return ExportHouseResults::kIOError; + + return ExportHouseResults::kOK; +} + +ExportHouseResult_t TryDownloadHouse(void) +{ + GpIOStream *stream = nullptr; + if (PortabilityLayer::FileManager::GetInstance()->OpenNonCompositeFile(PortabilityLayer::VirtualDirectories::kSourceExport, thisHouseName, ".gpf", PortabilityLayer::EFilePermission_Write, GpFileCreationDispositions::kCreateOrOverwrite, stream)) + return ExportHouseResults::kStreamFailed; + + ExportHouseResult_t result = TryDownloadHouseToStream(stream); + stream->Close(); + + return result; +} + +void DownloadHouse(void) +{ + ExportHouseResult_t result = TryDownloadHouse(); + + switch (result) + { + case ExportHouseResults::kOK: + break; + case ExportHouseResults::kMemError: + YellowAlert(kYellowNoMemory, 0); + break; + case ExportHouseResults::kInternalError: + YellowAlert(kYellowUnaccounted, 0); + break; + case ExportHouseResults::kIOError: + case ExportHouseResults::kStreamFailed: + YellowAlert(kYellowFailedWrite, 0); + break; + case ExportHouseResults::kResourceError: + YellowAlert(kYellowFailedResOpen, 0); + break; + } +} diff --git a/GpApp/InterfaceInit.cpp b/GpApp/InterfaceInit.cpp index c1296de..ed83d89 100644 --- a/GpApp/InterfaceInit.cpp +++ b/GpApp/InterfaceInit.cpp @@ -9,6 +9,7 @@ #include "Externs.h" #include "Environ.h" #include "IGpDisplayDriver.h" +#include "IGpSystemServices.h" #include "GpApplicationName.h" #include "Map.h" #include "MenuManager.h" @@ -79,8 +80,9 @@ void InitializeMenus (void) if (houseMenu == nil) RedAlert(kErrFailedResourceLoad); - exportMenu = mm->CreateMenu(PSTR("Export"), kExportMenuID, true, 100, 16, 0); + exportMenu = mm->CreateMenu(PSTR("Import/Export"), kExportMenuID, true, 100, 16, 0); mm->AppendMenuItem(exportMenu, 0, 0, 0, 0, true, false, PSTR("Export Glider PRO\xaa House...")); + mm->AppendMenuItem(exportMenu, 0, 0, 0, 0, false, false, PSTR("Download House...")); UpdateMenus(false); } diff --git a/GpApp/Menu.cpp b/GpApp/Menu.cpp index 6b5d8da..2422581 100644 --- a/GpApp/Menu.cpp +++ b/GpApp/Menu.cpp @@ -6,7 +6,7 @@ //============================================================================ -//#include +#include "PLDrivers.h" #include "PLNumberFormatting.h" #include "PLKeyEncoding.h" #include "PLHacks.h" @@ -17,6 +17,7 @@ #include "Externs.h" #include "Environ.h" #include "House.h" +#include "IGpSystemServices.h" #include "MenuManager.h" #include "ObjectEdit.h" @@ -143,9 +144,17 @@ void UpdateMenusHouseOpen (void) } if (houseUnlocked) + { EnableMenuItem(exportMenu, iExportGliderPROHouse); + if (PLDrivers::GetSystemServices()->GetOperatingSystem() == GpOperatingSystems::kWeb) + EnableMenuItem(exportMenu, iDownloadHouse); + } else + { DisableMenuItem(exportMenu, iExportGliderPROHouse); + if (PLDrivers::GetSystemServices()->GetOperatingSystem() == GpOperatingSystems::kWeb) + DisableMenuItem(exportMenu, iDownloadHouse); + } } //-------------------------------------------------------------- UpdateMenusHouseClosed @@ -486,6 +495,9 @@ void DoExportMenu(short theItem) case iExportGliderPROHouse: ExportHouse(); break; + case iDownloadHouse: + DownloadHouse(); + break; }; } diff --git a/GpCommon/IGpSystemServices.h b/GpCommon/IGpSystemServices.h index 76f64ac..98999b8 100644 --- a/GpCommon/IGpSystemServices.h +++ b/GpCommon/IGpSystemServices.h @@ -14,6 +14,34 @@ struct IGpMutex; struct IGpThreadEvent; struct IGpClipboardContents; +namespace GpOperatingSystems +{ + enum GpOperatingSystem + { + kUnknown, + + kWindows, + kAndroid, + kWeb, + kLinux, + kMacOS, + kIOS, + }; +} + +typedef GpOperatingSystems::GpOperatingSystem GpOperatingSystem_t; + +namespace GpOperatingSystemFlavors +{ + enum GpOperatingSystemFlavor + { + kGeneric, + }; +} + +typedef GpOperatingSystemFlavors::GpOperatingSystemFlavor GpOperatingSystemFlavor_t; + + struct IGpSystemServices { public: @@ -33,6 +61,8 @@ public: virtual bool IsFullscreenOnStartup() const = 0; virtual bool IsTextInputObstructive() const = 0; virtual bool HasNativeFileManager() const = 0; + virtual GpOperatingSystem_t GetOperatingSystem() const = 0; + virtual GpOperatingSystemFlavor_t GetOperatingSystemFlavor() const = 0; virtual unsigned int GetCPUCount() const = 0; virtual void SetTextInputEnabled(bool isEnabled) = 0; virtual bool IsTextInputEnabled() const = 0;