From eb472e1020af9d97c4bbe237d095c72cd2b4acc2 Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Sun, 29 Sep 2024 18:17:18 +0300 Subject: [PATCH] ScriptAPI: implemented File.GetFileTime() and Game.GetSaveSlotTime() --- Common/core/assetmanager.cpp | 24 +++++++++++++ Common/core/assetmanager.h | 3 ++ Common/util/file.cpp | 6 ++++ Common/util/file.h | 2 ++ Common/util/stdio_compat.c | 41 ++++++++++++++++------ Common/util/stdio_compat.h | 2 ++ Editor/AGS.Editor/Resources/agsdefns.sh | 10 ++++++ Engine/ac/dynobj/scriptdatetime.cpp | 14 ++++++++ Engine/ac/dynobj/scriptdatetime.h | 3 ++ Engine/ac/file.cpp | 28 +++++++++++++++ Engine/ac/game.cpp | 15 ++++++++ Engine/platform/base/agsplatformdriver.cpp | 15 ++------ 12 files changed, 140 insertions(+), 23 deletions(-) diff --git a/Common/core/assetmanager.cpp b/Common/core/assetmanager.cpp index 3796a9b201..001c1052e4 100644 --- a/Common/core/assetmanager.cpp +++ b/Common/core/assetmanager.cpp @@ -177,6 +177,30 @@ bool AssetManager::DoesAssetExist(const String &asset_name, const String &filter return false; } +bool AssetManager::GetAssetTime(const String &asset_name, time_t &ft, const String &filter) const +{ + for (const auto *lib : _activeLibs) + { + if (!lib->TestFilter(filter)) continue; // filter does not match + + if (IsAssetLibDir(lib)) + { + String filename = File::FindFileCI(lib->BaseDir, asset_name); + if (!filename.IsEmpty()) + { + ft = File::GetFileTime(filename); + return true; + } + } + else + { + ft = File::GetFileTime(lib->RealLibFiles[0]); + return true; + } + } + return false; +} + void AssetManager::FindAssets(std::vector &assets, const String &wildcard, const String &filter) const { diff --git a/Common/core/assetmanager.h b/Common/core/assetmanager.h index 0fc3f98562..3ba97fdefc 100644 --- a/Common/core/assetmanager.h +++ b/Common/core/assetmanager.h @@ -123,6 +123,9 @@ class AssetManager // Tells whether asset exists in any of the registered search locations bool DoesAssetExist(const String &asset_name, const String &filter = "") const; inline bool DoesAssetExist(const AssetPath &apath) const { return DoesAssetExist(apath.Name, apath.Filter); } + // Tries to get asset's "file time" (last modification time). + // Note that for the assets packed within a CLIB format this will return library's time instead. + bool GetAssetTime(const String &asset_name, time_t &ft, const String &filter = "") const; // Searches in all the registered locations and collects a list of // assets using given wildcard pattern // TODO: variant accepting std::regex instead of wildcard, and replace uses where convenient diff --git a/Common/util/file.cpp b/Common/util/file.cpp index d9302f163d..f320b07594 100644 --- a/Common/util/file.cpp +++ b/Common/util/file.cpp @@ -73,6 +73,12 @@ soff_t File::GetFileSize(const String &filename) return size; } +time_t File::GetFileTime(const String &filename) +{ + return ags_file_time(filename.GetCStr()); + // NOTE: ANDROID's AAsset storage seems to be unapplicable here +} + bool File::TestReadFile(const String &filename) { FILE *test_file = ags_fopen(filename.GetCStr(), "rb"); diff --git a/Common/util/file.h b/Common/util/file.h index 0b029d35aa..4522647c9e 100644 --- a/Common/util/file.h +++ b/Common/util/file.h @@ -58,6 +58,8 @@ namespace File bool IsFileOrDir(const String &filename); // Returns size of a file, or -1 if no such file found soff_t GetFileSize(const String &filename); + // Returns file's last writing time, or time_t() if no such file found + time_t GetFileTime(const String &filename); // Tests if file could be opened for reading bool TestReadFile(const String &filename); // Opens a file for writing or creates new one if it does not exist; deletes file if it was created during test diff --git a/Common/util/stdio_compat.c b/Common/util/stdio_compat.c index 962e98e98d..87ed34ccb3 100644 --- a/Common/util/stdio_compat.c +++ b/Common/util/stdio_compat.c @@ -126,17 +126,17 @@ int ags_directory_exists(const char *path) int ags_path_exists(const char *path) { - #if AGS_PLATFORM_OS_WINDOWS - WCHAR wstr[MAX_PATH_SZ]; - MultiByteToWideChar(CP_UTF8, 0, path, -1, wstr, MAX_PATH_SZ); - return PathFileExistsW(wstr); - #else - struct stat path_stat; - if (stat(path, &path_stat) != 0) { - return 0; - } - return S_ISREG(path_stat.st_mode) || S_ISDIR(path_stat.st_mode); - #endif +#if AGS_PLATFORM_OS_WINDOWS + WCHAR wstr[MAX_PATH_SZ]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wstr, MAX_PATH_SZ); + return PathFileExistsW(wstr); +#else + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return 0; + } + return S_ISREG(path_stat.st_mode) || S_ISDIR(path_stat.st_mode); +#endif } file_off_t ags_file_size(const char *path) @@ -158,6 +158,25 @@ file_off_t ags_file_size(const char *path) #endif } +time_t ags_file_time(const char *path) +{ +#if AGS_PLATFORM_OS_WINDOWS + WCHAR wstr[MAX_PATH_SZ]; + MultiByteToWideChar(CP_UTF8, 0, path, -1, wstr, MAX_PATH_SZ); + struct _stat64 path_stat; + if (_wstat64(wstr, &path_stat) != 0) { + return -1; + } + return path_stat.st_mtime; +#else + struct stat path_stat; + if (stat(path, &path_stat) != 0) { + return -1; + } + return path_stat.st_mtime; +#endif +} + int ags_file_remove(const char *path) { #if AGS_PLATFORM_OS_WINDOWS diff --git a/Common/util/stdio_compat.h b/Common/util/stdio_compat.h index eea223965f..d7853baad0 100644 --- a/Common/util/stdio_compat.h +++ b/Common/util/stdio_compat.h @@ -16,6 +16,7 @@ #include #include +#include typedef int64_t file_off_t; @@ -38,6 +39,7 @@ int ags_file_exists(const char *path); int ags_directory_exists(const char *path); int ags_path_exists(const char *path); file_off_t ags_file_size(const char *path); +time_t ags_file_time(const char *path); int ags_file_remove(const char *path); int ags_file_rename(const char *src, const char *dst); diff --git a/Editor/AGS.Editor/Resources/agsdefns.sh b/Editor/AGS.Editor/Resources/agsdefns.sh index 734547f2bb..c7ee2352a0 100644 --- a/Editor/AGS.Editor/Resources/agsdefns.sh +++ b/Editor/AGS.Editor/Resources/agsdefns.sh @@ -1278,6 +1278,8 @@ enum FileSeek { }; #endif +builtin managed struct DateTime; + builtin managed struct File { /// Delets the specified file from the disk. import static bool Delete(const string filename); // $AUTOCOMPLETESTATICONLY$ @@ -1328,6 +1330,10 @@ builtin managed struct File { import static String ResolvePath(const string filename); // $AUTOCOMPLETESTATICONLY$ /// Gets the path to opened file. readonly import attribute String Path; +#endif +#ifdef SCRIPT_API_v362 + /// Retrieves specified file's last write time; returns null if file does not exist + import static DateTime* GetFileTime(const string filename); // $AUTOCOMPLETESTATICONLY$ #endif int reserved[2]; // $AUTOCOMPLETEIGNORE$ }; @@ -3045,6 +3051,10 @@ builtin struct Game { /// Preloads and caches sprites and linked sounds for a view, within a selected range of loops. import static void PrecacheView(int view, int first_loop, int last_loop); #endif +#ifdef SCRIPT_API_v362 + /// Gets the write time of the specified save game slot. + import static DateTime* GetSaveSlotTime(int saveSlot); +#endif }; builtin struct GameState { diff --git a/Engine/ac/dynobj/scriptdatetime.cpp b/Engine/ac/dynobj/scriptdatetime.cpp index 6484992033..567af544e6 100644 --- a/Engine/ac/dynobj/scriptdatetime.cpp +++ b/Engine/ac/dynobj/scriptdatetime.cpp @@ -27,6 +27,20 @@ const char *ScriptDateTime::GetType() { return "DateTime"; } +void ScriptDateTime::SetFromStdTime(time_t time) +{ + // NOTE: subject to year 2038 problem due to shoving time_t in an integer + rawUnixTime = static_cast(time); + + struct tm *newtime = localtime(&time); + hour = newtime->tm_hour; + minute = newtime->tm_min; + second = newtime->tm_sec; + day = newtime->tm_mday; + month = newtime->tm_mon + 1; + year = newtime->tm_year + 1900; +} + size_t ScriptDateTime::CalcSerializeSize(const void* /*address*/) { return sizeof(int32_t) * 7; diff --git a/Engine/ac/dynobj/scriptdatetime.h b/Engine/ac/dynobj/scriptdatetime.h index 9a408f4617..7ec586f6c9 100644 --- a/Engine/ac/dynobj/scriptdatetime.h +++ b/Engine/ac/dynobj/scriptdatetime.h @@ -18,6 +18,7 @@ #ifndef __AGS_EE_DYNOBJ__SCRIPTDATETIME_H #define __AGS_EE_DYNOBJ__SCRIPTDATETIME_H +#include #include "ac/dynobj/cc_agsdynamicobject.h" struct ScriptDateTime final : AGSCCDynamicObject { @@ -29,6 +30,8 @@ struct ScriptDateTime final : AGSCCDynamicObject { const char *GetType() override; void Unserialize(int index, AGS::Common::Stream *in, size_t data_sz) override; + void SetFromStdTime(time_t time); + ScriptDateTime(); protected: diff --git a/Engine/ac/file.cpp b/Engine/ac/file.cpp index 12eb843fa7..1fb35cc045 100644 --- a/Engine/ac/file.cpp +++ b/Engine/ac/file.cpp @@ -54,6 +54,28 @@ int File_Exists(const char *fnmm) { return 1; // was found in fs } +ScriptDateTime* File_GetFileTime(const char *fnmm) { + const auto rp = ResolveScriptPathAndFindFile(fnmm, true); + if (!rp) + return nullptr; + + time_t ft; + if (rp.AssetMgr) + { + if (!AssetMgr->GetAssetTime(rp.FullPath, ft, "*")) + return nullptr; + } + else + { + ft = File::GetFileTime(rp.FullPath); + } + + ScriptDateTime *sdt = new ScriptDateTime(); + sdt->SetFromStdTime(ft); + ccRegisterManagedObject(sdt, sdt); + return sdt; +} + int File_Delete(const char *fnmm) { const auto rp = ResolveScriptPathAndFindFile(fnmm, false); if (!rp) @@ -763,6 +785,11 @@ RuntimeScriptValue Sc_File_Exists(const RuntimeScriptValue *params, int32_t para API_SCALL_INT_POBJ(File_Exists, const char); } +RuntimeScriptValue Sc_File_GetFileTime(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_POBJ(ScriptDateTime, File_GetFileTime, const char); +} + // void *(const char *fnmm, int mode) RuntimeScriptValue Sc_sc_OpenFile(const RuntimeScriptValue *params, int32_t param_count) { @@ -884,6 +911,7 @@ void RegisterFileAPI() ScFnRegister file_api[] = { { "File::Delete^1", API_FN_PAIR(File_Delete) }, { "File::Exists^1", API_FN_PAIR(File_Exists) }, + { "File::GetFileTime^1", API_FN_PAIR(File_GetFileTime) }, { "File::Open^2", API_FN_PAIR(sc_OpenFile) }, { "File::ResolvePath^1", API_FN_PAIR(File_ResolvePath) }, diff --git a/Engine/ac/game.cpp b/Engine/ac/game.cpp index 47a1983ad0..29f3a1eb44 100644 --- a/Engine/ac/game.cpp +++ b/Engine/ac/game.cpp @@ -417,6 +417,15 @@ const char* Game_GetSaveSlotDescription(int slnum) { return nullptr; } +ScriptDateTime* Game_GetSaveSlotTime(int slnum) +{ + time_t ft = File::GetFileTime(get_save_game_path(slnum)); + ScriptDateTime *sdt = new ScriptDateTime(); + sdt->SetFromStdTime(ft); + ccRegisterManagedObject(sdt, sdt); + return sdt; +} + void restore_game_dialog() { restore_game_dialog2(1, LEGACY_TOP_BUILTINDIALOGSAVESLOT); } @@ -1608,6 +1617,11 @@ RuntimeScriptValue Sc_Game_GetSaveSlotDescription(const RuntimeScriptValue *para API_SCALL_OBJ_PINT(const char, myScriptStringImpl, Game_GetSaveSlotDescription); } +RuntimeScriptValue Sc_Game_GetSaveSlotTime(const RuntimeScriptValue *params, int32_t param_count) +{ + API_SCALL_OBJAUTO_PINT(ScriptDateTime, Game_GetSaveSlotTime); +} + // ScriptViewFrame* (int viewNumber, int loopNumber, int frame) RuntimeScriptValue Sc_Game_GetViewFrame(const RuntimeScriptValue *params, int32_t param_count) { @@ -1882,6 +1896,7 @@ void RegisterGameAPI() { "Game::GetMODPattern^0", API_FN_PAIR(Game_GetMODPattern) }, { "Game::GetRunNextSettingForLoop^2", API_FN_PAIR(Game_GetRunNextSettingForLoop) }, { "Game::GetSaveSlotDescription^1", API_FN_PAIR(Game_GetSaveSlotDescription) }, + { "Game::GetSaveSlotTime^1", API_FN_PAIR(Game_GetSaveSlotTime) }, { "Game::GetViewFrame^3", API_FN_PAIR(Game_GetViewFrame) }, { "Game::InputBox^1", API_FN_PAIR(Game_InputBox) }, { "Game::SetSaveGameDirectory^1", API_FN_PAIR(Game_SetSaveGameDirectory) }, diff --git a/Engine/platform/base/agsplatformdriver.cpp b/Engine/platform/base/agsplatformdriver.cpp index be0c05eb19..a28f539cb3 100644 --- a/Engine/platform/base/agsplatformdriver.cpp +++ b/Engine/platform/base/agsplatformdriver.cpp @@ -78,19 +78,10 @@ const char *AGSPlatformDriver::GetDiskWriteAccessTroubleshootingText() return "Make sure you have write permissions, and also check the disk's free space."; } -void AGSPlatformDriver::GetSystemTime(ScriptDateTime *sdt) { +void AGSPlatformDriver::GetSystemTime(ScriptDateTime *sdt) +{ time_t t = time(nullptr); - - //note: subject to year 2038 problem due to shoving time_t in an integer - sdt->rawUnixTime = static_cast(t); - - struct tm *newtime = localtime(&t); - sdt->hour = newtime->tm_hour; - sdt->minute = newtime->tm_min; - sdt->second = newtime->tm_sec; - sdt->day = newtime->tm_mday; - sdt->month = newtime->tm_mon + 1; - sdt->year = newtime->tm_year + 1900; + sdt->SetFromStdTime(t); } void AGSPlatformDriver::DisplayAlert(const char *text, ...)