/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/config-manager.h" #include "ags/engine/ac/asset_helper.h" #include "ags/shared/ac/audio_clip_type.h" #include "ags/engine/ac/file.h" #include "ags/shared/ac/common.h" #include "ags/engine/ac/game.h" #include "ags/engine/ac/game_setup.h" #include "ags/shared/ac/game_setup_struct.h" #include "ags/engine/ac/global_file.h" #include "ags/engine/ac/path_helper.h" #include "ags/engine/ac/runtime_defines.h" #include "ags/engine/ac/string.h" #include "ags/engine/ac/dynobj/dynobj_manager.h" #include "ags/engine/debugging/debug_log.h" #include "ags/engine/debugging/debugger.h" #include "ags/engine/platform/base/ags_platform_driver.h" #include "ags/shared/util/stream.h" #include "ags/shared/core/asset_manager.h" #include "ags/shared/core/asset.h" #include "ags/engine/main/engine.h" #include "ags/engine/main/game_file.h" #include "ags/shared/util/directory.h" #include "ags/shared/util/path.h" #include "ags/shared/util/string.h" #include "ags/shared/util/string_utils.h" #include "ags/shared/debugging/out.h" #include "ags/engine/script/script_api.h" #include "ags/engine/script/script_runtime.h" #include "ags/engine/ac/dynobj/script_string.h" #include "ags/ags.h" #include "ags/globals.h" namespace AGS3 { using namespace AGS::Shared; // object-based File routines int File_Exists(const char *fnmm) { ResolvedPath rp; if (!ResolveScriptPath(fnmm, true, rp)) return 0; if (rp.AssetMgr) return _GP(AssetMgr)->DoesAssetExist(rp.FullPath); return (File::IsFile(rp.FullPath) || File::IsFile(rp.AltPath)) ? 1 : 0; } int File_Delete(const char *fnmm) { ResolvedPath rp; if (!ResolveScriptPath(fnmm, false, rp)) return 0; if (File::DeleteFile(rp.FullPath)) return 1; if (_G(errnum) == AL_ENOENT && !rp.AltPath.IsEmpty() && rp.AltPath.Compare(rp.FullPath) != 0) return File::DeleteFile(rp.AltPath) ? 1 : 0; return 0; } void *sc_OpenFile(const char *fnmm, int mode) { if ((mode < scFileRead) || (mode > scFileAppend)) quit("!OpenFile: invalid file mode"); sc_File *scf = new sc_File(); if (scf->OpenFile(fnmm, mode) == 0) { delete scf; return nullptr; } ccRegisterManagedObject(scf, scf); return scf; } const char *File_ResolvePath(const char *fnmm) { ResolvedPath rp; ResolveScriptPath(fnmm, true, rp); // Make path pretty - String path = Path::MakeAbsolutePath(rp.FullPath); return CreateNewScriptString(path.GetCStr()); } void File_Close(sc_File *fil) { fil->Close(); } void File_WriteString(sc_File *fil, const char *towrite) { FileWrite(fil->handle, towrite); } void File_WriteInt(sc_File *fil, int towrite) { FileWriteInt(fil->handle, towrite); } void File_WriteRawChar(sc_File *fil, int towrite) { FileWriteRawChar(fil->handle, towrite); } void File_WriteRawInt(sc_File *fil, int towrite) { Stream *out = get_valid_file_stream_from_handle(fil->handle, "FileWriteRawInt"); out->WriteInt32(towrite); } void File_WriteRawLine(sc_File *fil, const char *towrite) { FileWriteRawLine(fil->handle, towrite); } // Reads line of chars until linebreak is met or buffer is filled; // returns whether reached the end of line (false in case not enough buffer); // guarantees null-terminator in the buffer. static bool File_ReadRawLineImpl(sc_File *fil, char *buffer, size_t buf_len) { if (buf_len == 0) return false; Stream *in = get_valid_file_stream_from_handle(fil->handle, "File.ReadRawLine"); for (size_t i = 0; i < buf_len - 1; ++i) { int c = in->ReadByte(); if (c < 0 || c == '\n') // EOF or LF { buffer[i] = 0; return true; } if (c == '\r') // CR or CRLF { c = in->ReadByte(); // Look for '\n', but it may be missing, which is also a valid case if (c >= 0 && c != '\n') in->Seek(-1, kSeekCurrent); buffer[i] = 0; return true; } buffer[i] = c; } buffer[buf_len - 1] = 0; return false; // not enough buffer } void File_ReadRawLine(sc_File *fil, char *buffer) { size_t buflen = check_scstrcapacity(buffer); File_ReadRawLineImpl(fil, buffer, buflen); commit_scstr_update(buffer); } const char *File_ReadRawLineBack(sc_File *fil) { char readbuffer[MAX_MAXSTRLEN]; if (File_ReadRawLineImpl(fil, readbuffer, MAX_MAXSTRLEN)) return CreateNewScriptString(readbuffer); String sbuf = readbuffer; bool done = false; while (!done) { done = File_ReadRawLineImpl(fil, readbuffer, MAX_MAXSTRLEN); sbuf.Append(readbuffer); }; return CreateNewScriptString(sbuf.GetCStr()); } void File_ReadString(sc_File *fil, char *toread) { FileRead(fil->handle, toread); } const char *File_ReadStringBack(sc_File *fil) { Stream *in = get_valid_file_stream_from_handle(fil->handle, "File.ReadStringBack"); if (in->EOS()) { return CreateNewScriptString(""); } size_t lle = (uint32_t)in->ReadInt32(); if (lle == 0) { debug_script_warn("File.ReadStringBack: file was not written by WriteString"); return CreateNewScriptString(""); } char *buffer = CreateNewScriptString(lle); in->Read(buffer, lle); return buffer; } int File_ReadInt(sc_File *fil) { return FileReadInt(fil->handle); } int File_ReadRawChar(sc_File *fil) { return FileReadRawChar(fil->handle); } int File_ReadRawInt(sc_File *fil) { return FileReadRawInt(fil->handle); } int File_Seek(sc_File *fil, int offset, int origin) { Stream *in = get_valid_file_stream_from_handle(fil->handle, "File.Seek"); return in->Seek(offset, (StreamSeek)origin); } int File_GetEOF(sc_File *fil) { if (fil->handle <= 0) return 1; return FileIsEOF(fil->handle); } int File_GetError(sc_File *fil) { if (fil->handle <= 0) return 1; return FileIsError(fil->handle); } int File_GetPosition(sc_File *fil) { if (fil->handle <= 0) return -1; Stream *stream = get_valid_file_stream_from_handle(fil->handle, "File.Position"); // TODO: a problem is that AGS script does not support unsigned or long int return (int)stream->GetPosition(); } const char *File_GetPath(sc_File *fil) { if (fil->handle <= 0) return nullptr; Stream *stream = get_valid_file_stream_from_handle(fil->handle, "File.Path"); return CreateNewScriptString(stream->GetPath()); } //============================================================================= const char *GameInstallRootToken = "$INSTALLDIR$"; const char *UserSavedgamesRootToken = "$MYDOCS$"; const char *GameSavedgamesDirToken = "$SAVEGAMEDIR$"; const char *GameDataDirToken = "$APPDATADIR$"; const char *GameAssetToken = "$DATA$"; const char *UserConfigFileToken = "$CONFIGFILE$"; void FixupFilename(char *filename) { const char *illegal = _G(platform)->GetIllegalFileChars(); for (char *name_ptr = filename; *name_ptr; ++name_ptr) { if (*name_ptr < ' ') { *name_ptr = '_'; } else { for (const char *ch_ptr = illegal; *ch_ptr; ++ch_ptr) if (*name_ptr == *ch_ptr) *name_ptr = '_'; } } } String PathFromInstallDir(const String &path) { if (Path::IsRelativePath(path)) return Path::ConcatPaths(_GP(ResPaths).DataDir, path); return path; } FSLocation PathFromInstallDir(const FSLocation &fsloc) { if (is_relative_filename(fsloc.FullDir)) return FSLocation(_GP(ResPaths).DataDir).Rebase(fsloc.FullDir); return fsloc; } String PreparePathForWriting(const FSLocation &fsloc, const String &filename) { if (Directory::CreateAllDirectories(fsloc.BaseDir, fsloc.SubDir)) return Path::ConcatPaths(fsloc.FullDir, filename); return ""; } FSLocation GetGlobalUserConfigDir() { FSLocation dir = _G(platform)->GetUserGlobalConfigDirectory(); if (is_relative_filename(dir.FullDir)) // relative dir is resolved relative to the game data dir return FSLocation(_GP(ResPaths).DataDir).Rebase(dir.FullDir); return dir; } FSLocation GetGameUserConfigDir() { FSLocation dir = _G(platform)->GetUserConfigDirectory(); if (is_relative_filename(dir.FullDir)) // relative dir is resolved relative to the game data dir return FSLocation(_GP(ResPaths).DataDir).Rebase(dir.FullDir); else if (_GP(usetup).local_user_conf) // directive to use game dir location return FSLocation(_GP(ResPaths).DataDir); // For absolute dir, we assume it's a special directory prepared for AGS engine // and therefore append a game's own subdir return dir.Concat(_GP(game).saveGameFolderName); } // Constructs data dir using rules for default system location inline FSLocation MakeDefaultDataDir(const FSLocation &def_dir) { // Relative dir is resolved relative to the game data dir if (is_relative_filename(def_dir.FullDir)) return FSLocation(_GP(ResPaths).DataDir).Rebase(def_dir.FullDir); // For absolute dir, we assume it's a special directory prepared for AGS engine // and therefore amend it with a game's own subdir (to separate files from different games) return def_dir.Concat(_GP(game).saveGameFolderName); } // Constructs data dir using rules for the user-specified location inline FSLocation MakeUserDataDir(const String &user_dir) { // If user-set location is inside game dir, then form a relative path if (is_relative_filename(user_dir)) return FSLocation(_GP(ResPaths).DataDir).Rebase(user_dir); // Otherwise treat it as an absolute path return FSLocation(Path::MakeAbsolutePath(user_dir)); } FSLocation GetGameAppDataDir() { if (_GP(usetup).shared_data_dir.IsEmpty()) return MakeDefaultDataDir(_G(platform)->GetAllUsersDataDirectory()); return MakeUserDataDir(_GP(usetup).shared_data_dir); } FSLocation GetGameUserDataDir() { if (_GP(usetup).user_data_dir.IsEmpty()) return MakeDefaultDataDir(_G(platform)->GetUserSavedgamesDirectory()); return MakeUserDataDir(_GP(usetup).user_data_dir); } bool ResolveScriptPath(const String &orig_sc_path, bool read_only, ResolvedPath &rp) { rp = ResolvedPath(); // Make sure that the file path has system-compatible form String sc_path = orig_sc_path; sc_path.Replace('\\', '/'); // File tokens (they must be the only thing in script path) if (sc_path.Compare(UserConfigFileToken) == 0) { auto loc = GetGameUserConfigDir(); rp = ResolvedPath(loc, DefaultConfigFileName); return true; } // Test absolute paths if (!Path::IsRelativePath(sc_path)) { if (!read_only) { debug_script_warn("Attempt to access file '%s' denied (cannot write to absolute path)", sc_path.GetCStr()); return false; } rp = ResolvedPath(sc_path); return true; } if (sc_path.CompareLeft(GameAssetToken, strlen(GameAssetToken)) == 0) { if (!read_only) { debug_script_warn("Attempt to access file '%s' denied (cannot write to game assets)", orig_sc_path.GetCStr()); return false; } rp.FullPath = sc_path.Mid(strlen(GameAssetToken) + 1); rp.AssetMgr = true; return true; } FSLocation parent_dir; String child_path; String alt_path; if (sc_path.CompareLeft(GameInstallRootToken) == 0) { if (!read_only) { debug_script_warn("Attempt to access file '%s' denied (cannot write to game installation directory)", sc_path.GetCStr()); return false; } parent_dir = FSLocation(_GP(ResPaths).DataDir); child_path = sc_path.Mid(strlen(GameInstallRootToken)); } else if (sc_path.CompareLeft(GameSavedgamesDirToken) == 0) { parent_dir = get_save_game_directory(); child_path = sc_path.Mid(strlen(GameSavedgamesDirToken)); #if AGS_PLATFORM_SCUMMVM // Remap "agsgame.*" const char *agsSavePrefix = "/agssave."; if (child_path.CompareLeft(agsSavePrefix) == 0) { debugC(::AGS::kDebugFilePath, "Remapping agssave.* to ScummVM savegame files"); String suffix = child_path.Mid(strlen(agsSavePrefix)); if (suffix.CompareLeft("*") == 0) { Common::String file_name = ::AGS::g_vm->getSaveStateName(999); Common::replace(file_name, "999", "*"); child_path = file_name; } else { int slotNum = suffix.ToInt(); child_path = ::AGS::g_vm->getSaveStateName(slotNum); } } #endif } else if (sc_path.CompareLeft(GameDataDirToken) == 0) { #if AGS_PLATFORM_SCUMMVM // the shared data dir should be remapped to the savedir otherwise some games // are unable to create additional files debugC(::AGS::kDebugFilePath, "Remapping %s to save folder", GameDataDirToken); parent_dir = get_save_game_directory(); child_path = sc_path.Mid(strlen(GameSavedgamesDirToken)); #else parent_dir = GetGameAppDataDir(); child_path = sc_path.Mid(strlen(GameDataDirToken)); #endif } else { child_path = sc_path; // For cases where a file is trying to write to a game path, always remap // it to write to a savefile. For normal reading, we thus need to give // preference to any save file with a given name before looking in the // game folder. This for example fixes an issue with The Blackwell Legacy, // which wants to create a new prog.bwl in the game folder parent_dir = FSLocation(SAVE_FOLDER_PREFIX); if (read_only) alt_path = Path::ConcatPaths(_GP(ResPaths).DataDir, sc_path); } // Sometimes we have multiple consecutive slashes or backslashes. // Remove all of them at the start of the child path. while (!child_path.IsEmpty() && (child_path[0u] == '\\' || child_path[0u] == '/')) child_path.ClipLeft(1); #if AGS_PLATFORM_SCUMMVM // For files on savepath, always ensure it starts with the game target prefix to avoid // conflicts (as we usually have the same save dir for all games). // Also flatten the path if needed as we do not support subdirectories in the save folder. if (parent_dir.BaseDir == SAVE_FOLDER_PREFIX) { debugC(::AGS::kDebugFilePath, "Adding ScummVM game target prefix and flatten path"); child_path.Replace('/', '-'); String gameTarget = ConfMan.getActiveDomainName(); // When in Quest for Glory II "import a hero" window, don't add the gamename prefix when listing saves. This way // we can show saves created by the Sierra games if (strcmp(_GP(game).guid, "{a46a9171-f6f9-456c-9b2b-a509b560ddc0}") || !(_G(displayed_room) == 1) || !read_only) { if (child_path.CompareLeftNoCase(gameTarget) != 0) child_path = String::FromFormat("%s-%s", gameTarget.GetCStr(), child_path.GetCStr()); } else { debug("ResolveScriptPath: Skipping gameprefix for QfG2AGDI!"); } } #else // Create a proper ResolvedPath with FSLocation separating base location // (which the engine is not allowed to create) and sub-dirs (created by the engine). parent_dir = parent_dir.Concat(Path::GetDirectoryPath(child_path)); child_path = Path::GetFilename(child_path); #endif ResolvedPath test_rp = ResolvedPath(parent_dir, child_path, alt_path); // don't allow write operations for relative paths outside game dir if (!read_only) { if (!Path::IsSameOrSubDir(test_rp.Loc.FullDir, test_rp.FullPath)) { debug_script_warn("Attempt to access file '%s' denied (outside of game directory)", sc_path.GetCStr()); return false; } } rp = test_rp; debugC(::AGS::kDebugFilePath, "Final path: %s", rp.FullPath.GetCStr()); if (!rp.AltPath.IsEmpty()) debugC(::AGS::kDebugFilePath, "Alt path: %s", rp.AltPath.GetCStr()); return true; } bool ResolveWritePathAndCreateDirs(const String &sc_path, ResolvedPath &rp) { if (!ResolveScriptPath(sc_path, false, rp)) return false; if (!rp.Loc.SubDir.IsEmpty() && !Directory::CreateAllDirectories(rp.Loc.BaseDir, rp.Loc.SubDir)) { debug_script_warn("ResolveScriptPath: failed to create all subdirectories: %s", rp.FullPath.GetCStr()); return false; } return true; } bool CreateFSDirs(const FSLocation &fs) { return Directory::CreateAllDirectories(fs.BaseDir, fs.FullDir); } // // AGS custom PACKFILE callbacks, that use our own Stream object // static int ags_pf_fclose(void *userdata) { delete(AGS_PACKFILE_OBJ *)userdata; return 0; } static int ags_pf_getc(void *userdata) { AGS_PACKFILE_OBJ *obj = (AGS_PACKFILE_OBJ *)userdata; if (obj->remains > 0) { obj->remains--; return obj->stream->ReadByte(); } return -1; } static int ags_pf_ungetc(int /*c*/, void * /*userdata*/) { return -1; // we do not want to support this } static long ags_pf_fread(void *p, long n, void *userdata) { AGS_PACKFILE_OBJ *obj = (AGS_PACKFILE_OBJ *)userdata; if (obj->remains > 0) { size_t read = MIN(obj->remains, (size_t)n); obj->remains -= read; return obj->stream->Read(p, read); } return -1; } static int ags_pf_putc(int /*c*/, void * /*userdata*/) { return -1; // don't support write } static long ags_pf_fwrite(AL_CONST void * /*p*/, long /*n*/, void * /*userdata*/) { return -1; // don't support write } static int ags_pf_fseek(void * /*userdata*/, int /*offset*/) { return -1; // don't support seek } static int ags_pf_feof(void *userdata) { return ((AGS_PACKFILE_OBJ *)userdata)->remains == 0; } static int ags_pf_ferror(void *userdata) { return ((AGS_PACKFILE_OBJ *)userdata)->stream->GetError() ? 1 : 0; } // Custom PACKFILE callback table static PACKFILE_VTABLE ags_packfile_vtable = { ags_pf_fclose, ags_pf_getc, ags_pf_ungetc, ags_pf_fread, ags_pf_putc, ags_pf_fwrite, ags_pf_fseek, ags_pf_feof, ags_pf_ferror }; // PACKFILE *PackfileFromAsset(const AssetPath &path) { Stream *asset_stream = _GP(AssetMgr)->OpenAsset(path); if (!asset_stream) return nullptr; const size_t asset_size = asset_stream->GetLength(); if (asset_size == 0) return nullptr; AGS_PACKFILE_OBJ *obj = new AGS_PACKFILE_OBJ; obj->stream.reset(asset_stream); obj->asset_size = asset_size; obj->remains = asset_size; return pack_fopen_vtable(&ags_packfile_vtable, obj); } String find_assetlib(const String &filename) { String libname = File::FindFileCI(_GP(ResPaths).DataDir, filename); if (AssetManager::IsDataFile(libname)) return libname; if (!_GP(ResPaths).DataDir2.IsEmpty() && Path::ComparePaths(_GP(ResPaths).DataDir, _GP(ResPaths).DataDir2) != 0) { // Hack for running in Debugger libname = File::FindFileCI(_GP(ResPaths).DataDir2, filename); if (AssetManager::IsDataFile(libname)) return libname; } return ""; } AssetPath get_audio_clip_assetpath(int /*bundling_type*/, const String &filename) { // NOTE: bundling_type is ignored now return AssetPath(filename, "audio"); } AssetPath get_voice_over_assetpath(const String &filename) { return AssetPath(filename, "voice"); } ScriptFileHandle *check_valid_file_handle_ptr(Stream *stream_ptr, const char *operation_name) { if (stream_ptr) { for (int i = 0; i < _G(num_open_script_files); ++i) { if (stream_ptr == _G(valid_handles)[i].stream.get()) { return &_G(valid_handles)[i]; } } } String exmsg = String::FromFormat("!%s: invalid file handle; file not previously opened or has been closed", operation_name); quit(exmsg); return nullptr; } ScriptFileHandle *check_valid_file_handle_int32(int32_t handle, const char *operation_name) { if (handle > 0) { for (int i = 0; i < _G(num_open_script_files); ++i) { if (handle == _G(valid_handles)[i].handle) { return &_G(valid_handles)[i]; } } } String exmsg = String::FromFormat("!%s: invalid file handle; file not previously opened or has been closed", operation_name); quit(exmsg); return nullptr; } Stream *get_valid_file_stream_from_handle(int32_t handle, const char *operation_name) { ScriptFileHandle *sc_handle = check_valid_file_handle_int32(handle, operation_name); return sc_handle ? sc_handle->stream.get() : nullptr; } //============================================================================= // // Script API Functions // //============================================================================= // int (const char *fnmm) RuntimeScriptValue Sc_File_Delete(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_POBJ(File_Delete, const char); } // int (const char *fnmm) RuntimeScriptValue Sc_File_Exists(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_INT_POBJ(File_Exists, const char); } // void *(const char *fnmm, int mode) RuntimeScriptValue Sc_sc_OpenFile(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJAUTO_POBJ_PINT(sc_File, sc_OpenFile, const char); } RuntimeScriptValue Sc_File_ResolvePath(const RuntimeScriptValue *params, int32_t param_count) { API_SCALL_OBJ_POBJ(const char, _GP(myScriptStringImpl), File_ResolvePath, const char); } // void (sc_File *fil) RuntimeScriptValue Sc_File_Close(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID(sc_File, File_Close); } // int (sc_File *fil) RuntimeScriptValue Sc_File_ReadInt(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(sc_File, File_ReadInt); } // int (sc_File *fil) RuntimeScriptValue Sc_File_ReadRawChar(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(sc_File, File_ReadRawChar); } // int (sc_File *fil) RuntimeScriptValue Sc_File_ReadRawInt(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(sc_File, File_ReadRawInt); } // void (sc_File *fil, char* buffer) RuntimeScriptValue Sc_File_ReadRawLine(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_POBJ(sc_File, File_ReadRawLine, char); } // const char* (sc_File *fil) RuntimeScriptValue Sc_File_ReadRawLineBack(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_ReadRawLineBack); } // void (sc_File *fil, char *toread) RuntimeScriptValue Sc_File_ReadString(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_POBJ(sc_File, File_ReadString, char); } // const char* (sc_File *fil) RuntimeScriptValue Sc_File_ReadStringBack(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_ReadStringBack); } // void (sc_File *fil, int towrite) RuntimeScriptValue Sc_File_WriteInt(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_PINT(sc_File, File_WriteInt); } // void (sc_File *fil, int towrite) RuntimeScriptValue Sc_File_WriteRawChar(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_PINT(sc_File, File_WriteRawChar); } RuntimeScriptValue Sc_File_WriteRawInt(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_PINT(sc_File, File_WriteRawInt); } // void (sc_File *fil, const char *towrite) RuntimeScriptValue Sc_File_WriteRawLine(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_POBJ(sc_File, File_WriteRawLine, const char); } // void (sc_File *fil, const char *towrite) RuntimeScriptValue Sc_File_WriteString(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_VOID_POBJ(sc_File, File_WriteString, const char); } RuntimeScriptValue Sc_File_Seek(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT_PINT2(sc_File, File_Seek); } // int (sc_File *fil) RuntimeScriptValue Sc_File_GetEOF(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(sc_File, File_GetEOF); } // int (sc_File *fil) RuntimeScriptValue Sc_File_GetError(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(sc_File, File_GetError); } RuntimeScriptValue Sc_File_GetPosition(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_INT(sc_File, File_GetPosition); } RuntimeScriptValue Sc_File_GetPath(void *self, const RuntimeScriptValue *params, int32_t param_count) { API_OBJCALL_OBJ(sc_File, const char, _GP(myScriptStringImpl), File_GetPath); } void RegisterFileAPI() { ScFnRegister file_api[] = { {"File::Delete^1", API_FN_PAIR(File_Delete)}, {"File::Exists^1", API_FN_PAIR(File_Exists)}, {"File::Open^2", API_FN_PAIR(sc_OpenFile)}, {"File::ResolvePath^1", API_FN_PAIR(File_ResolvePath)}, {"File::Close^0", API_FN_PAIR(File_Close)}, {"File::ReadInt^0", API_FN_PAIR(File_ReadInt)}, {"File::ReadRawChar^0", API_FN_PAIR(File_ReadRawChar)}, {"File::ReadRawInt^0", API_FN_PAIR(File_ReadRawInt)}, {"File::ReadRawLine^1", API_FN_PAIR(File_ReadRawLine)}, {"File::ReadRawLineBack^0", API_FN_PAIR(File_ReadRawLineBack)}, {"File::ReadString^1", API_FN_PAIR(File_ReadString)}, {"File::ReadStringBack^0", API_FN_PAIR(File_ReadStringBack)}, {"File::WriteInt^1", API_FN_PAIR(File_WriteInt)}, {"File::WriteRawChar^1", API_FN_PAIR(File_WriteRawChar)}, {"File::WriteRawInt^1", API_FN_PAIR(File_WriteRawInt)}, {"File::WriteRawLine^1", API_FN_PAIR(File_WriteRawLine)}, {"File::WriteString^1", API_FN_PAIR(File_WriteString)}, {"File::Seek^2", API_FN_PAIR(File_Seek)}, {"File::get_EOF", API_FN_PAIR(File_GetEOF)}, {"File::get_Error", API_FN_PAIR(File_GetError)}, {"File::get_Position", API_FN_PAIR(File_GetPosition)}, {"File::get_Path", API_FN_PAIR(File_GetPath)}, }; ccAddExternalFunctions361(file_api); } } // namespace AGS3