if Il2CppString expects data to actually be trailing and not a pointer, then you need to take a different approach. This is what I'd do:
pub fn init(ally: std.mem.Allocator, s: []const u8) !*Il2CppString {
const utf16_len = try std.unicode.calcUtf16LeLen(s);
const full_byte_len = @sizeOf(Il2CppString) + (utf16_len * 2);
var string_bytes = try ally.alignedAlloc(u8, @alignOf(Il2CppString), full_byte_len);
var string = @as(*Il2CppString, @ptrCast(string_bytes.ptr));
string.* = .{
.klass = null,
.monitor = null,
.len = utf16_len,
.data = undefined,
};
var data_slice = @as([*]u16, @ptrCast(&string.data))[0..utf16_len];
std.unicode.utf8ToUtf16Le(data_slice, s) catch unreachable;
return string;
}
pub fn deinit(self: *Il2CppString, ally: std.mem.Allocator) void {
const byte_len = @sizeOf(Il2CppString) + self.len * 2;
const byte_slice = @as([*]align(@alignOf(Il2CppString)) u8, @ptrCast(self))[0..byte_len];
ally.free(byte_slice);
}
totally untested, so i might have gotten some of the casting and whatnot wrong but that'd be the idea--basically, allocate all the bytes necessary and then use the allocated bytes as the memory for the struct. you can't allocate a struct like this on the stack since data is variable length