【寻找逆向大佬中】关于HMX ecl opcode的求助
本人技术宅,上个暑假写了个STG游戏的框架,准备把HMX移植到64位通用c99上(SDL2+openAL).本体已经做完 在github repo但是对于opcode的一些细节产生了困难 比如#include <openstg.h>
#define VOID enemy_data* enm
#define P1 enemy_data* enm, param_t *p1
#define P2 enemy_data* enm, param_t *p1, param_t *p2
#define P3 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3
#define P4 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3, param_t *p4
#define P5 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3, param_t *p4, param_t *p5
#define P6 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3, param_t *p4, param_t *p5, param_t *p6
#define P7 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3, param_t *p4, param_t *p5, param_t *p6, param_t *p7
#define P8 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3, param_t *p4, param_t *p5, param_t *p6, param_t *p7, param_t *p8
#define P9 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3, param_t *p4, param_t *p5, param_t *p6, param_t *p7, param_t *p8, param_t *p9
#define P14 enemy_data* enm, param_t *p1, param_t *p2, param_t *p3, param_t *p4, param_t *p5, param_t *p6, param_t *p7, param_t *p8, param_t *p9, param_t *p10, param_t *p11, param_t *p12, param_t *p13, param_t *p14
#define ECL_INS void
// only void, 1, 2, 3, 4, 5, 6, 7, 8, 9, 14
int ins_arg_count[] =
{ 0, 1, 2, 3, 2, 2, 2, 3, 2, 3, 1, 1, 1, 3, 3, 3, 3, 3, 1, 1, 3, 3, 3, 3, 3,
5, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 0, 5, 5, 5, 5, 5, 5, 3, 3, 2, 1, 1, 1,
2, 2, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 1, 1, 1, 1, 4, 0, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1,
0, 0, 0, 3, 8,
0, 1, 14, 14, 1, 2, 2, 4, 1, 1, 3, 0, 7, 0, 1, 5, 2, 1, 1, 5, 3, 1, 1,
1, 1, 1, 2, 1,
1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 0, 1, 1, 1, 2, 1, 6, 1, 0, 0,
1, 0
};
int read_int_var(enemy_data * enm, int var_id)
{ // only used to get int
if (var_id >= 0 && var_id < FIELD_RANK_ID) {
// local var
ecl_stack_frame *frame = &(enm->stack);
if (var_id <= 3) {
return frame->i_local_grp_1;
} else if (var_id <= 7) {
return -1;
} else {
var_id -= 8;
return frame->i_local_grp_2;
}
}
switch (var_id) {
case FIELD_RANK_ID:{
return rank;
}
case FIELD_DIFFICULTY_ID:{
return difficulty;
}
case FIELD_TIME_ID:{
return enm->tick;
}
case FIELD_LIFE_ID:{
return enm->life;
}
case FIELD_SHOT_TYPE_ID:{
return shot_type;
}
}
return -1;
}
float read_float_var(enemy_data * enm, int var_id)
{ // only used to get float
if (var_id >= 0 && var_id < FIELD_RANK_ID) {
// local var
ecl_stack_frame *frame = &(enm->stack);
if (var_id <= 3) {
return -NAN;
} else if (var_id <= 7) {
var_id -= 4;
return frame->f_local_grp_1;
} else {
return -NAN;
}
}
switch (var_id) {
case FIELD_X_ID:{
return enm->x;
}
case FIELD_Y_ID:{
return enm->y;
}
case FIELD_Z_ID:{
return enm->z;
}
case FIELD_PLAYER_X_ID:{
return player_x;
}
case FIELD_PLAYER_Y_ID:{
return player_y;
}
case FIELD_PLAYER_Z_ID:{
return player_z;
}
case FIELD_AIM_ID:{
return enm->aim;
}
case FIELD_DIST_ID:{
return enm->dist;
}
}
return -NAN;
}
void write_int_var(enemy_data * enm, int var_id, int data)
{ // only used to put int
if (var_id >= 0 && var_id < FIELD_RANK_ID) {
// local var
ecl_stack_frame *frame = &(enm->stack);
if (var_id <= 3) {
frame->i_local_grp_1 = data;
return;
} else if (var_id <= 7) {
return;
} else {
var_id -= 8;
frame->i_local_grp_2 = data;
return;
}
}
switch (var_id) {
case FIELD_TIME_ID:{
enm->tick = data;
return;
}
case FIELD_LIFE_ID:{
enm->life = data;
return;
}
}
}
void write_float_var(enemy_data * enm, int var_id, float data)
{ // only used to put float
if (var_id >= 0 && var_id < FIELD_RANK_ID) {
// local var
ecl_stack_frame *frame = &(enm->stack);
if (var_id <= 3) {
return;
} else if (var_id <= 7) {
var_id -= 4;
frame->f_local_grp_1 = data;
return;
} else {
return;
}
}
switch (var_id) {
case FIELD_X_ID:{
enm->x = data;
return;
}
case FIELD_Y_ID:{
enm->y = data;
return;
}
case FIELD_Z_ID:{
enm->z = data;
return;
}
case FIELD_PLAYER_X_ID:{
player_x = data;
return;
}
case FIELD_PLAYER_Y_ID:{
player_y = data;
return;
}
case FIELD_PLAYER_Z_ID:{
player_z = data;
return;
}
}
}
ECL_INS nop(VOID)
{
}
ECL_INS delete(P1)
{
// TODO: finish this after bossrush finished
info("multi enemy not supported yet");
}
ECL_INS jmp(P2)
{
write_int_var(enm, FIELD_TIME_ID, p1->i);
int ip = search_symbol(enm, p2->hash);
if (ip == -1) {
ABORT("bad symbol");
}
enm->ip = ip - 1;
}
ECL_INS loop(P3)
{
int cx = read_int_var(enm, p3->var_id);
if (cx == 0) {
return;
}
write_int_var(enm, p3->var_id, cx - 1);
jmp(enm, p1, p2);
}
ECL_INS iset(P2)
{
write_int_var(enm, p1->var_id, p2->i);
}
ECL_INS fset(P2)
{
write_float_var(enm, p1->var_id, p2->f);
}
ECL_INS iset_r(P2)
{
write_int_var(enm, p1->var_id, zundom_i(p2->i));
}
ECL_INS iset_r2(P3)
{
write_int_var(enm, p1->var_id, zundom_i2(p2->i, p3->i));
}
ECL_INS fset_r(P2)
{
write_float_var(enm, p1->var_id, zundom_f(p2->f));
}
ECL_INS fset_r2(P3)
{
write_float_var(enm, p1->var_id, zundom_f2(p2->f, p3->f));
}
ECL_INS get_x(P1)
{
write_float_var(enm, p1->var_id, enm->x);
}
ECL_INS get_y(P1)
{
write_float_var(enm, p1->var_id, enm->y);
}
ECL_INS get_z(P1)
{
write_float_var(enm, p1->var_id, enm->z);
}
ECL_INS iadd(P3)
{
write_int_var(enm, p1->var_id, (p2->i) + (p3->i));
}
ECL_INS isub(P3)
{
write_int_var(enm, p1->var_id, (p2->i) - (p3->i));
}
ECL_INS imul(P3)
{
write_int_var(enm, p1->var_id, (p2->i) * (p3->i));
}
ECL_INS idiv(P3)
{
write_int_var(enm, p1->var_id, (p2->i) / (p3->i));
}
ECL_INS imod(P3)
{
write_int_var(enm, p1->var_id, (p2->i) % (p3->i));
}
ECL_INS inc(P1)
{
if (p1->type == PARAM_TYPE_INT) {
write_int_var(enm, p1->var_id,
read_int_var(enm, p1->var_id) + 1);
} else if (p1->type == PARAM_TYPE_FLOAT) {
write_float_var(enm, p1->var_id,
read_float_var(enm, p1->var_id) + 1.0f);
}
}
ECL_INS dec(P1)
{
if (p1->type == PARAM_TYPE_INT) {
write_int_var(enm, p1->var_id,
read_int_var(enm, p1->var_id) - 1);
} else if (p1->type == PARAM_TYPE_FLOAT) {
write_float_var(enm, p1->var_id,
read_float_var(enm, p1->var_id) - 1.0f);
}
}
ECL_INS fadd(P3)
{
write_float_var(enm, p1->var_id, (p2->f) + (p3->f));
}
ECL_INS fsub(P3)
{
write_float_var(enm, p1->var_id, (p2->f) - (p3->f));
}
ECL_INS fmul(P3)
{
write_float_var(enm, p1->var_id, (p2->f) * (p3->f));
}
ECL_INS fdiv(P3)
{
write_float_var(enm, p1->var_id, (p2->f) / (p3->f));
}
ECL_INS ecl_fmod(P3) // name conflict by libm
{ // IEEE 754 remainder
write_float_var(enm, p1->var_id, IEEE_754_remainder(p2->f, p3->f));
}
ECL_INS ecl_atan2(P5) // name conflict by libm
{
write_float_var(enm, p1->var_id,
atan((p4->f - p2->f) / (p5->f - p3->f)));
}
ECL_INS norm_r(P1)
{
float f = read_float_var(enm, p1->var_id);
int pi_count = (int)(f / M_PI);
if (f <= 0.0f) {
f += pi_count * M_PI;
} else {
f -= pi_count * M_PI;
}
write_float_var(enm, p1->var_id, f);
}
ECL_INS itest(P2)
{
if (p1->i < p2->i) {
enm->cmp_reg = -1;
} else if (p1->i == p2->i) {
enm->cmp_reg = 0;
} else {
enm->cmp_reg = 1;
}
}
ECL_INS ftest(P2)
{
if (p1->f < p2->f) {
enm->cmp_reg = -1;
} else if (p1->f == p2->f) {
enm->cmp_reg = 0;
} else if (p1->f > p2->f) {
enm->cmp_reg = 1;
}
}
ECL_INS jmp_l(P2)
{
if (enm->cmp_reg == -1) {
jmp(enm, p1, p2);
}
}
ECL_INS jmp_le(P2)
{
if (enm->cmp_reg == -1 || enm->cmp_reg == 0) {
jmp(enm, p1, p2);
}
}
ECL_INS jmp_e(P2)
{
if (enm->cmp_reg == 0) {
jmp(enm, p1, p2);
}
}
ECL_INS jmp_g(P2)
{
if (enm->cmp_reg == 1) {
jmp(enm, p1, p2);
}
}
ECL_INS jmp_ge(P2)
{
if (enm->cmp_reg == 0 || enm->cmp_reg == 1) {
jmp(enm, p1, p2);
}
}
ECL_INS jmp_n(P2)
{
if (enm->cmp_reg == -1 || enm->cmp_reg == 1) {
jmp(enm, p1, p2);
}
}
ECL_INS call(P3)
{
enm->sp++;
if (enm->sp >= MAX_STACK_DEPTH) {
ABORT("stack overflow");
}
ecl_stack_frame *curr_stack = &(enm->stack);
// enm->ip always point to currently executing instruction
// as ip will automaticly increase by 1 after any instruction exection
// point return addr to currently "call" and after "return", ip will point to the next ins after "call"
curr_stack->return_address = enm->ip;
// set param
curr_stack->i_local_grp_1 = p2->i;
curr_stack->f_local_grp_1 = p3->f;
curr_stack->return_function = enm->fp;
int ip = search_sub(p1->hash, &enm->fp);
if (ip == -1) {
ABORT("bad sub");
}
enm->ip = ip - 1;
}
ECL_INS ret(VOID)
{
if (enm->sp == 0 || (enm->stack).return_address == -1
|| (enm->stack).return_function == -1) {
ABORT("stack underflow");
}
enm->ip = (enm->stack).return_address;
enm->fp = (enm->stack).return_function;
enm->sp--;
info("%i %i", enm->fp, enm->ip);
}
ECL_INS call_l(P3)
{
if (enm->cmp_reg == -1) {
call(enm, p1, p2, p3);
}
}
ECL_INS call_le(P3)
{
if (enm->cmp_reg == -1 || enm->cmp_reg == 0) {
call(enm, p1, p2, p3);
}
}
ECL_INS call_e(P3)
{
if (enm->cmp_reg == 0) {
call(enm, p1, p2, p3);
}
}
ECL_INS call_g(P3)
{
if (enm->cmp_reg == 1) {
call(enm, p1, p2, p3);
}
}
ECL_INS call_ge(P3)
{
if (enm->cmp_reg == 0 || enm->cmp_reg == 1) {
call(enm, p1, p2, p3);
}
}
ECL_INS call_n(P3)
{
if (enm->cmp_reg == -1 || enm->cmp_reg == 1) {
call(enm, p1, p2, p3);
}
}
ECL_INS pos(P3)
{
enm->x = p1->f;
enm->y = p2->f;
enm->z = p3->f;
}
ECL_INS dir(P2)
{
enm->dir = p1->f;
enm->v = p2->f;
}
ECL_INS rot(P1)
{
enm->delta_dir = p1->f;
}
ECL_INS spd(P1)
{
enm->v = p1->f;
}
ECL_INS acc(P1)
{
enm->deltav = p1->f;
}
ECL_INS rot_r(P2)
{
enm->dir = zundom_f2(p1->f, p2->f);
}
ECL_INS rot_r2(P2)
{
enm->dir = zundom_f2(p1->f, p2->f);
}
ECL_INS rot_aim(P2)
{ // document conflict between pytouhou(P2) and touhouwiki(P3)
enm->dir = player_angle((v2d) {
enm->x, enm->y}
) + p1->f;
enm->v = p2->f;
}
// all acc and dec need test to match original game behavior
ECL_INS it_dec(P3)
{
enm->dir = p2->f;
enm->v = p3->f;
enm->deltav = -abs_f(enm->deltav);
}
ECL_INS it_dec2(P3)
{
}
ECL_INS it_acc(P3)
{
}
ECL_INS it_acc2(P3)
{
}
ECL_INS it_pt_lin(P4)
{
}
ECL_INS it_pt_dec(P4)
{
}
ECL_INS it_pt_dec2(P4)
{
}
ECL_INS it_pt_acc(P4)
{
}
ECL_INS it_pt_acc2(P4)
{
}
ECL_INS it_t_dec(P4)
{
}
ECL_INS it_t_dec2(P4)
{
}
ECL_INS it_t_acc(P4)
{
}
ECL_INS it_t_acc2(P4)
{
}
ECL_INS clip(P4)
{
enm->is_clip = 1;
enm->clip_lu.x = p1->f;
enm->clip_lu.y = p2->f;
enm->clip_rd.x = p3->f;
enm->clip_rd.y = p4->f;
}
ECL_INS unclip(VOID)
{
enm->is_clip = 0;
}
ECL_INS et_on_fan_aim(P9)
{
create_buman();
bstyle(TRACE);
bshape(p2->i, p1->i);
boffset(enm->x + enm->launch_off_x, enm->y + enm->launch_off_y);
bamount(p3->i, p4->i);
bspeed(p5->f, p6->f);
bangle(rad2ang(p7->f), rad2ang(p8->f));
}
ECL_INS et_on_fan(P9)
{
create_buman();
bstyle(DEFAULT);
bshape(p2->i, p1->i);
boffset(enm->x + enm->launch_off_x, enm->y + enm->launch_off_y);
bamount(p3->i, p4->i);
bspeed(p5->f, p6->f);
bangle(rad2ang(p7->f), rad2ang(p8->f));
}
ECL_INS et_on_cir_aim(P9)
{
create_buman();
bstyle(LAYER_DIF_TRACE);
bshape(p2->i, p1->i);
boffset(enm->x + enm->launch_off_x, enm->y + enm->launch_off_y);
bamount(p3->i, p4->i);
bspeed(p5->f, p6->f);
bangle(rad2ang(p7->f), rad2ang(p8->f));
}
ECL_INS et_on_cir(P9)
{
create_buman();
bstyle(LAYER_DIF);
bshape(p2->i, p1->i);
boffset(enm->x + enm->launch_off_x, enm->y + enm->launch_off_y);
bamount(p3->i, p4->i);
bspeed(p5->f, p6->f);
bangle(rad2ang(p7->f), rad2ang(p8->f));
}
ECL_INS et_on_random_r(P9)
{
create_buman();
bstyle(RANDOM_DIR);
bshape(p2->i, p1->i);
boffset(enm->x + enm->launch_off_x, enm->y + enm->launch_off_y);
bamount(p3->i, p4->i);
bspeed(p5->f, p6->f);
bangle(rad2ang(p7->f), rad2ang(p8->f));
}
ECL_INS et_on_random_sp(P9)
{
create_buman();
bstyle(RANDOM_SPEED);
bshape(p2->i, p1->i);
boffset(enm->x + enm->launch_off_x, enm->y + enm->launch_off_y);
bamount(p3->i, p4->i);
bspeed(p5->f, p6->f);
bangle(rad2ang(p7->f), rad2ang(p8->f));
}
ECL_INS et_on_random(P9)
{
create_buman();
bstyle(RANDOM);
bshape(p2->i, p1->i);
boffset(enm->x + enm->launch_off_x, enm->y + enm->launch_off_y);
bamount(p3->i, p4->i);
bspeed(p5->f, p6->f);
bangle(rad2ang(p7->f), rad2ang(p8->f));
}
ECL_INS interrupt(P2)
{
// int behave as jmp to sub
int ip = search_sub(p1->hash, &enm->fp);
if (ip == -1) {
ABORT("bad sub")
}
enm->ip = ip - 1;
}
ECL_INS breakpoint(VOID)
{
printf("STOP AT MAGIC BREAKPOINT\n");
is_step = 1;
}
void *ins_prg[] =
{ nop, delete, jmp, loop, iset, fset, iset_r, iset_r2, fset_r, fset_r2,
get_x, get_y,
get_z, iadd, isub, imul, idiv, imod, inc, dec, fadd, fsub, fmul, fdiv,
fmod,
ecl_atan2, norm_r, itest, ftest, jmp_l, jmp_le, jmp_e, jmp_g, jmp_ge,
jmp_n, call, ret,
call_l, call_le, // spectre instruction
call_e, call_g, call_ge, call_n, pos, NULL, dir, rot, spd, acc, rot_r,
rot_r2, rot_aim,
it_dec, it_dec2,
it_acc, it_acc2, it_pt_lin, it_pt_dec, it_pt_dec2, it_pt_acc,
it_pt_acc2, it_t_dec, it_t_dec2, it_t_acc, it_t_acc2, clip,
unclip, et_on_fan_aim,
et_on_fan, et_on_cir_aim, et_on_cir, NULL, NULL, et_on_random_r,
et_on_random_sp, et_on_random, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, interrupt, NULL,
NULL,
NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, breakpoint
};中530行后有很多敌人移动等opcode在thwiki与外文wiki中没有对具体加速/速度的像素数作出解释
求逆向大佬可不可以翻出原版游戏的具体数值 谢谢
PS:当然我知道这种帖子肯定没人会看的了 只是希望出现奇迹(笑)
不懂帮顶,或许你可以尝试用THTK自己拆包看看? 定难军节度使 发表于 2025-2-13 20:06
不懂帮顶,或许你可以尝试用THTK自己拆包看看?
这个不是ECL或者THTK可以拆出来的 是属于游戏EXE内的硬编码 Jar387 发表于 2025-2-13 21:20
这个不是ECL或者THTK可以拆出来的 是属于游戏EXE内的硬编码
目前来说对原作ecl比较有研究的佬在魔改群里,您可以来这个群里讨论一下:489710775
这群里的伙计很多对thbwiki上ecl代码资料的完善做出不少贡献,应该能解答你的问题。 本帖最后由 Kuriyama_Mirai 于 2025-7-10 17:32 编辑
你听过 Happy Havoc 的 红魔乡逆向工程仓库 吗
帮你问了一些大佬,zero318 说“你可能需要迁就一些剩下的 opcode 而更改大量的代码,
尤其是 Patchy 用的 ‘call stack disable flag’”。
他还在 这里 写过一个更可读的 ECL 解析器,尽管那个解析器不是为了 100% 准确地模拟而写的;
不过,至少里面的 instructions 都是基于对原作代码基于比较良好的理解而命名的。
那里面的很多目前的文档,据他所说,不够完整,甚至在一些细节上有错误。
Kuriyama_Mirai 发表于 2025-7-10 14:34
你听过 Happy Havoc 的 红魔乡逆向工程仓库 吗
帮你问了一些大佬,zero318 说“你可能需要迁就一些剩下的...
非常感谢 不过有点晚了 我的那个项目已经2次重构 现在和原作ecl没关系 更像86汇编和一些渲染 弹幕utility的结合体
页:
[1]