-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issues with TheChroniclesOfMyrtana mod #58
Comments
Thanks, I'll look into it. Just real quick:
Done. Files are now more helpfully labeled. The code you are referencing here can now be found in Source/CATINV_NEXTNONACTIVEITEM.d: func int CATINV_NEXTNONACTIVEITEM(var int LIST, var int MAX) {
var C_ITEM ITM;
var ZCLISTSORT L;
var int I;
I = 0;
WHILE((LIST) && ((I) < (MAX)));
L = _^(LIST);
if (HLP_IS_OCITEM(L.DATA)) {
ITM = _^(L.DATA);
if (((ITM.FLAGS) & (ITEM_ACTIVE)) != (ITEM_ACTIVE)) {
BREAK;
};
I += 1;
};
LIST = L.NEXT;
END;
return I;
} You can find the old version of the decompilation in the |
Just checked out new code. Apparently |
Yes they are now scattered throughout |
I've added 2 more things for control flow.
Here I'm planning to inject native function in replace of 'end;' |
This commit fixes a crash with higher-order functions. Previously, passing a function as a parameter might cause the callee to copy its value into a local variable which did not have enough space for the function value. With this fix, one integer of space is allocated for non-const function symbols which should fix that issue.
This commit adds additional checks for division by zero errors during `opcode::div`, `opcode::mod` and `opcode::divmovi`. If a division by zero is detected, a VM exception is thrown.
Previously, if the parent class of the instance was not found, the VM would try to de-reference a null pointer which is not a good thing. With this fix, an exception is thrown instead.
This should be fixed now. Because the script will assign function parameters to local variables, this caused issues with higher-order functions since no memory was actually allocated for the local (here
Yeah, that would be a null-pointer de-reference. I've replaced with an exception :)
This will require quite a bit of rework in the script. Currently, it's not even possible to change the value of a
This already works for instances. It won't work for general variables right now since I can't verify that that external will actually be called with a variable (it could just be called with a literal). If you'll scout the bytecode for
Basically, you just need access to the call-stack and a way to set the program counter, right? |
Are you sure? At least in place, where exception been hit, there is access: void symbol::set_int(std::int32_t value, std::size_t index, const std::shared_ptr<instance>& context) {
if (is_const()) {
//throw illegal_const_access(this);
}
I've checked source code, seem to be OK:
I don't really see how... Yet, I'm welcome for ideas, because scouting byte-code is clearly not a great workflow. As a side idea: have a naked calls, with no stack entry, for |
Yes, that's what I mean. If you try to set the value of a const variable, it will throw. That would have to change, of course, as I mentioned.
In this case, yes. That's not safe everywhere though. Maybe there is some way around making function overrides unsafe like that though, not sure.
If you override |
I need to think about it. So far not going smooth:
Nit: other way around. So far naked call, with no stack frame seem to be a good versatile solution.. except they need access to vm-variables stack to be usefull... |
Testing latest version of
Seems Offended commit: f0d6751 |
Whoops. And that's why I need script tests. It was an incorrect switch fall-through. Fixed in dc0e340. |
Previously, the `symbol` did not allow for the mutation of symbols declared `const`. This presents an issue whenever the VM's owner wants to apply some sort of runtime fix or just generally wants to be able to mutate the VM entirely. Changing this also allows for specifying a VM execution flag for allowing the mutation of symbols declared `const`. This is of use in some corner cases, like Gothic mods which exploit a vulnerability in the original VM implementation to gain direct memory access.
So after thinking about the (Daedalus code) func void do_something() {
var int i = 0;
repeat(i, 10);
wld_settime(10, i); // or whatever
end;
return;
} (C++ code) void setup(phoenix::vm& vm) {
// misc vm setup ...
vm.override_function("repeat", [&vm](phoenix::symbol* sym, int n) {
// introspect call stack
auto caller = vm.caller();
// scout bytecode for END; (you might want to cache the result)
auto pc = caller.pc;
auto instr_count = 0;
for (auto i = 0u; i < 50 /* arbitrary limit */; ++i) {
auto instr = vm.instruction_at(pc);
// either check for symbol index directly ...
if (instr.op == phoenix::opcode::pushv && instr.symbol == 0x165b) {
break;
}
// ... or for symbol name
if (instr.op == phoenix::opcode::pushv) {
auto* sym = vm.find_symbol_by_index(instr.symbol);
if (sym != nullptr && sym.name == "END") {
break;
}
}
pc += instr.size();
instr_count += 1;
}
// run the original instructions n - 1 times
for (int i = sym->get_int(); i < n - 1; ++i) {
sym->set_int(i);
vm.unsafe_exec_from(caller.pc, instr_count);
}
sym->set_int(n - 1);
// exit; the bytecode after repeat() will run one more time
});
} Obviously, the APIs |
Hm, can't say that I like it:
Naked call also do suffer from inability to use |
You're right I totally forgot about Things considered
The ideaLet's subclass class my_vm : phoenix::vm {
public:
my_vm(phoenix::script&& s) : phoenix::vm(std::forward(s), /* flags ... */) {
this->override_function("repeat", [this](phoenix::symbol* s, int n) {
return this->repeat(s, n);
});
}
void push_reference(phoenix::symbol* sym, uint8_t i) override final {
if (sym->name() == "END" && _m_loop_begin) {
this->_m_loop_end = this->pc();
this->jump(*_m_loop_begin);
return;
}
if (sym->name() == "CONTINUE" && _m_loop_begin) {
this->jump(*_m_loop_begin);
return;
}
if (sym->name() == "BREAK") {
this->jump(this->find_loop_end());
// reset
_m_loop_begin = std::nullopt;
_m_loop_end = std::nullopt;
return;
}
phoenix::vm::push_reference(sym, i);
}
private:
uint32_t find_loop_end() {
// discover `pushv END` like in my last comment and assign it to _m_loop_end
return *_m_loop_end;
}
phoenix::naked_call repeat(phoenix::symbol* i, int count)
if (!_m_loop_begin) {
// save loop start
_m_loop_begin = this->caller().pc;
}
if (i->get_int() >= count) {
// jump to loop end
this->jump(this->find_loop_end());
// reset
_m_loop_begin = std::nullopt;
_m_loop_end = std::nullopt;
return {};
}
// increment i and continue loop
i->set_int(i->get_int() + 1);
this->jump(this->caller().pc);
return {};
}
private:
std::optional<uint32_t> _m_loop_begin, _m_loop_end;
}; Obviously there are some optimizations possible here (like looking up the symbol indices of I'm not sure how this process would work for other custom Union constructs but it seems plausible that something like this could work too. It's also quite fast since you only have to scout the bytecode for Changes needed in
|
+ naked calls + loop traps GothicKit#58
I've pushed prototype for loops support.
Already done some testing locally - application side is clean and simple loops do work. Testing of |
One more big feature to discuss is dynamically allocated memory. Some ikarus code for context: instance _HT_CREATEPTR.ARR(ZCARRAY)
func int MEM_ARRAYCREATE() {
return MEM_ALLOC(SIZEOF_ZCARRAY);
}
func int _HT_CREATEPTR(var int SIZE) {
PTR = MEM_ARRAYCREATE();
ARR = _^(PTR);
ARR.ARRAY = MEM_ALLOC((SIZE) * (4));
ARR.NUMALLOC = (SIZE) * (4);
ARR.NUMINARRAY = 0;
return PTR;
} OpenGothic can hook into Another issue is class-declaration. Class |
There would be a pretty simple way of achieving that which I'll implement later this week.
How would you like to deal with that and/or what do you think you'll need from me? I don't have too much time to look into this at the moment, sorry :/ |
Only raising for now, do not have great ideas on my end either. Currently experimenting with solution based on proxy-instance: std::shared_ptr<phoenix::instance> Ikarus::mem_ptrtoinst(ptr32_t address) {
// memory_instance is special case in VM
return std::make_shared<memory_instance>(address);
}
...
int Ikarus::get_mem_int(std::shared_ptr<phoenix::instance> base, symbol* sym, size_t index) {
ptr = base->address + sym->offset + index*sym->sizeof_;
return allocator.readInt(ptr); // map to C++ real memory and return value to VM
}
No, rush. Keep in mind that both features should interact together, as script will probably allocate memory for those directly via |
It ended up being not quite as simple as I thought. I've pushed a minimal working implementation to experiment/opaque-instances. Feel free to adjust it to your liking and add it to #74 if you want. Basic usage is: phoenix::vm vm {...};
phoenix::register_all_script_classes(vm);
// Will actually run the initialization routine for ALL unregistered instances and assign them as an opaque object which is inaccessible to C++-code
vm.initialize_unregistered_instances_as_opaque(); |
Cool, but please do not forget to review #74 first. About DMA-access:
In struct Ikarus::memory_instance : public phoenix::instance {
explicit memory_instance(ptr32_t address):address(address){}
ptr32_t address;
};
Generally almost works... Doesn't work for:
|
Oh that would be really bad. It would require a re-design of the entire script/vm implementation since I only support
I'm not sure if this is what you want. fc940fc introduces a |
I've evaluated this solution. Not quite something that can work for mods: For instances allocated by Another(partially orthogonal case) is classes allocated in traditional way ( Current idea:
|
Hm but everything inside it is allocated using the
Maybe I'm missing something but that doesn't seem possible. Since there are many classes that might be opaque to C++ their size must be dynamic, which must lead to a heap allocation where we can't access a member of a given class by an offset from its
You can already do that by manually calling |
Yes, we just need to "not crash" on bad-access from script. Specially when those cases caused by lack of Ikarus features. (some objects are not mapped to script memory)
True. Need to look for good enough solution. My current understanding, that class-support can be broken into cases:
My plan was to extend mem-trap system to have non-trivial address mapping here - create illusion of g2-like strings. for plain data, also can be useful when class layout is not 1:1. BAR_HP = MEM_PTRTOINST(MEM_GAME.HPBAR);
BAR_HP.ZCVIEW_VPOSX = ... While, writing this comment realize: Hm, probably need to decouple it into ticket and close this one; for now I'll continue with case-study. Probably mem_trap + some extras will work good enough. |
I'm looking into KoM startup routine, to see if it's possible to have adequate support for this mod.
Before phoenix, I was able to react stating location and play tutorial segment on a ship.
Issues so far:
symbol::set_int
C++ side:
Script call-stack:
Apparently it's related to assignment to
HOOKENGINEF.FUNCTION
(function override?)opcode::div
andopcode::mod
If script divides by zero - whole application crashes
allocate_instance
, when called for"$INSTANCE_HELP"
Most likely not intended by script - happens late in initialize routines, and everything is supper broke there, due to no DMA support.
Proposed fix: introduce
vm_allow_const_cast_access
, flag similar tovm_allow_null_instance_access
Local variables were displayed as global. I'm aware, that Daedalus has no real stack-variables, so just readability issue.
This is required to implement control-flow functions in ikarus. Basically my plan is to:
vm.override_function("repeat", [this](phoenix::symbol* var, int count){ repeat(var,count); })
And access(and memorize)
var
as a reference to loop-counter.Proposed solution: remove check for
phoenix::symbol
incheck_external_params
, rest seem to work as-isFollowup to
repeat
. All together makes workflow:PS:
Probably it's better to have one ticket instead of million small ones.
The text was updated successfully, but these errors were encountered: