# V2 API Reference

The V2 API is the recommended surface for `FEX.Core.dll`. Every function
returns an `int32` result code, every string crosses the boundary as UTF-8,
and every variable-sized output is a DLL-allocated buffer the caller frees
with `FreeAllocatedBuffer`.

This page documents one function at a time with the Pascal signature, the
canonical C# `[DllImport]`, parameters, return codes, and a worked example.
For the underlying types, see [types-and-constants.md](types-and-constants.md).
For result-code diagnostics, see [error-codes.md](error-codes.md). For buffer
ownership rules, see [memory-management.md](memory-management.md). For the
column-windowed extraction surface used on huge datasets, see
[v3-api.md](v3-api.md).

- [Concepts at a glance](#concepts-at-a-glance)
- [Lifecycle](#lifecycle)
  - [InitLibrary](#initlibrary)
  - [OpenImage](#openimage)
  - [CloseImage](#closeimage)
  - [CloseAllImages](#closeallimages)
  - [ReadFileSystem](#readfilesystem)
- [Image metadata](#image-metadata)
  - [GetVersionAsJSON](#getversionasjson)
  - [GetLicenseInfoAsJSON](#getlicenseinfoasjson)
  - [GetImageTypeByFilename](#getimagetypebyfilename)
  - [GetImageTypeAsString](#getimagetypeasstring)
  - [CheckImageTypeLogical](#checkimagetypelogical)
  - [GetImageInfoByFilenameAsJSON](#getimageinfobyfilenameasjson)
  - [GetImageInfoByIdAsJSON](#getimageinfobyidasjson)
  - [GetPartitionInfoAsJSON](#getpartitioninfoasjson)
  - [GetImageSize](#getimagesize)
- [File records](#file-records)
  - [GetFileSystemRecords_V2](#getfilesystemrecords_v2)
  - [GetFilePath](#getfilepath)
  - [GetItemInfo](#getiteminfo)
- [File data](#file-data)
  - [GetFileSize](#getfilesize)
  - [ReadFileData](#readfiledata)
  - [ReadImageData](#readimagedata)
- [File properties](#file-properties)
  - [GetAvailableProperties](#getavailableproperties)
  - [GetFilePropertiesFromPathAsJSON](#getfilepropertiesfrompathasjson)
  - [GetFilePropertiesAsJSON](#getfilepropertiesasjson)
- [Buffer cleanup](#buffer-cleanup)
  - [FreeAllocatedBuffer](#freeallocatedbuffer)
- [Handle-based streaming](#handle-based-streaming)

---

## Concepts at a glance

| Topic | Detail |
|---|---|
| Calling convention | `cdecl` |
| String encoding | UTF-8, both directions |
| Result type | `int32` — `0` = `RESULT_OK`, negative = error, positive = function-specific (e.g. `OpenImage` returns image ID) |
| V2 buffer pattern | DLL allocates; caller frees with `FreeAllocatedBuffer(IntPtr)` |
| Thread safety | Full — concurrent calls against the same `ImageID` are safe |
| Library load | `FEX.Core.dll` (Windows), `libFEX.Core.so` (Linux), `libFEX.Core.dylib` (macOS) |

The C# bindings shown below are the canonical pattern used by the FexViewer
example. They use `[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]`
on every entry point and `[MarshalAs(UnmanagedType.LPStr)]` for UTF-8 string
inputs.

---

## Lifecycle

### `InitLibrary`

Verifies the license key and unlocks the rest of the API. Must be called
exactly once per process before any other export.

**Pascal signature:**

```pascal
function InitLibrary(licenseKey: PAnsiChar): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int InitLibrary([MarshalAs(UnmanagedType.LPStr)] string? licenseKey);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `licenseKey` | UTF-8 string | in | GetData license key (`FEX2-…`). Pass `null` to read from the `FEX_LICENSE_KEY` environment variable. |

**Returns:** `RESULT_OK` (0) on success, `RESULT_INVALIDKEY` (-20) if rejected.

**Example:**

```csharp
string? key = Environment.GetEnvironmentVariable("FEX_LICENSE_KEY");
int rc = NativeMethods.InitLibrary(key);
if (rc != NativeMethods.RESULT_OK)
{
    Console.Error.WriteLine($"License initialization failed (code {rc}).");
    Environment.Exit(2);
}
```

Pass the license key issued by GetData (the `FEX2-…` string). Contact
`sales@getdata.com` for evaluation keys; see
[License Keys](../license-keys.md) for installation guidance.

---

### `OpenImage`

Opens a forensic image and returns an image-session ID for use in subsequent
calls.

**Pascal signature:**

```pascal
function OpenImage(filename: PAnsiChar; optionsString: PAnsiChar;
                   callback: TCallbackFunction; userData: Pointer): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int OpenImage(
    [MarshalAs(UnmanagedType.LPStr)] string filename,
    [MarshalAs(UnmanagedType.LPStr)] string? optionsString,
    IntPtr callback,
    IntPtr userData);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `filename` | UTF-8 string | in | Absolute path to the first image segment (`.E01`, `.001`, `.dd`, etc.). Multi-segment images are auto-detected from this path. |
| `optionsString` | UTF-8 JSON or null | in | JSON options object — currently used to pass passwords. Pass `null` for unencrypted images. |
| `callback` | function pointer | in | Optional progress callback (`TCallbackFunction`). Pass `IntPtr.Zero` to disable. |
| `userData` | pointer | in | Opaque value forwarded to the callback. Pass `IntPtr.Zero` if unused. |

**Returns:** Positive integer image-session ID on success. Negative on failure
— most commonly:

| Code | Meaning |
|---:|---|
| `RESULT_NOFILEFOUND` (-3) | File doesn't exist |
| `RESULT_IMAGENOTSUPPORTED` (-4) | Format not recognised |
| `RESULT_COULDNOTOPENIMAGE` (-6) | Generic open failure (corrupt / locked) |
| `RESULT_PASSWORD_REQUIRED` (-31) | Encrypted image, password not supplied |
| `RESULT_PASSWORD_INCORRECT` (-32) | Password supplied but wrong |

**`optionsString` JSON shape:**

```json
{
  "Password": "MyVolumePassword"
}
```

For BitLocker recovery-key files, pass the path to the `.bek` file in
`Password` — the DLL detects key-file format. Multiple credentials can be
tried by closing and re-opening; the FexViewer manifest format wraps this
loop and is documented in `sdk/examples/csharp/README.md`.

**Example:**

```csharp
int imageId = NativeMethods.OpenImage(
    @"C:\evidence\disk.E01",
    optionsString: null,
    callback: IntPtr.Zero,
    userData: IntPtr.Zero);

if (imageId < 0)
    throw new FexCoreException($"OpenImage failed", imageId);

try
{
    // ... use imageId for queries ...
}
finally
{
    NativeMethods.CloseImage(imageId);
}
```

When `OpenImage` returns `RESULT_PASSWORD_REQUIRED` or
`RESULT_PASSWORD_INCORRECT`, no session is allocated — do not call
`CloseImage`. Retry with `optionsString` set instead.

---

### `CloseImage`

Releases the resources associated with an image-session ID. Safe to call
while reads are still in flight on other threads — destruction is deferred
until the last reader returns.

**Pascal signature:**

```pascal
function CloseImage(ImageID: int32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int CloseImage(int imageId);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | ID returned by `OpenImage`. |

**Returns:** `RESULT_OK` on success, `RESULT_IMAGEIDNOTFOUND` (-5) if the ID
is unknown.

**Example:**

```csharp
int rc = NativeMethods.CloseImage(imageId);
if (rc != NativeMethods.RESULT_OK)
    Console.Error.WriteLine($"CloseImage warned: {rc}");
```

A close failure is informational — the session is gone either way.

---

### `CloseAllImages`

Closes every open image session in the current process. Useful at shutdown
or on a process-wide reset.

**Pascal signature:**

```pascal
function CloseAllImages: int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int CloseAllImages();
```

**Returns:** `RESULT_OK`.

---

### `ReadFileSystem`

Parses the filesystem of an open image and reports the discovered file
count. Must be called once before `GetFileSystemRecords_V2`,
`GetFileSystemRecordsCustom_V2`, `GetFileSystemRecordsCustom_V3`, or any
indexed read like `ReadFileData`.

**Pascal signature:**

```pascal
function ReadFileSystem(ImageID: int32; out FileCount: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ReadFileSystem(int imageId, out uint fileCount);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. |
| `fileCount` | `uint32` | out | Total number of filesystem records discovered. |

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_NOFILESYSTEM`,
`RESULT_UNSUPPORTED_FILESYSTEM`, `RESULT_CORRUPTED_FILESYSTEM`.

**Example:**

```csharp
int rc = NativeMethods.ReadFileSystem(imageId, out uint count);
if (rc != NativeMethods.RESULT_OK)
    throw new FexCoreException("ReadFileSystem", rc);

Console.WriteLine($"Discovered {count} filesystem records.");
```

---

## Image metadata

### `GetVersionAsJSON`

Returns DLL version information as a UTF-8 JSON document.

**Pascal signature:**

```pascal
function GetVersionAsJSON(out Buffer: PByte; out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetVersionAsJSON(out IntPtr buffer, out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 string. **Caller must free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Byte count including the null terminator. |

**Returns:** `RESULT_OK`, `RESULT_ERROR`, `RESULT_BUFFER_OVERFLOW`.

**JSON output:**

```json
{
  "App Author": "GetData Forensics",
  "App Name": "FEXCore",
  "OEM App Name": "",
  "Major": 1,
  "Minor": 4,
  "ReleaseNo": 0,
  "BuildNo": 1234,
  "Flags": 0,
  "OEMFlags": 0
}
```

The version JSON uses descriptive labels rather than the camelCase keys
seen elsewhere in the SDK; this output is informational and intended for
display rather than programmatic parsing.

**Example:**

```csharp
IntPtr buffer = IntPtr.Zero;
try
{
    int rc = NativeMethods.GetVersionAsJSON(out buffer, out uint size);
    if (rc != NativeMethods.RESULT_OK)
        throw new FexCoreException("GetVersionAsJSON", rc);

    string json = Marshal.PtrToStringUTF8(buffer, (int)size)?.TrimEnd('\0') ?? "";
    Console.WriteLine(json);
}
finally
{
    if (buffer != IntPtr.Zero) NativeMethods.FreeAllocatedBuffer(buffer);
}
```

---

### `GetLicenseInfoAsJSON`

Returns the active licence details — including the holder name, company, and
email embedded in newer keys — as a UTF-8 JSON document. Only valid after a
successful `InitLibrary`; before that it returns `RESULT_INVALIDKEY`.

**Pascal signature:**

```pascal
function GetLicenseInfoAsJSON(out Buffer: PByte; out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetLicenseInfoAsJSON(out IntPtr buffer, out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 string. **Caller must free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Byte count including the null terminator. |

**Returns:** `RESULT_OK`, `RESULT_INVALIDKEY` (library not initialized), `RESULT_ERROR`.

**JSON output:**

```json
{
  "version": 3,
  "cid": 110,
  "name": "Jane Examiner",
  "company": "Example Forensics Ltd",
  "email": "jane@example.com",
  "features": "0xFFFFFFFFFFFFFFFF",
  "issued": "2026-06-04",
  "expires": "2026-12-31",
  "daysRemaining": 210
}
```

Keys issued before holder embedding return empty `name`, `company`, and
`email` strings (with `"version": 2`). `features` is a hex string to avoid
64-bit precision loss in JSON consumers; dates are `yyyy-mm-dd`.

**Example:**

```csharp
IntPtr buffer = IntPtr.Zero;
try
{
    int rc = NativeMethods.GetLicenseInfoAsJSON(out buffer, out uint size);
    if (rc != NativeMethods.RESULT_OK)
        throw new FexCoreException("GetLicenseInfoAsJSON", rc);

    string json = Marshal.PtrToStringUTF8(buffer, (int)size)?.TrimEnd('\0') ?? "";
    Console.WriteLine(json);
}
finally
{
    if (buffer != IntPtr.Zero) NativeMethods.FreeAllocatedBuffer(buffer);
}
```

See [License keys](../license-keys.md) for the end-to-end licensing guide.

---

### `GetImageTypeByFilename`

Returns the integer image-type code for a file based on extension and
header sniffing. Cheap — does not open the image.

**Pascal signature:**

```pascal
function GetImageTypeByFilename(filename: PAnsiChar; out ImageType: int32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetImageTypeByFilename(
    [MarshalAs(UnmanagedType.LPStr)] string filename,
    out int imageType);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `filename` | UTF-8 string | in | Absolute path. |
| `imageType` | `int32` | out | Numeric type code. Pass to `GetImageTypeAsString` for the human-readable name. |

**Returns:** `RESULT_OK` on detection, `RESULT_NOFILEFOUND`,
`RESULT_IMAGENOTSUPPORTED`.

**Example:**

```csharp
int rc = NativeMethods.GetImageTypeByFilename(@"C:\evidence\disk.E01", out int code);
if (rc == NativeMethods.RESULT_OK)
    Console.WriteLine($"Type code: {code}");
```

---

### `GetImageTypeAsString`

Translates an image-type code (from `GetImageTypeByFilename`) to a
human-readable name like `"E01"` or `"DMG"`.

**Pascal signature:**

```pascal
function GetImageTypeAsString(anImageType: int32; out Buffer: PByte;
                              out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetImageTypeAsString(
    int imageType,
    out IntPtr buffer,
    out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageType` | `int32` | in | Code from `GetImageTypeByFilename`. |
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 string. **Free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Bytes including null. |

**Returns:** `RESULT_OK`, `RESULT_ERROR`, `RESULT_BUFFER_OVERFLOW`.

---

### `CheckImageTypeLogical`

Tests whether a given image-type code represents a *logical* image (file-list
container like L01 / Lx01 / AD1) versus a physical disk image (E01 / DD).

**Pascal signature:**

```pascal
function CheckImageTypeLogical(anImageType: int32; out IsLogical: int32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int CheckImageTypeLogical(int imageType, out int isLogical);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageType` | `int32` | in | Code from `GetImageTypeByFilename`. |
| `isLogical` | `int32` | out | `1` if logical, `0` if physical. |

**Returns:** `RESULT_OK`, `RESULT_IMAGENOTSUPPORTED`.

---

### `GetImageInfoByFilenameAsJSON`

Returns image metadata as JSON without keeping the image open. Useful for
fast inventory passes.

**Pascal signature:**

```pascal
function GetImageInfoByFilenameAsJSON(filename: PUTF8Char;
                                      out Buffer: PByte;
                                      out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int GetImageInfoByFilenameAsJSON(
    [MarshalAs(UnmanagedType.LPStr)] string filename,
    out IntPtr buffer,
    out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `filename` | UTF-8 string | in | Absolute image path. |
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 JSON. **Free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Byte count incl. null. |

**Returns:** `RESULT_OK`, `RESULT_NOFILEFOUND`, `RESULT_COULDNOTOPENIMAGE`,
`RESULT_INVALID_UTF8`, `RESULT_PATH_TRAVERSAL`, `RESULT_BUFFER_OVERFLOW`,
`RESULT_PASSWORD_REQUIRED`, `RESULT_PASSWORD_INCORRECT`. The encryption
codes mean the format-recognition succeeded but the metadata block requires
a password — use `OpenImage` with credentials and switch to
`GetImageInfoByIdAsJSON` for those.

**JSON shape (camelCase per current convention):**

```json
{
  "deviceName": "Samsung SSD 860 EVO 250GB",
  "deviceDescription": "Internal HDD #2",
  "deviceSize": 250059350016,
  "imageType": "E01",
  "segmentCount": 12,
  "isLogical": false,
  "filename": "disk.E01",
  "md5":  "5d41402abc4b2a76b9719d911017c592",
  "sha1": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
  "encaseInformation": {
    "caseName": "OPS-2026-001",
    "evidenceNumber": "2",
    "examiner": "L. Investigator"
  }
}
```

Format-specific blocks like `encaseInformation` are present when the format
carries acquisition metadata; absent otherwise.

**Example:**

```csharp
IntPtr buf = IntPtr.Zero;
try
{
    int rc = NativeMethods.GetImageInfoByFilenameAsJSON(
        @"C:\evidence\disk.E01", out buf, out uint size);
    if (rc != NativeMethods.RESULT_OK)
        throw new FexCoreException("GetImageInfoByFilenameAsJSON", rc);

    string json = Marshal.PtrToStringUTF8(buf, (int)size)?.TrimEnd('\0') ?? "";
    var meta = JsonSerializer.Deserialize<ImageMetadata>(json);
    Console.WriteLine($"Device: {meta?.DeviceName}, size: {meta?.DeviceSize:N0} bytes");
}
finally
{
    if (buf != IntPtr.Zero) NativeMethods.FreeAllocatedBuffer(buf);
}
```

---

### `GetImageInfoByIdAsJSON`

Returns metadata for an already-opened image session. Equivalent JSON shape
to `GetImageInfoByFilenameAsJSON`, plus runtime-only fields populated after
parsing (e.g. confirmed filesystem type).

**Pascal signature:**

```pascal
function GetImageInfoByIdAsJSON(ImageID: int32;
                                out Buffer: PByte;
                                out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int GetImageInfoByIdAsJSON(int imageId, out IntPtr buffer, out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. |
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 JSON. **Free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Bytes incl. null. |

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_IMAGENODATA`,
`RESULT_BUFFER_OVERFLOW`.

Use this in preference to `GetImageInfoByFilenameAsJSON` whenever you've
already opened the image — it skips re-parsing the headers.

---

### `GetPartitionInfoAsJSON`

Returns a JSON array describing every partition in the image (location,
size, partition-table style, filesystem type, VBR fingerprint).

**Pascal signature:**

```pascal
function GetPartitionInfoAsJSON(ImageID: int32;
                                out Buffer: PByte;
                                out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetPartitionInfoAsJSON(int imageId, out IntPtr buffer, out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. |
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 JSON array. **Free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Bytes incl. null. |

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_IMAGENODATA`,
`RESULT_BUFFER_OVERFLOW`.

---

### `GetImageSize`

Returns the logical size of an opened image in bytes — i.e. the size of the
acquired device, not the on-disk size of the segment files.

**Pascal signature:**

```pascal
function GetImageSize(ImageID: int32; out Size: int64): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetImageSize(int imageId, out long size);
```

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`.

---

## File records

### `GetFileSystemRecords_V2`

Returns every filesystem record in the image as a flat array of
`TFileRecord` structs. The single allocation owns all contained pointers
(filenames, children lists). Free with one call to `FreeAllocatedBuffer`.

**Pascal signature:**

```pascal
function GetFileSystemRecords_V2(ImageID: int32;
                                 out FileCount: uint32;
                                 out Records: PFileRecord;
                                 out RecordArraySize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetFileSystemRecords_V2(
    int imageId,
    out uint fileCount,
    out IntPtr records,
    out uint recordArraySize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. Must already have had `ReadFileSystem` called. |
| `fileCount` | `uint32` | out | Number of records in the array. |
| `records` | `IntPtr` | out | DLL-allocated array of `TFileRecord`. **Free with `FreeAllocatedBuffer`.** |
| `recordArraySize` | `uint32` | out | Total byte size of the array allocation. |

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_OUT_OF_MEMORY`,
`RESULT_BUFFER_OVERFLOW` (datasets so large that `count × sizeof` overflows
`uint32` — switch to V3 in that case).

**Example:**

```csharp
NativeMethods.ReadFileSystem(imageId, out _);

IntPtr records = IntPtr.Zero;
try
{
    int rc = NativeMethods.GetFileSystemRecords_V2(
        imageId, out uint count, out records, out _);
    if (rc != NativeMethods.RESULT_OK)
        throw new FexCoreException("GetFileSystemRecords_V2", rc);

    int stride = Marshal.SizeOf<FileRecordNative>();
    IntPtr cur = records;
    for (int i = 0; i < count; i++)
    {
        var native = Marshal.PtrToStructure<FileRecordNative>(cur);
        string name = native.filename != IntPtr.Zero
            ? Marshal.PtrToStringUTF8(native.filename) ?? ""
            : "";

        bool isFolder  = (native.status & StatusFlags.FILESTATUS_FOLDER)  != 0;
        bool isDeleted = (native.status & StatusFlags.FILESTATUS_DELETED) != 0;

        Console.WriteLine($"{native.fileBatesId,8}  {(isFolder ? "D" : "F")}{(isDeleted ? "x" : " ")}  {native.logicalSize,12:N0}  {name}");

        cur = IntPtr.Add(cur, stride);
    }
}
finally
{
    if (records != IntPtr.Zero) NativeMethods.FreeAllocatedBuffer(records);
}
```

`FileRecordNative` and `StatusFlags` are defined in
[types-and-constants.md](types-and-constants.md). Resolve every `filename`
to a managed `string` *before* you call `FreeAllocatedBuffer` — the
pointers are dangling after the free.

---

### `GetFilePath`

Returns the full hierarchical path of a single record by walking parent
pointers from the root.

**Pascal signature:**

```pascal
function GetFilePath(ImageID: int32; FileIndex: int32;
                     out Buffer: PByte; out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetFilePath(
    int imageId, int fileIndex, out IntPtr buffer, out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. |
| `fileIndex` | `int32` | in | Index into the record array (matches `parentIndex` semantics). |
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 path string. **Free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Bytes incl. null. |

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_FILEINDEXNOTFOUND`,
`RESULT_BUFFER_OVERFLOW`.

For most use cases it's faster to reconstruct paths in C# from the flat
record array rather than calling `GetFilePath` per record. See the
`PathBuilder` class in `sdk/examples/csharp/Models/` for the pattern.

---

### `GetItemInfo`

Returns metadata for a single record as JSON, with selectable detail level.

**Pascal signature:**

```pascal
function GetItemInfo(ImageID: int32; FileIndex: int32; InfoType: int32;
                     out Buffer: PByte; out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetItemInfo(
    int imageId, int fileIndex, int infoType,
    out IntPtr buffer, out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. |
| `fileIndex` | `int32` | in | Index into the record array. |
| `infoType` | `int32` | in | `0` = full (dates + hashes), `1` = basic (dates only), `2` = minimal (id / size / status). |
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 JSON. **Free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Bytes incl. null. |

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_FILEINDEXNOTFOUND`,
`RESULT_NOFILESYSTEM`, `RESULT_BUFFER_OVERFLOW`.

---

## File data

### `GetFileSize`

Returns the size of a single file in the filesystem. Note: this returns the
*physical* size (cluster-rounded). For logical size use the `logicalSize`
field on the record.

**Pascal signature:**

```pascal
function GetFileSize(ImageID: int32; FileIndex: int32; out Size: uint64): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetFileSize(int imageId, int fileIndex, out ulong fileSize);
```

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_FILEINDEXNOTFOUND`,
`RESULT_FILENOSIZE`.

When extracting files, always truncate writes to the `logicalSize` from the
record; otherwise you will write cluster-slack bytes past the end of the
real content.

---

### `ReadFileData`

Reads a range of bytes from a single filesystem entry into a caller-allocated
buffer. This is a caller-allocated function (Pattern 3 in
[memory-management.md](memory-management.md)) — there's no
`FreeAllocatedBuffer` here.

**Pascal signature:**

```pascal
function ReadFileData(ImageID: int32; FileIndex: int32; Offset: int64;
                      Buffer: Pointer; BufferSize: int32;
                      out BytesRead: int32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ReadFileData(
    int imageId, int fileIndex, long offset,
    IntPtr buffer, int bufferSize, out int bytesRead);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. |
| `fileIndex` | `int32` | in | Record index. |
| `offset` | `int64` | in | Byte offset within the file. |
| `buffer` | `IntPtr` | in/out | Caller-allocated unmanaged buffer. |
| `bufferSize` | `int32` | in | Capacity of `buffer` in bytes. |
| `bytesRead` | `int32` | out | Bytes actually read (may be less than `bufferSize` near EOF). |

**Returns:** `RESULT_OK` plus partial read possible, `RESULT_INCOMPLETEREAD`
near EOF, `RESULT_FILEINDEXNOTFOUND`, `RESULT_FILENOSIZE`,
`RESULT_ENCRYPTED_DATA`.

**Example (1 MB chunked extraction):**

```csharp
const int CHUNK = 1024 * 1024;
IntPtr scratch = Marshal.AllocHGlobal(CHUNK);
try
{
    long offset = 0;
    while (offset < logicalSize)
    {
        int want = (int)Math.Min(CHUNK, logicalSize - offset);
        int rc = NativeMethods.ReadFileData(
            imageId, fileIndex, offset, scratch, want, out int got);
        if (rc != NativeMethods.RESULT_OK || got <= 0) break;

        var slice = new byte[got];
        Marshal.Copy(scratch, slice, 0, got);
        outputStream.Write(slice, 0, got);

        offset += got;
    }
}
finally
{
    Marshal.FreeHGlobal(scratch);
}
```

---

### `ReadImageData`

Reads raw bytes from anywhere in the underlying *image* (not a single file).
Useful for low-level analysis — partition tables, filesystem boot sectors,
unallocated space.

**Pascal signature:**

```pascal
function ReadImageData(ImageID: int32; Offset: int64;
                       Buffer: Pointer; BufferSize: int32;
                       out BytesRead: int32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int ReadImageData(
    int imageId, long offset,
    IntPtr buffer, int bufferSize,
    out int bytesRead);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `imageId` | `int32` | in | Image-session ID. |
| `offset` | `int64` | in | Byte offset within the image. |
| `buffer` | `IntPtr` | in/out | Caller-allocated buffer. |
| `bufferSize` | `int32` | in | Capacity. |
| `bytesRead` | `int32` | out | Actual bytes read. |

**Returns:** `RESULT_OK`, `RESULT_INCOMPLETEREAD`, `RESULT_IMAGEIDNOTFOUND`,
`RESULT_ENCRYPTED_DATA`.

---

## File properties

The properties API exposes the rich format-specific metadata that file-type
drivers extract — JPEG EXIF tags, PDF metadata, ZIP entry lists, etc.

### `GetAvailableProperties`

Returns the set of properties available for each filesystem record. Use this
to discover what column names are valid for V3 custom extraction.

**Pascal signature:**

```pascal
function GetAvailableProperties(out Buffer: PByte; out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetAvailableProperties(out IntPtr buffer, out uint bufferSize);
```

**Parameters:**

| Name | Type | Dir | Purpose |
|---|---|---|---|
| `buffer` | `IntPtr` | out | DLL-allocated UTF-8 JSON array. **Free with `FreeAllocatedBuffer`.** |
| `bufferSize` | `uint32` | out | Bytes incl. null. |

**Returns:** `RESULT_OK`, `RESULT_BUFFER_OVERFLOW`.

**JSON shape:**

```json
[
  {"name": "EntryName",   "type": "String",   "typeKind": "tkUString"},
  {"name": "LogicalSize", "type": "Int64",    "typeKind": "tkInt64"},
  {"name": "Modified",    "type": "DateTime", "typeKind": "tkFloat"},
  ...
]
```

---

### `GetFilePropertiesFromPathAsJSON`

Inspects a file on the host filesystem (not inside an image) using the
DLL's file-type drivers. Returns the property tree as JSON.

**Pascal signature:**

```pascal
function GetFilePropertiesFromPathAsJSON(Filename: PUTF8Char;
                                         out Buffer: PByte;
                                         out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern int GetFilePropertiesFromPathAsJSON(
    [MarshalAs(UnmanagedType.LPStr)] string filename,
    out IntPtr buffer, out uint bufferSize);
```

**Returns:** `RESULT_OK`, `RESULT_INVALID_UTF8`, `RESULT_PATH_TRAVERSAL`,
`RESULT_NOFILEFOUND`, `RESULT_IMAGENOTSUPPORTED` (no driver matched the
extension), `RESULT_BUFFER_OVERFLOW`.

---

### `GetFilePropertiesAsJSON`

Same as the path variant, but for a file inside an opened image session.

**Pascal signature:**

```pascal
function GetFilePropertiesAsJSON(ImageID: int32; FileIndex: int32;
                                 out Buffer: PByte;
                                 out BufferSize: uint32): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetFilePropertiesAsJSON(
    int imageId, int fileIndex,
    out IntPtr buffer, out uint bufferSize);
```

**Returns:** `RESULT_OK`, `RESULT_IMAGEIDNOTFOUND`, `RESULT_FILEINDEXNOTFOUND`,
`RESULT_NOFILESYSTEM`, `RESULT_IMAGENOTSUPPORTED`,
`RESULT_COULDNOTOPENIMAGE`, `RESULT_BUFFER_OVERFLOW`.

---

## Buffer cleanup

### `FreeAllocatedBuffer`

Releases any V2 buffer the DLL allocated for the caller. Safe to call with
`IntPtr.Zero` — it returns `RESULT_OK` without doing anything.

**Pascal signature:**

```pascal
function FreeAllocatedBuffer(Buffer: PByte): int32; cdecl;
```

**C# binding:**

```csharp
[DllImport("FEX.Core.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int FreeAllocatedBuffer(IntPtr buffer);
```

**Returns:** `RESULT_OK`, `RESULT_ERROR` (only if the underlying free
itself raised an exception).

Pair this with every V2 export that has an `out IntPtr Buffer` parameter.
For V3 spec arrays use `FreePropertySpecArray` instead — see
[v3-api.md](v3-api.md).

---

## Handle-based streaming

A separate streaming surface exists for callers that want to read very
large files without per-call setup overhead:

- `RequestFileStream(imageId, fileIndex, out streamHandle)`
- `ReadFileStream(streamHandle, offset, buffer, bufferSize, out bytesRead)`
- `ReleaseFileStream(streamHandle)`

This is functionally equivalent to `ReadFileData` but caches the underlying
stream object across reads. Most callers should start with `ReadFileData`
and only adopt the handle-based variant if profiling shows per-call
overhead is a bottleneck. Documentation for these will be expanded in a
future release.
