diff --git a/AerofoilAndroid/copy_source_package.bat b/AerofoilAndroid/copy_source_package.bat index 858c418..32ca75a 100644 --- a/AerofoilAndroid/copy_source_package.bat +++ b/AerofoilAndroid/copy_source_package.bat @@ -1,7 +1,8 @@ cd .. -del AerofoilAndroid\app\src\main\assets\SourceCode.zip -del AerofoilAndroid\app\src\main\assets\SourceCode.pkg -git archive -0 --format zip -o AerofoilAndroid\app\src\main\assets\SourceCode.zip HEAD -tools\7z.exe d AerofoilAndroid\app\src\main\assets\SourceCode.zip GliderProData\ -cd AerofoilAndroid\app\src\main\assets +copy /Y DefaultTimestamp.timestamp AerofoilAndroid\app\src\main\assets\Packaged\DefaultTimestamp.timestamp +del AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.zip +del AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.pkg +git archive -0 --format zip -o AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.zip HEAD +tools\7z.exe d AerofoilAndroid\app\src\main\assets\Packaged\SourceCode.zip GliderProData\ +cd AerofoilAndroid\app\src\main\assets\Packaged rename SourceCode.zip SourceCode.pkg diff --git a/GpApp/Android.mk b/GpApp/Android.mk index 46b56df..56a5a7c 100644 --- a/GpApp/Android.mk +++ b/GpApp/Android.mk @@ -72,6 +72,7 @@ LOCAL_SRC_FILES := \ Settings.cpp \ Sound.cpp \ SoundSync_Cpp11.cpp \ + SourceExport.cpp \ StringUtils.cpp \ StructuresInit.cpp \ StructuresInit2.cpp \ diff --git a/GpApp/Externs.h b/GpApp/Externs.h index 87bb84c..a664330 100644 --- a/GpApp/Externs.h +++ b/GpApp/Externs.h @@ -48,6 +48,7 @@ namespace PortabilityLayer #define iAbout 1 #define iAboutAerofoil 3 +#define iExportSourceCode 4 #define iNewGame 1 #define iTwoPlayer 2 #define iOpenSavedGame 3 @@ -126,6 +127,9 @@ typedef struct void DoAbout (void); // --- About.c void DoAboutFramework (void); +// --- SourceExport.c +void DoExportSourceCode (void); + void LoadCursors (void); // --- AnimCursor.c void DisposCursors (void); void IncrementCursor (void); diff --git a/GpApp/GpApp.vcxproj b/GpApp/GpApp.vcxproj index a465578..5f8e374 100644 --- a/GpApp/GpApp.vcxproj +++ b/GpApp/GpApp.vcxproj @@ -133,6 +133,7 @@ + diff --git a/GpApp/GpApp.vcxproj.filters b/GpApp/GpApp.vcxproj.filters index c5126ac..fdbb488 100644 --- a/GpApp/GpApp.vcxproj.filters +++ b/GpApp/GpApp.vcxproj.filters @@ -216,6 +216,9 @@ Source Files + + Source Files + diff --git a/GpApp/InterfaceInit.cpp b/GpApp/InterfaceInit.cpp index 9bba98f..4aa2ccf 100644 --- a/GpApp/InterfaceInit.cpp +++ b/GpApp/InterfaceInit.cpp @@ -55,6 +55,7 @@ void InitializeMenus (void) RedAlert(kErrFailedResourceLoad); //AppendResMenu(appleMenu, 'DRVR'); // GP: We don't support this AppendMenuItem(appleMenu, 0, 0, 0, 0, true, false, PSTR("About " GP_APPLICATION_NAME "\xc9")); + AppendMenuItem(appleMenu, 0, 0, 0, 0, true, false, PSTR("Export Source Code\xc9")); InsertMenu(appleMenu, 0); gameMenu = GetMenu(kGameMenuID); diff --git a/GpApp/Menu.cpp b/GpApp/Menu.cpp index c1a7998..b5a8ed1 100644 --- a/GpApp/Menu.cpp +++ b/GpApp/Menu.cpp @@ -297,6 +297,10 @@ void DoAppleMenu (short theItem) DoAboutFramework(); break; + case iExportSourceCode: + DoExportSourceCode(); + break; + default: // GetMenuItemText(appleMenu, theItem, daName); // GetPort(&wasPort); diff --git a/GpApp/SourceExport.cpp b/GpApp/SourceExport.cpp new file mode 100644 index 0000000..5797c4e --- /dev/null +++ b/GpApp/SourceExport.cpp @@ -0,0 +1,556 @@ +#include "CombinedTimestamp.h" +#include "DeflateCodec.h" +#include "Environ.h" +#include "GpIOStream.h" +#include "HostDirectoryCursor.h" +#include "HostFileSystem.h" +#include "MemoryManager.h" +#include "PLCore.h" +#include "PLStandardColors.h" +#include "PLSysCalls.h" +#include "RenderedFont.h" +#include "GpRenderedFontMetrics.h" +#include "ResolveCachingColor.h" +#include "ZipFile.h" +#include "WindowDef.h" +#include "WindowManager.h" +#include "FontFamily.h" + +#include +#include + +struct SourceExportState +{ + SourceExportState(); + ~SourceExportState(); + + Window *m_window; + Rect m_progressRect; + Rect m_filledProgress; + GpIOStream *m_tsStream; + GpIOStream *m_sourcePkgStream; + GpIOStream *m_looseStream; + + size_t m_dataProcessed; + size_t m_dataTotal; + + PortabilityLayer::CombinedTimestamp m_ts; + +private: + static void CloseStreamIfOpen(GpIOStream *stream) + { + if (stream) + stream->Close(); + } +}; + +SourceExportState::SourceExportState() + : m_window(nullptr) + , m_progressRect(Rect::Create(0, 0, 0, 0)) + , m_tsStream(nullptr) + , m_sourcePkgStream(nullptr) + , m_looseStream(nullptr) + , m_dataProcessed(0) + , m_dataTotal(0) +{ +} + +SourceExportState::~SourceExportState() +{ + if (m_window) + PortabilityLayer::WindowManager::GetInstance()->DestroyWindow(m_window); + + CloseStreamIfOpen(m_tsStream); + CloseStreamIfOpen(m_sourcePkgStream); + CloseStreamIfOpen(m_looseStream); +} + +struct SortableEntry +{ + std::string m_zipLocation; + PortabilityLayer::VirtualDirectory_t m_virtualDirectory; + PortabilityLayer::PascalStr<255> m_filename; + + static SortableEntry Create(const char *zipLocation, PortabilityLayer::VirtualDirectory_t virtualDir, const PLPasStr &filename); +}; + +SortableEntry SortableEntry::Create(const char *zipLocation, PortabilityLayer::VirtualDirectory_t virtualDir, const PLPasStr &filename) +{ + SortableEntry entry; + entry.m_zipLocation = zipLocation; + entry.m_virtualDirectory = virtualDir; + entry.m_filename.Set(filename.Length(), filename.Chars()); + + return entry; +} + +static void ConvertToMSDOSTimestamp(const PortabilityLayer::CombinedTimestamp &ts, uint16_t &msdosDate, uint16_t &msdosTime) +{ + int32_t yearsSince1980 = ts.GetLocalYear() - 1980; + uint8_t month = ts.m_localMonth; + uint8_t day = ts.m_localDay; + + uint8_t hour = ts.m_localHour; + uint8_t minute = ts.m_localMinute; + uint8_t second = ts.m_localSecond; + + if (yearsSince1980 < 0) + { + // Time machine + yearsSince1980 = 0; + second = 0; + minute = 0; + hour = 0; + day = 1; + month = 1; + } + else if (yearsSince1980 > 127) + { + // I was promised flying cars, but it's 2107 and you're still flying paper airplanes... + yearsSince1980 = 127; + second = 59; + minute = 59; + hour = 23; + day = 31; + month = 12; + } + + msdosTime = (second / 2) | (minute << 5) | (hour << 11); + msdosDate = day | (month << 5) | (yearsSince1980 << 9); +} + +static void InitSourceExportWindow(SourceExportState *state) +{ + static const int kLoadScreenHeight = 32; + static const int kLoadScreenWidth = 256; + + ForceSyncFrame(); + PLSysCalls::Sleep(1); + + int32_t lsX = (thisMac.fullScreen.Width() - kLoadScreenWidth) / 2; + int32_t lsY = (thisMac.fullScreen.Height() - kLoadScreenHeight) / 2; + + + const Rect loadScreenRect = Rect::Create(lsY, lsX, lsY + kLoadScreenHeight, lsX + kLoadScreenWidth); + const Rect loadScreenLocalRect = Rect::Create(0, 0, loadScreenRect.Height(), loadScreenRect.Width()); + + PortabilityLayer::WindowDef def = PortabilityLayer::WindowDef::Create(loadScreenRect, PortabilityLayer::WindowStyleFlags::kAlert, true, 0, 0, PSTR("")); + + state->m_window = PortabilityLayer::WindowManager::GetInstance()->CreateWindow(def); + PortabilityLayer::WindowManager::GetInstance()->PutWindowBehind(state->m_window, PL_GetPutInFrontWindowPtr()); + + DrawSurface *surface = state->m_window->GetDrawSurface(); + PortabilityLayer::ResolveCachingColor blackColor(StdColors::Black()); + PortabilityLayer::ResolveCachingColor whiteColor(StdColors::White()); + + surface->FillRect(loadScreenLocalRect, whiteColor); + + PortabilityLayer::WindowManager::GetInstance()->FlickerWindowIn(state->m_window, 32); + + const PLPasStr loadingText = PSTR("Exporting..."); + PortabilityLayer::RenderedFont *font = GetApplicationFont(18, PortabilityLayer::FontFamilyFlag_None, true); + int32_t textY = (kLoadScreenHeight + font->GetMetrics().m_ascent) / 2; + surface->DrawString(Point::Create(4 + 16, textY), loadingText, blackColor, font); + + static const int32_t loadBarPadding = 16; + int32_t loadBarStartX = static_cast(font->MeasureString(loadingText.UChars(), loadingText.Length())) + 4 + 16 + loadBarPadding; + int32_t loadBarEndX = loadScreenLocalRect.right - loadBarPadding; + + state->m_progressRect = Rect::Create((loadScreenLocalRect.Height() - 8) / 2, loadBarStartX, (loadScreenLocalRect.Height() + 8) / 2, loadBarEndX); + state->m_filledProgress = Rect::Create(state->m_progressRect.top, state->m_progressRect.left, state->m_progressRect.bottom, state->m_progressRect.left); + + surface->FrameRect(state->m_progressRect, blackColor); +} + +static bool RetrieveSingleFileSize(PortabilityLayer::VirtualDirectory_t virtualDir, char const* const* paths, size_t numPaths, size_t &outSize) +{ + GpIOStream *stream = PortabilityLayer::HostFileSystem::GetInstance()->OpenFileNested(virtualDir, paths, numPaths, false, GpFileCreationDispositions::kOpenExisting); + if (!stream) + return false; + + const size_t sz = static_cast(stream->Size()); + stream->Close(); + + outSize = sz; + return true; +} + +static bool RetrieveResourceTypeDirSize(PortabilityLayer::VirtualDirectory_t virtualDir, const char *arcDir, const char *typeDir, size_t &totalSizeOut) +{ + size_t totalSize = 0; + + const char *nestedDirs[2] = { arcDir, typeDir }; + + PortabilityLayer::HostDirectoryCursor *dirCursor = PortabilityLayer::HostFileSystem::GetInstance()->ScanDirectoryNested(virtualDir, nestedDirs, 2); + if (!dirCursor) + return false; + + const char *fname = nullptr; + while (dirCursor->GetNext(fname)) + { + const char *nestedFile[3] = { arcDir, typeDir, fname }; + + size_t fileSize = 0; + if (!RetrieveSingleFileSize(virtualDir, nestedFile, 3, fileSize)) + return false; + + totalSize += fileSize; + } + + dirCursor->Destroy(); + + totalSizeOut = totalSize; + + return true; +} + +static bool RetrieveResourceDirSize(PortabilityLayer::VirtualDirectory_t virtualDir, const char *arcDir, size_t arcDirLen, size_t &totalSizeOut) +{ + size_t totalSize = 0; + + char *subDir = static_cast(PortabilityLayer::MemoryManager::GetInstance()->Alloc(arcDirLen + 1)); + memcpy(subDir, arcDir, arcDirLen); + subDir[arcDirLen] = '\0'; + + PortabilityLayer::HostDirectoryCursor *dirCursor = PortabilityLayer::HostFileSystem::GetInstance()->ScanDirectoryNested(virtualDir, &subDir, 1); + if (!dirCursor) + { + // It's okay for the resource dir to not exist + totalSizeOut = 0; + return true; + } + + const char *fname = nullptr; + while (dirCursor->GetNext(fname)) + { + size_t resTypeDirSize = 0; + if (!RetrieveResourceTypeDirSize(virtualDir, subDir, fname, resTypeDirSize)) + return false; + + totalSize += resTypeDirSize; + } + + dirCursor->Destroy(); + + PortabilityLayer::MemoryManager::GetInstance()->Release(subDir); + + totalSizeOut = totalSize; + + return true; +} + +static bool RetrieveCompositeDirSize(PortabilityLayer::VirtualDirectory_t virtualDir, size_t &totalSizeOut) +{ + size_t totalSize = 0; + totalSizeOut = 0; + + PortabilityLayer::HostDirectoryCursor *dirCursor = PortabilityLayer::HostFileSystem::GetInstance()->ScanDirectory(virtualDir); + if (!dirCursor) + return false; + + const char *fname = nullptr; + while (dirCursor->GetNext(fname)) + { + size_t fnameLen = strlen(fname); + if (fnameLen >= 4 && !memcmp(fname + fnameLen - 4, ".gpf", 4)) + { + size_t resDirSize = 0; + if (!RetrieveResourceDirSize(virtualDir, fname, fnameLen - 4, resDirSize)) + return false; + + totalSize += resDirSize; + + size_t gpfSize = 0; + if (!RetrieveSingleFileSize(virtualDir, &fname, 1, gpfSize)) + return false; + + totalSize += gpfSize; + } + else if (fnameLen >= 4 && !memcmp(fname + fnameLen - 4, ".gpd", 4)) + { + size_t gpdSize = 0; + if (!RetrieveSingleFileSize(virtualDir, &fname, 1, gpdSize)) + return false; + + totalSize += gpdSize; + } + } + + totalSizeOut = totalSize; + + return true; +} + +static void UpdateProgress(SourceExportState &state) +{ + uint32_t progressWidth = state.m_progressRect.Width(); + uint32_t oldFillWidth = state.m_filledProgress.Width(); + uint32_t newFillWidth = static_cast(state.m_dataProcessed) * static_cast(progressWidth) / state.m_dataTotal; + if (newFillWidth > progressWidth) + newFillWidth = progressWidth; + + if (newFillWidth > oldFillWidth) + { + state.m_filledProgress.right = state.m_filledProgress.left + static_cast(newFillWidth); + Rect fillInRect = Rect::Create(state.m_progressRect.top, state.m_progressRect.left + oldFillWidth, state.m_progressRect.bottom, state.m_progressRect.left + newFillWidth); + + PortabilityLayer::ResolveCachingColor blackColor(StdColors::Black()); + state.m_window->GetDrawSurface()->FillRect(fillInRect, blackColor); + + PLSysCalls::Sleep(1); + } +} + +static bool RepackSourcePackage(SourceExportState &state, GpIOStream *outStream, std::vector ¢ralDirFiles, std::vector &fileNameChars, std::vector &fileNameLengths) +{ + PortabilityLayer::MemoryManager *mm = PortabilityLayer::MemoryManager::GetInstance(); + + for (;;) + { + LEUInt32_t signature; + + GpUFilePos_t inSigPos = state.m_sourcePkgStream->Tell(); + + if (state.m_sourcePkgStream->Read(&signature, sizeof(signature)) != sizeof(signature)) + return false; + + if (signature != PortabilityLayer::ZipFileLocalHeader::kSignature) + { + // Trim central dir size off of the data estimate + state.m_dataTotal -= (state.m_sourcePkgStream->Size() - inSigPos); + return true; + } + + PortabilityLayer::ZipFileLocalHeader lHeader; + lHeader.m_signature = signature; + + if (state.m_sourcePkgStream->Read(reinterpret_cast(&lHeader) + sizeof(signature), sizeof(lHeader) - sizeof(signature)) != sizeof(lHeader) - sizeof(signature)) + return false; + + state.m_dataProcessed += sizeof(lHeader); + + GpUFilePos_t outLocalHeaderPos = outStream->Tell(); + if (outStream->Write(&lHeader, sizeof(lHeader)) != sizeof(lHeader)) + return false; + + const size_t fileNameLength = lHeader.m_fileNameLength; + const size_t nameAndExtraFieldLength = fileNameLength + lHeader.m_extraFieldLength; + uint8_t *nameAndExtraField = static_cast(mm->Alloc(nameAndExtraFieldLength)); + if (!nameAndExtraField) + return false; + + if (state.m_sourcePkgStream->Read(nameAndExtraField, nameAndExtraFieldLength) != nameAndExtraFieldLength) + { + mm->Release(nameAndExtraField); + return false; + } + + state.m_dataProcessed += nameAndExtraFieldLength; + + fileNameChars.reserve(fileNameLength); + for (size_t i = 0; i < fileNameLength; i++) + fileNameChars.push_back(nameAndExtraField[i]); + + fileNameLengths.push_back(fileNameLength); + + if (!outStream->Write(nameAndExtraField, nameAndExtraFieldLength)) + return false; + + const bool endsInSlash = (nameAndExtraField[fileNameLength - 1] == '/'); + + mm->Release(nameAndExtraField); + + assert(lHeader.m_method == PortabilityLayer::ZipConstants::kStoredMethod); + + size_t uncompressedSize = lHeader.m_uncompressedSize; + size_t compressedSize = 0; + + GpUFilePos_t compressedDataStart = outStream->Tell(); + + uint8_t copyBuffer[2048]; + + bool isCompressed = false; + if (uncompressedSize != 0) + { + isCompressed = true; + + PortabilityLayer::DeflateContext *context = PortabilityLayer::DeflateContext::Create(outStream, 9); + if (!context) + return false; + + size_t compressRemaining = uncompressedSize; + while (compressRemaining > 0) + { + size_t chunkSize = compressRemaining; + if (chunkSize > sizeof(copyBuffer)) + chunkSize = sizeof(copyBuffer); + + if (state.m_sourcePkgStream->Read(copyBuffer, chunkSize) != chunkSize || !context->Append(copyBuffer, chunkSize)) + { + context->Destroy(); + return false; + } + + compressRemaining -= chunkSize; + } + + if (!context->Flush()) + { + context->Destroy(); + return false; + } + + context->Destroy(); + } + + GpUFilePos_t compressedDataEnd = outStream->Tell(); + + lHeader.m_compressedSize = static_cast(compressedDataEnd - compressedDataStart); + if (isCompressed) + { + lHeader.m_method = PortabilityLayer::ZipConstants::kDeflatedMethod; + lHeader.m_versionRequired = PortabilityLayer::ZipConstants::kCompressedRequiredVersion; + + if (!outStream->SeekStart(outLocalHeaderPos)) + return false; + + if (outStream->Write(&lHeader, sizeof(lHeader)) != sizeof(lHeader)) + return false; + + if (!outStream->SeekStart(compressedDataEnd)) + return false; + } + + state.m_dataProcessed += uncompressedSize; + + const bool isDirectory = (lHeader.m_uncompressedSize == 0 && endsInSlash); + + PortabilityLayer::ZipCentralDirectoryFileHeader cdirHeader; + cdirHeader.m_signature = PortabilityLayer::ZipCentralDirectoryFileHeader::kSignature; + cdirHeader.m_versionCreated = PortabilityLayer::ZipConstants::kCompressedRequiredVersion; + cdirHeader.m_versionRequired = isDirectory ? PortabilityLayer::ZipConstants::kDirectoryRequiredVersion : lHeader.m_versionRequired; + cdirHeader.m_flags = 0; + cdirHeader.m_method = lHeader.m_method; + cdirHeader.m_modificationTime = lHeader.m_modificationTime; + cdirHeader.m_modificationDate = lHeader.m_modificationDate; + cdirHeader.m_crc = lHeader.m_crc; + cdirHeader.m_compressedSize = lHeader.m_compressedSize; + cdirHeader.m_uncompressedSize = lHeader.m_uncompressedSize; + cdirHeader.m_fileNameLength = lHeader.m_fileNameLength; + cdirHeader.m_extraFieldLength = 0; + cdirHeader.m_commentLength = 0; + cdirHeader.m_diskNumber = 0; + cdirHeader.m_internalAttributes = 0; + cdirHeader.m_externalAttributes = isDirectory ? PortabilityLayer::ZipConstants::kDirectoryAttributes : PortabilityLayer::ZipConstants::kArchivedAttributes; + cdirHeader.m_localHeaderOffset = outLocalHeaderPos; + + centralDirFiles.push_back(cdirHeader); + + UpdateProgress(state); + } + + return true; +} + +static bool WriteCentralDirectory(GpIOStream *stream, const std::vector ¢ralDirFiles, const std::vector &fileNameChars, const std::vector &fileNameLengths) +{ + const size_t numEntries = centralDirFiles.size(); + assert(fileNameLengths.size() == numEntries); + + GpUFilePos_t cdirLoc = stream->Tell(); + + size_t fnameOffset = 0; + for (size_t i = 0; i < numEntries; i++) + { + const PortabilityLayer::ZipCentralDirectoryFileHeader &cdirHeader = centralDirFiles[i]; + + if (stream->Write(&cdirHeader, sizeof(cdirHeader)) != sizeof(cdirHeader)) + return false; + + const size_t fnameLength = fileNameLengths[i]; + if (stream->Write(&fileNameChars[fnameOffset], fnameLength) != fnameLength) + return false; + + fnameOffset += fnameLength; + } + + GpUFilePos_t cdirEnd = stream->Tell(); + + PortabilityLayer::ZipEndOfCentralDirectoryRecord ecdRec; + ecdRec.m_signature = PortabilityLayer::ZipEndOfCentralDirectoryRecord::kSignature; + ecdRec.m_thisDiskNumber = 0; + ecdRec.m_centralDirDisk = 0; + ecdRec.m_numCentralDirRecordsThisDisk = centralDirFiles.size(); + ecdRec.m_numCentralDirRecords = centralDirFiles.size(); + ecdRec.m_centralDirectorySizeBytes = cdirEnd - cdirLoc; + ecdRec.m_centralDirStartOffset = cdirLoc; + ecdRec.m_commentLength = 0; + + if (stream->Write(&ecdRec, sizeof(ecdRec)) != sizeof(ecdRec)) + return false; + + return true; +} + +bool ExportSourceToStream (GpIOStream *stream) +{ + SourceExportState state; + InitSourceExportWindow(&state); + + state.m_tsStream = PortabilityLayer::HostFileSystem::GetInstance()->OpenFile(PortabilityLayer::VirtualDirectories::kApplicationData, "DefaultTimestamp.timestamp", false, GpFileCreationDispositions::kOpenExisting); + if (!state.m_tsStream) + return false; + + // Read timestamp + const bool readTSOK = (state.m_tsStream->Read(&state.m_ts, sizeof(state.m_ts)) != sizeof(state.m_ts)); + state.m_tsStream->Close(); + state.m_tsStream = nullptr; + + state.m_sourcePkgStream = PortabilityLayer::HostFileSystem::GetInstance()->OpenFile(PortabilityLayer::VirtualDirectories::kApplicationData, "SourceCode.pkg", false, GpFileCreationDispositions::kOpenExisting); + if (!state.m_sourcePkgStream) + return false; + + PLSysCalls::ForceSyncFrame(); + PLSysCalls::Sleep(1); + + size_t sourcePkgSize = state.m_sourcePkgStream->Size(); + size_t looseFilesSize = 0; + if (!RetrieveCompositeDirSize(PortabilityLayer::VirtualDirectories::kGameData, looseFilesSize)) + return false; + + PLSysCalls::ForceSyncFrame(); + PLSysCalls::Sleep(1); + + size_t applicationDataSize = 0; + const char *applicationDataPath = "ApplicationResources"; + if (!RetrieveResourceDirSize(PortabilityLayer::VirtualDirectories::kApplicationData, applicationDataPath, strlen(applicationDataPath), applicationDataSize)) + return false; + + PLSysCalls::ForceSyncFrame(); + PLSysCalls::Sleep(1); + + state.m_dataTotal = applicationDataSize + looseFilesSize + sourcePkgSize; + + std::vector centralDirFiles; + std::vector fileNameChars; + std::vector fileNameLengths; + + RepackSourcePackage(state, stream, centralDirFiles, fileNameChars, fileNameLengths); + + if (!WriteCentralDirectory(stream, centralDirFiles, fileNameChars, fileNameLengths)) + return false; + + PortabilityLayer::WindowManager::GetInstance()->FlickerWindowOut(state.m_window, 32); + + return true; +} + +void DoExportSourceCode (void) +{ + GpIOStream *stream = PortabilityLayer::HostFileSystem::GetInstance()->OpenFile(PortabilityLayer::VirtualDirectories::kPrefs, "SourceExport.zip", true, GpFileCreationDispositions::kCreateOrOverwrite); + if (!stream) + return; + + ExportSourceToStream(stream); + stream->Close(); +} diff --git a/PortabilityLayer/DeflateCodec.cpp b/PortabilityLayer/DeflateCodec.cpp index 70eeb60..2a3f9f4 100644 --- a/PortabilityLayer/DeflateCodec.cpp +++ b/PortabilityLayer/DeflateCodec.cpp @@ -9,6 +9,8 @@ #include "zlib.h" #endif +#include + namespace { static voidpf ZlibAllocShim(voidpf opaque, uInt items, uInt size) @@ -22,66 +24,189 @@ namespace } } -namespace PortabilityLayer +bool PortabilityLayer::DeflateCodec::DecompressStream(GpIOStream *stream, size_t inSize, void *outBuffer, size_t outSize) { - bool DeflateCodec::DecompressStream(GpIOStream *stream, size_t inSize, void *outBuffer, size_t outSize) + z_stream zstream; + zstream.zalloc = ZlibAllocShim; + zstream.zfree = ZlibFreeShim; + zstream.opaque = MemoryManager::GetInstance(); + + if (inflateInit2(&zstream, -15) != Z_OK) + return false; + + const size_t bufferSize = 1024; + uint8_t buffer[1024]; + + zstream.avail_out = outSize; + zstream.next_out = static_cast(outBuffer); + zstream.avail_in = 0; + zstream.next_in = buffer; + + bool failed = false; + for (;;) { - z_stream zstream; - zstream.zalloc = ZlibAllocShim; - zstream.zfree = ZlibFreeShim; - zstream.opaque = MemoryManager::GetInstance(); - - if (inflateInit2(&zstream, -15) != Z_OK) - return false; - - const size_t bufferSize = 1024; - uint8_t buffer[1024]; - - zstream.avail_out = outSize; - zstream.next_out = static_cast(outBuffer); - zstream.avail_in = 0; - zstream.next_in = buffer; - - bool failed = false; - for (;;) + if (zstream.avail_in == 0) { - if (zstream.avail_in == 0) + const size_t sizeToRead = (bufferSize < inSize) ? bufferSize : inSize; + + if (sizeToRead == 0) { - const size_t sizeToRead = (bufferSize < inSize) ? bufferSize : inSize; - - if (sizeToRead == 0) - { - // Ran out of input - failed = true; - break; - } - - if (stream->Read(buffer, sizeToRead) != sizeToRead) - { - failed = true; - break; - } - - zstream.avail_in = sizeToRead; - zstream.next_in = buffer; - } - - int result = inflate(&zstream, Z_NO_FLUSH); - if (result == Z_STREAM_END) - { - failed = (zstream.avail_out != 0); + // Ran out of input + failed = true; break; } - if (result != Z_OK) + if (stream->Read(buffer, sizeToRead) != sizeToRead) { failed = true; break; } + + zstream.avail_in = sizeToRead; + zstream.next_in = buffer; } - inflateEnd(&zstream); + int result = inflate(&zstream, Z_NO_FLUSH); + if (result == Z_STREAM_END) + { + failed = (zstream.avail_out != 0); + break; + } - return !failed; + if (result != Z_OK) + { + failed = true; + break; + } } + + inflateEnd(&zstream); + + return !failed; +} + +namespace PortabilityLayer +{ + class DeflateContextImpl final : public DeflateContext + { + public: + DeflateContextImpl(GpIOStream *stream, int compressionLevel); + ~DeflateContextImpl(); + + bool Init(); + + void Destroy() override; + bool Append(const void *buffer, size_t size) override; + + bool Flush() override; + + private: + GpIOStream *m_ioStream; + z_stream m_zStream; + int m_compressionLevel; + bool m_streamInitialized; + + uint8_t m_flushBuffer[1024]; + }; +} + +PortabilityLayer::DeflateContextImpl::DeflateContextImpl(GpIOStream *stream, int compressionLevel) + : m_ioStream(stream) + , m_compressionLevel(compressionLevel) + , m_streamInitialized(false) +{ + memset(&m_zStream, 0, sizeof(m_zStream)); +} + +PortabilityLayer::DeflateContextImpl::~DeflateContextImpl() +{ + if (m_streamInitialized) + deflateEnd(&m_zStream); +} + +bool PortabilityLayer::DeflateContextImpl::Init() +{ + m_zStream.zalloc = ZlibAllocShim; + m_zStream.zfree = ZlibFreeShim; + m_zStream.opaque = MemoryManager::GetInstance(); + + if (deflateInit2(&m_zStream, m_compressionLevel, Z_DEFLATED, -15, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) + return false; + + m_streamInitialized = true; + return true; +} + +void PortabilityLayer::DeflateContextImpl::Destroy() +{ + this->~DeflateContextImpl(); + PortabilityLayer::MemoryManager::GetInstance()->Release(this); +} + +bool PortabilityLayer::DeflateContextImpl::Append(const void *buffer, size_t size) +{ + m_zStream.avail_out = sizeof(m_flushBuffer); + m_zStream.next_out = m_flushBuffer; + + m_zStream.next_in = reinterpret_cast(const_cast(buffer)); + m_zStream.avail_in = size; + + while (m_zStream.avail_in > 0) + { + if (deflate(&m_zStream, Z_NO_FLUSH) != Z_OK) + return false; + + if (m_zStream.avail_out != sizeof(m_flushBuffer)) + { + size_t amountOut = sizeof(m_flushBuffer) - m_zStream.avail_out; + if (m_ioStream->Write(m_flushBuffer, amountOut) != amountOut) + return false; + + m_zStream.avail_out = sizeof(m_flushBuffer); + m_zStream.next_out = m_flushBuffer; + } + } + + return true; +} + +bool PortabilityLayer::DeflateContextImpl::Flush() +{ + for (;;) + { + const int deflateCode = deflate(&m_zStream, Z_FINISH); + if (deflateCode != Z_OK && deflateCode != Z_STREAM_END) + return false; + + if (m_zStream.avail_out != sizeof(m_flushBuffer)) + { + size_t amountOut = sizeof(m_flushBuffer) - m_zStream.avail_out; + if (m_ioStream->Write(m_flushBuffer, amountOut) != amountOut) + return false; + + m_zStream.avail_out = sizeof(m_flushBuffer); + m_zStream.next_out = m_flushBuffer; + } + + if (deflateCode == Z_STREAM_END) + break; + } + + return true; +} + +PortabilityLayer::DeflateContext *PortabilityLayer::DeflateContext::Create(GpIOStream *stream, int compressionLevel) +{ + void *storage = PortabilityLayer::MemoryManager::GetInstance()->Alloc(sizeof(PortabilityLayer::DeflateContextImpl)); + if (!storage) + return nullptr; + + DeflateContextImpl *obj = new (storage) DeflateContextImpl(stream, compressionLevel); + if (!obj->Init()) + { + obj->Destroy(); + return nullptr; + } + + return obj; } diff --git a/PortabilityLayer/DeflateCodec.h b/PortabilityLayer/DeflateCodec.h index 637c7c1..da3aade 100644 --- a/PortabilityLayer/DeflateCodec.h +++ b/PortabilityLayer/DeflateCodec.h @@ -6,6 +6,17 @@ class GpIOStream; namespace PortabilityLayer { + class DeflateContext + { + public: + static DeflateContext *Create(GpIOStream *stream, int compressionLevel); + + virtual void Destroy() = 0; + virtual bool Append(const void *buffer, size_t size) = 0; + + virtual bool Flush() = 0; + }; + class DeflateCodec { public: diff --git a/PortabilityLayer/ZipFile.h b/PortabilityLayer/ZipFile.h index 6f4fa5a..4790ab3 100644 --- a/PortabilityLayer/ZipFile.h +++ b/PortabilityLayer/ZipFile.h @@ -34,6 +34,9 @@ namespace PortabilityLayer LEUInt32_t m_uncompressedSize; LEUInt16_t m_fileNameLength; LEUInt16_t m_extraFieldLength; + + // File name + // Extra field }; struct ZipCentralDirectoryFileHeader