Mục lục thư viện
Thư viện tài liệuBắt đầu theo ngôn ngữ
Bắt đầu với Unity
Unity không nạp được SDK C# (SDK đó cần .NET 8), nên game Unity gọi thư viện C của ClavisAUH qua P/Invoke, đúng cách Unity dùng mọi plugin native. Cùng mã đó chạy được trên .NET Framework 4.x. Bài này đi từ cài thư viện tới bản build của game.
5 phút đọc
Cần có
- Unity với scripting backend Mono hoặc IL2CPP, game build cho Windows x64 hoặc Linux x64. Thư viện C chưa có bản macOS, Android, iOS.
- Một ứng dụng trên dashboard. Trang Tích hợp › Kết nối phần mềm của nó có địa chỉ API, App ID, khoá công khai và nút Tạo key thử nghiệm. Địa chỉ API kết thúc bằng
/api, không có/v1:https://<trang ClavisAUH của bạn>/api.
1. Cài thư viện C
Tải ở trang Tải về và kiểm SHA-256:
- Windows:
clavis-c-1.0.0-windows-x64.zip, gồmclavis.dll: một file, chỉ cần DLL có sẵn của Windows. - Linux:
clavis-c-1.0.0-linux-x64.tar.gz, gồmlibclavis.so.1. Máy chạy game cầnlibsodiumvàlibcurlcủa hệ thống (Debian, Ubuntu:apt install libsodium23 libcurl4).
Đặt clavis.dll vào Assets/Plugins/x86_64/ của dự án. Với Linux, chép libclavis.so.1 vào cùng thư mục và đổi tên thành libclavis.so. Trong Inspector của từng file, chọn đúng nền tảng (Windows x86_64 hoặc Linux x86_64) cho cả Editor và Standalone.
2. Mã tối thiểu
Tạo Assets/Scripts/Clavis.cs với lớp dưới đây, nguyên văn. Lớp chỉ dùng DllImport và mảng byte cho chuỗi UTF-8, nên Mono và IL2CPP đều nhận như nhau:
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);
}
}
Mở một client cho máy này. Địa chỉ API, App ID và khoá công khai là ba giá trị ở trang Kết nối phần mềm; trong Unity, thư mục trạng thái nên là Application.persistentDataPath. Thư viện tự giữ khoá thiết bị và token offline mới nhất trong thư mục đó:
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;
}
Kích hoạt key của khách. Khi không có mạng, token offline lưu lần trước giữ game chạy tiếp:
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;
}
Khoá công khai chỉ dùng để kiểm chữ ký, nhúng vào game là an toàn. Đừng bao giờ nhúng một khoá bí mật nào.
3. Đặt vào game
- Mỗi lượt gọi chờ mạng tới vài giây, nên đừng gọi trên luồng chính của Unity: game sẽ đứng. Tạo một luồng nền (
System.Threading.Thread) giữ client từ lúc mở tới lúc đóng. - Một client chỉ dùng trên một luồng. Luồng nền mở client, kích hoạt, rồi gửi heartbeat theo lịch; luồng chính chỉ đọc kết quả (trạng thái license, câu báo lỗi) mà luồng nền để lại, ví dụ trong
Update(). - Chuỗi thư viện trả về chỉ còn đúng tới lượt gọi kế tiếp trên client đó;
Clavis.Textđã chép chúng sang chuỗi C#. - Gọi
clavis_closekhi game thoát (OnApplicationQuit).
4. Chạy thử
Trước khi đưa vào Unity, bạn có thể chạy cùng mã bằng .NET, không cần mở Unity:
- Ở trang Kết nối phần mềm, bấm Tạo key thử nghiệm.
- Tạo dự án console bằng
dotnet new console -n ClavisCheck, xoáProgram.csvà đặt fileK1.csđầy đủ dưới đây vào thay. - Đặt thư mục giải nén của thư viện lên PATH, đặt bốn cài đặt rồi chạy
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:\duong-dan\clavis-c-1.0.0-windows-x64;$env:PATH"
$env:CLAVIS_BASE_URL = "<địa chỉ API>"
$env:CLAVIS_APP_ID = "<App ID>"
$env:CLAVIS_APP_KEY = "<khoá công khai>"
$env:CLAVIS_LICENSE = "<key thử nghiệm>"
dotnet run
Dòng cuối in ra OK k1 active. Trên Linux, thay PATH bằng LD_LIBRARY_PATH trỏ tới thư mục có libclavis.so.
5. Thấy trên dashboard
- Tích hợp › Kết nối phần mềm: bước 4 chuyển sang Đã kết nối, kèm
c/1.0.0(thư viện C) và hệ điều hành của máy. - Phát hành & bản quyền › License: key chuyển từ Chưa dùng sang Đang dùng.
- Người dùng cuối › Thiết bị & phiên: máy vừa chạy, cùng phiên nó vừa mở.
6. Xử lý lỗi
Mọi hàm trả int trả 0 khi thành công, hoặc một số lỗi cố định (bảng Mã lỗi, cột C). Sau một lần thất bại:
clavis_error_code(client): mã dạng chữ, ví dụinvalid_credentialshaydevice_limit.clavis_error_message(client, "vi"): câu cho người chơi, tiếng Việt hoặc"en".clavis_retryable(số lỗi): khác0khi không nhận được câu trả lời; thử lại sau có thể thành công. Lúc đóclavis_offline(client)trả0nếu token offline đã lưu còn dùng được trên máy này.
Khi game đang chạy, gửi heartbeat đều đặn từ luồng nền, khoảng mỗi 5 tới 30 phút: phiên hết sau 60 phút không có heartbeat.
// 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);
Mọi gói đều có token offline, mặc định dùng được 7 ngày; Premium tự chọn thời hạn, còn Chế độ chỉ online thì tắt hẳn. Xem License offline.
7. Build và phát hành
- Ghi địa chỉ API, App ID và khoá công khai vào script hoặc ScriptableObject của game.
- Unity chép plugin vào thư mục
Pluginscủa bản build. Kiểm bản build trên một máy sạch: game phải tới được trạng thái đã kích hoạt mà không cần cài gì thêm (Windows) hoặc chỉ cầnlibsodiumvàlibcurl(Linux). - Truyền phiên bản game vào
clavis_open(tham số cuối): server dùng nó để chặn bản quá cũ và báo có bản mới, xem Bản phát hành có ký và cập nhật. - Trước khi bán, bật Đã phát hành công khai ở Tích hợp › Cài đặt ứng dụng. Mọi key TEST ngừng hoạt động, nên key thử nào lỡ lọt ra ngoài cũng không dùng được nữa.
Câu hỏi thường gặp
Sao không dùng SDK C#? SDK C# cần .NET 8, Unity chưa chạy được. Thư viện C là cùng SDK C++ bên dưới, cùng giao thức và cùng cách kiểm chữ ký.
Bản Android, iOS, macOS? Chưa có. Trang Tải về liệt kê các bản đang có.
Giới hạn tính năng theo gói license thế nào? clavis_has(client, "ten_tinh_nang") cho biết license có quyền tính năng đó không; thêm khai báo DllImport cho nó giống các hàm trong lớp Clavis. Danh sách đủ các hàm có trong include/clavis.h của gói tải về.
Khách cài lại hệ điều hành hay đổi máy thì sao? Cài lại trên cùng máy, server vẫn nhận ra máy nhờ dấu nhận diện phần cứng đã băm. Máy mới chiếm một suất mới; hết suất thì khách nhận device_limit, và bạn Đặt lại thiết bị trong trang License. Xem Thiết bị, HWID và chuyển máy.