mirror of
https://github.com/sfall-team/sfall.git
synced 2026-07-27 16:52:34 -07:00
558 lines
14 KiB
C++
558 lines
14 KiB
C++
/*
|
|
* sfall
|
|
* Copyright (C) 2008, 2009, 2010 The sfall team
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <vector>
|
|
|
|
#include "..\main.h"
|
|
#include "..\FalloutEngine\Fallout2.h"
|
|
#include "..\InputFuncs.h"
|
|
//#include "Graphics.h"
|
|
#include "LoadGameHook.h"
|
|
#include "ScriptExtender.h"
|
|
#include "Scripting\Arrays.h"
|
|
|
|
#include "DebugEditor.h"
|
|
|
|
namespace sfall
|
|
{
|
|
|
|
enum DECode {
|
|
CODE_SET_GLOBAL = 0,
|
|
CODE_SET_MAPVAR = 1,
|
|
CODE_GET_CRITTER = 2,
|
|
CODE_SET_CRITTER = 3,
|
|
CODE_SET_SGLOBAL = 4,
|
|
CODE_GET_PROTO = 5,
|
|
CODE_SET_PROTO = 6,
|
|
CODE_GET_PLAYER = 7,
|
|
CODE_SET_PLAYER = 8,
|
|
CODE_GET_ARRAY = 9,
|
|
CODE_SET_ARRAY = 10,
|
|
CODE_GET_LOCVARS = 11,
|
|
CODE_SET_LOCVARS = 12,
|
|
CODE_EXIT = 254
|
|
};
|
|
|
|
static const char* debugLog = "LOG";
|
|
static const char* debugGnw = "GNW";
|
|
|
|
static DWORD debugEditorKey;
|
|
static DWORD mapGridToggleKey;
|
|
|
|
struct sArray {
|
|
DWORD id;
|
|
long isMap;
|
|
long size;
|
|
long flag;
|
|
};
|
|
|
|
static bool SetBlocking(SOCKET s, bool block) {
|
|
DWORD d = !block;
|
|
ioctlsocket(s, FIONBIO, &d);
|
|
}
|
|
|
|
static bool InternalSend(SOCKET s, const void* _data, int size) {
|
|
const char* data = (const char*)_data;
|
|
int upto = 0;
|
|
while (upto < size) {
|
|
int tmp = send(s, &data[upto], size - upto, 0);
|
|
if (tmp > 0) {
|
|
upto += tmp;
|
|
} else {
|
|
DWORD d = WSAGetLastError();
|
|
if (d != WSAEWOULDBLOCK && d != WSAENOBUFS) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool InternalRecv(SOCKET s, void* _data, int size) {
|
|
char* data = (char*)_data;
|
|
int upto = 0;
|
|
while (upto < size) {
|
|
int tmp = recv(s, &data[upto], size - upto, 0);
|
|
if (tmp > 0) {
|
|
upto += tmp;
|
|
} else {
|
|
DWORD d = WSAGetLastError();
|
|
if (d != WSAEWOULDBLOCK && d != WSAENOBUFS) return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void RunEditorInternal(SOCKET &s) {
|
|
fo::var::setInt(FO_VAR_script_engine_running) = 0;
|
|
|
|
std::vector<DWORD*> vec = std::vector<DWORD*>();
|
|
for (int elv = 0; elv < 3; elv++) {
|
|
for (int tile = 0; tile < 40000; tile++) {
|
|
fo::GameObject* obj = fo::func::obj_find_first_at_tile(elv, tile);
|
|
while (obj) {
|
|
if (obj->IsCritter()) {
|
|
vec.push_back(reinterpret_cast<DWORD*>(obj));
|
|
}
|
|
obj = fo::func::obj_find_next_at_tile();
|
|
}
|
|
}
|
|
}
|
|
int numCritters = vec.size();
|
|
int numGlobals = *fo::ptr::num_game_global_vars;
|
|
int numMapVars = *fo::ptr::num_map_global_vars;
|
|
int numSGlobals = GetNumGlobals();
|
|
int numArrays = script::GetNumArrays();
|
|
|
|
InternalSend(s, &numGlobals, 4);
|
|
InternalSend(s, &numMapVars, 4);
|
|
InternalSend(s, &numSGlobals, 4);
|
|
InternalSend(s, &numArrays, 4);
|
|
InternalSend(s, &numCritters, 4);
|
|
|
|
GlobalVar* sglobals = new GlobalVar[numSGlobals];
|
|
GetGlobals(sglobals);
|
|
|
|
sArray* arrays = new sArray[numArrays];
|
|
script::GetArrays((int*)arrays);
|
|
|
|
InternalSend(s, reinterpret_cast<void*>(*fo::ptr::game_global_vars), 4 * numGlobals);
|
|
InternalSend(s, reinterpret_cast<void*>(*fo::ptr::map_global_vars), 4 * numMapVars);
|
|
InternalSend(s, sglobals, sizeof(GlobalVar) * numSGlobals);
|
|
InternalSend(s, arrays, numArrays * sizeof(sArray));
|
|
for (int i = 0; i < numCritters; i++) {
|
|
InternalSend(s, &vec[i][25], 4);
|
|
InternalSend(s, &vec[i], 4);
|
|
}
|
|
|
|
while (true) {
|
|
BYTE code;
|
|
InternalRecv(s, &code, 1);
|
|
if (code == CODE_EXIT) break;
|
|
int id, val;
|
|
switch (code) {
|
|
case CODE_SET_GLOBAL:
|
|
InternalRecv(s, &id, 4);
|
|
InternalRecv(s, &val, 4);
|
|
(*fo::ptr::game_global_vars)[id] = val;
|
|
break;
|
|
case CODE_SET_MAPVAR:
|
|
InternalRecv(s, &id, 4);
|
|
InternalRecv(s, &val, 4);
|
|
(*fo::ptr::map_global_vars)[id] = val;
|
|
break;
|
|
case CODE_GET_CRITTER:
|
|
InternalRecv(s, &id, 4);
|
|
InternalSend(s, vec[id], 0x84);
|
|
break;
|
|
case CODE_SET_CRITTER:
|
|
InternalRecv(s, &id, 4);
|
|
InternalRecv(s, vec[id], 0x84);
|
|
break;
|
|
case CODE_SET_SGLOBAL:
|
|
InternalRecv(s, &id, 4);
|
|
InternalRecv(s, &val, 4);
|
|
sglobals[id].val = val;
|
|
break;
|
|
case CODE_GET_ARRAY: // get array values
|
|
{
|
|
InternalRecv(s, &id, 4);
|
|
DWORD *types = new DWORD[arrays[id].size * 2]; // type, len
|
|
script::DEGetArray(arrays[id].id, types, nullptr);
|
|
int dataLen = 0;
|
|
for (long i = 0; i < arrays[id].size; i++) {
|
|
dataLen += types[i * 2 + 1];
|
|
}
|
|
char *data = new char[dataLen];
|
|
script::DEGetArray(arrays[id].id, nullptr, data);
|
|
InternalSend(s, types, arrays[id].size * 8);
|
|
InternalSend(s, data, dataLen);
|
|
delete[] data;
|
|
delete[] types;
|
|
}
|
|
break;
|
|
case CODE_SET_ARRAY: // set array values
|
|
{
|
|
InternalRecv(s, &id, 4);
|
|
InternalRecv(s, &val, 4); // len data
|
|
char *data = new char[val];
|
|
InternalRecv(s, data, val);
|
|
script::DESetArray(arrays[id].id, nullptr, data);
|
|
delete[] data;
|
|
}
|
|
break;
|
|
case CODE_GET_LOCVARS:
|
|
{
|
|
InternalRecv(s, &id, 4); // sid
|
|
val = fo::util::GetScriptLocalVars(id);
|
|
InternalSend(s, &val, 4);
|
|
if (val) {
|
|
std::vector<int> values(val);
|
|
long varVal;
|
|
for (int i = 0; i < val; i++) {
|
|
fo::func::scr_get_local_var(id, i, &varVal);
|
|
values[i] = varVal;
|
|
}
|
|
InternalSend(s, values.data(), val * 4);
|
|
}
|
|
}
|
|
break;
|
|
case CODE_SET_LOCVARS:
|
|
{
|
|
InternalRecv(s, &id, 4); // sid
|
|
InternalRecv(s, &val, 4); // len data
|
|
std::vector<int> values(val);
|
|
InternalRecv(s, values.data(), val * 4);
|
|
for (int i = 0; i < val; i++) {
|
|
fo::func::scr_set_local_var(id, i, values[i]);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
__asm call fo::funcoffs::GNW95_process_message_;
|
|
}
|
|
|
|
SetGlobals(sglobals);
|
|
delete[] sglobals;
|
|
delete[] arrays;
|
|
|
|
FlushInputBuffer();
|
|
fo::var::setInt(FO_VAR_script_engine_running) = 1;
|
|
}
|
|
|
|
void RunDebugEditor() {
|
|
WSADATA wsaData;
|
|
SOCKET sock, client;
|
|
|
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != NO_ERROR) return;
|
|
// create the socket
|
|
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
if (sock == INVALID_SOCKET) {
|
|
WSACleanup();
|
|
return;
|
|
}
|
|
// bind the socket
|
|
sockaddr_in service;
|
|
service.sin_family = AF_INET;
|
|
service.sin_addr.s_addr = inet_addr("127.0.0.1");
|
|
service.sin_port = htons(4245);
|
|
|
|
if (bind(sock, (SOCKADDR*)&service, sizeof(service)) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
WSACleanup();
|
|
return;
|
|
}
|
|
if (listen(sock, 4) == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
WSACleanup();
|
|
return;
|
|
}
|
|
|
|
// Start up the editor
|
|
STARTUPINFO si;
|
|
PROCESS_INFORMATION pi;
|
|
|
|
memset(&si, 0, sizeof(si));
|
|
memset(&pi, 0, sizeof(pi));
|
|
|
|
si.cb = sizeof(si);
|
|
|
|
if (!CreateProcessA("FalloutDebug.exe", "FalloutDebug.exe -debugedit", 0, 0, false, 0, 0, 0, &si, &pi)) {
|
|
closesocket(sock);
|
|
WSACleanup();
|
|
return;
|
|
}
|
|
|
|
CloseHandle(pi.hThread);
|
|
CloseHandle(pi.hProcess);
|
|
|
|
// Connect to the editor
|
|
client = accept(sock, 0, 0);
|
|
if (client == SOCKET_ERROR) {
|
|
closesocket(sock);
|
|
WSACleanup();
|
|
return;
|
|
}
|
|
|
|
MouseDeviceUnacquire(true);
|
|
RunEditorInternal(client);
|
|
|
|
closesocket(client);
|
|
closesocket(sock);
|
|
WSACleanup();
|
|
}
|
|
|
|
static void __declspec(naked) dbg_error_hack() {
|
|
static const DWORD dbg_error_ret = 0x453FD8;
|
|
__asm {
|
|
cmp ebx, 1;
|
|
je hide;
|
|
sub esp, 0x104;
|
|
jmp dbg_error_ret;
|
|
hide:
|
|
pop esi;
|
|
pop ecx;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) art_data_size_hook() {
|
|
static const char* artDbgMsg = "\nERROR: Art file not found: %s\n";
|
|
__asm {
|
|
test edi, edi; // 1 - isExistsArt
|
|
jz artNotExist;
|
|
retn;
|
|
artNotExist:
|
|
mov edx, [esp + 0x124 - 0x1C + 4]; // filename
|
|
push edx;
|
|
push artDbgMsg;
|
|
call fo::funcoffs::debug_printf_;
|
|
cmp isDebug, 0;
|
|
jne display;
|
|
add esp, 8;
|
|
retn;
|
|
display:
|
|
push edx; // filename
|
|
push artDbgMsg;
|
|
lea eax, [esp + 0x124 - 0x124 + 20]; // buf
|
|
push eax;
|
|
call fo::funcoffs::sprintf_;
|
|
add esp, 20;
|
|
lea eax, [esp + 4];
|
|
jmp fo::funcoffs::display_print_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) art_data_size_hook_check() {
|
|
using namespace fo;
|
|
__asm {
|
|
xor esi, esi;
|
|
mov eax, ebx; // ebx - FID
|
|
shr eax, 16; // al - animation code (ID2)
|
|
cmp al, ANIM_walk;
|
|
cmove ecx, esi;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) proto_load_pid_hack() {
|
|
static const char* proDbgMsg = "\nERROR: Reading prototype file: %s\n";
|
|
__asm {
|
|
mov dword ptr [esp + 0x120 - 0x1C + 4], -1;
|
|
lea eax, [esp + 0x120 - 0x120 + 4]; // pro file
|
|
push eax;
|
|
push proDbgMsg;
|
|
call fo::funcoffs::debug_printf_;
|
|
add esp, 8;
|
|
mov eax, 0x500494; // 'iisxxxx1'
|
|
jmp fo::funcoffs::gsound_play_sfx_file_;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) win_debug_hook() {
|
|
__asm {
|
|
call fo::funcoffs::debug_log_;
|
|
xor eax, eax;
|
|
cmp ds:[FO_VAR_GNW_win_init_flag], eax;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) debug_log_hack() {
|
|
__asm {
|
|
push eax; // text
|
|
push 0x5016EC; // fmt '%s'
|
|
push ebx; // log file
|
|
call fo::funcoffs::fprintf_;
|
|
add esp, 12;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) debugMsg() {
|
|
static const char* scrNameFmt = "\nScript: %s ";
|
|
__asm {
|
|
mov edx, ds:[FO_VAR_currentProgram];
|
|
push [edx]; // script name
|
|
push scrNameFmt;
|
|
call fo::funcoffs::debug_printf_;
|
|
add esp, 8;
|
|
jmp fo::funcoffs::debug_printf_; // ERROR: attempt to reference...
|
|
}
|
|
}
|
|
|
|
static void __declspec(naked) combat_load_hack() {
|
|
static const char* msgCombat = "LOADSAVE: Object ID not found while loading the combat data.\n";
|
|
__asm {
|
|
push msgCombat;
|
|
call fo::funcoffs::debug_printf_;
|
|
add esp, 4;
|
|
mov eax, -1;
|
|
retn;
|
|
}
|
|
}
|
|
|
|
// Shifts the string one character to the right and inserts a newline control character at the beginning
|
|
static void MoveDebugString(char* messageAddr) {
|
|
int i = 0;
|
|
do {
|
|
messageAddr[i + 1] = messageAddr[i];
|
|
i--;
|
|
} while (messageAddr[i] != '\0');
|
|
messageAddr[i + 1] = '\n';
|
|
}
|
|
|
|
static const DWORD addrDotChar[] = {
|
|
0x50B244, 0x50B27C, 0x50B2B6, 0x50B2EE // "ERROR: attempt to reference * var out of range: %d"
|
|
};
|
|
|
|
static const DWORD addrNewLineChar[] = {
|
|
0x500A64, // "Friendly was in the way!"
|
|
};
|
|
|
|
static void DebugModePatch() {
|
|
int dbgMode = IniReader::GetIntDefaultConfig("Debugging", "DebugMode", 0);
|
|
if (dbgMode > 0) {
|
|
dlog("Applying debugmode patch.", DL_INIT);
|
|
// If the player is using an exe with the debug patch already applied, just skip this block without erroring
|
|
if (*((DWORD*)0x444A64) != 0x082327E8) {
|
|
SafeWrite32(0x444A64, 0x082327E8); // call debug_register_env_
|
|
SafeWrite32(0x444A68, 0x0120E900); // jmp 0x444B8E
|
|
SafeWrite8(0x444A6D, 0);
|
|
SafeWrite32(0x444A6E, 0x90909090);
|
|
}
|
|
SafeWrite8(0x4C6D9B, 0xB8); // mov eax, GNW/LOG
|
|
if (dbgMode & 2) {
|
|
SafeWrite32(0x4C6D9C, (DWORD)debugLog);
|
|
if (dbgMode & 1) {
|
|
SafeWrite16(0x4C6E75, 0x66EB); // jmps 0x4C6EDD
|
|
SafeWrite8(0x4C6EF2, CodeType::JumpShort);
|
|
SafeWrite8(0x4C7034, 0x0);
|
|
MakeCall(0x4DC319, win_debug_hook, 2);
|
|
}
|
|
} else {
|
|
SafeWrite32(0x4C6D9C, (DWORD)debugGnw);
|
|
}
|
|
|
|
if (IniReader::GetIntDefaultConfig("Debugging", "HideObjIsNullMsg", 0)) {
|
|
MakeJump(0x453FD2, dbg_error_hack);
|
|
}
|
|
|
|
// prints a debug message about a missing critter art file to both debug.log and the message window in sfall debugging mode
|
|
HookCall(0x419B65, art_data_size_hook);
|
|
// checks the animation code, if ANIM_walk then skip printing the debug message
|
|
HookCall(0x419AA0, art_data_size_hook_check);
|
|
SafeWrite8(0x419B61, CodeType::JumpNZ); // jz > jnz
|
|
|
|
// Fix to prevent crashes when there is a '%' character in the printed message
|
|
if (dbgMode > 1) {
|
|
MakeCall(0x4C703F, debug_log_hack);
|
|
BlockCall(0x4C7044); // just nop code
|
|
}
|
|
// replace calling debug_printf_ with _debug_func
|
|
__int64 data = 0x51DF0415FFF08990; // mov eax, esi; call ds:_debug_func
|
|
SafeWriteBytes(0x455419, (BYTE*)&data, 8); // op_display_msg_
|
|
|
|
// set the position of the debug window
|
|
SafeWrite8(0x4DC34D, 15);
|
|
|
|
// Fix the format of some debug messages
|
|
SafeWriteBatch<BYTE>('\n', addrNewLineChar);
|
|
SafeWriteBatch<BYTE>('.', addrDotChar);
|
|
const DWORD mapVarErrorAddr[] = {
|
|
0x482240, // map_set_global_var_
|
|
0x482274, // map_get_global_var_
|
|
0x4822A0, // map_set_local_var_
|
|
0x4822D4 // map_get_local_var_
|
|
};
|
|
HookCalls(debugMsg, mapVarErrorAddr);
|
|
if (dbgMode != 1) {
|
|
MoveDebugString((char*)0x500A9B); // "computing attack..."
|
|
}
|
|
dlogr(" Done", DL_INIT);
|
|
}
|
|
}
|
|
|
|
static void DontDeleteProtosPatch() {
|
|
if (IniReader::GetIntDefaultConfig("Debugging", "DontDeleteProtos", 0)) {
|
|
dlog("Applying permanent protos patch.", DL_INIT);
|
|
SafeWrite8(0x48007E, CodeType::JumpShort);
|
|
dlogr(" Done", DL_INIT);
|
|
}
|
|
}
|
|
|
|
/*
|
|
void CheckTimerResolution() {
|
|
DWORD ticksList[50];
|
|
DWORD old_ticks = GetTickCount();
|
|
for (size_t i = 0; i < 50;) {
|
|
DWORD ticks = GetTickCount();
|
|
if (ticks != old_ticks) {
|
|
old_ticks = ticks;
|
|
ticksList[i++] = ticks;
|
|
}
|
|
}
|
|
int min = 100, max = 0;
|
|
for (size_t i = 0; i < 49; i++) {
|
|
int ms = ticksList[i + 1] - ticksList[i];
|
|
if (ms < min) min = ms;
|
|
if (ms > max) max = ms;
|
|
}
|
|
fo::func::debug_printf("System timer resolution: %d - %d ms.\n", min, max);
|
|
}
|
|
*/
|
|
|
|
void DebugEditor::OnAfterGameInit() {
|
|
if (mapGridToggleKey) {
|
|
fo::var::setInt(FO_VAR_tile_refresh) = fo::funcoffs::refresh_mapper_;
|
|
//fo::var::setInt(FO_VAR_map_scroll_refresh) = fo::funcoffs::map_scroll_refresh_mapper_;
|
|
}
|
|
}
|
|
|
|
void DebugEditor::init() {
|
|
DebugModePatch();
|
|
|
|
// Notifies and prints a debug message about a corrupted proto file to debug.log
|
|
MakeCall(0x4A1D73, proto_load_pid_hack, 6);
|
|
|
|
// Prints a debug message about a missing combat object to debug.log
|
|
const DWORD combatLoadNullObjAddr[] = {0x421146, 0x421189, 0x4211CC};
|
|
MakeCalls(combat_load_hack, combatLoadNullObjAddr);
|
|
if (!isDebug) {
|
|
const DWORD combatLoadObjRetAddr[] = {0x42114B, 0x42118E, 0x4211D1};
|
|
SafeWriteBatch<DWORD>(0x909008EB, combatLoadObjRetAddr); // jmp $+8
|
|
}
|
|
|
|
if (!isDebug) return;
|
|
DontDeleteProtosPatch();
|
|
|
|
debugEditorKey = IniReader::GetConfigInt("Input", "DebugEditorKey", 0);
|
|
mapGridToggleKey = IniReader::GetIntDefaultConfig("Debugging", "MapGridToggleKey", 0);
|
|
}
|
|
|
|
void DebugEditor::KeyPressedHook(DWORD scanCode, bool pressed) {
|
|
if (debugEditorKey && scanCode == debugEditorKey && pressed && IsGameLoaded()) {
|
|
RunDebugEditor();
|
|
}
|
|
|
|
if (mapGridToggleKey && scanCode == mapGridToggleKey && pressed && IsGameLoaded()) {
|
|
__asm call fo::funcoffs::grid_toggle_;
|
|
fo::func::tile_refresh_display();
|
|
}
|
|
}
|
|
|
|
}
|