Files
sfall/sfall/Modules/Tiles.cpp
T
NovaRain c855c6db19 Fixed and improved AllowLargeTiles option (#367)
Removed the unspent skill points limit.
2021-04-18 13:09:06 +08:00

317 lines
8.3 KiB
C++

/*
* sfall
* Copyright (C) 2008, 2009, 2010, 2012 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 <math.h>
#include <stdio.h>
#include "..\main.h"
#include "..\FalloutEngine\Fallout2.h"
#include "..\Utils.h"
//#include "FileSystem.h"
#include "Tiles.h"
namespace sfall
{
typedef int (__stdcall *functype)();
static const DWORD Tiles_0E[] = {
0x484255, 0x48429D, 0x484377, 0x484385, 0x48A897, 0x48A89A, 0x4B2231,
0x4B2374, 0x4B2381, 0x4B2480, 0x4B248D, 0x4B2A7C, 0x4B2BDA,
};
static const DWORD Tiles_3F[] = {
0x41875D, 0x4839E6, 0x483A2F, 0x484380, 0x48A803, 0x48A9F2, 0x48C96C,
0x48C99F, 0x48C9D2, 0x4B2247, 0x4B2334, 0x4B2440, 0x4B2AB9, 0x4B2B8F,
0x4B2BF4,
};
static const DWORD Tiles_40[] = {
0x48C941, 0x48C955, 0x48CA5F, 0x48CA71, 0x48CA9B, 0x48CAAD, 0x48CADB,
0x48CAEB, 0x48CAF7, 0x48CB20, 0x48CB32, 0x48CB61,
};
static const DWORD Tiles_C0[] = {
0x48424E, 0x484296, 0x484372, 0x48A88D, 0x48A892, 0x4B222C, 0x4B236F,
0x4B247B, 0x4B2A77, 0x4B2BD5,
};
#pragma pack(push, 1)
struct TilesData {
short tile[2];
};
#pragma pack(pop)
struct OverrideEntry {
DWORD xTiles;
DWORD yTiles;
DWORD replacementID;
OverrideEntry(DWORD xtiles, DWORD ytiles, DWORD repid)
: xTiles(xtiles), yTiles(ytiles), replacementID(repid) {
}
};
static OverrideEntry** overrides = nullptr;
static DWORD origTileCount = 0;
static DWORD tileMode;
static BYTE* mask;
static bool LoadMask() {
fo::DbFile* file = fo::func::db_fopen("art\\tiles\\gridmask.frm", "rb"); // same as grid000.frm from HRP
if (!file) {
dlogr("AllowLargeTiles: Failed to open gridmask.frm file.", DL_INIT);
return false;
}
mask = new BYTE[80 * 36];
fo::func::db_fseek(file, 0x4A, SEEK_SET);
fo::func::db_freadByteCount(file, mask, 80 * 36);
fo::func::db_fclose(file);
return true;
}
static int ProcessTile(fo::Art* tiles, int tile, int listPos) {
char buf[32] = "art\\tiles\\";
strncpy_s(&buf[10], 22, &tiles->names[13 * tile], _TRUNCATE);
fo::DbFile* artFile = fo::func::db_fopen(buf, "rb");
if (!artFile) return 0;
fo::func::db_fseek(artFile, 0x3E, SEEK_SET); // frameData
WORD width;
fo::func::db_freadShort(artFile, &width);
if (width <= 80) goto exit;
WORD height;
fo::func::db_freadShort(artFile, &height);
if (height < 36) goto exit;
float newWidth = (float)(width - (width % 8));
float newHeight = (float)(height - (height % 12));
// Number of tiles horizontally and vertically
// the calculation is unstable if the large tile is not proportional to the size of 80x36 (aspect ratio)
int xSize = std::lroundf(((newWidth / 32.0f) - (newHeight / 24.0f)) - 0.01f);
int ySize = std::lroundf(((newHeight / 16.0f) - (newWidth / 64.0f)) - 0.01f);
if (xSize <= 0 || ySize <= 0) goto exit;
long bytes = width * height;
BYTE* pixelData = new BYTE[bytes];
// Read pixels data
if (fo::func::db_fseek(artFile, 0x4A, SEEK_SET) ||
fo::func::db_fread(pixelData, 1, bytes, artFile) != bytes)
{
delete[] pixelData;
exit:
fo::func::db_fclose(artFile);
return 0;
}
long listID = listPos - tiles->total;
fo::FrmFile frame;
fo::func::db_fseek(artFile, 0, SEEK_SET);
fo::func::db_freadByteCount(artFile, (BYTE*)&frame, 74);
frame.height = ByteSwapW(36);
frame.width = ByteSwapW(80);
frame.frameSize = ByteSwapD(80 * 36);
frame.frameAreaSize = ByteSwapD(80 * 36 + 12);
for (int y = 0; y < ySize; y++) {
for (int x = 0; x < xSize; x++) {
// offset relative to the top-left corner
int xOffset = x * 48 + (ySize - (y + 1)) * 32;
int yOffset = height - (36 + x * 12 + y * 24);
for (int pY = 0; pY < 36; pY++) {
for (int pX = 0; pX < 80; pX++) {
int point = (80 * pY) + pX;
if (mask[point]) {
frame.pixels[point] = pixelData[(width * (yOffset + pY)) + xOffset + pX];
} else {
frame.pixels[point] = 0;
}
}
}
sprintf_s(&buf[10], 22, "zzz%04d.frm", listID++);
//FScreateFromData(buf, &frame, sizeof(frame));
fo::DbFile* file = fo::func::db_fopen(buf, "wb");
fo::func::db_fwriteByteCount(file, (BYTE*)&frame, sizeof(frame));
fo::func::db_fclose(file);
}
}
overrides[tile] = new OverrideEntry(xSize, ySize, listPos);
fo::func::db_fclose(artFile);
delete[] pixelData;
return xSize * ySize; // number of tiles added
}
static const functype art_init = (functype)fo::funcoffs::art_init_;
static int __stdcall ArtInitHook() {
if (art_init()) return -1;
if (!LoadMask()) return 0;
fo::Art* tiles = &fo::var::art[4];
long listpos = origTileCount = tiles->total;
overrides = new OverrideEntry*[listpos]();
if (tileMode == 2) {
fo::DbFile* file = fo::func::db_fopen("art\\tiles\\xltiles.lst", "rt");
if (!file) return 0;
char buf[32];
DWORD id;
char* comment;
while (fo::func::db_fgets(buf, 31, file) > 0) {
if (comment = strchr(buf, ';')) *comment = 0;
id = atoi(buf);
if (id > 1) listpos += ProcessTile(tiles, id, listpos);
}
fo::func::db_fclose(file);
} else {
for (int i = 2; i < tiles->total; i++) listpos += ProcessTile(tiles, i, listpos);
}
if (listpos != tiles->total) {
tiles->names = (char*)fo::func::mem_realloc(tiles->names, listpos * 13);
for (long i = tiles->total; i < listpos; i++) {
sprintf_s(&tiles->names[i * 13], 12, "zzz%04d.frm", i - tiles->total);
}
tiles->total = listpos;
}
delete[] mask;
return 0;
}
static void __declspec(naked) iso_init_hook() {
__asm {
mov ebx, dword ptr ds:[FO_VAR_read_callback];
xor eax, eax;
mov dword ptr ds:[FO_VAR_read_callback], eax;
call ArtInitHook;
mov dword ptr ds:[FO_VAR_read_callback], ebx;
retn;
}
}
static long __fastcall SquareLoadCheck(TilesData* data) {
if (!overrides) return 0;
for (DWORD y = 0; y < 100; y++) {
for (DWORD x = 0; x < 100; x++) {
for (DWORD z = 0; z < 2; z++) {
DWORD tile = data[y * 100 + x].tile[z];
if (tile > 1 && tile < origTileCount && overrides[tile]) {
DWORD newtile = overrides[tile]->replacementID - 1;
for (DWORD y2 = 0; y2 < overrides[tile]->yTiles; y2++) {
for (DWORD x2 = 0; x2 < overrides[tile]->xTiles; x2++) {
newtile++;
if (x - x2 < 0 || y - y2 < 0) continue;
data[(y - y2) * 100 + x - x2].tile[z] = (short)newtile;
}
}
}
}
}
}
return 0; // don't delete
}
static void __declspec(naked) square_load_hook() {
__asm {
mov ecx, edx;
call fo::funcoffs::db_freadIntCount_;
test eax, eax;
jnz end;
jmp SquareLoadCheck; // ecx - data
end:
retn;
}
}
static void __declspec(naked) art_id_hack() {
using namespace fo;
__asm {
cmp esi, (OBJ_TYPE_TILE << 24); // 0x4000000
jne end;
and eax, 0x3FFF;
retn;
end:
and eax, 0x0FFF;
retn;
}
}
static void __declspec(naked) art_get_name_hack() {
using namespace fo;
__asm {
sar eax, 24;
cmp eax, OBJ_TYPE_TILE;
jne end;
mov esi, edx;
mov ebp, edx;
and esi, 0x3FFF;
and ebp, 0xC000;
sar ebp, 0x0E;
end:
test esi, esi;
retn;
}
}
void Tiles::init() {
if (tileMode = IniReader::GetConfigInt("Misc", "AllowLargeTiles", 0)) {
dlog("Applying allow large tiles patch.", DL_INIT);
HookCall(0x481D72, iso_init_hook);
HookCall(0x48434C, square_load_hook);
dlogr(" Done", DL_INIT);
}
if (IniReader::GetConfigInt("Misc", "MoreTiles", 0)) {
dlog("Applying tile FRM limit patch.", DL_INIT);
MakeCall(0x419D46, art_id_hack);
MakeCall(0x419479, art_get_name_hack);
SafeWriteBatch<BYTE>(0x0E, Tiles_0E);
SafeWriteBatch<BYTE>(0x3F, Tiles_3F);
SafeWriteBatch<BYTE>(0x40, Tiles_40);
SafeWriteBatch<BYTE>(0xC0, Tiles_C0);
if (hrpVersionValid) { // Check HRP 4.1.8
SafeWrite8(HRPAddress(0x1000E1C0), 0x40);
SafeWrite8(HRPAddress(0x1000E1DA), 0x3F);
}
dlogr(" Done", DL_INIT);
}
}
void Tiles::exit() {
if (overrides) {
for (size_t i = 0; i < origTileCount; i++) delete overrides[i]; // free OverrideEntry
delete[] overrides;
}
}
}