Reading files and external assets
Checked UTF-8 reads, write failures and PNG loading without editor import.
Text: a complete example
Attach FileDemo.cs to an ordinary Entity, build and run the level. It creates user://io-demo/message.txt, writes two UTF-8 lines, reads them and prints the text followed by File demo: Ok. Another run overwrites this teaching file.
WriteText() accepts a path defined by your code. ReadText() returns an empty string on failure and limits input to 1 MiB. Here OutOfMemory means the example's size limit was exceeded. using closes the file even on an early return.
FileDemo.cs
using System.Text;
using Reckfell;
public partial class FileDemo : Entity
{
public const string Folder = "user://io-demo";
public const string Note = Folder + "/message.txt";
public const int MaxBytes = 1024 * 1024;
private static readonly UTF8Encoding Utf8 = new(false, true);
public static Error WriteNote(string text) => WriteText(Note, text);
public static Error WriteText(string path, string text)
{
try
{
if (Utf8.GetByteCount(text) > MaxBytes)
return Error.OutOfMemory;
}
catch (EncoderFallbackException)
{
return Error.InvalidData;
}
Error error = DirAccess.MakeDirRecursiveAbsolute(path.GetBaseDir());
if (error != Error.Ok)
return error;
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Write);
if (file is null)
return FileAccess.GetOpenError();
return file.StoreString(text) ? file.GetError() : Error.FileCantWrite;
}
public static Error ReadText(string path, out string text)
{
text = "";
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
if (file is null)
return FileAccess.GetOpenError();
ulong length = file.GetLength();
if (length > MaxBytes)
return Error.OutOfMemory;
byte[] bytes = file.GetBuffer((long)length);
if ((ulong)bytes.Length != length)
return Error.FileCorrupt;
try
{
text = Utf8.GetString(bytes);
return Error.Ok;
}
catch (DecoderFallbackException)
{
return Error.InvalidData;
}
}
public override void _Ready()
{
Error error = WriteNote("Reckfell: привет\nSecond line");
if (error == Error.Ok)
{
error = ReadText(Note, out string text);
if (error == Error.Ok)
ReckfellApi.Print(text);
}
ReckfellApi.Print($"File demo: {error}");
}
}Open() returns null on failure. Obtain GetOpenError() immediately, before another file open in the same thread. Check both opening and writing. Reading distinguishes an empty file from failure and rejects malformed UTF-8.
This is a direct synchronous overwrite, without backup or power-loss recovery guarantees. Use the replacement and backup slot for progress. Do not write the same slot from several tasks concurrently.
Open modes
| Mode | Behavior |
|---|---|
Read | Reads an existing file |
Write | Creates a file or truncates an existing one |
ReadWrite | Reads and writes an existing file without initial truncation |
WriteRead | Creates or truncates, then permits writing and reading |
To append, open an existing file with ReadWrite and call SeekEnd(). Use Write if it does not exist yet. Writing from the beginning without truncating can leave an old suffix.
External image
Copy a PNG to user://io-demo/preview.png. Create a TextureRect and an ordinary Entity with ExternalPreview.cs; assign its Target. The first load runs in _Ready(). Call Reload() from your button handler for another load. Error.Ok means the new texture was assigned; failure retains the old one. Give TextureRect a nonzero size to make it visible. Enable WithMipmaps for downsampling with a suitable filter or for 3D use.
ExternalPreview.cs
using Reckfell;
public partial class ExternalPreview : Entity
{
[Export] public string ImagePath { get; set; } = "user://io-demo/preview.png";
[Export] public TextureRect Target { get; set; } = null!;
[Export] public bool WithMipmaps { get; set; }
public override void _Ready() => ReckfellApi.Print(Reload());
public Error Reload()
{
if (!ReckfellObject.IsInstanceValid(Target))
return Error.Unconfigured;
if (!FileAccess.FileExists(ImagePath))
return Error.FileNotFound;
using var image = new Image();
Error error = image.Load(ImagePath);
if (error != Error.Ok)
return error;
if (WithMipmaps)
{
error = image.GenerateMipmaps();
if (error != Error.Ok)
return error;
}
ImageTexture texture = ImageTexture.CreateFromImage(image);
Target.Texture = texture;
return Error.Ok;
}
}Load project images with ResourceLoader.Load<Texture2D>(); the export may contain an imported resource rather than the source PNG.
Other external formats
| Data | API and next step |
|---|---|
| WAV, Ogg Vorbis, MP3 | AudioStreamWav.LoadFromFile(), AudioStreamOggVorbis.LoadFromFile(), AudioStreamMP3.LoadFromFile(); check the result, assign AudioStreamPlayer.Stream, then Play() |
| TTF / OTF | Create a new FontFile, call LoadDynamicFont(path) and check the file/data; Error.Ok alone is insufficient. Assign through a theme or AddThemeFontOverride() after checking |
| glTF / GLB | GltfDocument.AppendFromFile(path, state) → check Error.Ok → GenerateScene(state) → AddChild() on the main thread |
| ZIP | ZipReader.Open() → GetFiles() / FileExists() → ReadFile() → Close(); reading an entry does not extract it to disk |
| Write ZIP | ZipPacker.Open() → StartFile() → WriteFile() → CloseFile() → Close(); check each Error |
| Custom C# binary format | byte[], System.Buffers.Binary.BinaryPrimitives and an explicit byte-order convention; byte[] has no Variant-array decode_* methods |
For fonts, first check file existence, nonempty size and an acceptable read limit. This implementation resets state, reads bytes, assigns them and returns Ok without checking decoding success. Nonempty FontFile.Data establishes only that data exist: also check required characters and display. Reload into a new resource so the currently used font is not reset before validation.
An external model does not pass through the full editor import pipeline; your code must configure behavior, collisions and allowed dependencies. Define file-size limits and accepted formats for your application. Reading ZIP entries into memory does not itself bound the decompressed size.
Diagnostics
| Failure | Check |
|---|---|
FileNotFound | Actual path, case and file existence |
CantCreate, FileNoPermission | Parent directory, permissions, space and file access |
FileCorrupt | The number of read bytes differed from the file length |
InvalidData in the text example | The file is not valid UTF-8 |
Font returns Ok but text is absent | File, size limit, FontFile.Data, supported format and required characters; Ok does not establish decoding success |
| Asset works in the editor but is missing after export | Use the imported-resource loader or explicitly ship the external source file |
| Texture loads but remains invisible | Target, TextureRect size and visibility; for 3D, material and surface |