Add in the base registry

This commit is contained in:
Bjorn Beishline 2026-02-01 01:23:47 -07:00
parent eebcb0467e
commit 18442c2298
10 changed files with 550 additions and 25 deletions

View File

@ -18,6 +18,7 @@ public class NativePatchBukkit {
private static MethodHandle getLocationNative;
private static MethodHandle getWorldNative;
private static MethodHandle freeStringNative;
private static MethodHandle getRegistryDataNative;
// Struct layout matching Rust's #[repr(C)] AbilitiesFFI
private static final StructLayout ABILITIES_LAYOUT =
@ -184,7 +185,8 @@ public class NativePatchBukkit {
long setAbilitiesAddr,
long getLocationAddr,
long freeStringAddr,
long getWorldAddr
long getWorldAddr,
long getRegistryDataAddr
) {
// void rust_send_message(const char* uuid, const char* message)
sendMessageNative = LINKER.downcallHandle(
@ -240,6 +242,11 @@ public class NativePatchBukkit {
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)
);
// const char* rust_get_registry_entries(const char* registry_name)
getRegistryDataNative = LINKER.downcallHandle(
MemorySegment.ofAddress(getRegistryDataAddr),
FunctionDescriptor.of(ValueLayout.ADDRESS, ValueLayout.ADDRESS)
);
}
/**
@ -401,4 +408,26 @@ public class NativePatchBukkit {
}
}
/**
* Get all data for a registry (entries + tags) as JSON.
*
* @param registryName The registry identifier (e.g. "sound_event", "block")
* @return JSON string with "entries" and "tags" fields, or null if registry unknown
*/
public static String getRegistryData(String registryName) {
try (Arena arena = Arena.ofConfined()) {
MemorySegment nameStr = arena.allocateFrom(registryName);
MemorySegment resultPtr = (MemorySegment) getRegistryDataNative.invokeExact(nameStr);
if (resultPtr.equals(MemorySegment.NULL)) return null;
try {
return resultPtr.reinterpret(Long.MAX_VALUE).getString(0);
} finally {
freeRustString(resultPtr);
}
} catch (Throwable t) {
throw new RuntimeException("Failed to get registry data: " + registryName, t);
}
}
}

View File

@ -0,0 +1,53 @@
package org.patchbukkit.registry;
import com.google.common.collect.ImmutableMap;
import io.papermc.paper.registry.RegistryKey;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Map;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
@Deprecated
public final class LegacyRegistryIdentifiers {
public static final Map<Class<?>, RegistryKey<?>> CLASS_TO_KEY_MAP;
static {
final ImmutableMap.Builder<Class<?>, RegistryKey<?>> builder = ImmutableMap.builder();
try {
for (final Field field : RegistryKey.class.getFields()) {
if (field.getType() == RegistryKey.class) {
// get the legacy type from the RegistryKey generic parameter on the field
final Class<?> legacyType = LegacyRegistryIdentifiers.erase(((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]);
builder.put(legacyType, (RegistryKey<?>) field.get(null));
}
}
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
CLASS_TO_KEY_MAP = builder.build();
}
private static Class<?> erase(Type type) {
if (type instanceof Class<?> clazz) {
return clazz;
} else if (type instanceof ParameterizedType parameterized) {
return (Class<?>) parameterized.getRawType();
} else if (type instanceof GenericArrayType arrayType) {
return Array.newInstance(erase(arrayType.getGenericComponentType()), 0).getClass();
} else if (type instanceof TypeVariable<?> typeVar) {
return erase(typeVar.getBounds()[0]);
} else if (type instanceof WildcardType wildcard) {
return erase(wildcard.getUpperBounds()[0]);
}
throw new IllegalArgumentException("Unsupported type: " + type);
}
private LegacyRegistryIdentifiers() {
}
}

View File

@ -0,0 +1,143 @@
package org.patchbukkit.registry;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.tag.Tag;
import io.papermc.paper.registry.tag.TagKey;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.patchbukkit.bridge.NativePatchBukkit;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Stream;
public class PatchBukkitRegistry<B extends Keyed> implements Registry<B> {
private final Map<NamespacedKey, B> entries = new LinkedHashMap<>();
private final Map<String, PatchBukkitTag<B>> tags = new LinkedHashMap<>();
/**
* Build a registry by fetching JSON from the native side and parsing
* entries and tags from the combined response.
*
* Expected JSON shape:
* {
* "entries": [{"name": "...", "id": ...}, ...],
* "tags": {"minecraft:logs": ["minecraft:oak_log", ...], ...}
* }
*
* @param registryName The native registry name (e.g. "sound_event")
* @param registryKey The Paper RegistryKey for this registry
* @param factory Converts a single JsonObject into a B instance
*/
public PatchBukkitRegistry(
String registryName,
RegistryKey<B> registryKey,
Function<JsonObject, B> factory
) {
String json = NativePatchBukkit.getRegistryData(registryName);
if (json == null) return;
JsonObject root = JsonParser.parseString(json).getAsJsonObject();
JsonArray entriesArray = root.getAsJsonArray("entries");
if (entriesArray != null) {
for (JsonElement element : entriesArray) {
B value = factory.apply(element.getAsJsonObject());
if (value != null) {
entries.put(value.getKey(), value);
}
}
}
JsonElement tagsElement = root.get("tags");
if (tagsElement != null && tagsElement.isJsonObject()) {
loadTags(tagsElement.getAsJsonObject(), registryKey);
}
}
private void loadTags(JsonObject tagMap, RegistryKey<B> registryKey) {
for (Map.Entry<String, JsonElement> entry : tagMap.entrySet()) {
String tagName = entry.getKey();
JsonArray members = entry.getValue().getAsJsonArray();
Set<NamespacedKey> memberKeys = new LinkedHashSet<>();
for (JsonElement member : members) {
String raw = member.getAsString();
// Tag values may or may not have "minecraft:" prefix
NamespacedKey key;
if (raw.contains(":")) {
key = NamespacedKey.fromString(raw);
} else {
key = new NamespacedKey(NamespacedKey.MINECRAFT, raw);
}
if (key != null) memberKeys.add(key);
}
NamespacedKey tagKeyName = tagName.contains(":")
? NamespacedKey.fromString(tagName)
: new NamespacedKey(NamespacedKey.MINECRAFT, tagName);
if (tagKeyName == null) continue;
TagKey<B> tagKey = TagKey.create(registryKey, tagKeyName);
tags.put(tagName, new PatchBukkitTag<>(tagKey, registryKey, memberKeys));
}
}
@Override
public @Nullable B get(NamespacedKey key) {
return entries.get(key);
}
@Override
public @Nullable NamespacedKey getKey(B value) {
return value.getKey();
}
@Override
public boolean hasTag(TagKey<B> key) {
return tags.containsKey(key.key().asString());
}
@Override
public @NonNull Tag<B> getTag(TagKey<B> key) {
PatchBukkitTag<B> tag = tags.get(key.key().asString());
if (tag == null) {
throw new NoSuchElementException("Unknown tag: " + key.key().asString());
}
return tag;
}
@Override
public @NonNull Collection<Tag<B>> getTags() {
return Collections.unmodifiableCollection(tags.values());
}
@Override
public @NonNull Stream<B> stream() {
return entries.values().stream();
}
@Override
public @NonNull Stream<NamespacedKey> keyStream() {
return entries.keySet().stream();
}
@Override
public int size() {
return entries.size();
}
@Override
public @NonNull Iterator<B> iterator() {
return Collections.unmodifiableCollection(entries.values()).iterator();
}
}

View File

@ -4,19 +4,77 @@ import io.papermc.paper.registry.RegistryAccess;
import io.papermc.paper.registry.RegistryKey;
import org.bukkit.Keyed;
import org.bukkit.Registry;
import org.bukkit.Sound;
import org.jspecify.annotations.Nullable;
import org.patchbukkit.bridge.NativePatchBukkit;
import com.google.gson.JsonObject;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
public class PatchBukkitRegistryAccess implements RegistryAccess {
private static final Map<RegistryKey<?>, Registry<?>> instances = new ConcurrentHashMap<>();
/**
* Maps a RegistryKey to (native registry name, factory function).
* Add an entry here for each registry you support.
*/
private static final Map<RegistryKey<?>, RegistryFactory<?>> FACTORIES = Map.of(
RegistryKey.SOUND_EVENT, new RegistryFactory<>(
"sound_event",
RegistryKey.SOUND_EVENT,
json -> new PatchBukkitSound(
json.get("name").getAsString(),
json.get("id").getAsInt()
)
)
);
private record RegistryFactory<B extends Keyed>(
String nativeRegistryName,
RegistryKey<B> registryKey,
Function<JsonObject, B> factory
) {}
@Override
public <T extends Keyed> @Nullable Registry<T> getRegistry(Class<T> type) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getRegistry'");
final RegistryKey<T> registryKey = byType(type);
return this.getRegistry(registryKey);
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public <T extends Keyed> Registry<T> getRegistry(RegistryKey<T> registryKey) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'getRegistry'");
if (registryKey == null) return null;
return (Registry<T>) instances.computeIfAbsent(registryKey, key -> {
RegistryFactory<?> factoryEntry = FACTORIES.get(key);
if (factoryEntry != null) {
return buildRegistry(factoryEntry);
}
// We return an empty registry for unsupported types, since if we don't the server will crash immediately when the registry class is first loaded
return new PatchBukkitRegistry<>(
key.key().value(),
(RegistryKey) key,
json -> null
);
});
}
private static <B extends Keyed> PatchBukkitRegistry<B> buildRegistry(RegistryFactory<B> factory) {
return new PatchBukkitRegistry<>(
factory.nativeRegistryName(),
factory.registryKey(),
factory.factory()
);
}
@SuppressWarnings({"unchecked", "deprecation"})
public static <T extends Keyed> @Nullable RegistryKey<T> byType(final Class<T> type) {
return (RegistryKey<T>) LegacyRegistryIdentifiers.CLASS_TO_KEY_MAP.get(type);
}
}

View File

@ -0,0 +1,62 @@
package org.patchbukkit.registry;
import net.kyori.adventure.key.Key;
import org.bukkit.NamespacedKey;
import org.bukkit.Sound;
public class PatchBukkitSound implements Sound {
private final NamespacedKey namespacedKey;
private final Key adventureKey;
private final String enumName;
private final int protocolId;
public PatchBukkitSound(String soundName, int protocolId) {
this.namespacedKey = new NamespacedKey(NamespacedKey.MINECRAFT, soundName);
this.adventureKey = Key.key(Key.MINECRAFT_NAMESPACE, soundName);
this.protocolId = protocolId;
this.enumName = soundName.toUpperCase().replace('.', '_');
}
@Override
public NamespacedKey getKey() {
return namespacedKey;
}
@Override
public Key key() {
return adventureKey;
}
@Override
public int ordinal() {
return protocolId;
}
@Override
public String name() {
return enumName;
}
@Override
public int compareTo(Sound other) {
return Integer.compare(this.ordinal(), other.ordinal());
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Sound other)) return false;
return namespacedKey.equals(other.getKey());
}
@Override
public int hashCode() {
return namespacedKey.hashCode();
}
@Override
public String toString() {
return "PatchBukkitSound{" + namespacedKey + "}";
}
}

View File

@ -0,0 +1,56 @@
package org.patchbukkit.registry;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import io.papermc.paper.registry.tag.Tag;
import io.papermc.paper.registry.tag.TagKey;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.jetbrains.annotations.Unmodifiable;
import java.util.*;
import java.util.stream.Collectors;
public class PatchBukkitTag<B extends Keyed> implements Tag<B> {
private final TagKey<B> tagKey;
private final Set<TypedKey<B>> memberKeys;
public PatchBukkitTag(TagKey<B> tagKey, RegistryKey<B> registryKey, Set<NamespacedKey> rawKeys) {
this.tagKey = tagKey;
this.memberKeys = rawKeys.stream()
.map(k -> TypedKey.create(registryKey, k))
.collect(Collectors.toUnmodifiableSet());
}
@Override
public TagKey<B> tagKey() {
return tagKey;
}
@Override
public @Unmodifiable Collection<TypedKey<B>> values() {
return memberKeys;
}
@Override
public @Unmodifiable Collection<B> resolve(Registry<B> registry) {
List<B> resolved = new ArrayList<>();
for (TypedKey<B> key : memberKeys) {
B value = registry.get(key.key());
if (value != null) resolved.add(value);
}
return Collections.unmodifiableList(resolved);
}
@Override
public boolean contains(TypedKey<B> valueKey) {
return memberKeys.contains(valueKey);
}
@Override
public RegistryKey<B> registryKey() {
return tagKey.registryKey();
}
}

93
rust/Cargo.lock generated
View File

@ -1695,10 +1695,12 @@ dependencies = [
"log",
"pumpkin",
"pumpkin-api-macros",
"pumpkin-util",
"pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"rust-embed",
"serde",
"serde-saphyr",
"serde_json",
"tokio",
"uuid",
"walkdir",
@ -1977,12 +1979,12 @@ dependencies = [
"num-bigint",
"pkcs8 0.11.0-rc.10",
"pumpkin-config",
"pumpkin-data",
"pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-inventory",
"pumpkin-macros",
"pumpkin-nbt",
"pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-protocol",
"pumpkin-util",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-world",
"rand",
"rsa",
@ -2019,12 +2021,32 @@ version = "0.1.0-dev+1.21.11"
source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa3969502c13d5d284da59fe1017ed9a"
dependencies = [
"log",
"pumpkin-util",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"serde",
"toml",
"uuid",
]
[[package]]
name = "pumpkin-data"
version = "0.1.0-dev+1.21.11"
source = "git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master#007b97eed8243cf744cb0353b12438db8eab2938"
dependencies = [
"crc-fast",
"heck",
"indexmap",
"phf",
"proc-macro2",
"pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)",
"quote",
"rayon",
"serde",
"serde_json",
"serde_json5",
"syn",
]
[[package]]
name = "pumpkin-data"
version = "0.1.0-dev+1.21.11"
@ -2035,8 +2057,8 @@ dependencies = [
"indexmap",
"phf",
"proc-macro2",
"pumpkin-nbt",
"pumpkin-util",
"pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"quote",
"rayon",
"serde",
@ -2052,9 +2074,9 @@ source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa
dependencies = [
"crossbeam-utils",
"log",
"pumpkin-data",
"pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-protocol",
"pumpkin-util",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-world",
"thiserror",
"tokio",
@ -2067,11 +2089,23 @@ source = "git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master#f93be68caa
dependencies = [
"proc-macro-error2",
"proc-macro2",
"pumpkin-data",
"pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"quote",
"syn",
]
[[package]]
name = "pumpkin-nbt"
version = "0.1.0-dev+1.21.11"
source = "git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master#007b97eed8243cf744cb0353b12438db8eab2938"
dependencies = [
"bytes",
"cesu8",
"flate2",
"serde",
"thiserror",
]
[[package]]
name = "pumpkin-nbt"
version = "0.1.0-dev+1.21.11"
@ -2094,10 +2128,10 @@ dependencies = [
"bitflags",
"bytes",
"cfb8",
"pumpkin-data",
"pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-macros",
"pumpkin-nbt",
"pumpkin-util",
"pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-world",
"serde",
"take_mut",
@ -2106,6 +2140,31 @@ dependencies = [
"uuid",
]
[[package]]
name = "pumpkin-util"
version = "0.1.0-dev+1.21.11"
source = "git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master#007b97eed8243cf744cb0353b12438db8eab2938"
dependencies = [
"base64",
"bytes",
"colored",
"ecdsa",
"enum_dispatch",
"md5",
"num-derive",
"num-traits",
"p384",
"proc-macro2",
"pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/BjornTheProgrammer/Pumpkin.git?branch=master)",
"quote",
"serde",
"serde_json",
"syn",
"thiserror",
"tokio",
"uuid",
]
[[package]]
name = "pumpkin-util"
version = "0.1.0-dev+1.21.11"
@ -2121,7 +2180,7 @@ dependencies = [
"num-traits",
"p384",
"proc-macro2",
"pumpkin-nbt",
"pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"quote",
"serde",
"serde_json",
@ -2152,9 +2211,9 @@ dependencies = [
"num-traits",
"num_cpus",
"pumpkin-config",
"pumpkin-data",
"pumpkin-nbt",
"pumpkin-util",
"pumpkin-data 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-nbt 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"pumpkin-util 0.1.0-dev+1.21.11 (git+https://github.com/Pumpkin-MC/Pumpkin.git?branch=master)",
"rand",
"rustc-hash",
"ruzstd",

View File

@ -8,6 +8,7 @@ crate-type = ["cdylib"]
[dependencies]
pumpkin = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin" }
pumpkin-data = { git = "https://github.com/BjornTheProgrammer/Pumpkin.git", branch = "master", package = "pumpkin-data" }
pumpkin-util = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-util" }
pumpkin-api-macros = { git = "https://github.com/Pumpkin-MC/Pumpkin.git", branch = "master", package = "pumpkin-api-macros" }
@ -20,12 +21,13 @@ rust-embed = { version = "8.11.0", features = ["interpolate-folder-path"] }
walkdir = "2"
zip = "7.2"
serde-saphyr = "0.0.16"
serde = { version = "1", features = ["derive"] }
anyhow = "1.0"
uuid = "1.20.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde-saphyr = "0.0.16"
[build-dependencies]
j4rs = { version = "0.24", artifact = "cdylib", lib = true }
env_logger = "0.11.8"

View File

@ -9,6 +9,7 @@ pub mod events;
pub mod location;
pub mod memory;
pub mod message;
pub mod registry;
pub mod utils;
pub mod world;
@ -42,6 +43,7 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
let get_location_addr = location::rust_get_location as *const () as i64;
let free_string_addr = memory::rust_free_string as *const () as i64;
let get_world_addr = world::rust_get_world as *const () as i64;
let rust_get_registry_data_addr = registry::rust_get_registry_data as *const () as i64;
jvm.invoke_static(
"org.patchbukkit.bridge.NativePatchBukkit",
@ -54,6 +56,7 @@ pub fn initialize_callbacks(jvm: &Jvm) -> Result<()> {
InvocationArg::try_from(get_location_addr)?.into_primitive()?,
InvocationArg::try_from(free_string_addr)?.into_primitive()?,
InvocationArg::try_from(get_world_addr)?.into_primitive()?,
InvocationArg::try_from(rust_get_registry_data_addr)?.into_primitive()?,
],
)?;

View File

@ -0,0 +1,60 @@
use std::{collections::HashMap, ffi::CString, os::raw::c_char};
use serde::Serialize;
use serde_json::Value;
use crate::java::native_callbacks::utils::get_string;
#[derive(Serialize, Debug, Clone)]
struct Registry {
pub entries: Vec<Value>,
pub tags: HashMap<String, Vec<String>>,
}
impl Registry {
pub fn new(entries: Vec<Value>, tags: HashMap<String, Vec<String>>) -> Self {
Registry { entries, tags }
}
}
#[unsafe(no_mangle)]
pub extern "C" fn rust_get_registry_data(registry_name: *const c_char) -> *const c_char {
let name = get_string(registry_name);
let registry: Option<Registry> = match name.as_ref() {
"sound_event" => {
let entries: Vec<serde_json::Value> = pumpkin_data::sound::Sound::slice()
.iter()
.map(|s| {
serde_json::json!({
"name": s.to_name(),
"id": *s as u16
})
})
.collect();
Some(Registry::new(entries, HashMap::new()))
}
_ => None,
};
let registry = match registry {
Some(registry) => registry,
None => return std::ptr::null(),
};
let json_str = match serde_json::to_string(&registry) {
Ok(json_str) => json_str,
Err(err) => {
log::error!("Failed to serialize registry: {}", err);
return std::ptr::null();
}
};
match CString::new(json_str) {
Ok(cstring) => cstring.into_raw(),
Err(err) => {
log::error!("Failed to convert registry string to CString: {}", err);
return std::ptr::null_mut();
}
}
}