-
Notifications
You must be signed in to change notification settings - Fork 1
/
farm_auto_reseed.c
257 lines (205 loc) · 8.61 KB
/
farm_auto_reseed.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
#include "hook.h"
#include <stdint.h>
#include <stdio.h>
#include <windows.h>
#ifdef NDEBUG
#define dbg_print(...)
#else
#define dbg_print(...) printf("[aoc-farm-auto-reseed] " __VA_ARGS__)
#endif
static int32_t* allow_command_input = (int32_t*)0x679B10;
// Offset in the player structure that stores an unused byte where only the
// lower bit is filled (always with 1). We can store the autoqueue state in the
// second bit.
static const size_t FARM_AUTOQUEUE_SETTING_OFFSET = 0x1E70;
static const size_t FARM_AUTOQUEUE_MASK = 0x2;
static uint8_t is_autoqueue_enabled(void* player) {
uint8_t bits = *(uint8_t*)((size_t)player + FARM_AUTOQUEUE_SETTING_OFFSET);
return (bits & FARM_AUTOQUEUE_MASK) ? 1 : 0;
}
static void toggle_autoqueue_flag(void* player) {
int32_t player_id = *(int32_t*)((size_t)player + 0x9C);
uint8_t current = *(uint8_t*)((size_t)player + FARM_AUTOQUEUE_SETTING_OFFSET);
if (current & FARM_AUTOQUEUE_MASK)
current &= ~FARM_AUTOQUEUE_MASK;
else
current |= FARM_AUTOQUEUE_MASK;
dbg_print("setting autoqueue for %d to %d\n", player_id, !current);
*(uint8_t*)((size_t)player + FARM_AUTOQUEUE_SETTING_OFFSET) = current;
}
// Some utility functions.
typedef void* __thiscall (*fn_get_player)(void*);
static inline void* get_player() {
fn_get_player aoc_get_player = (fn_get_player)0x5E7560;
return aoc_get_player(*(void**)0x7912A0);
}
// Add one to the farm queue.
typedef char __thiscall (*fn_queue_farms)(void*, int32_t);
static void __thiscall enqueue_farm(size_t player) {
fn_queue_farms queue_farms = (fn_queue_farms)0x45E4F0;
dbg_print("enqueue farm for %d\n", *(int32_t*)(player + 0x9C));
queue_farms((void*)player, 1);
}
// When a farm expired, refill the farm queue if it's empty.
typedef char __thiscall (*fn_auto_rebuild_farm)(void*);
static char __thiscall rebuild_farm_hook(void* ptr) {
fn_auto_rebuild_farm original = (fn_auto_rebuild_farm)0x603D40;
size_t obj = *(size_t*)((size_t)ptr + 0x8);
size_t player = *(size_t*)(obj + 0xC);
int32_t farm_queue_count = *(int32_t*)(player + 0x1EAC);
uint8_t autoqueue_enabled = is_autoqueue_enabled((void*)player);
dbg_print("farm_queue_count = %d, should autoqueue = %d\n", farm_queue_count,
autoqueue_enabled);
if (farm_queue_count <= 0 && autoqueue_enabled) {
enqueue_farm(player);
}
return original(ptr);
}
// Toggle the automatic queueing setting for the current player.
typedef char* __stdcall (*fn_get_string)(int32_t);
typedef void __thiscall (*fn_add_farm)(void*, int32_t, int32_t);
typedef void __stdcall (*fn_display_message)(char*, int32_t);
static void __thiscall toggle_farm_reseed() {
dbg_print("toggle_farm_reseed()\n");
fn_add_farm aoc_add_farm = (fn_add_farm)0x46A6F0;
fn_display_message aoc_display_message = (fn_display_message)0x51CD30;
fn_get_string aoc_get_string = (fn_get_string)0x562CE0;
void* world = *(void**)(*(size_t*)0x7912A0 + 0x424);
void* player = get_player();
// RIP if this happens...
if (player == NULL)
return;
char message[260];
// Reseed Farm: On|Off
// Done kinda clumsily because aoc_get_string reuses the buffer,
// and inverted because we haven't flipped the flag yet.
sprintf(message, "%s: ", aoc_get_string(4146));
strcat(message, aoc_get_string(is_autoqueue_enabled(player) ? 10752 : 10751));
dbg_print("%s\n", message);
aoc_display_message(message, 1); // 1 = silent
int32_t player_id = *(int32_t*)((size_t)player + 0x9C);
void* commander = *(void**)((size_t)world + 0x68);
// Add 0 farms to indicate toggling automatic reseed queueing,
// this is meaningless to plain AoC.
aoc_add_farm(commander, player_id, 0);
}
static uint8_t is_mill(void* obj) {
void* obj_type = *(void**)((size_t)obj + 0x8);
if (obj_type == NULL) {
dbg_print("obj_type == NULL\n");
return 0;
}
if (*(int16_t*)((size_t)obj_type + 0x10) != 68) {
dbg_print("obj_type != 68\n");
return 0;
}
return 1;
}
static void refresh_queue_button() {
dbg_print("refresh_queue_button()\n");
void* screen = *(void**)(*(size_t*)0x7912A0 + 0x1820);
void* player = get_player();
void* obj = *(void**)((size_t)screen + 0x1230);
if (obj == NULL) {
dbg_print("obj == NULL\n");
return;
}
if (*(void**)((size_t)obj + 0xC) != player) {
dbg_print("obj != player\n");
return;
}
if (!is_mill(obj)) {
return;
}
if (*(uint8_t*)((size_t)obj + 0x48) != 2) {
dbg_print("obj flag != 2\n");
return;
}
// Selected object type is Mill (68).
void __thiscall (*aoc_redraw_buttons)(void*) =
(void __thiscall (*)(void*))0x527AF0;
aoc_redraw_buttons(screen);
}
// Handle a player changing their automatic queueing setting.
static int32_t __thiscall add_to_player_queue(void* player, int32_t count) {
dbg_print("add_to_player_queue()\n");
fn_queue_farms queue_farms = (fn_queue_farms)0x45E4F0;
if (count == 0) {
toggle_autoqueue_flag(player);
if (player == get_player()) {
refresh_queue_button();
}
#ifndef NDEBUG
fn_display_message aoc_display_message = (fn_display_message)0x51CD30;
fn_get_string aoc_get_string = (fn_get_string)0x562CE0;
char message[260];
// Reseed Farm: On|Off
sprintf(message, "<%s> DEBUG %s: ", *(char**)((size_t)player + 0x98),
aoc_get_string(4146));
strcat(message,
aoc_get_string(is_autoqueue_enabled(player) ? 10751 : 10752));
aoc_display_message(message, 1); // 1 = silent
#endif
return 1; // Success!
}
return queue_farms(player, count);
}
// Allow right-clicking the farm reseed button.
typedef void __thiscall (*fn_configure_button)(void*, void*, int16_t, int16_t,
int32_t, int32_t, int32_t,
int32_t, int32_t, char*, char*,
char*, int32_t);
typedef void __thiscall (*fn_set_number_display_type)(void*, int32_t);
typedef void __thiscall (*fn_set_number_display_value)(void*, int32_t, int32_t);
static void __thiscall configure_button(void* screen, void* a,
int16_t button_id, int16_t c, int32_t d,
int32_t e, int32_t f, int32_t g,
int32_t h, char* i, char* j, char* k,
int32_t l) {
fn_configure_button original = (fn_configure_button)0x520620;
original(screen, a, button_id, c, d, e, f, g, h, i, j, k, l);
dbg_print("configure_button(%d)\n", d);
if (d != 173)
return;
fn_set_number_display_type aoc_set_number_display_type =
(fn_set_number_display_type)0x453B70;
fn_set_number_display_value aoc_set_number_display_value =
(fn_set_number_display_value)0x453B90;
void* button = *(void**)((size_t)screen + 0x1080 + 4 * button_id);
// This is reset by the game before redrawing the buttons list so
// we don't have to worry about lingering state.
*(int32_t*)((size_t)button + 0x2D0) = d == 173;
aoc_set_number_display_type(button, 2); // only display if nonzero
aoc_set_number_display_value(button, is_autoqueue_enabled(get_player()), 0);
}
// Handle right click action on the farm reseed button.
typedef void __thiscall (*fn_do_button_action)(void*, int32_t, int32_t,
int32_t);
static fn_do_button_action aoc_do_button_action = NULL;
static void __thiscall do_button_action(void* screen, int32_t action_in,
int32_t action2_in, int32_t right_btn) {
dbg_print("do_button_action(%d, %d, %d)\n", action_in, action2_in, right_btn);
if (action_in == 173 && action2_in == 50 && right_btn == 1) {
dbg_print("allow_command_input = %d\n", *allow_command_input);
if (*allow_command_input) {
toggle_farm_reseed();
}
// The right-click handling code actually _unqueues_ a farm reseed in the
// base game, but you couldn't right-click the button. We can safely
// override it, but it does mean we need to return early here.
return;
}
aoc_do_button_action(screen, action_in, action2_in, right_btn);
}
void aoc_farm_auto_reseed_setup() {
dbg_print("init()\n");
install_callhook((void*)0x467809, (void*)add_to_player_queue);
install_callhook((void*)0x602FD3, (void*)rebuild_farm_hook);
install_callhook((void*)0x60305B, (void*)rebuild_farm_hook);
install_callhook((void*)0x527C59, (void*)configure_button);
aoc_do_button_action = (fn_do_button_action)(0x51E1C9 + *(int32_t*)0x51E1C5);
dbg_print("aoc_do_button_action = %p\n", aoc_do_button_action);
install_callhook((void*)0x51DBC0, (void*)do_button_action);
install_callhook((void*)0x51E1C4, (void*)do_button_action);
install_callhook((void*)0x51E209, (void*)do_button_action);
}