Kyiro
Kyiro•12mo ago

FFI string corrupted?

Is there anything i'm doing wrong here? I'm confused on why it's crashing on setVerisonUE5 in particular. The code without FFI in C++ works fine
import { Dataminer } from "./mod.ts";

Dataminer.withLogs(true);
Dataminer.setOodle("...");

const core = new Dataminer("...");
console.log(core); // printed
core.setVersionUE5(1008); // crashes here?
console.log("set version"); // not printed
import { Dataminer } from "./mod.ts";

Dataminer.withLogs(true);
Dataminer.setOodle("...");

const core = new Dataminer("...");
console.log(core); // printed
core.setVersionUE5(1008); // crashes here?
console.log("set version"); // not printed
const DLL_PATH = "...";

const library = Deno.dlopen(DLL_PATH, {
"dataminer_options_with_oodle": {
parameters: ["buffer"],
result: "void"
},
"dataminer_with_logging": {
parameters: ["bool"],
result: "void"
},
"dataminer_construct": {
parameters: ["buffer"],
result: "pointer"
},
"dataminer_set_version_ue5": {
parameters: ["pointer", "i32"],
result: "void"
}
});

function encode(text: string) {
return new TextEncoder().encode(text);
}

export class Dataminer {
pointer: Deno.PointerValue;

constructor(paksDir: string) {
this.pointer = library.symbols.dataminer_construct(encode(paksDir));
}

checkPointer() {
if (!this.pointer) throw new Error("pointer is null");
}

static setOodle(oodlePath: string) {
library.symbols.dataminer_options_with_oodle(encode(oodlePath));
}

static withLogs(shouldLog: boolean) {
library.symbols.dataminer_with_logging(shouldLog);
}

setVersionUE5(version: number) {
this.checkPointer();
library.symbols.dataminer_set_version_ue5(this.pointer, version);
}
}
const DLL_PATH = "...";

const library = Deno.dlopen(DLL_PATH, {
"dataminer_options_with_oodle": {
parameters: ["buffer"],
result: "void"
},
"dataminer_with_logging": {
parameters: ["bool"],
result: "void"
},
"dataminer_construct": {
parameters: ["buffer"],
result: "pointer"
},
"dataminer_set_version_ue5": {
parameters: ["pointer", "i32"],
result: "void"
}
});

function encode(text: string) {
return new TextEncoder().encode(text);
}

export class Dataminer {
pointer: Deno.PointerValue;

constructor(paksDir: string) {
this.pointer = library.symbols.dataminer_construct(encode(paksDir));
}

checkPointer() {
if (!this.pointer) throw new Error("pointer is null");
}

static setOodle(oodlePath: string) {
library.symbols.dataminer_options_with_oodle(encode(oodlePath));
}

static withLogs(shouldLog: boolean) {
library.symbols.dataminer_with_logging(shouldLog);
}

setVersionUE5(version: number) {
this.checkPointer();
library.symbols.dataminer_set_version_ue5(this.pointer, version);
}
}
12 Replies
Kyiro
Kyiro•12mo ago
#include "Dataminer/Dataminer.h"

#define CPAKPARSER_API extern "C" __declspec(dllexport)

CPAKPARSER_API void dataminer_options_with_oodle(const char* OodleDllPath)
{
Dataminer::Options::WithOodleDecompressor(OodleDllPath);
}

CPAKPARSER_API void dataminer_with_logging(bool bEnableLogging)
{
Dataminer::Options::WithLogging(bEnableLogging);
}

CPAKPARSER_API Dataminer* dataminer_construct(const char* PaksFolderDir)
{
auto Result = Dataminer(PaksFolderDir);
return &Result;
}

CPAKPARSER_API void dataminer_set_version_ue5(Dataminer* This, int Version)
{
This->SetVersionUE5(Version);
}
#include "Dataminer/Dataminer.h"

#define CPAKPARSER_API extern "C" __declspec(dllexport)

CPAKPARSER_API void dataminer_options_with_oodle(const char* OodleDllPath)
{
Dataminer::Options::WithOodleDecompressor(OodleDllPath);
}

CPAKPARSER_API void dataminer_with_logging(bool bEnableLogging)
{
Dataminer::Options::WithLogging(bEnableLogging);
}

CPAKPARSER_API Dataminer* dataminer_construct(const char* PaksFolderDir)
{
auto Result = Dataminer(PaksFolderDir);
return &Result;
}

CPAKPARSER_API void dataminer_set_version_ue5(Dataminer* This, int Version)
{
This->SetVersionUE5(Version);
}
I think it only happens when dealing with numbers? what's up with that I managed to fix it but now my strings end up corrupted?
CPAKPARSER_API bool dataminer_load_type_mappings(Dataminer* This, const char* UsmapFilePath)
{
return This->LoadTypeMappings(UsmapFilePath);
}
CPAKPARSER_API bool dataminer_load_type_mappings(Dataminer* This, const char* UsmapFilePath)
{
return This->LoadTypeMappings(UsmapFilePath);
}
[CPakParser] ERR Invalid paks directory Content\Paksď`☻
[CPakParser] ERR Invalid paks directory Content\Paksď`☻
const library = Deno.dlopen(DLL_PATH, {
// other FFI exports here
"dataminer_load_type_mappings": {
parameters: ["pointer", "buffer"],
result: "bool"
}
});

function encode(text: string) {
return new TextEncoder().encode(text);
}

// function in the class that wraps it
loadTypeMappings(usmapFileDir: string) {
return library.symbols.dataminer_load_type_mappings(this.pointer, encode(usmapFileDir));
}
const library = Deno.dlopen(DLL_PATH, {
// other FFI exports here
"dataminer_load_type_mappings": {
parameters: ["pointer", "buffer"],
result: "bool"
}
});

function encode(text: string) {
return new TextEncoder().encode(text);
}

// function in the class that wraps it
loadTypeMappings(usmapFileDir: string) {
return library.symbols.dataminer_load_type_mappings(this.pointer, encode(usmapFileDir));
}
Got it to work by doing uint32_t UsmapLength, const char* UsmapFilePath but idk if that's the way to go without leaking memory
AapoAlas
AapoAlas•12mo ago
You at least don't seem to be null-terminating your strings. That's going to be an issue since then C++ won't know when the strings end and odd things happen. Giving the length explicitly will then fix that issue since now C++ can rely on the length parameter to stop reading the string and doesn't need to look for a null byte.
Kyiro
Kyiro•12mo ago
How do I pass a null terminated string to C++ from Deno? do i just add a null byte at the end myself?
AapoAlas
AapoAlas•12mo ago
Yeah, easiest and probably fastest is to do
new TextEncoder().encode(`${myString}\0`)
new TextEncoder().encode(`${myString}\0`)
Kyiro
Kyiro•12mo ago
alr ty will try
AapoAlas
AapoAlas•12mo ago
If you know you're dealing with ASCII strings then you could also use encodeInto with a buffer you create yourself with its length being one longer than the length of your string. The extra byte is then your null byte. That could lead to a faster call, but it could also be slower since the buffer is initially zeroes which is not needed in the above version.
Kyiro
Kyiro•12mo ago
seems like this worked i never expected to deal with pointers in javascript lmfao but it's pretty nice
AapoAlas
AapoAlas•12mo ago
Yeah 🙂 FFI pretty much drew me into Deno in the first place, and by god it's been fun to deal with bytes and pointers and whatnot in JS. It feels so dirty but oh so fun 🙂 Just in case you haven't seen this, do check out Denonomicon: https://denonomicon.deno.dev/introduction I've written up at least some of the internals and intricacies of FFI there.
Kyiro
Kyiro•12mo ago
never heard about the denomicon thank you!
AapoAlas
AapoAlas•12mo ago
Also in YouTube we did a three part podcasty video series on FFI with Andy. It's on Deno's channel with the name "WTF is FFI"
Kyiro
Kyiro•12mo ago
last question, the "V8 Fast API" in the denomicon is just used internally within Deno and is not usable via the FFI APIs right?
AapoAlas
AapoAlas•12mo ago
Yes. It gets used automatically with all FFI calls except calls that use struct-by-value parameters or return values ({ struct: [...] }) And feel free to ask as many questions as you feel like, I'm happy to see FFI being used and enjoyed! 🙂