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