Browse the library
Documentation libraryGet started by language
Get started with Unity
Unity cannot load the C# SDK (that SDK needs .NET 8), so a Unity game calls the ClavisAUH C library through P/Invoke, the way Unity uses any native plugin. The same code runs on .NET Framework 4.x. This guide goes from installing the library to your game's build.
5 min read
What you need
- Unity with the Mono or IL2CPP scripting backend, building for Windows x64 or Linux x64. The C library has no macOS, Android or iOS build yet.
- An app on the dashboard. Its Integration › Connect software page has the API address, the App ID, the public key and a Make a test key button. The API address ends in
/api, with no/v1:https://<your ClavisAUH site>/api.
1. Install the C library
Download from Downloads and check the SHA-256:
- Windows:
clavis-c-1.0.0-windows-x64.zip, withclavis.dll: one file that needs only Windows's own DLLs. - Linux:
clavis-c-1.0.0-linux-x64.tar.gz, withlibclavis.so.1. The machine running the game needs the system'slibsodiumandlibcurl(Debian, Ubuntu:apt install libsodium23 libcurl4).
Put clavis.dll in your project's Assets/Plugins/x86_64/. For Linux, copy libclavis.so.1 into the same folder and rename it libclavis.so. In each file's Inspector, pick its platform (Windows x86_64 or Linux x86_64) for both the Editor and Standalone.
2. The minimal code
Create Assets/Scripts/Clavis.cs with the class below, as it is. It uses only DllImport and byte arrays for UTF-8 strings, so Mono and IL2CPP take it the same way:
static class Clavis
{
const string Lib = "clavis";
const CallingConvention Cdecl = CallingConvention.Cdecl;
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_open(
byte[] baseUrl, byte[] appId, byte[] appKey, byte[] stateDir, byte[] appVersion);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern void clavis_close(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_init(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_activate(IntPtr client, byte[] key);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_heartbeat(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_logout(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_offline(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_variable(IntPtr client, byte[] name);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_error_code(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_error_message(IntPtr client, byte[] lang);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_retryable(int error);
/// <summary>UTF-8 with its closing zero, as every clavis_* string argument.</summary>
public static byte[] Utf8(string s) => Encoding.UTF8.GetBytes(s + "\0");
/// <summary>A string the library returns: UTF-8, owned by the client until its next call. Read by hand, as
/// Marshal.PtrToStringUTF8 is missing from .NET Framework and older Unity.</summary>
public static string Text(IntPtr p)
{
if (p == IntPtr.Zero) return "";
int length = 0;
while (Marshal.ReadByte(p, length) != 0) length++;
var bytes = new byte[length];
Marshal.Copy(p, bytes, 0, length);
return Encoding.UTF8.GetString(bytes);
}
}
Open a client for this machine. The API address, App ID and public key are the three values on the Connect software page; in Unity, the state folder should be Application.persistentDataPath. The library keeps the device key and the latest offline token in that folder itself:
IntPtr client = Clavis.clavis_open(
Clavis.Utf8(Env("CLAVIS_BASE_URL")),
Clavis.Utf8(Env("CLAVIS_APP_ID")),
Clavis.Utf8(Env("CLAVIS_APP_KEY")),
// The device key and the last offline token live here, so sessions and offline use carry over between runs.
Clavis.Utf8(Env("CLAVIS_STATE_DIR", ".clavis")),
Clavis.Utf8("1.0.0"));
if (client == IntPtr.Zero)
{
Console.WriteLine("FAIL settings");
return 1;
}
Activate the customer's key. Without a network, the offline token saved last time keeps the game running:
int status = Clavis.clavis_init(client);
if (status == 0) status = Clavis.clavis_activate(client, Clavis.Utf8(Env("CLAVIS_LICENSE")));
if (status != 0)
{
// No answer (network_error, timeout...): the offline token saved last time keeps the software working.
if (Clavis.clavis_retryable(status) != 0 && Clavis.clavis_offline(client) == 0)
{
Console.WriteLine("Offline: using the license saved on this machine");
Console.WriteLine("OK k1 offline");
return 0;
}
Console.WriteLine(Clavis.Text(Clavis.clavis_error_message(client, lang)));
Console.WriteLine("FAIL " + Clavis.Text(Clavis.clavis_error_code(client)));
return 1;
}
The public key only checks signatures, so embedding it in your game is safe. Never embed any secret key.
3. Put it in the game
- Each call waits for the network, up to a few seconds, so never call on Unity's main thread: the game would freeze. Create a background thread (
System.Threading.Thread) that holds the client from opening to closing. - Use one client on one thread only. The background thread opens the client, activates, then sends heartbeats on a schedule; the main thread only reads the results it leaves (license state, error sentence), in
Update()for example. - Strings the library returns stay valid only until the next call on that client;
Clavis.Textalready copies them into C# strings. - Call
clavis_closewhen the game quits (OnApplicationQuit).
4. Run it
Before putting it in Unity, you can run the same code with .NET, without opening Unity:
- On the Connect software page, select Make a test key.
- Create a console project with
dotnet new console -n ClavisCheck, deleteProgram.csand put the wholeK1.csbelow in its place. - Put the library's unpacked folder on the PATH, set the four settings and run
dotnet run.
// K1 from C# through the C library: the way Unity, and any .NET that cannot take the C# SDK, calls
// ClavisAUH. Only DllImport and byte arrays, so Mono, IL2CPP and .NET Framework take it as it is (checked against
// netstandard2.0 and C# 9). Put clavis.dll (one file) beside the program or on the PATH, or libclavis.so /
// libclavis.dylib; in Unity, in Assets/Plugins.
// Settings: CLAVIS_BASE_URL, CLAVIS_APP_ID, CLAVIS_APP_KEY, CLAVIS_LICENSE, CLAVIS_STATE_DIR, CLAVIS_LANG.
using System;
using System.Runtime.InteropServices;
using System.Text;
static class Clavis
{
const string Lib = "clavis";
const CallingConvention Cdecl = CallingConvention.Cdecl;
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_open(
byte[] baseUrl, byte[] appId, byte[] appKey, byte[] stateDir, byte[] appVersion);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern void clavis_close(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_init(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_activate(IntPtr client, byte[] key);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_heartbeat(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_logout(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_offline(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_variable(IntPtr client, byte[] name);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_error_code(IntPtr client);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern IntPtr clavis_error_message(IntPtr client, byte[] lang);
[DllImport(Lib, CallingConvention = Cdecl)]
public static extern int clavis_retryable(int error);
/// <summary>UTF-8 with its closing zero, as every clavis_* string argument.</summary>
public static byte[] Utf8(string s) => Encoding.UTF8.GetBytes(s + "\0");
/// <summary>A string the library returns: UTF-8, owned by the client until its next call. Read by hand, as
/// Marshal.PtrToStringUTF8 is missing from .NET Framework and older Unity.</summary>
public static string Text(IntPtr p)
{
if (p == IntPtr.Zero) return "";
int length = 0;
while (Marshal.ReadByte(p, length) != 0) length++;
var bytes = new byte[length];
Marshal.Copy(p, bytes, 0, length);
return Encoding.UTF8.GetString(bytes);
}
}
static class Program
{
static string Env(string name, string fallback = "") => Environment.GetEnvironmentVariable(name) ?? fallback;
static int Main()
{
// Messages in Vietnamese need a UTF-8 console.
Console.OutputEncoding = Encoding.UTF8;
byte[] lang = Clavis.Utf8(Env("CLAVIS_LANG", "en"));
IntPtr client = Clavis.clavis_open(
Clavis.Utf8(Env("CLAVIS_BASE_URL")),
Clavis.Utf8(Env("CLAVIS_APP_ID")),
Clavis.Utf8(Env("CLAVIS_APP_KEY")),
// The device key and the last offline token live here, so sessions and offline use carry over between runs.
Clavis.Utf8(Env("CLAVIS_STATE_DIR", ".clavis")),
Clavis.Utf8("1.0.0"));
if (client == IntPtr.Zero)
{
Console.WriteLine("FAIL settings");
return 1;
}
try
{
int status = Clavis.clavis_init(client);
if (status == 0) status = Clavis.clavis_activate(client, Clavis.Utf8(Env("CLAVIS_LICENSE")));
if (status != 0)
{
// No answer (network_error, timeout...): the offline token saved last time keeps the software working.
if (Clavis.clavis_retryable(status) != 0 && Clavis.clavis_offline(client) == 0)
{
Console.WriteLine("Offline: using the license saved on this machine");
Console.WriteLine("OK k1 offline");
return 0;
}
Console.WriteLine(Clavis.Text(Clavis.clavis_error_message(client, lang)));
Console.WriteLine("FAIL " + Clavis.Text(Clavis.clavis_error_code(client)));
return 1;
}
// Every few minutes while the software runs; sessions end after an hour without one.
Clavis.clavis_heartbeat(client);
string motd = Clavis.Text(Clavis.clavis_variable(client, Clavis.Utf8("motd")));
Console.WriteLine("Message of the day: " + motd);
Clavis.clavis_logout(client);
Console.WriteLine("OK k1 active");
return 0;
}
finally
{
Clavis.clavis_close(client);
}
}
}
$env:PATH = "C:\path\to\clavis-c-1.0.0-windows-x64;$env:PATH"
$env:CLAVIS_BASE_URL = "<API address>"
$env:CLAVIS_APP_ID = "<App ID>"
$env:CLAVIS_APP_KEY = "<public key>"
$env:CLAVIS_LICENSE = "<test key>"
dotnet run
The last line reads OK k1 active. On Linux, use LD_LIBRARY_PATH pointing at the folder with libclavis.so instead of the PATH.
5. See it on the dashboard
- Integration › Connect software: step 4 turns to Connected, with
c/1.0.0(the C library) and the machine's operating system. - Licensing & releases › Licenses: the key goes from Unused to Active.
- End users › Devices & sessions: the machine that ran it, and the session it opened.
6. Handle errors
Every int function returns 0 on success, or a fixed error number (the C column of the error codes table). After a failure:
clavis_error_code(client): the code as text, such asinvalid_credentialsordevice_limit.clavis_error_message(client, "en"): a sentence for the player, in English or"vi".clavis_retryable(number): not0when no answer arrived; trying again later may work. Thenclavis_offline(client)returns0when the saved offline token is still good on this machine.
While the game runs, send a heartbeat regularly from the background thread, every 5 to 30 minutes or so: a session ends after 60 minutes without one.
// Every few minutes while the software runs; sessions end after an hour without one.
Clavis.clavis_heartbeat(client);
string motd = Clavis.Text(Clavis.clavis_variable(client, Clavis.Utf8("motd")));
Console.WriteLine("Message of the day: " + motd);
Offline tokens come on every plan and last 7 days by default; Premium chooses the length, and Online-Only Mode turns them off. See Offline licenses.
7. Build and release
- Write the API address, App ID and public key in a script or a ScriptableObject of the game.
- Unity copies plugins into the build's
Pluginsfolder. Check the build on a clean machine: the game must reach the activated state with nothing else installed (Windows) or with onlylibsodiumandlibcurl(Linux). - Pass your game's version to
clavis_open(its last argument): the server uses it to block builds that are too old and to announce new ones, see Signed releases and updates. - Before you sell, turn on Publicly released in Integration › App settings. Every TEST key stops working, so a test key that got out cannot be used.
Questions
Why not the C# SDK? The C# SDK needs .NET 8, which Unity cannot run yet. The C library is the same C++ SDK underneath, with the same protocol and the same signature checks.
Android, iOS, macOS? Not yet. Downloads lists the builds there are.
How do I limit features by license plan? clavis_has(client, "feature_name") tells whether the license grants that feature; add a DllImport line for it like the functions in the Clavis class. Every function is in include/clavis.h in the download.
What if a customer reinstalls the system or changes machines? After a reinstall on the same machine, the server still recognizes it from its hashed hardware identifiers. A new machine takes a new seat; when none is left the customer gets device_limit, and you Reset devices on the Licenses page. See Devices, HWID and transfers.