Skip to content

Zipped Save Files #166

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 35 commits into
base: development
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4d948f8
First implementation of saving/loading menu
Causeless Aug 14, 2023
ece8b62
Added activity/scene/date info
Causeless Aug 17, 2023
8dcf097
Always show save/load - we should disable the "Create Save" button in…
Causeless Aug 18, 2023
65fef08
Added order-by to save list
Causeless Aug 18, 2023
dd02bc4
Lots of fixes and improvements. Added save menu to main menu. Lots of…
Causeless Aug 18, 2023
79e0a79
Cleanup and consistency with listbox selection. Show "Overwrite" whe…
Causeless Aug 19, 2023
1f10ab7
Added confirmation dialog for overwrite/delete
Causeless Aug 19, 2023
bc69c68
Fixed spacing
Causeless Aug 19, 2023
2f29d79
Hopefully fix build
Causeless Aug 20, 2023
7656ea9
Added read/write with arbitrary streams
Causeless Aug 20, 2023
44a0d78
Prototyping for zip saves...
Causeless Aug 20, 2023
d5619dd
Merge branch 'development' into zip-save-files
Causeless Nov 13, 2023
d621aa6
Fix compile error
Causeless Nov 13, 2023
e16149a
Merge branch 'development' into zip-save-files
Causeless Nov 13, 2023
43f664e
Merge branch 'development' into zip-save-files
Causeless Dec 31, 2023
bfbb06a
Merge remote-tracking branch 'source/zip-save-files' into zip-save-files
traunts Jan 2, 2024
7bb4d19
Merge branch 'development' into zip-save-files
traunts Jan 2, 2024
2c6d289
Merge branch 'development' into zip-save-files
Causeless Jan 5, 2024
59ffb04
Merge branch 'development' into zip-save-files
Causeless Jan 20, 2024
b22c5ab
format
HeliumAnt Jan 20, 2024
882b41d
Merge branch 'development' into zip-save-files
HeliumAnt Jan 20, 2024
c0357ba
fix eof newlines
HeliumAnt Jan 20, 2024
5224634
apply doxygen
HeliumAnt Jan 20, 2024
6507b46
Merge branch 'development' into zip-save-files
HeliumAnt Jan 20, 2024
3542ee6
Merge branch 'development' into zip-save-files
Causeless Jan 21, 2024
6294d3d
Merge branch 'development' into zip-save-files
Causeless Jan 21, 2024
43dff35
Merge branch 'development' into zip-save-files
Causeless Jan 21, 2024
4dfdad6
Merge branch 'development' into zip-save-files
Causeless Jan 21, 2024
56bd587
re-run clang format
Causeless Jan 21, 2024
920f01a
Merge branch 'development' into zip-save-files
Causeless Sep 11, 2024
fdef918
Merge branch 'development' into zip-save-files
Causeless Dec 7, 2024
bb24006
Merge branch 'development' into zip-save-files
Causeless Dec 7, 2024
0853cbe
Merge branch 'development' into zip-save-files
Causeless Feb 28, 2025
1d3c878
Merge branch 'development' into zip-save-files
Causeless Jul 6, 2025
2bc4bdd
added comments so I kinda vaguely remember what I need to do
Causeless Jul 6, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions Source/Entities/Scene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,31 @@ int Scene::SaveData(std::string pathBase, bool doAsyncSaves) {
return 0;
}

std::vector<SceneLayerInfo> Scene::GetCopiedSceneLayerBitmaps() {
std::vector<SceneLayerInfo> layerInfos;

// TODO- implement this. Basically just copy each scene layer to the vector
// This is for saving- we block to copy these layers, but it means the actual saving (compression, write to disk etc) can be done async
/*
// Save Terrain's data
m_pTerrain

// Don't bother saving background layers to disk, as they are never altered

// Save unseen layers' data
char str[64];
for (int team = Activity::TeamOne; team < Activity::MaxTeamCount; ++team)
{
if (m_apUnseenLayer[team])
{
m_apUnseenLayer[team]
}
}
*/

return layerInfos;
}

int Scene::SavePreview(const std::string& bitmapPath) {
// Do not save preview for MetaScenes!
if (!m_MetasceneParent.empty()) {
Expand Down
7 changes: 7 additions & 0 deletions Source/Entities/Scene.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ namespace RTE {
class SceneObject;
class Deployment;

struct SceneLayerInfo {
std::string name;
std::unique_ptr<BITMAP> bitmap;
};

/// Contains everything that defines a complete scene.
class Scene : public Entity {

Expand Down Expand Up @@ -246,6 +251,8 @@ namespace RTE {
/// Anything below 0 is an error signal.
int SaveData(std::string pathBase, bool doAsyncSaves = true);

std::vector<SceneLayerInfo> GetCopiedSceneLayerBitmaps();

/// Saves preview bitmap for this scene.
/// @param bitmapPath The full filepath the where to save the Bitmap data.
int SavePreview(const std::string& bitmapPath);
Expand Down
10 changes: 10 additions & 0 deletions Source/Entities/SceneLayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,16 @@ int SceneLayerImpl<TRACK_DRAWINGS, STATIC_TEXTURE>::SaveData(const std::string&
return 0;
}

template <bool TRACK_DRAWINGS, bool STATIC_TEXTURE>
std::unique_ptr<BITMAP> SceneLayerImpl<TRACK_DRAWINGS, STATIC_TEXTURE>::CopyBitmap() {
BITMAP* outputBitmap = create_bitmap_ex(bitmap_color_depth(m_MainBitmap), m_MainBitmap->w, m_MainBitmap->h);
if (m_MainBitmap) {
outputBitmap = create_bitmap_ex(bitmap_color_depth(m_MainBitmap), m_MainBitmap->w, m_MainBitmap->h);
blit(m_MainBitmap, outputBitmap, 0, 0, 0, 0, m_MainBitmap->w, m_MainBitmap->h);
}
return std::unique_ptr<BITMAP>(outputBitmap);
}

template <bool TRACK_DRAWINGS, bool STATIC_TEXTURE>
int SceneLayerImpl<TRACK_DRAWINGS, STATIC_TEXTURE>::ClearData() {
if (m_MainBitmap && m_MainBitmapOwned) {
Expand Down
4 changes: 4 additions & 0 deletions Source/Entities/SceneLayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ namespace RTE {
/// Clears out any previously loaded bitmap data from memory.
/// @return An error return value signaling success or any particular failure. Anything below 0 is an error signal.
virtual int ClearData();

/// Copies the bitmap.
/// @return The copied bitmap.
std::unique_ptr<BITMAP> CopyBitmap();
#pragma endregion

#pragma region Getters and Setters
Expand Down
37 changes: 34 additions & 3 deletions Source/Managers/ActivityMan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

#include "MusicMan.h"

#include "zip.h"
#include "unzip.h"

using namespace RTE;

ActivityMan::ActivityMan() {
Expand Down Expand Up @@ -79,6 +82,8 @@ bool ActivityMan::SaveCurrentGame(const std::string& fileName) {
return false;
}

// THIS BLOCK OF CODE NEEDS ZIPPIFIED!
/*
// TODO, save to a zip instead of a directory
std::filesystem::create_directory(g_PresetMan.GetFullModulePath(c_UserScriptedSavesModuleName) + "/" + fileName);

Expand All @@ -87,6 +92,7 @@ bool ActivityMan::SaveCurrentGame(const std::string& fileName) {
g_ConsoleMan.PrintString("ERROR: Failed to save scene bitmaps while saving!");
return false;
}
*/

// We need a copy of our scene, because we have to do some fixup to remove PLACEONLOAD items and only keep the current MovableMan state.
std::unique_ptr<Scene> modifiableScene(dynamic_cast<Scene*>(scene->Clone()));
Expand All @@ -103,8 +109,17 @@ bool ActivityMan::SaveCurrentGame(const std::string& fileName) {
modifiableScene->GetTerrain()->SetPresetName(fileName);
modifiableScene->GetTerrain()->MigrateToModule(g_PresetMan.GetModuleID(c_UserScriptedSavesModuleName));

// Create zip sav file
zipFile zippedSaveFile = zipOpen((g_PresetMan.GetFullModulePath(c_UserScriptedSavesModuleName) + "/" + fileName + ".ccsave").c_str(), APPEND_STATUS_CREATE);
if (!zippedSaveFile) {
g_ConsoleMan.PrintString("ERROR: Couldn't create zip save file!");
return false;
}

std::unique_ptr<std::stringstream> iniStream = std::make_unique<std::stringstream>();

// Block the main thread for a bit to let the Writer access the relevant data.
std::unique_ptr<Writer> writer(std::make_unique<Writer>(g_PresetMan.GetFullModulePath(c_UserScriptedSavesModuleName) + "/" + fileName + "/Save.ini"));
std::unique_ptr<Writer> writer(std::make_unique<Writer>(std::move(iniStream)));
writer->NewPropertyWithValue("Activity", activity);

// Pull all stuff from MovableMan into the Scene for saving, so existing Actors/ADoors are saved, without transferring ownership, so the game can continue.
Expand All @@ -121,8 +136,22 @@ bool ActivityMan::SaveCurrentGame(const std::string& fileName) {
writer->NewPropertyWithValue("PlaceUnitsIfSceneIsRestarted", g_SceneMan.GetPlaceUnitsOnLoad());
writer->NewPropertyWithValue("Scene", modifiableScene.get());

auto saveWriterData = [](Writer* writerToSave) {
writerToSave->EndWrite();
auto saveWriterData = [&](Writer* writerToSave) {
std::stringstream* stream = static_cast<std::stringstream*>(writerToSave->GetStream());
stream->flush();

// Ugly copies, but eh. todo - use a string stream that just gives us a raw buffer to grab at
std::string streamAsString = stream->str();

zip_fileinfo zfi = {0};

const int defaultCompression = 6;
zipOpenNewFileInZip(zippedSaveFile, (fileName + ".ini").c_str(), &zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, defaultCompression);
zipWriteInFileInZip(zippedSaveFile, streamAsString.data(), streamAsString.size());
zipCloseFileInZip(zippedSaveFile);

zipClose(zippedSaveFile, fileName.c_str());

delete writerToSave;
};

Expand All @@ -139,6 +168,8 @@ bool ActivityMan::SaveCurrentGame(const std::string& fileName) {
bool ActivityMan::LoadAndLaunchGame(const std::string& fileName) {
m_SaveGameTask.wait();

// TODO- this needs to load a zip!

std::string saveFilePath = g_PresetMan.GetFullModulePath(c_UserScriptedSavesModuleName) + "/" + fileName + "/Save.ini";

if (!std::filesystem::exists(saveFilePath)) {
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/FrameMan.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace RTE {
std::shared_ptr<RenderTarget> GetBackBuffer() const { return m_BackBuffer; }
#pragma endregion

#pragma region Split - Screen Handling
#pragma region Split-Screen Handling
/// Gets whether the screen is split horizontally across the screen, ie as two splitscreens one above the other.
/// @return Whether or not screen has a horizontal split.
bool GetHSplit() const { return m_HSplit; }
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/LuaMan.h
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ namespace RTE {
const std::unordered_map<std::string, PerformanceMan::ScriptTiming> GetScriptTimings() const;
#pragma endregion

#pragma region File I / O Handling
#pragma region File I/O Handling
/// Returns a vector of all the directories in path, which is relative to the working directory.
/// @param path Directory path relative to the working directory.
/// @return A vector of the directories in path.
Expand Down
2 changes: 1 addition & 1 deletion Source/Managers/WindowMan.h
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ namespace RTE {
void AttemptToRevertToPreviousResolution(bool revertToDefaults = false);
#pragma endregion

#pragma region Multi - Display Handling
#pragma region Multi-Display Handling
/// Clears all the multi-display data, resetting the game to a single-window-single-display state.
void ClearMultiDisplayData();

Expand Down
10 changes: 5 additions & 5 deletions Source/Menus/SaveLoadMenuGUI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,18 +82,18 @@ void SaveLoadMenuGUI::PopulateSaveGamesList() {

std::string saveFilePath = g_PresetMan.GetFullModulePath(c_UserScriptedSavesModuleName) + "/";
for (const auto& entry: std::filesystem::directory_iterator(saveFilePath)) {
if (entry.is_directory()) {
if (entry.path().extension() == ".ccsave" && entry.path().filename() != "Index.ini") {
SaveRecord record;
record.SavePath = entry.path();
record.SaveDate = std::filesystem::last_write_time(entry.path() / "Save.ini");
record.SaveDate = entry.last_write_time();
m_SaveGames.push_back(record);
}
}

std::for_each(std::execution::par_unseq,
m_SaveGames.begin(), m_SaveGames.end(),
[](SaveRecord& record) {
Reader reader(record.SavePath.string() + "/Save.ini", true, nullptr, true);
Reader reader(record.SavePath.string(), true, nullptr, true);

bool readActivity = false;
bool readSceneName = false;
Expand Down Expand Up @@ -191,9 +191,9 @@ void SaveLoadMenuGUI::CreateSave() {
}

void SaveLoadMenuGUI::DeleteSave() {
std::string saveFilePath = g_PresetMan.GetFullModulePath(c_UserScriptedSavesModuleName) + "/" + m_SaveGameName->GetText();
std::string saveFilePath = g_PresetMan.GetFullModulePath(c_UserScriptedSavesModuleName) + "/" + m_SaveGameName->GetText() + ".ccsave";

std::filesystem::remove_all(saveFilePath);
std::filesystem::remove(saveFilePath);
g_GUISound.ConfirmSound()->Play();

PopulateSaveGamesList();
Expand Down
2 changes: 1 addition & 1 deletion Source/System/Constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,7 @@ namespace RTE {
{Directions::Right, 0.0F}};
#pragma endregion

#pragma region Un - Definitions
#pragma region Un-Definitions
// Allegro defines these via define in astdint.h and Boost with stdlib go crazy so we need to undefine them manually.
#undef int8_t
#undef uint8_t
Expand Down
2 changes: 1 addition & 1 deletion Source/System/System.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ namespace RTE {
static bool PathExistsCaseSensitive(const std::string& pathToCheck);
#pragma endregion

#pragma region Command - Line Interface
#pragma region Command-Line Interface
/// Tells whether printing loading progress report and console to command-line is enabled or not.
/// @return Whether printing to command-line is enabled or not.
static bool IsLoggingToCLI() { return s_LogToCLI; }
Expand Down
3 changes: 3 additions & 0 deletions Source/System/Writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ namespace RTE {
/// @return Whether the writer is ready to start accepting data streamed to it or not.
bool WriterOK() const { return m_Stream.get() && m_Stream->good(); }

/// Returns the underlying stream.
std::ostream* GetStream() { return m_Stream.get(); }

/// Flushes and closes the output stream of this Writer. This happens automatically at destruction but needs to be called manually if a written file must be read from in the same scope.
void EndWrite() {
m_Stream->flush();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ APNG_FUNC(BITMAP *, load_memory_png, (AL_CONST void *buffer, int buffer_size, RG
/* Save a bitmap to disk in PNG format. */
APNG_FUNC(int, save_png, (AL_CONST char *filename, BITMAP *bmp, AL_CONST RGB *pal));

/* Save a bitmap to a PACKFILE in PNG format. */
APNG_FUNC(int, save_png_pf, (PACKFILE *pack, BITMAP *bmp, AL_CONST RGB *pal));

/* Adds `PNG' to Allegro's internal file type table.
* You can then just use load_bitmap and save_bitmap as usual.
*/
Expand Down
14 changes: 14 additions & 0 deletions external/sources/allegro 4.4.3.1-custom/addons/loadpng/savepng.c
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,17 @@ int save_png(AL_CONST char *filename, BITMAP *bmp, AL_CONST RGB *pal)

return result;
}

int save_png_pf(PACKFILE *pack, BITMAP *bmp, AL_CONST RGB *pal)
{
int result;

ASSERT(pack);
ASSERT(bmp);

acquire_bitmap(bmp);
result = really_save_png(pack, bmp, pal);
release_bitmap(bmp);

return result;
}