Перейти к основному содержимому

file

Sandbox-доступ к файловой системе. 8 канонических функций, все single-form lowercase (без алиасов).

Функций8
Проверено вживую8 из 8
Требуемый eventнет
Сайд-эффектычитает, пишет, удаляет, создаёт файлы и директории на диске
Sandbox-кореньдиректория files/ чита (relative paths резолвятся сюда)

Только single form. file.read, file.write и т.д. Без PascalCase алиасов. См. Обзор / Конвенция именования.

Binary-safe. file.write/file.read сохраняют каждый байт включая \0. Проверено на 7-byte payload с null-байтами и high-bit байтами.

Sandbox

Семантика путей, проверено вживую:

  • Relative-пути резолвятся под директорией files/ чита. file.write("data.json", json) приземляется в <cheat>/files/data.json.
  • .. блокирован жёсткой ошибкой: read("../foo") raise'ит "File path cannot contain '..'".
  • Абсолютные Windows-пути обходят sandbox. file.read("C:/Windows/win.ini") вернул реальный контент файла в нашем verify-прогоне. Считай sandbox advisory, не security-границей.
  • write НЕ создаёт parent directories. file.write("subdir/inner.txt", "x") молча возвращает false если subdir/ не существует. Используй сначала mkdir (он рекурсивен).
  • Пустая строка "" и no-arg оба означают root для exists, listdir. Для read/write это error.

Краткий справочник

ФункцияСигнатураЗаметкаСтатус
read(path: string) → string | nilполный контент файла как строкапроверено
write(path: string, content: string) → booloverwrite, НЕ создаёт parent dirsпроверено
append(path: string, content: string) → boolappend, создаёт файл если missingпроверено
delete(path: string) → booltrue на успех, false на missing или non-empty dirпроверено
exists(path: string) → boolработает на файлах и директориях, ""true (root)проверено
isdir(path: string) → booltrue если path существует И является directoryпроверено
mkdir(path: string) → boolрекурсивен, идемпотентен на existing directoryпроверено
listdir(path?: string) → table | nilмассив записей {name, isDirectory, isFile, size?}проверено

read

file.read(path: string) → string | nil

Возвращает весь контент файла как Lua-строку. Binary-safe. Возвращает nil для отсутствующих файлов.

ВызовРезультат
read("data.txt")контент файла как строка
read("missing.txt")nil (без error)
read("../foo")error: "File path cannot contain '..'"
read("C:/Windows/win.ini")абсолютный путь обходит sandbox, возвращает файл
read()"bad argument #1 to '?' (string expected, got no value)"
read(nil)"bad argument #1 to '?' (string expected, got nil)"
local content = file.read("config.json")
if content then
local parsed = decode_json(content)
end

write

file.write(path: string, content: string) → bool

Записывает content в path, перезаписывая существующий файл. Возвращает true на успех, false на silent fail (чаще всего отсутствует parent directory).

ВызовРезультат
write("file.txt", "x")true, файл создан или перезаписан
write("file.txt", "")true, пустой файл
write("subdir/inner.txt", "x")false если subdir/ не существует (silent)
write("file.txt", nil)"bad argument #2 to '?' (string expected, got nil)"

write binary-safe:

file.write("payload.bin", string.char(0, 1, 2, 0xCA, 0xFE, 0xBA, 0xBE))
local back = file.read("payload.bin")
print(#back)

Чтобы создать файл во вложенной директории:

file.mkdir("logs/today")
file.write("logs/today/run.log", "...")

append

file.append(path: string, content: string) → bool

Добавляет content в конец файла. Создаёт файл если не существует, в отличие от write с missing parent dir.

ВызовРезультат
append("log.txt", "AAA"); append("log.txt", "BBB")контент файла становится "AAABBB"
append("brand_new.txt", "CCC")true, файл создан с контентом "CCC"
local function log_line(line)
file.append("session.log", string.format("[%d] %s\n",
utility.GetTickCount(), line))
end

cheat.Register("onSlowUpdate", function()
log_line("heartbeat")
end)

delete

file.delete(path: string) → bool

Удаляет файл или пустую директорию. Возвращает true на успех, false на failure (отсутствующий path, non-empty directory).

ВызовРезультат
delete("file.txt")true, файл удалён
delete("empty_dir")true, директория удалена
delete("non_empty_dir")false, директория всё ещё есть
delete("missing_path")false (silent)
delete()"bad argument #1 to '?' (string expected, got no value)"
delete(nil)"bad argument #1 to '?' (string expected, got nil)"
Нет рекурсивного удаления

Нет встроенного recursive directory removal. Чтобы удалить non-empty directory, walk её через listdir и удаляй каждую запись:

local function rm_rf(path)
if file.isdir(path) then
for _, entry in ipairs(file.listdir(path) or {}) do
rm_rf(path .. "/" .. entry.name)
end
end
file.delete(path)
end

exists

file.exists(path: string) → bool

Возвращает true если что-то (файл или directory) находится по path. "" трактуется как sandbox root и всегда возвращает true.

ВызовРезультат
exists("file.txt")true если файл существует
exists("some_dir")true если директория существует
exists("missing")false
exists("")true (root)
exists(123)false (числовой path молча false, без error)
exists()"bad argument #1 to '?' (string expected, got no value)"
exists(nil)"bad argument #1 to '?' (string expected, got nil)"

isdir

file.isdir(path: string) → bool

Возвращает true только если path существует и указывает на directory. Файлы и missing paths возвращают false без error.

ВызовРезультат
isdir("some_dir")true
isdir("file.txt")false
isdir("missing")false (без error)
isdir(nil)"bad argument #1 to '?' (string expected, got nil)"
if file.isdir("logs") then

end

mkdir

file.mkdir(path: string) → bool

Создаёт directory. Рекурсивен: mkdir("a/b/c") создаёт a, a/b, и a/b/c за один раз. Идемпотентен: возвращает true если directory уже существует.

ВызовРезультат
mkdir("new")true, directory создан
mkdir("existing")true (no-op, без error)
mkdir("a/b/c")true, все 3 уровня созданы
mkdir(nil)"bad argument #1 to '?' (string expected, got nil)"
isdir("a/b/c") послеtrue (проверено)
file.mkdir("cache/preset_v2")
file.write("cache/preset_v2/settings.json", json)

listdir

file.listdir(path?: string) → table | nil

Перечисляет содержимое directory. Без аргументов или "" перечисляет sandbox root.

Каждая запись в возвращаемом массиве:

ПолеТипЗначение
namestringbasename записи
isDirectorybooltrue для subdirectories
isFilebooltrue для обычных файлов
sizenumberразмер в байтах, только если isFile == true

Проверенные return shapes:

ВызовРезультат
listdir("dir_with_3_files_1_subdir")массив из 4 записей
listdir("empty_dir"){} (пустая таблица, НЕ nil)
listdir("missing")nil
listdir("file.txt") (обычный файл)nil
listdir() или listdir("")sandbox root содержимое

Пример ответа (выдержка из verify-прогона, root listing):

{
{ name = "logo.png", isDirectory = false, isFile = true, size = 21816 },
{ name = "serotonin_api_dump_v2.json", isDirectory = false, isFile = true, size = 42954 },
{ name = "test", isDirectory = true, isFile = false },

}
for _, entry in ipairs(file.listdir("logs") or {}) do
if entry.isFile and entry.name:match("%.log$") then
print(string.format("%-30s %d bytes", entry.name, entry.size))
end
end

Паттерны

Atomic-ish JSON config save

local function save_config(path, json)
local tmp = path .. ".tmp"
if file.write(tmp, json) then
file.delete(path)

if file.write(path, json) then
file.delete(tmp)
return true
end
end
return false
end

Рекурсивный обход директорий

local function walk(dir, fn)
for _, entry in ipairs(file.listdir(dir) or {}) do
local full = dir == "" and entry.name or (dir .. "/" .. entry.name)
if entry.isDirectory then
walk(full, fn)
else
fn(full, entry)
end
end
end

walk("", function(path, entry)
print(path, entry.size)
end)

Read-or-default

local function read_or(path, default)
return file.read(path) or default
end

local cfg_text = read_or("settings.json", "{}")

Logger с ротацией

local LOG = "session.log"
local MAX = 1024 * 1024

local function log(line)
local entry = file.listdir("")

local size = 0
for _, e in ipairs(entry or {}) do
if e.name == LOG and e.isFile then size = e.size; break end
end
if size > MAX then
file.delete(LOG .. ".old")

local content = file.read(LOG)
if content then file.write(LOG .. ".old", content) end
file.delete(LOG)
end
file.append(LOG, string.format("[%d] %s\n", utility.GetTickCount(), line))
end

Recursive delete (нет built-in)

local function rm_rf(path)
if file.isdir(path) then
for _, entry in ipairs(file.listdir(path) or {}) do
rm_rf(path .. "/" .. entry.name)
end
end
file.delete(path)
end