Files
sfall/sfall/DebugEditor.cpp
T
NovaRain 8d813a952e Fixed and improved the behavior of nested timer events in global scripts
Added a fix to prevent the execution of critter_p_proc and game events
when playing movies.

Added a debug message about a corrupted proto file.

Some minor code/document edits.
2020-08-14 21:31:06 +08:00

439 lines
11 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.h"
#include "Arrays.h"
#include "Graphics.h"
#include "InputFuncs.h"
#include "LoadGameHook.h"
#include "ScriptExtender.h"
#include "DebugEditor.h"
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 = 0;
struct sArray {
DWORD id;
long isMap;
long size;
long flag;
};
static void DEGameWinRedraw() {
if (GraphicsMode != 0) ProcessBk();
}
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) {
*(DWORD*)_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++) {
TGameObj* obj = ObjFindFirstAtTile(elv, tile);
while (obj) {
if (obj->Type() == OBJ_TYPE_CRITTER) {
vec.push_back(reinterpret_cast<DWORD*>(obj));
}
obj = ObjFindNextAtTile();
}
}
}
int numCritters = vec.size();
int numGlobals = *ptr_num_game_global_vars;
int numMapVars = *ptr_num_map_global_vars;
int numSGlobals = GetNumGlobals();
int numArrays = GetNumArrays();
InternalSend(s, &numGlobals, 4);
InternalSend(s, &numMapVars, 4);
InternalSend(s, &numSGlobals, 4);
InternalSend(s, &numArrays, 4);
InternalSend(s, &numCritters, 4);
sGlobalVar* sglobals=new sGlobalVar[numSGlobals];
GetGlobals(sglobals);
sArray* arrays = new sArray[numArrays];
GetArrays((int*)arrays);
InternalSend(s, reinterpret_cast<void*>(*ptr_game_global_vars), 4 * numGlobals);
InternalSend(s, reinterpret_cast<void*>(*ptr_map_global_vars), 4 * numMapVars);
InternalSend(s, sglobals, sizeof(sGlobalVar) * 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);
*ptr_game_global_vars[id] = val;
break;
case CODE_SET_MAPVAR:
InternalRecv(s, &id, 4);
InternalRecv(s, &val, 4);
*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
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];
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);
DESetArray(arrays[id].id, nullptr, data);
delete[] data;
}
break;
case CODE_GET_LOCVARS:
{
InternalRecv(s, &id, 4); // sid
val = GetScriptLocalVars(id);
InternalSend(s, &val, 4);
if (val) {
std::vector<int> values(val);
long varVal;
for (int i = 0; i < val; i++) {
ScrGetLocalVar(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++) {
ScrSetLocalVar(id, i, values[i]);
}
}
break;
}
DEGameWinRedraw();
}
SetGlobals(sglobals);
delete[] sglobals;
delete[] arrays;
FlushInputBuffer();
*(DWORD*)_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("FalloutClient.exe", "FalloutClient.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;
}
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 char* artDbgMsg = "\nERROR: File not found: %s\n";
__asm {
test edi, edi;
jz artNotExist;
retn;
artNotExist:
mov edx, [esp + 0x124 - 0x1C + 4]; // filename
push edx;
push artDbgMsg;
call 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 sprintf_;
add esp, 20;
lea eax, [esp + 4];
jmp display_print_;
}
}
static void __declspec(naked) proto_load_pid_hack() {
static 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 debug_printf_;
add esp, 8;
mov eax, 0x500494; // 'iisxxxx1'
jmp gsound_play_sfx_file_;
}
}
static void __declspec(naked) win_debug_hook() {
__asm {
call debug_log_;
xor eax, eax;
cmp ds:[_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 fprintf_;
add esp, 12;
retn;
}
}
static void DebugModePatch() {
int dbgMode = iniGetInt("Debugging", "DebugMode", 0, ddrawIniDef);
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 (iniGetInt("Debugging", "HideObjIsNullMsg", 0, ddrawIniDef)) {
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);
// 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_
dlogr(" Done", DL_INIT);
}
}
static void DontDeleteProtosPatch() {
if (iniGetInt("Debugging", "DontDeleteProtos", 0, ddrawIniDef)) {
dlog("Applying permanent protos patch.", DL_INIT);
SafeWrite8(0x48007E, CODETYPE_JumpShort);
dlogr(" Done", DL_INIT);
}
}
void DebugEditorInit() {
DebugModePatch();
// Notifies and prints a debug message about a corrupted proto file to debug.log
MakeCall(0x4A1D73, proto_load_pid_hack, 6);
if (!isDebug) return;
DontDeleteProtosPatch();
debugEditorKey = GetConfigInt("Input", "DebugEditorKey", 0);
}
void DebugEditorKeyPressedHook(DWORD scanCode, bool pressed) {
if (debugEditorKey != 0 && scanCode == debugEditorKey && pressed && IsGameLoaded()) {
RunDebugEditor();
}
}