koffi 2.5.20 → 2.5.21-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/koffi/darwin_arm64/koffi.node +0 -0
- package/build/koffi/darwin_x64/koffi.node +0 -0
- package/build/koffi/freebsd_arm64/koffi.node +0 -0
- package/build/koffi/freebsd_ia32/koffi.node +0 -0
- package/build/koffi/freebsd_x64/koffi.node +0 -0
- package/build/koffi/linux_arm32hf/koffi.node +0 -0
- package/build/koffi/linux_arm64/koffi.node +0 -0
- package/build/koffi/linux_ia32/koffi.node +0 -0
- package/build/koffi/linux_riscv64hf64/koffi.node +0 -0
- package/build/koffi/linux_x64/koffi.node +0 -0
- package/build/koffi/openbsd_ia32/koffi.node +0 -0
- package/build/koffi/openbsd_x64/koffi.node +0 -0
- package/build/koffi/win32_arm64/koffi.node +0 -0
- package/build/koffi/win32_ia32/koffi.node +0 -0
- package/build/koffi/win32_x64/koffi.node +0 -0
- package/doc/contribute.md +20 -0
- package/package.json +1 -1
- package/src/index.js +2 -1
- package/src/koffi/CMakeLists.txt +1 -0
- package/src/koffi/src/call.cc +55 -55
- package/src/koffi/src/call.hh +8 -8
- package/src/koffi/src/ffi.cc +67 -4
- package/src/koffi/src/ffi.hh +2 -0
- package/src/koffi/src/util.cc +200 -0
- package/src/koffi/src/util.hh +3 -0
- package/src/koffi/src/win32.cc +155 -0
- package/src/koffi/src/win32.hh +44 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/doc/contribute.md
CHANGED
|
@@ -127,6 +127,26 @@ Each machine is configured to run a VNC server available locally, which you can
|
|
|
127
127
|
node qemu.js info debian_x64
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
+
## Making a release
|
|
131
|
+
|
|
132
|
+
First, you must update the code in three steps:
|
|
133
|
+
|
|
134
|
+
- Change the version numbers in `package.json` (version and stable for stable releases)
|
|
135
|
+
- Add an entry to `CHANGELOG.md` to summarize the changes since last release
|
|
136
|
+
- Commit theses changes with the message *Bump Koffi to X.Y.Z*
|
|
137
|
+
|
|
138
|
+
Once this is done, you can publish a new release with the following commands:
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
node qemu.js test # If not done before
|
|
142
|
+
node qemu.js dist
|
|
143
|
+
|
|
144
|
+
cd build/dist
|
|
145
|
+
npm publish
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Some platforms are emulated so this can take a few minutes until the pre-built binaries are ready. Go grab a cup of coffee, come back and execute the `npm publish` command!
|
|
149
|
+
|
|
130
150
|
## Code style
|
|
131
151
|
|
|
132
152
|
Koffi is programmed in a mix of C++ and assembly code (architecture-specific code). It uses [node-addon-api](https://github.com/nodejs/node-addon-api) (C++ N-API wrapper) to interact with Node.js.
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -378,7 +378,7 @@ var require_package = __commonJS({
|
|
|
378
378
|
"build/dist/src/koffi/package.json"(exports2, module2) {
|
|
379
379
|
module2.exports = {
|
|
380
380
|
name: "koffi",
|
|
381
|
-
version: "2.5.
|
|
381
|
+
version: "2.5.21-beta.2",
|
|
382
382
|
stable: "2.5.20",
|
|
383
383
|
description: "Fast and simple C FFI (foreign function interface) for Node.js",
|
|
384
384
|
keywords: [
|
|
@@ -413,6 +413,7 @@ var require_package = __commonJS({
|
|
|
413
413
|
chalk: "^4.1.2",
|
|
414
414
|
esbuild: "^0.19.2",
|
|
415
415
|
"ffi-napi": "^4.0.3",
|
|
416
|
+
"ffi-rs": "^1.0.12",
|
|
416
417
|
minimatch: "^5.0.1",
|
|
417
418
|
"node-ssh": "^12.0.3",
|
|
418
419
|
raylib: "^0.9.2",
|
package/src/koffi/CMakeLists.txt
CHANGED
package/src/koffi/src/call.cc
CHANGED
|
@@ -1165,6 +1165,61 @@ Size CallData::PushIndirectString(Napi::Array array, const TypeInfo *ref, uint8_
|
|
|
1165
1165
|
}
|
|
1166
1166
|
}
|
|
1167
1167
|
|
|
1168
|
+
void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func)
|
|
1169
|
+
{
|
|
1170
|
+
if (!InitAsyncBroker(env, instance)) [[unlikely]]
|
|
1171
|
+
return nullptr;
|
|
1172
|
+
|
|
1173
|
+
int16_t idx;
|
|
1174
|
+
{
|
|
1175
|
+
std::lock_guard<std::mutex> lock(shared.mutex);
|
|
1176
|
+
|
|
1177
|
+
if (!shared.available.len) [[unlikely]] {
|
|
1178
|
+
ThrowError<Napi::Error>(env, "Too many callbacks are in use (max = %1)", MaxTrampolines);
|
|
1179
|
+
return env.Null();
|
|
1180
|
+
}
|
|
1181
|
+
if (!used_trampolines.Available()) [[unlikely]] {
|
|
1182
|
+
ThrowError<Napi::Error>(env, "This call uses too many temporary callbacks (max = %1)", RG_LEN(used_trampolines.data));
|
|
1183
|
+
return env.Null();
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
idx = shared.available.data[--shared.available.len];
|
|
1187
|
+
used_trampolines.Append(idx);
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
TrampolineInfo *trampoline = &shared.trampolines[idx];
|
|
1191
|
+
|
|
1192
|
+
trampoline->instance = instance;
|
|
1193
|
+
trampoline->proto = proto;
|
|
1194
|
+
trampoline->func.Reset(func, 1);
|
|
1195
|
+
trampoline->recv.Reset();
|
|
1196
|
+
trampoline->generation = (int32_t)mem->generation;
|
|
1197
|
+
|
|
1198
|
+
void *ptr = GetTrampoline(idx, proto);
|
|
1199
|
+
|
|
1200
|
+
return ptr;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
void CallData::DumpForward(const FunctionInfo *func) const
|
|
1204
|
+
{
|
|
1205
|
+
PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
|
|
1206
|
+
|
|
1207
|
+
if (func->parameters.len) {
|
|
1208
|
+
PrintLn(stderr, "Parameters:");
|
|
1209
|
+
for (Size i = 0; i < func->parameters.len; i++) {
|
|
1210
|
+
const ParameterInfo ¶m = func->parameters[i];
|
|
1211
|
+
PrintLn(stderr, " %1 = %2 (%3)", i, param.type->name, FmtMemSize(param.type->size));
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
PrintLn(stderr, "Return: %1 (%2)", func->ret.type->name, FmtMemSize(func->ret.type->size));
|
|
1215
|
+
|
|
1216
|
+
Span<const uint8_t> stack = MakeSpan(mem->stack.end(), old_stack_mem.end() - mem->stack.end());
|
|
1217
|
+
Span<const uint8_t> heap = MakeSpan(old_heap_mem.ptr, mem->heap.ptr - old_heap_mem.ptr);
|
|
1218
|
+
|
|
1219
|
+
DumpMemory("Stack", stack);
|
|
1220
|
+
DumpMemory("Heap", heap);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1168
1223
|
static inline Napi::Value GetReferenceValue(Napi::Env env, napi_ref ref)
|
|
1169
1224
|
{
|
|
1170
1225
|
napi_value value;
|
|
@@ -1234,59 +1289,4 @@ void CallData::PopOutArguments()
|
|
|
1234
1289
|
}
|
|
1235
1290
|
}
|
|
1236
1291
|
|
|
1237
|
-
void *CallData::ReserveTrampoline(const FunctionInfo *proto, Napi::Function func)
|
|
1238
|
-
{
|
|
1239
|
-
if (!InitAsyncBroker(env, instance)) [[unlikely]]
|
|
1240
|
-
return nullptr;
|
|
1241
|
-
|
|
1242
|
-
int16_t idx;
|
|
1243
|
-
{
|
|
1244
|
-
std::lock_guard<std::mutex> lock(shared.mutex);
|
|
1245
|
-
|
|
1246
|
-
if (!shared.available.len) [[unlikely]] {
|
|
1247
|
-
ThrowError<Napi::Error>(env, "Too many callbacks are in use (max = %1)", MaxTrampolines);
|
|
1248
|
-
return env.Null();
|
|
1249
|
-
}
|
|
1250
|
-
if (!used_trampolines.Available()) [[unlikely]] {
|
|
1251
|
-
ThrowError<Napi::Error>(env, "This call uses too many temporary callbacks (max = %1)", RG_LEN(used_trampolines.data));
|
|
1252
|
-
return env.Null();
|
|
1253
|
-
}
|
|
1254
|
-
|
|
1255
|
-
idx = shared.available.data[--shared.available.len];
|
|
1256
|
-
used_trampolines.Append(idx);
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
TrampolineInfo *trampoline = &shared.trampolines[idx];
|
|
1260
|
-
|
|
1261
|
-
trampoline->instance = instance;
|
|
1262
|
-
trampoline->proto = proto;
|
|
1263
|
-
trampoline->func.Reset(func, 1);
|
|
1264
|
-
trampoline->recv.Reset();
|
|
1265
|
-
trampoline->generation = (int32_t)mem->generation;
|
|
1266
|
-
|
|
1267
|
-
void *ptr = GetTrampoline(idx, proto);
|
|
1268
|
-
|
|
1269
|
-
return ptr;
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
void CallData::DumpForward(const FunctionInfo *func) const
|
|
1273
|
-
{
|
|
1274
|
-
PrintLn(stderr, "%!..+---- %1 (%2) ----%!0", func->name, CallConventionNames[(int)func->convention]);
|
|
1275
|
-
|
|
1276
|
-
if (func->parameters.len) {
|
|
1277
|
-
PrintLn(stderr, "Parameters:");
|
|
1278
|
-
for (Size i = 0; i < func->parameters.len; i++) {
|
|
1279
|
-
const ParameterInfo ¶m = func->parameters[i];
|
|
1280
|
-
PrintLn(stderr, " %1 = %2 (%3)", i, param.type->name, FmtMemSize(param.type->size));
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
PrintLn(stderr, "Return: %1 (%2)", func->ret.type->name, FmtMemSize(func->ret.type->size));
|
|
1284
|
-
|
|
1285
|
-
Span<const uint8_t> stack = MakeSpan(mem->stack.end(), old_stack_mem.end() - mem->stack.end());
|
|
1286
|
-
Span<const uint8_t> heap = MakeSpan(old_heap_mem.ptr, mem->heap.ptr - old_heap_mem.ptr);
|
|
1287
|
-
|
|
1288
|
-
DumpMemory("Stack", stack);
|
|
1289
|
-
DumpMemory("Heap", heap);
|
|
1290
|
-
}
|
|
1291
|
-
|
|
1292
1292
|
}
|
package/src/koffi/src/call.hh
CHANGED
|
@@ -113,12 +113,6 @@ public:
|
|
|
113
113
|
|
|
114
114
|
void DumpForward(const FunctionInfo *func) const;
|
|
115
115
|
|
|
116
|
-
private:
|
|
117
|
-
template <typename T>
|
|
118
|
-
bool AllocStack(Size size, Size align, T **out_ptr);
|
|
119
|
-
template <typename T = uint8_t>
|
|
120
|
-
T *AllocHeap(Size size, Size align);
|
|
121
|
-
|
|
122
116
|
bool PushString(Napi::Value value, int directions, const char **out_str);
|
|
123
117
|
Size PushStringValue(Napi::Value value, const char **out_str);
|
|
124
118
|
bool PushString16(Napi::Value value, int directions, const char16_t **out_str16);
|
|
@@ -130,9 +124,15 @@ private:
|
|
|
130
124
|
bool PushPointer(Napi::Value value, const TypeInfo *type, int directions, void **out_ptr);
|
|
131
125
|
Size PushIndirectString(Napi::Array array, const TypeInfo *ref, uint8_t **out_ptr);
|
|
132
126
|
|
|
133
|
-
void PopOutArguments();
|
|
134
|
-
|
|
135
127
|
void *ReserveTrampoline(const FunctionInfo *proto, Napi::Function func);
|
|
128
|
+
|
|
129
|
+
private:
|
|
130
|
+
template <typename T>
|
|
131
|
+
bool AllocStack(Size size, Size align, T **out_ptr);
|
|
132
|
+
template <typename T = uint8_t>
|
|
133
|
+
T *AllocHeap(Size size, Size align);
|
|
134
|
+
|
|
135
|
+
void PopOutArguments();
|
|
136
136
|
};
|
|
137
137
|
|
|
138
138
|
template <typename T>
|
package/src/koffi/src/ffi.cc
CHANGED
|
@@ -1568,6 +1568,32 @@ static Napi::Value UnloadLibrary(const Napi::CallbackInfo &info)
|
|
|
1568
1568
|
return env.Undefined();
|
|
1569
1569
|
}
|
|
1570
1570
|
|
|
1571
|
+
#ifdef _WIN32
|
|
1572
|
+
static HANDLE LoadWindowsLibrary(Napi::Env env, const char16_t *filename)
|
|
1573
|
+
{
|
|
1574
|
+
HANDLE module = LoadLibraryW((LPCWSTR)filename);
|
|
1575
|
+
|
|
1576
|
+
if (!module) {
|
|
1577
|
+
if (GetLastError() == ERROR_BAD_EXE_FORMAT) {
|
|
1578
|
+
int process = GetSelfMachine();
|
|
1579
|
+
int dll = GetDllMachine(filename);
|
|
1580
|
+
|
|
1581
|
+
if (process >= 0 && dll >= 0 && dll != process) {
|
|
1582
|
+
ThrowError<Napi::Error>(env, "Cannot load '%1' DLL in '%2' process",
|
|
1583
|
+
WindowsMachineNames.FindValue(dll, "Unknown"),
|
|
1584
|
+
WindowsMachineNames.FindValue(process, "Unknown"));
|
|
1585
|
+
return nullptr;
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
ThrowError<Napi::Error>(env, "Failed to load shared library: %1", GetWin32ErrorString());
|
|
1590
|
+
return nullptr;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
return module;
|
|
1594
|
+
}
|
|
1595
|
+
#endif
|
|
1596
|
+
|
|
1571
1597
|
static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
|
|
1572
1598
|
{
|
|
1573
1599
|
Napi::Env env = info.Env();
|
|
@@ -1592,12 +1618,10 @@ static Napi::Value LoadSharedLibrary(const Napi::CallbackInfo &info)
|
|
|
1592
1618
|
#ifdef _WIN32
|
|
1593
1619
|
if (info[0].IsString()) {
|
|
1594
1620
|
std::u16string filename = info[0].As<Napi::String>();
|
|
1595
|
-
module =
|
|
1621
|
+
module = LoadWindowsLibrary(env, filename.c_str());
|
|
1596
1622
|
|
|
1597
|
-
if (!module)
|
|
1598
|
-
ThrowError<Napi::Error>(env, "Failed to load shared library: %1", GetWin32ErrorString());
|
|
1623
|
+
if (!module)
|
|
1599
1624
|
return env.Null();
|
|
1600
|
-
}
|
|
1601
1625
|
} else {
|
|
1602
1626
|
module = GetModuleHandle(nullptr);
|
|
1603
1627
|
RG_ASSERT(module);
|
|
@@ -1873,6 +1897,39 @@ static Napi::Value CallPointerSync(const Napi::CallbackInfo &info)
|
|
|
1873
1897
|
: TranslateNormalCall(proto, ptr, info);
|
|
1874
1898
|
}
|
|
1875
1899
|
|
|
1900
|
+
static Napi::Value EncodeValue(const Napi::CallbackInfo &info)
|
|
1901
|
+
{
|
|
1902
|
+
Napi::Env env = info.Env();
|
|
1903
|
+
|
|
1904
|
+
bool has_offset = (info.Length() >= 2 && info[1].IsNumber());
|
|
1905
|
+
bool has_len = (info.Length() >= 4u + has_offset && info[2u + has_offset].IsNumber());
|
|
1906
|
+
|
|
1907
|
+
if (info.Length() < 3u + has_offset) [[unlikely]] {
|
|
1908
|
+
ThrowError<Napi::TypeError>(env, "Expected %1 to 5 arguments, got %2", 3 + has_offset, info.Length());
|
|
1909
|
+
return env.Null();
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
const TypeInfo *type = ResolveType(info[1u + has_offset]);
|
|
1913
|
+
if (!type) [[unlikely]]
|
|
1914
|
+
return env.Null();
|
|
1915
|
+
|
|
1916
|
+
Napi::Value ref = info[0];
|
|
1917
|
+
int64_t offset = has_offset ? info[1].As<Napi::Number>().Int64Value() : 0;
|
|
1918
|
+
Napi::Value value = info[2u + has_offset + has_len];
|
|
1919
|
+
|
|
1920
|
+
if (has_len) {
|
|
1921
|
+
Size len = info[2u + has_offset].As<Napi::Number>();
|
|
1922
|
+
|
|
1923
|
+
if (!Encode(ref, offset, value, type, &len))
|
|
1924
|
+
return env.Null();
|
|
1925
|
+
} else {
|
|
1926
|
+
if (!Encode(ref, offset, value, type))
|
|
1927
|
+
return env.Null();
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
return env.Undefined();
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1876
1933
|
void LibraryHolder::Unload()
|
|
1877
1934
|
{
|
|
1878
1935
|
#ifdef _WIN32
|
|
@@ -1956,6 +2013,11 @@ bool InitAsyncBroker(Napi::Env env, InstanceData *instance)
|
|
|
1956
2013
|
return true;
|
|
1957
2014
|
}
|
|
1958
2015
|
|
|
2016
|
+
CallData *GetThreadCall()
|
|
2017
|
+
{
|
|
2018
|
+
return exec_call;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
1959
2021
|
static void RegisterPrimitiveType(Napi::Env env, Napi::Object map, std::initializer_list<const char *> names,
|
|
1960
2022
|
PrimitiveKind primitive, int32_t size, int16_t align, const char *ref = nullptr)
|
|
1961
2023
|
{
|
|
@@ -2169,6 +2231,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
|
|
|
2169
2231
|
exports.Set("decode", Napi::Function::New(env, DecodeValue));
|
|
2170
2232
|
exports.Set("address", Napi::Function::New(env, GetPointerAddress));
|
|
2171
2233
|
exports.Set("call", Napi::Function::New(env, CallPointerSync));
|
|
2234
|
+
exports.Set("encode", Napi::Function::New(env, EncodeValue));
|
|
2172
2235
|
|
|
2173
2236
|
exports.Set("reset", Napi::Function::New(env, ResetKoffi));
|
|
2174
2237
|
|
package/src/koffi/src/ffi.hh
CHANGED
|
@@ -99,6 +99,7 @@ static const char *const PrimitiveKindNames[] = {
|
|
|
99
99
|
struct TypeInfo;
|
|
100
100
|
struct RecordMember;
|
|
101
101
|
struct FunctionInfo;
|
|
102
|
+
class CallData;
|
|
102
103
|
|
|
103
104
|
typedef void DisposeFunc (Napi::Env env, const TypeInfo *type, const void *ptr);
|
|
104
105
|
|
|
@@ -342,5 +343,6 @@ Napi::Value TranslateVariadicCall(const Napi::CallbackInfo &info);
|
|
|
342
343
|
Napi::Value TranslateAsyncCall(const Napi::CallbackInfo &info);
|
|
343
344
|
|
|
344
345
|
bool InitAsyncBroker(Napi::Env env, InstanceData *instance);
|
|
346
|
+
CallData *GetThreadCall();
|
|
345
347
|
|
|
346
348
|
}
|
package/src/koffi/src/util.cc
CHANGED
|
@@ -1193,6 +1193,206 @@ Napi::Value Decode(Napi::Env env, const uint8_t *ptr, const TypeInfo *type, cons
|
|
|
1193
1193
|
return env.Null();
|
|
1194
1194
|
}
|
|
1195
1195
|
|
|
1196
|
+
bool Encode(Napi::Value ref, Size offset, Napi::Value value, const TypeInfo *type, const Size *len)
|
|
1197
|
+
{
|
|
1198
|
+
Napi::Env env = ref.Env();
|
|
1199
|
+
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
1200
|
+
|
|
1201
|
+
uint8_t *ptr = nullptr;
|
|
1202
|
+
|
|
1203
|
+
if (ref.IsExternal()) {
|
|
1204
|
+
Napi::External<void> external = ref.As<Napi::External<void>>();
|
|
1205
|
+
ptr = (uint8_t *)external.Data();
|
|
1206
|
+
} else if (IsRawBuffer(ref)) {
|
|
1207
|
+
Span<uint8_t> buffer = GetRawBuffer(ref);
|
|
1208
|
+
|
|
1209
|
+
if (buffer.len - offset < type->size) [[unlikely]] {
|
|
1210
|
+
ThrowError<Napi::Error>(env, "Expected buffer with size superior or equal to type %1 (%2 bytes)",
|
|
1211
|
+
type->name, type->size + offset);
|
|
1212
|
+
return env.Null();
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
ptr = (uint8_t *)buffer.ptr;
|
|
1216
|
+
} else {
|
|
1217
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value for reference, expected external or TypedArray", GetValueType(instance, value));
|
|
1218
|
+
return env.Null();
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
if (!ptr) [[unlikely]] {
|
|
1222
|
+
ThrowError<Napi::Error>(env, "Cannot encode data in NULL pointer");
|
|
1223
|
+
return env.Null();
|
|
1224
|
+
}
|
|
1225
|
+
ptr += offset;
|
|
1226
|
+
|
|
1227
|
+
return Encode(env, ptr, value, type, len);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
bool Encode(Napi::Env env, uint8_t *origin, Napi::Value value, const TypeInfo *type, const Size *len)
|
|
1231
|
+
{
|
|
1232
|
+
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
|
1233
|
+
CallData *call = GetThreadCall();
|
|
1234
|
+
|
|
1235
|
+
if (len && type->primitive != PrimitiveKind::String &&
|
|
1236
|
+
type->primitive != PrimitiveKind::String16 &&
|
|
1237
|
+
type->primitive != PrimitiveKind::Prototype) {
|
|
1238
|
+
if (*len < 0) [[unlikely]] {
|
|
1239
|
+
ThrowError<Napi::TypeError>(env, "Automatic (negative) length is only supported when decoding");
|
|
1240
|
+
return env.Null();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
type = MakeArrayType(instance, type, *len);
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
if (!call) [[unlikely]] {
|
|
1247
|
+
ThrowError<Napi::Error>(env, "koffi.encode() can only be used inside callbacks");
|
|
1248
|
+
return false;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
#define PUSH_INTEGER(CType) \
|
|
1252
|
+
do { \
|
|
1253
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] { \
|
|
1254
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value)); \
|
|
1255
|
+
return false; \
|
|
1256
|
+
} \
|
|
1257
|
+
\
|
|
1258
|
+
CType v = GetNumber<CType>(value); \
|
|
1259
|
+
*(CType *)origin = v; \
|
|
1260
|
+
} while (false)
|
|
1261
|
+
#define PUSH_INTEGER_SWAP(CType) \
|
|
1262
|
+
do { \
|
|
1263
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] { \
|
|
1264
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value)); \
|
|
1265
|
+
return false; \
|
|
1266
|
+
} \
|
|
1267
|
+
\
|
|
1268
|
+
CType v = GetNumber<CType>(value); \
|
|
1269
|
+
*(CType *)origin = ReverseBytes(v); \
|
|
1270
|
+
} while (false)
|
|
1271
|
+
|
|
1272
|
+
switch (type->primitive) {
|
|
1273
|
+
case PrimitiveKind::Void: { RG_UNREACHABLE(); } break;
|
|
1274
|
+
|
|
1275
|
+
case PrimitiveKind::Bool: {
|
|
1276
|
+
if (!value.IsBoolean()) [[unlikely]] {
|
|
1277
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected boolean", GetValueType(instance, value));
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
bool b = value.As<Napi::Boolean>();
|
|
1282
|
+
*(bool *)origin = b;
|
|
1283
|
+
} break;
|
|
1284
|
+
case PrimitiveKind::Int8: { PUSH_INTEGER(int8_t); } break;
|
|
1285
|
+
case PrimitiveKind::UInt8: { PUSH_INTEGER(uint8_t); } break;
|
|
1286
|
+
case PrimitiveKind::Int16: { PUSH_INTEGER(int16_t); } break;
|
|
1287
|
+
case PrimitiveKind::Int16S: { PUSH_INTEGER_SWAP(int16_t); } break;
|
|
1288
|
+
case PrimitiveKind::UInt16: { PUSH_INTEGER(uint16_t); } break;
|
|
1289
|
+
case PrimitiveKind::UInt16S: { PUSH_INTEGER_SWAP(uint16_t); } break;
|
|
1290
|
+
case PrimitiveKind::Int32: { PUSH_INTEGER(int32_t); } break;
|
|
1291
|
+
case PrimitiveKind::Int32S: { PUSH_INTEGER_SWAP(int32_t); } break;
|
|
1292
|
+
case PrimitiveKind::UInt32: { PUSH_INTEGER(uint32_t); } break;
|
|
1293
|
+
case PrimitiveKind::UInt32S: { PUSH_INTEGER_SWAP(uint32_t); } break;
|
|
1294
|
+
case PrimitiveKind::Int64: { PUSH_INTEGER(int64_t); } break;
|
|
1295
|
+
case PrimitiveKind::Int64S: { PUSH_INTEGER_SWAP(int64_t); } break;
|
|
1296
|
+
case PrimitiveKind::UInt64: { PUSH_INTEGER(uint64_t); } break;
|
|
1297
|
+
case PrimitiveKind::UInt64S: { PUSH_INTEGER_SWAP(uint64_t); } break;
|
|
1298
|
+
case PrimitiveKind::String: {
|
|
1299
|
+
const char *str;
|
|
1300
|
+
if (!call->PushString(value, 1, &str)) [[unlikely]]
|
|
1301
|
+
return false;
|
|
1302
|
+
*(const char **)origin = str;
|
|
1303
|
+
} break;
|
|
1304
|
+
case PrimitiveKind::String16: {
|
|
1305
|
+
const char16_t *str16;
|
|
1306
|
+
if (!call->PushString16(value, 1, &str16)) [[unlikely]]
|
|
1307
|
+
return false;
|
|
1308
|
+
*(const char16_t **)origin = str16;
|
|
1309
|
+
} break;
|
|
1310
|
+
case PrimitiveKind::Pointer: {
|
|
1311
|
+
void *ptr;
|
|
1312
|
+
if (!call->PushPointer(value, type, 1, &ptr)) [[unlikely]]
|
|
1313
|
+
return false;
|
|
1314
|
+
*(void **)origin = ptr;
|
|
1315
|
+
} break;
|
|
1316
|
+
case PrimitiveKind::Record:
|
|
1317
|
+
case PrimitiveKind::Union: {
|
|
1318
|
+
if (!IsObject(value)) [[unlikely]] {
|
|
1319
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected object", GetValueType(instance, value));
|
|
1320
|
+
return false;
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
Napi::Object obj = value.As<Napi::Object>();
|
|
1324
|
+
|
|
1325
|
+
if (!call->PushObject(obj, type, origin))
|
|
1326
|
+
return false;
|
|
1327
|
+
} break;
|
|
1328
|
+
case PrimitiveKind::Array: {
|
|
1329
|
+
if (value.IsArray()) {
|
|
1330
|
+
Napi::Array array = value.As<Napi::Array>();
|
|
1331
|
+
Size len = (Size)type->size / type->ref.type->size;
|
|
1332
|
+
|
|
1333
|
+
if (!call->PushNormalArray(array, len, type, origin))
|
|
1334
|
+
return false;
|
|
1335
|
+
} else if (IsRawBuffer(value)) {
|
|
1336
|
+
Span<const uint8_t> buffer = GetRawBuffer(value);
|
|
1337
|
+
|
|
1338
|
+
if (!call->PushBuffer(buffer, type->size, type, origin))
|
|
1339
|
+
return false;
|
|
1340
|
+
} else if (value.IsString()) {
|
|
1341
|
+
if (!call->PushStringArray(value, type, origin))
|
|
1342
|
+
return false;
|
|
1343
|
+
} else {
|
|
1344
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected array", GetValueType(instance, value));
|
|
1345
|
+
return false;
|
|
1346
|
+
}
|
|
1347
|
+
} break;
|
|
1348
|
+
case PrimitiveKind::Float32: {
|
|
1349
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] {
|
|
1350
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value));
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
float f = GetNumber<float>(value);
|
|
1355
|
+
*(float *)origin = f;
|
|
1356
|
+
} break;
|
|
1357
|
+
case PrimitiveKind::Float64: {
|
|
1358
|
+
if (!value.IsNumber() && !value.IsBigInt()) [[unlikely]] {
|
|
1359
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected number", GetValueType(instance, value));
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
double d = GetNumber<double>(value);
|
|
1364
|
+
*(double *)origin = d;
|
|
1365
|
+
} break;
|
|
1366
|
+
case PrimitiveKind::Callback: {
|
|
1367
|
+
void *ptr;
|
|
1368
|
+
|
|
1369
|
+
if (value.IsFunction()) {
|
|
1370
|
+
Napi::Function func = value.As<Napi::Function>();
|
|
1371
|
+
|
|
1372
|
+
ptr = call->ReserveTrampoline(type->ref.proto, func);
|
|
1373
|
+
if (!ptr) [[unlikely]]
|
|
1374
|
+
return false;
|
|
1375
|
+
} else if (CheckValueTag(instance, value, type->ref.marker)) {
|
|
1376
|
+
ptr = value.As<Napi::External<void>>().Data();
|
|
1377
|
+
} else if (IsNullOrUndefined(value)) {
|
|
1378
|
+
ptr = nullptr;
|
|
1379
|
+
} else {
|
|
1380
|
+
ThrowError<Napi::TypeError>(env, "Unexpected %1 value, expected %2", GetValueType(instance, value), type->name);
|
|
1381
|
+
return false;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
*(void **)origin = ptr;
|
|
1385
|
+
} break;
|
|
1386
|
+
|
|
1387
|
+
case PrimitiveKind::Prototype: { RG_UNREACHABLE(); } break;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
#undef PUSH_INTEGER_SWAP
|
|
1391
|
+
#undef PUSH_INTEGER
|
|
1392
|
+
|
|
1393
|
+
return true;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1196
1396
|
Napi::Function WrapFunction(Napi::Env env, const FunctionInfo *func)
|
|
1197
1397
|
{
|
|
1198
1398
|
InstanceData *instance = env.GetInstanceData<InstanceData>();
|
package/src/koffi/src/util.hh
CHANGED
|
@@ -170,6 +170,9 @@ void DecodeBuffer(Span<uint8_t> buffer, const uint8_t *origin, const TypeInfo *r
|
|
|
170
170
|
Napi::Value Decode(Napi::Value value, Size offset, const TypeInfo *type, const Size *len = nullptr);
|
|
171
171
|
Napi::Value Decode(Napi::Env env, const uint8_t *ptr, const TypeInfo *type, const Size *len = nullptr);
|
|
172
172
|
|
|
173
|
+
bool Encode(Napi::Value ref, Size offset, Napi::Value value, const TypeInfo *type, const Size *len = nullptr);
|
|
174
|
+
bool Encode(Napi::Env env, uint8_t *ptr, Napi::Value value, const TypeInfo *type, const Size *len = nullptr);
|
|
175
|
+
|
|
173
176
|
static inline Napi::Value NewBigInt(Napi::Env env, int64_t value)
|
|
174
177
|
{
|
|
175
178
|
if (value <= 9007199254740992ll && value >= -9007199254740992ll) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// Copyright 2023 Niels Martignène <niels.martignene@protonmail.com>
|
|
2
|
+
//
|
|
3
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
4
|
+
// this software and associated documentation files (the “Software”), to deal in
|
|
5
|
+
// the Software without restriction, including without limitation the rights to use,
|
|
6
|
+
// copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
|
7
|
+
// Software, and to permit persons to whom the Software is furnished to do so,
|
|
8
|
+
// subject to the following conditions:
|
|
9
|
+
//
|
|
10
|
+
// The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
// copies or substantial portions of the Software.
|
|
12
|
+
//
|
|
13
|
+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
|
14
|
+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
15
|
+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
16
|
+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
|
17
|
+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
18
|
+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
19
|
+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
20
|
+
// OTHER DEALINGS IN THE SOFTWARE.
|
|
21
|
+
|
|
22
|
+
#ifdef _WIN32
|
|
23
|
+
|
|
24
|
+
#include "win32.hh"
|
|
25
|
+
|
|
26
|
+
#ifndef NOMINMAX
|
|
27
|
+
#define NOMINMAX
|
|
28
|
+
#endif
|
|
29
|
+
#ifndef WIN32_LEAN_AND_MEAN
|
|
30
|
+
#define WIN32_LEAN_AND_MEAN
|
|
31
|
+
#endif
|
|
32
|
+
#include <windows.h>
|
|
33
|
+
#include <ntsecapi.h>
|
|
34
|
+
#include <processthreadsapi.h>
|
|
35
|
+
|
|
36
|
+
namespace RG {
|
|
37
|
+
|
|
38
|
+
const HashMap<int, const char *> WindowsMachineNames = {
|
|
39
|
+
{ 0x184, "Alpha AXP, 32-bit" },
|
|
40
|
+
{ 0x284, "Alpha 64" },
|
|
41
|
+
{ 0x1d3, "Matsushita AM33" },
|
|
42
|
+
{ 0x8664, "AMD x64" },
|
|
43
|
+
{ 0x1c0, "ARM little endian" },
|
|
44
|
+
{ 0xaa64, "ARM64 little endian" },
|
|
45
|
+
{ 0x1c4, "ARM Thumb-2 little endian" },
|
|
46
|
+
{ 0x284, "AXP 64" },
|
|
47
|
+
{ 0xebc, "EFI byte code" },
|
|
48
|
+
{ 0x14c, "Intel 386+" },
|
|
49
|
+
{ 0x200, "Intel Itanium" },
|
|
50
|
+
{ 0x6232, "LoongArch 32-bit" },
|
|
51
|
+
{ 0x6264, "LoongArch 64-bit" },
|
|
52
|
+
{ 0x9041, "Mitsubishi M32R little endian" },
|
|
53
|
+
{ 0x266, "MIPS16" },
|
|
54
|
+
{ 0x366, "MIPS with FPU" },
|
|
55
|
+
{ 0x466, "MIPS16 with FPU" },
|
|
56
|
+
{ 0x1f0, "Power PC little endian" },
|
|
57
|
+
{ 0x1f1, "Power PC with FP support" },
|
|
58
|
+
{ 0x166, "MIPS little endian" },
|
|
59
|
+
{ 0x5032, "RISC-V 32-bit" },
|
|
60
|
+
{ 0x5064, "RISC-V 64-bit" },
|
|
61
|
+
{ 0x5128, "RISC-V 128-bit" },
|
|
62
|
+
{ 0x1a2, "Hitachi SH3" },
|
|
63
|
+
{ 0x1a3, "Hitachi SH3 DSP" },
|
|
64
|
+
{ 0x1a6, "Hitachi SH4" },
|
|
65
|
+
{ 0x1a8, "Hitachi SH5" },
|
|
66
|
+
{ 0x1c2, "Thumb" },
|
|
67
|
+
{ 0x169, "MIPS little-endian WCE v2" }
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Fails silently on purpose
|
|
71
|
+
static bool ReadAt(HANDLE h, int32_t offset, void *buf, int len)
|
|
72
|
+
{
|
|
73
|
+
OVERLAPPED ov = {};
|
|
74
|
+
DWORD read;
|
|
75
|
+
|
|
76
|
+
ov.Offset = offset & 0x7FFFFFFFu;
|
|
77
|
+
|
|
78
|
+
if (offset < 0)
|
|
79
|
+
return false;
|
|
80
|
+
if (!ReadFile(h, buf, (DWORD)len, &read, &ov))
|
|
81
|
+
return false;
|
|
82
|
+
if (read != len)
|
|
83
|
+
return false;
|
|
84
|
+
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
static int GetFileMachine(HANDLE h, bool check_dll)
|
|
89
|
+
{
|
|
90
|
+
PE_DOS_HEADER dos = {};
|
|
91
|
+
PE_NT_HEADERS nt = {};
|
|
92
|
+
|
|
93
|
+
if (!ReadAt(h, 0, &dos, RG_SIZE(dos)))
|
|
94
|
+
goto generic;
|
|
95
|
+
if (!ReadAt(h, dos.e_lfanew, &nt, RG_SIZE(nt)))
|
|
96
|
+
goto generic;
|
|
97
|
+
|
|
98
|
+
if (dos.e_magic != 0x5A4D) // MZ
|
|
99
|
+
goto generic;
|
|
100
|
+
if (nt.Signature != 0x00004550) // PE\0\0
|
|
101
|
+
goto generic;
|
|
102
|
+
if (check_dll && !(nt.FileHeader.Characteristics & IMAGE_FILE_DLL))
|
|
103
|
+
goto generic;
|
|
104
|
+
|
|
105
|
+
return (int)nt.FileHeader.Machine;
|
|
106
|
+
|
|
107
|
+
generic:
|
|
108
|
+
LogError("Invalid or forbidden %1 file: %2", check_dll ? "DLL" : "executable", GetWin32ErrorString());
|
|
109
|
+
return -1;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
int GetSelfMachine()
|
|
113
|
+
{
|
|
114
|
+
const char *filename = GetApplicationExecutable();
|
|
115
|
+
|
|
116
|
+
HANDLE h;
|
|
117
|
+
if (IsWin32Utf8()) {
|
|
118
|
+
h = CreateFileA(filename, GENERIC_READ,
|
|
119
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
120
|
+
nullptr, OPEN_EXISTING, 0, nullptr);
|
|
121
|
+
} else {
|
|
122
|
+
wchar_t filename_w[4096];
|
|
123
|
+
if (ConvertUtf8ToWin32Wide(filename, filename_w) < 0)
|
|
124
|
+
return -1;
|
|
125
|
+
|
|
126
|
+
h = CreateFileW(filename_w, GENERIC_READ,
|
|
127
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
128
|
+
nullptr, OPEN_EXISTING, 0, nullptr);
|
|
129
|
+
}
|
|
130
|
+
if (h == INVALID_HANDLE_VALUE) {
|
|
131
|
+
LogError("Cannot open '%1': %2", filename, GetWin32ErrorString());
|
|
132
|
+
return -1;
|
|
133
|
+
}
|
|
134
|
+
RG_DEFER { CloseHandle(h); };
|
|
135
|
+
|
|
136
|
+
return GetFileMachine(h, false);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
int GetDllMachine(const char16_t *filename)
|
|
140
|
+
{
|
|
141
|
+
HANDLE h = CreateFileW((LPCWSTR)filename, GENERIC_READ,
|
|
142
|
+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
143
|
+
nullptr, OPEN_EXISTING, 0, nullptr);
|
|
144
|
+
if (h == INVALID_HANDLE_VALUE) {
|
|
145
|
+
LogError("Cannot open '%1': %2", filename, GetWin32ErrorString());
|
|
146
|
+
return -1;
|
|
147
|
+
}
|
|
148
|
+
RG_DEFER { CloseHandle(h); };
|
|
149
|
+
|
|
150
|
+
return GetFileMachine(h, true);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
#endif
|
package/src/koffi/src/win32.hh
CHANGED
|
@@ -27,6 +27,45 @@
|
|
|
27
27
|
|
|
28
28
|
namespace RG {
|
|
29
29
|
|
|
30
|
+
struct PE_DOS_HEADER {
|
|
31
|
+
uint16_t e_magic;
|
|
32
|
+
uint16_t e_cblp;
|
|
33
|
+
uint16_t e_cp;
|
|
34
|
+
uint16_t e_crlc;
|
|
35
|
+
uint16_t e_cparhdr;
|
|
36
|
+
uint16_t e_minalloc;
|
|
37
|
+
uint16_t e_maxalloc;
|
|
38
|
+
uint16_t e_ss;
|
|
39
|
+
uint16_t e_sp;
|
|
40
|
+
uint16_t e_csum;
|
|
41
|
+
uint16_t e_ip;
|
|
42
|
+
uint16_t e_cs;
|
|
43
|
+
uint16_t e_lfarlc;
|
|
44
|
+
uint16_t e_ovno;
|
|
45
|
+
uint16_t e_res[4];
|
|
46
|
+
uint16_t e_oemid;
|
|
47
|
+
uint16_t e_oeminfo;
|
|
48
|
+
uint16_t e_res2[10];
|
|
49
|
+
uint32_t e_lfanew;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
struct PE_FILE_HEADER {
|
|
53
|
+
uint16_t Machine;
|
|
54
|
+
uint16_t NumberOfSections;
|
|
55
|
+
uint32_t TimeDateStamp;
|
|
56
|
+
uint32_t PointerToSymbolTable;
|
|
57
|
+
uint32_t NumberOfSymbols;
|
|
58
|
+
uint16_t SizeOfOptionalHeader;
|
|
59
|
+
uint16_t Characteristics;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
struct PE_NT_HEADERS {
|
|
63
|
+
uint32_t Signature;
|
|
64
|
+
PE_FILE_HEADER FileHeader;
|
|
65
|
+
|
|
66
|
+
// ... OptionalHeader;
|
|
67
|
+
};
|
|
68
|
+
|
|
30
69
|
#if _WIN64
|
|
31
70
|
|
|
32
71
|
struct TEB {
|
|
@@ -70,4 +109,9 @@ static inline TEB *GetTEB()
|
|
|
70
109
|
return teb;
|
|
71
110
|
}
|
|
72
111
|
|
|
112
|
+
extern const HashMap<int, const char *> WindowsMachineNames;
|
|
113
|
+
|
|
114
|
+
int GetSelfMachine();
|
|
115
|
+
int GetDllMachine(const char16_t *filename);
|
|
116
|
+
|
|
73
117
|
}
|