GothicKit / ZenKit

A re-implementation of file formats used by the early 2000's ZenGin
http://zk.gothickit.dev/
MIT License
49 stars 9 forks source link

DaedalusVM - external_function call - shared_ptr seems to point to nowhere any longer #64

Closed JaXt0r closed 1 year ago

JaXt0r commented 1 year ago

Hi there,

I'm currently struggling with shared_ptr on an external function call. If I want to check values of the npcRef smart pointer, I get a Segmentation fault. I checked with OpenGothic's implementation but can't find my issue why I get the error.

Any pointer into the right direction would be helpful. :-) Thank you.

auto vm = createVM((char*)"C:\\PATH\\TO\\GOTHIC.DAT");
register_all_script_classes(*vm);
vm->register_default_external([](std::string_view name) { std::cout << "VM: No external for " << name << "\n"; });

vm->register_external("Wld_InsertNpc", [vm](int npcInstance, std::string_view spawnpoint) {
    std::cout << "Wld_InsertNpc called. npcInstance: " << npcInstance << ", spawnPoint: " << spawnpoint << std::endl;

    auto tempNpc = std::make_shared<c_npc>();
    auto sym = vm->find_symbol_by_index(uint32_t(npcInstance));

    vm->init_instance(tempNpc, sym);

    if(tempNpc->daily_routine!=0) {
      auto* daily_routine = vm->find_symbol_by_index(uint32_t(tempNpc->daily_routine));

      std::cout << "NAME before daily_routine called: " << *tempNpc->name << std::endl; // 1. e.g. Buddler
      vm->call_function(daily_routine);
      std::cout << "NAME after daily_routine called: " << *tempNpc->name << std::endl; // 1. e.g. Buddler
    }
});

vm->register_external("TA_MIN", [](std::shared_ptr<phoenix::c_npc> npcRef, int start_h, int start_m, int stop_h, int stop_m, int action, std::string_view waypoint) {
    npcRef->name; // 0x28
    std::string name = *npcRef->name; // 2. Segmentation fault
    std::cout << "TA_MIN called: npc:" << name << std::endl;
});

Output:

Wld_InsertNpc called. npcInstance: 7656, spawnPoint: OC1
VM: No external for NPC_SETTALENTVALUE
[...]
VM: No external for CREATEINVITEM
NAME before daily_routine called: Buddler
Segmentation fault
lmichaelis commented 1 year ago

Hi @JaXt0r, in this case, npcRef in your implementation of TA_MIN is nullptr. This happens, because the value passed as the first parameter is actually SELF which you did not set when calling the daily routine. You need to vm.global_self()->set_instance(tempNpc) before calling the daily routine. You should also save the previous value of global_self and restore it after you have called the daily routine.


Here's the steps you can take to debug a problem like this. In the offending callback, put a vm.print_stack_trace() (or call it from a debugger). This will give you a call stack as well as the values currently on the VM's stack:

------- CALL STACK (MOST RECENT CALL FIRST) -------
in TA_MIN at 0x60a4c
in TA_STAND_WP at 0x60a4c
in RTN_START_309 at 0x9bc7d
in WLD_INSERTNPC at 0x220238
in STARTUP_NEWWORLD_PART_CITY_01 at 0x220238
in STARTUP_NEWWORLD at 0x224d7a

------- STACK (MOST RECENT PUSH FIRST) -------

Here we can see that TA_MIN is called from TA_STAND_WP which we can look up either in the script source which can be obtained from the Gothic II Modkit (_work/Data/Scripts/CONTENT) or by using the zscript tool from phoenix-studio. Here's how you could use it in this case:

$ zscript -f GOTHIC.DAT -k -s TA_STAND_WP

func void TA_STAND_WP(var int START_H, var int START_M, var int STOP_H, var int STOP_M, var string WAYPOINT) {
    TA_MIN(SELF, START_H, START_M, STOP_H, STOP_M, 9784, WAYPOINT);
}

Here you can see that TA_MIN is called with SELF as the first parameter.

JaXt0r commented 1 year ago

Wonderful. Works! Saved my day. :-)

Also thanks for the detailled explanation. Especially the stacktrace is super easy to use.