Add arrow/button images
[fim-ctu.git] / ctu.c
blobb7e9e48e89233f23a5fbf5c401e071a4a29781b6
1 #include "ctu.h"
3 #include <windows.h>
4 #include <shlwapi.h>
5 #include <stdio.h>
7 #include "res.h"
8 #include "inject.h"
9 #include "menu.h"
11 HINSTANCE hinst;
13 static HANDLE proc;
14 static HANDLE proc_thread;
15 DWORD proc_thread_id;
17 static int recording;
18 static int input_swapped;
19 static int input_replay;
20 static int p2_input;
21 static int pausemenu_active = 0;
22 static int p2_block;
24 static int16_t p1_magic_max;
25 static int16_t p2_magic_max;
27 static int infodlg_on;
29 /* recording history stuff */
30 struct input {
31 int count;
32 int val;
35 static struct input_history {
36 struct input *ih_vals;
37 struct input *ih_cur;
38 int ih_pos;
39 int ih_curcount;
40 int ih_size;
41 int ih_alloc;
42 } hist;
44 static void
45 recording_alloc(void)
47 hist.ih_vals = realloc(hist.ih_vals,
48 sizeof(hist.ih_vals[0]) * hist.ih_alloc);
51 static void
52 recording_start(void)
54 void *vals_save = hist.ih_vals;
55 memset(&hist, 0, sizeof(hist));
56 hist.ih_alloc = 1024;
57 hist.ih_vals = vals_save;
58 recording_alloc();
59 hist.ih_cur = NULL;
62 static void
63 recording_rec(int val)
65 if (!hist.ih_cur && val == 0) {
66 /* don't actually start recording until we get a real input */
67 return;
70 if (!hist.ih_cur || hist.ih_cur->val != val) {
71 if (hist.ih_size >= hist.ih_alloc) {
72 hist.ih_alloc += 1024;
73 recording_alloc();
75 hist.ih_cur = &hist.ih_vals[hist.ih_size++];
76 hist.ih_cur->val = val;
77 hist.ih_cur->count = 1;
78 } else {
79 hist.ih_cur->count++;
83 static void
84 recording_rewind(void)
86 if (hist.ih_cur && hist.ih_cur->val == 0) {
87 /* if we ended with a neutral input, trim it from the end */
88 hist.ih_size--;
91 if (hist.ih_size) {
92 hist.ih_cur = &hist.ih_vals[0];
93 } else {
94 hist.ih_cur = NULL;
96 hist.ih_pos = 0;
99 static int
100 recording_play(void)
102 if (!hist.ih_cur) {
103 return 0;
105 if (hist.ih_curcount >= hist.ih_cur->count) {
106 hist.ih_pos++;
107 hist.ih_curcount = 0;
108 if (hist.ih_pos >= hist.ih_size) {
109 /* reset back to the beginning, but give ourselves 1 frame of neutral */
110 hist.ih_pos = 0;
111 hist.ih_cur = &hist.ih_vals[0];
112 return 0;
114 hist.ih_cur = &hist.ih_vals[hist.ih_pos];
116 hist.ih_curcount++;
117 return hist.ih_cur->val;
120 static int
121 translate(int val)
123 if ((val & (CTRL_BACK | CTRL_FWD))) {
124 val ^= (CTRL_BACK | CTRL_FWD);
126 return val;
129 static const char *ctrl_state = "CONTROLLING P1";
130 static void
131 switch_state(const char *str)
133 ctud("\n%s", str);
134 fflush(stdout);
135 ctrl_state = str;
138 static void
139 output_ctrl(int val)
141 static int last_val;
142 static int ignore_ud = CTRL_UP;
143 static int ignore_bf = CTRL_FWD;
145 if (!infodlg_on) {
146 return;
149 switch (val & (CTRL_BACK | CTRL_FWD)) {
150 case CTRL_BACK: ignore_bf = CTRL_FWD; break;
151 case CTRL_FWD: ignore_bf = CTRL_BACK; break;
153 switch (val & (CTRL_UP | CTRL_DOWN)) {
154 case CTRL_UP: ignore_ud = CTRL_DOWN; break;
155 case CTRL_DOWN: ignore_ud = CTRL_UP; break;
158 if ((val & (CTRL_BACK | CTRL_FWD)) == (CTRL_BACK | CTRL_FWD)) {
159 val ^= ignore_bf;
161 if ((val & (CTRL_UP | CTRL_DOWN)) == (CTRL_UP | CTRL_DOWN)) {
162 val ^= ignore_ud;
165 if (val == last_val || !val) {
166 return;
168 last_val = val;
169 if (!PostThreadMessage(mainthread, CTU_INPUT, 0, (LPARAM)val)) {
170 ctud("PostThreadMessage (CTU_INPUT) %d\n", (int)GetLastError());
174 DWORD mainthread;
175 static unsigned char p1_reverse;
177 struct comboinfo {
178 void *addr_health;
179 void *addr_meter;
180 void *addr_meter_partial;
181 void *addr_meter_partial_max;
182 void *addr_magic;
184 int start_health;
185 unsigned char start_meter[2];
186 int16_t start_magic;
188 int combo_health;
189 float combo_meter;
190 int combo_magic;
193 static struct comboinfo comboinfo_p1 = {
194 .addr_health = P2_HP_VADDR,
195 .addr_meter = P1_SUPER_BARS_VADDR,
196 .addr_meter_partial = P1_SUPER_PARTIAL_VADDR,
197 .addr_meter_partial_max = P1_SUPER_PARTIAL_MAX_VADDR,
198 .addr_magic = P1_MAGIC_VADDR,
200 static struct comboinfo comboinfo_p2 = {
201 .addr_health = P1_HP_VADDR,
202 .addr_meter = P2_SUPER_BARS_VADDR,
203 .addr_meter_partial = P2_SUPER_PARTIAL_VADDR,
204 .addr_meter_partial_max = P2_SUPER_PARTIAL_MAX_VADDR,
205 .addr_magic = P2_MAGIC_VADDR,
208 static void
209 combo_update(struct comboinfo *cinfo)
211 int cur_health;
212 unsigned char cur_meter[2];
213 int16_t cur_magic, start_magic;
214 unsigned char max_partial_meter;
216 ReadProcessMemory(proc, cinfo->addr_health, &cur_health,
217 sizeof(cur_health), NULL);
218 if (cur_health >= cinfo->start_health) {
219 cinfo->combo_health = 0;
220 return;
222 ReadProcessMemory(proc, cinfo->addr_meter, &cur_meter[0],
223 sizeof(cur_meter[0]), NULL);
224 ReadProcessMemory(proc, cinfo->addr_meter_partial, &cur_meter[1],
225 sizeof(cur_meter[1]), NULL);
226 ReadProcessMemory(proc, cinfo->addr_magic, &cur_magic,
227 sizeof(cur_magic), NULL);
228 ReadProcessMemory(proc, cinfo->addr_meter_partial_max, &max_partial_meter,
229 sizeof(max_partial_meter), NULL);
231 cinfo->combo_health = cinfo->start_health - cur_health;
232 cinfo->combo_meter = (cur_meter[0] +
233 ((float)cur_meter[1])/((float)max_partial_meter)) -
234 (cinfo->start_meter[0] +
235 ((float)cinfo->start_meter[1])/((float)max_partial_meter));
237 start_magic = cinfo->start_magic;
238 if (cur_magic < 0) {
239 cur_magic *= -1;
241 if (start_magic < 0) {
242 start_magic *= -1;
244 cinfo->combo_magic = cur_magic - start_magic;
247 static void
248 combo_reset(struct comboinfo *cinfo)
250 ReadProcessMemory(proc, cinfo->addr_health, &cinfo->start_health,
251 sizeof(cinfo->start_health), NULL);
252 ReadProcessMemory(proc, cinfo->addr_meter, &cinfo->start_meter[0],
253 sizeof(cinfo->start_meter[0]), NULL);
254 ReadProcessMemory(proc, cinfo->addr_meter_partial, &cinfo->start_meter[1],
255 sizeof(cinfo->start_meter[1]), NULL);
256 ReadProcessMemory(proc, cinfo->addr_magic, &cinfo->start_magic,
257 sizeof(cinfo->start_magic), NULL);
260 static void
261 fimwindow(void)
263 /* the FiM window should now exist */
264 if (!PostThreadMessage(mainthread, CTU_SETTITLE, 0, 0)) {
265 ctud("PostThreadMessage (FIMWINDOW) %d\n", (int)GetLastError());
269 static void
270 activate_pausemenu(void)
272 ReadProcessMemory(proc, P1_REVERSE_VADDR, &p1_reverse, sizeof(p1_reverse), NULL);
274 if (!PostThreadMessage(mainthread, CTU_MENUON, 0, 0)) {
275 ctud("PostThreadMessage (MENUON) %d\n", (int)GetLastError());
278 pausemenu_active = 1;
281 static void
282 pause_ctrl(int val)
284 static int last_val;
285 if (last_val == val) {
286 return;
288 last_val = val;
289 if (!val) {
290 return;
292 if (!PostThreadMessage(mainthread, CTU_MENUINPUT, (WPARAM)val, 0)) {
293 ctud("PostThreadMessage (MENUINPUT) %d\n", (int)GetLastError());
297 static int refill_health = 1;
298 static int refill_meter = 0;
299 static int refill_magic = 0;
300 static int deplete_magic = 0;
302 static int
303 dlgval(int index)
305 return dlg[index].val_arr[dlg[index].cur].val;
308 static void
309 deactivate_infodlg(void)
311 if (!infodlg_on) {
312 return;
314 infodlg_on = 0;
315 if (!PostThreadMessage(mainthread, CTU_INFOOFF, 0, 0)) {
316 ctud("PostThreadMessage (INFOOFF) %d\n", (int)GetLastError());
319 static void
320 activate_infodlg(void)
322 if (infodlg_on) {
323 return;
325 infodlg_on = 1;
326 if (!PostThreadMessage(mainthread, CTU_INFOON, 0, 0)) {
327 ctud("PostThreadMessage (INFOON) %d\n", (int)GetLastError());
331 static void
332 deactivate_pausemenu(void)
334 if (!PostThreadMessage(mainthread, CTU_MENUOFF, 0, 0)) {
335 ctud("PostThreadMessage (MENUOFF) %d\n", (int)GetLastError());
337 pause_ctrl(0);
338 pausemenu_active = 0;
340 input_swapped = input_replay = recording = p2_block = 0;
341 recording_rewind();
343 switch (dlgval(CTU_IDX_ACTION)) {
344 case CTU_DLGITEM_Control:
345 ctud("**INPUT SWAPPED\n");
346 input_swapped = 1;
347 break;
348 case CTU_DLGITEM_Record:
349 ctud("**RECORDING P2\n");
350 input_swapped = 1;
351 recording = 1;
352 recording_start();
353 break;
354 case CTU_DLGITEM_Replay:
355 ctud("**REPLAYING RECORDING\n");
356 input_replay = 1;
357 break;
358 case CTU_DLGITEM_Recover:
359 p2_block = 1;
360 default:
361 ctud("**CONTROLLING P1\n");
364 refill_health = 0;
366 switch (dlgval(CTU_IDX_HEALTH)) {
367 case CTU_DLGITEM_Refill:
368 refill_health = 1;
369 break;
372 refill_meter = 0;
374 switch (dlgval(CTU_IDX_METER)) {
375 case CTU_DLGITEM_0_Bars:
376 refill_meter = 1;
377 break;
378 case CTU_DLGITEM_1_Bar:
379 refill_meter = 2;
380 break;
381 case CTU_DLGITEM_2_Bars:
382 refill_meter = 3;
383 break;
384 case CTU_DLGITEM_3_Bars:
385 refill_meter = 4;
386 break;
389 refill_magic = deplete_magic = 0;
391 switch (dlgval(CTU_IDX_MAGIC)) {
392 case CTU_DLGITEM_Refill:
393 refill_magic = 1;
394 break;
395 case CTU_DLGITEM_Deplete:
396 deplete_magic = 1;
397 break;
400 switch (dlgval(CTU_IDX_INFO)) {
401 case CTU_DLGITEM_Off:
402 deactivate_infodlg();
403 break;
405 default:
406 activate_infodlg();
410 static int round_no = -1;
412 static int
413 isfighting(void)
415 return round_no >= 0;
418 static int
419 should_refill_health(int state)
421 switch (state & STATE_STUN_MASK) {
422 case 0:
423 return 1;
425 return 0;
428 static int
429 is_attacking(int state)
431 switch (state & STATE_STUN_MASK) {
432 /* note that this is the same as STATE_STUN_GBOUNCE, but for our usages,
433 * it shouldn't matter if we mistake one for the other */
434 case STATE_STUN_ATTACK:
435 return 1;
437 return 0;
440 static void
441 handle_health(void)
443 static int p1_last_state, p2_last_state;
444 int p1_state, p2_state;
445 int p1_refill_ok = 0, p2_refill_ok = 0;
447 if (!refill_health && !refill_meter && !refill_magic && !infodlg_on) {
448 return;
451 ReadProcessMemory(proc, P1_STATE_VADDR, &p1_state, 4, NULL);
452 ReadProcessMemory(proc, P2_STATE_VADDR, &p2_state, 4, NULL);
453 if (p1_state != p1_last_state) {
454 ctud("p1 state: 0x%x\n", (unsigned)p1_state);
456 if (p2_state != p2_last_state) {
457 ctud(" p2 state: 0x%x\n", (unsigned)p2_state);
459 p2_last_state = p2_state;
460 p1_last_state = p1_state;
463 /* only refill meter/magic if we haven't attacked recently, so we don't
464 * refill instantly when someone starts a combo with meter or magic */
465 static const int cooldown_timer = 20;
466 static int p1_refill_cooldown, p2_refill_cooldown;
468 if (is_attacking(p1_state)) {
469 p1_refill_cooldown = cooldown_timer;
470 } else {
471 if (p1_refill_cooldown < 1) {
472 p1_refill_ok = 1;
473 } else {
474 p1_refill_cooldown--;
478 if (is_attacking(p2_state)) {
479 p2_refill_cooldown = cooldown_timer;
480 } else {
481 if (p2_refill_cooldown < 1) {
482 p2_refill_ok = 1;
483 } else {
484 p2_refill_cooldown--;
489 if (infodlg_on) {
490 combo_update(&comboinfo_p1);
491 combo_update(&comboinfo_p2);
494 if (should_refill_health(p1_state)) {
495 if (refill_health) {
496 int max;
497 int cur;
498 ReadProcessMemory(proc, P1_MAX_HP_VADDR, &max, sizeof(max), NULL);
499 ReadProcessMemory(proc, P1_HP_VADDR, &cur, sizeof(max), NULL);
500 if (cur != max) {
501 ctud("p2 combo damage: %d/%d (%.1f%%)\n", (max - cur), max,
502 (100.0*(float)(max-cur))/((float)max));
503 WriteProcessMemory(proc, P1_HP_VADDR, &max, sizeof(max), NULL);
506 /* remember, fill the _opponent_ super/magic meter when we are recovered */
507 if (refill_meter && p2_refill_ok) {
508 unsigned char bars;
509 unsigned char part;
510 ReadProcessMemory(proc, P2_SUPER_BARS_VADDR, &bars, sizeof(bars), NULL);
511 ReadProcessMemory(proc, P2_SUPER_PARTIAL_VADDR, &part, sizeof(part), NULL);
512 if (bars != (refill_meter-1) || part != 0) {
513 bars = refill_meter - 1;
514 part = 0;
515 WriteProcessMemory(proc, P2_SUPER_BARS_VADDR, &bars, sizeof(bars), NULL);
516 WriteProcessMemory(proc, P2_SUPER_PARTIAL_VADDR, &part, sizeof(part), NULL);
519 if (refill_magic && p2_refill_ok) {
520 int16_t magic;
521 ReadProcessMemory(proc, P2_MAGIC_VADDR, &magic, sizeof(magic), NULL);
522 if (magic != p2_magic_max) {
523 WriteProcessMemory(proc, P2_MAGIC_VADDR, &p2_magic_max, sizeof(p2_magic_max), NULL);
526 if (deplete_magic && p2_refill_ok) {
527 int16_t magic;
528 ReadProcessMemory(proc, P2_MAGIC_VADDR, &magic, sizeof(magic), NULL);
529 if (magic != 0) {
530 magic = 0;
531 WriteProcessMemory(proc, P2_MAGIC_VADDR, &magic, sizeof(magic), NULL);
534 combo_reset(&comboinfo_p2);
536 if (should_refill_health(p2_state)) {
537 if (refill_health) {
538 int max;
539 int cur;
540 ReadProcessMemory(proc, P2_MAX_HP_VADDR, &max, sizeof(max), NULL);
541 ReadProcessMemory(proc, P2_HP_VADDR, &cur, sizeof(max), NULL);
542 if (cur != max) {
543 ctud("p1 combo damage: %d/%d (%.1f%%)\n", (max - cur), max,
544 (100.0*(float)(max-cur))/((float)max));
545 WriteProcessMemory(proc, P2_HP_VADDR, &max, sizeof(max), NULL);
548 /* remember, fill the _opponent_ super/magic meter when we are recovered */
549 if (refill_meter && p1_refill_ok) {
550 unsigned char bars;
551 unsigned char part;
552 ReadProcessMemory(proc, P1_SUPER_BARS_VADDR, &bars, sizeof(bars), NULL);
553 ReadProcessMemory(proc, P1_SUPER_PARTIAL_VADDR, &part, sizeof(part), NULL);
554 if (bars != (refill_meter-1) || part != 0) {
555 bars = refill_meter - 1;
556 part = 0;
557 WriteProcessMemory(proc, P1_SUPER_BARS_VADDR, &bars, sizeof(bars), NULL);
558 WriteProcessMemory(proc, P1_SUPER_PARTIAL_VADDR, &part, sizeof(part), NULL);
561 if (refill_magic && p1_refill_ok) {
562 int16_t magic;
563 ReadProcessMemory(proc, P1_MAGIC_VADDR, &magic, sizeof(magic), NULL);
564 if (magic != p1_magic_max) {
565 WriteProcessMemory(proc, P1_MAGIC_VADDR, &p1_magic_max, sizeof(p1_magic_max), NULL);
568 if (deplete_magic && p1_refill_ok) {
569 int16_t magic;
570 ReadProcessMemory(proc, P1_MAGIC_VADDR, &magic, sizeof(magic), NULL);
571 if (magic != 0) {
572 magic = 0;
573 WriteProcessMemory(proc, P1_MAGIC_VADDR, &magic, sizeof(magic), NULL);
576 combo_reset(&comboinfo_p1);
580 static int
581 handle_p1_input(int val)
583 static int last_val;
584 static int just_swapped;
585 int mylastval = last_val;
587 last_val = val;
589 if (isfighting()) {
590 handle_health();
591 } else {
592 if (!input_swapped) {
593 static int choosing_char;
594 int color;
595 ReadProcessMemory(proc, P1_CHAR_COLOR_VADDR, &color, sizeof(color), NULL);
596 if (!choosing_char && color < 0) {
597 choosing_char = 1;
599 if (choosing_char && color >= 0) {
600 input_swapped = 1;
601 just_swapped = 1;
602 last_val = mylastval = val;
607 if (isfighting()) {
608 if ((val & CTRL_PAUSE) && !(mylastval & CTRL_PAUSE)) {
609 if (pausemenu_active) {
610 deactivate_pausemenu();
611 } else {
612 activate_pausemenu();
614 if (isfighting()) {
615 ctud("we are fighting; sending pause to fm2k\n");
616 return CTRL_PAUSE;
617 } else {
618 ctud("not fighting; swallowed pause\n");
619 return 0;
623 if ((val & CTRL_PAUSE)) {
624 val &= ~CTRL_PAUSE;
627 if (pausemenu_active) {
628 if (p1_reverse) {
629 val = translate(val);
631 pause_ctrl(val);
632 return 0;
636 if (input_swapped) {
637 if (just_swapped && val == mylastval) {
638 val = 0;
639 } else {
640 just_swapped = 0;
642 p2_input = translate(val);
643 output_ctrl(p2_input);
644 return 0;
647 output_ctrl(val);
649 return val;
652 static int
653 block_input(int state, int *atech)
655 int val = 0;
657 *atech = 0;
659 switch ((state & STATE_STUN_MASK)) {
660 case STATE_STUN_HIT:
661 /* we are in hitstun; hold back to try to block */
662 val = CTRL_BACK;
663 *atech = 1;
664 break;
666 case STATE_STUN_BLOCK:
667 /* hitstun; try to block */
668 val = CTRL_BACK;
669 break;
671 case STATE_STUN_GBOUNCE:
672 /* try to ground tech */
673 val = CTRL_BACK | CTRL_DOWN;
674 break;
676 return val;
679 static int
680 handle_p2_input(int val)
682 if (input_swapped) {
683 val = p2_input;
685 if (recording) {
686 recording_rec(val);
688 } else if (input_replay) {
689 val = recording_play();
691 } else if (p2_block) {
692 static int p2_last_state, block_cooldown, block_cooldown_val;
693 int p2_state;
694 int tech;
695 unsigned int p2_height = HEIGHT_GROUNDED;
697 ReadProcessMemory(proc, P2_STATE_VADDR, &p2_state, 4, NULL);
699 val = block_input(p2_state, &tech);
700 if (tech) {
701 /* if we are in the air, hold 'up' to air tech if we can */
702 ReadProcessMemory(proc, P2_HEIGHT_VADDR, &p2_height, sizeof(p2_height), NULL);
703 if (p2_height != HEIGHT_GROUNDED) {
704 val |= CTRL_UP;
708 if (!val) {
709 if (block_cooldown) {
710 block_cooldown--;
711 if (!block_cooldown) {
712 ctud(" == cooldown ending\n");
714 val = block_cooldown_val;
716 } else {
717 val = block_input(p2_last_state, &tech);
718 if (val) {
719 block_cooldown = 30;
720 block_cooldown_val = val;
725 p2_last_state = p2_state;
727 return val;
730 static void
731 round_end(void)
733 ctud("\nround_end\n");
734 switch_state("CONTROLLING P1");
735 recording_start();
736 recording_rewind();
737 recording = input_swapped = 0;
740 static int
741 select_stage(void)
743 #if 0
744 SYSTEMTIME st;
745 FILETIME ft;
747 GetSystemTime(&st);
748 SystemTimeToFileTime(&st, &ft);
750 srand(ft.dwLowDateTime);
752 return rand() % 4;
753 #else
754 /* just always return rarity's stage for now, to ensure we minimize
755 * performance issues */
756 return 0;
757 #endif
760 static void
761 mainthread_update_input(void)
763 if (!PostThreadMessage(mainthread, CTU_THREADUPDATE, 0, 0)) {
764 ctud("PostThreadMessage (threadupdate) %d\n", (int)GetLastError());
768 static int stats_dirty;
770 static void
771 stat_health(void *haddr, void *maxhaddr, struct info_val *val)
773 int health;
774 ReadProcessMemory(proc, haddr, &health, sizeof(health), NULL);
775 if (val->val[0] != health) {
776 val->val[0] = health;
777 val->dirty = 1;
778 stats_dirty = 1;
781 if (!val->val[1]) {
782 ReadProcessMemory(proc, maxhaddr, &val->val[1], sizeof(val->val[1]), NULL);
783 val->dirty = 1;
784 stats_dirty = 1;
788 static void
789 stat_meter(void *meter_addr, void *partial_addr, void *maxp_addr,
790 struct info_val *val)
792 unsigned char bars;
793 unsigned char partial;
794 unsigned char max;
795 float fval;
797 ReadProcessMemory(proc, meter_addr, &bars, sizeof(bars), NULL);
798 ReadProcessMemory(proc, partial_addr, &partial, sizeof(partial), NULL);
799 if (!val->val[0]) {
800 ReadProcessMemory(proc, maxp_addr, &max, sizeof(max), NULL);
801 val->val[0] = max;
802 } else {
803 max = val->val[0];
806 fval = bars + ((float)partial/(float)val->val[0]);
808 if (fval != val->fval) {
809 val->fval = fval;
810 val->dirty = 1;
811 stats_dirty = 1;
815 static void
816 stat_magic(void *magic_addr, int max, struct info_val *val)
818 int16_t magic;
820 ReadProcessMemory(proc, magic_addr, &magic, sizeof(magic), NULL);
822 if (max == 6) {
823 } else {
824 max = 3;
825 magic *= -1;
828 if (magic != val->val[0]) {
829 val->val[0] = magic;
830 val->dirty = 1;
831 stats_dirty = 1;
833 if (max != val->val[1]) {
834 val->val[1] = max;
835 val->dirty = 1;
836 stats_dirty = 1;
840 static void
841 stat_combo(struct comboinfo *cinfo)
843 if (info_curc_dmg.val[0] != cinfo->combo_health) {
844 info_curc_dmg.val[0] = cinfo->combo_health;
845 info_curc_dmg.dirty = 1;
846 stats_dirty = 1;
849 if (info_curc_meter.fval != cinfo->combo_meter) {
850 info_curc_meter.fval = cinfo->combo_meter;
851 info_curc_meter.dirty = 1;
852 stats_dirty = 1;
855 if (info_curc_magic.val[0] != cinfo->combo_magic) {
856 info_curc_magic.val[0] = cinfo->combo_magic;
857 info_curc_magic.dirty = 1;
858 stats_dirty = 1;
861 if (info_curc_dmg.val[0] > info_maxc_dmg.val[0]) {
862 info_maxc_dmg.val[0] = info_curc_dmg.val[0];
863 info_maxc_dmg.dirty = 1;
864 stats_dirty = 1;
866 if (info_maxc_meter.fval != info_curc_meter.fval) {
867 info_maxc_meter.fval = info_curc_meter.fval;
868 info_maxc_meter.dirty = 1;
870 if (info_maxc_magic.val[0] != info_curc_magic.val[0]) {
871 info_maxc_magic.val[0] = info_curc_magic.val[0];
872 info_maxc_magic.dirty = 1;
877 static void
878 update_stats(void)
880 if (!infodlg_on) {
881 return;
884 stats_dirty = 0;
886 stat_health(P1_HP_VADDR, P1_MAX_HP_VADDR, &info_p1_health);
887 stat_health(P2_HP_VADDR, P2_MAX_HP_VADDR, &info_p2_health);
889 stat_meter(P1_SUPER_BARS_VADDR, P1_SUPER_PARTIAL_VADDR, P1_SUPER_PARTIAL_MAX_VADDR, &info_p1_meter);
890 stat_meter(P2_SUPER_BARS_VADDR, P2_SUPER_PARTIAL_VADDR, P2_SUPER_PARTIAL_MAX_VADDR, &info_p2_meter);
892 stat_magic(P1_MAGIC_VADDR, p1_magic_max, &info_p1_magic);
893 stat_magic(P2_MAGIC_VADDR, p2_magic_max, &info_p2_magic);
895 if (comboinfo_p1.combo_health > 0 || comboinfo_p2.combo_health > 0) {
896 if (comboinfo_p1.combo_health >= comboinfo_p2.combo_health) {
897 stat_combo(&comboinfo_p1);
898 } else {
899 stat_combo(&comboinfo_p2);
903 if (stats_dirty) {
904 if (!PostThreadMessage(mainthread, CTU_INFOUPDATE, 0, 0)) {
905 ctud("PostThreadMessage (INFOUPDATE) error %d\n", (int)GetLastError());
910 static int16_t
911 character_max_magic(int charid)
913 switch (charid) {
914 case FIM_CHAR_TWILIGHT:
915 case FIM_CHAR_APPLEJACK:
916 return -3;
918 case FIM_CHAR_RARITY:
919 case FIM_CHAR_PINKIE:
920 return 6;
922 default:
923 ctud("warning got weird character id 0x%x\n", (unsigned)charid);
924 return 0;
928 static int bg_volume;
929 static int se_volume;
931 static unsigned int
932 handle_exc(DEBUG_EVENT *de)
934 if (de->u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
935 ctud("Access Violation %08x at %08x: %s@%08x\n",
936 (unsigned)de->u.Exception.ExceptionRecord.ExceptionCode,
937 (unsigned)de->u.Exception.ExceptionRecord.ExceptionAddress,
938 de->u.Exception.ExceptionRecord.ExceptionInformation[0] ? "read" : "write",
939 (unsigned)de->u.Exception.ExceptionRecord.ExceptionInformation[1]);
940 return DBG_EXCEPTION_NOT_HANDLED;
943 if (de->u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) {
944 unsigned long address;
945 CONTEXT c;
947 if (de->dwThreadId != proc_thread_id) {
948 ctud("bad thread id: %d != %d\n", (int)de->dwThreadId, (int)proc_thread_id);
949 proc_thread_id = de->dwThreadId;
950 proc_thread = OpenThread(THREAD_ALL_ACCESS, FALSE, proc_thread_id);
951 mainthread_update_input();
952 if (!proc_thread) {
953 ctud("OpenThread returned error %d\n", (int)GetLastError());
955 return DBG_CONTINUE;
958 address = (unsigned long)de->u.Exception.ExceptionRecord.ExceptionAddress;
960 switch (address) {
961 case STAGE_SELECT_BREAK:
962 c.ContextFlags = CONTEXT_INTEGER;
963 GetThreadContext(proc_thread, &c);
964 c.Eax = select_stage();
965 SetThreadContext(proc_thread, &c);
966 break;
968 case ROUND_START_BREAK:
969 ctud("new round\n");
970 round_no++;
971 input_swapped = 0;
972 p2_block = 1;
974 unsigned int charid;
975 ReadProcessMemory(proc, P1_CHAR_VADDR, &charid, sizeof(charid), NULL);
976 p1_magic_max = character_max_magic(charid);
978 ReadProcessMemory(proc, P2_CHAR_VADDR, &charid, sizeof(charid), NULL);
979 p2_magic_max = character_max_magic(charid);
981 break;
983 case ROUND_END_BREAK: {
984 DWORD mem;
986 ReadProcessMemory(proc, ROUND_ESI_VADDR, &mem, 4, NULL);
988 c.ContextFlags = CONTEXT_INTEGER;
989 GetThreadContext(proc_thread, &c);
991 c.Esi = mem;
993 if (c.Eax == c.Edx) {
994 round_no = -1;
995 } else if (c.Eax == c.Esi) {
996 round_no = -1;
999 SetThreadContext(proc_thread, &c);
1001 round_end();
1002 break;
1005 case VS_P1_KEY_BREAK:
1006 c.ContextFlags = CONTEXT_INTEGER;
1007 GetThreadContext(proc_thread, &c);
1009 c.Eax = handle_p1_input(c.Eax);
1011 WriteProcessMemory(proc, P1_INPUT_VADDR, &c.Eax, 4, NULL);
1012 if (0) {
1013 static int last;
1014 if (last != c.Eax) {
1015 /*ctud(" === p1 input %x\n", (unsigned)c.Eax);*/
1017 last = c.Eax;
1019 SetThreadContext(proc_thread, &c);
1020 break;
1022 case VS_P2_KEY_BREAK:
1023 c.ContextFlags = CONTEXT_INTEGER;
1024 GetThreadContext(proc_thread, &c);
1026 c.Eax = handle_p2_input(c.Eax);
1028 WriteProcessMemory(proc, P2_INPUT_VADDR, &c.Eax, 4, NULL);
1029 if (0) {
1030 static int last;
1031 if (last != c.Eax) {
1032 /*ctud(" === p2 input %x\n", (unsigned)c.Eax);*/
1034 last = c.Eax;
1036 SetThreadContext(proc_thread, &c);
1037 break;
1039 case SE_VOLUME_BREAK:
1040 c.ContextFlags = CONTEXT_INTEGER;
1041 GetThreadContext(proc_thread, &c);
1042 c.Edx = se_volume;
1043 SetThreadContext(proc_thread, &c);
1044 break;
1045 case BGM_VOLUME_BREAK:
1046 c.ContextFlags = CONTEXT_INTEGER;
1047 GetThreadContext(proc_thread, &c);
1048 c.Edx = bg_volume;
1049 SetThreadContext(proc_thread, &c);
1050 break;
1052 case FRAME_BREAK: {
1053 static int frame_counter;
1055 c.ContextFlags = CONTEXT_INTEGER;
1056 GetThreadContext(proc_thread, &c);
1057 c.Esi = c.Eax;
1058 SetThreadContext(proc_thread, &c);
1060 if (frame_counter == 10) {
1061 fimwindow();
1062 activate_infodlg();
1064 if (frame_counter > 10) {
1065 if (round_no >= 0) {
1066 update_stats();
1068 frame_counter = 0;
1070 frame_counter++;
1071 break;
1074 default:
1075 ctud("unhandled breakpoint: 0x%lx\n", address);
1076 #if 0
1078 unsigned char nop = 0x90;
1079 CONTEXT c;
1081 c.ContextFlags = CONTEXT_FULL;
1082 GetThreadContext(proc_thread, &c);
1083 c.Eip--;
1084 WriteProcessMemory(proc, (void *)c.Eip, &nop, 1, NULL);
1085 FlushInstructionCache(proc, NULL, 0);
1087 #endif
1090 return DBG_CONTINUE;
1093 static void
1094 inject_hooks(void)
1096 /* frame draw callback */
1097 WriteProcessMemory(proc, FRAME_CADDR, FRAME_CODE, sizeof(FRAME_CODE), NULL);
1099 /* set stage */
1100 WriteProcessMemory(proc, STAGE_SELECT_CADDR, STAGE_SELECT_CODE, sizeof(STAGE_SELECT_CODE), NULL);
1102 /* record and alter player inputs */
1103 WriteProcessMemory(proc, VS_P1_KEY_CADDR, VS_P1_KEY_CODE, sizeof(VS_P1_KEY_CODE), NULL);
1104 WriteProcessMemory(proc, VS_P2_KEY_CADDR, VS_P2_KEY_CODE, sizeof(VS_P2_KEY_CODE), NULL);
1106 /* notice when a round starts */
1107 WriteProcessMemory(proc, ROUND_START_CADDR, ROUND_START_CODE,
1108 sizeof(ROUND_START_CODE), NULL);
1109 /* notice when a new round starts */
1110 WriteProcessMemory(proc, ROUND_END_CADDR, ROUND_END_CODE,
1111 sizeof(ROUND_END_CODE), NULL);
1113 if (bg_volume || se_volume) {
1114 WriteProcessMemory(proc, VOLUME_SET_1_CADDR, VOLUME_SET_1_CODE, sizeof(VOLUME_SET_1_CODE), NULL);
1115 WriteProcessMemory(proc, VOLUME_SET_2_CADDR, VOLUME_SET_2_CODE, sizeof(VOLUME_SET_2_CODE), NULL);
1119 static DWORD WINAPI
1120 runproc(LPVOID lpParam)
1122 char path[] = "FightingIsMagic.exe";
1123 /*char path[] = "K:\\games\\mlp\\FightingIsMagic\\FightingIsMagic.exe";*/
1124 STARTUPINFO si;
1125 PROCESS_INFORMATION pi;
1126 DEBUG_EVENT de;
1128 ZeroMemory(&si, sizeof(si));
1129 si.cb = sizeof(si);
1130 ZeroMemory(&pi, sizeof(pi));
1132 if (!CreateProcess(NULL, path /* command line */,
1133 NULL /* process security attrs */,
1134 NULL /* thread security attrs */,
1135 FALSE /* child inherits handles? */,
1136 DEBUG_PROCESS /* flags */,
1137 NULL /* environment */,
1138 NULL /* cwd */,
1139 &si, &pi)) {
1140 ctud("Could not CreateProcess, error %d\n", (int)GetLastError());
1141 exit(1);
1144 proc = OpenProcess(PROCESS_ALL_ACCESS, FALSE /* inherit handles? */,
1145 pi.dwProcessId);
1146 if (!proc) {
1147 ctud("Could not OpenProcess, error %d\n", (int)GetLastError());
1148 exit(1);
1151 while (WaitForDebugEvent(&de, INFINITE)) {
1152 unsigned int state = DBG_EXCEPTION_NOT_HANDLED;
1153 switch (de.dwDebugEventCode) {
1154 case EXCEPTION_DEBUG_EVENT:
1155 state = handle_exc(&de);
1157 case CREATE_THREAD_DEBUG_EVENT:
1158 break;
1160 case CREATE_PROCESS_DEBUG_EVENT:
1161 proc_thread = de.u.CreateProcessInfo.hThread;
1162 proc_thread_id = de.dwThreadId;
1163 mainthread_update_input();
1164 inject_hooks();
1165 break;
1167 case EXIT_THREAD_DEBUG_EVENT:
1168 break;
1170 case EXIT_PROCESS_DEBUG_EVENT:
1171 goto done;
1173 case LOAD_DLL_DEBUG_EVENT:
1174 break;
1176 case UNLOAD_DLL_DEBUG_EVENT:
1177 break;
1179 case OUTPUT_DEBUG_STRING_EVENT: {
1180 char *str = malloc(de.u.DebugString.nDebugStringLength);
1181 ReadProcessMemory(proc, de.u.DebugString.lpDebugStringData, str,
1182 de.u.DebugString.nDebugStringLength, NULL);
1183 ctud(" = OUTPUT_DEBUG_STRING_EVENT(%s)\n", str);
1184 free(str);
1185 break;
1188 case RIP_EVENT:
1189 ctud(" = RIP_EVENT\n");
1190 break;
1192 default:
1193 ctud(" = unhandled de.dwDebugEventCode %d\n", (int)de.dwDebugEventCode);
1196 ContinueDebugEvent(de.dwProcessId, de.dwThreadId, state);
1199 done:
1200 PostThreadMessage(mainthread, WM_QUIT, 0, 0);
1202 return 0;
1205 int WINAPI
1206 WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
1207 LPSTR lpCmdLine, int nCmdShow)
1209 LPWSTR *argv;
1210 int argc, i;
1211 argv = CommandLineToArgvW(GetCommandLineW(), &argc);
1212 for (i = 1; i < argc; i++) {
1213 if (StrCmpW(argv[i], L"-stdout") == 0) {
1214 i++;
1215 if (i >= argc) {
1216 ctud("-stdout takes an argument\n");
1217 return 1;
1219 if (!_wfreopen(argv[i], L"w", stdout)) {
1220 return 2;
1222 continue;
1224 if (StrCmpW(argv[i], L"-stderr") == 0) {
1225 i++;
1226 if (i >= argc) {
1227 ctud("-stderr takes an argument\n");
1228 return 1;
1230 if (!_wfreopen(argv[i], L"w", stderr)) {
1231 return 2;
1233 continue;
1235 if (StrCmpW(argv[i], L"-bgvol") == 0) {
1236 i++;
1237 if (i >= argc) {
1238 ctud("-bgvol takes an argument\n");
1239 return 1;
1241 if (!StrToIntExW(argv[i], 0, &bg_volume)) {
1242 ctud("Invalid -bgvol integer specified (error %d)\n", (int)GetLastError());
1243 return 1;
1245 continue;
1247 if (StrCmpW(argv[i], L"-sevol") == 0) {
1248 i++;
1249 if (i >= argc) {
1250 ctud("-bgvol takes an argument\n");
1251 return 1;
1253 if (!StrToIntExW(argv[i], 0, &se_volume)) {
1254 ctud("Invalid -sevol integer specified (error %d)\n", (int)GetLastError());
1255 return 1;
1257 continue;
1259 if (StrCmpW(argv[i], L"-vol") == 0) {
1260 i++;
1261 if (i >= argc) {
1262 ctud("-vol takes an argument\n");
1263 return 1;
1265 if (!StrToIntExW(argv[i], 0, &bg_volume)) {
1266 ctud("Invalid -vol integer specified (error %d)\n", (int)GetLastError());
1267 return 1;
1269 se_volume = bg_volume;
1270 continue;
1272 if (StrCmpW(argv[i], L"-mutebg") == 0) {
1273 bg_volume = -10000;
1274 continue;
1276 if (StrCmpW(argv[i], L"-mutese") == 0) {
1277 se_volume = -10000;
1278 continue;
1280 if (StrCmpW(argv[i], L"-mute") == 0) {
1281 bg_volume = se_volume = -10000;
1282 continue;
1284 ctud("Invalid option '%S' given\n", argv[i]);
1285 return 1;
1288 hinst = hinstance;
1289 mainthread = GetCurrentThreadId();
1291 if (!CreateThread(NULL /* attrs*/,
1292 0 /* stack size */,
1293 &runproc,
1294 NULL /* param */,
1295 0 /* flags */,
1296 NULL /* thread id */)) {
1297 ctud("CreateThread error %d\n", (int)GetLastError());
1298 return 1;
1301 return menu_loop();